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,592 @@
|
|
|
1
|
+
# multiplayer-state: 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
|
+
const REACH := 2.0 # how close a player must stand to collect a coin
|
|
16
|
+
|
|
17
|
+
var spawner: SummerNetworkSpawner
|
|
18
|
+
var world: SummerNetworkWorld
|
|
19
|
+
var seat_of := {} # session_id -> seat, held until the Session leaves
|
|
20
|
+
var allowance := {} # seat -> metres the player may still move
|
|
21
|
+
var refilled_at := {} # seat -> msec of the last allowance refill
|
|
22
|
+
|
|
23
|
+
var match_group: SummerNetworkStateGroup
|
|
24
|
+
var match_doc := {"coins": {"0": [10.0, 0.5, 0.0], "1": [-10.0, 0.5, 0.0], "2": [0.0, 0.5, 10.0]}, "scores": {}}
|
|
25
|
+
var mine_groups := {} # session_id -> private group
|
|
26
|
+
var wallets := {} # session_id -> coins this player holds
|
|
27
|
+
var receipts := {} # session_id -> {request id: result}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
func _ready() -> void:
|
|
31
|
+
spawner = Game.build_network(self, PlayerArchetype)
|
|
32
|
+
world = $World
|
|
33
|
+
spawner.entity_spawned.connect(_on_player_spawned)
|
|
34
|
+
spawner.command_received.connect(_on_command)
|
|
35
|
+
world.session_left.connect(_on_session_left)
|
|
36
|
+
var initialized: SummerResult = await Summer.initialize(Game.GAME_ID).get_result_or_completed_signal()
|
|
37
|
+
if not initialized.ok:
|
|
38
|
+
push_error(initialized.message)
|
|
39
|
+
return
|
|
40
|
+
if not world.is_ready():
|
|
41
|
+
await world.binding_ready
|
|
42
|
+
if not spawner.is_network_ready():
|
|
43
|
+
await spawner.network_ready
|
|
44
|
+
match_group = spawner.create_state_group(Game.MATCH, &"match", Game.pack(match_doc))
|
|
45
|
+
print("COURTYARD_AUTHORITY_READY")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## A seat is a player's place in this World for their whole stay, across
|
|
49
|
+
## reconnects. Returns -1 when the World is full.
|
|
50
|
+
func _seat(session: SummerSession) -> int:
|
|
51
|
+
if seat_of.has(session.session_id):
|
|
52
|
+
return seat_of[session.session_id]
|
|
53
|
+
var taken := seat_of.values()
|
|
54
|
+
for seat in Game.MAX_PLAYERS:
|
|
55
|
+
if seat not in taken:
|
|
56
|
+
seat_of[session.session_id] = seat
|
|
57
|
+
return seat
|
|
58
|
+
return -1
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## The Spawner spawns one player entity per joined player, once that player's
|
|
62
|
+
## network is ready (and again after a reconnect). Its owner becomes the only
|
|
63
|
+
## writer of the pose; the group ends with the entity.
|
|
64
|
+
func _on_player_spawned(entity: SummerNetworkEntity) -> void:
|
|
65
|
+
var seat := _seat(entity.session)
|
|
66
|
+
if seat < 0:
|
|
67
|
+
push_warning("no free seat")
|
|
68
|
+
return
|
|
69
|
+
allowance[seat] = BURST
|
|
70
|
+
refilled_at[seat] = Time.get_ticks_msec()
|
|
71
|
+
var group := spawner.create_state_group(Game.MOTION, Game.motion_key(seat), Game.spawn_pose(seat, entity.entity_id), entity.session, entity)
|
|
72
|
+
group.set_acceptance_policy(_accept_motion.bind(seat))
|
|
73
|
+
print("COURTYARD_PLAYER_SPAWNED seat=%d entity=%d" % [seat, entity.entity_id])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## Runs on every pose an owner submits. True accepts it unchanged; false
|
|
77
|
+
## refuses it, and the owner snaps back to the last accepted pose.
|
|
78
|
+
func _accept_motion(candidate: Dictionary, current: Dictionary, seat: int) -> bool:
|
|
79
|
+
if candidate.entity != current.entity:
|
|
80
|
+
return false # a player can move, never become someone else
|
|
81
|
+
var position: Vector3 = candidate.position
|
|
82
|
+
if not Game.in_bounds(position) or not is_finite(candidate.yaw):
|
|
83
|
+
return false
|
|
84
|
+
# A distance allowance refilled at MAX_SPEED: bursts after a stall pass,
|
|
85
|
+
# speed hacks and teleports do not.
|
|
86
|
+
var now := Time.get_ticks_msec()
|
|
87
|
+
var budget := minf(float(allowance[seat]) + Game.MAX_SPEED * (now - int(refilled_at[seat])) / 1000.0, BURST)
|
|
88
|
+
refilled_at[seat] = now
|
|
89
|
+
var distance := position.distance_to(current.position)
|
|
90
|
+
if distance > budget:
|
|
91
|
+
allowance[seat] = budget
|
|
92
|
+
return false
|
|
93
|
+
allowance[seat] = budget - distance
|
|
94
|
+
return true
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
## Moves a player on purpose (respawn, portal). The owner snaps to it.
|
|
98
|
+
func teleport(seat: int, position: Vector3) -> void:
|
|
99
|
+
var group := spawner.get_state_group(Game.MOTION, Game.motion_key(seat))
|
|
100
|
+
if group != null and group.active:
|
|
101
|
+
var pose := group.get_state()
|
|
102
|
+
pose.position = position
|
|
103
|
+
allowance[seat] = BURST
|
|
104
|
+
group.reset(pose)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
func _on_command(request: SummerNetworkCommandRequest) -> void:
|
|
108
|
+
var command := Game.unpack(request.payload)
|
|
109
|
+
var session := request.get_session()
|
|
110
|
+
var rid := str(command.get("rid", ""))
|
|
111
|
+
# A retry of a request already answered gets the same answer, applied once.
|
|
112
|
+
var answered: Dictionary = receipts.get(session.session_id, {})
|
|
113
|
+
if rid != "" and answered.has(rid):
|
|
114
|
+
request.accept({}, Game.pack(answered[rid]))
|
|
115
|
+
return
|
|
116
|
+
match str(command.get("c", "")):
|
|
117
|
+
"join":
|
|
118
|
+
_join(request)
|
|
119
|
+
"collect":
|
|
120
|
+
_collect(request, rid, str(command.get("coin", "")))
|
|
121
|
+
_:
|
|
122
|
+
request.refuse(&"unknown_command")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
## A client sends "join" once its network is ready: set up what the player
|
|
126
|
+
## owns besides their body.
|
|
127
|
+
func _join(request: SummerNetworkCommandRequest) -> void:
|
|
128
|
+
var session := request.get_session()
|
|
129
|
+
var seat := _seat(session)
|
|
130
|
+
if seat < 0:
|
|
131
|
+
request.refuse(&"match_full")
|
|
132
|
+
return
|
|
133
|
+
if not mine_groups.has(session.session_id):
|
|
134
|
+
wallets[session.session_id] = 0
|
|
135
|
+
# Private state: only this Session receives it, only the authority writes it.
|
|
136
|
+
mine_groups[session.session_id] = spawner.create_state_group(Game.MINE, StringName("mine%d" % seat), Game.pack({"coins": 0}), null, null, session)
|
|
137
|
+
request.accept({}, Game.pack({"seat": seat}))
|
|
138
|
+
print("COURTYARD_PLAYER_JOINED seat=%d" % seat)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
## Checks everything before changing anything, then changes state only
|
|
142
|
+
## through the groups so every client, late joiners included, agrees.
|
|
143
|
+
func _collect(request: SummerNetworkCommandRequest, rid: String, coin: String) -> void:
|
|
144
|
+
var session := request.get_session()
|
|
145
|
+
if rid == "":
|
|
146
|
+
request.refuse(&"missing_request_id")
|
|
147
|
+
return
|
|
148
|
+
if not mine_groups.has(session.session_id):
|
|
149
|
+
request.refuse(&"not_joined")
|
|
150
|
+
return
|
|
151
|
+
if not match_doc.coins.has(coin):
|
|
152
|
+
request.refuse(&"coin_gone")
|
|
153
|
+
return
|
|
154
|
+
# The player's latest accepted pose, never a position from the payload.
|
|
155
|
+
var body := spawner.get_state_group(Game.MOTION, Game.motion_key(seat_of[session.session_id]))
|
|
156
|
+
if body == null or not body.active:
|
|
157
|
+
request.refuse(&"not_spawned")
|
|
158
|
+
return
|
|
159
|
+
var at: Array = match_doc.coins[coin]
|
|
160
|
+
if (body.get_state().position as Vector3).distance_to(Vector3(at[0], at[1], at[2])) > REACH:
|
|
161
|
+
request.refuse(&"too_far")
|
|
162
|
+
return
|
|
163
|
+
match_doc.coins.erase(coin)
|
|
164
|
+
var name := session.player.display_name
|
|
165
|
+
match_doc.scores[name] = int(match_doc.scores.get(name, 0)) + 1
|
|
166
|
+
match_group.reset(Game.pack(match_doc))
|
|
167
|
+
wallets[session.session_id] += 1
|
|
168
|
+
(mine_groups[session.session_id] as SummerNetworkStateGroup).reset(Game.pack({"coins": wallets[session.session_id]}))
|
|
169
|
+
spawner.publish_event_by_policy(Game.FX, {"position": Vector3(at[0], at[1], at[2])})
|
|
170
|
+
var result := {"coin": coin, "coins": wallets[session.session_id]}
|
|
171
|
+
var answered: Dictionary = receipts.get(session.session_id, {})
|
|
172
|
+
answered[rid] = result
|
|
173
|
+
receipts[session.session_id] = answered
|
|
174
|
+
request.accept({}, Game.pack(result))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
## The Session ended for good: free the seat and everything the player had.
|
|
178
|
+
func _on_session_left(session: SummerSession, reason: String) -> void:
|
|
179
|
+
var seat: int = seat_of.get(session.session_id, -1)
|
|
180
|
+
seat_of.erase(session.session_id)
|
|
181
|
+
var mine: SummerNetworkStateGroup = mine_groups.get(session.session_id)
|
|
182
|
+
if mine != null and mine.active:
|
|
183
|
+
mine.retire()
|
|
184
|
+
mine_groups.erase(session.session_id)
|
|
185
|
+
wallets.erase(session.session_id)
|
|
186
|
+
receipts.erase(session.session_id)
|
|
187
|
+
print("COURTYARD_PLAYER_LEFT seat=%d reason=%s" % [seat, reason])
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## `client/bot.gd`
|
|
191
|
+
|
|
192
|
+
```gdscript
|
|
193
|
+
extends Node
|
|
194
|
+
## Test bot for Local Play, enabled with `-- --bot={client}`. It presses the
|
|
195
|
+
## real input actions. Bot 1 walks to the first coin and collects it; every
|
|
196
|
+
## bot prints what it sees, for the test run to check.
|
|
197
|
+
|
|
198
|
+
const COIN := "0"
|
|
199
|
+
|
|
200
|
+
var number := 0
|
|
201
|
+
var main: Node
|
|
202
|
+
var elapsed := 0.0
|
|
203
|
+
var step := 0
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
func _process(delta: float) -> void:
|
|
207
|
+
elapsed += delta
|
|
208
|
+
var me := _own_player()
|
|
209
|
+
if me == null:
|
|
210
|
+
return
|
|
211
|
+
if number == 1 and step == 0:
|
|
212
|
+
var at: Array = main.match_doc.get("coins", {}).get(COIN, [])
|
|
213
|
+
if at.is_empty():
|
|
214
|
+
return
|
|
215
|
+
var to_coin := Vector2(at[0] - me.global_position.x, at[2] - me.global_position.z)
|
|
216
|
+
if to_coin.length() > 0.5:
|
|
217
|
+
_steer(to_coin.normalized())
|
|
218
|
+
return
|
|
219
|
+
_steer(Vector2.ZERO)
|
|
220
|
+
step = 1
|
|
221
|
+
print("BOT_MOVED %s" % me.global_position)
|
|
222
|
+
var collected: Dictionary = await main.collect(COIN)
|
|
223
|
+
print("BOT_COLLECT %s" % JSON.stringify(collected))
|
|
224
|
+
if elapsed > 6.0 and step < 2:
|
|
225
|
+
step = 2
|
|
226
|
+
for node in main.get_node("Entities").get_children():
|
|
227
|
+
if node != me:
|
|
228
|
+
print("BOT_SAW %s" % node.global_position)
|
|
229
|
+
print("BOT_SCORES %s" % JSON.stringify(main.match_doc.get("scores", {})))
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
func _own_player() -> Node3D:
|
|
233
|
+
for node in main.get_node("Entities").get_children():
|
|
234
|
+
if node.get_script() == preload("res://client/player_owner.gd"):
|
|
235
|
+
return node
|
|
236
|
+
return null
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
func _steer(direction: Vector2) -> void:
|
|
240
|
+
for action in [&"ui_left", &"ui_right", &"ui_up", &"ui_down"]:
|
|
241
|
+
Input.action_release(action)
|
|
242
|
+
if direction.x > 0.0: Input.action_press(&"ui_right", direction.x)
|
|
243
|
+
if direction.x < 0.0: Input.action_press(&"ui_left", -direction.x)
|
|
244
|
+
if direction.y > 0.0: Input.action_press(&"ui_down", direction.y)
|
|
245
|
+
if direction.y < 0.0: Input.action_press(&"ui_up", -direction.y)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## `client/main.gd`
|
|
249
|
+
|
|
250
|
+
```gdscript
|
|
251
|
+
extends Node
|
|
252
|
+
## What each player runs: joins the match and shows every player.
|
|
253
|
+
|
|
254
|
+
const Game = preload("res://network/game.gd")
|
|
255
|
+
const PlayerArchetype = preload("res://client/player_archetype.tres")
|
|
256
|
+
|
|
257
|
+
var spawner: SummerNetworkSpawner
|
|
258
|
+
var waiting := {} # entity id -> pose group that arrived before its entity
|
|
259
|
+
var match_doc := {} # what the authority published for everyone
|
|
260
|
+
var mine := {} # what the authority published for this player only
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
func _ready() -> void:
|
|
264
|
+
spawner = Game.build_network(self, PlayerArchetype)
|
|
265
|
+
# Connect before joining, or the first values arrive unseen.
|
|
266
|
+
spawner.state_group_created.connect(_on_group_created)
|
|
267
|
+
spawner.entity_spawned.connect(_on_entity_spawned)
|
|
268
|
+
spawner.event_received.connect(_on_event)
|
|
269
|
+
var initialized: SummerResult = await Summer.initialize(Game.GAME_ID).get_result_or_completed_signal()
|
|
270
|
+
if not initialized.ok:
|
|
271
|
+
push_error(initialized.message)
|
|
272
|
+
return
|
|
273
|
+
var join := Summer.client.join(SummerJoinTarget.queue(Game.QUEUE))
|
|
274
|
+
# Hosted matchmaking may ask the player to accept a match.
|
|
275
|
+
join.acceptance_required.connect(func(proposal: SummerMatchmakingProposal) -> void: proposal.accept())
|
|
276
|
+
var joined: SummerResult = await join.get_result_or_completed_signal()
|
|
277
|
+
if not joined.ok:
|
|
278
|
+
push_error("join failed: %s %s" % [joined.code, joined.message])
|
|
279
|
+
return
|
|
280
|
+
if not spawner.is_network_ready():
|
|
281
|
+
await spawner.network_ready
|
|
282
|
+
var entered := await command({"c": "join"})
|
|
283
|
+
if not entered.ok:
|
|
284
|
+
push_error("could not enter the match: %s" % entered.error)
|
|
285
|
+
return
|
|
286
|
+
print("COURTYARD_CLIENT_JOINED")
|
|
287
|
+
# Local Play test bot: `-- --bot={client}` (see multiplayer-testing).
|
|
288
|
+
for arg in OS.get_cmdline_user_args():
|
|
289
|
+
if arg.begins_with("--bot="):
|
|
290
|
+
var bot := preload("res://client/bot.gd").new()
|
|
291
|
+
bot.number = int(arg.trim_prefix("--bot="))
|
|
292
|
+
bot.main = self
|
|
293
|
+
add_child(bot)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
## Sends one Command and waits for the authority's answer.
|
|
297
|
+
func command(payload: Dictionary) -> Dictionary:
|
|
298
|
+
var admission := spawner.enqueue_command(Game.CMD, Game.pack(payload))
|
|
299
|
+
if not admission.is_enqueued():
|
|
300
|
+
return {"ok": false, "error": String(admission.get_code())}
|
|
301
|
+
var handle := admission.get_handle()
|
|
302
|
+
if not handle.is_terminal():
|
|
303
|
+
await handle.completed
|
|
304
|
+
if handle.get_outcome() == SummerNetworkCommandHandle.OUTCOME_ACCEPTED:
|
|
305
|
+
return {"ok": true, "error": "", "result": Game.unpack(handle.get_result())}
|
|
306
|
+
return {"ok": false, "error": String(handle.get_reason())}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
func _on_group_created(group: SummerNetworkStateGroup) -> void:
|
|
310
|
+
match group.get_stream_id():
|
|
311
|
+
Game.MOTION:
|
|
312
|
+
_attach_pose(group, int(group.get_state().entity))
|
|
313
|
+
Game.MATCH, Game.MINE:
|
|
314
|
+
# The baseline is already installed when this fires; later
|
|
315
|
+
# changes arrive through state_installed.
|
|
316
|
+
_on_document(group, group.get_state())
|
|
317
|
+
group.state_installed.connect(func(state: Dictionary, _revision: int, _reset: bool) -> void: _on_document(group, state))
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
## The pose group and the player's entity can arrive in either order.
|
|
321
|
+
func _attach_pose(group: SummerNetworkStateGroup, entity_id: int) -> void:
|
|
322
|
+
var entity := spawner.get_entity(entity_id)
|
|
323
|
+
if entity == null or entity.root == null:
|
|
324
|
+
waiting[entity_id] = group
|
|
325
|
+
return
|
|
326
|
+
entity.root.bind(group)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
func _on_entity_spawned(entity: SummerNetworkEntity) -> void:
|
|
330
|
+
if waiting.has(entity.entity_id):
|
|
331
|
+
_attach_pose(waiting[entity.entity_id], entity.entity_id)
|
|
332
|
+
waiting.erase(entity.entity_id)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
func _on_document(group: SummerNetworkStateGroup, state: Dictionary) -> void:
|
|
336
|
+
if group.get_stream_id() == Game.MATCH:
|
|
337
|
+
match_doc = Game.unpack(state)
|
|
338
|
+
else:
|
|
339
|
+
mine = Game.unpack(state)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
func _on_event(event: SummerNetworkEvent) -> void:
|
|
343
|
+
if event.stream_id == Game.FX:
|
|
344
|
+
print("COURTYARD_SPARKLE at %s" % event.payload.position)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
## Asks the authority for a coin. The request id makes a retry safe.
|
|
348
|
+
func collect(coin: String) -> Dictionary:
|
|
349
|
+
return await command({"c": "collect", "coin": coin, "rid": "collect-%s-%d" % [coin, Time.get_ticks_usec()]})
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## `export_presets.cfg`
|
|
353
|
+
|
|
354
|
+
```ini
|
|
355
|
+
[preset.0]
|
|
356
|
+
|
|
357
|
+
name="summer.games"
|
|
358
|
+
platform="summer.games"
|
|
359
|
+
runnable=false
|
|
360
|
+
dedicated_server=false
|
|
361
|
+
custom_features="summer_client"
|
|
362
|
+
export_filter="all_resources"
|
|
363
|
+
include_filter=""
|
|
364
|
+
exclude_filter=""
|
|
365
|
+
export_path=""
|
|
366
|
+
encrypt_pck=false
|
|
367
|
+
encrypt_directory=false
|
|
368
|
+
script_export_mode=2
|
|
369
|
+
|
|
370
|
+
[preset.0.options]
|
|
371
|
+
|
|
372
|
+
platforms/ios=true
|
|
373
|
+
platforms/macos=true
|
|
374
|
+
platforms/windows=false
|
|
375
|
+
|
|
376
|
+
[preset.1]
|
|
377
|
+
|
|
378
|
+
name="Courtyard Authority"
|
|
379
|
+
platform="Linux"
|
|
380
|
+
runnable=false
|
|
381
|
+
dedicated_server=true
|
|
382
|
+
custom_features="summer_authority"
|
|
383
|
+
export_filter="all_resources"
|
|
384
|
+
include_filter=""
|
|
385
|
+
exclude_filter="client/*"
|
|
386
|
+
export_path=""
|
|
387
|
+
encrypt_pck=false
|
|
388
|
+
encrypt_directory=false
|
|
389
|
+
script_export_mode=2
|
|
390
|
+
|
|
391
|
+
[preset.1.options]
|
|
392
|
+
|
|
393
|
+
binary_format/architecture="x86_64"
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## `network/composition.tres`
|
|
397
|
+
|
|
398
|
+
```ini
|
|
399
|
+
[gd_resource type="SummerNetworkComposition" load_steps=22 format=3]
|
|
400
|
+
|
|
401
|
+
[sub_resource type="SummerNetworkField" id="MotionPosition"]
|
|
402
|
+
field_name = &"position"
|
|
403
|
+
type = 9
|
|
404
|
+
|
|
405
|
+
[sub_resource type="SummerNetworkField" id="MotionYaw"]
|
|
406
|
+
field_name = &"yaw"
|
|
407
|
+
type = 2
|
|
408
|
+
|
|
409
|
+
[sub_resource type="SummerNetworkField" id="MotionEntity"]
|
|
410
|
+
field_name = &"entity"
|
|
411
|
+
type = 1
|
|
412
|
+
|
|
413
|
+
[sub_resource type="SummerNetworkSchema" id="MotionSchema"]
|
|
414
|
+
schema_id = &"courtyard.motion.v1"
|
|
415
|
+
state_fields = Array[SummerNetworkField]([SubResource("MotionEntity"), SubResource("MotionPosition"), SubResource("MotionYaw")])
|
|
416
|
+
|
|
417
|
+
[sub_resource type="SummerWorldAudience" id="Everyone"]
|
|
418
|
+
max_expansion = 8
|
|
419
|
+
|
|
420
|
+
[sub_resource type="SummerNetworkStateStream" id="Motion"]
|
|
421
|
+
stream_id = &"courtyard.motion"
|
|
422
|
+
writer_policy = 1
|
|
423
|
+
skip_writer_echo = true
|
|
424
|
+
state_schema = SubResource("MotionSchema")
|
|
425
|
+
audience_policy = SubResource("Everyone")
|
|
426
|
+
max_audience_count = 8
|
|
427
|
+
max_keys = 8
|
|
428
|
+
max_state_bytes = 1024
|
|
429
|
+
max_total_bytes = 8192
|
|
430
|
+
|
|
431
|
+
[sub_resource type="SummerNetworkField" id="CommandBytes"]
|
|
432
|
+
field_name = &"b"
|
|
433
|
+
type = 20
|
|
434
|
+
|
|
435
|
+
[sub_resource type="SummerNetworkSchema" id="CommandSchema"]
|
|
436
|
+
schema_id = &"courtyard.cmd.v1"
|
|
437
|
+
state_fields = Array[SummerNetworkField]([SubResource("CommandBytes")])
|
|
438
|
+
|
|
439
|
+
[sub_resource type="SummerNetworkField" id="ResultBytes"]
|
|
440
|
+
field_name = &"b"
|
|
441
|
+
type = 20
|
|
442
|
+
|
|
443
|
+
[sub_resource type="SummerNetworkSchema" id="ResultSchema"]
|
|
444
|
+
schema_id = &"courtyard.cmd.result.v1"
|
|
445
|
+
state_fields = Array[SummerNetworkField]([SubResource("ResultBytes")])
|
|
446
|
+
|
|
447
|
+
[sub_resource type="SummerNetworkOccurrenceStream" id="Commands"]
|
|
448
|
+
stream_id = &"courtyard.cmd"
|
|
449
|
+
command_scope = 2
|
|
450
|
+
command_tick_dispatch = true
|
|
451
|
+
command_timeout_msec = 8000
|
|
452
|
+
payload_schema = SubResource("CommandSchema")
|
|
453
|
+
result_schema = SubResource("ResultSchema")
|
|
454
|
+
|
|
455
|
+
[sub_resource type="SummerNetworkField" id="MatchBytes"]
|
|
456
|
+
field_name = &"b"
|
|
457
|
+
type = 20
|
|
458
|
+
|
|
459
|
+
[sub_resource type="SummerNetworkSchema" id="MatchSchema"]
|
|
460
|
+
schema_id = &"courtyard.match.v1"
|
|
461
|
+
state_fields = Array[SummerNetworkField]([SubResource("MatchBytes")])
|
|
462
|
+
|
|
463
|
+
[sub_resource type="SummerWorldAudience" id="MatchAudience"]
|
|
464
|
+
max_expansion = 8
|
|
465
|
+
|
|
466
|
+
[sub_resource type="SummerNetworkStateStream" id="Match"]
|
|
467
|
+
stream_id = &"courtyard.match"
|
|
468
|
+
state_schema = SubResource("MatchSchema")
|
|
469
|
+
audience_policy = SubResource("MatchAudience")
|
|
470
|
+
max_audience_count = 8
|
|
471
|
+
max_keys = 1
|
|
472
|
+
max_state_bytes = 4096
|
|
473
|
+
max_total_bytes = 4096
|
|
474
|
+
|
|
475
|
+
[sub_resource type="SummerNetworkField" id="MineBytes"]
|
|
476
|
+
field_name = &"b"
|
|
477
|
+
type = 20
|
|
478
|
+
|
|
479
|
+
[sub_resource type="SummerNetworkSchema" id="MineSchema"]
|
|
480
|
+
schema_id = &"courtyard.mine.v1"
|
|
481
|
+
state_fields = Array[SummerNetworkField]([SubResource("MineBytes")])
|
|
482
|
+
|
|
483
|
+
[sub_resource type="SummerTargetSessionAudience" id="OnlyThatPlayer"]
|
|
484
|
+
max_expansion = 1
|
|
485
|
+
|
|
486
|
+
[sub_resource type="SummerNetworkStateStream" id="Mine"]
|
|
487
|
+
stream_id = &"courtyard.mine"
|
|
488
|
+
state_schema = SubResource("MineSchema")
|
|
489
|
+
audience_policy = SubResource("OnlyThatPlayer")
|
|
490
|
+
max_audience_count = 1
|
|
491
|
+
max_keys = 8
|
|
492
|
+
max_state_bytes = 1024
|
|
493
|
+
max_total_bytes = 8192
|
|
494
|
+
|
|
495
|
+
[sub_resource type="SummerNetworkField" id="FxPosition"]
|
|
496
|
+
field_name = &"position"
|
|
497
|
+
type = 9
|
|
498
|
+
|
|
499
|
+
[sub_resource type="SummerNetworkSchema" id="FxSchema"]
|
|
500
|
+
schema_id = &"courtyard.fx.v1"
|
|
501
|
+
state_fields = Array[SummerNetworkField]([SubResource("FxPosition")])
|
|
502
|
+
|
|
503
|
+
[sub_resource type="SummerWorldAudience" id="FxAudience"]
|
|
504
|
+
max_expansion = 8
|
|
505
|
+
|
|
506
|
+
[sub_resource type="SummerNetworkOccurrenceStream" id="Fx"]
|
|
507
|
+
stream_id = &"courtyard.fx"
|
|
508
|
+
kind = 2
|
|
509
|
+
command_scope = 0
|
|
510
|
+
event_scope = 2
|
|
511
|
+
event_delivery = 2
|
|
512
|
+
transient_ttl_ticks = 30
|
|
513
|
+
max_outcome_history = 0
|
|
514
|
+
command_timeout_msec = 0
|
|
515
|
+
audience_policy = SubResource("FxAudience")
|
|
516
|
+
max_audience_count = 8
|
|
517
|
+
payload_schema = SubResource("FxSchema")
|
|
518
|
+
|
|
519
|
+
[resource]
|
|
520
|
+
network_version = "1.0.0"
|
|
521
|
+
state_streams = Array[SummerNetworkStateStream]([SubResource("Motion"), SubResource("Match"), SubResource("Mine")])
|
|
522
|
+
occurrence_streams = Array[SummerNetworkOccurrenceStream]([SubResource("Commands"), SubResource("Fx")])
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
## `network/game.gd`
|
|
526
|
+
|
|
527
|
+
```gdscript
|
|
528
|
+
extends RefCounted
|
|
529
|
+
## Shared by the client and the authority: ids, streams and shared rules.
|
|
530
|
+
|
|
531
|
+
const GAME_ID := "game_courtyard"
|
|
532
|
+
const QUEUE := &"courtyard"
|
|
533
|
+
const MOTION := &"courtyard.motion"
|
|
534
|
+
const CMD := &"courtyard.cmd"
|
|
535
|
+
const MATCH := &"courtyard.match" # one document every player sees
|
|
536
|
+
const MINE := &"courtyard.mine" # one private document per player
|
|
537
|
+
const FX := &"courtyard.fx" # one-off effects, never stored
|
|
538
|
+
const MAX_PLAYERS := 8
|
|
539
|
+
const COMPOSITION := preload("res://network/composition.tres")
|
|
540
|
+
|
|
541
|
+
## The authority's sanity bound on movement, in metres per second. Keep it
|
|
542
|
+
## above the fastest legitimate speed (walking, jumping, falling).
|
|
543
|
+
const MAX_SPEED := 15.0
|
|
544
|
+
const ARENA_RADIUS := 40.0
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
## Adds the World, an Entities root and the Spawner under `parent`. Both sides
|
|
548
|
+
## pass their own player archetype; the composition is the same file.
|
|
549
|
+
static func build_network(parent: Node, player: SummerEntityArchetype) -> SummerNetworkSpawner:
|
|
550
|
+
var world := SummerNetworkWorld.new()
|
|
551
|
+
world.name = "World"
|
|
552
|
+
parent.add_child(world)
|
|
553
|
+
var entities := Node3D.new()
|
|
554
|
+
entities.name = "Entities"
|
|
555
|
+
parent.add_child(entities)
|
|
556
|
+
var spawner := SummerNetworkSpawner.new()
|
|
557
|
+
spawner.name = "Spawner"
|
|
558
|
+
spawner.network_world_path = NodePath("../World")
|
|
559
|
+
spawner.entities_root_path = NodePath("../Entities")
|
|
560
|
+
spawner.archetypes = [player]
|
|
561
|
+
spawner.player_archetype = &"player" # one entity per joined player
|
|
562
|
+
spawner.network_composition = COMPOSITION
|
|
563
|
+
parent.add_child(spawner)
|
|
564
|
+
return spawner
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
## Pose groups are keyed by seat, so a stream needs at most MAX_PLAYERS keys.
|
|
568
|
+
static func motion_key(seat: int) -> StringName:
|
|
569
|
+
return StringName("p%d" % seat)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
## Seats spawn on a circle. `entity` ties the pose to the player's entity.
|
|
573
|
+
static func spawn_pose(seat: int, entity_id: int) -> Dictionary:
|
|
574
|
+
var angle := TAU * float(seat) / MAX_PLAYERS
|
|
575
|
+
return {"entity": entity_id, "position": Vector3(cos(angle) * 4.0, 1.0, sin(angle) * 4.0), "yaw": 0.0}
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
static func in_bounds(position: Vector3) -> bool:
|
|
579
|
+
return position.is_finite() and Vector2(position.x, position.z).length() <= ARENA_RADIUS \
|
|
580
|
+
and position.y > -10.0 and position.y < 30.0
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
## Commands and results travel as JSON bytes: a network String holds at most
|
|
584
|
+
## 256 bytes.
|
|
585
|
+
static func pack(document: Dictionary) -> Dictionary:
|
|
586
|
+
return {"b": JSON.stringify(document).to_utf8_buffer()}
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
static func unpack(fields: Dictionary) -> Dictionary:
|
|
590
|
+
var parsed: Variant = JSON.parse_string((fields.get("b", PackedByteArray()) as PackedByteArray).get_string_from_utf8())
|
|
591
|
+
return parsed if parsed is Dictionary else {}
|
|
592
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
id: skill/multiplayer-state
|
|
2
|
+
kind: skill
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
summary: "Authority-owned game state in Summer multiplayer: shared and private State groups, validated Commands, and Events."
|
|
5
|
+
use_when:
|
|
6
|
+
- "score, health, doors, rounds, inventories or wallets in a Summer multiplayer game, and who owns them"
|
|
7
|
+
- "players need to collect, buy, use, open or trade things and the authority must validate it"
|
|
8
|
+
- "clients desync, cheat or double-spend; sending one-off effects to every player"
|
|
9
|
+
facets:
|
|
10
|
+
lifecycle:
|
|
11
|
+
- build
|
|
12
|
+
domains:
|
|
13
|
+
- multiplayer
|
|
14
|
+
- game-design
|
|
15
|
+
- scripting
|
|
16
|
+
modalities:
|
|
17
|
+
- scripts
|
|
18
|
+
compatibility:
|
|
19
|
+
engine: ">=4.6"
|
|
20
|
+
related:
|
|
21
|
+
skills:
|
|
22
|
+
- skill/multiplayer
|
|
23
|
+
- skill/multiplayer-movement
|
|
24
|
+
- skill/multiplayer-testing
|
|
25
|
+
- skill/summer-player-data
|
|
26
|
+
- skill/summer-match-results
|
|
27
|
+
- skill/multiplayer-project
|
|
28
|
+
- skill/summer-analytics
|
|
29
|
+
- skill/summer-store
|
|
30
|
+
- skill/summer-world-chat
|
|
31
|
+
- skill/summer-world-saves
|
|
32
|
+
source: official
|
|
33
|
+
license: MIT
|
|
34
|
+
status: stable
|
|
35
|
+
recommended: false
|