summer-engine 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +27 -5
- package/.codex-plugin/plugin.json +28 -6
- package/.cursor-plugin/plugin.json +27 -5
- package/.factory-plugin/plugin.json +27 -5
- package/.opencode/plugins/summer.js +1 -1
- package/CHANGELOG.md +102 -0
- package/README.md +18 -3
- package/assets/audit/scene_audit.gd +4136 -0
- package/assets/seeing/seeing_probe.gd +1873 -0
- package/dist/cli/commands/skills.js +20 -2
- package/dist/core/api-client.d.ts +9 -1
- package/dist/core/api-client.js +21 -4
- package/dist/core/capabilities/agent-playbook.js +46 -0
- package/dist/core/capabilities/audit/accept.d.ts +55 -0
- package/dist/core/capabilities/audit/accept.js +147 -0
- package/dist/core/capabilities/audit/args.d.ts +95 -0
- package/dist/core/capabilities/audit/args.js +89 -0
- package/dist/core/capabilities/audit/audit.d.ts +56 -0
- package/dist/core/capabilities/audit/audit.js +287 -0
- package/dist/core/capabilities/audit/frame.d.ts +14 -0
- package/dist/core/capabilities/audit/frame.js +35 -0
- package/dist/core/capabilities/audit/gaps.d.ts +96 -0
- package/dist/core/capabilities/audit/gaps.js +257 -0
- package/dist/core/capabilities/audit/judge.d.ts +162 -0
- package/dist/core/capabilities/audit/judge.js +1134 -0
- package/dist/core/capabilities/audit/math.d.ts +110 -0
- package/dist/core/capabilities/audit/math.js +263 -0
- package/dist/core/capabilities/audit/summary.d.ts +74 -0
- package/dist/core/capabilities/audit/summary.js +212 -0
- package/dist/core/capabilities/board.d.ts +31 -0
- package/dist/core/capabilities/board.js +43 -0
- package/dist/core/capabilities/camera-view.d.ts +4 -2
- package/dist/core/capabilities/camera-view.js +6 -3
- package/dist/core/capabilities/connect-signal.d.ts +85 -0
- package/dist/core/capabilities/connect-signal.js +285 -0
- package/dist/core/capabilities/engine-fallbacks.d.ts +5 -0
- package/dist/core/capabilities/engine-fallbacks.js +5 -0
- package/dist/core/capabilities/engine-ops.d.ts +5 -1
- package/dist/core/capabilities/engine-ops.js +0 -3
- package/dist/core/capabilities/engine-receipt.d.ts +12 -0
- package/dist/core/capabilities/engine-receipt.js +26 -0
- package/dist/core/capabilities/file-read.d.ts +64 -0
- package/dist/core/capabilities/file-read.js +325 -0
- package/dist/core/capabilities/game-task-plan.js +4 -2
- package/dist/core/capabilities/grep.d.ts +60 -0
- package/dist/core/capabilities/grep.js +190 -0
- package/dist/core/capabilities/inspect-node.d.ts +56 -0
- package/dist/core/capabilities/inspect-node.js +225 -0
- package/dist/core/capabilities/inspect-resource.d.ts +61 -0
- package/dist/core/capabilities/inspect-resource.js +376 -0
- package/dist/core/capabilities/math3d.d.ts +28 -0
- package/dist/core/capabilities/math3d.js +88 -0
- package/dist/core/capabilities/motion-generation.d.ts +62 -0
- package/dist/core/capabilities/motion-generation.js +138 -0
- package/dist/core/capabilities/placement-batch.d.ts +102 -0
- package/dist/core/capabilities/placement-batch.js +483 -0
- package/dist/core/capabilities/placement-math.d.ts +70 -0
- package/dist/core/capabilities/placement-math.js +209 -0
- package/dist/core/capabilities/placement-script.d.ts +42 -0
- package/dist/core/capabilities/placement-script.js +1096 -0
- package/dist/core/capabilities/placement.d.ts +485 -0
- package/dist/core/capabilities/placement.js +1731 -0
- package/dist/core/capabilities/project-context.d.ts +33 -2
- package/dist/core/capabilities/project-context.js +87 -4
- package/dist/core/capabilities/replace-node.d.ts +91 -0
- package/dist/core/capabilities/replace-node.js +423 -0
- package/dist/core/capabilities/runtime-control.d.ts +18 -2
- package/dist/core/capabilities/runtime-control.js +67 -2
- package/dist/core/capabilities/scene-batch.d.ts +63 -0
- package/dist/core/capabilities/scene-batch.js +394 -0
- package/dist/core/capabilities/scene-readback.d.ts +36 -0
- package/dist/core/capabilities/scene-readback.js +54 -0
- package/dist/core/capabilities/seeing/args.d.ts +348 -0
- package/dist/core/capabilities/seeing/args.js +178 -0
- package/dist/core/capabilities/seeing/candidates.d.ts +127 -0
- package/dist/core/capabilities/seeing/candidates.js +282 -0
- package/dist/core/capabilities/seeing/math.d.ts +134 -0
- package/dist/core/capabilities/seeing/math.js +269 -0
- package/dist/core/capabilities/seeing/probe.d.ts +62 -0
- package/dist/core/capabilities/seeing/probe.js +188 -0
- package/dist/core/capabilities/seeing/scoring.d.ts +235 -0
- package/dist/core/capabilities/seeing/scoring.js +639 -0
- package/dist/core/capabilities/seeing/seeing.d.ts +233 -0
- package/dist/core/capabilities/seeing/seeing.js +1341 -0
- package/dist/core/capabilities/seeing/shot-store.d.ts +52 -0
- package/dist/core/capabilities/seeing/shot-store.js +204 -0
- package/dist/core/capabilities/surface-snap-mesh.d.ts +88 -0
- package/dist/core/capabilities/surface-snap-mesh.js +425 -0
- package/dist/core/capabilities/surface-snap.d.ts +77 -0
- package/dist/core/capabilities/surface-snap.js +461 -0
- package/dist/core/capabilities/tool-dispatch.js +231 -72
- package/dist/core/capabilities/tscn.d.ts +85 -0
- package/dist/core/capabilities/tscn.js +391 -0
- package/dist/core/capabilities/world-snapshot.d.ts +58 -0
- package/dist/core/capabilities/world-snapshot.js +150 -0
- package/dist/core/capability-skew.d.ts +5 -5
- package/dist/core/capability-skew.js +14 -6
- package/dist/core/headless/worker-engine-client.d.ts +1 -1
- package/dist/core/headless/worker-engine-client.js +2 -2
- package/dist/core/library-read.d.ts +37 -5
- package/dist/core/library-read.js +264 -33
- package/dist/core/library-search.d.ts +2 -2
- package/dist/installer/agent-config.js +5 -3
- package/dist/installer/agent-table.d.ts +16 -0
- package/dist/installer/agent-table.js +52 -17
- package/dist/mcp/server.js +9 -3
- package/dist/mcp/tools/asset-tools.js +21 -3
- package/dist/mcp/tools/audit-tools.d.ts +23 -0
- package/dist/mcp/tools/audit-tools.js +60 -0
- package/dist/mcp/tools/debug-tools.js +3 -1
- package/dist/mcp/tools/file-tools.js +17 -4
- package/dist/mcp/tools/generate-tools.js +74 -66
- package/dist/mcp/tools/library-tools.js +2 -0
- package/dist/mcp/tools/perception-tools.js +6 -19
- package/dist/mcp/tools/placement-tools.d.ts +24 -0
- package/dist/mcp/tools/placement-tools.js +91 -0
- package/dist/mcp/tools/project-tools.js +18 -15
- package/dist/mcp/tools/scene-tools.js +71 -83
- package/dist/mcp/tools/seeing-tools.d.ts +24 -0
- package/dist/mcp/tools/seeing-tools.js +64 -0
- package/dist/mcp/tools/spatial-tools.js +17 -9
- package/dist/mcp/tools/visual-tools.js +59 -7
- package/gemini-extension.json +26 -4
- package/integrations/amp/README.md +1 -1
- package/integrations/crush/README.md +1 -1
- package/integrations/grok-build/README.md +1 -1
- package/integrations/hermes/README.md +1 -1
- package/integrations/kimi-code/README.md +1 -1
- package/integrations/mistral-vibe/README.md +1 -1
- package/integrations/opencode/README.md +1 -1
- package/integrations/rovo-dev/README.md +1 -1
- package/integrations/vscode-copilot/README.md +1 -1
- package/integrations/warp/README.md +1 -1
- package/integrations/windsurf/README.md +1 -1
- package/library/references/mcp-tools-reference/mcp-tools-reference.md +59 -15
- package/library/references/mcp-tools-reference/resource.yaml +1 -1
- package/library/references/summer-folder/resource.yaml +1 -1
- package/library/references/summer-folder/summer-folder.md +1 -1
- package/library/skills/3d-lighting/SKILL.md +1 -1
- package/library/skills/README.md +36 -14
- package/library/skills/adaptive-music/SKILL.md +1 -1
- package/library/skills/agent-playtesting/SKILL.md +1 -1
- package/library/skills/ai-characters-in-games/SKILL.md +220 -0
- package/library/skills/ai-characters-in-games/resource.yaml +30 -0
- package/library/skills/ambient-bed/SKILL.md +1 -1
- package/library/skills/animated-loop/SKILL.md +1 -1
- package/library/skills/animation-tree/SKILL.md +1 -1
- package/library/skills/art-direction/SKILL.md +1 -1
- package/library/skills/asset-strategy/SKILL.md +2 -1
- package/library/skills/asset-strategy/resource.yaml +2 -1
- package/library/skills/audio-direction/SKILL.md +1 -1
- package/library/skills/auto-fire-targeting/SKILL.md +1 -1
- package/library/skills/brainstorm-game/SKILL.md +1 -1
- package/library/skills/brainstorming/SKILL.md +1 -1
- package/library/skills/browse-templates/SKILL.md +3 -3
- package/library/skills/browse-templates/resource.yaml +1 -1
- package/library/skills/camera-rigs/SKILL.md +128 -0
- package/library/skills/camera-rigs/resource.yaml +29 -0
- package/library/skills/celeste-momentum-platforming/SKILL.md +1 -1
- package/library/skills/character-animation-wiring/SKILL.md +1 -1
- package/library/skills/character-model/SKILL.md +1 -1
- package/library/skills/character-movement/SKILL.md +326 -0
- package/library/skills/character-movement/references/default-third-person-controller.gd +201 -0
- package/library/skills/character-movement/references/default-third-person-verification-probe.gd +154 -0
- package/library/skills/character-movement/references/movement-3d.md +136 -0
- package/library/skills/character-movement/references/platformer-feel.md +83 -0
- package/library/skills/character-movement/resource.yaml +40 -0
- package/library/skills/character-portrait/SKILL.md +1 -1
- package/library/skills/cinematic-cutscene/SKILL.md +1 -1
- package/library/skills/combat-basics/SKILL.md +159 -0
- package/library/skills/combat-basics/references/projectiles-and-aoe.md +100 -0
- package/library/skills/combat-basics/resource.yaml +34 -0
- package/library/skills/concept-art/SKILL.md +1 -1
- package/library/skills/create-asset-sheet/SKILL.md +1 -1
- package/library/skills/debug/SKILL.md +1 -1
- package/library/skills/debugging-game-feel/SKILL.md +1 -1
- package/library/skills/design-level/SKILL.md +1 -1
- package/library/skills/design-mechanic/SKILL.md +1 -1
- package/library/skills/design-npc/SKILL.md +1 -1
- package/library/skills/diagnosing-perf-regressions/SKILL.md +1 -1
- package/library/skills/dispatching-parallel-agents/SKILL.md +1 -1
- package/library/skills/driving-the-editor-ui/SKILL.md +1 -1
- package/library/skills/environment-kit/SKILL.md +1 -1
- package/library/skills/export-and-ship/SKILL.md +1 -1
- package/library/skills/fabricating-assets/SKILL.md +1 -1
- package/library/skills/facial-and-lipsync/SKILL.md +1 -1
- package/library/skills/fps-controller/SKILL.md +9 -7
- package/library/skills/fps-controller/resource.yaml +4 -1
- package/library/skills/game-feel/SKILL.md +1 -1
- package/library/skills/gameskill/SKILL.md +1 -1
- package/library/skills/gdscript-patterns/SKILL.md +1 -1
- package/library/skills/generate-motion/SKILL.md +9 -9
- package/library/skills/generate-motion/resource.yaml +2 -1
- package/library/skills/godogen-visual-proof-game-generation/SKILL.md +1 -1
- package/library/skills/headless-scripting/SKILL.md +1 -1
- package/library/skills/in-game-purchases/SKILL.md +254 -0
- package/library/skills/in-game-purchases/resource.yaml +29 -0
- package/library/skills/instantiate-asset-pack/SKILL.md +1 -1
- package/library/skills/investigating-bugs/SKILL.md +1 -1
- package/library/skills/lumera-single-image-scene-reconstruction/SKILL.md +1 -1
- package/library/skills/make-game/SKILL.md +25 -3
- package/library/skills/make-game/references/2d-platformer-polish.md +93 -0
- package/library/skills/make-game/references/2d-platformer.md +117 -0
- package/library/skills/make-game/references/2d-rpg-content.md +234 -0
- package/library/skills/make-game/references/2d-rpg.md +241 -0
- package/library/skills/make-game/references/2d-survivors-progression.md +201 -0
- package/library/skills/make-game/references/2d-survivors.md +237 -0
- package/library/skills/make-game/references/3d-fps-systems.md +216 -0
- package/library/skills/make-game/references/3d-fps.md +177 -0
- package/library/skills/make-game/references/3d-horror-systems.md +186 -0
- package/library/skills/make-game/references/3d-horror.md +201 -0
- package/library/skills/make-game/references/3d-third-person-systems.md +220 -0
- package/library/skills/make-game/references/3d-third-person.md +215 -0
- package/library/skills/make-game/resource.yaml +6 -1
- package/library/skills/multiplayer/SKILL.md +150 -0
- package/library/skills/multiplayer/resource.yaml +33 -0
- package/library/skills/multiplayer-movement/SKILL.md +510 -0
- package/library/skills/multiplayer-movement/files.md +501 -0
- package/library/skills/multiplayer-movement/resource.yaml +30 -0
- package/library/skills/multiplayer-project/SKILL.md +521 -0
- package/library/skills/multiplayer-project/files.md +448 -0
- package/library/skills/multiplayer-project/resource.yaml +36 -0
- package/library/skills/multiplayer-publish/SKILL.md +229 -0
- package/library/skills/multiplayer-publish/export-errors.md +89 -0
- package/library/skills/multiplayer-publish/resource.yaml +28 -0
- package/library/skills/multiplayer-state/SKILL.md +438 -0
- package/library/skills/multiplayer-state/files.md +592 -0
- package/library/skills/multiplayer-state/resource.yaml +35 -0
- package/library/skills/multiplayer-testing/SKILL.md +236 -0
- package/library/skills/multiplayer-testing/resource.yaml +39 -0
- package/library/skills/music-track/SKILL.md +1 -1
- package/library/skills/navigate-summer/SKILL.md +1 -1
- package/library/skills/new-project/SKILL.md +1 -1
- package/library/skills/organic-model/SKILL.md +1 -1
- package/library/skills/pixel-art/SKILL.md +1 -1
- package/library/skills/play/SKILL.md +1 -1
- package/library/skills/playtesting-a-feature/SKILL.md +1 -1
- package/library/skills/procedural-animation/SKILL.md +2 -2
- package/library/skills/procedural-animation/resource.yaml +1 -1
- package/library/skills/prop-model/SKILL.md +1 -1
- package/library/skills/psx-retro-rendering/SKILL.md +1 -1
- package/library/skills/realtime-wet-surfaces/SKILL.md +2 -2
- package/library/skills/remote-deploy/SKILL.md +1 -1
- package/library/skills/retarget/SKILL.md +1 -1
- package/library/skills/running-in-the-cloud/SKILL.md +1 -1
- package/library/skills/save-load/SKILL.md +156 -0
- package/library/skills/save-load/resource.yaml +27 -0
- package/library/skills/scene-composition/SKILL.md +1 -1
- package/library/skills/scene-hierarchy-design/SKILL.md +1 -1
- package/library/skills/scene-scripting/SKILL.md +1 -1
- package/library/skills/scene-to-level/SKILL.md +1 -1
- package/library/skills/skill-create/SKILL.md +1 -1
- package/library/skills/skill-improve/SKILL.md +1 -1
- package/library/skills/skill-test/SKILL.md +1 -1
- package/library/skills/skintokens-auto-rigging/SKILL.md +1 -1
- package/library/skills/skybox-panorama/SKILL.md +1 -1
- package/library/skills/sound-effect/SKILL.md +1 -1
- package/library/skills/spatial-placement/SKILL.md +134 -3
- package/library/skills/spatial-placement/references/kit-placement-tools.md +364 -0
- package/library/skills/spatial-placement/resource.yaml +16 -2
- package/library/skills/sprite-sheet/SKILL.md +1 -1
- package/library/skills/summer-analytics/SKILL.md +147 -0
- package/library/skills/summer-analytics/resource.yaml +28 -0
- package/library/skills/summer-friends/SKILL.md +226 -0
- package/library/skills/summer-friends/resource.yaml +28 -0
- package/library/skills/summer-leaderboards/SKILL.md +231 -0
- package/library/skills/summer-leaderboards/resource.yaml +30 -0
- package/library/skills/summer-match-results/SKILL.md +286 -0
- package/library/skills/summer-match-results/resource.yaml +32 -0
- package/library/skills/summer-matchmaking/SKILL.md +301 -0
- package/library/skills/summer-matchmaking/resource.yaml +32 -0
- package/library/skills/summer-parties/SKILL.md +253 -0
- package/library/skills/summer-parties/resource.yaml +30 -0
- package/library/skills/summer-player-data/SKILL.md +322 -0
- package/library/skills/summer-player-data/resource.yaml +30 -0
- package/library/skills/summer-store/SKILL.md +271 -0
- package/library/skills/summer-store/resource.yaml +29 -0
- package/library/skills/summer-world-chat/SKILL.md +195 -0
- package/library/skills/summer-world-chat/resource.yaml +30 -0
- package/library/skills/summer-world-saves/SKILL.md +284 -0
- package/library/skills/summer-world-saves/resource.yaml +29 -0
- package/library/skills/text-to-motion/SKILL.md +99 -0
- package/library/skills/text-to-motion/prompts-and-controls.md +34 -0
- package/library/skills/text-to-motion/resource.yaml +41 -0
- package/library/skills/text-to-motion/rigging-for-text-motion.md +54 -0
- package/library/skills/tileable-texture/SKILL.md +1 -1
- package/library/skills/trailer-shot/SKILL.md +1 -1
- package/library/skills/tune-performance/SKILL.md +1 -1
- package/library/skills/ui-basics/SKILL.md +10 -1
- package/library/skills/ui-graphics/SKILL.md +1 -1
- package/library/skills/use-widget-asset/SKILL.md +1 -1
- package/library/skills/using-summer/SKILL.md +2 -2
- package/library/skills/using-summer/resource.yaml +2 -2
- package/library/skills/vehicle-model/SKILL.md +1 -1
- package/library/skills/verification-before-completion/SKILL.md +1 -1
- package/library/skills/verifying-scenes/SKILL.md +76 -2
- package/library/skills/verifying-scenes/resource.yaml +13 -2
- package/library/skills/vfx-dissolve/SKILL.md +1 -1
- package/library/skills/vfx-fire/SKILL.md +1 -1
- package/library/skills/vfx-hit-spark/SKILL.md +1 -1
- package/library/skills/vfx-lightning/SKILL.md +1 -1
- package/library/skills/vfx-magic-glow/SKILL.md +1 -1
- package/library/skills/vfx-muzzle-flash/SKILL.md +1 -1
- package/library/skills/vfx-smoke/SKILL.md +1 -1
- package/library/skills/vfx-water-ripple/SKILL.md +1 -1
- package/library/skills/voice-line/SKILL.md +1 -1
- package/library/skills/web-and-touch-input/SKILL.md +38 -0
- package/library/skills/web-and-touch-input/resource.yaml +28 -0
- package/library/skills/web-compatibility-look/SKILL.md +62 -0
- package/library/skills/web-compatibility-look/resource.yaml +30 -0
- package/library/skills/world-building-3d/SKILL.md +8 -1
- package/library/skills/world-building-3d/references/spatial-tools.md +1 -1
- package/library/skills/world-building-3d/resource.yaml +2 -1
- package/library/skills/writing-plans/SKILL.md +1 -1
- package/library/skills/writing-skills/SKILL.md +1 -1
- package/library/templates/3d-lan-multiplayer-starter/resource.yaml +2 -2
- package/library/tools/attach-to-surface/resource.yaml +142 -0
- package/library/tools/batch/resource.yaml +9 -3
- package/library/tools/connect-ports/resource.yaml +120 -0
- package/library/tools/connect-signal/resource.yaml +3 -2
- package/library/tools/debug-views/resource.yaml +94 -0
- package/library/tools/frame-nodes/resource.yaml +104 -0
- package/library/tools/frame-shot/resource.yaml +134 -0
- package/library/tools/generate-motion/resource.yaml +44 -9
- package/library/tools/get-board/resource.yaml +50 -0
- package/library/tools/get-project-context/resource.yaml +13 -2
- package/library/tools/grep/resource.yaml +77 -0
- package/library/tools/inspect-asset/resource.yaml +71 -0
- package/library/tools/inspect-node/resource.yaml +9 -2
- package/library/tools/inspect-resource/resource.yaml +16 -5
- package/library/tools/instantiate-scene/resource.yaml +27 -2
- package/library/tools/measure/resource.yaml +96 -0
- package/library/tools/place-adjacent/resource.yaml +113 -0
- package/library/tools/play/resource.yaml +15 -2
- package/library/tools/raycast/resource.yaml +101 -0
- package/library/tools/read-file/resource.yaml +36 -2
- package/library/tools/read-library/resource.yaml +2 -2
- package/library/tools/repeat-along/resource.yaml +129 -0
- package/library/tools/replace-node/resource.yaml +11 -5
- package/library/tools/scene-audit/resource.yaml +144 -0
- package/library/tools/screenshot/resource.yaml +13 -2
- package/library/tools/search-assets/resource.yaml +18 -0
- package/library/tools/shot-sheet/resource.yaml +110 -0
- package/library/tools/snap-to-surface/resource.yaml +4 -1
- package/library/tools/world-snapshot/resource.yaml +24 -3
- package/library/tools/zoom/resource.yaml +105 -0
- package/package.json +1 -1
- package/registry/generated/aliases.json +17 -3
- package/registry/generated/counts.json +3 -3
- package/registry/generated/gemini-extension.json +26 -4
- package/registry/generated/index.json +2154 -469
- package/registry/generated/marketplace.claude.json +2 -2
- package/registry/generated/plugin.claude.json +27 -5
- package/registry/generated/plugin.codex.json +28 -6
- package/registry/generated/plugin.cursor.json +27 -5
- package/registry/generated/plugin.factory.json +27 -5
- package/registry/generated/skills-index.md +36 -14
- package/registry/generated/skills-registry.json +421 -124
- package/registry/generated/templates-registry.json +2 -2
- package/library/skills/host-authoritative-state/SKILL.md +0 -332
- package/library/skills/host-authoritative-state/resource.yaml +0 -28
- package/library/skills/peer-to-peer-multiplayer/SKILL.md +0 -373
- package/library/skills/peer-to-peer-multiplayer/resource.yaml +0 -28
- package/library/skills/setup-multiplayer/SKILL.md +0 -336
- package/library/skills/setup-multiplayer/resource.yaml +0 -28
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
# multiplayer-movement: complete files after this step
|
|
2
|
+
|
|
3
|
+
Every file below ran as shown under Local Play. Compare against your project, or copy a file whole.
|
|
4
|
+
|
|
5
|
+
## `authority/main.gd`
|
|
6
|
+
|
|
7
|
+
```gdscript
|
|
8
|
+
extends Node
|
|
9
|
+
## The match server. It runs headless and owns every rule.
|
|
10
|
+
|
|
11
|
+
const Game = preload("res://network/game.gd")
|
|
12
|
+
const PlayerArchetype = preload("res://authority/player_archetype.tres")
|
|
13
|
+
## How far a player may move in one burst after a network stall, in metres.
|
|
14
|
+
const BURST := Game.MAX_SPEED * 0.5
|
|
15
|
+
|
|
16
|
+
var spawner: SummerNetworkSpawner
|
|
17
|
+
var world: SummerNetworkWorld
|
|
18
|
+
var seat_of := {} # session_id -> seat, held until the Session leaves
|
|
19
|
+
var allowance := {} # seat -> metres the player may still move
|
|
20
|
+
var refilled_at := {} # seat -> msec of the last allowance refill
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
func _ready() -> void:
|
|
24
|
+
spawner = Game.build_network(self, PlayerArchetype)
|
|
25
|
+
world = $World
|
|
26
|
+
spawner.entity_spawned.connect(_on_player_spawned)
|
|
27
|
+
spawner.command_received.connect(_on_command)
|
|
28
|
+
world.session_left.connect(_on_session_left)
|
|
29
|
+
var initialized: SummerResult = await Summer.initialize(Game.GAME_ID).get_result_or_completed_signal()
|
|
30
|
+
if not initialized.ok:
|
|
31
|
+
push_error(initialized.message)
|
|
32
|
+
return
|
|
33
|
+
if not world.is_ready():
|
|
34
|
+
await world.binding_ready
|
|
35
|
+
if not spawner.is_network_ready():
|
|
36
|
+
await spawner.network_ready
|
|
37
|
+
print("COURTYARD_AUTHORITY_READY")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## A seat is a player's place in this World for their whole stay, across
|
|
41
|
+
## reconnects. Returns -1 when the World is full.
|
|
42
|
+
func _seat(session: SummerSession) -> int:
|
|
43
|
+
if seat_of.has(session.session_id):
|
|
44
|
+
return seat_of[session.session_id]
|
|
45
|
+
var taken := seat_of.values()
|
|
46
|
+
for seat in Game.MAX_PLAYERS:
|
|
47
|
+
if seat not in taken:
|
|
48
|
+
seat_of[session.session_id] = seat
|
|
49
|
+
return seat
|
|
50
|
+
return -1
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## The Spawner spawns one player entity per joined player, once that player's
|
|
54
|
+
## network is ready (and again after a reconnect). Its owner becomes the only
|
|
55
|
+
## writer of the pose; the group ends with the entity.
|
|
56
|
+
func _on_player_spawned(entity: SummerNetworkEntity) -> void:
|
|
57
|
+
var seat := _seat(entity.session)
|
|
58
|
+
if seat < 0:
|
|
59
|
+
push_warning("no free seat")
|
|
60
|
+
return
|
|
61
|
+
allowance[seat] = BURST
|
|
62
|
+
refilled_at[seat] = Time.get_ticks_msec()
|
|
63
|
+
var group := spawner.create_state_group(Game.MOTION, Game.motion_key(seat), Game.spawn_pose(seat, entity.entity_id), entity.session, entity)
|
|
64
|
+
group.set_acceptance_policy(_accept_motion.bind(seat))
|
|
65
|
+
print("COURTYARD_PLAYER_SPAWNED seat=%d entity=%d" % [seat, entity.entity_id])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## Runs on every pose an owner submits. True accepts it unchanged; false
|
|
69
|
+
## refuses it, and the owner snaps back to the last accepted pose.
|
|
70
|
+
func _accept_motion(candidate: Dictionary, current: Dictionary, seat: int) -> bool:
|
|
71
|
+
if candidate.entity != current.entity:
|
|
72
|
+
return false # a player can move, never become someone else
|
|
73
|
+
var position: Vector3 = candidate.position
|
|
74
|
+
if not Game.in_bounds(position) or not is_finite(candidate.yaw):
|
|
75
|
+
return false
|
|
76
|
+
# A distance allowance refilled at MAX_SPEED: bursts after a stall pass,
|
|
77
|
+
# speed hacks and teleports do not.
|
|
78
|
+
var now := Time.get_ticks_msec()
|
|
79
|
+
var budget := minf(float(allowance[seat]) + Game.MAX_SPEED * (now - int(refilled_at[seat])) / 1000.0, BURST)
|
|
80
|
+
refilled_at[seat] = now
|
|
81
|
+
var distance := position.distance_to(current.position)
|
|
82
|
+
if distance > budget:
|
|
83
|
+
allowance[seat] = budget
|
|
84
|
+
return false
|
|
85
|
+
allowance[seat] = budget - distance
|
|
86
|
+
return true
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
## Moves a player on purpose (respawn, portal). The owner snaps to it.
|
|
90
|
+
func teleport(seat: int, position: Vector3) -> void:
|
|
91
|
+
var group := spawner.get_state_group(Game.MOTION, Game.motion_key(seat))
|
|
92
|
+
if group != null and group.active:
|
|
93
|
+
var pose := group.get_state()
|
|
94
|
+
pose.position = position
|
|
95
|
+
allowance[seat] = BURST
|
|
96
|
+
group.reset(pose)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
func _on_command(request: SummerNetworkCommandRequest) -> void:
|
|
100
|
+
var command := Game.unpack(request.payload)
|
|
101
|
+
match str(command.get("c", "")):
|
|
102
|
+
"join":
|
|
103
|
+
_join(request)
|
|
104
|
+
_:
|
|
105
|
+
request.refuse(&"unknown_command")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## A client sends "join" once its network is ready.
|
|
109
|
+
func _join(request: SummerNetworkCommandRequest) -> void:
|
|
110
|
+
var seat := _seat(request.get_session())
|
|
111
|
+
if seat < 0:
|
|
112
|
+
request.refuse(&"match_full")
|
|
113
|
+
return
|
|
114
|
+
request.accept({}, Game.pack({"seat": seat}))
|
|
115
|
+
print("COURTYARD_PLAYER_JOINED seat=%d" % seat)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## The Session ended for good: free the seat. (Its pose group already ended
|
|
119
|
+
## with the player's entity.)
|
|
120
|
+
func _on_session_left(session: SummerSession, reason: String) -> void:
|
|
121
|
+
var seat: int = seat_of.get(session.session_id, -1)
|
|
122
|
+
seat_of.erase(session.session_id)
|
|
123
|
+
print("COURTYARD_PLAYER_LEFT seat=%d reason=%s" % [seat, reason])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## `client/main.gd`
|
|
127
|
+
|
|
128
|
+
```gdscript
|
|
129
|
+
extends Node
|
|
130
|
+
## What each player runs: joins the match and shows every player.
|
|
131
|
+
|
|
132
|
+
const Game = preload("res://network/game.gd")
|
|
133
|
+
const PlayerArchetype = preload("res://client/player_archetype.tres")
|
|
134
|
+
|
|
135
|
+
var spawner: SummerNetworkSpawner
|
|
136
|
+
var waiting := {} # entity id -> pose group that arrived before its entity
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
func _ready() -> void:
|
|
140
|
+
spawner = Game.build_network(self, PlayerArchetype)
|
|
141
|
+
# Connect before joining, or the first values arrive unseen.
|
|
142
|
+
spawner.state_group_created.connect(_on_group_created)
|
|
143
|
+
spawner.entity_spawned.connect(_on_entity_spawned)
|
|
144
|
+
var initialized: SummerResult = await Summer.initialize(Game.GAME_ID).get_result_or_completed_signal()
|
|
145
|
+
if not initialized.ok:
|
|
146
|
+
push_error(initialized.message)
|
|
147
|
+
return
|
|
148
|
+
var join := Summer.client.join(SummerJoinTarget.queue(Game.QUEUE))
|
|
149
|
+
# Hosted matchmaking may ask the player to accept a match.
|
|
150
|
+
join.acceptance_required.connect(func(proposal: SummerMatchmakingProposal) -> void: proposal.accept())
|
|
151
|
+
var joined: SummerResult = await join.get_result_or_completed_signal()
|
|
152
|
+
if not joined.ok:
|
|
153
|
+
push_error("join failed: %s %s" % [joined.code, joined.message])
|
|
154
|
+
return
|
|
155
|
+
if not spawner.is_network_ready():
|
|
156
|
+
await spawner.network_ready
|
|
157
|
+
var entered := await command({"c": "join"})
|
|
158
|
+
if not entered.ok:
|
|
159
|
+
push_error("could not enter the match: %s" % entered.error)
|
|
160
|
+
return
|
|
161
|
+
print("COURTYARD_CLIENT_JOINED")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
## Sends one Command and waits for the authority's answer.
|
|
165
|
+
func command(payload: Dictionary) -> Dictionary:
|
|
166
|
+
var admission := spawner.enqueue_command(Game.CMD, Game.pack(payload))
|
|
167
|
+
if not admission.is_enqueued():
|
|
168
|
+
return {"ok": false, "error": String(admission.get_code())}
|
|
169
|
+
var handle := admission.get_handle()
|
|
170
|
+
if not handle.is_terminal():
|
|
171
|
+
await handle.completed
|
|
172
|
+
if handle.get_outcome() == SummerNetworkCommandHandle.OUTCOME_ACCEPTED:
|
|
173
|
+
return {"ok": true, "error": "", "result": Game.unpack(handle.get_result())}
|
|
174
|
+
return {"ok": false, "error": String(handle.get_reason())}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
func _on_group_created(group: SummerNetworkStateGroup) -> void:
|
|
178
|
+
if group.get_stream_id() == Game.MOTION:
|
|
179
|
+
_attach_pose(group, int(group.get_state().entity))
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
## The pose group and the player's entity can arrive in either order.
|
|
183
|
+
func _attach_pose(group: SummerNetworkStateGroup, entity_id: int) -> void:
|
|
184
|
+
var entity := spawner.get_entity(entity_id)
|
|
185
|
+
if entity == null or entity.root == null:
|
|
186
|
+
waiting[entity_id] = group
|
|
187
|
+
return
|
|
188
|
+
entity.root.bind(group)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
func _on_entity_spawned(entity: SummerNetworkEntity) -> void:
|
|
192
|
+
if waiting.has(entity.entity_id):
|
|
193
|
+
_attach_pose(waiting[entity.entity_id], entity.entity_id)
|
|
194
|
+
waiting.erase(entity.entity_id)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## `client/player_observer.gd`
|
|
198
|
+
|
|
199
|
+
```gdscript
|
|
200
|
+
extends Node3D
|
|
201
|
+
## Someone else's player. It glides between the poses its owner published,
|
|
202
|
+
## drawn VIEW_DELAY in the past so there is always a newer pose to glide to.
|
|
203
|
+
|
|
204
|
+
const VIEW_DELAY := 0.1
|
|
205
|
+
|
|
206
|
+
var group: SummerNetworkStateGroup
|
|
207
|
+
var _samples: Array = [] # [time, position, yaw] per received pose
|
|
208
|
+
var _offsets: Array = [] # recent arrival minus authority acceptance, seconds
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
func bind(motion: SummerNetworkStateGroup) -> void:
|
|
212
|
+
group = motion
|
|
213
|
+
group.state_installed.connect(_on_installed)
|
|
214
|
+
_on_installed(group.get_state(), group.get_revision(), true)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
func _on_installed(state: Dictionary, _revision: int, reset: bool) -> void:
|
|
218
|
+
if state.is_empty():
|
|
219
|
+
return
|
|
220
|
+
var now := Time.get_ticks_usec() / 1000000.0
|
|
221
|
+
if reset:
|
|
222
|
+
_samples.clear() # a spawn or teleport is a cut, not a slide
|
|
223
|
+
global_position = state.position
|
|
224
|
+
rotation.y = state.yaw
|
|
225
|
+
_samples.append([_pose_time(now), state.position, state.yaw])
|
|
226
|
+
if _samples.size() > 20:
|
|
227
|
+
_samples.pop_front()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
func _process(_delta: float) -> void:
|
|
231
|
+
if _samples.is_empty():
|
|
232
|
+
return
|
|
233
|
+
var at := Time.get_ticks_usec() / 1000000.0 - VIEW_DELAY
|
|
234
|
+
var newest: Array = _samples[-1]
|
|
235
|
+
if at >= newest[0] or _samples.size() == 1:
|
|
236
|
+
global_position = newest[1]
|
|
237
|
+
rotation.y = newest[2]
|
|
238
|
+
return
|
|
239
|
+
for i in range(_samples.size() - 1, 0, -1):
|
|
240
|
+
var a: Array = _samples[i - 1]
|
|
241
|
+
var b: Array = _samples[i]
|
|
242
|
+
if at >= a[0]:
|
|
243
|
+
var t := clampf((at - a[0]) / maxf(b[0] - a[0], 0.0001), 0.0, 1.0)
|
|
244
|
+
global_position = (a[1] as Vector3).lerp(b[1], t)
|
|
245
|
+
rotation.y = lerp_angle(a[2], b[2], t)
|
|
246
|
+
return
|
|
247
|
+
global_position = _samples[0][1]
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
## Places a pose on the local timeline by when the authority accepted it, so
|
|
251
|
+
## poses that arrive bunched together still play back evenly.
|
|
252
|
+
func _pose_time(now: float) -> float:
|
|
253
|
+
var timing := group.get_timing()
|
|
254
|
+
if timing == null or timing.state_authority_usec < 0:
|
|
255
|
+
return now
|
|
256
|
+
var accepted := timing.state_authority_usec / 1000000.0
|
|
257
|
+
_offsets.append(now - accepted)
|
|
258
|
+
if _offsets.size() > 40:
|
|
259
|
+
_offsets.pop_front()
|
|
260
|
+
return minf(accepted + float(_offsets.min()), now)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## `client/player_observer.tscn`
|
|
264
|
+
|
|
265
|
+
```ini
|
|
266
|
+
[gd_scene load_steps=3 format=3]
|
|
267
|
+
|
|
268
|
+
[ext_resource type="Script" path="res://client/player_observer.gd" id="1"]
|
|
269
|
+
|
|
270
|
+
[sub_resource type="CapsuleMesh" id="Mesh"]
|
|
271
|
+
|
|
272
|
+
[node name="PlayerObserver" type="Node3D"]
|
|
273
|
+
script = ExtResource("1")
|
|
274
|
+
|
|
275
|
+
[node name="Body" type="MeshInstance3D" parent="."]
|
|
276
|
+
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
|
277
|
+
mesh = SubResource("Mesh")
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## `client/player_owner.gd`
|
|
281
|
+
|
|
282
|
+
```gdscript
|
|
283
|
+
extends CharacterBody3D
|
|
284
|
+
## Your own player. You move it locally and publish where it is; the
|
|
285
|
+
## authority only snaps you somewhere on spawn, teleport or a refused pose.
|
|
286
|
+
|
|
287
|
+
const WALK_SPEED := 6.0
|
|
288
|
+
const JUMP_VELOCITY := 5.0
|
|
289
|
+
const SEND_INTERVAL := 1.0 / 20.0
|
|
290
|
+
|
|
291
|
+
var group: SummerNetworkStateGroup
|
|
292
|
+
var _pose := {} # the full pose; only position and yaw change
|
|
293
|
+
var _sent := {}
|
|
294
|
+
var _send_elapsed := 0.0
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
func bind(motion: SummerNetworkStateGroup) -> void:
|
|
298
|
+
group = motion
|
|
299
|
+
group.state_installed.connect(_on_installed)
|
|
300
|
+
_on_installed(group.get_state(), group.get_revision(), true)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
func _on_installed(state: Dictionary, _revision: int, reset: bool) -> void:
|
|
304
|
+
# Accepted poses are not echoed back to you (skip_writer_echo). A reset is
|
|
305
|
+
# the authority moving you: spawn, teleport, or a pose it refused.
|
|
306
|
+
if not reset or state.is_empty():
|
|
307
|
+
return
|
|
308
|
+
_pose = state.duplicate()
|
|
309
|
+
_sent = state.duplicate()
|
|
310
|
+
global_position = state.position
|
|
311
|
+
rotation.y = state.yaw
|
|
312
|
+
velocity = Vector3.ZERO
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
func _physics_process(delta: float) -> void:
|
|
316
|
+
if group == null or not group.active:
|
|
317
|
+
return
|
|
318
|
+
if not is_on_floor():
|
|
319
|
+
velocity += get_gravity() * delta
|
|
320
|
+
if Input.is_action_just_pressed(&"ui_accept") and is_on_floor():
|
|
321
|
+
velocity.y = JUMP_VELOCITY
|
|
322
|
+
var input := Input.get_vector(&"ui_left", &"ui_right", &"ui_up", &"ui_down")
|
|
323
|
+
velocity.x = input.x * WALK_SPEED
|
|
324
|
+
velocity.z = input.y * WALK_SPEED
|
|
325
|
+
if input != Vector2.ZERO:
|
|
326
|
+
rotation.y = atan2(-input.x, -input.y)
|
|
327
|
+
move_and_slide()
|
|
328
|
+
_publish(delta)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
## Publishes the pose 20 times a second, and only when it changed.
|
|
332
|
+
func _publish(delta: float) -> void:
|
|
333
|
+
_send_elapsed += delta
|
|
334
|
+
if _send_elapsed < SEND_INTERVAL:
|
|
335
|
+
return
|
|
336
|
+
_send_elapsed = 0.0
|
|
337
|
+
_pose.position = global_position
|
|
338
|
+
_pose.yaw = rotation.y
|
|
339
|
+
if _pose == _sent:
|
|
340
|
+
return
|
|
341
|
+
group.submit(_pose)
|
|
342
|
+
_sent = _pose.duplicate()
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## `client/player_owner.tscn`
|
|
346
|
+
|
|
347
|
+
```ini
|
|
348
|
+
[gd_scene load_steps=5 format=3]
|
|
349
|
+
|
|
350
|
+
[ext_resource type="Script" path="res://client/player_owner.gd" id="1"]
|
|
351
|
+
|
|
352
|
+
[sub_resource type="CapsuleShape3D" id="Shape"]
|
|
353
|
+
|
|
354
|
+
[sub_resource type="CapsuleMesh" id="Mesh"]
|
|
355
|
+
|
|
356
|
+
[node name="PlayerOwner" type="CharacterBody3D"]
|
|
357
|
+
script = ExtResource("1")
|
|
358
|
+
|
|
359
|
+
[node name="Shape" type="CollisionShape3D" parent="."]
|
|
360
|
+
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
|
361
|
+
shape = SubResource("Shape")
|
|
362
|
+
|
|
363
|
+
[node name="Body" type="MeshInstance3D" parent="."]
|
|
364
|
+
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
|
|
365
|
+
mesh = SubResource("Mesh")
|
|
366
|
+
|
|
367
|
+
[node name="Camera3D" type="Camera3D" parent="."]
|
|
368
|
+
transform = Transform3D(1, 0, 0, 0, 0.8, 0.6, 0, -0.6, 0.8, 0, 6, 8)
|
|
369
|
+
current = true
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## `network/composition.tres`
|
|
373
|
+
|
|
374
|
+
```ini
|
|
375
|
+
[gd_resource type="SummerNetworkComposition" load_steps=12 format=3]
|
|
376
|
+
|
|
377
|
+
[sub_resource type="SummerNetworkField" id="MotionPosition"]
|
|
378
|
+
field_name = &"position"
|
|
379
|
+
type = 9
|
|
380
|
+
|
|
381
|
+
[sub_resource type="SummerNetworkField" id="MotionYaw"]
|
|
382
|
+
field_name = &"yaw"
|
|
383
|
+
type = 2
|
|
384
|
+
|
|
385
|
+
[sub_resource type="SummerNetworkField" id="MotionEntity"]
|
|
386
|
+
field_name = &"entity"
|
|
387
|
+
type = 1
|
|
388
|
+
|
|
389
|
+
[sub_resource type="SummerNetworkSchema" id="MotionSchema"]
|
|
390
|
+
schema_id = &"courtyard.motion.v1"
|
|
391
|
+
state_fields = Array[SummerNetworkField]([SubResource("MotionEntity"), SubResource("MotionPosition"), SubResource("MotionYaw")])
|
|
392
|
+
|
|
393
|
+
[sub_resource type="SummerWorldAudience" id="Everyone"]
|
|
394
|
+
max_expansion = 8
|
|
395
|
+
|
|
396
|
+
[sub_resource type="SummerNetworkStateStream" id="Motion"]
|
|
397
|
+
stream_id = &"courtyard.motion"
|
|
398
|
+
writer_policy = 1
|
|
399
|
+
skip_writer_echo = true
|
|
400
|
+
state_schema = SubResource("MotionSchema")
|
|
401
|
+
audience_policy = SubResource("Everyone")
|
|
402
|
+
max_audience_count = 8
|
|
403
|
+
max_keys = 8
|
|
404
|
+
max_state_bytes = 1024
|
|
405
|
+
max_total_bytes = 8192
|
|
406
|
+
|
|
407
|
+
[sub_resource type="SummerNetworkField" id="CommandBytes"]
|
|
408
|
+
field_name = &"b"
|
|
409
|
+
type = 20
|
|
410
|
+
|
|
411
|
+
[sub_resource type="SummerNetworkSchema" id="CommandSchema"]
|
|
412
|
+
schema_id = &"courtyard.cmd.v1"
|
|
413
|
+
state_fields = Array[SummerNetworkField]([SubResource("CommandBytes")])
|
|
414
|
+
|
|
415
|
+
[sub_resource type="SummerNetworkField" id="ResultBytes"]
|
|
416
|
+
field_name = &"b"
|
|
417
|
+
type = 20
|
|
418
|
+
|
|
419
|
+
[sub_resource type="SummerNetworkSchema" id="ResultSchema"]
|
|
420
|
+
schema_id = &"courtyard.cmd.result.v1"
|
|
421
|
+
state_fields = Array[SummerNetworkField]([SubResource("ResultBytes")])
|
|
422
|
+
|
|
423
|
+
[sub_resource type="SummerNetworkOccurrenceStream" id="Commands"]
|
|
424
|
+
stream_id = &"courtyard.cmd"
|
|
425
|
+
command_scope = 2
|
|
426
|
+
command_tick_dispatch = true
|
|
427
|
+
command_timeout_msec = 8000
|
|
428
|
+
payload_schema = SubResource("CommandSchema")
|
|
429
|
+
result_schema = SubResource("ResultSchema")
|
|
430
|
+
|
|
431
|
+
[resource]
|
|
432
|
+
network_version = "1.0.0"
|
|
433
|
+
state_streams = Array[SummerNetworkStateStream]([SubResource("Motion")])
|
|
434
|
+
occurrence_streams = Array[SummerNetworkOccurrenceStream]([SubResource("Commands")])
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## `network/game.gd`
|
|
438
|
+
|
|
439
|
+
```gdscript
|
|
440
|
+
extends RefCounted
|
|
441
|
+
## Shared by the client and the authority: ids, streams and shared rules.
|
|
442
|
+
|
|
443
|
+
const GAME_ID := "game_courtyard"
|
|
444
|
+
const QUEUE := &"courtyard"
|
|
445
|
+
const MOTION := &"courtyard.motion"
|
|
446
|
+
const CMD := &"courtyard.cmd"
|
|
447
|
+
const MAX_PLAYERS := 8
|
|
448
|
+
const COMPOSITION := preload("res://network/composition.tres")
|
|
449
|
+
|
|
450
|
+
## The authority's sanity bound on movement, in metres per second. Keep it
|
|
451
|
+
## above the fastest legitimate speed (walking, jumping, falling).
|
|
452
|
+
const MAX_SPEED := 15.0
|
|
453
|
+
const ARENA_RADIUS := 40.0
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
## Adds the World, an Entities root and the Spawner under `parent`. Both sides
|
|
457
|
+
## pass their own player archetype; the composition is the same file.
|
|
458
|
+
static func build_network(parent: Node, player: SummerEntityArchetype) -> SummerNetworkSpawner:
|
|
459
|
+
var world := SummerNetworkWorld.new()
|
|
460
|
+
world.name = "World"
|
|
461
|
+
parent.add_child(world)
|
|
462
|
+
var entities := Node3D.new()
|
|
463
|
+
entities.name = "Entities"
|
|
464
|
+
parent.add_child(entities)
|
|
465
|
+
var spawner := SummerNetworkSpawner.new()
|
|
466
|
+
spawner.name = "Spawner"
|
|
467
|
+
spawner.network_world_path = NodePath("../World")
|
|
468
|
+
spawner.entities_root_path = NodePath("../Entities")
|
|
469
|
+
spawner.archetypes = [player]
|
|
470
|
+
spawner.player_archetype = &"player" # one entity per joined player
|
|
471
|
+
spawner.network_composition = COMPOSITION
|
|
472
|
+
parent.add_child(spawner)
|
|
473
|
+
return spawner
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
## Pose groups are keyed by seat, so a stream needs at most MAX_PLAYERS keys.
|
|
477
|
+
static func motion_key(seat: int) -> StringName:
|
|
478
|
+
return StringName("p%d" % seat)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
## Seats spawn on a circle. `entity` ties the pose to the player's entity.
|
|
482
|
+
static func spawn_pose(seat: int, entity_id: int) -> Dictionary:
|
|
483
|
+
var angle := TAU * float(seat) / MAX_PLAYERS
|
|
484
|
+
return {"entity": entity_id, "position": Vector3(cos(angle) * 4.0, 1.0, sin(angle) * 4.0), "yaw": 0.0}
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
static func in_bounds(position: Vector3) -> bool:
|
|
488
|
+
return position.is_finite() and Vector2(position.x, position.z).length() <= ARENA_RADIUS \
|
|
489
|
+
and position.y > -10.0 and position.y < 30.0
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
## Commands and results travel as JSON bytes: a network String holds at most
|
|
493
|
+
## 256 bytes.
|
|
494
|
+
static func pack(document: Dictionary) -> Dictionary:
|
|
495
|
+
return {"b": JSON.stringify(document).to_utf8_buffer()}
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
static func unpack(fields: Dictionary) -> Dictionary:
|
|
499
|
+
var parsed: Variant = JSON.parse_string((fields.get("b", PackedByteArray()) as PackedByteArray).get_string_from_utf8())
|
|
500
|
+
return parsed if parsed is Dictionary else {}
|
|
501
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
id: skill/multiplayer-movement
|
|
2
|
+
kind: skill
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
summary: "Client-side movement in Summer multiplayer: owners move instantly, others glide, the authority checks each pose."
|
|
5
|
+
use_when:
|
|
6
|
+
- "moving players in a Summer multiplayer game: each player's own character, other players drawn smoothly"
|
|
7
|
+
- "remote players stutter, jitter, teleport or rubberband in a Summer multiplayer game"
|
|
8
|
+
- "stopping speed hacks and teleport cheats on player movement; respawning or teleporting a player on purpose"
|
|
9
|
+
facets:
|
|
10
|
+
lifecycle:
|
|
11
|
+
- build
|
|
12
|
+
domains:
|
|
13
|
+
- multiplayer
|
|
14
|
+
- character-controller
|
|
15
|
+
- scripting
|
|
16
|
+
modalities:
|
|
17
|
+
- scripts
|
|
18
|
+
- scenes
|
|
19
|
+
compatibility:
|
|
20
|
+
engine: ">=4.6"
|
|
21
|
+
related:
|
|
22
|
+
skills:
|
|
23
|
+
- skill/multiplayer
|
|
24
|
+
- skill/multiplayer-project
|
|
25
|
+
- skill/multiplayer-state
|
|
26
|
+
- skill/multiplayer-testing
|
|
27
|
+
source: official
|
|
28
|
+
license: MIT
|
|
29
|
+
status: stable
|
|
30
|
+
recommended: false
|