Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
412dd61ad0 | ||
|
|
b01ccf88b0 | ||
|
|
5ac4256972 | ||
|
|
e240aaae3e | ||
|
|
8b7cec9a7a | ||
|
|
fd051da397 | ||
|
|
eb8202b0bc | ||
|
|
b6e1166c48 | ||
|
|
2f3db01d0a | ||
|
|
908b886bcc | ||
|
|
77599a68cc | ||
|
|
35fbe4ac66 | ||
|
|
2d8645172b | ||
|
|
b38c73c511 |
@@ -0,0 +1,154 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Build & Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.TEKTON_RELEASE_TOKEN }}
|
||||||
|
TAG_NAME: ${{ github.ref_name }}
|
||||||
|
steps:
|
||||||
|
- name: Install tools
|
||||||
|
run: apt-get update -qq && apt-get install -y -qq curl unzip zip
|
||||||
|
|
||||||
|
- name: Checkout Code
|
||||||
|
run: |
|
||||||
|
git config --global credential.helper store
|
||||||
|
echo "https://god:${{ secrets.TEKTON_RELEASE_TOKEN }}@git.klud.top" > ~/.git-credentials
|
||||||
|
git clone https://git.klud.top/danchie/tekton.git .
|
||||||
|
git checkout $TAG_NAME
|
||||||
|
|
||||||
|
- name: Setup Godot (Cached)
|
||||||
|
run: |
|
||||||
|
apt-get update -qq && apt-get install -y -qq unzip curl
|
||||||
|
if [ ! -f /cache/godot_4.6.3 ]; then
|
||||||
|
echo "Downloading Godot 4.6.3..."
|
||||||
|
curl -sL -o /tmp/godot.zip "https://github.com/godotengine/godot-builds/releases/download/4.6.3-stable/Godot_v4.6.3-stable_linux.x86_64.zip"
|
||||||
|
unzip -q -o /tmp/godot.zip -d /cache/
|
||||||
|
mv /cache/Godot_v4.6.3-stable_linux.x86_64 /cache/godot_4.6.3
|
||||||
|
fi
|
||||||
|
cp /cache/godot_4.6.3 /usr/local/bin/godot
|
||||||
|
chmod +x /usr/local/bin/godot
|
||||||
|
mkdir -p ~/.local/share/godot/export_templates/4.6.3.stable
|
||||||
|
if [ ! -f /cache/Godot_v4.6.3-stable_export_templates.tpz ]; then
|
||||||
|
echo "Downloading templates..."
|
||||||
|
curl -sL -o /cache/Godot_v4.6.3-stable_export_templates.tpz \
|
||||||
|
"https://github.com/godotengine/godot-builds/releases/download/4.6.3-stable/Godot_v4.6.3-stable_export_templates.tpz"
|
||||||
|
fi
|
||||||
|
cd ~/.local/share/godot/export_templates/4.6.3.stable
|
||||||
|
unzip -q -o /cache/Godot_v4.6.3-stable_export_templates.tpz
|
||||||
|
mv templates/* .
|
||||||
|
rm -rf templates
|
||||||
|
cd $GITHUB_WORKSPACE
|
||||||
|
mkdir -p build
|
||||||
|
|
||||||
|
- name: Export Windows
|
||||||
|
run: |
|
||||||
|
mkdir -p build/windows
|
||||||
|
cp addons/godotsteam/libgodotsteam* build/windows/ 2>/dev/null || true
|
||||||
|
godot --headless --export-release "Windows Desktop" build/windows/tekton_armageddon_windows.exe || true
|
||||||
|
cd build/windows && zip -r ../tekton_armageddon_windows_${TAG_NAME}.zip .
|
||||||
|
|
||||||
|
- name: Export Linux
|
||||||
|
run: |
|
||||||
|
mkdir -p build/linux
|
||||||
|
godot --headless --export-release "Linux/X11" build/linux/tekton_armageddon_linux.x86_64 || true
|
||||||
|
cd build/linux && zip -r ../tekton_armageddon_linux_${TAG_NAME}.zip .
|
||||||
|
|
||||||
|
- name: Export macOS
|
||||||
|
run: |
|
||||||
|
mkdir -p build/macos
|
||||||
|
godot --headless --export-release "macOS" build/macos/tekton_armageddon_macos.zip 2>&1 | tail -5 || true
|
||||||
|
if [ -f build/macos/tekton_armageddon_macos.zip ]; then
|
||||||
|
mv build/macos/tekton_armageddon_macos.zip build/tekton_armageddon_macos_${TAG_NAME}.zip
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Extract changelog
|
||||||
|
run: |
|
||||||
|
# Extract changelog for this tag version from CHANGELOG_DRAFT.md
|
||||||
|
V="${TAG_NAME#v}"
|
||||||
|
BODY=$(awk -v ver="[$V]" '
|
||||||
|
/^## / { if (found) exit }
|
||||||
|
/^## / && index($0, ver) { found=1; next }
|
||||||
|
found { print }
|
||||||
|
' CHANGELOG_DRAFT.md | sed 's/^ *//')
|
||||||
|
echo "CHANGELOG_BODY<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$BODY" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create Gitea Release
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
API="https://git.klud.top/api/v1/repos/danchie/tekton/releases"
|
||||||
|
TAG="$TAG_NAME"
|
||||||
|
echo "Checking existing release for $TAG..."
|
||||||
|
RELEASE_JSON=$(curl -s -H "Authorization: token $GITEA_TOKEN" "$API/tags/$TAG" 2>/dev/null || echo "")
|
||||||
|
RELEASE_ID=$(echo "$RELEASE_JSON" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*' || true)
|
||||||
|
if [ -z "$RELEASE_ID" ]; then
|
||||||
|
echo "Creating new release for $TAG..."
|
||||||
|
# Escape body for JSON
|
||||||
|
BODY_ESCAPED=$(echo "$CHANGELOG_BODY" | python3 -c "import json,sys; print(json.dumps(sys.stdin.read().strip()))" 2>/dev/null || echo '""')
|
||||||
|
RELEASE_JSON=$(curl -s -X POST \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":$BODY_ESCAPED,\"draft\":true}" \
|
||||||
|
"$API")
|
||||||
|
echo "API response: $RELEASE_JSON"
|
||||||
|
RELEASE_ID=$(echo "$RELEASE_JSON" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*')
|
||||||
|
if [ -z "$RELEASE_ID" ]; then
|
||||||
|
echo "FATAL: Could not create release"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "release_id=$RELEASE_ID"
|
||||||
|
echo "$RELEASE_ID" > /tmp/release_id.txt
|
||||||
|
|
||||||
|
- name: Upload Windows asset
|
||||||
|
run: |
|
||||||
|
RELEASE_ID=$(cat /tmp/release_id.txt)
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: multipart/form-data" \
|
||||||
|
-F "attachment=@build/tekton_armageddon_windows_${TAG_NAME}.zip" \
|
||||||
|
"https://git.klud.top/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
|
||||||
|
echo "Windows uploaded"
|
||||||
|
|
||||||
|
- name: Upload Linux asset
|
||||||
|
run: |
|
||||||
|
RELEASE_ID=$(cat /tmp/release_id.txt)
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: multipart/form-data" \
|
||||||
|
-F "attachment=@build/tekton_armageddon_linux_${TAG_NAME}.zip" \
|
||||||
|
"https://git.klud.top/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
|
||||||
|
echo "Linux uploaded"
|
||||||
|
|
||||||
|
- name: Upload macOS asset
|
||||||
|
run: |
|
||||||
|
RELEASE_ID=$(cat /tmp/release_id.txt)
|
||||||
|
if [ -f "build/tekton_armageddon_macos_${TAG_NAME}.zip" ]; then
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: multipart/form-data" \
|
||||||
|
-F "attachment=@build/tekton_armageddon_macos_${TAG_NAME}.zip" \
|
||||||
|
"https://git.klud.top/api/v1/repos/danchie/tekton/releases/$RELEASE_ID/assets"
|
||||||
|
echo "macOS uploaded"
|
||||||
|
else
|
||||||
|
echo "macOS asset not built, skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Publish release
|
||||||
|
run: |
|
||||||
|
RELEASE_ID=$(cat /tmp/release_id.txt)
|
||||||
|
curl -s -X PATCH \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"draft":false}' \
|
||||||
|
"https://git.klud.top/api/v1/repos/danchie/tekton/releases/$RELEASE_ID"
|
||||||
|
echo "Published: https://git.klud.top/danchie/tekton/releases/tag/$TAG_NAME"
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
name: Deploy Patch
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Patch version (e.g., 2.4.4)"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
notes:
|
||||||
|
description: "Release notes"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
name: Build & Deploy Patch
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository (shallow)
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.TEKTON_RELEASE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
git clone --depth 1 https://god:$GITEA_TOKEN@git.klud.top/danchie/tekton.git .
|
||||||
|
git config user.name "god"
|
||||||
|
git config user.email "god@noreply.git.klud.top"
|
||||||
|
|
||||||
|
- name: Setup Godot (Cached)
|
||||||
|
run: |
|
||||||
|
if [ ! -f /cache/godot_4.6.3 ]; then
|
||||||
|
echo "Downloading Godot 4.6.3..."
|
||||||
|
apt-get update -qq && apt-get install -y -qq unzip curl
|
||||||
|
curl -sL -o /tmp/godot.zip "https://github.com/godotengine/godot-builds/releases/download/4.6.3-stable/Godot_v4.6.3-stable_linux.x86_64.zip"
|
||||||
|
unzip -q -o /tmp/godot.zip -d /cache/
|
||||||
|
mv /cache/Godot_v4.6.3-stable_linux.x86_64 /cache/godot_4.6.3
|
||||||
|
fi
|
||||||
|
cp /cache/godot_4.6.3 /usr/local/bin/godot
|
||||||
|
chmod +x /usr/local/bin/godot
|
||||||
|
mkdir -p build
|
||||||
|
|
||||||
|
- name: Generate version.json & bump version
|
||||||
|
env:
|
||||||
|
PATCH_VERSION: ${{ github.event.inputs.version }}
|
||||||
|
PATCH_NOTES: ${{ github.event.inputs.notes }}
|
||||||
|
run: |
|
||||||
|
python3 tools/generate_version_json.py --skip-changelog
|
||||||
|
|
||||||
|
- name: Export patch PCK
|
||||||
|
run: |
|
||||||
|
godot --headless --export-pack "Windows Desktop" build/patch.pck 2>&1 | tail -5
|
||||||
|
|
||||||
|
- name: Push to patches branch
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.TEKTON_RELEASE_TOKEN }}
|
||||||
|
run: |
|
||||||
|
mkdir -p patch-deploy
|
||||||
|
cp build/patch.pck patch-deploy/
|
||||||
|
cp assets/data/version.json patch-deploy/
|
||||||
|
cd patch-deploy
|
||||||
|
git init
|
||||||
|
git config user.name "god"
|
||||||
|
git config user.email "god@noreply.git.klud.top"
|
||||||
|
git remote add origin https://god:$GITEA_TOKEN@git.klud.top/danchie/tekton.git
|
||||||
|
git checkout -b patches
|
||||||
|
git add .
|
||||||
|
git commit -m "patch ${{ github.event.inputs.version }}"
|
||||||
|
git push -f origin patches
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
@@ -1,81 +0,0 @@
|
|||||||
name: Build and Release Patch PCK
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'patch-release'
|
|
||||||
paths:
|
|
||||||
- 'scripts/**'
|
|
||||||
- 'scenes/**'
|
|
||||||
- 'assets/**'
|
|
||||||
- 'CHANGELOG_DRAFT.md'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-deploy-patch:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Source Code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
# ── 1. Auto-generate version.json from CHANGELOG_DRAFT.md ────────────
|
|
||||||
- name: Generate Version JSON & Bump Version
|
|
||||||
run: python3 tools/generate_version_json.py
|
|
||||||
|
|
||||||
# ── 2. Commit bumped files back to the repo ───────────────────────────
|
|
||||||
- name: Commit Version Bump
|
|
||||||
run: |
|
|
||||||
git config user.name "PatchBot"
|
|
||||||
git config user.email "action@github.com"
|
|
||||||
git add assets/data/version.json project.godot CHANGELOG_DRAFT.md
|
|
||||||
git diff --staged --quiet || git commit -m "[AUTO] Version bump & changelog update"
|
|
||||||
git push
|
|
||||||
|
|
||||||
# ── 3. Detect changed files for patch PCK ────────────────────────────
|
|
||||||
- name: Generate Changed Files List
|
|
||||||
run: |
|
|
||||||
git diff --name-only HEAD^ HEAD -- 'scripts/**' 'scenes/**' 'assets/**' > changed_files.txt
|
|
||||||
echo "Files to patch:"
|
|
||||||
cat changed_files.txt
|
|
||||||
|
|
||||||
# ── 4. Build patch.pck ────────────────────────────────────────────────
|
|
||||||
- name: Setup Godot
|
|
||||||
uses: chickensoft-games/setup-godot@v1
|
|
||||||
with:
|
|
||||||
version: '4.3.0'
|
|
||||||
use-dotnet: false
|
|
||||||
|
|
||||||
- name: Run Build Patch Script
|
|
||||||
run: godot --headless -s tools/build_patch.gd
|
|
||||||
|
|
||||||
# ── 5. Push patch.pck to public repo ─────────────────────────────────
|
|
||||||
- name: Push patch.pck to Public Repository
|
|
||||||
uses: dmnemec/copy_file_to_another_repo_action@main
|
|
||||||
env:
|
|
||||||
API_TOKEN_GITHUB: ${{ secrets.PUBLIC_REPO_PAT }}
|
|
||||||
with:
|
|
||||||
source_file: 'patch.pck'
|
|
||||||
destination_repo: '${{ github.actor }}/tekton-updates'
|
|
||||||
destination_folder: 'latest'
|
|
||||||
user_email: 'action@github.com'
|
|
||||||
user_name: 'PatchBot'
|
|
||||||
commit_message: '[AUTO] Pushed new patch.pck'
|
|
||||||
|
|
||||||
# ── 6. Push version.json to public repo ──────────────────────────────
|
|
||||||
- name: Push version.json to Public Repository
|
|
||||||
uses: dmnemec/copy_file_to_another_repo_action@main
|
|
||||||
env:
|
|
||||||
API_TOKEN_GITHUB: ${{ secrets.PUBLIC_REPO_PAT }}
|
|
||||||
with:
|
|
||||||
source_file: 'assets/data/version.json'
|
|
||||||
destination_repo: '${{ github.actor }}/tekton-updates'
|
|
||||||
destination_folder: 'latest'
|
|
||||||
user_email: 'action@github.com'
|
|
||||||
user_name: 'PatchBot'
|
|
||||||
commit_message: '[AUTO] Pushed new version.json'
|
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
# Change Log
|
||||||
|
All notable changes to this project are documented below.
|
||||||
|
|
||||||
|
The format is based on [keep a changelog](http://keepachangelog.com/) and this project uses [semantic versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [3.3.1] - 2023-04-17
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix arguments for HTTPRequest.request() for beta17
|
||||||
|
- Fix typehints for enums in Godot 4.0-rc1
|
||||||
|
- Fix type check and typehint for Godot 4.0-rc3
|
||||||
|
- Fix null byte array error in GodotHttpAdapter for C#
|
||||||
|
|
||||||
|
## [3.3.0] - 2023-01-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for subscription validation APIs that were added in Nakama v3.13.0
|
||||||
|
- Add support for sending events
|
||||||
|
- Allow disabling threads for making HTTP requests
|
||||||
|
- Add support for delete_account_sync() and other API changes that were added in Nakama v3.15.0
|
||||||
|
|
||||||
|
## [3.2.0] - 2022-08-30
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix NakamaSocket.add_matchmaker_party_async() and the tests for it
|
||||||
|
- Fix MatchData.op_code type in schema to TYPE_INT
|
||||||
|
- Fix circular reference in Nakama singleton to itself
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for receiving binary data in "NakamaRTAPI.MatchState"
|
||||||
|
- Add support for sending and receiving binary data in "NakamaRTAPI.PartyData"
|
||||||
|
- Add NakamaMultiplayerBridge to integrate with Godot's High-Level Multiplayer API
|
||||||
|
|
||||||
|
## [3.1.0] - 2022-04-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Expose the "seen_before" property on "NakamaAPI.ApiValidatedPurchase"
|
||||||
|
- Add support for creating match by name
|
||||||
|
- Add support for "count_multple" on "NakamaSocket.add_matchmaker_async()" and "NakamaSocket.add_matchmaker_party_async()"
|
||||||
|
- Add C# support classes to better integrate the .NET client with the Mono version of Godot, allowing HTML5 exports to work
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix receiving "NakamaRTAPI.PartyClose" message
|
||||||
|
- Fix sending and receiving of PartyData
|
||||||
|
|
||||||
|
## [3.0.0] - 2022-03-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add realtime party support.
|
||||||
|
- Add purchase validation functions.
|
||||||
|
- Add Apple authentication functions.
|
||||||
|
- Add "demote_group_users_async" function.
|
||||||
|
- A session can be refreshed on demand with "session_refresh_async" method.
|
||||||
|
- Session and/or refresh tokens can now be invalidated with a client logout.
|
||||||
|
- The client now supports session auto-refresh using refresh tokens. This is enabled by default.
|
||||||
|
- The client now supports auto-retrying failed request due to network error. This is enabled by defulut.
|
||||||
|
- The client now support cancelling requests in-flight via "client.cancel_request".
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix Dictionary serialization (e.g. "NakamaSocket.add_matchmaker_async" "p_string_props" and "p_numeric_props").
|
||||||
|
- Pass join metadata onwards into match join message.
|
||||||
|
- Don't stop processing messages when the game is paused.
|
||||||
|
- Fix "rpc_async", "rpc_async_with_key". Now uses GET request only if no payload is passed.
|
||||||
|
- Fix client errors parsing in Nakama 3.x
|
||||||
|
- Make it possible to omit the label and query on NakamaClient.list_matches_async().
|
||||||
|
|
||||||
|
### Backwards incompatible changes
|
||||||
|
|
||||||
|
- The "received_error" signal on "NakamaSocket" is now emited with an "NakamaRTAPI.Error" object received from the server.
|
||||||
|
Previously, it was emitted with an integer error code when the socket failed to connect.
|
||||||
|
If you have old code using the "received_error" signal, you can switch to the new "connection_error" signal, which was added to replace it.
|
||||||
|
|
||||||
|
## [2.1.0] - 2020-08-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add an optional log level parameter to "Nakama.create_client".
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Update variable definitions to new gdscript variable controls.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix "add_friends_async" should have its "id" field input as optional.
|
||||||
|
- Fix "add_matchmaker_async" and "MatchmakerAdd" parameter assignment.
|
||||||
|
- Fix missing "presence" property in NakamaRTAPI.MatchData.
|
||||||
|
- Fix NakamaSocket not emitting "received_error" correctly.
|
||||||
|
- Fix "DEFAULT_LOG_LEVEL" in Nakama.gd not doing anything.
|
||||||
|
|
||||||
|
## [2.0.0] - 2020-04-02
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Decode base64 data in "MatchData". (breaks compat)
|
||||||
|
- Add "FacebookInstantGame" endpoints (link/unlink/authenticate).
|
||||||
|
- GDScript-style comments (removing all XML tags).
|
||||||
|
- Add "list_storage_objects_async" "p_user_id" parameter to allow listing user(s) objects.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix encoding of "op_code" in "MatchDataSend" and marshalling of "NakamaSocket.send_match_state_[raw_]async".
|
||||||
|
- Fix parsing of "MatchmakerMatched" messages when no token is specified.
|
||||||
|
- Disable "HTTPRequest.use_threads" in HTML5 exports.
|
||||||
|
- "NakamaSession.is_expired" returned reversed result.
|
||||||
|
- Fix "NakamaClient.update_account_async" to allow updating account without username change.
|
||||||
|
- Fix "NakamaClient.update_group_async" to allow updating group without name change.
|
||||||
|
- Fix "HTTPAdapter._send_async" error catching for some edge cases.
|
||||||
|
- Fix "NakamaClient.send_rpc_async" with empty payload (will send empty string now).
|
||||||
|
- Fix "NakamaRTAPI.Status" parsing.
|
||||||
|
- Fix "NakamaClient" "list_leaderboard_records_around_owner_async" and "list_leaderboard_records_async" parameter order. (breaks compat)
|
||||||
|
- Rename "NakamaClient.JoinTournamentAsync" to "join_tournament_async" for consistent naming.
|
||||||
|
- Update all "p_limit" parameters default in "NakamaClient" to "10".
|
||||||
|
- Fix "NakamaRTAPI.Stream" parsing.
|
||||||
|
|
||||||
|
## [1.0.0] - 2020-01-28
|
||||||
|
### Added
|
||||||
|
- Initial public release.
|
||||||
|
- Client API implementation.
|
||||||
|
- Realtime Socket implementation.
|
||||||
|
- Helper singleton.
|
||||||
|
- Setup instructions.
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://dhw5mg25iw5dr"
|
||||||
|
path="res://.godot/imported/dasher_getting_hit.glb-c78f6332496d820acfe217a7dee1a171.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/characters/dashers/dasher_getting_hit.glb"
|
||||||
|
dest_files=["res://.godot/imported/dasher_getting_hit.glb-c78f6332496d820acfe217a7dee1a171.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://du6f10iq0vup5"
|
||||||
|
path="res://.godot/imported/dasher_hit.glb-30828368c8a512ea3a0f74180da77e8d.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/characters/dashers/dasher_hit.glb"
|
||||||
|
dest_files=["res://.godot/imported/dasher_hit.glb-30828368c8a512ea3a0f74180da77e8d.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://c4noulpnr0sxc"
|
||||||
|
path="res://.godot/imported/dasher_hold.glb-91cd82b87dfb263f0f79ba3e311e0190.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/characters/dashers/dasher_hold.glb"
|
||||||
|
dest_files=["res://.godot/imported/dasher_hold.glb-91cd82b87dfb263f0f79ba3e311e0190.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://c1ymy6xseihds"
|
||||||
|
path="res://.godot/imported/dasher_put.glb-6349d495a8444cf30de4860b525f36c7.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/characters/dashers/dasher_put.glb"
|
||||||
|
dest_files=["res://.godot/imported/dasher_put.glb-6349d495a8444cf30de4860b525f36c7.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://3ipo6a5j5s48"
|
||||||
|
path="res://.godot/imported/dasher_stun.glb-5c67aab95f2516134af8dc025fb37bf5.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/characters/dashers/dasher_stun.glb"
|
||||||
|
dest_files=["res://.godot/imported/dasher_stun.glb-5c67aab95f2516134af8dc025fb37bf5.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://jqfmoyxrxuk6"
|
||||||
|
path="res://.godot/imported/dasher_take.glb-855d3406fda9c42eaacac6fd49155c0f.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/characters/dashers/dasher_take.glb"
|
||||||
|
dest_files=["res://.godot/imported/dasher_take.glb-855d3406fda9c42eaacac6fd49155c0f.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://bt8pfg1j14lq4"
|
||||||
|
path="res://.godot/imported/tekton_hold.glb-200d797f4b202164003717fecbdfba97.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/characters/dashers/tekton_hold.glb"
|
||||||
|
dest_files=["res://.godot/imported/tekton_hold.glb-200d797f4b202164003717fecbdfba97.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://bkhla0bd8gh6k"
|
||||||
|
path="res://.godot/imported/tekton_put.glb-10897df056830be9e27ad171553e71b3.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/characters/dashers/tekton_put.glb"
|
||||||
|
dest_files=["res://.godot/imported/tekton_put.glb-10897df056830be9e27ad171553e71b3.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://ci2lcm0tf02s0"
|
||||||
|
path="res://.godot/imported/tekton_take.glb-e5326a2ca2848c1e748b4be0ea916233.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/characters/dashers/tekton_take.glb"
|
||||||
|
dest_files=["res://.godot/imported/tekton_take.glb-e5326a2ca2848c1e748b4be0ea916233.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[gd_resource type="StandardMaterial3D" format=3 uid="uid://v1qdug4x2ifm"]
|
|
||||||
|
|
||||||
[ext_resource type="Texture2D" uid="uid://d0egs6j3sg0me" path="res://assets/characters/skins/clothing/oldpop_cloth_white_pant.png" id="1_fw7uu"]
|
|
||||||
|
|
||||||
[resource]
|
|
||||||
transparency = 2
|
|
||||||
alpha_scissor_threshold = 0.5
|
|
||||||
alpha_antialiasing_mode = 0
|
|
||||||
albedo_texture = ExtResource("1_fw7uu")
|
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://cuporokvsp4ml"
|
||||||
|
path="res://.godot/imported/tekton_fishing_animation.glb-4469ef86e01e801d40365a39e33a43d9.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/characters/tektons/tekton_fishing_animation.glb"
|
||||||
|
dest_files=["res://.godot/imported/tekton_fishing_animation.glb-4469ef86e01e801d40365a39e33a43d9.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 432 KiB |
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://csjf5vwqc7kjc"
|
||||||
|
path.s3tc="res://.godot/imported/tekton_fishing_animation_Ted_tex.png-843bfeb42afe9f0cc8bbef67a1660342.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/tekton_fishing_animation_Ted_tex.png-843bfeb42afe9f0cc8bbef67a1660342.etc2.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
generator_parameters={
|
||||||
|
"md5": "ad130a61dfc420142fb25fb3f8aa3c6f"
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/characters/tektons/tekton_fishing_animation_Ted_tex.png"
|
||||||
|
dest_files=["res://.godot/imported/tekton_fishing_animation_Ted_tex.png-843bfeb42afe9f0cc8bbef67a1660342.s3tc.ctex", "res://.godot/imported/tekton_fishing_animation_Ted_tex.png-843bfeb42afe9f0cc8bbef67a1660342.etc2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
[gd_resource type="StandardMaterial3D" format=3 uid="uid://deufkavgeo0jg"]
|
[gd_resource type="StandardMaterial3D" format=3 uid="uid://deufkavgeo0jg"]
|
||||||
|
|
||||||
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_ywepx"]
|
|
||||||
load_path = "res://.godot/imported/4e3103ab214e443c83147afcd786aa6e_RGB_M_Buche_albedo.jpeg-69394e445366683380463d4afc846305.s3tc.ctex"
|
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
albedo_texture = SubResource("CompressedTexture2D_ywepx")
|
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ Ke 0.000000 0.000000 0.000000
|
|||||||
Ni 1.500000
|
Ni 1.500000
|
||||||
d 1.000000
|
d 1.000000
|
||||||
illum 2
|
illum 2
|
||||||
map_Kd C:/Users/beng/Downloads/3d_Ripper_Pro_v103/Downloads/SarahMyriamMadi/01- Handpainted.Log/4e3103ab214e443c83147afcd786aa6e_RGB_M_Buche_albedo.jpeg
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 618 KiB |
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cyb7umn1uk7j8"
|
||||||
|
path.s3tc="res://.godot/imported/Block_box_tex.png-3a9361e801fe4eacf19d5d43c9246e12.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/Block_box_tex.png-3a9361e801fe4eacf19d5d43c9246e12.etc2.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/props/Block_box_tex.png"
|
||||||
|
dest_files=["res://.godot/imported/Block_box_tex.png-3a9361e801fe4eacf19d5d43c9246e12.s3tc.ctex", "res://.godot/imported/Block_box_tex.png-3a9361e801fe4eacf19d5d43c9246e12.etc2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
||||||
Binary file not shown.
@@ -0,0 +1,543 @@
|
|||||||
|
{
|
||||||
|
"asset":{
|
||||||
|
"generator":"Khronos glTF Blender I/O v4.3.47",
|
||||||
|
"version":"2.0"
|
||||||
|
},
|
||||||
|
"scene":0,
|
||||||
|
"scenes":[
|
||||||
|
{
|
||||||
|
"name":"Scene",
|
||||||
|
"nodes":[
|
||||||
|
4
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nodes":[
|
||||||
|
{
|
||||||
|
"name":"Bone.002",
|
||||||
|
"rotation":[
|
||||||
|
0,
|
||||||
|
1.1920928955078125e-07,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"translation":[
|
||||||
|
0,
|
||||||
|
0.7185347676277161,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children":[
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"name":"Bone.001",
|
||||||
|
"translation":[
|
||||||
|
0,
|
||||||
|
0.7113578915596008,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children":[
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"name":"Bone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mesh":0,
|
||||||
|
"name":"wall_body",
|
||||||
|
"skin":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children":[
|
||||||
|
3,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"name":"Wall"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"animations":[
|
||||||
|
{
|
||||||
|
"channels":[
|
||||||
|
{
|
||||||
|
"sampler":0,
|
||||||
|
"target":{
|
||||||
|
"node":2,
|
||||||
|
"path":"translation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler":1,
|
||||||
|
"target":{
|
||||||
|
"node":2,
|
||||||
|
"path":"rotation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler":2,
|
||||||
|
"target":{
|
||||||
|
"node":2,
|
||||||
|
"path":"scale"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler":3,
|
||||||
|
"target":{
|
||||||
|
"node":1,
|
||||||
|
"path":"translation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler":4,
|
||||||
|
"target":{
|
||||||
|
"node":1,
|
||||||
|
"path":"rotation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler":5,
|
||||||
|
"target":{
|
||||||
|
"node":1,
|
||||||
|
"path":"scale"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler":6,
|
||||||
|
"target":{
|
||||||
|
"node":0,
|
||||||
|
"path":"translation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler":7,
|
||||||
|
"target":{
|
||||||
|
"node":0,
|
||||||
|
"path":"rotation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler":8,
|
||||||
|
"target":{
|
||||||
|
"node":0,
|
||||||
|
"path":"scale"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler":9,
|
||||||
|
"target":{
|
||||||
|
"node":4,
|
||||||
|
"path":"translation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler":10,
|
||||||
|
"target":{
|
||||||
|
"node":4,
|
||||||
|
"path":"rotation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sampler":11,
|
||||||
|
"target":{
|
||||||
|
"node":4,
|
||||||
|
"path":"scale"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name":"Wall.001|Wall Animation|Anima_Layer",
|
||||||
|
"samplers":[
|
||||||
|
{
|
||||||
|
"input":7,
|
||||||
|
"interpolation":"LINEAR",
|
||||||
|
"output":8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input":9,
|
||||||
|
"interpolation":"STEP",
|
||||||
|
"output":10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input":7,
|
||||||
|
"interpolation":"LINEAR",
|
||||||
|
"output":11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input":9,
|
||||||
|
"interpolation":"LINEAR",
|
||||||
|
"output":12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input":9,
|
||||||
|
"interpolation":"STEP",
|
||||||
|
"output":13
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input":7,
|
||||||
|
"interpolation":"LINEAR",
|
||||||
|
"output":14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input":9,
|
||||||
|
"interpolation":"LINEAR",
|
||||||
|
"output":15
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input":9,
|
||||||
|
"interpolation":"STEP",
|
||||||
|
"output":16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input":7,
|
||||||
|
"interpolation":"LINEAR",
|
||||||
|
"output":17
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input":9,
|
||||||
|
"interpolation":"STEP",
|
||||||
|
"output":18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input":9,
|
||||||
|
"interpolation":"STEP",
|
||||||
|
"output":19
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"input":9,
|
||||||
|
"interpolation":"STEP",
|
||||||
|
"output":20
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"materials":[
|
||||||
|
{
|
||||||
|
"doubleSided":true,
|
||||||
|
"name":"Material",
|
||||||
|
"pbrMetallicRoughness":{
|
||||||
|
"baseColorTexture":{
|
||||||
|
"index":0
|
||||||
|
},
|
||||||
|
"metallicFactor":0,
|
||||||
|
"roughnessFactor":0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meshes":[
|
||||||
|
{
|
||||||
|
"name":"Cube",
|
||||||
|
"primitives":[
|
||||||
|
{
|
||||||
|
"attributes":{
|
||||||
|
"POSITION":0,
|
||||||
|
"NORMAL":1,
|
||||||
|
"TEXCOORD_0":2,
|
||||||
|
"JOINTS_0":3,
|
||||||
|
"WEIGHTS_0":4
|
||||||
|
},
|
||||||
|
"indices":5,
|
||||||
|
"material":0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"textures":[
|
||||||
|
{
|
||||||
|
"sampler":0,
|
||||||
|
"source":0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"images":[
|
||||||
|
{
|
||||||
|
"mimeType":"image/png",
|
||||||
|
"name":"Block_box_tex",
|
||||||
|
"uri":"Block_box_tex.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"skins":[
|
||||||
|
{
|
||||||
|
"inverseBindMatrices":6,
|
||||||
|
"joints":[
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"name":"Wall"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accessors":[
|
||||||
|
{
|
||||||
|
"bufferView":0,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":553,
|
||||||
|
"max":[
|
||||||
|
1,
|
||||||
|
2.0071864128112793,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"min":[
|
||||||
|
-1,
|
||||||
|
0.007186145056039095,
|
||||||
|
-1.0000003576278687
|
||||||
|
],
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":1,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":553,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":2,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":553,
|
||||||
|
"type":"VEC2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":3,
|
||||||
|
"componentType":5121,
|
||||||
|
"count":553,
|
||||||
|
"type":"VEC4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":4,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":553,
|
||||||
|
"type":"VEC4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":5,
|
||||||
|
"componentType":5123,
|
||||||
|
"count":2952,
|
||||||
|
"type":"SCALAR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":6,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":3,
|
||||||
|
"type":"MAT4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":7,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":15,
|
||||||
|
"max":[
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"min":[
|
||||||
|
0.03333333333333333
|
||||||
|
],
|
||||||
|
"type":"SCALAR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":8,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":15,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":9,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":2,
|
||||||
|
"max":[
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"min":[
|
||||||
|
0.03333333333333333
|
||||||
|
],
|
||||||
|
"type":"SCALAR"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":10,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":2,
|
||||||
|
"type":"VEC4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":11,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":15,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":12,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":2,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":13,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":2,
|
||||||
|
"type":"VEC4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":14,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":15,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":15,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":2,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":16,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":2,
|
||||||
|
"type":"VEC4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":17,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":15,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":18,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":2,
|
||||||
|
"type":"VEC3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":19,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":2,
|
||||||
|
"type":"VEC4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bufferView":20,
|
||||||
|
"componentType":5126,
|
||||||
|
"count":2,
|
||||||
|
"type":"VEC3"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bufferViews":[
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":6636,
|
||||||
|
"byteOffset":0,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":6636,
|
||||||
|
"byteOffset":6636,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":4424,
|
||||||
|
"byteOffset":13272,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":2212,
|
||||||
|
"byteOffset":17696,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":8848,
|
||||||
|
"byteOffset":19908,
|
||||||
|
"target":34962
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":5904,
|
||||||
|
"byteOffset":28756,
|
||||||
|
"target":34963
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":192,
|
||||||
|
"byteOffset":34660
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":60,
|
||||||
|
"byteOffset":34852
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":180,
|
||||||
|
"byteOffset":34912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":8,
|
||||||
|
"byteOffset":35092
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":32,
|
||||||
|
"byteOffset":35100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":180,
|
||||||
|
"byteOffset":35132
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":24,
|
||||||
|
"byteOffset":35312
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":32,
|
||||||
|
"byteOffset":35336
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":180,
|
||||||
|
"byteOffset":35368
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":24,
|
||||||
|
"byteOffset":35548
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":32,
|
||||||
|
"byteOffset":35572
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":180,
|
||||||
|
"byteOffset":35604
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":24,
|
||||||
|
"byteOffset":35784
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":32,
|
||||||
|
"byteOffset":35808
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"buffer":0,
|
||||||
|
"byteLength":24,
|
||||||
|
"byteOffset":35840
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"samplers":[
|
||||||
|
{
|
||||||
|
"magFilter":9729,
|
||||||
|
"minFilter":9987
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buffers":[
|
||||||
|
{
|
||||||
|
"byteLength":35864,
|
||||||
|
"uri":"Box_block_2.bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://b6xusldnaa288"
|
||||||
|
path="res://.godot/imported/Box_block_2.gltf-6b278c07bb52956b8b62d9b428e8f6a6.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/props/Box_block_2.gltf"
|
||||||
|
dest_files=["res://.godot/imported/Box_block_2.gltf-6b278c07bb52956b8b62d9b428e8f6a6.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
@@ -0,0 +1,44 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://bh5epppot37nh"
|
||||||
|
path="res://.godot/imported/wall_animation.fbx-22f993a05720796858e5daa12a9ef4c5.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/props/wall_animation.fbx"
|
||||||
|
dest_files=["res://.godot/imported/wall_animation.fbx-22f993a05720796858e5daa12a9ef4c5.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=true
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
fbx/importer=0
|
||||||
|
fbx/allow_geometry_helper_nodes=false
|
||||||
|
fbx/embedded_image_handling=1
|
||||||
|
fbx/naming_version=2
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://dahuvrhr1u74y"
|
||||||
|
path="res://.godot/imported/wheat_block_0.glb-35fd252da9f0a42b79ed5759815c731c.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/props/wheat_block_0.glb"
|
||||||
|
dest_files=["res://.godot/imported/wheat_block_0.glb-35fd252da9f0a42b79ed5759815c731c.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cdfu3bvdiisg2"
|
||||||
|
path.s3tc="res://.godot/imported/wheat_block_0_wheat_tex.png-c6a087ae50523310eace371eb3353908.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/wheat_block_0_wheat_tex.png-c6a087ae50523310eace371eb3353908.etc2.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
generator_parameters={
|
||||||
|
"md5": "448396071c54a15f593f0eb2628889c1"
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/props/wheat_block_0_wheat_tex.png"
|
||||||
|
dest_files=["res://.godot/imported/wheat_block_0_wheat_tex.png-c6a087ae50523310eace371eb3353908.s3tc.ctex", "res://.godot/imported/wheat_block_0_wheat_tex.png-c6a087ae50523310eace371eb3353908.etc2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="scene"
|
||||||
|
importer_version=1
|
||||||
|
type="PackedScene"
|
||||||
|
uid="uid://ddjg2x57kr6"
|
||||||
|
path="res://.godot/imported/wheat_block_1.glb-5d996ad9d4004837507f80c51b730927.scn"
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/props/wheat_block_1.glb"
|
||||||
|
dest_files=["res://.godot/imported/wheat_block_1.glb-5d996ad9d4004837507f80c51b730927.scn"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
nodes/root_type=""
|
||||||
|
nodes/root_name=""
|
||||||
|
nodes/root_script=null
|
||||||
|
nodes/apply_root_scale=true
|
||||||
|
nodes/root_scale=1.0
|
||||||
|
nodes/import_as_skeleton_bones=false
|
||||||
|
nodes/use_name_suffixes=true
|
||||||
|
nodes/use_node_type_suffixes=true
|
||||||
|
meshes/ensure_tangents=true
|
||||||
|
meshes/generate_lods=true
|
||||||
|
meshes/create_shadow_meshes=true
|
||||||
|
meshes/light_baking=1
|
||||||
|
meshes/lightmap_texel_size=0.2
|
||||||
|
meshes/force_disable_compression=false
|
||||||
|
skins/use_named_skins=true
|
||||||
|
animation/import=true
|
||||||
|
animation/fps=30
|
||||||
|
animation/trimming=false
|
||||||
|
animation/remove_immutable_tracks=true
|
||||||
|
animation/import_rest_as_RESET=false
|
||||||
|
import_script/path=""
|
||||||
|
materials/extract=0
|
||||||
|
materials/extract_format=0
|
||||||
|
materials/extract_path=""
|
||||||
|
_subresources={}
|
||||||
|
gltf/naming_version=2
|
||||||
|
gltf/embedded_image_handling=1
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,45 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://blluf0gwi8nbi"
|
||||||
|
path.s3tc="res://.godot/imported/wheat_block_1_wheat_tex.png-0784d9deebdc1ba07043c2e1e3363717.s3tc.ctex"
|
||||||
|
path.etc2="res://.godot/imported/wheat_block_1_wheat_tex.png-0784d9deebdc1ba07043c2e1e3363717.etc2.ctex"
|
||||||
|
metadata={
|
||||||
|
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||||
|
"vram_texture": true
|
||||||
|
}
|
||||||
|
generator_parameters={
|
||||||
|
"md5": "448396071c54a15f593f0eb2628889c1"
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/models/props/wheat_block_1_wheat_tex.png"
|
||||||
|
dest_files=["res://.godot/imported/wheat_block_1_wheat_tex.png-0784d9deebdc1ba07043c2e1e3363717.s3tc.ctex", "res://.godot/imported/wheat_block_1_wheat_tex.png-0784d9deebdc1ba07043c2e1e3363717.etc2.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=2
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=true
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=0
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
codegen
|
||||||
|
=======
|
||||||
|
|
||||||
|
> A util tool to generate a client from the Swagger spec of Nakama's server API.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go run main.go "$GOPATH/src/github.com/heroiclabs/nakama/apigrpc/apigrpc.swagger.json" > ../addons/com.heroiclabs.nakama/api/NakamaAPI.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rationale
|
||||||
|
|
||||||
|
We want to maintain a simple lean low level client within our GDScript client which has minimal dependencies so we built our own. This gives us complete control over the dependencies required and structure of the code generated.
|
||||||
|
|
||||||
|
The generated code is designed to be supported Godot Engine `3.1+`.
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
|
||||||
|
The code generator has __only__ been checked against the Swagger specification generated for Nakama server. YMMV.
|
||||||
|
|
||||||
+639
@@ -0,0 +1,639 @@
|
|||||||
|
// Copyright 2018 The Nakama Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
var utilities = map[string]string {
|
||||||
|
"ApiAccount":
|
||||||
|
`
|
||||||
|
|
||||||
|
var _wallet_dict = null
|
||||||
|
var wallet_dict : Dictionary:
|
||||||
|
get:
|
||||||
|
if _wallet_dict == null:
|
||||||
|
if _wallet == null:
|
||||||
|
return {}
|
||||||
|
var json = JSON.new()
|
||||||
|
if json.parse(_wallet) != OK:
|
||||||
|
return {}
|
||||||
|
_wallet_dict = json.get_data()
|
||||||
|
return _wallet_dict as Dictionary
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeTemplate string = `### Code generated by codegen/main.go. DO NOT EDIT. ###
|
||||||
|
|
||||||
|
extends RefCounted
|
||||||
|
class_name NakamaAPI
|
||||||
|
{{- range $defname, $definition := .Definitions }}
|
||||||
|
{{- $classname := $defname | title }}
|
||||||
|
{{- if isRefToEnum $classname }}
|
||||||
|
|
||||||
|
# {{ enumSummary $definition | stripNewlines }}
|
||||||
|
{{- range $idx, $val := ($definition | enumDescriptions) }}
|
||||||
|
# {{ $val }}
|
||||||
|
{{- end -}}
|
||||||
|
# {{ $definition | enumDescriptions }}
|
||||||
|
enum {{ $classname | title }} { {{- range $idx, $enum := $definition.Enum }}{{ $enum }} = {{ $idx }},{{- end -}} }
|
||||||
|
{{- else }}
|
||||||
|
|
||||||
|
# {{ $definition.Description | stripNewlines }}
|
||||||
|
class {{ $classname }} extends NakamaAsyncResult:
|
||||||
|
|
||||||
|
const _SCHEMA = {
|
||||||
|
{{- range $propname, $property := $definition.Properties }}
|
||||||
|
{{- $fieldname := $propname | pascalToSnake }}
|
||||||
|
{{- $_field := printf "_%s" $fieldname }}
|
||||||
|
{{- $gdType := godotType $property.Type $property.Ref $property.Items.Type $property.Items.Ref (isRefToEnum (cleanRef $property.Ref)) }}
|
||||||
|
"{{ $fieldname }}": {"name": "{{ $_field }}", "type": {{ $gdType | godotSchemaType }}, "required": false
|
||||||
|
{{- if eq $property.Type "array" -}}
|
||||||
|
, "content": {{ (godotType $property.Items.Type $property.Items.Ref "" "" false) | godotSchemaType }}
|
||||||
|
{{- else if eq $property.Type "object" -}}
|
||||||
|
, "content": {{ (godotType $property.AdditionalProperties.Type "" "" "" false) | godotSchemaType }}
|
||||||
|
{{- end -}}
|
||||||
|
},
|
||||||
|
{{- end }}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{- range $propname, $property := $definition.Properties }}
|
||||||
|
{{- $fieldname := $propname | pascalToSnake }}
|
||||||
|
{{- $_field := printf "_%s" $fieldname }}
|
||||||
|
{{- $gdType := godotType $property.Type $property.Ref $property.Items.Type $property.Items.Ref (isRefToEnum (cleanRef $property.Ref)) }}
|
||||||
|
{{- $gdDef := $gdType | godotDef }}
|
||||||
|
|
||||||
|
# {{ $property.Description }}
|
||||||
|
var {{ $_field }}
|
||||||
|
var {{ $fieldname }} : {{ $gdType }}:
|
||||||
|
get:
|
||||||
|
{{- if $property.Ref }}
|
||||||
|
{{- if isRefToEnum (cleanRef $property.Ref) }}{{/* Enums */}}
|
||||||
|
return {{ cleanRef $property.Ref }}.values()[0] if not {{ cleanRef $property.Ref }}.values().has({{ $_field }}) else {{ $_field }}
|
||||||
|
{{- else }}{{/* Object reference */}}
|
||||||
|
return _{{ $fieldname }} as {{ $gdType }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if eq $property.Type "object"}}{{/* Dictionaries */}}
|
||||||
|
return Dictionary() if not {{ $_field }} is Dictionary else {{ $_field }}.duplicate()
|
||||||
|
{{- else }}{{/* Simple type */}}
|
||||||
|
return {{ $gdDef }} if not {{ $_field }} is {{ $gdType }} else {{ $gdType }}({{ $_field }})
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- godotClassUtils $classname }}
|
||||||
|
|
||||||
|
func _init(p_exception = null):
|
||||||
|
super(p_exception)
|
||||||
|
|
||||||
|
static func create(p_ns : GDScript, p_dict : Dictionary) -> {{ $classname }}:
|
||||||
|
return _safe_ret(NakamaSerializer.deserialize(p_ns, "{{ $classname }}", p_dict), {{ $classname }}) as {{ $classname }}
|
||||||
|
|
||||||
|
func serialize() -> Dictionary:
|
||||||
|
return NakamaSerializer.serialize(self)
|
||||||
|
|
||||||
|
func _to_string() -> String:
|
||||||
|
if is_exception():
|
||||||
|
return get_exception()._to_string()
|
||||||
|
var output : String = ""
|
||||||
|
{{- range $propname, $property := $definition.Properties }}
|
||||||
|
{{- $fieldname := $propname | pascalToSnake }}
|
||||||
|
{{- $_field := printf "_%s" $fieldname }}
|
||||||
|
{{- if eq $property.Type "array" }}
|
||||||
|
output += "{{ $fieldname }}: %s, " % [{{ $_field }}]
|
||||||
|
{{- else if eq $property.Type "object" }}
|
||||||
|
var map_string : String = ""
|
||||||
|
if typeof({{ $_field }}) == TYPE_DICTIONARY:
|
||||||
|
for k in {{ $_field }}:
|
||||||
|
map_string += "{%s=%s}, " % [k, {{ $_field }}[k]]
|
||||||
|
output += "{{ $fieldname }}: [%s], " % map_string
|
||||||
|
{{- else }}
|
||||||
|
output += "{{ $fieldname }}: %s, " % {{ $_field }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
return output
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
# The low level client for the Nakama API.
|
||||||
|
class ApiClient extends RefCounted:
|
||||||
|
|
||||||
|
var _base_uri : String
|
||||||
|
|
||||||
|
var _http_adapter
|
||||||
|
var _namespace : GDScript
|
||||||
|
var _server_key : String
|
||||||
|
var auto_refresh := true
|
||||||
|
var auto_refresh_time := 300
|
||||||
|
|
||||||
|
var auto_retry : bool:
|
||||||
|
set(p_value):
|
||||||
|
_http_adapter.auto_retry = p_value
|
||||||
|
get:
|
||||||
|
return _http_adapter.auto_retry
|
||||||
|
|
||||||
|
var auto_retry_count : int:
|
||||||
|
set(p_value):
|
||||||
|
_http_adapter.auto_retry_count = p_value
|
||||||
|
get:
|
||||||
|
return _http_adapter.auto_retry_count
|
||||||
|
|
||||||
|
var auto_retry_backoff_base : int:
|
||||||
|
set(p_value):
|
||||||
|
_http_adapter.auto_retry_backoff_base = p_value
|
||||||
|
get:
|
||||||
|
return _http_adapter.auto_retry_backoff_base
|
||||||
|
|
||||||
|
var last_cancel_token:
|
||||||
|
get:
|
||||||
|
return _http_adapter.get_last_token()
|
||||||
|
|
||||||
|
func _init(p_base_uri : String, p_http_adapter, p_namespace : GDScript, p_server_key : String, p_timeout : int = 10):
|
||||||
|
_base_uri = p_base_uri
|
||||||
|
_http_adapter = p_http_adapter
|
||||||
|
_http_adapter.timeout = p_timeout
|
||||||
|
_namespace = p_namespace
|
||||||
|
_server_key = p_server_key
|
||||||
|
|
||||||
|
func _refresh_session(p_session : NakamaSession):
|
||||||
|
if auto_refresh and p_session.is_valid() and p_session.refresh_token and not p_session.is_refresh_expired() and p_session.would_expire_in(auto_refresh_time):
|
||||||
|
var request = ApiSessionRefreshRequest.new()
|
||||||
|
request._token = p_session.refresh_token
|
||||||
|
return await session_refresh_async(_server_key, "", request)
|
||||||
|
return null
|
||||||
|
|
||||||
|
func cancel_request(p_token):
|
||||||
|
if p_token:
|
||||||
|
_http_adapter.cancel_request(p_token)
|
||||||
|
|
||||||
|
{{- range $url, $path := .Paths }}
|
||||||
|
{{- range $method, $operation := $path}}
|
||||||
|
|
||||||
|
# {{ $operation.Summary | stripNewlines }}
|
||||||
|
{{- if $operation.Responses.Ok.Schema.Ref }}
|
||||||
|
func {{ $operation.OperationId | apiFuncName }}_async(
|
||||||
|
{{- else }}
|
||||||
|
func {{ $operation.OperationId | apiFuncName }}_async(
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
{{- if $operation.Security }}
|
||||||
|
{{- with (index $operation.Security 0) }}
|
||||||
|
{{- range $key, $value := . }}
|
||||||
|
{{- if eq $key "BasicAuth" }}
|
||||||
|
p_basic_auth_username : String
|
||||||
|
, p_basic_auth_password : String
|
||||||
|
{{- else if eq $key "HttpKeyAuth" }}
|
||||||
|
p_bearer_token : String
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
p_session : NakamaSession
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- range $parameter := $operation.Parameters }}
|
||||||
|
{{- $argument := $parameter.Name | prependParameter }}
|
||||||
|
{{- if not $parameter.Required }}{{/* Godot does not support typed optional parameters yet. */}}
|
||||||
|
, {{ $argument }} = null # : {{ $parameter.Type }}
|
||||||
|
{{- else if eq $parameter.In "body" }}
|
||||||
|
{{- if eq $parameter.Schema.Type "string" }}
|
||||||
|
, {{ $argument }} : String
|
||||||
|
{{- else }}
|
||||||
|
, {{ $argument }} : {{ $parameter.Schema.Ref | cleanRef }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
, {{ $argument }} : {{ godotType $parameter.Type $parameter.Schema.Ref $parameter.Items.Type "" (isRefToEnum (cleanRef $parameter.Schema.Ref)) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
)
|
||||||
|
{{- if $operation.Responses.Ok.Schema.Ref }} -> {{ $operation.Responses.Ok.Schema.Ref | cleanRef }}
|
||||||
|
{{- else }} -> NakamaAsyncResult
|
||||||
|
{{- end }}:
|
||||||
|
{{- $classname := "NakamaAsyncResult" }}
|
||||||
|
{{- if $operation.Responses.Ok.Schema.Ref }}
|
||||||
|
{{- $classname = $operation.Responses.Ok.Schema.Ref | cleanRef }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not $operation.Security }}
|
||||||
|
var try_refresh = await _refresh_session(p_session)
|
||||||
|
if try_refresh != null:
|
||||||
|
if try_refresh.is_exception():
|
||||||
|
return {{ $classname }}.new(try_refresh.get_exception())
|
||||||
|
await p_session.refresh(try_refresh)
|
||||||
|
{{- end }}
|
||||||
|
var urlpath : String = "{{- $url }}"
|
||||||
|
{{- range $parameter := $operation.Parameters }}
|
||||||
|
{{- $argument := $parameter.Name | prependParameter }}
|
||||||
|
{{- if eq $parameter.In "path" }}
|
||||||
|
urlpath = urlpath.replace("{{- print "{" $parameter.Name "}"}}", NakamaSerializer.escape_http({{ $argument }}))
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
var query_params = ""
|
||||||
|
{{- range $parameter := $operation.Parameters }}
|
||||||
|
{{- $argument := $parameter.Name | prependParameter }}
|
||||||
|
{{- $snakecase := $parameter.Name | pascalToSnake }}
|
||||||
|
{{- if eq $parameter.In "query"}}
|
||||||
|
{{- if $parameter.Required }}
|
||||||
|
if true: # Hack for static checks
|
||||||
|
{{- else }}
|
||||||
|
if {{ $argument }} != null:
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq $parameter.Type "integer" }}
|
||||||
|
query_params += "{{- $snakecase }}=%d&" % {{ $argument }}
|
||||||
|
{{- else if eq $parameter.Type "string" }}
|
||||||
|
query_params += "{{- $snakecase }}=%s&" % NakamaSerializer.escape_http({{ $argument }})
|
||||||
|
{{- else if eq $parameter.Type "boolean" }}
|
||||||
|
query_params += "{{- $snakecase }}=%s&" % str(bool({{ $argument }})).to_lower()
|
||||||
|
{{- else if eq $parameter.Type "array" }}
|
||||||
|
for elem in {{ $argument }}:
|
||||||
|
query_params += "{{- $snakecase }}=%s&" % elem
|
||||||
|
{{- else }}
|
||||||
|
{{ $parameter }} // ERROR
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
var uri = "%s%s%s" % [_base_uri, urlpath, "?" + query_params if query_params else ""]
|
||||||
|
var method = "{{- $method | uppercase }}"
|
||||||
|
var headers = {}
|
||||||
|
{{- if $operation.Security }}
|
||||||
|
{{- with (index $operation.Security 0) }}
|
||||||
|
{{- range $key, $value := . }}
|
||||||
|
{{- if eq $key "BasicAuth" }}
|
||||||
|
var credentials = Marshalls.utf8_to_base64(p_basic_auth_username + ":" + p_basic_auth_password)
|
||||||
|
var header = "Basic %s" % credentials
|
||||||
|
headers["Authorization"] = header
|
||||||
|
{{- else if eq $key "HttpKeyAuth" }}
|
||||||
|
if (p_bearer_token):
|
||||||
|
var header = "Bearer %s" % p_bearer_token
|
||||||
|
headers["Authorization"] = header
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
var header = "Bearer %s" % p_session.token
|
||||||
|
headers["Authorization"] = header
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
var content : PackedByteArray
|
||||||
|
{{- range $parameter := $operation.Parameters }}
|
||||||
|
{{- $argument := $parameter.Name | prependParameter }}
|
||||||
|
{{- if eq $parameter.In "body" }}
|
||||||
|
{{- if eq $parameter.Schema.Type "string" }}
|
||||||
|
content = JSON.stringify({{ $argument }}).to_utf8_buffer()
|
||||||
|
{{- else }}
|
||||||
|
content = JSON.stringify({{ $argument }}.serialize()).to_utf8_buffer()
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
var result = await _http_adapter.send_async(method, uri, headers, content)
|
||||||
|
if result is NakamaException:
|
||||||
|
return {{ $classname }}.new(result)
|
||||||
|
|
||||||
|
{{- if $operation.Responses.Ok.Schema.Ref }}
|
||||||
|
var out : {{ $classname }} = NakamaSerializer.deserialize(_namespace, "{{ $classname }}", result)
|
||||||
|
return out
|
||||||
|
{{- else }}
|
||||||
|
return NakamaAsyncResult.new()
|
||||||
|
{{- end}}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
`
|
||||||
|
|
||||||
|
func convertRefToClassName(input string) (className string) {
|
||||||
|
cleanRef := strings.TrimPrefix(input, "#/definitions/")
|
||||||
|
className = strings.Title(cleanRef)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripNewlines(input string) (output string) {
|
||||||
|
output = strings.Replace(input, "\n", " ", -1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func prependParameter(input string) (output string) {
|
||||||
|
output = "p_" + pascalToSnake(input)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pascalToSnake(input string) (output string) {
|
||||||
|
output = ""
|
||||||
|
prev_low := false
|
||||||
|
for _, v := range input {
|
||||||
|
is_cap := v >= 'A' && v <= 'Z'
|
||||||
|
is_low := v >= 'a' && v <= 'z'
|
||||||
|
if is_cap && prev_low {
|
||||||
|
output = output + "_"
|
||||||
|
}
|
||||||
|
output += strings.ToLower(string(v))
|
||||||
|
prev_low = is_low
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiFuncName(input string) (output string) {
|
||||||
|
output = pascalToSnake(input[7:])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func godotType(p_type string, p_ref string, p_item_type string, p_extra string, p_is_enum bool) (out string) {
|
||||||
|
|
||||||
|
is_array := false
|
||||||
|
is_dict := false
|
||||||
|
switch p_type {
|
||||||
|
case "integer":
|
||||||
|
out = "int"
|
||||||
|
case "string":
|
||||||
|
out = "String"
|
||||||
|
case "boolean":
|
||||||
|
out = "bool"
|
||||||
|
case "array":
|
||||||
|
is_array = true
|
||||||
|
case "object":
|
||||||
|
is_dict = true
|
||||||
|
default:
|
||||||
|
if p_is_enum {
|
||||||
|
out = "int"
|
||||||
|
} else {
|
||||||
|
out = convertRefToClassName(p_ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_array {
|
||||||
|
switch p_item_type {
|
||||||
|
case "integer":
|
||||||
|
out = "PackedIntArray"
|
||||||
|
return
|
||||||
|
case "string":
|
||||||
|
out = "PackedStringArray"
|
||||||
|
return
|
||||||
|
case "boolean":
|
||||||
|
out = "PackedIntArray"
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
out = "Array"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if is_dict {
|
||||||
|
out = "Dictionary"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func godotDef(p_type string) (out string) {
|
||||||
|
switch(p_type) {
|
||||||
|
case "bool": out = "false"
|
||||||
|
case "int": out = "0"
|
||||||
|
case "String": out = "\"\""
|
||||||
|
case "PackedIntArray": out = "PackedIntArray()"
|
||||||
|
case "PackedStringArray": out = "PackedStringArray()"
|
||||||
|
case "Array": out = "Array()"
|
||||||
|
case "Dictionary": out = "Dictionary()"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func godotLooseType(p_type string) (out string) {
|
||||||
|
switch(p_type) {
|
||||||
|
case "PackedStringArray", "PackedIntArray":
|
||||||
|
out = "Array"
|
||||||
|
default:
|
||||||
|
out = p_type
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func godotSchemaType(p_type string) (out string) {
|
||||||
|
out = "TYPE_"
|
||||||
|
switch(p_type) {
|
||||||
|
case "bool": out += "BOOL"
|
||||||
|
case "int": out += "INT"
|
||||||
|
case "String": out += "STRING"
|
||||||
|
case "PackedIntArray": out += "ARRAY"
|
||||||
|
case "PackedStringArray": out += "ARRAY"
|
||||||
|
case "Array": out += "ARRAY"
|
||||||
|
case "Dictionary": out += "DICTIONARY"
|
||||||
|
default: out = "\"" + p_type + "\""
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pascalToCamel(input string) (camelCase string) {
|
||||||
|
if input == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
camelCase = strings.ToLower(string(input[0]))
|
||||||
|
camelCase += string(input[1:])
|
||||||
|
return camelCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func camelToPascal(camelCase string) (pascalCase string) {
|
||||||
|
|
||||||
|
if len(camelCase) <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
pascalCase = strings.ToUpper(string(camelCase[0])) + camelCase[1:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func enumSummary(def Definition) string {
|
||||||
|
// quirk of swagger generation: if enum doesn't have a title
|
||||||
|
// then the title can be found as the first entry in the split description.
|
||||||
|
if def.Title != "" {
|
||||||
|
return def.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.Split(def.Description, "\n")
|
||||||
|
|
||||||
|
if len(split) <= 0 {
|
||||||
|
panic("No newlines in enum description found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return split[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func enumDescriptions(def Definition) (output []string) {
|
||||||
|
|
||||||
|
split := strings.Split(def.Description, "\n")
|
||||||
|
|
||||||
|
if len(split) <= 0 {
|
||||||
|
panic("No newlines in enum description found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if def.Title != "" {
|
||||||
|
return split
|
||||||
|
}
|
||||||
|
|
||||||
|
// quirk of swagger generation: if enum doesn't have a title
|
||||||
|
// then the title can be found as the first entry in the split description.
|
||||||
|
// so ignore for individual enum descriptions.
|
||||||
|
return split[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Definition struct {
|
||||||
|
Properties map[string]struct {
|
||||||
|
Type string
|
||||||
|
Ref string `json:"$ref"` // used with object
|
||||||
|
Items struct { // used with type "array"
|
||||||
|
Type string
|
||||||
|
Ref string `json:"$ref"`
|
||||||
|
}
|
||||||
|
AdditionalProperties struct {
|
||||||
|
Type string // used with type "map"
|
||||||
|
}
|
||||||
|
Format string // used with type "boolean"
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
Enum []string
|
||||||
|
Description string
|
||||||
|
// used only by enums
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|
||||||
|
func godotClassUtils(p_name string) string {
|
||||||
|
if val, ok := utilities[p_name]; ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Argument flags
|
||||||
|
var output = flag.String("output", "", "The output for generated code.")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
inputs := flag.Args()
|
||||||
|
if len(inputs) < 1 {
|
||||||
|
fmt.Printf("No input file found: %s\n\n", inputs)
|
||||||
|
fmt.Println("openapi-gen [flags] inputs...")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
input := inputs[0]
|
||||||
|
content, err := ioutil.ReadFile(input)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unable to read file: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var schema struct {
|
||||||
|
Paths map[string]map[string]struct {
|
||||||
|
Summary string
|
||||||
|
OperationId string
|
||||||
|
Responses struct {
|
||||||
|
Ok struct {
|
||||||
|
Schema struct {
|
||||||
|
Ref string `json:"$ref"`
|
||||||
|
}
|
||||||
|
} `json:"200"`
|
||||||
|
}
|
||||||
|
Parameters []struct {
|
||||||
|
Name string
|
||||||
|
In string
|
||||||
|
Required bool
|
||||||
|
Type string // used with primitives
|
||||||
|
Items struct { // used with type "array"
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
Schema struct { // used with http body
|
||||||
|
Type string
|
||||||
|
Ref string `json:"$ref"`
|
||||||
|
}
|
||||||
|
Format string // used with type "boolean"
|
||||||
|
}
|
||||||
|
Security []map[string][]struct {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Definitions map[string]Definition
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(content, &schema); err != nil {
|
||||||
|
fmt.Printf("Unable to decode input %s : %s\n", input, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmap := template.FuncMap{
|
||||||
|
"cleanRef": convertRefToClassName,
|
||||||
|
"stripNewlines": stripNewlines,
|
||||||
|
"title": strings.Title,
|
||||||
|
"uppercase": strings.ToUpper,
|
||||||
|
"prependParameter": prependParameter,
|
||||||
|
"pascalToSnake": pascalToSnake,
|
||||||
|
"apiFuncName": apiFuncName,
|
||||||
|
"godotType": godotType,
|
||||||
|
"godotLooseType": godotLooseType,
|
||||||
|
"godotSchemaType": godotSchemaType,
|
||||||
|
"godotDef": godotDef,
|
||||||
|
"isRefToEnum": func(ref string) bool {
|
||||||
|
if len(ref) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// swagger schema definition keys have inconsistent casing
|
||||||
|
var camelOk bool
|
||||||
|
var pascalOk bool
|
||||||
|
var enums []string
|
||||||
|
|
||||||
|
asCamel := pascalToCamel(ref)
|
||||||
|
if _, camelOk = schema.Definitions[asCamel]; camelOk {
|
||||||
|
enums = schema.Definitions[asCamel].Enum
|
||||||
|
}
|
||||||
|
|
||||||
|
asPascal := camelToPascal(ref)
|
||||||
|
if _, pascalOk = schema.Definitions[asPascal]; pascalOk {
|
||||||
|
enums = schema.Definitions[asPascal].Enum
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pascalOk && !camelOk {
|
||||||
|
fmt.Printf("no definition found: %v", ref)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(enums) > 0
|
||||||
|
},
|
||||||
|
"enumDescriptions": enumDescriptions,
|
||||||
|
"enumSummary": enumSummary,
|
||||||
|
"godotClassUtils": godotClassUtils,
|
||||||
|
}
|
||||||
|
tmpl, err := template.New(input).Funcs(fmap).Parse(codeTemplate)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Template parse error: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(*output) < 1 {
|
||||||
|
tmpl.Execute(os.Stdout, schema)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(*output)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unable to create file: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
writer := bufio.NewWriter(f)
|
||||||
|
tmpl.Execute(writer, schema)
|
||||||
|
writer.Flush()
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
[8a2fb36a98](https://git.klud.top/danchie/tekton/commit/8a2fb36a9867abb06ffef0d1967e9b45529d5b90)
|
||||||
|
|
||||||
|
/assets/characters/*
|
||||||
|
/assets/models/*
|
||||||
|
|
||||||
|
/scenes/player.gd
|
||||||
|
`:249-:251``_load_dasher_animations()`
|
||||||
|
`:376-:416``_load_dasher_animation():`
|
||||||
|
`:908-:950``if rank <=4:`
|
||||||
|
`:958-:961` `pos_label.modul..`
|
||||||
|
|
||||||
|
/scenes/ui/lobby_room.gd
|
||||||
|
`:128-:131` # Update area visual immediately when game mode changes
|
||||||
|
|
||||||
|
/scripts/managers/auth_manager.gd
|
||||||
|
`:445-:448`
|
||||||
|
`:454-:458`
|
||||||
|
`:463-:471`
|
||||||
|
|
||||||
|
/scripts/managers/lobby_manager.gd
|
||||||
|
`:728-:729`
|
||||||
|
`:732-:742`
|
||||||
|
`:749-:759`
|
||||||
|
|
||||||
|
/scripts/managers/settings_manager.gd
|
||||||
|
`:320-:329`
|
||||||
|
|
||||||
|
/scripts/nakama_manager.gd
|
||||||
|
`:126-:135`
|
||||||
|
|
||||||
|
/scripts/tekton.gd
|
||||||
|
`:417-:426`
|
||||||
|
`:451-:456`
|
||||||
@@ -450,6 +450,12 @@ func _on_server_option_selected(index: int) -> void:
|
|||||||
NakamaManager.set_server("tektondash.vps.webdock.cloud")
|
NakamaManager.set_server("tektondash.vps.webdock.cloud")
|
||||||
LobbyManager.is_lan_mode = false
|
LobbyManager.is_lan_mode = false
|
||||||
connection_status.text = "Mode: Online (Tekton Dash EU)"
|
connection_status.text = "Mode: Online (Tekton Dash EU)"
|
||||||
|
elif index == 4:
|
||||||
|
# Tekton Dash Asia
|
||||||
|
if server_ip_input: server_ip_input.visible = false
|
||||||
|
NakamaManager.set_server("tekton-asia.klud.top")
|
||||||
|
LobbyManager.is_lan_mode = false
|
||||||
|
connection_status.text = "Mode: Online (Tekton Dash Asia)"
|
||||||
|
|
||||||
func _on_server_ip_submitted(new_text: String) -> void:
|
func _on_server_ip_submitted(new_text: String) -> void:
|
||||||
if server_option and server_option.selected == 1:
|
if server_option and server_option.selected == 1:
|
||||||
|
|||||||
+4
-2
@@ -926,8 +926,8 @@ text = "TEKTON DASH"
|
|||||||
[node name="ServerOption" type="OptionButton" parent="MainMenuPanel/HiddenLogic" unique_id=645]
|
[node name="ServerOption" type="OptionButton" parent="MainMenuPanel/HiddenLogic" unique_id=645]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 0
|
layout_mode = 0
|
||||||
selected = 3
|
selected = 4
|
||||||
item_count = 4
|
item_count = 5
|
||||||
popup/item_0/text = "Nakama - Localhost (Testing)"
|
popup/item_0/text = "Nakama - Localhost (Testing)"
|
||||||
popup/item_0/id = 0
|
popup/item_0/id = 0
|
||||||
popup/item_1/text = "Nakama - Remote Server (Host IP)"
|
popup/item_1/text = "Nakama - Remote Server (Host IP)"
|
||||||
@@ -936,6 +936,8 @@ popup/item_2/text = "LAN Direct (No Server)"
|
|||||||
popup/item_2/id = 2
|
popup/item_2/id = 2
|
||||||
popup/item_3/text = "Tekton Dash EU"
|
popup/item_3/text = "Tekton Dash EU"
|
||||||
popup/item_3/id = 3
|
popup/item_3/id = 3
|
||||||
|
popup/item_4/text = "Tekton Dash Asia"
|
||||||
|
popup/item_4/id = 4
|
||||||
|
|
||||||
[node name="ServerIPInput" type="LineEdit" parent="MainMenuPanel/HiddenLogic" unique_id=1616424715]
|
[node name="ServerIPInput" type="LineEdit" parent="MainMenuPanel/HiddenLogic" unique_id=1616424715]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|||||||
+88
-67
@@ -70,25 +70,23 @@ var is_carrying_tekton: bool = false:
|
|||||||
emit_signal("tekton_carried_changed", value)
|
emit_signal("tekton_carried_changed", value)
|
||||||
# Visual/Logic side effects if any
|
# Visual/Logic side effects if any
|
||||||
|
|
||||||
var is_attack_mode: bool = false:
|
var is_charged_strike: bool = false:
|
||||||
set(value):
|
set(value):
|
||||||
if is_attack_mode == value:
|
if is_charged_strike == value:
|
||||||
return # Prevent infinite recursion / redundant updates
|
return
|
||||||
|
|
||||||
is_attack_mode = value
|
is_charged_strike = value
|
||||||
if is_attack_mode:
|
if is_charged_strike:
|
||||||
attack_mode_timer = MAX_ATTACK_MODE_TIME
|
charged_strike_timer = MAX_CHARGED_STRIKE_TIME
|
||||||
_refresh_player_visuals()
|
_refresh_player_visuals()
|
||||||
|
|
||||||
# Sync to others if we are the authority
|
# Sync to others if we are the authority
|
||||||
if is_multiplayer_authority() and can_rpc():
|
if is_multiplayer_authority() and can_rpc():
|
||||||
rpc("sync_attack_mode", is_attack_mode)
|
rpc("sync_charged_strike", is_charged_strike)
|
||||||
|
|
||||||
@rpc("any_peer", "call_local", "reliable")
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
func sync_attack_mode(state: bool):
|
func sync_charged_strike(state: bool):
|
||||||
# We WANT to trigger the setter to apply visuals on clients
|
is_charged_strike = state
|
||||||
# Using self.var triggers setter in GDScript
|
|
||||||
is_attack_mode = state
|
|
||||||
|
|
||||||
@export var is_bot: bool = false
|
@export var is_bot: bool = false
|
||||||
|
|
||||||
@@ -248,6 +246,9 @@ func _ready():
|
|||||||
# Character Pointer Visibility
|
# Character Pointer Visibility
|
||||||
# Visible to all human players. Green for local player, Red for others.
|
# Visible to all human players. Green for local player, Red for others.
|
||||||
var pointer = get_node_or_null("CharacterPointer")
|
var pointer = get_node_or_null("CharacterPointer")
|
||||||
|
|
||||||
|
# === Dynamically load new Dasher animations ===
|
||||||
|
_load_dasher_animations()
|
||||||
if pointer:
|
if pointer:
|
||||||
pointer.visible = true
|
pointer.visible = true
|
||||||
|
|
||||||
@@ -372,6 +373,47 @@ func _init_floor_spawn_anchor():
|
|||||||
if floor_spawn_top:
|
if floor_spawn_top:
|
||||||
floor_spawn_top.reparent(floor_spawn_anchor, false)
|
floor_spawn_top.reparent(floor_spawn_anchor, false)
|
||||||
|
|
||||||
|
func _load_dasher_animations():
|
||||||
|
"""Dynamically loads dasher animations from GLB files and adds them to the AnimationPlayer."""
|
||||||
|
if not anim_player: return
|
||||||
|
|
||||||
|
var anim_library = anim_player.get_animation_library("animation-pack")
|
||||||
|
if not anim_library:
|
||||||
|
anim_library = AnimationLibrary.new()
|
||||||
|
anim_player.add_animation_library("animation-pack", anim_library)
|
||||||
|
|
||||||
|
var dasher_files = [
|
||||||
|
{"path": "res://assets/characters/dashers/dasher_getting_hit.glb", "name": "dasher_getting_hit"},
|
||||||
|
{"path": "res://assets/characters/dashers/dasher_hit.glb", "name": "dasher_hit"},
|
||||||
|
{"path": "res://assets/characters/dashers/dasher_hold.glb", "name": "dasher_hold"},
|
||||||
|
{"path": "res://assets/characters/dashers/dasher_put.glb", "name": "dasher_put"},
|
||||||
|
{"path": "res://assets/characters/dashers/dasher_stun.glb", "name": "dasher_stun"},
|
||||||
|
{"path": "res://assets/characters/dashers/dasher_take.glb", "name": "dasher_take"}
|
||||||
|
]
|
||||||
|
|
||||||
|
for file_data in dasher_files:
|
||||||
|
var gltf_doc = GLTFDocument.new()
|
||||||
|
var gltf_state = GLTFState.new()
|
||||||
|
var error = gltf_doc.append_from_file(file_data.path, gltf_state)
|
||||||
|
|
||||||
|
if error == OK:
|
||||||
|
var anim_player_node = gltf_state.get_animation_player(0)
|
||||||
|
# Godot's GLTF importer creates an AnimationPlayer inside the scene
|
||||||
|
var scene = gltf_doc.generate_scene(gltf_state)
|
||||||
|
if scene:
|
||||||
|
var scene_anim_player = scene.find_child("AnimationPlayer", true, false)
|
||||||
|
if scene_anim_player:
|
||||||
|
var libs = scene_anim_player.get_animation_library_list()
|
||||||
|
for lib_name in libs:
|
||||||
|
var temp_lib = scene_anim_player.get_animation_library(lib_name)
|
||||||
|
for anim_name in temp_lib.get_animation_list():
|
||||||
|
var anim = temp_lib.get_animation(anim_name)
|
||||||
|
if not anim_library.has_animation(file_data.name):
|
||||||
|
anim_library.add_animation(file_data.name, anim)
|
||||||
|
scene.queue_free()
|
||||||
|
|
||||||
|
print("[Player] Dasher animations loaded into 'animation-pack'.")
|
||||||
|
|
||||||
@onready var floor_spawn_bot: AnimatedSprite3D = $floor_spawn_bot
|
@onready var floor_spawn_bot: AnimatedSprite3D = $floor_spawn_bot
|
||||||
@onready var floor_spawn_top: AnimatedSprite3D = $floor_spawn_top
|
@onready var floor_spawn_top: AnimatedSprite3D = $floor_spawn_top
|
||||||
@onready var vfx_scatter_knock: AnimatedSprite3D = $scatter_knock
|
@onready var vfx_scatter_knock: AnimatedSprite3D = $scatter_knock
|
||||||
@@ -874,10 +916,10 @@ func _refresh_player_visuals():
|
|||||||
color_to_apply = Color.CYAN # Stop n Go Freeze
|
color_to_apply = Color.CYAN # Stop n Go Freeze
|
||||||
elif is_slowed:
|
elif is_slowed:
|
||||||
color_to_apply = Color(0.6, 0.8, 1.0) # Slowed / Icy Blue
|
color_to_apply = Color(0.6, 0.8, 1.0) # Slowed / Icy Blue
|
||||||
elif is_attack_mode:
|
elif is_charged_strike:
|
||||||
color_to_apply = Color(1.0, 0.5, 0.5) # Attack Mode (Red Tint)
|
color_to_apply = Color(1.0, 0.5, 0.5) # Charged Strike (Red Tint)
|
||||||
elif is_carrying_tekton or is_knock_mode:
|
elif is_carrying_tekton:
|
||||||
color_to_apply = Color(1.0, 1.0, 0.0) # Carrying or Knocking (Yellow)
|
color_to_apply = Color(1.0, 1.0, 0.0) # Carrying (Yellow)
|
||||||
alpha_to_apply = 0.5 # 50% opacity when carrying Tekton
|
alpha_to_apply = 0.5 # 50% opacity when carrying Tekton
|
||||||
elif immunity_timer > 0:
|
elif immunity_timer > 0:
|
||||||
color_to_apply = Color(0.5, 1.0, 0.5) # Immunity (Light Green)
|
color_to_apply = Color(0.5, 1.0, 0.5) # Immunity (Light Green)
|
||||||
@@ -905,7 +947,7 @@ func update_rank_visuals(rank: int):
|
|||||||
if not pos_label:
|
if not pos_label:
|
||||||
return
|
return
|
||||||
|
|
||||||
if rank <= 3:
|
if rank <= 4:
|
||||||
pos_label.visible = true
|
pos_label.visible = true
|
||||||
if race_manager:
|
if race_manager:
|
||||||
pos_label.text = race_manager.get_ordinal_string(rank)
|
pos_label.text = race_manager.get_ordinal_string(rank)
|
||||||
@@ -913,9 +955,10 @@ func update_rank_visuals(rank: int):
|
|||||||
pos_label.text = str(rank)
|
pos_label.text = str(rank)
|
||||||
|
|
||||||
match rank:
|
match rank:
|
||||||
1: pos_label.modulate = Color(0.85, 0.0, 0.0) # Red
|
1: pos_label.modulate = Color(1.0, 0.84, 0.0) # Gold
|
||||||
2: pos_label.modulate = Color(0.0, 0.0, 1.0) # Blue
|
2: pos_label.modulate = Color(0.75, 0.75, 0.75) # Silver
|
||||||
3: pos_label.modulate = Color(1.0, 0.9, 0.0) # Yellow
|
3: pos_label.modulate = Color(0.8, 0.5, 0.2) # Bronze
|
||||||
|
4: pos_label.modulate = Color(0.5, 0.5, 0.5) # Grey
|
||||||
else:
|
else:
|
||||||
pos_label.visible = false
|
pos_label.visible = false
|
||||||
|
|
||||||
@@ -941,8 +984,8 @@ var slow_timer: float = 0.0
|
|||||||
var tekton_carry_timer: float = 0.0
|
var tekton_carry_timer: float = 0.0
|
||||||
const MAX_TEKTON_CARRY_TIME: float = 3.0
|
const MAX_TEKTON_CARRY_TIME: float = 3.0
|
||||||
|
|
||||||
var attack_mode_timer: float = 0.0
|
var charged_strike_timer: float = 0.0
|
||||||
const MAX_ATTACK_MODE_TIME: float = 5.0
|
const MAX_CHARGED_STRIKE_TIME: float = 5.0
|
||||||
|
|
||||||
@rpc("any_peer", "call_local")
|
@rpc("any_peer", "call_local")
|
||||||
func apply_stagger(duration: float = 1.5):
|
func apply_stagger(duration: float = 1.5):
|
||||||
@@ -1220,7 +1263,7 @@ func attempt_target_action(target_index: int):
|
|||||||
inventory_ui.deselect()
|
inventory_ui.deselect()
|
||||||
|
|
||||||
func activate_powerup(effect_id: int):
|
func activate_powerup(effect_id: int):
|
||||||
if is_carrying_tekton or is_knock_mode or is_attack_mode:
|
if is_carrying_tekton or is_charged_strike:
|
||||||
NotificationManager.send_message(self, "Cannot use Power-Up right now!", NotificationManager.MessageType.WARNING)
|
NotificationManager.send_message(self, "Cannot use Power-Up right now!", NotificationManager.MessageType.WARNING)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1253,7 +1296,7 @@ func activate_powerup(effect_id: int):
|
|||||||
|
|
||||||
func activate_held_powerup():
|
func activate_held_powerup():
|
||||||
"""Finds whichever powerup is currently held and activates it."""
|
"""Finds whichever powerup is currently held and activates it."""
|
||||||
if is_carrying_tekton or is_knock_mode or is_attack_mode:
|
if is_carrying_tekton or is_charged_strike:
|
||||||
NotificationManager.send_message(self, "Cannot use Power-Up right now!", NotificationManager.MessageType.WARNING)
|
NotificationManager.send_message(self, "Cannot use Power-Up right now!", NotificationManager.MessageType.WARNING)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1299,15 +1342,14 @@ func _process(delta):
|
|||||||
if movement_manager:
|
if movement_manager:
|
||||||
movement_manager._process(delta)
|
movement_manager._process(delta)
|
||||||
|
|
||||||
# Attack/Knock Mode Expiration Timer
|
# Charged Strike Expiration Timer
|
||||||
if is_multiplayer_authority() and (is_attack_mode or is_knock_mode):
|
if is_multiplayer_authority() and is_charged_strike:
|
||||||
if attack_mode_timer > 0:
|
if charged_strike_timer > 0:
|
||||||
attack_mode_timer -= delta
|
charged_strike_timer -= delta
|
||||||
if attack_mode_timer <= 0:
|
if charged_strike_timer <= 0:
|
||||||
attack_mode_timer = 0.0
|
charged_strike_timer = 0.0
|
||||||
is_attack_mode = false
|
is_charged_strike = false
|
||||||
is_knock_mode = false
|
NotificationManager.send_message(self, "Charged Strike Expired!", NotificationManager.MessageType.WARNING)
|
||||||
NotificationManager.send_message(self, "Knock Mode Expired!", NotificationManager.MessageType.WARNING)
|
|
||||||
if powerup_manager:
|
if powerup_manager:
|
||||||
powerup_manager.reset_boost()
|
powerup_manager.reset_boost()
|
||||||
|
|
||||||
@@ -2393,8 +2435,8 @@ func sync_snatch_tekton(carrier_path: NodePath, tekton_path: NodePath):
|
|||||||
tekton_carry_timer = 0.0
|
tekton_carry_timer = 0.0
|
||||||
|
|
||||||
# Visual/Logic side effects
|
# Visual/Logic side effects
|
||||||
if is_attack_mode:
|
if is_charged_strike:
|
||||||
is_attack_mode = false
|
is_charged_strike = false
|
||||||
|
|
||||||
SfxManager.play("pick_up_tekton_roaming")
|
SfxManager.play("pick_up_tekton_roaming")
|
||||||
play_pickup_animation()
|
play_pickup_animation()
|
||||||
@@ -2414,9 +2456,9 @@ func sync_grab_tekton(tekton_path: NodePath):
|
|||||||
self.is_carrying_tekton = true
|
self.is_carrying_tekton = true
|
||||||
tekton.set_carried(true, self )
|
tekton.set_carried(true, self )
|
||||||
|
|
||||||
# Disposed of AttackMode upon grab
|
# Disposed of Charged Strike upon grab
|
||||||
if is_attack_mode:
|
if is_charged_strike:
|
||||||
is_attack_mode = false
|
is_charged_strike = false
|
||||||
|
|
||||||
SfxManager.play("pick_up_tekton_roaming")
|
SfxManager.play("pick_up_tekton_roaming")
|
||||||
play_pickup_animation()
|
play_pickup_animation()
|
||||||
@@ -2558,38 +2600,17 @@ func sync_drop_tekton():
|
|||||||
|
|
||||||
print("[Player %s] Dropped Tekton at %s" % [name, current_position])
|
print("[Player %s] Dropped Tekton at %s" % [name, current_position])
|
||||||
|
|
||||||
# is_attack_mode is already declared at top of file (or inherited?)
|
|
||||||
# Keeping is_knock_mode here for now or moving it up would be better, but let's just fix the error first.
|
|
||||||
var is_knock_mode: bool = false:
|
|
||||||
set(value):
|
|
||||||
if is_knock_mode == value: return
|
|
||||||
is_knock_mode = value
|
|
||||||
if is_knock_mode:
|
|
||||||
attack_mode_timer = MAX_ATTACK_MODE_TIME
|
|
||||||
_refresh_player_visuals()
|
|
||||||
|
|
||||||
func enter_attack_mode():
|
|
||||||
|
func enter_charged_strike():
|
||||||
if not is_multiplayer_authority(): return
|
if not is_multiplayer_authority(): return
|
||||||
|
|
||||||
if is_invisible:
|
if is_invisible:
|
||||||
NotificationManager.send_message(self , "Cannot enter Attack Mode while in Ghost mode!", NotificationManager.MessageType.WARNING)
|
NotificationManager.send_message(self , "Cannot use Charged Strike while in Ghost mode!", NotificationManager.MessageType.WARNING)
|
||||||
return
|
return
|
||||||
|
|
||||||
is_attack_mode = true
|
is_charged_strike = true
|
||||||
is_knock_mode = false # Mutually exclusive
|
NotificationManager.send_message(self , "Charged Strike ACTIVATED (Red)", NotificationManager.MessageType.POWERUP)
|
||||||
NotificationManager.send_message(self , "Attack Mode ACTIVATED (Red)", NotificationManager.MessageType.POWERUP)
|
|
||||||
update_active_player_indicator()
|
|
||||||
|
|
||||||
func enter_knock_mode():
|
|
||||||
if not is_multiplayer_authority(): return
|
|
||||||
|
|
||||||
if is_invisible:
|
|
||||||
NotificationManager.send_message(self , "Cannot enter Knock Mode while in Ghost mode!", NotificationManager.MessageType.WARNING)
|
|
||||||
return
|
|
||||||
|
|
||||||
is_knock_mode = true
|
|
||||||
is_attack_mode = false # Mutually exclusive
|
|
||||||
NotificationManager.send_message(self , "Knock Mode ACTIVATED (Yellow)", NotificationManager.MessageType.POWERUP)
|
|
||||||
update_active_player_indicator()
|
update_active_player_indicator()
|
||||||
|
|
||||||
func update_active_player_indicator():
|
func update_active_player_indicator():
|
||||||
@@ -2619,8 +2640,8 @@ func knock_tekton():
|
|||||||
if not is_multiplayer_authority() or is_frozen or is_stop_frozen or is_invisible:
|
if not is_multiplayer_authority() or is_frozen or is_stop_frozen or is_invisible:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Requirement: Full Powerup Bar (or we are already in knock mode)
|
# Requirement: Full Powerup Bar (or we are already charged)
|
||||||
if not is_knock_mode and (not powerup_manager or not powerup_manager.can_use_special()):
|
if not is_charged_strike and (not powerup_manager or not powerup_manager.can_use_special()):
|
||||||
NotificationManager.send_message(self , "Need Full Boost to Knock!", NotificationManager.MessageType.WARNING)
|
NotificationManager.send_message(self , "Need Full Boost to Knock!", NotificationManager.MessageType.WARNING)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2633,8 +2654,8 @@ func knock_tekton():
|
|||||||
if is_multiplayer_authority():
|
if is_multiplayer_authority():
|
||||||
rpc("sync_knock_tekton", tekton.get_path())
|
rpc("sync_knock_tekton", tekton.get_path())
|
||||||
|
|
||||||
# Reset Knock Mode after successful hit
|
# Reset Charged Strike Mode after successful hit
|
||||||
is_knock_mode = false
|
is_charged_strike = false
|
||||||
NotificationManager.send_message(self , "Knock Successful!", NotificationManager.MessageType.POWERUP)
|
NotificationManager.send_message(self , "Knock Successful!", NotificationManager.MessageType.POWERUP)
|
||||||
update_active_player_indicator()
|
update_active_player_indicator()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ custom_minimum_size = Vector2(0, 20)
|
|||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_styles/background = SubResource("StyleBoxFlat_wusgu")
|
theme_override_styles/background = SubResource("StyleBoxFlat_wusgu")
|
||||||
theme_override_styles/fill = SubResource("StyleBoxFlat_2h0ma")
|
theme_override_styles/fill = SubResource("StyleBoxFlat_2h0ma")
|
||||||
|
show_percentage = false
|
||||||
|
|
||||||
[node name="ProgressLabel" type="Label" parent="CenterContainer/MainVBox/ProgressBar" unique_id=1293848486]
|
[node name="ProgressLabel" type="Label" parent="CenterContainer/MainVBox/ProgressBar" unique_id=1293848486]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|||||||
@@ -351,8 +351,8 @@ grow_vertical = 0
|
|||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(0, 44)
|
custom_minimum_size = Vector2(0, 44)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
selected = 3
|
selected = 4
|
||||||
item_count = 4
|
item_count = 5
|
||||||
popup/item_0/text = "Nakama - Localhost (Testing)"
|
popup/item_0/text = "Nakama - Localhost (Testing)"
|
||||||
popup/item_0/id = 0
|
popup/item_0/id = 0
|
||||||
popup/item_1/text = "Nakama - Remote Server (Host IP)"
|
popup/item_1/text = "Nakama - Remote Server (Host IP)"
|
||||||
@@ -361,6 +361,8 @@ popup/item_2/text = "LAN Direct (No Server)"
|
|||||||
popup/item_2/id = 2
|
popup/item_2/id = 2
|
||||||
popup/item_3/text = "Nakama - Tekton Dash EU"
|
popup/item_3/text = "Nakama - Tekton Dash EU"
|
||||||
popup/item_3/id = 3
|
popup/item_3/id = 3
|
||||||
|
popup/item_4/text = "Nakama - Tekton Dash Asia"
|
||||||
|
popup/item_4/id = 4
|
||||||
|
|
||||||
[node name="ServerIPInput" type="LineEdit" parent="ServerSelectionSection" unique_id=1444265129]
|
[node name="ServerIPInput" type="LineEdit" parent="ServerSelectionSection" unique_id=1444265129]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ var auth_mode: AuthMode = AuthMode.GUEST
|
|||||||
const SESSION_FILE := "user://auth_session.dat"
|
const SESSION_FILE := "user://auth_session.dat"
|
||||||
const CREDENTIALS_FILE := "user://auth_credentials.dat"
|
const CREDENTIALS_FILE := "user://auth_credentials.dat"
|
||||||
|
|
||||||
# Encryption key for session storage (replace with your own!)
|
# Encryption key for session storage (device-specific)
|
||||||
const ENCRYPTION_KEY := "tekton_secret_key_change_me_123"
|
var ENCRYPTION_KEY: String = OS.get_unique_id().sha256_text()
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
# Try to restore session on startup
|
# Try to restore session on startup
|
||||||
@@ -368,7 +368,7 @@ func login_with_steam() -> bool:
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
func _authenticate_steam_with_fallback(steamworks: Node) -> NakamaSession:
|
func _authenticate_steam_with_fallback(steamworks: Node) -> NakamaSession:
|
||||||
# Try proper Steam ticket auth first
|
# Proper Steam ticket auth
|
||||||
var auth_ticket = steamworks.get_auth_session_ticket()
|
var auth_ticket = steamworks.get_auth_session_ticket()
|
||||||
if not auth_ticket.is_empty():
|
if not auth_ticket.is_empty():
|
||||||
print("[AuthManager] Got Steam auth ticket, authenticating with Nakama...")
|
print("[AuthManager] Got Steam auth ticket, authenticating with Nakama...")
|
||||||
@@ -376,22 +376,10 @@ func _authenticate_steam_with_fallback(steamworks: Node) -> NakamaSession:
|
|||||||
if not session.is_exception():
|
if not session.is_exception():
|
||||||
return session
|
return session
|
||||||
print("[AuthManager] Steam ticket auth failed: %s" % session.get_exception().message)
|
print("[AuthManager] Steam ticket auth failed: %s" % session.get_exception().message)
|
||||||
print("[AuthManager] Falling back to Steam ID custom auth (dev mode)...")
|
|
||||||
|
|
||||||
# Fallback: use Steam ID + username to create an email-style account (works without publisher key)
|
|
||||||
var steam_id = str(steamworks.get_steam_user_id())
|
|
||||||
var steam_name = steamworks.get_steam_user_name()
|
|
||||||
if steam_id == "0" or steam_id.is_empty():
|
|
||||||
return null
|
return null
|
||||||
|
|
||||||
# Derive email and password from Steam credentials
|
print("[AuthManager] Steam auth ticket is empty.")
|
||||||
var email = steam_name.to_lower().replace(" ", "_") + "@steam.local"
|
return null
|
||||||
var password = steam_name # Default password = Steam username
|
|
||||||
var username = steam_name
|
|
||||||
|
|
||||||
print("[AuthManager] Using Steam email auth: %s (%s)" % [email, username])
|
|
||||||
var fallback_session: NakamaSession = await NakamaManager.client.authenticate_email_async(email, password, username, true)
|
|
||||||
return fallback_session
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Account Linking (Convert Guest to Full Account)
|
# Account Linking (Convert Guest to Full Account)
|
||||||
@@ -454,17 +442,33 @@ func logout() -> void:
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
func _connect_socket() -> bool:
|
func _connect_socket() -> bool:
|
||||||
|
if not NakamaManager.session:
|
||||||
|
push_error("[AuthManager] Socket connection failed: no Nakama session")
|
||||||
|
return false
|
||||||
|
|
||||||
if NakamaManager.socket and NakamaManager.socket.is_connected_to_host():
|
if NakamaManager.socket and NakamaManager.socket.is_connected_to_host():
|
||||||
if not multiplayer.has_multiplayer_peer() and NakamaManager.bridge:
|
if not multiplayer.has_multiplayer_peer() and NakamaManager.bridge:
|
||||||
multiplayer.set_multiplayer_peer(NakamaManager.bridge.multiplayer_peer)
|
multiplayer.set_multiplayer_peer(NakamaManager.bridge.multiplayer_peer)
|
||||||
NakamaManager.connected_to_nakama.emit()
|
NakamaManager.connected_to_nakama.emit()
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
if NakamaManager.socket:
|
||||||
|
NakamaManager.socket.close()
|
||||||
|
NakamaManager.socket = null
|
||||||
|
|
||||||
NakamaManager.socket = Nakama.create_socket_from(NakamaManager.client)
|
NakamaManager.socket = Nakama.create_socket_from(NakamaManager.client)
|
||||||
var result = await NakamaManager.socket.connect_async(NakamaManager.session)
|
var result = await NakamaManager.socket.connect_async(NakamaManager.session)
|
||||||
|
|
||||||
if result.is_exception():
|
if result.is_exception():
|
||||||
push_error("[AuthManager] Socket connection failed: " + result.get_exception().message)
|
var exception = result.get_exception()
|
||||||
|
var error_message = "Socket connection failed"
|
||||||
|
if exception and not exception.message.is_empty():
|
||||||
|
error_message = exception.message
|
||||||
|
elif exception and exception.status_code >= 0:
|
||||||
|
error_message = "Socket connection failed with error code %s" % exception.status_code
|
||||||
|
push_error("[AuthManager] " + error_message)
|
||||||
|
NakamaManager.socket.close()
|
||||||
|
NakamaManager.socket = null
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# Initialize multiplayer bridge
|
# Initialize multiplayer bridge
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ signal doors_swap_time_changed(time: int)
|
|||||||
signal doors_refresh_time_changed(time: int)
|
signal doors_refresh_time_changed(time: int)
|
||||||
signal doors_required_goals_changed(goals: int)
|
signal doors_required_goals_changed(goals: int)
|
||||||
|
|
||||||
|
# Gauntlet settings signals
|
||||||
|
signal gauntlet_round_duration_changed(duration: int)
|
||||||
|
signal gauntlet_cannon_interval_changed(interval: int)
|
||||||
|
signal gauntlet_volley_size_changed(size: int)
|
||||||
|
|
||||||
# Room data structure
|
# Room data structure
|
||||||
var current_room: Dictionary = {}
|
var current_room: Dictionary = {}
|
||||||
var players_in_room: Array = [] # [{id, name, is_ready}]
|
var players_in_room: Array = [] # [{id, name, is_ready}]
|
||||||
@@ -74,13 +79,18 @@ var doors_swap_time: int = 15
|
|||||||
var doors_refresh_time: int = 25
|
var doors_refresh_time: int = 25
|
||||||
var doors_required_goals: int = 8
|
var doors_required_goals: int = 8
|
||||||
|
|
||||||
|
# Gauntlet settings
|
||||||
|
var gauntlet_round_duration: int = 180
|
||||||
|
var gauntlet_cannon_interval: int = 5
|
||||||
|
var gauntlet_volley_size: int = 5
|
||||||
|
|
||||||
# Rematch tracking
|
# Rematch tracking
|
||||||
var rematch_votes: Array = [] # [player_id, ...]
|
var rematch_votes: Array = [] # [player_id, ...]
|
||||||
|
|
||||||
# Character and area selection
|
# Character and area selection
|
||||||
var available_characters: Array[String] = ["Copper", "Dabro", "Gatot", "Pip", "Random"]
|
var available_characters: Array[String] = ["Copper", "Dabro", "Gatot", "Pip", "Random"]
|
||||||
var available_areas: Array[String] = []
|
var available_areas: Array[String] = []
|
||||||
var available_game_modes: Array[String] = ["Freemode", "Stop n Go"]
|
var available_game_modes: Array[String] = ["Freemode", "Stop n Go", "Candy Cannon Survival"]
|
||||||
var selected_area: String = "Freemode Arena" # Host-controlled
|
var selected_area: String = "Freemode Arena" # Host-controlled
|
||||||
var game_mode: String = "Freemode" # Host-controlled
|
var game_mode: String = "Freemode" # Host-controlled
|
||||||
var local_character_index: int = 0 # Local player's character index
|
var local_character_index: int = 0 # Local player's character index
|
||||||
@@ -135,6 +145,8 @@ func _update_available_areas(mode: String) -> void:
|
|||||||
available_areas = ["Freemode Arena", "Classic", "Colloseum"]
|
available_areas = ["Freemode Arena", "Classic", "Colloseum"]
|
||||||
"Stop n Go":
|
"Stop n Go":
|
||||||
available_areas = ["Stop N Go Arena"]
|
available_areas = ["Stop N Go Arena"]
|
||||||
|
"Candy Cannon Survival":
|
||||||
|
available_areas = ["Gauntlet Arena"]
|
||||||
_:
|
_:
|
||||||
available_areas = ["Classic"]
|
available_areas = ["Classic"]
|
||||||
|
|
||||||
@@ -537,6 +549,37 @@ func sync_doors_required_goals(goals: int) -> void:
|
|||||||
doors_required_goals = goals
|
doors_required_goals = goals
|
||||||
emit_signal("doors_required_goals_changed", goals)
|
emit_signal("doors_required_goals_changed", goals)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Gauntlet Settings
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
func set_gauntlet_round_duration(duration: int) -> void:
|
||||||
|
gauntlet_round_duration = duration
|
||||||
|
if is_host: rpc("sync_gauntlet_round_duration", duration)
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func sync_gauntlet_round_duration(duration: int) -> void:
|
||||||
|
gauntlet_round_duration = duration
|
||||||
|
emit_signal("gauntlet_round_duration_changed", duration)
|
||||||
|
|
||||||
|
func set_gauntlet_cannon_interval(interval: int) -> void:
|
||||||
|
gauntlet_cannon_interval = interval
|
||||||
|
if is_host: rpc("sync_gauntlet_cannon_interval", interval)
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func sync_gauntlet_cannon_interval(interval: int) -> void:
|
||||||
|
gauntlet_cannon_interval = interval
|
||||||
|
emit_signal("gauntlet_cannon_interval_changed", interval)
|
||||||
|
|
||||||
|
func set_gauntlet_volley_size(size: int) -> void:
|
||||||
|
gauntlet_volley_size = size
|
||||||
|
if is_host: rpc("sync_gauntlet_volley_size", size)
|
||||||
|
|
||||||
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func sync_gauntlet_volley_size(size: int) -> void:
|
||||||
|
gauntlet_volley_size = size
|
||||||
|
emit_signal("gauntlet_volley_size_changed", size)
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Character Selection
|
# Character Selection
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -682,14 +725,40 @@ func set_game_mode(mode: String) -> void:
|
|||||||
rpc("sync_game_mode", mode)
|
rpc("sync_game_mode", mode)
|
||||||
|
|
||||||
_update_available_areas(mode)
|
_update_available_areas(mode)
|
||||||
|
|
||||||
|
# Only force switch the area if the selected area is NOT valid for this mode
|
||||||
if selected_area not in available_areas:
|
if selected_area not in available_areas:
|
||||||
set_area(available_areas[0])
|
set_area(available_areas[0])
|
||||||
|
else:
|
||||||
|
# Important: even if the area is technically in the list, if they just clicked Free Mode
|
||||||
|
# we should default them to Free Mode Area if they were on Stop n Go Area before.
|
||||||
|
if mode == "Free Mode" and "Free Mode Area" in available_areas:
|
||||||
|
set_area("Free Mode Area")
|
||||||
|
elif mode == "Stop n Go" and "Stop n Go Area" in available_areas:
|
||||||
|
set_area("Stop n Go Area")
|
||||||
|
elif mode == "Tekton Doors" and "Tekton Doors Area" in available_areas:
|
||||||
|
set_area("Tekton Doors Area")
|
||||||
|
elif mode == "Gauntlet" and "Candy Pump Arena" in available_areas:
|
||||||
|
set_area("Candy Pump Arena")
|
||||||
|
|
||||||
@rpc("authority", "call_local", "reliable")
|
@rpc("authority", "call_local", "reliable")
|
||||||
func sync_game_mode(mode: String) -> void:
|
func sync_game_mode(mode: String) -> void:
|
||||||
"""Sync game mode selection from host to clients."""
|
"""Sync game mode selection from host to clients."""
|
||||||
game_mode = mode
|
game_mode = mode
|
||||||
_update_available_areas(mode)
|
_update_available_areas(mode)
|
||||||
|
|
||||||
|
# Try to smart-match the client's local area to the mode as well so their UI matches
|
||||||
|
if mode == "Free Mode" and "Free Mode Area" in available_areas:
|
||||||
|
selected_area = "Free Mode Area"
|
||||||
|
elif mode == "Stop n Go" and "Stop n Go Area" in available_areas:
|
||||||
|
selected_area = "Stop n Go Area"
|
||||||
|
elif mode == "Tekton Doors" and "Tekton Doors Area" in available_areas:
|
||||||
|
selected_area = "Tekton Doors Area"
|
||||||
|
elif mode == "Gauntlet" and "Candy Pump Arena" in available_areas:
|
||||||
|
selected_area = "Candy Pump Arena"
|
||||||
|
elif selected_area not in available_areas:
|
||||||
|
selected_area = available_areas[0]
|
||||||
|
|
||||||
emit_signal("game_mode_changed", mode)
|
emit_signal("game_mode_changed", mode)
|
||||||
|
|
||||||
func start_game(force: bool = false) -> void:
|
func start_game(force: bool = false) -> void:
|
||||||
@@ -715,6 +784,10 @@ func start_game(force: bool = false) -> void:
|
|||||||
rpc("sync_doors_swap_time", doors_swap_time)
|
rpc("sync_doors_swap_time", doors_swap_time)
|
||||||
rpc("sync_doors_refresh_time", doors_refresh_time)
|
rpc("sync_doors_refresh_time", doors_refresh_time)
|
||||||
rpc("sync_doors_required_goals", doors_required_goals)
|
rpc("sync_doors_required_goals", doors_required_goals)
|
||||||
|
# Sync gauntlet settings
|
||||||
|
rpc("sync_gauntlet_round_duration", gauntlet_round_duration)
|
||||||
|
rpc("sync_gauntlet_cannon_interval", gauntlet_cannon_interval)
|
||||||
|
rpc("sync_gauntlet_volley_size", gauntlet_volley_size)
|
||||||
# Sync game mode
|
# Sync game mode
|
||||||
rpc("sync_game_mode", game_mode)
|
rpc("sync_game_mode", game_mode)
|
||||||
|
|
||||||
@@ -790,6 +863,9 @@ func request_room_info(requester_id: int, requester_name: String, requester_char
|
|||||||
rpc_id(requester_id, "sync_doors_swap_time", doors_swap_time)
|
rpc_id(requester_id, "sync_doors_swap_time", doors_swap_time)
|
||||||
rpc_id(requester_id, "sync_doors_refresh_time", doors_refresh_time)
|
rpc_id(requester_id, "sync_doors_refresh_time", doors_refresh_time)
|
||||||
rpc_id(requester_id, "sync_doors_required_goals", doors_required_goals)
|
rpc_id(requester_id, "sync_doors_required_goals", doors_required_goals)
|
||||||
|
rpc_id(requester_id, "sync_gauntlet_round_duration", gauntlet_round_duration)
|
||||||
|
rpc_id(requester_id, "sync_gauntlet_cannon_interval", gauntlet_cannon_interval)
|
||||||
|
rpc_id(requester_id, "sync_gauntlet_volley_size", gauntlet_volley_size)
|
||||||
rpc_id(requester_id, "sync_game_mode", game_mode)
|
rpc_id(requester_id, "sync_game_mode", game_mode)
|
||||||
rpc_id(requester_id, "sync_area", selected_area)
|
rpc_id(requester_id, "sync_area", selected_area)
|
||||||
|
|
||||||
|
|||||||
@@ -289,7 +289,9 @@ func get_action_display(action_key: String) -> String:
|
|||||||
"grab": "ctrl_grab",
|
"grab": "ctrl_grab",
|
||||||
"use_powerup": "ctrl_use_powerup",
|
"use_powerup": "ctrl_use_powerup",
|
||||||
"tekton_grab": "ctrl_tekton_grab",
|
"tekton_grab": "ctrl_tekton_grab",
|
||||||
|
"action_grab_tekton": "ctrl_tekton_grab",
|
||||||
"attack_mode": "ctrl_attack_mode",
|
"attack_mode": "ctrl_attack_mode",
|
||||||
|
"action_knock_tekton": "ctrl_attack_mode",
|
||||||
}
|
}
|
||||||
if ctrl_key_map.has(action_key):
|
if ctrl_key_map.has(action_key):
|
||||||
return get_controller_binding_text(ctrl_key_map[action_key])
|
return get_controller_binding_text(ctrl_key_map[action_key])
|
||||||
@@ -315,7 +317,16 @@ func is_controller_button_used(button_index: int) -> String:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
func get_control_keycode(action_name: String) -> int:
|
func get_control_keycode(action_name: String) -> int:
|
||||||
return settings.controls.get(action_name, -1)
|
# Map friendly names to their internal settings.controls keys
|
||||||
|
var mapped_name = action_name
|
||||||
|
if action_name == "tekton_grab":
|
||||||
|
mapped_name = "action_grab_tekton"
|
||||||
|
elif action_name == "attack_mode":
|
||||||
|
mapped_name = "action_knock_tekton"
|
||||||
|
elif action_name == "grab":
|
||||||
|
mapped_name = "action_grab"
|
||||||
|
|
||||||
|
return settings.controls.get(mapped_name, -1)
|
||||||
|
|
||||||
func get_control_text(action_name: String) -> String:
|
func get_control_text(action_name: String) -> String:
|
||||||
var code = get_control_keycode(action_name)
|
var code = get_control_keycode(action_name)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
# Standard Nakama Configuration
|
# Standard Nakama Configuration
|
||||||
var nakama_server_key = "defaultkey"
|
var nakama_server_key = OS.get_environment("NAKAMA_SERVER_KEY") if OS.has_environment("NAKAMA_SERVER_KEY") else ProjectSettings.get_setting("network/nakama/server_key", "defaultkey")
|
||||||
var nakama_host = "tektondash.vps.webdock.cloud"
|
var nakama_host = OS.get_environment("NAKAMA_HOST") if OS.has_environment("NAKAMA_HOST") else ProjectSettings.get_setting("network/nakama/host", "tektondash.vps.webdock.cloud")
|
||||||
var nakama_port = 7350
|
var nakama_port = OS.get_environment("NAKAMA_PORT").to_int() if OS.has_environment("NAKAMA_PORT") else ProjectSettings.get_setting("network/nakama/port", 7350)
|
||||||
var nakama_scheme = "http"
|
var nakama_scheme = OS.get_environment("NAKAMA_SCHEME") if OS.has_environment("NAKAMA_SCHEME") else ProjectSettings.get_setting("network/nakama/scheme", "http")
|
||||||
|
|
||||||
# Core Nakama Variables
|
# Core Nakama Variables
|
||||||
var client: NakamaClient
|
var client: NakamaClient
|
||||||
@@ -123,8 +123,16 @@ func connect_to_nakama_async(email: String = "", password: String = "") -> bool:
|
|||||||
return false
|
return false
|
||||||
elif socket_result.is_exception():
|
elif socket_result.is_exception():
|
||||||
var err = socket_result.get_exception()
|
var err = socket_result.get_exception()
|
||||||
printerr("[NakamaManager] Socket Error: %s (Code: %s)" % [err.message, err.status_code])
|
var err_msg = "Socket connection failed"
|
||||||
emit_signal("connection_failed", err.message)
|
if err and not err.message.is_empty():
|
||||||
|
err_msg = err.message
|
||||||
|
elif err and err.status_code >= 0:
|
||||||
|
err_msg = "Socket connection failed with code %s" % err.status_code
|
||||||
|
printerr("[NakamaManager] Socket Error: %s (Code: %s)" % [err_msg, err.status_code if err else -1])
|
||||||
|
emit_signal("connection_failed", err_msg)
|
||||||
|
if socket:
|
||||||
|
socket.close()
|
||||||
|
socket = null
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# 3. Initialize Multiplayer Bridge
|
# 3. Initialize Multiplayer Bridge
|
||||||
|
|||||||
@@ -414,6 +414,15 @@ func spawn_tiles_around(count: int = 4):
|
|||||||
# FIX 1: Make tekton look/rotate toward a random spawning direction
|
# FIX 1: Make tekton look/rotate toward a random spawning direction
|
||||||
if not is_carried and not is_thrown:
|
if not is_carried and not is_thrown:
|
||||||
var random_angle = rng.randf_range(0, TAU)
|
var random_angle = rng.randf_range(0, TAU)
|
||||||
|
|
||||||
|
# If it's a static turret, make it face the target tile it's about to spawn instead
|
||||||
|
if is_static_turret:
|
||||||
|
# We don't have a specific target yet, but we can pick an average direction
|
||||||
|
# Or just let it throw randomly like the others. Wait, the user wants:
|
||||||
|
# "static tekton, should facing toward where they're going to thrown the tiles"
|
||||||
|
# We'll calculate rotation inside the spawning loop for static turrets.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
rotation.y = random_angle
|
rotation.y = random_angle
|
||||||
|
|
||||||
# Play throw animation
|
# Play throw animation
|
||||||
@@ -439,6 +448,12 @@ func spawn_tiles_around(count: int = 4):
|
|||||||
|
|
||||||
var pos = current_position + Vector2i(x, y)
|
var pos = current_position + Vector2i(x, y)
|
||||||
|
|
||||||
|
# For static turret, update rotation to face the exact tile being thrown
|
||||||
|
if is_static_turret and not is_carried and not is_thrown:
|
||||||
|
var throw_dir = Vector3(x, 0, y).normalized()
|
||||||
|
if throw_dir.length_squared() > 0.01:
|
||||||
|
rotation.y = atan2(throw_dir.x, throw_dir.z)
|
||||||
|
|
||||||
# Don't overwrite the Tekton's own cell? Or do?
|
# Don't overwrite the Tekton's own cell? Or do?
|
||||||
# Maybe avoid center.
|
# Maybe avoid center.
|
||||||
if x == 0 and y == 0: continue
|
if x == 0 and y == 0: continue
|
||||||
@@ -472,6 +487,9 @@ func spawn_tiles_around(count: int = 4):
|
|||||||
if roll < 0.6 or (LobbyManager and LobbyManager.game_mode == "Stop n Go"):
|
if roll < 0.6 or (LobbyManager and LobbyManager.game_mode == "Stop n Go"):
|
||||||
# 60% Normal Tile (7-10) OR 100% if Stop n Go (User Request)
|
# 60% Normal Tile (7-10) OR 100% if Stop n Go (User Request)
|
||||||
item_id = rng.randi_range(7, 10)
|
item_id = rng.randi_range(7, 10)
|
||||||
|
elif LobbyManager and LobbyManager.get_game_mode() == GameMode.Mode.GAUNTLET:
|
||||||
|
# Gauntlet mode: No power-up spawns from Tekton grabs
|
||||||
|
item_id = rng.randi_range(7, 10)
|
||||||
else:
|
else:
|
||||||
# 40% PowerUp (11-14)
|
# 40% PowerUp (11-14)
|
||||||
var mode = GameMode.Mode.FREEMODE
|
var mode = GameMode.Mode.FREEMODE
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ func _ready() -> void:
|
|||||||
server_option.selected = 0
|
server_option.selected = 0
|
||||||
elif NakamaManager.nakama_host == "tektondash.vps.webdock.cloud":
|
elif NakamaManager.nakama_host == "tektondash.vps.webdock.cloud":
|
||||||
server_option.selected = 3
|
server_option.selected = 3
|
||||||
|
elif NakamaManager.nakama_host == "thunderobot.tapir-atria.ts.net":
|
||||||
|
server_option.selected = 4
|
||||||
else:
|
else:
|
||||||
server_option.selected = 1
|
server_option.selected = 1
|
||||||
server_ip_input.text = NakamaManager.nakama_host if NakamaManager.nakama_host != "localhost" else "127.0.0.1"
|
server_ip_input.text = NakamaManager.nakama_host if NakamaManager.nakama_host != "localhost" else "127.0.0.1"
|
||||||
@@ -241,6 +243,11 @@ func _on_server_option_selected(index: int) -> void:
|
|||||||
server_ip_input.visible = false
|
server_ip_input.visible = false
|
||||||
if lan_section: lan_section.visible = false
|
if lan_section: lan_section.visible = false
|
||||||
NakamaManager.set_server("tektondash.vps.webdock.cloud")
|
NakamaManager.set_server("tektondash.vps.webdock.cloud")
|
||||||
|
elif index == 4:
|
||||||
|
# Tekton Dash Asia
|
||||||
|
server_ip_input.visible = false
|
||||||
|
if lan_section: lan_section.visible = false
|
||||||
|
NakamaManager.set_server("thunderobot.tapir-atria.ts.net")
|
||||||
|
|
||||||
func _on_server_ip_submitted(new_text: String) -> void:
|
func _on_server_ip_submitted(new_text: String) -> void:
|
||||||
if server_option and server_option.selected == 1:
|
if server_option and server_option.selected == 1:
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
nakama:
|
nakama:
|
||||||
container_name: nakama
|
container_name: Tekton-Dash-Asia
|
||||||
image: heroiclabs/nakama:3.24.2
|
image: heroiclabs/nakama:latest
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -35,6 +35,9 @@ services:
|
|||||||
--console.username admin
|
--console.username admin
|
||||||
--console.password password
|
--console.password password
|
||||||
--runtime.path /nakama/data/modules
|
--runtime.path /nakama/data/modules
|
||||||
|
--runtime.js_entrypoint tekton_admin.js
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./nakama:/nakama/data/modules
|
- ./nakama:/nakama/data/modules
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
class Log:
|
||||||
|
|
||||||
|
enum {ERROR, INFO, DEBUG}
|
||||||
|
|
||||||
|
static func _s(lvl):
|
||||||
|
if lvl == ERROR:
|
||||||
|
return "ERROR"
|
||||||
|
elif lvl == INFO:
|
||||||
|
return "INFO"
|
||||||
|
elif lvl == DEBUG:
|
||||||
|
return "DEBUG"
|
||||||
|
return "WTF"
|
||||||
|
|
||||||
|
static func __log(lvl, msg, data):
|
||||||
|
print("======= %s: %s" % [_s(lvl), msg])
|
||||||
|
if not data.is_empty():
|
||||||
|
var json = JSON.new()
|
||||||
|
print(json.stringify(data, " ", true))
|
||||||
|
|
||||||
|
static func error(msg, data={}):
|
||||||
|
__log(ERROR, msg, data)
|
||||||
|
|
||||||
|
static func info(msg, data={}):
|
||||||
|
__log(INFO, msg, data)
|
||||||
|
|
||||||
|
static func debug(msg, data={}):
|
||||||
|
__log(DEBUG, msg, data)
|
||||||
|
|
||||||
|
const REAL_ASSERT = false
|
||||||
|
|
||||||
|
var _success = false
|
||||||
|
var _start_time = 0
|
||||||
|
var _quit = false
|
||||||
|
var _disabled = false
|
||||||
|
var __me = ""
|
||||||
|
|
||||||
|
# Override this to specify test initialization (called on _ready if not disabled)
|
||||||
|
# You can run your whole test here, and call done() when finished
|
||||||
|
func setup():
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Override this to do cleanup, make assertions at the end of the test (called on _exit_tree)
|
||||||
|
# NOTE: You can still fail here if you like with fail() or by failing an assertion
|
||||||
|
func teardown():
|
||||||
|
pass
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
__me = get_script()
|
||||||
|
while __me.get_base_script() != null:
|
||||||
|
__me = __me.get_base_script()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
if _disabled:
|
||||||
|
set_process(false)
|
||||||
|
set_physics_process(false)
|
||||||
|
set_process_input(false)
|
||||||
|
set_process_unhandled_input(false)
|
||||||
|
set_process_unhandled_key_input(false)
|
||||||
|
done()
|
||||||
|
Log.info("SKIP: %s" % __get_source(self))
|
||||||
|
return
|
||||||
|
_start_time = Time.get_ticks_usec()
|
||||||
|
Log.info("RUNNING: %s" % __get_source(self))
|
||||||
|
setup()
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
if _disabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
teardown()
|
||||||
|
Log.info("%s: %s" % ["SUCCESS" if _success else "!!!!!!!!!!!!!!!!!!! FAILURE", __get_source(self)])
|
||||||
|
|
||||||
|
func __get_source(who):
|
||||||
|
if who == null or who.get_script() == null:
|
||||||
|
return "Unknown source: %s" % str(who)
|
||||||
|
return who.get_script().resource_path
|
||||||
|
|
||||||
|
func __get_caller():
|
||||||
|
for s in get_stack():
|
||||||
|
if __me.resource_path != s.source:
|
||||||
|
return s
|
||||||
|
return null
|
||||||
|
|
||||||
|
func __get_assertion():
|
||||||
|
var stack = get_stack()
|
||||||
|
stack.reverse()
|
||||||
|
for s in stack:
|
||||||
|
if __me.resource_path == s.source:
|
||||||
|
return s
|
||||||
|
return null
|
||||||
|
|
||||||
|
func __assert(v1, v2):
|
||||||
|
if not (v1 == v2):
|
||||||
|
Log.error("Assert Failed", {"caller": __get_caller(), "assertion": __get_assertion(), "full_stack": get_stack()})
|
||||||
|
print_tree_pretty()
|
||||||
|
_quit = true
|
||||||
|
if REAL_ASSERT:
|
||||||
|
assert(v1 == v2)
|
||||||
|
if v1 == v2:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
func assert_time(max_time):
|
||||||
|
__assert(max_time > float(Time.get_ticks_usec() - _start_time) / 1000.0 / 1000.0, true)
|
||||||
|
|
||||||
|
### Returns true if the assertion failed, so you can do:
|
||||||
|
### if assert_cond(cond):
|
||||||
|
### return # Assertion failed!
|
||||||
|
func assert_cond(cond):
|
||||||
|
return __assert(cond, true)
|
||||||
|
|
||||||
|
func assert_false(cond):
|
||||||
|
return __assert(cond, false)
|
||||||
|
|
||||||
|
func assert_equal(v1, v2):
|
||||||
|
return __assert(v1, v2)
|
||||||
|
|
||||||
|
func done():
|
||||||
|
_success = true
|
||||||
|
_quit = true
|
||||||
|
|
||||||
|
func fail():
|
||||||
|
_success = false
|
||||||
|
_quit = true
|
||||||
|
__assert(false, true)
|
||||||
|
|
||||||
|
func disable():
|
||||||
|
_disabled = true
|
||||||
|
|
||||||
|
func is_disabled():
|
||||||
|
return _disabled
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=5
|
||||||
|
|
||||||
|
_global_script_classes=[{
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaAPI",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaAPI.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaAsyncResult",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaAsyncResult.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaClient",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/client/NakamaClient.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaException",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaException.gd"
|
||||||
|
}, {
|
||||||
|
"base": "Node",
|
||||||
|
"class": &"NakamaHTTPAdapter",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/client/NakamaHTTPAdapter.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaLogger",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaLogger.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaMultiplayerBridge",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaMultiplayerBridge.gd"
|
||||||
|
}, {
|
||||||
|
"base": "MultiplayerPeerExtension",
|
||||||
|
"class": &"NakamaMultiplayerPeer",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaMultiplayerPeer.gd"
|
||||||
|
}, {
|
||||||
|
"base": "NakamaAsyncResult",
|
||||||
|
"class": &"NakamaRTAPI",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaRTAPI.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaRTMessage",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaRTMessage.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaSerializer",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaSerializer.gd"
|
||||||
|
}, {
|
||||||
|
"base": "NakamaAsyncResult",
|
||||||
|
"class": &"NakamaSession",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaSession.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaSocket",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/socket/NakamaSocket.gd"
|
||||||
|
}, {
|
||||||
|
"base": "Node",
|
||||||
|
"class": &"NakamaSocketAdapter",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/socket/NakamaSocketAdapter.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaStorageObjectId",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaStorageObjectId.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaWriteStorageObject",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaWriteStorageObject.gd"
|
||||||
|
}]
|
||||||
|
_global_script_class_icons={
|
||||||
|
"NakamaAPI": "",
|
||||||
|
"NakamaAsyncResult": "",
|
||||||
|
"NakamaClient": "",
|
||||||
|
"NakamaException": "",
|
||||||
|
"NakamaHTTPAdapter": "",
|
||||||
|
"NakamaLogger": "",
|
||||||
|
"NakamaMultiplayerBridge": "",
|
||||||
|
"NakamaMultiplayerPeer": "",
|
||||||
|
"NakamaRTAPI": "",
|
||||||
|
"NakamaRTMessage": "",
|
||||||
|
"NakamaSerializer": "",
|
||||||
|
"NakamaSession": "",
|
||||||
|
"NakamaSocket": "",
|
||||||
|
"NakamaSocketAdapter": "",
|
||||||
|
"NakamaStorageObjectId": "",
|
||||||
|
"NakamaWriteStorageObject": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
run/main_scene="res://tester.tscn"
|
||||||
|
config/features=PackedStringArray("4.0")
|
||||||
|
|
||||||
|
[autoload]
|
||||||
|
|
||||||
|
Nakama="*res://addons/com.heroiclabs.nakama/Nakama.gd"
|
||||||
|
Config="*res://utils/config.gd"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
END_STRING="======= TESTS END"
|
||||||
|
PROJECT_PATH="test_suite/"
|
||||||
|
GODOT_BIN="godot"
|
||||||
|
OUT=`$GODOT_BIN --headless --debug --path test_suite/ -s res://runner.gd`
|
||||||
|
RUN=`echo $OUT | grep "$END_STRING"`
|
||||||
|
RES=`echo $OUT | grep FAILURE`
|
||||||
|
|
||||||
|
echo "$OUT"
|
||||||
|
|
||||||
|
if [ -z "$RUN" ]; then
|
||||||
|
echo "Run failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$RES" ]; then
|
||||||
|
echo "Tests passed!"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Tests failed!"
|
||||||
|
exit 1
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
var me = ProjectSettings.localize_path(ProjectSettings.globalize_path(get_script().resource_path))
|
||||||
|
var dir = ProjectSettings.localize_path(me.get_base_dir())
|
||||||
|
|
||||||
|
if dir != "res://":
|
||||||
|
print("Must be run with the script dir as the res:// path")
|
||||||
|
print("Current path: ", ProjectSettings.globalize_path(dir))
|
||||||
|
print("RES:// path: ", ProjectSettings.globalize_path("res://"))
|
||||||
|
quit()
|
||||||
|
return
|
||||||
|
var n = load("res://tester.gd").new()
|
||||||
|
root.add_child(n)
|
||||||
|
current_scene = n
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"HOST": "127.0.0.1",
|
||||||
|
"PORT": 7350,
|
||||||
|
"SCHEME": "http",
|
||||||
|
"SERVER_KEY": "defaultkey"
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var current = null
|
||||||
|
var tests = []
|
||||||
|
var frame_time = 0.0
|
||||||
|
var fixed_time = 0.0
|
||||||
|
var dirs = ["tests"]
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
while not dirs.is_empty():
|
||||||
|
var d = dirs.pop_front()
|
||||||
|
_expand(d, dirs)
|
||||||
|
print("Running %d tests:\n%s" % [tests.size(), tests])
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
frame_time += delta
|
||||||
|
_check_end()
|
||||||
|
|
||||||
|
func _physics_process(delta):
|
||||||
|
fixed_time += delta
|
||||||
|
_check_end()
|
||||||
|
|
||||||
|
func _check_end():
|
||||||
|
if current == null:
|
||||||
|
if tests.size() == 0:
|
||||||
|
_end_tests()
|
||||||
|
return
|
||||||
|
current = load(tests.pop_front()).new()
|
||||||
|
add_child(current)
|
||||||
|
elif current._quit:
|
||||||
|
if not current._success:
|
||||||
|
_end_tests()
|
||||||
|
current.queue_free()
|
||||||
|
current = null
|
||||||
|
|
||||||
|
func _end_tests():
|
||||||
|
print("======= TESTS END")
|
||||||
|
set_process(false)
|
||||||
|
set_physics_process(false)
|
||||||
|
await get_tree().create_timer(1.0).timeout
|
||||||
|
get_tree().call_deferred("quit")
|
||||||
|
|
||||||
|
func _expand(p_name, r_dirs):
|
||||||
|
var dir = DirAccess.open("res://")
|
||||||
|
if dir.change_dir(p_name) != OK:
|
||||||
|
print("Unable to chdir into: %s" % p_name)
|
||||||
|
return
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var f = dir.get_next()
|
||||||
|
while f != "":
|
||||||
|
if dir.current_is_dir():
|
||||||
|
r_dirs.append("%s/%s" % [p_name, f])
|
||||||
|
if f.ends_with("_test.gd"):
|
||||||
|
tests.append("%s/%s" % [p_name, f])
|
||||||
|
f = dir.get_next()
|
||||||
|
dir.list_dir_end()
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bglgahpqv1ojs"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://tester.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="Control" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
extends "res://base_test.gd"
|
||||||
|
|
||||||
|
func setup():
|
||||||
|
var username = str(randi_range(1000, 100000))
|
||||||
|
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||||
|
# POST
|
||||||
|
var session = await client.authenticate_custom_async("MyIdentifier")
|
||||||
|
if assert_cond(session.is_valid()) or assert_cond(!session.is_expired()):
|
||||||
|
return
|
||||||
|
# PUT
|
||||||
|
var update = await client.update_account_async(session, username)
|
||||||
|
if assert_false(update.is_exception()):
|
||||||
|
return
|
||||||
|
# GET
|
||||||
|
var account : NakamaAPI.ApiAccount = await client.get_account_async(session)
|
||||||
|
if assert_false(account.is_exception()):
|
||||||
|
return
|
||||||
|
if assert_cond(account.user.username == username):
|
||||||
|
return
|
||||||
|
# POST - DELETE
|
||||||
|
var group : NakamaAPI.ApiGroup = await client.create_group_async(session, "MyGroupName3")
|
||||||
|
if assert_false(group.is_exception()):
|
||||||
|
return
|
||||||
|
var delete = await client.delete_group_async(session, group.id)
|
||||||
|
if assert_false(delete.is_exception()):
|
||||||
|
return
|
||||||
|
# All good
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
assert_time(3)
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
extends "res://base_test.gd"
|
||||||
|
|
||||||
|
var content = {"My": "message"}
|
||||||
|
var match_string_props = {"region": "europe"}
|
||||||
|
var match_numeric_props = {"rank": 8}
|
||||||
|
var got_msg = false
|
||||||
|
var got_match = false
|
||||||
|
var socket1 = null
|
||||||
|
var socket2 = null
|
||||||
|
|
||||||
|
func setup():
|
||||||
|
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||||
|
|
||||||
|
var session1 = await client.authenticate_custom_async("MyIdentifier")
|
||||||
|
if assert_cond(session1.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var session2 = await client.authenticate_custom_async("MyIdentifier2")
|
||||||
|
if assert_cond(session1.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
socket1 = Nakama.create_socket_from(client)
|
||||||
|
socket1.received_channel_message.connect(self._on_socket1_message)
|
||||||
|
socket1.received_matchmaker_matched.connect(self._on_socket1_matchmaker_matched)
|
||||||
|
var done = await socket1.connect_async(session1)
|
||||||
|
# Check that connection succeded
|
||||||
|
if assert_false(done.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
socket2 = Nakama.create_socket_from(client)
|
||||||
|
done = await socket2.connect_async(session2)
|
||||||
|
# Check that connection succeded
|
||||||
|
if assert_false(done.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Join room
|
||||||
|
var room1 = await socket1.join_chat_async("MyRoom", NakamaSocket.ChannelType.Room)
|
||||||
|
if assert_false(room1.is_exception()):
|
||||||
|
return
|
||||||
|
var room2 = await socket2.join_chat_async("MyRoom", NakamaSocket.ChannelType.Room)
|
||||||
|
if assert_false(room2.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Socket 2 send message to socket 1
|
||||||
|
var msg_ack = await socket2.write_chat_message_async(room2.id, content)
|
||||||
|
if assert_false(msg_ack.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var ticket1 = await socket1.add_matchmaker_async("+properties.region:europe +properties.rank:>=7 +properties.rank:<=9", 2, 8, match_string_props, match_numeric_props)
|
||||||
|
if assert_false(ticket1.is_exception()):
|
||||||
|
return
|
||||||
|
var ticket2 = await socket2.add_matchmaker_async("+properties.region:europe +properties.rank:>=7 +properties.rank:<=9", 2, 8, match_string_props, match_numeric_props)
|
||||||
|
if assert_false(ticket2.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
func _on_socket1_message(msg):
|
||||||
|
if assert_equal(msg.content, JSON.stringify(content)):
|
||||||
|
return
|
||||||
|
got_msg = true
|
||||||
|
check_end()
|
||||||
|
|
||||||
|
func _on_socket1_matchmaker_matched(p_matchmaker_matched):
|
||||||
|
if assert_equal(JSON.stringify(p_matchmaker_matched.users[0].string_properties), JSON.stringify(match_string_props)):
|
||||||
|
return
|
||||||
|
if assert_equal(JSON.stringify(p_matchmaker_matched.users[0].numeric_properties), JSON.stringify(match_numeric_props)):
|
||||||
|
return
|
||||||
|
if assert_equal(JSON.stringify(p_matchmaker_matched.users[1].string_properties), JSON.stringify(match_string_props)):
|
||||||
|
return
|
||||||
|
if assert_equal(JSON.stringify(p_matchmaker_matched.users[1].numeric_properties), JSON.stringify(match_numeric_props)):
|
||||||
|
return
|
||||||
|
got_match = true
|
||||||
|
check_end()
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
assert_time(60)
|
||||||
|
|
||||||
|
func check_end():
|
||||||
|
if got_match and got_msg:
|
||||||
|
done()
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
extends "res://base_test.gd"
|
||||||
|
|
||||||
|
var content = {"My": "message"}
|
||||||
|
var match_props = {"region": "europe"}
|
||||||
|
var got_msg = false
|
||||||
|
var got_match = false
|
||||||
|
var socket1 : NakamaSocket = null
|
||||||
|
var socket2 : NakamaSocket = null
|
||||||
|
|
||||||
|
func setup():
|
||||||
|
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||||
|
|
||||||
|
var session1 = await client.authenticate_custom_async("MyIdentifier")
|
||||||
|
if assert_cond(session1.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var session2 = await client.authenticate_custom_async("MyIdentifier2")
|
||||||
|
if assert_cond(session2.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
socket1 = Nakama.create_socket_from(client)
|
||||||
|
socket1.received_party_close.connect(self._on_party_close)
|
||||||
|
socket1.received_party_data.connect(self._on_party_data)
|
||||||
|
socket1.received_party_join_request.connect(self._on_party_join_request)
|
||||||
|
|
||||||
|
var conn = await socket1.connect_async(session1)
|
||||||
|
# Check that connection succeded
|
||||||
|
if assert_false(conn.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var party = await socket1.create_party_async(false, 2)
|
||||||
|
if assert_false(party.is_exception()):
|
||||||
|
return
|
||||||
|
#done()
|
||||||
|
|
||||||
|
socket2 = Nakama.create_socket_from(client)
|
||||||
|
socket2.received_party.connect(self._on_party)
|
||||||
|
socket2.received_party_close.connect(self._on_party_close)
|
||||||
|
socket2.received_party_join_request.connect(self._on_party_join_request)
|
||||||
|
socket2.received_party_leader.connect(self._on_party_leader)
|
||||||
|
socket2.received_party_presence.connect(self._on_party_presence)
|
||||||
|
|
||||||
|
var conn2 = await socket2.connect_async(session2)
|
||||||
|
# Check that connection succeded
|
||||||
|
if assert_false(conn2.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var join = await socket2.join_party_async(party.party_id)
|
||||||
|
if assert_false(join.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
func _on_party_join_request(party_join_request : NakamaRTAPI.PartyJoinRequest):
|
||||||
|
prints("_on_party_join_request", party_join_request)
|
||||||
|
var requests : NakamaRTAPI.PartyJoinRequest = await socket1.list_party_join_requests_async(party_join_request.party_id)
|
||||||
|
if assert_false(requests.is_exception()):
|
||||||
|
return
|
||||||
|
if assert_cond(requests.presences.size() == 1):
|
||||||
|
return
|
||||||
|
|
||||||
|
await socket1.accept_party_member_async(party_join_request.party_id, party_join_request.presences[0])
|
||||||
|
await socket1.promote_party_member(party_join_request.party_id, party_join_request.presences[0])
|
||||||
|
|
||||||
|
func _on_party(party):
|
||||||
|
prints("_on_party", party)
|
||||||
|
|
||||||
|
func _on_party_close(party_close):
|
||||||
|
prints("_on_party_close", party_close)
|
||||||
|
|
||||||
|
func _on_party_data(data : NakamaRTAPI.PartyData):
|
||||||
|
prints("_on_party_data", data)
|
||||||
|
var left = await socket1.leave_party_async(data.party_id)
|
||||||
|
if assert_false(left.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
func _on_party_leader(party_leader : NakamaRTAPI.PartyLeader):
|
||||||
|
prints("_on_party_leader", party_leader)
|
||||||
|
var ticket = await socket2.add_matchmaker_party_async(party_leader.party_id)
|
||||||
|
if assert_false(ticket.is_exception()):
|
||||||
|
return
|
||||||
|
_on_party_ticket(ticket)
|
||||||
|
|
||||||
|
func _on_party_ticket(ticket : NakamaRTAPI.PartyMatchmakerTicket):
|
||||||
|
prints("_on_party_ticket", ticket)
|
||||||
|
var removed = await socket2.remove_matchmaker_party_async(ticket.party_id, ticket.ticket)
|
||||||
|
if assert_false(removed.is_exception()):
|
||||||
|
return
|
||||||
|
var sent = await socket2.send_party_data_async(ticket.party_id, 1, "asd")
|
||||||
|
if assert_false(sent.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
func _on_party_presence(party_presence : NakamaRTAPI.PartyPresenceEvent):
|
||||||
|
prints("_on_party_presence", party_presence)
|
||||||
|
var left = party_presence.leaves.size() == 1
|
||||||
|
if left:
|
||||||
|
var closed = await socket2.close_party_async(party_presence.party_id)
|
||||||
|
if assert_false(closed.is_exception()):
|
||||||
|
return
|
||||||
|
done()
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
extends "res://base_test.gd"
|
||||||
|
|
||||||
|
var _connected = false
|
||||||
|
|
||||||
|
func setup():
|
||||||
|
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||||
|
|
||||||
|
var session = await client.authenticate_custom_async("MyIdentifier")
|
||||||
|
if assert_cond(session.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var socket = Nakama.create_socket_from(client)
|
||||||
|
socket.connected.connect(self._on_socket_connected)
|
||||||
|
var conn = await socket.connect_async(session)
|
||||||
|
# Check that connection succeded
|
||||||
|
if assert_false(conn.is_exception()):
|
||||||
|
return
|
||||||
|
# Check that signal has been called
|
||||||
|
if assert_cond(_connected):
|
||||||
|
return
|
||||||
|
done()
|
||||||
|
|
||||||
|
func _on_socket_connected():
|
||||||
|
_connected = true
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
assert_time(3)
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
extends "res://base_test.gd"
|
||||||
|
|
||||||
|
const COLLECTION = "ACollection"
|
||||||
|
const K1 = "Question"
|
||||||
|
const K2 = "Answer"
|
||||||
|
const V1 = {"question": "Ultimate question"}
|
||||||
|
const V2 = {"answer": "42"}
|
||||||
|
|
||||||
|
func setup():
|
||||||
|
var username = str(randi_range(1000, 100000))
|
||||||
|
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||||
|
|
||||||
|
var session = await client.authenticate_custom_async("MyIdentifier")
|
||||||
|
if assert_cond(session.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var objs = [
|
||||||
|
NakamaWriteStorageObject.new(COLLECTION, K1, 1, 1, to_json(V1), ""),
|
||||||
|
NakamaWriteStorageObject.new(COLLECTION, K2, 1, 1, to_json(V2), "")
|
||||||
|
]
|
||||||
|
var write : NakamaAPI.ApiStorageObjectAcks = await client.write_storage_objects_async(session, objs)
|
||||||
|
if assert_false(write.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var objs_ids = []
|
||||||
|
for a in write.acks:
|
||||||
|
#var obj : NakamaAPI.ApiStorageObjectAck = a
|
||||||
|
objs_ids.append(NakamaStorageObjectId.new(a.collection, a.key, a.user_id, a.version))
|
||||||
|
|
||||||
|
var read : NakamaAPI.ApiStorageObjects = await client.read_storage_objects_async(session, objs_ids)
|
||||||
|
if assert_false(read.is_exception()):
|
||||||
|
return
|
||||||
|
if assert_equal(read.objects.size(), 2):
|
||||||
|
return
|
||||||
|
if assert_equal(read.objects[0].collection, COLLECTION):
|
||||||
|
return
|
||||||
|
if assert_cond(read.objects[0].key in [K1, K2]):
|
||||||
|
return
|
||||||
|
if assert_cond(to_json(parse_json(read.objects[0].value)) in [to_json(V1), to_json(V2)]):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Delete one
|
||||||
|
var del = await client.delete_storage_objects_async(session, [objs_ids[0]])
|
||||||
|
if assert_false(del.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Confirm that one was deleted
|
||||||
|
var read2 : NakamaAPI.ApiStorageObjects = await client.read_storage_objects_async(session, objs_ids)
|
||||||
|
if assert_false(read2.is_exception()):
|
||||||
|
return
|
||||||
|
if assert_equal(read2.objects.size(), 1):
|
||||||
|
return
|
||||||
|
if assert_equal(read2.objects[0].collection, COLLECTION):
|
||||||
|
return
|
||||||
|
if assert_equal(read2.objects[0].key, objs_ids[1].key):
|
||||||
|
return
|
||||||
|
if assert_cond(to_json(parse_json(read2.objects[0].value)) in [to_json(V1), to_json(V2)]):
|
||||||
|
return
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
assert_time(3)
|
||||||
|
|
||||||
|
func to_json(value) -> String:
|
||||||
|
return JSON.stringify(value)
|
||||||
|
|
||||||
|
func parse_json(value):
|
||||||
|
var json = JSON.new()
|
||||||
|
if json.parse(value) != OK:
|
||||||
|
return null
|
||||||
|
return json.get_data()
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var HOST = "127.0.0.1"
|
||||||
|
var PORT = 7350
|
||||||
|
var SCHEME = "http"
|
||||||
|
var SERVER_KEY = "defaultkey"
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
var f = FileAccess.open("res://settings.json", FileAccess.READ)
|
||||||
|
if not f:
|
||||||
|
return
|
||||||
|
var json = JSON.new()
|
||||||
|
var error = json.parse(f.get_as_text())
|
||||||
|
var parsed = json.get_data()
|
||||||
|
if error != OK or typeof(parsed) != TYPE_DICTIONARY:
|
||||||
|
return
|
||||||
|
for k in parsed:
|
||||||
|
match k:
|
||||||
|
"HOST": HOST = parsed[k]
|
||||||
|
"PORT": PORT = parsed[k]
|
||||||
|
"SCHEME": SCHEME = parsed[k]
|
||||||
|
"SERVER_KEY": SERVER_KEY = parsed[k]
|
||||||
Binary file not shown.
Reference in New Issue
Block a user