agentworkshop 0.2.1
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/.AgentWorkShop/prompts/host-tools.json +631 -0
- package/.AgentWorkShop/prompts/lead-supervise.md +42 -0
- package/.AgentWorkShop/prompts/mode-goal.md +26 -0
- package/.AgentWorkShop/prompts/mode-loop.md +10 -0
- package/.AgentWorkShop/prompts/mode-pipeline.md +16 -0
- package/.AgentWorkShop/prompts/peer-lead-roster.md +3 -0
- package/.AgentWorkShop/prompts/peer-message.md +14 -0
- package/.AgentWorkShop/prompts/peer-reply-optional.md +2 -0
- package/.AgentWorkShop/prompts/peer-reply-required.md +8 -0
- package/.AgentWorkShop/prompts/scenario-default.md +11 -0
- package/.AgentWorkShop/prompts/system-manual.md +18 -0
- package/.AgentWorkShop/prompts/team-roster.md +9 -0
- package/.AgentWorkShop/prompts/worker-workflow.md +19 -0
- package/.env.example +34 -0
- package/README-zh.md +424 -0
- package/README.md +424 -0
- package/app/.mimosa/hook-state/sess_05189059-e42d-4188-8911-4f7baabe009b.json +1 -0
- package/app/.mimosa/hook-state/sess_05189059-e42d-4188-8911-4f7baabe009b.mtg0vz68-49792-75b0709555.baseline/1459e6269990a5d229b9228c8b2f62ffaa7d350068380ae8b875fee0db45cca6.source +180 -0
- package/app/.mimosa/hook-state/sess_05189059-e42d-4188-8911-4f7baabe009b.mtg0vz68-49792-75b0709555.baseline/c2991e909c70c5c12b90a407c7adcf63384c51668a2b334e6e047fa675c268fe.source +305 -0
- package/app/.mimosa/hook-state/sess_05189059-e42d-4188-8911-4f7baabe009b.mtg0vz68-49792-75b0709555.baseline/e09129039d1e27e3fac24becc9bdafd2d1a7c8049bff24173df007be865f0165.source +264 -0
- package/app/.mimosa/hook-status/sess_05189059-e42d-4188-8911-4f7baabe009b-28ef7c75ea.json +14 -0
- package/app/app.vue +139 -0
- package/app/assets/css/main.css +1257 -0
- package/app/components/AppHeader.vue +675 -0
- package/app/components/AppSidebar.vue +286 -0
- package/app/components/AwChart.vue +44 -0
- package/app/components/AwLogo.vue +75 -0
- package/app/components/workshop/A2aRpcDebugger.vue +237 -0
- package/app/components/workshop/AgentInspectorDrawer.vue +424 -0
- package/app/components/workshop/AgentLanesView.vue +947 -0
- package/app/components/workshop/ArtifactCard.vue +120 -0
- package/app/components/workshop/AssetLibrary.vue +450 -0
- package/app/components/workshop/ChannelSessionList.vue +593 -0
- package/app/components/workshop/CommandPalette.vue +270 -0
- package/app/components/workshop/Composer.vue +731 -0
- package/app/components/workshop/DeviceTwinPanel.vue +607 -0
- package/app/components/workshop/EventBlock.vue +602 -0
- package/app/components/workshop/FileSelectorModal.vue +188 -0
- package/app/components/workshop/InspectorPanel.vue +265 -0
- package/app/components/workshop/LiveAgentBar.vue +362 -0
- package/app/components/workshop/MemoryPanel.vue +364 -0
- package/app/components/workshop/MentionHoverCard.vue +155 -0
- package/app/components/workshop/MultiChannelView.vue +189 -0
- package/app/components/workshop/PaneSplitter.vue +142 -0
- package/app/components/workshop/StatsPanel.vue +168 -0
- package/app/components/workshop/TaskBoardView.vue +636 -0
- package/app/components/workshop/TaskInspectorDrawer.vue +390 -0
- package/app/components/workshop/TranscriptTimeline.vue +545 -0
- package/app/components/workshop/blocks/ClusterArtifact.vue +77 -0
- package/app/components/workshop/blocks/ClusterError.vue +85 -0
- package/app/components/workshop/blocks/ClusterLife.vue +87 -0
- package/app/components/workshop/blocks/ClusterMember.vue +93 -0
- package/app/components/workshop/blocks/ClusterMemory.vue +84 -0
- package/app/components/workshop/blocks/ClusterOther.vue +67 -0
- package/app/components/workshop/blocks/ClusterRoute.vue +359 -0
- package/app/components/workshop/blocks/ClusterStatus.vue +147 -0
- package/app/components/workshop/blocks/ClusterStream.vue +325 -0
- package/app/components/workshop/blocks/ClusterTask.vue +151 -0
- package/app/components/workshop/blocks/ClusterTool.vue +158 -0
- package/app/components/workshop/lanes/LaneBlocks.vue +149 -0
- package/app/components/workshop/terminal/OmpTerminalPanel.vue +1010 -0
- package/app/components/workshop/town/ModelPreview3D.vue +276 -0
- package/app/components/workshop/town/TownScene.ts +1355 -0
- package/app/components/workshop/town/TownScene3D.ts +4533 -0
- package/app/components/workshop/town/TownView.vue +7071 -0
- package/app/composables/useCodeCopy.ts +30 -0
- package/app/composables/useHttp.ts +8 -0
- package/app/composables/useRouteMeta.ts +50 -0
- package/app/composables/useSiteConfig.ts +21 -0
- package/app/composables/workshop/apiClient.ts +95 -0
- package/app/composables/workshop/useCharacterAssets.ts +292 -0
- package/app/composables/workshop/useClusteredBlocks.ts +150 -0
- package/app/composables/workshop/useComposerBus.ts +16 -0
- package/app/composables/workshop/useDaqStream.ts +379 -0
- package/app/composables/workshop/useDcwStream.ts +300 -0
- package/app/composables/workshop/useDeviceTwins.ts +147 -0
- package/app/composables/workshop/useEventBlocks.ts +662 -0
- package/app/composables/workshop/useLocalTime.ts +33 -0
- package/app/composables/workshop/useOpsLog.ts +164 -0
- package/app/composables/workshop/useSceneLayouts.ts +166 -0
- package/app/composables/workshop/useTownBus.ts +47 -0
- package/app/composables/workshop/useVisibleInterval.ts +42 -0
- package/app/composables/workshop/useWorkshopApi.ts +224 -0
- package/app/composables/workshop/useWorkshopWs.ts +113 -0
- package/app/config/index.ts +66 -0
- package/app/config/schema.ts +68 -0
- package/app/error.vue +76 -0
- package/app/layouts/default.vue +100 -0
- package/app/layouts/town.vue +16 -0
- package/app/pages/daq/[id].vue +796 -0
- package/app/pages/daq/index.vue +2577 -0
- package/app/pages/dcw/[id].vue +2259 -0
- package/app/pages/dcw/index.vue +1079 -0
- package/app/pages/index.vue +830 -0
- package/app/pages/logs/index.vue +621 -0
- package/app/pages/monitor.vue +665 -0
- package/app/pages/settings.vue +757 -0
- package/app/pages/tokens.vue +610 -0
- package/app/pages/town.vue +145 -0
- package/app/pages/users.vue +257 -0
- package/app/pages/workshop/agents.vue +382 -0
- package/app/pages/workshop/channel-templates.vue +456 -0
- package/app/pages/workshop/index.vue +604 -0
- package/app/pages/workshop/teams.vue +448 -0
- package/app/pages/workshop/w/[wsId].vue +516 -0
- package/app/plugins/http.ts +71 -0
- package/app/plugins/locale-restore.client.ts +13 -0
- package/app/plugins/runtime-config.client.ts +65 -0
- package/app/plugins/session-restore.client.ts +24 -0
- package/app/plugins/theme.client.ts +15 -0
- package/app/stores/app.ts +32 -0
- package/app/stores/route-trail.ts +45 -0
- package/app/stores/runtime-config.ts +212 -0
- package/app/stores/workshop/connection.ts +184 -0
- package/app/stores/workshop/entities.ts +350 -0
- package/app/stores/workshop/events.ts +292 -0
- package/app/stores/workshop/user.ts +176 -0
- package/app/stores/workshop/workspaces.ts +133 -0
- package/bin/aw.mjs +13 -0
- package/cli/aw.mjs +184 -0
- package/cli/commands/build.mjs +33 -0
- package/cli/commands/config.mjs +157 -0
- package/cli/commands/dev.mjs +74 -0
- package/cli/commands/doctor.mjs +120 -0
- package/cli/commands/home.mjs +75 -0
- package/cli/commands/init.mjs +150 -0
- package/cli/commands/register.mjs +123 -0
- package/cli/commands/start.mjs +106 -0
- package/cli/commands/status.mjs +67 -0
- package/cli/commands/update.mjs +116 -0
- package/cli/commands/version.mjs +32 -0
- package/cli/core/args.mjs +95 -0
- package/cli/core/context.mjs +264 -0
- package/cli/core/errors.mjs +18 -0
- package/cli/core/help.mjs +68 -0
- package/cli/core/logger.mjs +40 -0
- package/cli/core/registry.mjs +111 -0
- package/config.yml +70 -0
- package/docker-compose.yml +34 -0
- package/docs/cli.md +220 -0
- package/i18n/dicts/_en.json +1097 -0
- package/i18n/dicts/_en2.json +300 -0
- package/i18n/dicts/_en3.json +102 -0
- package/i18n/dicts/_en4.json +115 -0
- package/i18n/dicts/_en5.json +33 -0
- package/i18n/dicts/a2aRpcDebugger.json +10 -0
- package/i18n/dicts/agentInspectorDrawer.json +25 -0
- package/i18n/dicts/agentLanesView.json +57 -0
- package/i18n/dicts/agents.json +27 -0
- package/i18n/dicts/appHeader.json +7 -0
- package/i18n/dicts/appSidebar.json +1 -0
- package/i18n/dicts/artifactCard.json +6 -0
- package/i18n/dicts/assetLibrary.json +18 -0
- package/i18n/dicts/awChart.json +1 -0
- package/i18n/dicts/awLogo.json +1 -0
- package/i18n/dicts/channelSessionList.json +45 -0
- package/i18n/dicts/clusterArtifact.json +4 -0
- package/i18n/dicts/clusterError.json +3 -0
- package/i18n/dicts/clusterLife.json +5 -0
- package/i18n/dicts/clusterMember.json +3 -0
- package/i18n/dicts/clusterMemory.json +3 -0
- package/i18n/dicts/clusterOther.json +3 -0
- package/i18n/dicts/clusterRoute.json +15 -0
- package/i18n/dicts/clusterStatus.json +3 -0
- package/i18n/dicts/clusterStream.json +3 -0
- package/i18n/dicts/clusterTask.json +10 -0
- package/i18n/dicts/clusterTool.json +3 -0
- package/i18n/dicts/commandPalette.json +16 -0
- package/i18n/dicts/composer.json +38 -0
- package/i18n/dicts/ctpl.json +32 -0
- package/i18n/dicts/daq.json +157 -0
- package/i18n/dicts/daqDetail.json +55 -0
- package/i18n/dicts/dcw.json +68 -0
- package/i18n/dicts/dcwDetail.json +199 -0
- package/i18n/dicts/deviceTwinPanel.json +12 -0
- package/i18n/dicts/eventBlock.json +8 -0
- package/i18n/dicts/fileSelectorModal.json +6 -0
- package/i18n/dicts/inspectorPanel.json +12 -0
- package/i18n/dicts/laneBlocks.json +6 -0
- package/i18n/dicts/liveAgentBar.json +8 -0
- package/i18n/dicts/logs.json +39 -0
- package/i18n/dicts/memoryPanel.json +20 -0
- package/i18n/dicts/mentionHoverCard.json +4 -0
- package/i18n/dicts/modelPreview3D.json +1 -0
- package/i18n/dicts/monitor.json +5 -0
- package/i18n/dicts/multiChannelView.json +13 -0
- package/i18n/dicts/ompTerminalPanel.json +7 -0
- package/i18n/dicts/paneSplitter.json +3 -0
- package/i18n/dicts/settings.json +1 -0
- package/i18n/dicts/statsPanel.json +6 -0
- package/i18n/dicts/taskBoardView.json +22 -0
- package/i18n/dicts/taskInspectorDrawer.json +19 -0
- package/i18n/dicts/teams.json +33 -0
- package/i18n/dicts/tokens.json +30 -0
- package/i18n/dicts/town.json +7 -0
- package/i18n/dicts/townView.json +209 -0
- package/i18n/dicts/transcriptTimeline.json +31 -0
- package/i18n/dicts/users.json +15 -0
- package/i18n/dicts/wsHome.json +33 -0
- package/i18n/dicts/wsView.json +15 -0
- package/i18n/locales/en.ts +1737 -0
- package/i18n/locales/zh-CN.ts +1736 -0
- package/nuxt.config.ts +209 -0
- package/package.json +123 -0
- package/public/assets/game/character/bot.png +0 -0
- package/public/assets/game/character/device-3d.glb +0 -0
- package/public/assets/game/character/hero-3d.glb +0 -0
- package/public/assets/game/character/hero-anime-1.glb +0 -0
- package/public/assets/game/character/hero-anime-2.glb +0 -0
- package/public/assets/game/character/hero-anime-3.glb +0 -0
- package/public/assets/game/character/hero-anime-4.glb +0 -0
- package/public/assets/game/character/knight.png +0 -0
- package/public/assets/game/character/mage.png +0 -0
- package/public/assets/game/coin.png +0 -0
- package/public/assets/game/devices/agv.glb +0 -0
- package/public/assets/game/devices/caster.glb +0 -0
- package/public/assets/game/devices/device-console.glb +0 -0
- package/public/assets/game/devices/device-robot-arm.glb +0 -0
- package/public/assets/game/devices/device-scanner.glb +0 -0
- package/public/assets/game/devices/extruder.glb +0 -0
- package/public/assets/game/devices/mdo.glb +0 -0
- package/public/assets/game/devices/power-cabinet.glb +0 -0
- package/public/assets/game/devices/pump.glb +0 -0
- package/public/assets/game/devices/tdo.glb +0 -0
- package/public/assets/game/devices/thickness-scanner.glb +0 -0
- package/public/assets/game/devices/winder.glb +0 -0
- package/public/assets/game/npc/boltnu-sheet.png +0 -0
- package/public/assets/game/npc/fluttaflap-sheet.png +0 -0
- package/public/assets/game/npc/rockitten-sheet.png +0 -0
- package/public/assets/game/npc/tux-sheet.png +0 -0
- package/public/assets/game/player/misa-back-walk.000.png +0 -0
- package/public/assets/game/player/misa-back-walk.001.png +0 -0
- package/public/assets/game/player/misa-back-walk.002.png +0 -0
- package/public/assets/game/player/misa-back-walk.003.png +0 -0
- package/public/assets/game/player/misa-back.png +0 -0
- package/public/assets/game/player/misa-front-walk.000.png +0 -0
- package/public/assets/game/player/misa-front-walk.001.png +0 -0
- package/public/assets/game/player/misa-front-walk.002.png +0 -0
- package/public/assets/game/player/misa-front-walk.003.png +0 -0
- package/public/assets/game/player/misa-front.png +0 -0
- package/public/assets/game/player/misa-left-walk.000.png +0 -0
- package/public/assets/game/player/misa-left-walk.001.png +0 -0
- package/public/assets/game/player/misa-left-walk.002.png +0 -0
- package/public/assets/game/player/misa-left-walk.003.png +0 -0
- package/public/assets/game/player/misa-left.png +0 -0
- package/public/assets/game/player/misa-right-walk.000.png +0 -0
- package/public/assets/game/player/misa-right-walk.001.png +0 -0
- package/public/assets/game/player/misa-right-walk.002.png +0 -0
- package/public/assets/game/player/misa-right-walk.003.png +0 -0
- package/public/assets/game/player/misa-right.png +0 -0
- package/public/assets/game/player-sheet.png +0 -0
- package/public/assets/game/tuxemon-town.json +1 -0
- package/public/assets/game/tuxmon-sample-32px-extruded.png +0 -0
- package/public/assets/game/wuwa/scene-map.png +0 -0
- package/public/assets/game/wuwa/world-far.png +0 -0
- package/public/assets/game/wuwa/world-middle.png +0 -0
- package/public/assets/game/wuwa/world-near.png +0 -0
- package/public/assets/game/wuwa/wu-aura.png +0 -0
- package/public/assets/game/wuwa/wu-lead.png +0 -0
- package/public/assets/game/wuwa/wu-ring.png +0 -0
- package/public/assets/game/wuwa/wu-slash.png +0 -0
- package/public/assets/game/wuwa/wu-worker-0.png +0 -0
- package/public/assets/game/wuwa/wu-worker-1.png +0 -0
- package/public/assets/game/wuwa/wu-worker-2.png +0 -0
- package/public/favicon.svg +14 -0
- package/public/scene/background/cyber-town-background.svg +110 -0
- package/scripts/_daq-realtime-probe.mjs +68 -0
- package/scripts/_daq-warn-probe.mjs +50 -0
- package/scripts/_dbg-agent-tools-audit.mjs +154 -0
- package/scripts/_dbg-agent-workflow-audit.mjs +177 -0
- package/scripts/_dbg-anim-verify.mjs +81 -0
- package/scripts/_dbg-audit-all-pages.mjs +43 -0
- package/scripts/_dbg-autoframe.mjs +103 -0
- package/scripts/_dbg-beacon-final.mjs +181 -0
- package/scripts/_dbg-bind-bidi-e2e.mjs +239 -0
- package/scripts/_dbg-callout-near.mjs +108 -0
- package/scripts/_dbg-chan-focus.mjs +49 -0
- package/scripts/_dbg-cleanup.mjs +31 -0
- package/scripts/_dbg-closedloop-ui-shot.mjs +61 -0
- package/scripts/_dbg-console-audit.mjs +61 -0
- package/scripts/_dbg-console-probe.mjs +23 -0
- package/scripts/_dbg-context-inject-audit.mjs +231 -0
- package/scripts/_dbg-crash-recovery.mjs +143 -0
- package/scripts/_dbg-crop-shot.mjs +16 -0
- package/scripts/_dbg-daq-acc-probe.mjs +24 -0
- package/scripts/_dbg-daq-attrib.mjs +34 -0
- package/scripts/_dbg-daq-bind-audit.mjs +119 -0
- package/scripts/_dbg-daq-cadence.mjs +104 -0
- package/scripts/_dbg-daq-debug.mjs +22 -0
- package/scripts/_dbg-daq-diag.mjs +33 -0
- package/scripts/_dbg-daq-e2e.mjs +171 -0
- package/scripts/_dbg-daq-film.mjs +136 -0
- package/scripts/_dbg-daq-filter-e2e.mjs +109 -0
- package/scripts/_dbg-daq-filter-e2e2.mjs +62 -0
- package/scripts/_dbg-daq-final-regression.mjs +78 -0
- package/scripts/_dbg-daq-full-check.mjs +179 -0
- package/scripts/_dbg-daq-line-note-verify.mjs +61 -0
- package/scripts/_dbg-daq-linebanner-audit.mjs +107 -0
- package/scripts/_dbg-daq-pages-shot.mjs +37 -0
- package/scripts/_dbg-daq-perf.mjs +61 -0
- package/scripts/_dbg-daq-probe.mjs +27 -0
- package/scripts/_dbg-daq-recipe-e2e.mjs +30 -0
- package/scripts/_dbg-daq-redesign-shot.mjs +88 -0
- package/scripts/_dbg-daq-rows-probe.mjs +26 -0
- package/scripts/_dbg-daq-rowstate-e2e.mjs +152 -0
- package/scripts/_dbg-daq-rowstate-shot.mjs +45 -0
- package/scripts/_dbg-daq-rowstate-shot2.mjs +38 -0
- package/scripts/_dbg-daq-tpl.mjs +144 -0
- package/scripts/_dbg-dash-console-check.mjs +49 -0
- package/scripts/_dbg-dash-deep-probe.mjs +61 -0
- package/scripts/_dbg-dash-hitpoint.mjs +44 -0
- package/scripts/_dbg-dash-live-shot.mjs +36 -0
- package/scripts/_dbg-dash-shot.mjs +21 -0
- package/scripts/_dbg-dashboard-shot.mjs +35 -0
- package/scripts/_dbg-dcw-audit.mjs +126 -0
- package/scripts/_dbg-dcw-card-e2e.mjs +108 -0
- package/scripts/_dbg-dcw-channel-ui.mjs +144 -0
- package/scripts/_dbg-dcw-examples.mjs +81 -0
- package/scripts/_dbg-dcw-light-shot.mjs +19 -0
- package/scripts/_dbg-dcw-line-audit.mjs +107 -0
- package/scripts/_dbg-dcw-pause-tpl-audit.mjs +161 -0
- package/scripts/_dbg-dcw-pause-ui.mjs +175 -0
- package/scripts/_dbg-dcw-scene.mjs +72 -0
- package/scripts/_dbg-dcw-shot2.mjs +14 -0
- package/scripts/_dbg-dcw-twin-e2e.mjs +192 -0
- package/scripts/_dbg-dt-interact.mjs +193 -0
- package/scripts/_dbg-error-probe.mjs +54 -0
- package/scripts/_dbg-filter-light-verify.mjs +54 -0
- package/scripts/_dbg-final-acceptance-2.mjs +72 -0
- package/scripts/_dbg-final-acceptance.mjs +192 -0
- package/scripts/_dbg-final-agent-e2e.mjs +151 -0
- package/scripts/_dbg-final-full-e2e.mjs +218 -0
- package/scripts/_dbg-final-line-e2e.mjs +173 -0
- package/scripts/_dbg-final-t6-hitl.mjs +38 -0
- package/scripts/_dbg-focus-range.mjs +115 -0
- package/scripts/_dbg-fresh-scene-e2e.mjs +125 -0
- package/scripts/_dbg-full-feature-e2e.mjs +219 -0
- package/scripts/_dbg-fullchain-e2e.mjs +178 -0
- package/scripts/_dbg-gate-debug.mjs +37 -0
- package/scripts/_dbg-gif-encode.mjs +111 -0
- package/scripts/_dbg-gizmo.mjs +95 -0
- package/scripts/_dbg-glass-probe.mjs +57 -0
- package/scripts/_dbg-glass-shot.mjs +50 -0
- package/scripts/_dbg-glass-sweep.mjs +25 -0
- package/scripts/_dbg-glb-probe.mjs +43 -0
- package/scripts/_dbg-hardening-probe.mjs +90 -0
- package/scripts/_dbg-hero-btn-probe.mjs +21 -0
- package/scripts/_dbg-hidden-check.mjs +21 -0
- package/scripts/_dbg-hitl-timeout-e2e.mjs +93 -0
- package/scripts/_dbg-hmi-audit.mjs +164 -0
- package/scripts/_dbg-home-repro.mjs +24 -0
- package/scripts/_dbg-home-stack.mjs +13 -0
- package/scripts/_dbg-huashu-survey.mjs +31 -0
- package/scripts/_dbg-huashu-survey2.mjs +35 -0
- package/scripts/_dbg-huashu-verify.mjs +25 -0
- package/scripts/_dbg-huashu-verify2.mjs +26 -0
- package/scripts/_dbg-hydration-detail.mjs +18 -0
- package/scripts/_dbg-i18n-debug.mjs +27 -0
- package/scripts/_dbg-i18n-debug2.mjs +26 -0
- package/scripts/_dbg-i18n-final.mjs +42 -0
- package/scripts/_dbg-i18n-scan.mjs +38 -0
- package/scripts/_dbg-infra-lifecycle.mjs +44 -0
- package/scripts/_dbg-line-aura-shots.mjs +54 -0
- package/scripts/_dbg-line-control-e2e.mjs +120 -0
- package/scripts/_dbg-line-gate-audit.mjs +108 -0
- package/scripts/_dbg-line-live-e2e.mjs +144 -0
- package/scripts/_dbg-line-scenario-e2e.mjs +256 -0
- package/scripts/_dbg-minimal-omp.mjs +77 -0
- package/scripts/_dbg-mode-nav.mjs +173 -0
- package/scripts/_dbg-mqtt-chain-e2e.mjs +90 -0
- package/scripts/_dbg-nav-rpg.mjs +84 -0
- package/scripts/_dbg-nav-shot.mjs +28 -0
- package/scripts/_dbg-nav.mjs +27 -0
- package/scripts/_dbg-opslog-e2e.mjs +121 -0
- package/scripts/_dbg-opslog-shots.mjs +62 -0
- package/scripts/_dbg-opt-team-e2e.mjs +133 -0
- package/scripts/_dbg-optimization-e2e.mjs +94 -0
- package/scripts/_dbg-orbit.mjs +58 -0
- package/scripts/_dbg-panesplitter-fix-check.mjs +62 -0
- package/scripts/_dbg-phase-e3-monitor.mjs +25 -0
- package/scripts/_dbg-pop-clamp-verify.mjs +77 -0
- package/scripts/_dbg-preset-e2e.mjs +116 -0
- package/scripts/_dbg-preview-console.mjs +28 -0
- package/scripts/_dbg-preview-minrepro.mjs +156 -0
- package/scripts/_dbg-preview-pixels.mjs +93 -0
- package/scripts/_dbg-quality-pages-e2e.mjs +84 -0
- package/scripts/_dbg-query-cancel.ts +12 -0
- package/scripts/_dbg-rail-tree-e2e.mjs +183 -0
- package/scripts/_dbg-real-driver-e2e.mjs +55 -0
- package/scripts/_dbg-redesign-shots.mjs +34 -0
- package/scripts/_dbg-restart-persist.mjs +41 -0
- package/scripts/_dbg-rollback-mgr-audit.mjs +169 -0
- package/scripts/_dbg-rpg.mjs +71 -0
- package/scripts/_dbg-sc001-dispatch.mjs +63 -0
- package/scripts/_dbg-scenario-final-shot.mjs +24 -0
- package/scripts/_dbg-scenario-phase-e2.mjs +75 -0
- package/scripts/_dbg-scenario-phase-e3.mjs +96 -0
- package/scripts/_dbg-scenario-phase1.mjs +90 -0
- package/scripts/_dbg-scenario-phase2.mjs +105 -0
- package/scripts/_dbg-scenario-phase3-monitor.mjs +48 -0
- package/scripts/_dbg-scenario-phase3.mjs +111 -0
- package/scripts/_dbg-scene-polish-shot.mjs +118 -0
- package/scripts/_dbg-selring-probe.mjs +47 -0
- package/scripts/_dbg-sideinfo.mjs +101 -0
- package/scripts/_dbg-splitter.mjs +88 -0
- package/scripts/_dbg-team-repro.mjs +76 -0
- package/scripts/_dbg-town-alarm-e2e.mjs +40 -0
- package/scripts/_dbg-town-beauty-shots.mjs +54 -0
- package/scripts/_dbg-town-daq-shot.mjs +26 -0
- package/scripts/_dbg-town-full-e2e.mjs +104 -0
- package/scripts/_dbg-town-gate.mjs +44 -0
- package/scripts/_dbg-town-ins-probe.mjs +37 -0
- package/scripts/_dbg-town-perf.mjs +50 -0
- package/scripts/_dbg-town-polish-shots.mjs +82 -0
- package/scripts/_dbg-town-realtime.mjs +42 -0
- package/scripts/_dbg-town-shot3.mjs +17 -0
- package/scripts/_dbg-town-truth.mjs +37 -0
- package/scripts/_dbg-town-truth2.mjs +27 -0
- package/scripts/_dbg-transform-audit.mjs +98 -0
- package/scripts/_dbg-trend-expand-test.mjs +38 -0
- package/scripts/_dbg-twindaq-focus.mjs +81 -0
- package/scripts/_dbg-ui-polish-shot.mjs +43 -0
- package/scripts/_dbg-ui-polish-verify1.mjs +92 -0
- package/scripts/_dbg-ui-polish-verify2.mjs +50 -0
- package/scripts/_dbg-ui-polish-verify3.mjs +51 -0
- package/scripts/_dbg-ui-polish-verify4.mjs +50 -0
- package/scripts/_dbg-ui-shot.mjs +26 -0
- package/scripts/_dbg-unify-shot.mjs +43 -0
- package/scripts/_dbg-uplink-probe.mjs +36 -0
- package/scripts/_dbg-wizard-preview-shot.mjs +58 -0
- package/scripts/_dbg-wizard-ui-e2e.mjs +93 -0
- package/scripts/_dbg-ws-probe.mjs +86 -0
- package/scripts/_dbg-ws-shot2.mjs +23 -0
- package/scripts/_dbg-wsdot-sanity.mjs +25 -0
- package/scripts/_dev-stack.mjs +46 -0
- package/scripts/_http-driver-probe.mjs +30 -0
- package/scripts/_lib-dcw-line.mjs +24 -0
- package/scripts/_mqtt-driver-probe.mjs +30 -0
- package/scripts/_newdriver-rest-probe.mjs +100 -0
- package/scripts/_node-state-probe.mjs +5 -0
- package/scripts/_opcua-driver-probe.mjs +31 -0
- package/scripts/_rtu-debug.mjs +41 -0
- package/scripts/_rtu-direct.mjs +6 -0
- package/scripts/_rtu-driver-probe.mjs +53 -0
- package/scripts/_rtu-mini-slave.mjs +63 -0
- package/scripts/_townview_style.css +1185 -0
- package/scripts/_townview_template.html +1215 -0
- package/scripts/_wizard-shots.mjs +90 -0
- package/scripts/acceptance-suite.py +207 -0
- package/scripts/api-live-e2e.mjs +467 -0
- package/scripts/build-anime-models.mjs +339 -0
- package/scripts/build-character-models.mjs +175 -0
- package/scripts/build-film-devices.mjs +227 -0
- package/scripts/build-model-3d.mjs +173 -0
- package/scripts/build-player-sheet.mjs +58 -0
- package/scripts/build-world-map.mjs +188 -0
- package/scripts/build-wuwa-assets.mjs +345 -0
- package/scripts/collab-live-test.py +177 -0
- package/scripts/debug-mcp-http.ts +64 -0
- package/scripts/demo-lead-worker.ts +256 -0
- package/scripts/demo-modes.ts +415 -0
- package/scripts/demo-omp-channel.ts +135 -0
- package/scripts/design-preflight.mjs +223 -0
- package/scripts/design-verify-templates.mjs +110 -0
- package/scripts/design-verify.mjs +170 -0
- package/scripts/dev-guard.mjs +74 -0
- package/scripts/dev-modbus-simulator.mjs +55 -0
- package/scripts/dev-opcua-simulator.mjs +57 -0
- package/scripts/dev-protocol-simulators.mjs +145 -0
- package/scripts/dev-window.cmd +6 -0
- package/scripts/device-line-restore.mjs +34 -0
- package/scripts/diag-lead-status.ts +101 -0
- package/scripts/e2e-a2a-steer-live.mjs +193 -0
- package/scripts/e2e-agent-channel-task.ts +151 -0
- package/scripts/e2e-agent-team.mjs +168 -0
- package/scripts/e2e-auth-matrix.mjs +119 -0
- package/scripts/e2e-channel-isolation.ts +325 -0
- package/scripts/e2e-dual-drive-comm.ts +259 -0
- package/scripts/e2e-lead-team-mgmt.ts +217 -0
- package/scripts/e2e-memory-persistence.ts +320 -0
- package/scripts/e2e-memory-system.ts +332 -0
- package/scripts/e2e-omp-modes.ts +274 -0
- package/scripts/e2e-omp-realtime.ts +258 -0
- package/scripts/e2e-omp-workspace.ts +180 -0
- package/scripts/e2e-parallel-execution.mjs +151 -0
- package/scripts/e2e-rest-memory.mjs +251 -0
- package/scripts/e2e-rest-robustness.mjs +177 -0
- package/scripts/e2e-resume-crash.mjs +254 -0
- package/scripts/e2e-tab-close.mjs +136 -0
- package/scripts/e2e-task-queue.ts +277 -0
- package/scripts/e2e-template-isolation.mjs +267 -0
- package/scripts/e2e-token-ui.mjs +149 -0
- package/scripts/final-soup-run.py +192 -0
- package/scripts/final-soup-watch.py +83 -0
- package/scripts/home-bootstrap.mjs +117 -0
- package/scripts/i18n-codemod.mjs +173 -0
- package/scripts/i18n-fix-en.py +28 -0
- package/scripts/i18n-fix-en2.py +31 -0
- package/scripts/i18n-merge.mjs +67 -0
- package/scripts/omp-real-test.py +328 -0
- package/scripts/smoke-optimization-verify.ts +72 -0
- package/scripts/start.mjs +60 -0
- package/scripts/test-a2a-relay.mjs +222 -0
- package/scripts/test-a2a-types.ts +296 -0
- package/scripts/test-agent-brain.ts +135 -0
- package/scripts/test-agent-multi-channel.ts +160 -0
- package/scripts/test-agent-runtime.ts +348 -0
- package/scripts/test-api-client.ts +108 -0
- package/scripts/test-api-suite.mjs +271 -0
- package/scripts/test-block-consumption.mjs +194 -0
- package/scripts/test-channel-routing.ts +301 -0
- package/scripts/test-cmd-render.mjs +218 -0
- package/scripts/test-collab-e2e.mjs +136 -0
- package/scripts/test-collab-omp.mjs +192 -0
- package/scripts/test-dedup-logic.ts +132 -0
- package/scripts/test-dual-drive.ts +227 -0
- package/scripts/test-e2e-collaboration.ts +262 -0
- package/scripts/test-e2e-monitor.ts +192 -0
- package/scripts/test-event-history.ts +112 -0
- package/scripts/test-event-persistence.mjs +169 -0
- package/scripts/test-exec-modes.ts +316 -0
- package/scripts/test-fifo-realtime.mjs +193 -0
- package/scripts/test-final-e2e.mjs +290 -0
- package/scripts/test-full-system.ts +439 -0
- package/scripts/test-game-session.ts +160 -0
- package/scripts/test-goal-summary.ts +202 -0
- package/scripts/test-harness-liveness.ts +109 -0
- package/scripts/test-harness-terminal.ts +164 -0
- package/scripts/test-header-trail.mjs +95 -0
- package/scripts/test-hitl.ts +360 -0
- package/scripts/test-koda-restyle.mjs +105 -0
- package/scripts/test-lane-history-live.mjs +103 -0
- package/scripts/test-lane-raw.ts +67 -0
- package/scripts/test-lanes-history-render.mjs +138 -0
- package/scripts/test-loop-omp.mjs +154 -0
- package/scripts/test-mcp-tools.ts +285 -0
- package/scripts/test-mdlite.ts +53 -0
- package/scripts/test-memory-dynamic.ts +99 -0
- package/scripts/test-memory-maintenance.ts +117 -0
- package/scripts/test-memory-vector.ts +154 -0
- package/scripts/test-memory.ts +431 -0
- package/scripts/test-mock-collab.mjs +226 -0
- package/scripts/test-modern-render.mjs +174 -0
- package/scripts/test-omp-terminal.mjs +187 -0
- package/scripts/test-orchestration.ts +425 -0
- package/scripts/test-persistence-lazy.ts +323 -0
- package/scripts/test-prompt-system.mjs +181 -0
- package/scripts/test-prompts-loader.ts +56 -0
- package/scripts/test-protocol.ts +156 -0
- package/scripts/test-real-scenario.mjs +290 -0
- package/scripts/test-route-reason.mjs +111 -0
- package/scripts/test-scheduler-loop.ts +469 -0
- package/scripts/test-stream-render.ts +37 -0
- package/scripts/test-task-drawer.mjs +204 -0
- package/scripts/test-task-engine.ts +433 -0
- package/scripts/test-task-invariants.mjs +169 -0
- package/scripts/test-terminal-e2e.mjs +264 -0
- package/scripts/test-town-3d.mjs +121 -0
- package/scripts/test-town-drag.mjs +218 -0
- package/scripts/test-town-layout.mjs +200 -0
- package/scripts/test-town-page.mjs +136 -0
- package/scripts/test-town-scene-math.ts +169 -0
- package/scripts/test-town-twin.mjs +121 -0
- package/scripts/test-town-upload.mjs +89 -0
- package/scripts/test-town-user-flow.mjs +619 -0
- package/scripts/test-town-world.mjs +76 -0
- package/scripts/test-town-wuwa.mjs +198 -0
- package/scripts/test-user-isolation.mjs +193 -0
- package/scripts/test-workshop-db.ts +280 -0
- package/scripts/test-workshop-entries.ts +370 -0
- package/scripts/test-workspace-api.ts +329 -0
- package/scripts/test-ws-aep.mjs +187 -0
- package/scripts/test-ws-protocol.ts +134 -0
- package/scripts/test-ws-stress.mjs +208 -0
- package/scripts/town-demo-seed.mjs +75 -0
- package/scripts/town-scene-clear.mjs +56 -0
- package/scripts/ui-screenshot.mjs +128 -0
- package/scripts/verify-a2a-live.mjs +124 -0
- package/scripts/verify-exec-modes-report.ts +248 -0
- package/scripts/verify-game.mjs +477 -0
- package/scripts/verify-lead-mail.ts +188 -0
- package/scripts/verify-page-smoke.mjs +87 -0
- package/scripts/verify-ws-events.mjs +123 -0
- package/scripts/ws-test.mjs +176 -0
- package/scripts/ws-test.py +4 -0
- package/server/api/health.get.ts +32 -0
- package/server/api/mcp/workshop.ts +78 -0
- package/server/api/metrics.ts +75 -0
- package/server/api/system/config.get.ts +12 -0
- package/server/api/system/monitor/terminal/ws.ts +177 -0
- package/server/api/system/monitor/terminate.post.ts +36 -0
- package/server/api/system/monitor.get.ts +13 -0
- package/server/api/system/settings/events.get.ts +28 -0
- package/server/api/system/settings/index.get.ts +14 -0
- package/server/api/system/settings/index.patch.ts +22 -0
- package/server/api/system/settings/reset.post.ts +12 -0
- package/server/api/users/[id].delete.ts +11 -0
- package/server/api/users/[id].get.ts +11 -0
- package/server/api/users/[id].put.ts +14 -0
- package/server/api/users/index.get.ts +13 -0
- package/server/api/users/index.post.ts +13 -0
- package/server/api/users/login.post.ts +11 -0
- package/server/api/users/logout.post.ts +9 -0
- package/server/api/users/me.get.ts +9 -0
- package/server/api/users/register.post.ts +11 -0
- package/server/api/users/tokens/[id]/token.get.ts +11 -0
- package/server/api/users/tokens/[id].delete.ts +11 -0
- package/server/api/users/tokens/[id].patch.ts +14 -0
- package/server/api/users/tokens/index.get.ts +9 -0
- package/server/api/users/tokens/index.post.ts +13 -0
- package/server/api/workshop/[...path].ts +13 -0
- package/server/api/workshop/a2a/[agentId]/card/index.get.ts +75 -0
- package/server/api/workshop/a2a/[agentId]/rpc/index.post.ts +429 -0
- package/server/api/workshop/a2a/send.post.ts +28 -0
- package/server/api/workshop/agent-tools/approvals/[id]/decide.post.ts +21 -0
- package/server/api/workshop/agent-tools/approvals/index.get.ts +18 -0
- package/server/api/workshop/agent-tools/bindings/[id].delete.ts +21 -0
- package/server/api/workshop/agent-tools/bindings/[id].patch.ts +14 -0
- package/server/api/workshop/agent-tools/bindings/index.get.ts +14 -0
- package/server/api/workshop/agent-tools/bindings/index.post.ts +20 -0
- package/server/api/workshop/agent-tools/invoke.post.ts +34 -0
- package/server/api/workshop/agents/[id].delete.ts +20 -0
- package/server/api/workshop/agents/[id].get.ts +19 -0
- package/server/api/workshop/agents/[id].patch.ts +30 -0
- package/server/api/workshop/agents/index.get.ts +13 -0
- package/server/api/workshop/agents/index.post.ts +29 -0
- package/server/api/workshop/agents/subscribe.post.ts +21 -0
- package/server/api/workshop/approvals/[id]/decide.post.ts +30 -0
- package/server/api/workshop/approvals/index.get.ts +22 -0
- package/server/api/workshop/assets/character/[id].delete.ts +33 -0
- package/server/api/workshop/assets/character/index.get.ts +17 -0
- package/server/api/workshop/assets/character/index.post.ts +87 -0
- package/server/api/workshop/assets/devices/[id].delete.ts +43 -0
- package/server/api/workshop/assets/devices/index.get.ts +103 -0
- package/server/api/workshop/assets/devices/index.post.ts +53 -0
- package/server/api/workshop/audit.get.ts +43 -0
- package/server/api/workshop/caller.ts +141 -0
- package/server/api/workshop/channel-templates/[id]/instantiate.post.ts +31 -0
- package/server/api/workshop/channel-templates/[id].delete.ts +20 -0
- package/server/api/workshop/channel-templates/[id].get.ts +19 -0
- package/server/api/workshop/channel-templates/[id].patch.ts +48 -0
- package/server/api/workshop/channel-templates/from-channel.post.ts +31 -0
- package/server/api/workshop/channel-templates/index.get.ts +13 -0
- package/server/api/workshop/channel-templates/index.post.ts +61 -0
- package/server/api/workshop/channels/[id]/activate.post.ts +19 -0
- package/server/api/workshop/channels/[id]/agents/[agentId]/memories/[memoryId].delete.ts +30 -0
- package/server/api/workshop/channels/[id]/agents/[agentId]/memories/index.get.ts +29 -0
- package/server/api/workshop/channels/[id]/agents/[agentId]/memories/index.post.ts +48 -0
- package/server/api/workshop/channels/[id]/agents/[agentId]/memories/search.post.ts +43 -0
- package/server/api/workshop/channels/[id]/agents/[agentId]/model.patch.ts +39 -0
- package/server/api/workshop/channels/[id]/agents/[agentId]/position.patch.ts +33 -0
- package/server/api/workshop/channels/[id]/agents/[agentId]/range.patch.ts +54 -0
- package/server/api/workshop/channels/[id]/agents/[agentId]/stop.post.ts +25 -0
- package/server/api/workshop/channels/[id]/agents/[agentId].delete.ts +23 -0
- package/server/api/workshop/channels/[id]/agents/[agentId].patch.ts +28 -0
- package/server/api/workshop/channels/[id]/agents/index.get.ts +27 -0
- package/server/api/workshop/channels/[id]/agents/index.post.ts +61 -0
- package/server/api/workshop/channels/[id]/events.get.ts +55 -0
- package/server/api/workshop/channels/[id]/index.get.ts +18 -0
- package/server/api/workshop/channels/[id]/index.patch.ts +30 -0
- package/server/api/workshop/channels/[id]/memories/[memoryId].delete.ts +29 -0
- package/server/api/workshop/channels/[id]/memories/index.get.ts +28 -0
- package/server/api/workshop/channels/[id]/memories/index.post.ts +42 -0
- package/server/api/workshop/channels/[id]/messages/index.get.ts +56 -0
- package/server/api/workshop/channels/[id]/messages/index.post.ts +75 -0
- package/server/api/workshop/channels/[id]/queue.get.ts +25 -0
- package/server/api/workshop/channels/[id]/tasks/index.get.ts +24 -0
- package/server/api/workshop/channels/[id]/tasks/index.post.ts +79 -0
- package/server/api/workshop/channels/[id]/terminals.get.ts +21 -0
- package/server/api/workshop/channels/[id].delete.ts +18 -0
- package/server/api/workshop/channels.get.ts +11 -0
- package/server/api/workshop/channels.post.ts +48 -0
- package/server/api/workshop/daq/[id]/bind.post.ts +19 -0
- package/server/api/workshop/daq/[id]/samples.get.ts +22 -0
- package/server/api/workshop/daq/[id]/test.post.ts +17 -0
- package/server/api/workshop/daq/[id].delete.ts +30 -0
- package/server/api/workshop/daq/[id].patch.ts +31 -0
- package/server/api/workshop/daq/alarms/[id]/ack.post.ts +15 -0
- package/server/api/workshop/daq/alarms.get.ts +16 -0
- package/server/api/workshop/daq/controller.post.ts +52 -0
- package/server/api/workshop/daq/index.get.ts +40 -0
- package/server/api/workshop/daq/index.post.ts +33 -0
- package/server/api/workshop/daq/infra/reconnect.post.ts +17 -0
- package/server/api/workshop/daq/infra.get.ts +11 -0
- package/server/api/workshop/daq/templates/[key].delete.ts +17 -0
- package/server/api/workshop/daq/templates/[key].patch.ts +18 -0
- package/server/api/workshop/daq/templates/index.post.ts +17 -0
- package/server/api/workshop/daq/test-driver.post.ts +23 -0
- package/server/api/workshop/dcw/[id]/bind.post.ts +17 -0
- package/server/api/workshop/dcw/[id]/param-ledger.get.ts +15 -0
- package/server/api/workshop/dcw/[id]/test.post.ts +15 -0
- package/server/api/workshop/dcw/[id]/write.post.ts +19 -0
- package/server/api/workshop/dcw/[id].delete.ts +30 -0
- package/server/api/workshop/dcw/[id].patch.ts +18 -0
- package/server/api/workshop/dcw/controller.post.ts +43 -0
- package/server/api/workshop/dcw/index.get.ts +25 -0
- package/server/api/workshop/dcw/index.post.ts +20 -0
- package/server/api/workshop/dcw/journal/index.get.ts +21 -0
- package/server/api/workshop/dcw/journal/node/[nodeId]/rollback.post.ts +22 -0
- package/server/api/workshop/dcw/line/query.get.ts +31 -0
- package/server/api/workshop/dcw/lines/[id]/start.post.ts +35 -0
- package/server/api/workshop/dcw/lines/[id]/stop.post.ts +34 -0
- package/server/api/workshop/dcw/lines/[id].delete.ts +35 -0
- package/server/api/workshop/dcw/lines/[id].patch.ts +15 -0
- package/server/api/workshop/dcw/lines/index.get.ts +14 -0
- package/server/api/workshop/dcw/lines/index.post.ts +31 -0
- package/server/api/workshop/dcw/optimizations/[id]/judge.post.ts +24 -0
- package/server/api/workshop/dcw/optimizations/[id]/rollback.post.ts +29 -0
- package/server/api/workshop/dcw/optimizations/[id]/series.get.ts +18 -0
- package/server/api/workshop/dcw/optimizations/[id].get.ts +17 -0
- package/server/api/workshop/dcw/optimizations/index.get.ts +23 -0
- package/server/api/workshop/dcw/products/[id].delete.ts +16 -0
- package/server/api/workshop/dcw/products/[id].patch.ts +17 -0
- package/server/api/workshop/dcw/products/index.post.ts +16 -0
- package/server/api/workshop/dcw/recipes/[id]/apply.post.ts +40 -0
- package/server/api/workshop/dcw/recipes/[id]/mark-good.post.ts +21 -0
- package/server/api/workshop/dcw/recipes/[id]/rollback-good.post.ts +19 -0
- package/server/api/workshop/dcw/recipes/[id].delete.ts +14 -0
- package/server/api/workshop/dcw/recipes/[id].patch.ts +16 -0
- package/server/api/workshop/dcw/recipes/index.get.ts +12 -0
- package/server/api/workshop/dcw/recipes/index.post.ts +15 -0
- package/server/api/workshop/dcw/runs/[id]/close.post.ts +15 -0
- package/server/api/workshop/dcw/runs/[id]/data.get.ts +15 -0
- package/server/api/workshop/dcw/runs/[id]/rollback.post.ts +20 -0
- package/server/api/workshop/dcw/runs/index.get.ts +11 -0
- package/server/api/workshop/dcw/templates/[key].delete.ts +14 -0
- package/server/api/workshop/dcw/templates/[key].patch.ts +16 -0
- package/server/api/workshop/dcw/templates/index.post.ts +15 -0
- package/server/api/workshop/dcw/test-driver.post.ts +16 -0
- package/server/api/workshop/device-twins/[id]/control.post.ts +21 -0
- package/server/api/workshop/device-twins/[id]/telemetry.post.ts +21 -0
- package/server/api/workshop/device-twins/[id].delete.ts +25 -0
- package/server/api/workshop/device-twins/[id].patch.ts +29 -0
- package/server/api/workshop/device-twins/index.get.ts +16 -0
- package/server/api/workshop/device-twins/index.post.ts +32 -0
- package/server/api/workshop/fs/dirs.get.ts +40 -0
- package/server/api/workshop/mailbox/all.get.ts +19 -0
- package/server/api/workshop/mailbox.get.ts +14 -0
- package/server/api/workshop/memories/maintenance.post.ts +15 -0
- package/server/api/workshop/ops-logs/index.get.ts +30 -0
- package/server/api/workshop/ops-logs/index.post.ts +39 -0
- package/server/api/workshop/runtime.get.ts +11 -0
- package/server/api/workshop/scene/layouts/[channelId].delete.ts +23 -0
- package/server/api/workshop/scene/layouts/[channelId].put.ts +31 -0
- package/server/api/workshop/scene/layouts.get.ts +13 -0
- package/server/api/workshop/tasks/[id]/cancel/index.post.ts +34 -0
- package/server/api/workshop/tasks/[id]/complete.post.ts +32 -0
- package/server/api/workshop/tasks/[id]/dispatch.post.ts +30 -0
- package/server/api/workshop/tasks/[id]/report.post.ts +38 -0
- package/server/api/workshop/tasks/[id]/retry.post.ts +33 -0
- package/server/api/workshop/tasks/[id].get.ts +32 -0
- package/server/api/workshop/tasks.get.ts +13 -0
- package/server/api/workshop/teams/[id]/deploy.post.ts +40 -0
- package/server/api/workshop/teams/[id]/members/[agentId].delete.ts +19 -0
- package/server/api/workshop/teams/[id]/members/index.get.ts +19 -0
- package/server/api/workshop/teams/[id]/members/index.post.ts +32 -0
- package/server/api/workshop/teams/[id].delete.ts +20 -0
- package/server/api/workshop/teams/[id].get.ts +19 -0
- package/server/api/workshop/teams/[id].patch.ts +32 -0
- package/server/api/workshop/teams/index.get.ts +13 -0
- package/server/api/workshop/teams/index.post.ts +27 -0
- package/server/api/workshop/users/me.get.ts +23 -0
- package/server/api/workshop/users/register.post.ts +33 -0
- package/server/api/workshop/workspaces/[id]/channel-templates/[tplId].post.ts +31 -0
- package/server/api/workshop/workspaces/[id]/channels/[channelId].delete.ts +15 -0
- package/server/api/workshop/workspaces/[id]/channels/[channelId].post.ts +15 -0
- package/server/api/workshop/workspaces/[id]/index.delete.ts +14 -0
- package/server/api/workshop/workspaces/index.get.ts +11 -0
- package/server/api/workshop/workspaces/index.post.ts +17 -0
- package/server/api/workshop/ws.ts +694 -0
- package/server/data/agent-node-bindings.json +554 -0
- package/server/data/daq-templates.json +16 -0
- package/server/data/daqs.json +2680 -0
- package/server/data/dcw-lines.json +919 -0
- package/server/data/dcw-products.json +835 -0
- package/server/data/dcw-recipes.json +1650 -0
- package/server/data/dcw-rollback.json +9339 -0
- package/server/data/dcw-runs.json +4633 -0
- package/server/data/dcw-templates.json +26 -0
- package/server/data/dcw-writes.json +9770 -0
- package/server/data/dcws.json +1736 -0
- package/server/data/device-twins.json +133 -0
- package/server/data/line-runs.json +152 -0
- package/server/data/scene-layouts.json +200 -0
- package/server/mcp/workshop-server.ts +510 -0
- package/server/middleware/request-log.ts +15 -0
- package/server/plugins/backup.ts +85 -0
- package/server/plugins/daq.ts +65 -0
- package/server/plugins/dev-stability-guard.ts +42 -0
- package/server/plugins/security.ts +29 -0
- package/server/plugins/system-config.ts +38 -0
- package/server/plugins/workshop.ts +145 -0
- package/server/repositories/user.repository.ts +391 -0
- package/server/schemas/user.schema.ts +67 -0
- package/server/services/system-config.ts +356 -0
- package/server/services/user.service.ts +186 -0
- package/server/services/workshop/agents/adapters/omp-rpc-client.ts +539 -0
- package/server/services/workshop/agents/adapters/to-a2a.ts +40 -0
- package/server/services/workshop/agents/agent-interface.ts +219 -0
- package/server/services/workshop/agents/claude-agent.ts +47 -0
- package/server/services/workshop/agents/factory.ts +24 -0
- package/server/services/workshop/agents/harness-process.ts +158 -0
- package/server/services/workshop/agents/harness-terminal.ts +545 -0
- package/server/services/workshop/agents/industrial-context.ts +222 -0
- package/server/services/workshop/agents/industrial-tools.ts +362 -0
- package/server/services/workshop/agents/mock-agent.ts +396 -0
- package/server/services/workshop/agents/node-bindings.repo.ts +165 -0
- package/server/services/workshop/agents/omp-agent.ts +1598 -0
- package/server/services/workshop/agents/tool-approvals.ts +174 -0
- package/server/services/workshop/assets/character-asset.repo.ts +120 -0
- package/server/services/workshop/assets/device-twin.repo.ts +236 -0
- package/server/services/workshop/daq/alarm-notify.ts +100 -0
- package/server/services/workshop/daq/bus/index.ts +46 -0
- package/server/services/workshop/daq/bus/inproc.adapter.ts +62 -0
- package/server/services/workshop/daq/bus/mqtt.adapter.ts +112 -0
- package/server/services/workshop/daq/bus/queue-port.ts +36 -0
- package/server/services/workshop/daq/daq-controller.ts +778 -0
- package/server/services/workshop/daq/daq-node.repo.ts +96 -0
- package/server/services/workshop/daq/daq-node.ts +247 -0
- package/server/services/workshop/daq/daq-runtime.ts +168 -0
- package/server/services/workshop/daq/daq-templates.ts +157 -0
- package/server/services/workshop/daq/drivers.ts +771 -0
- package/server/services/workshop/daq/host-bindings.ts +71 -0
- package/server/services/workshop/daq/host-ports.ts +64 -0
- package/server/services/workshop/daq/infra.ts +202 -0
- package/server/services/workshop/daq/storage/index.ts +96 -0
- package/server/services/workshop/daq/storage/sqlite.adapter.ts +205 -0
- package/server/services/workshop/daq/storage/timescale.adapter.ts +185 -0
- package/server/services/workshop/daq/storage/tsdb-port.ts +74 -0
- package/server/services/workshop/db/agent.repo.ts +105 -0
- package/server/services/workshop/db/channel-agent.repo.ts +119 -0
- package/server/services/workshop/db/channel-event.repo.ts +154 -0
- package/server/services/workshop/db/channel-template.repo.ts +113 -0
- package/server/services/workshop/db/channel.repo.ts +101 -0
- package/server/services/workshop/db/database.ts +934 -0
- package/server/services/workshop/db/memory.repo.ts +249 -0
- package/server/services/workshop/db/message.repo.ts +149 -0
- package/server/services/workshop/db/ops.repo.ts +323 -0
- package/server/services/workshop/db/schema.sql +143 -0
- package/server/services/workshop/db/subscription.repo.ts +62 -0
- package/server/services/workshop/db/task.repo.ts +175 -0
- package/server/services/workshop/db/team-member.repo.ts +70 -0
- package/server/services/workshop/db/team.repo.ts +109 -0
- package/server/services/workshop/db/user.repo.ts +100 -0
- package/server/services/workshop/dcw/dcw-controller.ts +841 -0
- package/server/services/workshop/dcw/dcw-line.repo.ts +86 -0
- package/server/services/workshop/dcw/dcw-node.repo.ts +89 -0
- package/server/services/workshop/dcw/dcw-node.ts +200 -0
- package/server/services/workshop/dcw/dcw-product.repo.ts +95 -0
- package/server/services/workshop/dcw/dcw-recipe.repo.ts +349 -0
- package/server/services/workshop/dcw/dcw-runtime.ts +82 -0
- package/server/services/workshop/dcw/dcw-templates.ts +138 -0
- package/server/services/workshop/dcw/drivers.ts +523 -0
- package/server/services/workshop/dcw/line-run.ts +101 -0
- package/server/services/workshop/dcw/recipe-rollback-manager.ts +702 -0
- package/server/services/workshop/dcw/recipe-rollback.repo.ts +197 -0
- package/server/services/workshop/logger.ts +58 -0
- package/server/services/workshop/ops/ops.ts +78 -0
- package/server/services/workshop/prompts/loader.ts +112 -0
- package/server/services/workshop/runtime/agent-runtime.ts +698 -0
- package/server/services/workshop/runtime/channel-runtime.ts +202 -0
- package/server/services/workshop/runtime/embedding-provider.ts +79 -0
- package/server/services/workshop/runtime/execution-mode.ts +215 -0
- package/server/services/workshop/runtime/mailbox.ts +148 -0
- package/server/services/workshop/runtime/manager.ts +2543 -0
- package/server/services/workshop/runtime/memory.ts +439 -0
- package/server/services/workshop/runtime/monitor.ts +194 -0
- package/server/services/workshop/runtime/scheduler-loop.ts +773 -0
- package/server/services/workshop/runtime/task-engine.ts +574 -0
- package/server/services/workshop/scene/scene-layout.repo.ts +112 -0
- package/server/services/workshop/scene-events.ts +63 -0
- package/server/services/workshop/types/a2a.ts +61 -0
- package/server/services/workshop/types/task.ts +85 -0
- package/server/types/user.ts +53 -0
- package/server/utils/approval-gate.ts +67 -0
- package/server/utils/auth.ts +13 -0
- package/server/utils/config.ts +40 -0
- package/server/utils/errors.ts +24 -0
- package/server/utils/response.ts +49 -0
- package/server/utils/validate.ts +19 -0
- package/shared/config/engine.d.mts +41 -0
- package/shared/config/engine.mjs +291 -0
- package/shared/config/home.d.mts +48 -0
- package/shared/config/home.mjs +146 -0
- package/shared/config/schema.json +30 -0
- package/shared/daq-protocol.ts +361 -0
- package/shared/dcw-protocol.ts +576 -0
- package/shared/terminal-protocol.ts +70 -0
- package/shared/town-anim.ts +56 -0
- package/shared/town-behavior.ts +136 -0
- package/shared/town-protocol.ts +110 -0
- package/shared/town-scene-math.ts +283 -0
- package/shared/workshop-protocol.ts +199 -0
- package/tsconfig.json +10 -0
- package/uno.config.ts +37 -0
|
@@ -0,0 +1,1355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentTeam RPG 小镇(鸣潮·共鸣黄昏版)— Phaser 2D 实时可视化场景。
|
|
3
|
+
*
|
|
4
|
+
* 渲染层职责(只渲染,不决策):
|
|
5
|
+
* - 背景:原创「共鸣黄昏」场景地图(黄昏天空/山脊/台地),不再是白色方块。
|
|
6
|
+
* - 每个 Channel → 地图上一片同色「共鸣领地」(环形能场 + 柔光,频道色=领地色);
|
|
7
|
+
* - 每个 Agent → 一个「员工」sprite(Leader/Worker 职务章),头顶名字/状态环/进度条;
|
|
8
|
+
* - 同一 Channel 的 Agent 共享同一种领地共鸣色:脚下灵光(aura)颜色一致,一眼可辨"谁属于哪个频道";
|
|
9
|
+
* - 所有 Agent 均可被用户手动拖动到地图任意位置(拖动期间暂停自动行为,松手后落点即新 home);
|
|
10
|
+
* - 事件驱动(useTownBus 旁路,与时间线同源)→ 头顶气泡 / 状态环 / 进度 / 行为 FSM;
|
|
11
|
+
* - `getDebugState()` 暴露渲染态供浏览器断言。
|
|
12
|
+
*
|
|
13
|
+
* 数据源:初始实体来自 Vue 传入 entities 快照;实时增量来自 handleTownEvent(AEP)。
|
|
14
|
+
*/
|
|
15
|
+
import * as Phaser from 'phaser'
|
|
16
|
+
import type { AepEnvelope } from '#shared/workshop-protocol'
|
|
17
|
+
import { mapEnvelopeToIntent, type TownBubbleKind } from '#shared/town-protocol'
|
|
18
|
+
import { parseActionFromEnvelope, stepToward, type ActionKind, type ActionContext } from '#shared/town-behavior'
|
|
19
|
+
import { resolveAnimDef, type ModelAnimSpec } from '#shared/town-anim'
|
|
20
|
+
|
|
21
|
+
/** 场景 → Vue HUD 事件 */
|
|
22
|
+
export type TownEventMap = {
|
|
23
|
+
ready: boolean
|
|
24
|
+
fps: number
|
|
25
|
+
agentCount: number
|
|
26
|
+
blockCount: number
|
|
27
|
+
/** 最后一个气泡(去重去抖动):HUD 显示"此刻谁在说话" */
|
|
28
|
+
lastActivity: { channelId: string, agentName: string, text: string } | null
|
|
29
|
+
/** 行为动作日志(供调试/E2E 断言"角色跑去下发任务") */
|
|
30
|
+
behavior: { agentName: string, action: string, targetName: string | null } | null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 由 Vue entities store 传入的初始实体基线 */
|
|
34
|
+
export interface TownEntityInput {
|
|
35
|
+
channelId: string
|
|
36
|
+
channelName: string
|
|
37
|
+
agents: Array<{
|
|
38
|
+
agentId: string
|
|
39
|
+
name: string
|
|
40
|
+
role: 'lead' | 'worker'
|
|
41
|
+
harness: string
|
|
42
|
+
state: 'idle' | 'busy' | 'stopped'
|
|
43
|
+
currentTaskId?: string | null
|
|
44
|
+
currentTaskTitle?: string | null
|
|
45
|
+
currentTaskProgress?: number | null
|
|
46
|
+
/** 用户给该角色绑定的自定义模型(assetId;缺省用内置员工模型) */
|
|
47
|
+
modelRef?: string | null
|
|
48
|
+
}>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 频道共鸣领地渲染态 */
|
|
52
|
+
interface TownBlockDef {
|
|
53
|
+
channelId: string
|
|
54
|
+
name: string
|
|
55
|
+
centerX: number
|
|
56
|
+
centerY: number
|
|
57
|
+
radius: number
|
|
58
|
+
/** 频道共鸣色(number,供 tint) */
|
|
59
|
+
colorNum: number
|
|
60
|
+
/** 频道共鸣色(css rgba,供名字牌) */
|
|
61
|
+
rgba: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 角色 sprite 渲染态 */
|
|
65
|
+
interface AgentSprite {
|
|
66
|
+
channelId: string
|
|
67
|
+
agentId: string
|
|
68
|
+
name: string
|
|
69
|
+
role: 'lead' | 'worker'
|
|
70
|
+
sprite: Phaser.Physics.Arcade.Sprite
|
|
71
|
+
/** 脚下共鸣灵光(同频道同色) */
|
|
72
|
+
aura: Phaser.GameObjects.Image
|
|
73
|
+
/** 状态环(头顶) */
|
|
74
|
+
statusRing: Phaser.GameObjects.Graphics
|
|
75
|
+
/** 名字标签(头顶) */
|
|
76
|
+
nameLabel: Phaser.GameObjects.Text
|
|
77
|
+
/** 进度标签(busy 时) */
|
|
78
|
+
progressLabel: Phaser.GameObjects.Text
|
|
79
|
+
/** 当前气泡(每 agent 至多一个) */
|
|
80
|
+
bubble: Phaser.GameObjects.Container | null
|
|
81
|
+
bubbleTimer: Phaser.Time.TimerEvent | null
|
|
82
|
+
state: 'idle' | 'busy' | 'stopped'
|
|
83
|
+
progress: number | null
|
|
84
|
+
/** 行为状态机 */
|
|
85
|
+
behavior: BehaviorState
|
|
86
|
+
/** 用户正在拖动此 sprite(拖动期间暂停自动行为) */
|
|
87
|
+
dragging: boolean
|
|
88
|
+
/** 初始身位(成员原生位置,行为结束后回归;用户拖动后更新为落点) */
|
|
89
|
+
homeX: number
|
|
90
|
+
homeY: number
|
|
91
|
+
/** 当前渲染纹理 key(内置 wu-* 或自定义模型) */
|
|
92
|
+
textureKey: string
|
|
93
|
+
/** 用户绑定的自定义模型 id(缺省空字符串=内置) */
|
|
94
|
+
modelRef: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 行为状态机(标准 AI 控制:事件→决策→动作) */
|
|
98
|
+
type BehaviorMode
|
|
99
|
+
= | 'idle' // 待机(位于 home)
|
|
100
|
+
| 'roam' // 在领地内/附近游走(来回移动)
|
|
101
|
+
| 'approach' // 跑去目标(下发任务/通信)
|
|
102
|
+
| 'wait' // 在目标附近等待回复/执行结果
|
|
103
|
+
| 'returnHome' // 事毕回归出生位
|
|
104
|
+
|
|
105
|
+
interface BehaviorState {
|
|
106
|
+
mode: BehaviorMode
|
|
107
|
+
/** 游走目标点(来回移动端点) */
|
|
108
|
+
roamTarget: { x: number, y: number } | null
|
|
109
|
+
/** 目标角色(跑去下发/通信对象) */
|
|
110
|
+
targetId: string | null
|
|
111
|
+
/** 等待计时(到点返回) */
|
|
112
|
+
waitUntil: number
|
|
113
|
+
/** 行为动作日志(调试) */
|
|
114
|
+
action?: { kind: ActionKind, taskKind?: string, requireReply: boolean, text: string }
|
|
115
|
+
/** 是否正被他人「跑来下发/通信」(被 approach 或 wait)中 —— 暂停自身游走,驻足配合 */
|
|
116
|
+
engaged: boolean
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---- 世界尺度(大坐标世界:3200×2400) ----
|
|
120
|
+
const WORLD_W = 3200
|
|
121
|
+
const WORLD_H = 2400
|
|
122
|
+
/** 领地带基线(台地可立足区) */
|
|
123
|
+
const FIELD_Y = 1760
|
|
124
|
+
/** 环形大道布点:街区围绕世界中心围成一圈 */
|
|
125
|
+
const WORLD_CX = WORLD_W / 2
|
|
126
|
+
const WORLD_CY = FIELD_Y
|
|
127
|
+
const RING_RADIUS_X = 980
|
|
128
|
+
const RING_RADIUS_Y = 560
|
|
129
|
+
|
|
130
|
+
/** worker sprite 图集候选(按 agentId 哈希选长相;均为员工形象) */
|
|
131
|
+
const WORKER_SHEETS = ['wu-worker-0', 'wu-worker-1', 'wu-worker-2'] as const
|
|
132
|
+
const LEAD_SHEET = 'wu-lead'
|
|
133
|
+
const WALK_SPEED = 150
|
|
134
|
+
|
|
135
|
+
/** 角色行为运动速度(下发任务跑动 / 游走) */
|
|
136
|
+
const AGENT_SPEED = 96
|
|
137
|
+
/** 等待回复/执行结果时长(ms) */
|
|
138
|
+
const WAIT_MS = 2600
|
|
139
|
+
|
|
140
|
+
/** 气泡配色 */
|
|
141
|
+
const BUBBLE_STYLE: Record<TownBubbleKind, { bg: number, fg: string }> = {
|
|
142
|
+
info: { bg: 0x1c1917, fg: '#ffffff' },
|
|
143
|
+
artifact: { bg: 0x1c1917, fg: '#9ecb7a' },
|
|
144
|
+
error: { bg: 0x3a1d1c, fg: '#ff9e9e' },
|
|
145
|
+
system: { bg: 0x1c1917, fg: '#c9c4bd' },
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---- 频道共鸣色(稳定,同频道同色;色相桶与 shared/town-scene-math 同源) ----
|
|
149
|
+
const CHANNEL_HUES = [16, 40, 158, 178, 195, 212, 235, 262] as const
|
|
150
|
+
function channelHue(id: string): number {
|
|
151
|
+
if (!id) return 212
|
|
152
|
+
let h = 0
|
|
153
|
+
for (const c of id) h = (h * 31 + c.charCodeAt(0)) % 360
|
|
154
|
+
return CHANNEL_HUES[h % CHANNEL_HUES.length]!
|
|
155
|
+
}
|
|
156
|
+
function channelColorNum(channelId: string): number {
|
|
157
|
+
const c = Phaser.Display.Color.HSLToColor(channelHue(channelId) / 360, 0.52, 0.6)
|
|
158
|
+
return c.color
|
|
159
|
+
}
|
|
160
|
+
function channelRGBA(channelId: string, alpha = 1): string {
|
|
161
|
+
const c = Phaser.Display.Color.HSLToColor(channelHue(channelId) / 360, 0.52, 0.6)
|
|
162
|
+
return `rgba(${c.red},${c.green},${c.blue},${alpha})`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export class TownScene extends Phaser.Scene {
|
|
166
|
+
private blocks = new Map<string, TownBlockDef>()
|
|
167
|
+
private agents = new Map<string, AgentSprite>()
|
|
168
|
+
/** 领地/装饰(game objects),resetAll 时销毁 */
|
|
169
|
+
private blockDecor: Phaser.GameObjects.GameObject[] = []
|
|
170
|
+
private mapBg!: Phaser.GameObjects.Image
|
|
171
|
+
/** 视差背景层(远/中);scroll 因子控制不同步平移 */
|
|
172
|
+
private farLayer!: Phaser.GameObjects.Image
|
|
173
|
+
private midLayer!: Phaser.GameObjects.Image
|
|
174
|
+
private scrollFactorFar = 0.06
|
|
175
|
+
private scrollFactorMid = 0.14
|
|
176
|
+
private cursors!: Phaser.Types.Input.Keyboard.CursorKeys
|
|
177
|
+
private keyW!: Phaser.Input.Keyboard.Key
|
|
178
|
+
private keyA!: Phaser.Input.Keyboard.Key
|
|
179
|
+
private keyS!: Phaser.Input.Keyboard.Key
|
|
180
|
+
private keyD!: Phaser.Input.Keyboard.Key
|
|
181
|
+
private player!: Phaser.Physics.Arcade.Sprite
|
|
182
|
+
private facing: 'down' | 'left' | 'right' | 'up' = 'down'
|
|
183
|
+
private dirty = true
|
|
184
|
+
private dbgBubbles: Array<{ text: string, at: number }> = []
|
|
185
|
+
private dbgActivity: { channelId: string, agentName: string, text: string } | null = null
|
|
186
|
+
/** 最近活动队列(跑马灯,上限 6) */
|
|
187
|
+
private recentActivity: Array<{ channelId: string, agentName: string, text: string }> = []
|
|
188
|
+
private agentCount = 0
|
|
189
|
+
private blockCount = 0
|
|
190
|
+
|
|
191
|
+
private frameCount = 0
|
|
192
|
+
private fpsAccum = 0
|
|
193
|
+
private readonly bus = new Phaser.Events.EventEmitter()
|
|
194
|
+
/** 已注册的自定义模型(id → file),供拖拽换装/生成用 */
|
|
195
|
+
private modelsById = new Map<string, { id: string, file: string, name: string, spec?: ModelAnimSpec }>()
|
|
196
|
+
/** 任务 ID → assignee 反查(由 Vue 注入;mock 任务投递缺 target-agent 时用) */
|
|
197
|
+
resolveTaskAssignee: ((taskId: string) => string | null) | null = null
|
|
198
|
+
|
|
199
|
+
constructor(seed?: TownEntityInput[]) {
|
|
200
|
+
super('town')
|
|
201
|
+
if (seed) this._seed = seed
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
on<K extends keyof TownEventMap>(event: K, fn: (e: TownEventMap[K]) => void): () => void {
|
|
205
|
+
this.bus.on(event, fn)
|
|
206
|
+
return () => this.bus.off(event, fn)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private emit<K extends keyof TownEventMap>(event: K, e: TownEventMap[K]): void {
|
|
210
|
+
this.bus.emit(event, e)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** 初始化实体基线(挂载后、场景 create 前调用;快照未到时为空) */
|
|
214
|
+
seedEntities(channels: TownEntityInput[]): void {
|
|
215
|
+
this._seed = channels
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** 快照后重建:全量替换领地与角色(丢弃旧树) */
|
|
219
|
+
rebuild(channels: TownEntityInput[]): void {
|
|
220
|
+
this.resetAll()
|
|
221
|
+
this._seed = channels
|
|
222
|
+
this.buildBlocks()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** 聚焦某频道领地(传送玩家 + 镜头;用于深链/截图稳定) */
|
|
226
|
+
focusChannel(channelId: string): void {
|
|
227
|
+
const def = this.blocks.get(channelId)
|
|
228
|
+
if (!def) return
|
|
229
|
+
this.player.setPosition(def.centerX, def.centerY + 70)
|
|
230
|
+
;(this.player.body as Phaser.Physics.Arcade.Body).reset(def.centerX, def.centerY + 70)
|
|
231
|
+
this.cameras.main.centerOn(def.centerX, def.centerY + 70)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private _seed: TownEntityInput[] = []
|
|
235
|
+
|
|
236
|
+
preload(): void {
|
|
237
|
+
// 大世界分层背景(远山天空 / 中景城市 / 近景台地)
|
|
238
|
+
this.load.image('world-far', '/assets/game/wuwa/world-far.png')
|
|
239
|
+
this.load.image('world-middle', '/assets/game/wuwa/world-middle.png')
|
|
240
|
+
this.load.image('world-near', '/assets/game/wuwa/world-near.png')
|
|
241
|
+
// 辉光/环/飘带(运行时 tint 成频道共鸣色)
|
|
242
|
+
this.load.image('wu-aura', '/assets/game/wuwa/wu-aura.png')
|
|
243
|
+
this.load.image('wu-ring', '/assets/game/wuwa/wu-ring.png')
|
|
244
|
+
this.load.image('wu-slash', '/assets/game/wuwa/wu-slash.png')
|
|
245
|
+
// 员工 sprite(4 帧悬停 bob;48x88 → 192x88)
|
|
246
|
+
const fr = { frameWidth: 48, frameHeight: 88 }
|
|
247
|
+
this.load.spritesheet(LEAD_SHEET, '/assets/game/wuwa/wu-lead.png', fr)
|
|
248
|
+
this.load.spritesheet('wu-worker-0', '/assets/game/wuwa/wu-worker-0.png', fr)
|
|
249
|
+
this.load.spritesheet('wu-worker-1', '/assets/game/wuwa/wu-worker-1.png', fr)
|
|
250
|
+
this.load.spritesheet('wu-worker-2', '/assets/game/wuwa/wu-worker-2.png', fr)
|
|
251
|
+
// 角色模型库(可拖拽加载的自定义模型;与内置同帧布局,textures 在 create 前就绪)
|
|
252
|
+
this.load.spritesheet('knight', '/assets/game/character/knight.png', fr)
|
|
253
|
+
this.load.spritesheet('mage', '/assets/game/character/mage.png', fr)
|
|
254
|
+
this.load.spritesheet('bot', '/assets/game/character/bot.png', fr)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
create(): void {
|
|
258
|
+
// ---------- 世界地面(近景台地,随相机滚动) ----------
|
|
259
|
+
this.mapBg = this.add.image(WORLD_W / 2, WORLD_H / 2, 'world-near')
|
|
260
|
+
.setDisplaySize(WORLD_W, WORLD_H)
|
|
261
|
+
.setDepth(-1000)
|
|
262
|
+
this.physics.world.setBounds(0, 0, WORLD_W, WORLD_H)
|
|
263
|
+
|
|
264
|
+
// ---------- 视差背景(远山天空/城市剪影:相机固定,轻微不同步 → 纵深) ----------
|
|
265
|
+
this.scrollFactorFar = 0.06
|
|
266
|
+
this.scrollFactorMid = 0.14
|
|
267
|
+
this.createParallax()
|
|
268
|
+
|
|
269
|
+
this.createAnimations()
|
|
270
|
+
|
|
271
|
+
// ---------- 玩家(镜头跟随;WASD/方向键漫游;滚轮缩放) ----------
|
|
272
|
+
this.viewer = this.physics.add.sprite(WORLD_W / 2, FIELD_Y, LEAD_SHEET, 0)
|
|
273
|
+
this.viewer.setScale(1.05)
|
|
274
|
+
const vbody = this.viewer.body as Phaser.Physics.Arcade.Body
|
|
275
|
+
vbody.setSize(18, 18)
|
|
276
|
+
vbody.setOffset(15, 66)
|
|
277
|
+
this.viewer.setCollideWorldBounds(true)
|
|
278
|
+
this.viewer.setDepth(this.viewer.y)
|
|
279
|
+
this.viewer.anims.play(`wu-bob-${LEAD_SHEET}`, true)
|
|
280
|
+
// 玩家脚下微光(与频道区分:中性暖白)
|
|
281
|
+
this.playerAura = this.add.image(this.viewer.x, this.viewer.y, 'wu-aura')
|
|
282
|
+
.setTint(0xffe9c4).setAlpha(0.45).setDepth(this.viewer.y - 10).setScale(1.3)
|
|
283
|
+
|
|
284
|
+
const cam = this.cameras.main
|
|
285
|
+
cam.setBounds(0, 0, WORLD_W, WORLD_H)
|
|
286
|
+
cam.startFollow(this.viewer, true, 0.09, 0.09)
|
|
287
|
+
cam.setRoundPixels(true)
|
|
288
|
+
cam.setZoom(1.0)
|
|
289
|
+
this.input.on('wheel', (_p: Phaser.Input.Pointer, _o: unknown, _dx: number, dy: number) => {
|
|
290
|
+
const nz = cam.zoom + (dy < 0 ? 0.06 : -0.06)
|
|
291
|
+
cam.setZoom(Phaser.Math.Clamp(nz, 0.7, 1.4))
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
// ---------- 输入 ----------
|
|
295
|
+
this.cursors = this.input.keyboard!.createCursorKeys()
|
|
296
|
+
this.keyW = this.input.keyboard!.addKey(Phaser.Input.Keyboard.KeyCodes.W)
|
|
297
|
+
this.keyA = this.input.keyboard!.addKey(Phaser.Input.Keyboard.KeyCodes.A)
|
|
298
|
+
this.keyS = this.input.keyboard!.addKey(Phaser.Input.Keyboard.KeyCodes.S)
|
|
299
|
+
this.keyD = this.input.keyboard!.addKey(Phaser.Input.Keyboard.KeyCodes.D)
|
|
300
|
+
|
|
301
|
+
// ---------- 领地 + 角色 ----------
|
|
302
|
+
this.buildBlocks()
|
|
303
|
+
this.drawCore()
|
|
304
|
+
|
|
305
|
+
// 镜头聚焦镇中心:玩家传送到第一领地附近
|
|
306
|
+
const firstDef = this.blocks.values().next().value
|
|
307
|
+
if (firstDef) {
|
|
308
|
+
this.viewer.setPosition(firstDef.centerX, firstDef.centerY + 70)
|
|
309
|
+
;(this.viewer.body as Phaser.Physics.Arcade.Body).reset(firstDef.centerX, firstDef.centerY + 70)
|
|
310
|
+
cam.centerOn(firstDef.centerX, firstDef.centerY + 70)
|
|
311
|
+
}
|
|
312
|
+
this.emit('ready', true)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private viewer!: Phaser.Physics.Arcade.Sprite
|
|
316
|
+
private playerAura!: Phaser.GameObjects.Image
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* 视差背景:远山天空/城市剪影按相机 scroll 因子不同步平移,产生纵深。
|
|
320
|
+
* 层固定在相机上(scrollFactor=0),位置随 update 里相机 scroll 反推。
|
|
321
|
+
*/
|
|
322
|
+
private createParallax(): void {
|
|
323
|
+
const cam = this.cameras.main
|
|
324
|
+
// 铺满视野(约 1100x700 @ zoom1),随 zoom 放大
|
|
325
|
+
this.farLayer = this.add.image(WORLD_W / 2, WORLD_H / 2, 'world-far')
|
|
326
|
+
.setScrollFactor(0).setDepth(-1200)
|
|
327
|
+
this.midLayer = this.add.image(WORLD_W / 2, WORLD_H / 2, 'world-middle')
|
|
328
|
+
.setScrollFactor(0).setDepth(-1150)
|
|
329
|
+
// 初始尺寸
|
|
330
|
+
void cam
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** 每帧按相机 scroll 让视差层产生缓慢位移(远层更慢) */
|
|
334
|
+
private updateParallax(): void {
|
|
335
|
+
const cam = this.cameras.main
|
|
336
|
+
// 以世界中心为锚:相机偏离中心越多,背景偏移越多(但远层偏移更少 → 相对慢速)
|
|
337
|
+
const dx = (cam.scrollX + cam.width / 2) - WORLD_CX
|
|
338
|
+
const dy = (cam.scrollY + cam.height / 2) - WORLD_CY
|
|
339
|
+
const f = cam.zoom
|
|
340
|
+
this.farLayer.setPosition(cam.width / 2 - dx * this.scrollFactorFar / f, cam.height / 2 - dy * this.scrollFactorFar / f)
|
|
341
|
+
this.midLayer.setPosition(cam.width / 2 - dx * this.scrollFactorMid / f, cam.height / 2 - dy * this.scrollFactorMid / f)
|
|
342
|
+
// 随缩放放大背景(与视野同比例)
|
|
343
|
+
this.farLayer.setScale(Math.max(0.6, 0.6 / f))
|
|
344
|
+
this.midLayer.setScale(Math.max(0.72, 0.72 / f))
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** 依据实体基线建领地与角色(围绕世界中心的环形大道布点) */
|
|
348
|
+
buildBlocks(): void {
|
|
349
|
+
const seeds = this._seed ?? []
|
|
350
|
+
const count = Math.max(1, seeds.length)
|
|
351
|
+
const RING_X = RING_RADIUS_X
|
|
352
|
+
const RING_Y = RING_RADIUS_Y
|
|
353
|
+
const radius = count <= 1 ? 210 : Math.min(210, 300 - count * 8)
|
|
354
|
+
|
|
355
|
+
seeds.forEach((ch, i) => {
|
|
356
|
+
// 环形布点:沿椭圆等分角度,中心对称、有机不呆板
|
|
357
|
+
const ang = i === 0 ? -Math.PI / 2 : -Math.PI / 2 + (i * 2 * Math.PI) / count
|
|
358
|
+
const cx = WORLD_CX + Math.cos(ang) * RING_X
|
|
359
|
+
const cy = WORLD_CY + Math.sin(ang) * RING_Y
|
|
360
|
+
const def: TownBlockDef = {
|
|
361
|
+
channelId: ch.channelId,
|
|
362
|
+
name: ch.channelName,
|
|
363
|
+
centerX: Math.round(cx),
|
|
364
|
+
centerY: Math.round(cy),
|
|
365
|
+
radius,
|
|
366
|
+
colorNum: channelColorNum(ch.channelId),
|
|
367
|
+
rgba: channelRGBA(ch.channelId, 0.9),
|
|
368
|
+
}
|
|
369
|
+
this.blocks.set(ch.channelId, def)
|
|
370
|
+
this.drawBlock(def)
|
|
371
|
+
for (const a of ch.agents) {
|
|
372
|
+
this.ensureAgentSprite({ ...a, channelId: ch.channelId }, def.centerX, def.centerY)
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
this.blockCount = this.blocks.size
|
|
376
|
+
this.agentCount = this.agents.size
|
|
377
|
+
this.dirty = true
|
|
378
|
+
this.emit('blockCount', this.blockCount)
|
|
379
|
+
this.emit('agentCount', this.agentCount)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** 建一片频道共鸣领地(环形能场 + 柔光 + 频道名牌;无白色方块) */
|
|
383
|
+
private drawBlock(def: TownBlockDef): void {
|
|
384
|
+
const { centerX: cx, centerY: cy, radius: r, colorNum } = def
|
|
385
|
+
// 地面柔光(大面积低透明,暗示能场)
|
|
386
|
+
const field = this.add.image(cx, cy, 'wu-aura')
|
|
387
|
+
.setScale(r / 56).setTint(colorNum).setAlpha(0.30).setDepth(-20)
|
|
388
|
+
// 环形能场边界(呼吸)
|
|
389
|
+
const ring = this.add.image(cx, cy, 'wu-ring')
|
|
390
|
+
.setScale(r / 128).setTint(colorNum).setAlpha(0.55).setDepth(-19)
|
|
391
|
+
this.tweens.add({
|
|
392
|
+
targets: ring,
|
|
393
|
+
alpha: { from: 0.35, to: 0.6 },
|
|
394
|
+
scale: { from: (r / 128) * 0.96, to: (r / 128) * 1.04 },
|
|
395
|
+
yoyo: true,
|
|
396
|
+
repeat: -1,
|
|
397
|
+
duration: 2400,
|
|
398
|
+
ease: 'sine.inout',
|
|
399
|
+
})
|
|
400
|
+
// 频道名牌(顶部) —— 由 drawLandmark 挂在塔顶
|
|
401
|
+
const stroke = this.add.graphics().setDepth(-18)
|
|
402
|
+
stroke.lineStyle(1.2, colorNum, 0.4)
|
|
403
|
+
stroke.strokeCircle(cx, cy, r)
|
|
404
|
+
// 地面柔软阴影(建筑/领地投影,低透明)
|
|
405
|
+
const shadow = this.add.image(cx, cy + 22, 'wu-aura')
|
|
406
|
+
.setScale(r / 52).setTint(0x0a1410).setAlpha(0.22).setDepth(-17)
|
|
407
|
+
this.blockDecor.push(field, ring, stroke, shadow)
|
|
408
|
+
|
|
409
|
+
this.drawLandmark(def)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 街区地标建筑(2.5D 挤出:顶面 + 侧壁,侧壁随朝向变暗 → 有体积)。
|
|
414
|
+
* 用 wu-ring 作底座 + wu-aura 作顶面辉光,叠加成一座低模塔;无真实 tile 也能有"人工建筑"体积感。
|
|
415
|
+
*/
|
|
416
|
+
private drawLandmark(def: TownBlockDef): void {
|
|
417
|
+
const { centerX: cx, centerY: cy, radius: r, colorNum } = def
|
|
418
|
+
const baseY = cy - r * 0.55
|
|
419
|
+
// 底座(地面投影)
|
|
420
|
+
const base = this.add.ellipse(cx, baseY, r * 0.5, r * 0.2, colorNum, 0.55).setDepth(-16)
|
|
421
|
+
// 塔身(侧壁,深色→体积,受夕阳侧光:右亮左暗)
|
|
422
|
+
const body = this.add.rectangle(cx, baseY - 42, r * 0.34, 88, colorNum, 0.85).setDepth(baseY - 20).setOrigin(0.5, 1)
|
|
423
|
+
body.setStrokeStyle(1.5, colorNum, 0.6)
|
|
424
|
+
// 顶面(略亮,承接天光)
|
|
425
|
+
const top = this.add.ellipse(cx, baseY - 84, r * 0.4, r * 0.16, 0xffffff, 0.28).setDepth(baseY - 20)
|
|
426
|
+
top.setStrokeStyle(1.5, colorNum, 0.6)
|
|
427
|
+
// 顶面光柱(呼吸)
|
|
428
|
+
const beam = this.add.image(cx, baseY - 120, 'wu-aura')
|
|
429
|
+
.setScale(0.9).setTint(0xfff0cf).setAlpha(0.5).setDepth(baseY - 20)
|
|
430
|
+
this.tweens.add({ targets: beam, alpha: { from: 0.3, to: 0.6 }, scale: { from: 0.7, to: 1.05 }, yoyo: true, repeat: -1, duration: 1800, ease: 'sine.inout' })
|
|
431
|
+
// 频道名牌(挂在塔顶)
|
|
432
|
+
const plaque = this.add.text(cx, baseY - 128, def.name, {
|
|
433
|
+
fontFamily: 'Geist, PingFang SC, sans-serif',
|
|
434
|
+
fontSize: '13px',
|
|
435
|
+
fontStyle: '600',
|
|
436
|
+
color: '#fff',
|
|
437
|
+
padding: { x: 10, y: 4 },
|
|
438
|
+
backgroundColor: 'rgba(18,20,30,0.6)',
|
|
439
|
+
}).setOrigin(0.5).setDepth(baseY - 19)
|
|
440
|
+
// 色盲徽记:频道用几何形状区分(不单靠色相),置于名牌上方
|
|
441
|
+
const emblem = this.drawEmblem(cx, baseY - 148, colorNum, def.channelId)
|
|
442
|
+
this.blockDecor.push(base, body, top, beam, plaque, emblem)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** 频道几何徽记(色盲友好:形状+色双通道);形状由 channelId 哈希稳定决定 */
|
|
446
|
+
private drawEmblem(cx: number, cy: number, colorNum: number, channelId: string): Phaser.GameObjects.Graphics {
|
|
447
|
+
const g = this.add.graphics().setDepth(cy + 1000)
|
|
448
|
+
const s = 9
|
|
449
|
+
g.lineStyle(2, colorNum, 0.95)
|
|
450
|
+
g.fillStyle(colorNum, 0.35)
|
|
451
|
+
const shape = Math.abs(channelId.split('').reduce((h, c) => h * 31 + c.charCodeAt(0), 0)) % 5
|
|
452
|
+
if (shape === 0) g.strokeCircle(cx, cy, s) // 圆
|
|
453
|
+
else if (shape === 1) { // 三角
|
|
454
|
+
g.beginPath()
|
|
455
|
+
g.moveTo(cx, cy - s)
|
|
456
|
+
g.lineTo(cx + s, cy + s)
|
|
457
|
+
g.lineTo(cx - s, cy + s)
|
|
458
|
+
g.closePath()
|
|
459
|
+
g.strokePath()
|
|
460
|
+
}
|
|
461
|
+
else if (shape === 2) { // 方块
|
|
462
|
+
g.strokeRect(cx - s, cy - s, s * 2, s * 2)
|
|
463
|
+
}
|
|
464
|
+
else if (shape === 3) { // 菱形
|
|
465
|
+
g.beginPath()
|
|
466
|
+
g.moveTo(cx, cy - s)
|
|
467
|
+
g.lineTo(cx + s, cy)
|
|
468
|
+
g.lineTo(cx, cy + s)
|
|
469
|
+
g.lineTo(cx - s, cy)
|
|
470
|
+
g.closePath()
|
|
471
|
+
g.strokePath()
|
|
472
|
+
}
|
|
473
|
+
else { // 五边
|
|
474
|
+
g.beginPath()
|
|
475
|
+
for (let i = 0; i < 5; i++) {
|
|
476
|
+
const a = -Math.PI / 2 + (i * 2 * Math.PI) / 5
|
|
477
|
+
const px = cx + Math.cos(a) * s
|
|
478
|
+
const py = cy + Math.sin(a) * s
|
|
479
|
+
if (i === 0) g.moveTo(px, py)
|
|
480
|
+
else g.lineTo(px, py)
|
|
481
|
+
}
|
|
482
|
+
g.closePath()
|
|
483
|
+
g.strokePath()
|
|
484
|
+
}
|
|
485
|
+
return g
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* 世界中心「共鸣核心塔」(= workspace 本体):高耸能量柱,随全局活动呼吸。
|
|
490
|
+
* 街区沿大道环绕它分布,一眼可辨世界中心。
|
|
491
|
+
*/
|
|
492
|
+
private drawCore(): void {
|
|
493
|
+
const cx = WORLD_CX
|
|
494
|
+
const cy = WORLD_CY
|
|
495
|
+
// 塔基能量场(大面积)
|
|
496
|
+
const field = this.add.image(cx, cy, 'wu-aura').setScale(6).setTint(0x4da3ff).setAlpha(0.28).setDepth(-20)
|
|
497
|
+
// 塔身(多层收窄的能量柱)
|
|
498
|
+
const body = this.add.rectangle(cx, cy - 90, 120, 300, 0x57d29a, 0.7).setOrigin(0.5, 1).setDepth(cy - 22)
|
|
499
|
+
body.setStrokeStyle(2, 0xd8fff2, 0.6)
|
|
500
|
+
const body2 = this.add.rectangle(cx, cy - 220, 70, 180, 0xc4f4e8, 0.75).setOrigin(0.5, 1).setDepth(cy - 21)
|
|
501
|
+
// 顶部光球(呼吸)
|
|
502
|
+
const orb = this.add.image(cx, cy - 320, 'wu-aura').setScale(1.4).setTint(0xeafff8).setAlpha(0.9).setDepth(cy - 20)
|
|
503
|
+
this.tweens.add({ targets: orb, alpha: { from: 0.7, to: 1 }, scale: { from: 1.2, to: 1.6 }, yoyo: true, repeat: -1, duration: 1600, ease: 'sine.inout' })
|
|
504
|
+
// 塔名
|
|
505
|
+
const label = this.add.text(cx, cy - 360, '共鸣核心', {
|
|
506
|
+
fontFamily: 'Geist, PingFang SC, sans-serif',
|
|
507
|
+
fontSize: '14px',
|
|
508
|
+
fontStyle: '600',
|
|
509
|
+
color: '#fff',
|
|
510
|
+
padding: { x: 12, y: 5 },
|
|
511
|
+
backgroundColor: 'rgba(18,20,30,0.6)',
|
|
512
|
+
}).setOrigin(0.5).setDepth(cy - 19)
|
|
513
|
+
this.blockDecor.push(field, body, body2, orb, label)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** 确保某 agent 的 sprite 存在(不存在则建);返回是否新建 */
|
|
517
|
+
private ensureAgentSprite(
|
|
518
|
+
a: { channelId: string, agentId: string, name: string, role: 'lead' | 'worker', state: 'idle' | 'busy' | 'stopped', currentTaskProgress?: number | null, modelRef?: string | null },
|
|
519
|
+
cx?: number,
|
|
520
|
+
cy?: number,
|
|
521
|
+
): boolean {
|
|
522
|
+
const key = a.agentId
|
|
523
|
+
if (this.agents.has(key)) {
|
|
524
|
+
this.agents.get(key)!.state = a.state
|
|
525
|
+
return false
|
|
526
|
+
}
|
|
527
|
+
const def = this.blocks.get(a.channelId)
|
|
528
|
+
const colorNum = def?.colorNum ?? channelColorNum(a.channelId)
|
|
529
|
+
const builtinSheet = a.role === 'lead' ? LEAD_SHEET : WORKER_SHEETS[Math.abs(a.agentId.split('').reduce((h, c) => h * 31 + c.charCodeAt(0), 0)) % WORKER_SHEETS.length]!
|
|
530
|
+
// 模型绑定:若该角色绑定了自定义模型且纹理已注册,则用之;否则回退内置员工模型
|
|
531
|
+
const sheet = a.modelRef && this.textures.exists(a.modelRef) ? a.modelRef : builtinSheet
|
|
532
|
+
const bx = def?.centerX ?? cx ?? 400
|
|
533
|
+
const by = def?.centerY ?? cy ?? 300
|
|
534
|
+
// 领地成员沿中线横向排布:首个(lead)居中,其余左右展开
|
|
535
|
+
const inBlock = [...this.agents.values()].filter(s => s.channelId === a.channelId).length
|
|
536
|
+
const colSlot = Math.floor(inBlock / 2)
|
|
537
|
+
const x = bx + (inBlock === 0 ? 0 : (inBlock % 2 === 0 ? -1 : 1) * (colSlot > 0 ? colSlot * 54 : 54))
|
|
538
|
+
const y = by + 12 + Math.floor(inBlock / 2) * 16
|
|
539
|
+
|
|
540
|
+
const sprite = this.physics.add.sprite(x, y, sheet, 0)
|
|
541
|
+
const sbody = sprite.body as Phaser.Physics.Arcade.Body
|
|
542
|
+
sbody.setSize(18, 18)
|
|
543
|
+
sbody.setOffset(15, 66)
|
|
544
|
+
sprite.setCollideWorldBounds(true)
|
|
545
|
+
sprite.setDepth(y)
|
|
546
|
+
sprite.anims.play(`wu-bob-${sheet}`, true)
|
|
547
|
+
|
|
548
|
+
// 共鸣灵光:同频道同色(频道共鸣色 tint),居中于角色身体,形成"周身散发同色光"的环绕感
|
|
549
|
+
const aura = this.add.image(x, y, 'wu-aura')
|
|
550
|
+
.setTint(colorNum).setAlpha(0.42).setDepth(y - 10).setScale(1.5)
|
|
551
|
+
this.tweens.add({ targets: aura, alpha: { from: 0.30, to: 0.56 }, scale: { from: 1.35, to: 1.65 }, yoyo: true, repeat: -1, duration: 2200, ease: 'sine.inout' })
|
|
552
|
+
|
|
553
|
+
// 头顶:状态环 + 名字 + 进度(名字牌用频道共鸣色)
|
|
554
|
+
const statusRing = this.add.graphics().setDepth(y + 200)
|
|
555
|
+
const nameLabel = this.add.text(x, y - 30, a.name, {
|
|
556
|
+
fontFamily: 'Geist, PingFang SC, sans-serif',
|
|
557
|
+
fontSize: '10.5px',
|
|
558
|
+
fontStyle: '600',
|
|
559
|
+
color: '#ffffff',
|
|
560
|
+
padding: { x: 6, y: 2 },
|
|
561
|
+
}).setOrigin(0.5, 1).setDepth(y + 200)
|
|
562
|
+
nameLabel.setStyle({ backgroundColor: `rgba(18,20,30,0.62)`, color: '#fff' })
|
|
563
|
+
const progressLabel = this.add.text(x, y - 44, '', {
|
|
564
|
+
fontFamily: 'Geist Mono, monospace',
|
|
565
|
+
fontSize: '9px',
|
|
566
|
+
color: '#4c8f63',
|
|
567
|
+
backgroundColor: 'rgba(255,255,255,0.85)',
|
|
568
|
+
padding: { x: 4, y: 1 },
|
|
569
|
+
}).setOrigin(0.5, 1).setDepth(y + 200)
|
|
570
|
+
|
|
571
|
+
this.agents.set(key, {
|
|
572
|
+
channelId: a.channelId,
|
|
573
|
+
agentId: a.agentId,
|
|
574
|
+
name: a.name,
|
|
575
|
+
role: a.role,
|
|
576
|
+
sprite,
|
|
577
|
+
aura,
|
|
578
|
+
statusRing,
|
|
579
|
+
nameLabel,
|
|
580
|
+
progressLabel,
|
|
581
|
+
bubble: null,
|
|
582
|
+
bubbleTimer: null,
|
|
583
|
+
state: a.state,
|
|
584
|
+
progress: a.currentTaskProgress ?? null,
|
|
585
|
+
dragging: false,
|
|
586
|
+
homeX: x,
|
|
587
|
+
homeY: y,
|
|
588
|
+
textureKey: sheet,
|
|
589
|
+
modelRef: a.modelRef ?? '',
|
|
590
|
+
behavior: {
|
|
591
|
+
mode: 'idle',
|
|
592
|
+
roamTarget: null,
|
|
593
|
+
targetId: null,
|
|
594
|
+
waitUntil: 0,
|
|
595
|
+
engaged: false,
|
|
596
|
+
},
|
|
597
|
+
})
|
|
598
|
+
// 所有角色都允许用户手动拖动到地图任意位置
|
|
599
|
+
this.enableDraggable(this.agents.get(key)!)
|
|
600
|
+
this.agentCount = this.agents.size
|
|
601
|
+
this.emit('agentCount', this.agentCount)
|
|
602
|
+
return true
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** 让全部角色可被用户拖动(手动定位到地图任意位置) */
|
|
606
|
+
private enableDraggable(asp: AgentSprite): void {
|
|
607
|
+
const sprite = asp.sprite
|
|
608
|
+
sprite.setInteractive({ draggable: true, useHandCursor: true })
|
|
609
|
+
sprite.on('dragstart', () => {
|
|
610
|
+
asp.dragging = true
|
|
611
|
+
// 拖动期间暂停自动行为与物理
|
|
612
|
+
;(sprite.body as Phaser.Physics.Arcade.Body).setEnable(false)
|
|
613
|
+
asp.behavior.engaged = false
|
|
614
|
+
sprite.setDepth(this.worldYMax + 500)
|
|
615
|
+
sprite.setAlpha(0.96)
|
|
616
|
+
sprite.setScale(1.12)
|
|
617
|
+
})
|
|
618
|
+
sprite.on('drag', (_p: Phaser.Input.Pointer, dragX: number, dragY: number) => {
|
|
619
|
+
sprite.setPosition(dragX, dragY)
|
|
620
|
+
asp.aura.setPosition(dragX, dragY)
|
|
621
|
+
asp.nameLabel.setPosition(dragX, dragY - 30)
|
|
622
|
+
asp.progressLabel.setPosition(dragX, dragY - 44)
|
|
623
|
+
asp.statusRing.setPosition(dragX, dragY - 18)
|
|
624
|
+
asp.sprite.setDepth(dragY)
|
|
625
|
+
asp.aura.setDepth(dragY - 10)
|
|
626
|
+
})
|
|
627
|
+
sprite.on('dragend', () => {
|
|
628
|
+
asp.dragging = false
|
|
629
|
+
sprite.setScale(1)
|
|
630
|
+
sprite.setAlpha(1)
|
|
631
|
+
;(sprite.body as Phaser.Physics.Arcade.Body).setEnable(true)
|
|
632
|
+
;(sprite.body as Phaser.Physics.Arcade.Body).stop()
|
|
633
|
+
;(sprite.body as Phaser.Physics.Arcade.Body).reset(sprite.x, sprite.y)
|
|
634
|
+
// 落点即新 home:行为结束后回归用户放置的位置
|
|
635
|
+
asp.homeX = sprite.x
|
|
636
|
+
asp.homeY = sprite.y
|
|
637
|
+
asp.behavior.mode = 'idle'
|
|
638
|
+
asp.behavior.roamTarget = null
|
|
639
|
+
asp.behavior.targetId = null
|
|
640
|
+
asp.behavior.engaged = false
|
|
641
|
+
})
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
private get worldYMax(): number { return WORLD_H }
|
|
645
|
+
|
|
646
|
+
// ================================================================
|
|
647
|
+
// 模型库 → 场景(拖拽加载 / 换装 / 落地生成)
|
|
648
|
+
// ================================================================
|
|
649
|
+
|
|
650
|
+
/** 批量注册模型库清单(由 Vue 从 useCharacterAssets 注入;幂等) */
|
|
651
|
+
registerModelsFromList(list: Array<{ id: string, file: string, name: string, spec?: ModelAnimSpec }>) {
|
|
652
|
+
for (const m of list) {
|
|
653
|
+
if (!this.modelsById.has(m.id)) {
|
|
654
|
+
this.modelsById.set(m.id, { id: m.id, file: m.file, name: m.name, spec: m.spec })
|
|
655
|
+
}
|
|
656
|
+
if (m.spec) this.animSpecs.set(m.id, m.spec)
|
|
657
|
+
// 注意:此处不得调用 ensureSheetAnims —— 场景尚未挂到 game,this.anims/this.textures 未初始化。
|
|
658
|
+
// 动画统一在 create()/createAnimations() 里建(那时 game 已挂载)。
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* 注册一个自定义模型(按 assetId 从模型库清单查 file);幂等。
|
|
664
|
+
* 记录元信息 + 帧布局,加载纹理动画。成功返回 true。
|
|
665
|
+
*/
|
|
666
|
+
registerModelFromId(id: string, file: string, name: string, spec?: ModelAnimSpec): boolean {
|
|
667
|
+
if (!this.modelsById.has(id)) this.modelsById.set(id, { id, file, name, spec })
|
|
668
|
+
if (spec) this.animSpecs.set(id, spec)
|
|
669
|
+
if (this.textures.exists(id)) {
|
|
670
|
+
this.ensureSheetAnims(id)
|
|
671
|
+
return true
|
|
672
|
+
}
|
|
673
|
+
return false
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* HTML5 拖拽落下 → 落到某个角色上则「换装」该角色,否则在落点「生成一个居民」。
|
|
678
|
+
* assetId 由 AssetLibrary 的 dragstart 写入 dataTransfer。
|
|
679
|
+
*/
|
|
680
|
+
dropModelOnWorld(worldX: number, worldY: number, assetId: string): { mode: 'rebind' | 'spawn', agentId?: string, textureKey: string, x: number, y: number } {
|
|
681
|
+
// 就近找落点 80px 内的角色
|
|
682
|
+
const near = this.nearestAgent(worldX, worldY, 80)
|
|
683
|
+
const model = this.modelsById.get(assetId)
|
|
684
|
+
const texKey = model?.id ?? assetId
|
|
685
|
+
// 若纹理未注册,先按模型库 file 加载(无则回退 assetId,可能为空 → 显示原纹理)
|
|
686
|
+
if (model && !this.textures.exists(texKey)) this.registerModelFromId(assetId, model.file, model.name)
|
|
687
|
+
if (near) {
|
|
688
|
+
// 换装:直接改该角色所用纹理 + 动画 key
|
|
689
|
+
this.swapTexture(near, texKey)
|
|
690
|
+
this.showBubble(near.channelId, near.agentId, 'info', `换装 → ${model?.name ?? assetId}`, 2200)
|
|
691
|
+
return { mode: 'rebind', agentId: near.agentId, textureKey: texKey, x: Math.round(near.sprite.x), y: Math.round(near.sprite.y) }
|
|
692
|
+
}
|
|
693
|
+
// 落点生成一个"居民"(可拖拽、可游走的装饰性角色;无实际 agent 绑定)
|
|
694
|
+
if (this.textures.exists(texKey)) {
|
|
695
|
+
this.spawnResident(worldX, worldY, texKey, model?.name ?? assetId)
|
|
696
|
+
return { mode: 'spawn', textureKey: texKey, x: Math.round(worldX), y: Math.round(worldY) }
|
|
697
|
+
}
|
|
698
|
+
return { mode: 'rebind', textureKey: texKey, x: Math.round(worldX), y: Math.round(worldY) }
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/** 就近角色(落点 80px 内) */
|
|
702
|
+
private nearestAgent(x: number, y: number, maxDist: number): AgentSprite | undefined {
|
|
703
|
+
let best: AgentSprite | undefined
|
|
704
|
+
let bestD = maxDist
|
|
705
|
+
for (const a of this.agents.values()) {
|
|
706
|
+
const d = Math.hypot(a.sprite.x - x, a.sprite.y - y)
|
|
707
|
+
if (d < bestD) {
|
|
708
|
+
best = a
|
|
709
|
+
bestD = d
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return best
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/** 换装:改纹理 key + 重播动画 + 记录 modelRef */
|
|
716
|
+
private swapTexture(asp: AgentSprite, texKey: string): void {
|
|
717
|
+
if (!this.textures.exists(texKey)) return
|
|
718
|
+
asp.sprite.setTexture(texKey)
|
|
719
|
+
asp.sprite.anims.play(`wu-bob-${texKey}`, true)
|
|
720
|
+
asp.textureKey = texKey
|
|
721
|
+
// 仅当 texKey 是自定义模型 id 时记录;内置员工模型不回写(避免覆盖引导)
|
|
722
|
+
asp.modelRef = texKey
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/** 在落点生成一个可拖拽居民(无 agent 绑定;纯装饰,游走于频道外) */
|
|
726
|
+
private spawnResident(x: number, y: number, texKey: string, name: string): void {
|
|
727
|
+
const sprite = this.physics.add.sprite(x, y, texKey, 0)
|
|
728
|
+
const sbody = sprite.body as Phaser.Physics.Arcade.Body
|
|
729
|
+
sbody.setSize(18, 18)
|
|
730
|
+
sbody.setOffset(15, 66)
|
|
731
|
+
sprite.setCollideWorldBounds(true)
|
|
732
|
+
sprite.setDepth(y)
|
|
733
|
+
sprite.anims.play(`wu-bob-${texKey}`, true)
|
|
734
|
+
const aura = this.add.image(x, y, 'wu-aura').setTint(0xffe9c4).setAlpha(0.35).setDepth(y - 10).setScale(1.2)
|
|
735
|
+
this.tweens.add({ targets: aura, alpha: { from: 0.25, to: 0.5 }, scale: { from: 1.05, to: 1.4 }, yoyo: true, repeat: -1, duration: 2400, ease: 'sine.inout' })
|
|
736
|
+
const label = this.add.text(x, y - 30, name, {
|
|
737
|
+
fontFamily: 'Geist, PingFang SC, sans-serif',
|
|
738
|
+
fontSize: '10px', fontStyle: '600', color: '#fff', padding: { x: 6, y: 2 },
|
|
739
|
+
}).setOrigin(0.5, 1).setDepth(y + 200).setStyle({ backgroundColor: 'rgba(18,20,30,0.62)' })
|
|
740
|
+
// 作为轻量"居民"注册进 agents,便于拖动/显示(标记为 decor 角色,不入 agentCount 统计语义)
|
|
741
|
+
const statusRing = this.add.graphics().setDepth(y + 200)
|
|
742
|
+
const progressLabel = this.add.text(x, y - 44, '', {
|
|
743
|
+
fontFamily: 'Geist Mono, monospace',
|
|
744
|
+
fontSize: '9px',
|
|
745
|
+
color: '#4c8f63',
|
|
746
|
+
backgroundColor: 'rgba(255,255,255,0.85)',
|
|
747
|
+
padding: { x: 4, y: 1 },
|
|
748
|
+
}).setOrigin(0.5, 1).setDepth(y + 200)
|
|
749
|
+
const decor: AgentSprite = {
|
|
750
|
+
channelId: '',
|
|
751
|
+
agentId: `resident-${Date.now().toString(36)}`,
|
|
752
|
+
name,
|
|
753
|
+
role: 'worker',
|
|
754
|
+
sprite,
|
|
755
|
+
aura,
|
|
756
|
+
statusRing,
|
|
757
|
+
nameLabel: label,
|
|
758
|
+
progressLabel,
|
|
759
|
+
bubble: null,
|
|
760
|
+
bubbleTimer: null,
|
|
761
|
+
state: 'idle',
|
|
762
|
+
progress: null,
|
|
763
|
+
dragging: false,
|
|
764
|
+
homeX: x,
|
|
765
|
+
homeY: y,
|
|
766
|
+
textureKey: texKey,
|
|
767
|
+
modelRef: texKey,
|
|
768
|
+
behavior: { mode: 'idle', roamTarget: null, targetId: null, waitUntil: 0, engaged: false },
|
|
769
|
+
}
|
|
770
|
+
this.agents.set(decor.agentId, decor)
|
|
771
|
+
this.enableDraggable(decor)
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// ================================================================
|
|
775
|
+
// 事件驱动入口(useTownBus 订阅转发)
|
|
776
|
+
// ================================================================
|
|
777
|
+
|
|
778
|
+
handleTownEvent(e: AepEnvelope): void {
|
|
779
|
+
// channel.snapshot:实体基线重建(丢弃旧领地/角色,按新快照重建)
|
|
780
|
+
if (e.type === 'channel.snapshot') {
|
|
781
|
+
this.resetAll()
|
|
782
|
+
return
|
|
783
|
+
}
|
|
784
|
+
const intent = mapEnvelopeToIntent(e)
|
|
785
|
+
if (!intent) return
|
|
786
|
+
|
|
787
|
+
// 角色状态/进度刷新
|
|
788
|
+
if (intent.agentId) {
|
|
789
|
+
const asp = this.agents.get(intent.agentId)
|
|
790
|
+
if (asp) {
|
|
791
|
+
if (e.type === 'agent.status') {
|
|
792
|
+
asp.state = (e.payload as { state: 'idle' | 'busy' | 'stopped' }).state
|
|
793
|
+
if (asp.state !== 'busy') asp.progress = null
|
|
794
|
+
}
|
|
795
|
+
if (e.type === 'task.progress') {
|
|
796
|
+
asp.progress = (e.payload as { progress: number }).progress
|
|
797
|
+
}
|
|
798
|
+
this.dirty = true
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
// 气泡
|
|
802
|
+
if (intent.bubble) this.showBubble(intent.bubble.channelId, intent.bubble.agentId, intent.bubble.kind, intent.bubble.text, intent.bubble.ttlMs)
|
|
803
|
+
|
|
804
|
+
// 事件共鸣可视化(说话脉冲 / 任务完成光柱 / 错误红涟漪 / 频道旗)
|
|
805
|
+
this.emitResonance(e)
|
|
806
|
+
|
|
807
|
+
// 行为驱动:点对点通信/任务投递 → 发送方跑去接收方身边下发;需回复则等待
|
|
808
|
+
const action = parseActionFromEnvelope(e, { resolveTaskAssignee: this.resolveTaskAssignee ?? undefined })
|
|
809
|
+
if (action) this.startBehavior(action)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* 事件 → 视觉共鸣(把目光吸过去,但不打断):
|
|
814
|
+
* - 消息类 → 说话者身上扩散一圈共鸣波纹(speaking pulse)
|
|
815
|
+
* - task.status(完成) → 频道领地冲出一道光柱 + 星星粒子
|
|
816
|
+
* - error → 该频道一圈红色涟漪 + 频道旗变红
|
|
817
|
+
* - task.status(其它/进行中) → 频道旗变黄(busy)
|
|
818
|
+
*/
|
|
819
|
+
private emitResonance(e: AepEnvelope): void {
|
|
820
|
+
const def = this.blocks.get(e.channelId)
|
|
821
|
+
const asp = e.agentId ? this.agents.get(e.agentId) : undefined
|
|
822
|
+
const cx = asp?.sprite.x ?? def?.centerX
|
|
823
|
+
const cy = asp?.sprite.y ?? def?.centerY
|
|
824
|
+
if (cx === undefined || cy === undefined) return
|
|
825
|
+
|
|
826
|
+
if (e.type === 'agent.message' || e.type === 'agent.status.message' || e.type === 'a2a.message') {
|
|
827
|
+
this.pulseRing(cx, cy, asp?.aura.tintTopLeft ?? def?.colorNum ?? 0xffffff, 0x4da3ff)
|
|
828
|
+
}
|
|
829
|
+
else if (e.type === 'error') {
|
|
830
|
+
this.pulseRing(cx, cy, 0xff6b5c, 0xff6b5c)
|
|
831
|
+
this.setFlag(e.channelId, 'danger')
|
|
832
|
+
}
|
|
833
|
+
else if (e.type === 'task.status') {
|
|
834
|
+
const state = (e.payload as { state?: string }).state
|
|
835
|
+
if (state === 'completed') {
|
|
836
|
+
this.lightColumn(cx, cy, 0xd8fff2)
|
|
837
|
+
}
|
|
838
|
+
else if (state === 'working' || state === 'assigned') {
|
|
839
|
+
this.setFlag(e.channelId, 'busy')
|
|
840
|
+
}
|
|
841
|
+
else if (state === 'failed' || state === 'canceled') {
|
|
842
|
+
this.pulseRing(cx, cy, 0xff9e6b, 0xff9e6b)
|
|
843
|
+
this.setFlag(e.channelId, 'danger')
|
|
844
|
+
}
|
|
845
|
+
else if (state === 'waiting') {
|
|
846
|
+
this.setFlag(e.channelId, 'wait')
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/** 一个扩散共鸣波纹(圆环放大并淡出) */
|
|
852
|
+
private pulseRing(x: number, y: number, color: number, _glow: number): void {
|
|
853
|
+
const ring = this.add.image(x, y, 'wu-ring').setTint(color).setAlpha(0.8).setDepth(y + 300).setScale(0.3)
|
|
854
|
+
this.tweens.add({
|
|
855
|
+
targets: ring,
|
|
856
|
+
scale: 2.2,
|
|
857
|
+
alpha: 0,
|
|
858
|
+
duration: 700,
|
|
859
|
+
ease: 'sine.out',
|
|
860
|
+
onComplete: () => ring.destroy(),
|
|
861
|
+
})
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/** 高频光柱(交付/完成时从频道领地冲天) */
|
|
865
|
+
private lightColumn(x: number, y: number, color: number): void {
|
|
866
|
+
const beam = this.add.image(x, y - 40, 'wu-aura').setTint(color).setAlpha(0.9).setDepth(y + 320).setScale(0, 6)
|
|
867
|
+
this.tweens.add({
|
|
868
|
+
targets: beam,
|
|
869
|
+
scaleX: 1.1,
|
|
870
|
+
scaleY: 2.2,
|
|
871
|
+
alpha: 0,
|
|
872
|
+
duration: 900,
|
|
873
|
+
ease: 'power2.out',
|
|
874
|
+
onComplete: () => beam.destroy(),
|
|
875
|
+
})
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/** 频道状态旗(地标塔顶变色;busy/wait/danger) */
|
|
879
|
+
private setFlag(channelId: string, state: 'busy' | 'wait' | 'danger'): void {
|
|
880
|
+
const def = this.blocks.get(channelId)
|
|
881
|
+
if (!def) return
|
|
882
|
+
const color = state === 'danger' ? 0xff6b5c : state === 'wait' ? 0xf5a742 : 0x57d29a
|
|
883
|
+
// 在塔顶光球处再放一个呼吸旗(轻量;若已存在则复用)
|
|
884
|
+
let flag = this.flagBy.get(channelId)
|
|
885
|
+
if (!flag) {
|
|
886
|
+
flag = this.add.circle(def.centerX, def.centerY - 150, 10, color, 0.9).setDepth(def.centerY - 10)
|
|
887
|
+
this.flagBy.set(channelId, flag)
|
|
888
|
+
this.blockDecor.push(flag)
|
|
889
|
+
}
|
|
890
|
+
flag.setFillStyle(color, 0.9)
|
|
891
|
+
this.tweens.add({ targets: flag, alpha: { from: 0.6, to: 1 }, yoyo: true, repeat: -1, duration: 800 })
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/** 频道旗(顶球)缓存 */
|
|
895
|
+
private flagBy = new Map<string, Phaser.GameObjects.Arc>()
|
|
896
|
+
|
|
897
|
+
// ================================================================
|
|
898
|
+
// 行为状态机(事件→决策→动作;标准 AI 控制游戏架构)
|
|
899
|
+
// ================================================================
|
|
900
|
+
|
|
901
|
+
/** 触发一次「跑去下发」行为:from 跑到 to 身边 */
|
|
902
|
+
private startBehavior(action: ActionContext): void {
|
|
903
|
+
const from = this.agents.get(action.fromId)
|
|
904
|
+
const to = this.agents.get(action.toId)
|
|
905
|
+
if (!from || !to) {
|
|
906
|
+
// 目标 agent 尚未出现在镇上(可能跨块懒装配中):忽略,等实体到达
|
|
907
|
+
return
|
|
908
|
+
}
|
|
909
|
+
// 用户正在拖动的角色不被打断
|
|
910
|
+
if (from.dragging) return
|
|
911
|
+
const b = from.behavior
|
|
912
|
+
b.mode = 'approach'
|
|
913
|
+
b.targetId = to.agentId
|
|
914
|
+
b.action = action
|
|
915
|
+
// 邀请接收方驻足配合(暂停其游走,双方才能对上话;不再互相追逐)
|
|
916
|
+
if (!to.dragging) {
|
|
917
|
+
to.behavior.engaged = true
|
|
918
|
+
to.behavior.roamTarget = null
|
|
919
|
+
;(to.sprite.body as Phaser.Physics.Arcade.Body).stop()
|
|
920
|
+
}
|
|
921
|
+
if (from.sprite.body) (from.sprite.body as Phaser.Physics.Arcade.Body).stop()
|
|
922
|
+
this.emit('behavior', {
|
|
923
|
+
agentName: from.name,
|
|
924
|
+
action: this.behaviorActionLabel(action.kind),
|
|
925
|
+
targetName: to.name,
|
|
926
|
+
})
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
private behaviorActionLabel(kind: ActionKind): string {
|
|
930
|
+
return kind === 'task' ? '下发任务' : kind === 'reply' ? '回复' : '发送消息'
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* 行为状态机逐帧执行(由 update 调用)。
|
|
935
|
+
* 状态流转:approach(跑到下发对象)→ 到达 → 送达 → wait[需回复]/returnHome[否]→ roam。
|
|
936
|
+
*/
|
|
937
|
+
private runBehavior(asp: AgentSprite, dt: number): void {
|
|
938
|
+
const b = asp.behavior
|
|
939
|
+
|
|
940
|
+
// ---------- roam:领地内来回移动(idle 时的默认行为;被邀请时驻足配合) ----------
|
|
941
|
+
if (b.mode === 'idle' || b.mode === 'roam') {
|
|
942
|
+
if (asp.behavior.engaged || asp.dragging) {
|
|
943
|
+
this.stopAt(asp)
|
|
944
|
+
return
|
|
945
|
+
}
|
|
946
|
+
b.mode = 'roam'
|
|
947
|
+
if (!b.roamTarget) {
|
|
948
|
+
const def = this.blocks.get(asp.channelId)
|
|
949
|
+
const range = def?.radius ? def.radius * 0.5 : 80
|
|
950
|
+
b.roamTarget = {
|
|
951
|
+
x: asp.homeX + (Math.random() * 2 - 1) * range,
|
|
952
|
+
y: asp.homeY + (Math.random() * 2 - 1) * range * 0.6,
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
const ok = this.driveToward(asp, b.roamTarget, AGENT_SPEED * 0.5, dt)
|
|
956
|
+
if (ok) b.roamTarget = null
|
|
957
|
+
return
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// ---------- approach:跑向下发对象 ----------
|
|
961
|
+
if (b.mode === 'approach') {
|
|
962
|
+
const target = b.targetId ? this.agents.get(b.targetId) : undefined
|
|
963
|
+
if (!target) {
|
|
964
|
+
this.stopAt(asp)
|
|
965
|
+
b.mode = 'idle'
|
|
966
|
+
return
|
|
967
|
+
}
|
|
968
|
+
const pos = { x: target.sprite.x, y: target.sprite.y }
|
|
969
|
+
const arrived = this.driveToward(asp, pos, AGENT_SPEED, dt)
|
|
970
|
+
if (arrived) {
|
|
971
|
+
this.stopAt(asp)
|
|
972
|
+
this.behaviorDeliver(asp, target)
|
|
973
|
+
if (b.action?.requireReply) {
|
|
974
|
+
b.mode = 'wait'
|
|
975
|
+
b.waitUntil = this.time.now + WAIT_MS
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
b.mode = 'returnHome'
|
|
979
|
+
this.releaseEngaged(asp)
|
|
980
|
+
b.targetId = null
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// ---------- wait:在目标附近等待回复/执行结果 ----------
|
|
987
|
+
if (b.mode === 'wait') {
|
|
988
|
+
if (asp.dragging) {
|
|
989
|
+
this.stopAt(asp)
|
|
990
|
+
return
|
|
991
|
+
}
|
|
992
|
+
const target = b.targetId ? this.agents.get(b.targetId) : undefined
|
|
993
|
+
if (target && !target.dragging) {
|
|
994
|
+
const stand = { x: target.sprite.x + 28, y: target.sprite.y + 8 }
|
|
995
|
+
this.driveToward(asp, stand, AGENT_SPEED * 0.6, dt)
|
|
996
|
+
}
|
|
997
|
+
else this.stopAt(asp)
|
|
998
|
+
if (this.time.now >= b.waitUntil) {
|
|
999
|
+
b.mode = 'returnHome'
|
|
1000
|
+
this.releaseEngaged(asp)
|
|
1001
|
+
}
|
|
1002
|
+
return
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// ---------- returnHome:事毕回归出生位(用户拖动后为落点) ----------
|
|
1006
|
+
if (b.mode === 'returnHome') {
|
|
1007
|
+
if (asp.dragging) {
|
|
1008
|
+
this.stopAt(asp)
|
|
1009
|
+
return
|
|
1010
|
+
}
|
|
1011
|
+
const arrived = this.driveToward(asp, { x: asp.homeX, y: asp.homeY }, AGENT_SPEED * 0.7, dt)
|
|
1012
|
+
if (arrived) {
|
|
1013
|
+
this.stopAt(asp)
|
|
1014
|
+
asp.sprite.setPosition(asp.homeX, asp.homeY)
|
|
1015
|
+
b.mode = 'idle'
|
|
1016
|
+
b.targetId = null
|
|
1017
|
+
b.action = undefined
|
|
1018
|
+
}
|
|
1019
|
+
return
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/** 释放被本角色邀请(engaged)的目标:让其恢复游走 */
|
|
1024
|
+
private releaseEngaged(asp: AgentSprite): void {
|
|
1025
|
+
const target = asp.behavior.targetId ? this.agents.get(asp.behavior.targetId) : undefined
|
|
1026
|
+
if (target) target.behavior.engaged = false
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/** 送达:在目标头上弹气泡(下发/通信内容) + 广播行为日志 */
|
|
1030
|
+
private behaviorDeliver(asp: AgentSprite, target: AgentSprite): void {
|
|
1031
|
+
const text = asp.behavior.action?.text ?? ''
|
|
1032
|
+
if (text) {
|
|
1033
|
+
this.showBubble(asp.channelId, target.agentId, 'info', text, 2600)
|
|
1034
|
+
}
|
|
1035
|
+
this.emit('behavior', {
|
|
1036
|
+
agentName: asp.name,
|
|
1037
|
+
action: this.behaviorActionLabel(asp.behavior.action?.kind ?? 'message'),
|
|
1038
|
+
targetName: target.name,
|
|
1039
|
+
})
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* 驱动角色朝目标移动一步(stepToward 计算方向 → velocity)。
|
|
1044
|
+
* 返回是否已到达。速度随距离减速(近目标放缓,站位更自然)。
|
|
1045
|
+
*/
|
|
1046
|
+
private driveToward(asp: AgentSprite, target: { x: number, y: number }, speed: number, dt: number): boolean {
|
|
1047
|
+
const body = asp.sprite.body as Phaser.Physics.Arcade.Body
|
|
1048
|
+
if (!body.enable) return false
|
|
1049
|
+
const cur = { x: asp.sprite.x, y: asp.sprite.y }
|
|
1050
|
+
const next = stepToward(cur, target, speed, dt)
|
|
1051
|
+
asp.sprite.setFlipX(next.dir === 'left')
|
|
1052
|
+
body.setVelocity((next.x - cur.x) / dt, (next.y - cur.y) / dt)
|
|
1053
|
+
// 行走态(与静止 bob 区分)
|
|
1054
|
+
this.playSheetAnim(asp.sprite, 'walk')
|
|
1055
|
+
return next.arrived
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/** 停止角色运动(velocity=0) */
|
|
1059
|
+
private stopAt(asp: AgentSprite): void {
|
|
1060
|
+
const body = asp.sprite.body as Phaser.Physics.Arcade.Body | null
|
|
1061
|
+
body?.stop()
|
|
1062
|
+
this.playSheetAnim(asp.sprite, 'idle')
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/** 统一播放某纹理的 idle/walk/work 动画;未注册时回退悬停 bob */
|
|
1066
|
+
private playSheetAnim(sprite: Phaser.GameObjects.Sprite, state: 'idle' | 'walk' | 'work'): void {
|
|
1067
|
+
const key = `${sprite.texture.key}-${state}`
|
|
1068
|
+
if (this.anims.exists(key)) {
|
|
1069
|
+
if (sprite.anims.currentAnim?.key !== key) sprite.anims.play(key, true)
|
|
1070
|
+
}
|
|
1071
|
+
else {
|
|
1072
|
+
const bobKey = `wu-bob-${sprite.texture.key}`
|
|
1073
|
+
if (this.anims.exists(bobKey) && sprite.anims.currentAnim?.key !== bobKey) sprite.anims.play(bobKey, true)
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// ================================================================
|
|
1078
|
+
// 头顶气泡(每 agent 至多一个;SLACK 白卡样式)
|
|
1079
|
+
// ================================================================
|
|
1080
|
+
|
|
1081
|
+
private showBubble(channelId: string, agentId: string | undefined, kind: TownBubbleKind, text: string, ttlMs: number): void {
|
|
1082
|
+
const asp = agentId ? this.agents.get(agentId) : undefined
|
|
1083
|
+
const anchor = asp?.sprite
|
|
1084
|
+
// 无具体 agent 的气泡:挂到频道领地上方
|
|
1085
|
+
const def = this.blocks.get(channelId)
|
|
1086
|
+
const ax = anchor?.x ?? def?.centerX ?? 400
|
|
1087
|
+
const ay = (anchor?.y ?? def?.centerY ?? 300) - 34
|
|
1088
|
+
const style = BUBBLE_STYLE[kind] ?? BUBBLE_STYLE.info
|
|
1089
|
+
|
|
1090
|
+
const destroyPrev = (): void => {
|
|
1091
|
+
if (asp?.bubble) {
|
|
1092
|
+
asp.bubble.destroy()
|
|
1093
|
+
asp.bubble = null
|
|
1094
|
+
if (asp.bubbleTimer) {
|
|
1095
|
+
asp.bubbleTimer.remove(false)
|
|
1096
|
+
asp.bubbleTimer = null
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
destroyPrev()
|
|
1101
|
+
|
|
1102
|
+
const bg = this.add.rectangle(0, 0, 12, 12, style.bg, 0.94)
|
|
1103
|
+
const label = this.add.text(0, 0, text, {
|
|
1104
|
+
fontFamily: 'Geist, PingFang SC, sans-serif',
|
|
1105
|
+
fontSize: '11px',
|
|
1106
|
+
color: style.fg,
|
|
1107
|
+
padding: { x: 9, y: 5 },
|
|
1108
|
+
wordWrap: { width: 220 },
|
|
1109
|
+
align: 'left',
|
|
1110
|
+
})
|
|
1111
|
+
const w = Math.min(240, label.width + 18)
|
|
1112
|
+
const h = label.height + 10
|
|
1113
|
+
bg.setSize(w, h)
|
|
1114
|
+
const container = this.add.container(ax, ay, [bg, label])
|
|
1115
|
+
container.setDepth(500)
|
|
1116
|
+
container.setAlpha(0)
|
|
1117
|
+
container.setScale(0.92)
|
|
1118
|
+
this.tweens.add({ targets: container, alpha: 1, y: ay - 6, scale: 1, duration: 180, ease: 'back.out' })
|
|
1119
|
+
|
|
1120
|
+
this.dbgBubbles.push({ text, at: Date.now() })
|
|
1121
|
+
this.dbgActivity = { channelId, agentName: asp?.name ?? def?.name ?? '系统', text }
|
|
1122
|
+
this.recentActivity.push({ channelId, agentName: asp?.name ?? def?.name ?? '系统', text })
|
|
1123
|
+
if (this.recentActivity.length > 6) this.recentActivity.splice(0, this.recentActivity.length - 6)
|
|
1124
|
+
this.emit('lastActivity', this.dbgActivity)
|
|
1125
|
+
|
|
1126
|
+
if (asp) {
|
|
1127
|
+
asp.bubble = container
|
|
1128
|
+
asp.bubbleTimer = this.time.delayedCall(ttlMs, () => {
|
|
1129
|
+
this.tweens.add({
|
|
1130
|
+
targets: container,
|
|
1131
|
+
alpha: 0,
|
|
1132
|
+
y: container.y - 8,
|
|
1133
|
+
duration: 220,
|
|
1134
|
+
onComplete: () => {
|
|
1135
|
+
container.destroy()
|
|
1136
|
+
if (asp) asp.bubble = null
|
|
1137
|
+
},
|
|
1138
|
+
})
|
|
1139
|
+
if (asp) asp.bubbleTimer = null
|
|
1140
|
+
})
|
|
1141
|
+
}
|
|
1142
|
+
else {
|
|
1143
|
+
this.time.delayedCall(ttlMs, () => {
|
|
1144
|
+
this.tweens.add({ targets: container, alpha: 0, y: container.y - 8, duration: 220, onComplete: () => container.destroy() })
|
|
1145
|
+
})
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/** channel.snapshot 后重建(清空旧树,由 Vue 重新 seedEntities + buildBlocks) */
|
|
1150
|
+
resetAll(): void {
|
|
1151
|
+
for (const decor of this.blockDecor) decor.destroy()
|
|
1152
|
+
this.blockDecor = []
|
|
1153
|
+
for (const asp of this.agents.values()) {
|
|
1154
|
+
asp.sprite.destroy()
|
|
1155
|
+
asp.aura.destroy()
|
|
1156
|
+
asp.statusRing.destroy()
|
|
1157
|
+
asp.nameLabel.destroy()
|
|
1158
|
+
asp.progressLabel.destroy()
|
|
1159
|
+
if (asp.bubble) asp.bubble.destroy()
|
|
1160
|
+
}
|
|
1161
|
+
this.agents.clear()
|
|
1162
|
+
this.blocks.clear()
|
|
1163
|
+
this.flagBy.clear()
|
|
1164
|
+
// 通知 Vue 重建(快照携带完整实体)
|
|
1165
|
+
this.events.emit('town-reset')
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// ================================================================
|
|
1169
|
+
// 调试/E2E 钩子
|
|
1170
|
+
// ================================================================
|
|
1171
|
+
|
|
1172
|
+
getDebugState(): {
|
|
1173
|
+
blocks: number
|
|
1174
|
+
agents: Array<{ agentId: string, name: string, role: string, channelId: string, state: string, progress: number | null, x: number, y: number, visible: boolean, draggable: boolean, auraColor: number, behavior: string, targetId: string | null, homeX: number, homeY: number, textureKey: string, modelRef: string, decorated: boolean }>
|
|
1175
|
+
bubbles: Array<{ text: string, at: number }>
|
|
1176
|
+
activity: { channelId: string, agentName: string, text: string } | null
|
|
1177
|
+
player: { x: number, y: number }
|
|
1178
|
+
} {
|
|
1179
|
+
return {
|
|
1180
|
+
blocks: this.blocks.size,
|
|
1181
|
+
agents: [...this.agents.values()].map(a => ({
|
|
1182
|
+
agentId: a.agentId,
|
|
1183
|
+
name: a.name,
|
|
1184
|
+
role: a.role,
|
|
1185
|
+
channelId: a.channelId,
|
|
1186
|
+
state: a.state,
|
|
1187
|
+
progress: a.progress,
|
|
1188
|
+
x: Math.round(a.sprite.x),
|
|
1189
|
+
y: Math.round(a.sprite.y),
|
|
1190
|
+
visible: a.sprite.visible,
|
|
1191
|
+
draggable: a.sprite.input?.enabled ?? false,
|
|
1192
|
+
auraColor: a.aura.tintTopLeft,
|
|
1193
|
+
behavior: a.behavior.mode,
|
|
1194
|
+
targetId: a.behavior.targetId,
|
|
1195
|
+
homeX: a.homeX,
|
|
1196
|
+
homeY: a.homeY,
|
|
1197
|
+
textureKey: a.textureKey,
|
|
1198
|
+
modelRef: a.modelRef,
|
|
1199
|
+
decorated: !a.channelId,
|
|
1200
|
+
})),
|
|
1201
|
+
bubbles: this.dbgBubbles.slice(-8),
|
|
1202
|
+
activity: this.dbgActivity,
|
|
1203
|
+
player: { x: Math.round(this.viewer.x), y: Math.round(this.viewer.y) },
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
/** 迷你地图数据:归一化(0~1)的领地/角色/玩家坐标 + 色相,供 Vue HUD 渲染缩略图 */
|
|
1208
|
+
getMinimapState(): {
|
|
1209
|
+
world: { w: number, h: number }
|
|
1210
|
+
blocks: Array<{ x: number, y: number, color: number, name: string }>
|
|
1211
|
+
agents: Array<{ x: number, y: number, color: number, busy: boolean }>
|
|
1212
|
+
devices: Array<{ x: number, y: number, color: number, state: string }>
|
|
1213
|
+
player: { x: number, y: number }
|
|
1214
|
+
} {
|
|
1215
|
+
const nx = (x: number) => x / WORLD_W
|
|
1216
|
+
const ny = (y: number) => y / WORLD_H
|
|
1217
|
+
return {
|
|
1218
|
+
world: { w: WORLD_W, h: WORLD_H },
|
|
1219
|
+
blocks: [...this.blocks.values()].map(b => ({ x: nx(b.centerX), y: ny(b.centerY), color: b.colorNum, name: b.name })),
|
|
1220
|
+
agents: [...this.agents.values()].map(a => ({ x: nx(a.sprite.x), y: ny(a.sprite.y), color: a.aura.tintTopLeft, busy: a.state === 'busy' })),
|
|
1221
|
+
devices: [],
|
|
1222
|
+
player: { x: nx(this.viewer.x), y: ny(this.viewer.y) },
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
/** HUD 跑马灯数据:最近事件队列(上限 6) */
|
|
1227
|
+
getRecentActivity(): Array<{ channelId: string, agentName: string, text: string }> {
|
|
1228
|
+
return [...this.recentActivity]
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
private createAnimations(): void {
|
|
1232
|
+
const sheets = [...WORKER_SHEETS, LEAD_SHEET, 'knight', 'mage', 'bot']
|
|
1233
|
+
for (const sheet of sheets) {
|
|
1234
|
+
this.ensureSheetAnims(sheet)
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/** 为某纹理 key 建 idle/walk/work 三态动画(幂等);内置与自定义模型统一接口 */
|
|
1239
|
+
private ensureSheetAnims(key: string): void {
|
|
1240
|
+
const def = resolveAnimDef(key, this.animSpecs.get(key))
|
|
1241
|
+
for (const state of ['idle', 'walk', 'work'] as const) {
|
|
1242
|
+
const a = def[state]
|
|
1243
|
+
const animKey = a.key
|
|
1244
|
+
if (!this.anims.exists(animKey)) {
|
|
1245
|
+
this.anims.create({
|
|
1246
|
+
key: animKey,
|
|
1247
|
+
frames: this.anims.generateFrameNumbers(key, { start: a.start, end: a.end }),
|
|
1248
|
+
frameRate: a.frameRate,
|
|
1249
|
+
repeat: a.repeat,
|
|
1250
|
+
})
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
// 兼容旧播放名 wu-bob-<key>(仍能播悬停)
|
|
1254
|
+
if (!this.anims.exists(`wu-bob-${key}`)) {
|
|
1255
|
+
this.anims.create({
|
|
1256
|
+
key: `wu-bob-${key}`,
|
|
1257
|
+
frames: this.anims.generateFrameNumbers(key, { start: 0, end: (this.animSpecs.get(key)?.frames ?? 4) - 1 }),
|
|
1258
|
+
frameRate: 3,
|
|
1259
|
+
repeat: -1,
|
|
1260
|
+
})
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/** 已声明帧布局的动画规格(自定义模型经 registerModelFromId 注入) */
|
|
1265
|
+
private animSpecs = new Map<string, ModelAnimSpec>()
|
|
1266
|
+
|
|
1267
|
+
override update(_t: number, delta: number): void {
|
|
1268
|
+
// 玩家移动(漫游)
|
|
1269
|
+
let dx = 0
|
|
1270
|
+
let dy = 0
|
|
1271
|
+
if (this.cursors.left.isDown || this.keyA.isDown) dx -= 1
|
|
1272
|
+
if (this.cursors.right.isDown || this.keyD.isDown) dx += 1
|
|
1273
|
+
if (this.cursors.up.isDown || this.keyW.isDown) dy -= 1
|
|
1274
|
+
if (this.cursors.down.isDown || this.keyS.isDown) dy += 1
|
|
1275
|
+
if (dx !== 0 || dy !== 0) {
|
|
1276
|
+
const len = Math.hypot(dx, dy)
|
|
1277
|
+
this.viewer.setVelocity((dx / len) * WALK_SPEED, (dy / len) * WALK_SPEED)
|
|
1278
|
+
const next: typeof this.facing = dx < 0
|
|
1279
|
+
? 'left'
|
|
1280
|
+
: dx > 0
|
|
1281
|
+
? 'right'
|
|
1282
|
+
: dy < 0
|
|
1283
|
+
? 'up'
|
|
1284
|
+
: 'down'
|
|
1285
|
+
if (next !== this.facing) {
|
|
1286
|
+
this.facing = next
|
|
1287
|
+
this.viewer.setFlipX(next === 'left')
|
|
1288
|
+
this.viewer.anims.play(`wu-bob-${LEAD_SHEET}`, true)
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
else {
|
|
1292
|
+
this.viewer.setVelocity(0, 0)
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// 深度排序 + 头顶标签跟随 + 行为状态机 + 视差
|
|
1296
|
+
this.updateParallax()
|
|
1297
|
+
this.viewer.setDepth(this.viewer.y)
|
|
1298
|
+
this.playerAura.setPosition(this.viewer.x, this.viewer.y)
|
|
1299
|
+
this.playerAura.setDepth(this.viewer.y - 10)
|
|
1300
|
+
for (const asp of this.agents.values()) {
|
|
1301
|
+
// 防御:snapshot 重建中 sprite 可能正被销毁(ensureAgentSprite/rebuild 竞态),跳过不崩溃
|
|
1302
|
+
if (!asp || !asp.sprite || !asp.sprite.body) continue
|
|
1303
|
+
// 行为驱动:stopped 成员不游走/不下发(保持静止);拖动中暂停;其余按 FSM 运动
|
|
1304
|
+
if (asp.state !== 'stopped' && !asp.dragging) {
|
|
1305
|
+
this.runBehavior(asp, delta / 1000)
|
|
1306
|
+
}
|
|
1307
|
+
else {
|
|
1308
|
+
this.stopAt(asp)
|
|
1309
|
+
}
|
|
1310
|
+
asp.sprite.setDepth(asp.sprite.y)
|
|
1311
|
+
asp.aura.setPosition(asp.sprite.x, asp.sprite.y)
|
|
1312
|
+
asp.aura.setDepth(asp.sprite.y - 10)
|
|
1313
|
+
asp.nameLabel.setPosition(asp.sprite.x, asp.sprite.y - 30)
|
|
1314
|
+
asp.nameLabel.setDepth(asp.sprite.y + 200)
|
|
1315
|
+
asp.progressLabel.setPosition(asp.sprite.x, asp.sprite.y - 44)
|
|
1316
|
+
asp.progressLabel.setDepth(asp.sprite.y + 200)
|
|
1317
|
+
// 状态环
|
|
1318
|
+
this.drawStatusRing(asp)
|
|
1319
|
+
// 进度标签
|
|
1320
|
+
if (asp.state === 'busy' && asp.progress != null) {
|
|
1321
|
+
asp.progressLabel.setText(`${asp.progress}%`)
|
|
1322
|
+
asp.progressLabel.setColor('#4c8f63')
|
|
1323
|
+
asp.progressLabel.setVisible(true)
|
|
1324
|
+
}
|
|
1325
|
+
else {
|
|
1326
|
+
asp.progressLabel.setVisible(false)
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// FPS
|
|
1331
|
+
this.frameCount += 1
|
|
1332
|
+
this.fpsAccum += delta
|
|
1333
|
+
if (this.fpsAccum >= 1000) {
|
|
1334
|
+
this.emit('fps', this.frameCount)
|
|
1335
|
+
this.frameCount = 0
|
|
1336
|
+
this.fpsAccum = 0
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
private drawStatusRing(asp: AgentSprite): void {
|
|
1341
|
+
const g = asp.statusRing
|
|
1342
|
+
g.clear()
|
|
1343
|
+
// 状态点放在名字牌左侧边缘(不遮挡):y 轴与 nameLabel 底对齐
|
|
1344
|
+
const labelW = asp.nameLabel.width
|
|
1345
|
+
const x = asp.sprite.x - labelW / 2 - 5
|
|
1346
|
+
const y = asp.sprite.y - 20
|
|
1347
|
+
const color = asp.state === 'busy' ? 0xefb56a : asp.state === 'stopped' ? 0xc25a4e : 0x9ecb7a
|
|
1348
|
+
g.fillStyle(color, 1)
|
|
1349
|
+
g.fillCircle(x, y, 3)
|
|
1350
|
+
if (asp.state === 'busy') {
|
|
1351
|
+
g.lineStyle(1.5, 0xefb56a, 0.6)
|
|
1352
|
+
g.strokeCircle(x, y, 6)
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|