@agent-os-lab/agent-game-sdk 0.1.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/README.md +99 -0
- package/package.json +38 -0
- package/src/core/agent-game-store.ts +110 -0
- package/src/core/agent-service-event-adapter.ts +20 -0
- package/src/core/assets.ts +119 -0
- package/src/core/commands.ts +42 -0
- package/src/core/errors.ts +19 -0
- package/src/core/event-adapter.ts +40 -0
- package/src/core/index.ts +23 -0
- package/src/core/life-presets.ts +54 -0
- package/src/core/movement.ts +50 -0
- package/src/core/office-building-layout.ts +376 -0
- package/src/core/office-layout.ts +152 -0
- package/src/core/pixel-character-avatar.ts +87 -0
- package/src/core/pixel-character.ts +684 -0
- package/src/core/realtime-events.ts +44 -0
- package/src/core/realtime-transport.ts +39 -0
- package/src/core/reducer.ts +105 -0
- package/src/core/scene.ts +144 -0
- package/src/core/schedule.ts +20 -0
- package/src/core/sequence.ts +48 -0
- package/src/core/state.ts +26 -0
- package/src/core/svg-pixel-avatar.ts +372 -0
- package/src/core/town-office-assets.ts +109 -0
- package/src/core/town-office-room-presets.ts +455 -0
- package/src/core/town-office-seat-layout.ts +238 -0
- package/src/graph.ts +112 -0
- package/src/index.ts +2 -0
- package/src/office/core/projection.ts +89 -0
- package/src/office/core/source.ts +46 -0
- package/src/office/core/types.ts +110 -0
- package/src/office/index.ts +4 -0
- package/src/office/mount.ts +104 -0
- package/src/office/react/AgentGameOfficeView.ts +58 -0
- package/src/office/react/index.ts +1 -0
- package/src/office/renderers/three/agent-activity-effects.ts +161 -0
- package/src/office/renderers/three/agent-animation.ts +205 -0
- package/src/office/renderers/three/agent-body-instancing.ts +119 -0
- package/src/office/renderers/three/agent-label.ts +82 -0
- package/src/office/renderers/three/agent-layout.ts +72 -0
- package/src/office/renderers/three/agent-mesh.ts +145 -0
- package/src/office/renderers/three/mount.ts +253 -0
- package/src/office/renderers/three/scene.ts +790 -0
- package/src/phaser/agent-game-scene.ts +87 -0
- package/src/phaser/anchor-debug.ts +22 -0
- package/src/phaser/avatar-registry.ts +46 -0
- package/src/phaser/camera-controls.ts +419 -0
- package/src/phaser/camera-model.ts +81 -0
- package/src/phaser/create-agent-game.ts +242 -0
- package/src/phaser/debug-overlay.ts +21 -0
- package/src/phaser/index.ts +13 -0
- package/src/phaser/movement-tween.ts +59 -0
- package/src/phaser/office-background.ts +48 -0
- package/src/phaser/office-building-renderer.ts +87 -0
- package/src/phaser/office-layout-renderer.ts +58 -0
- package/src/phaser/render-layers.ts +30 -0
- package/src/phaser/scene-reconciler.ts +614 -0
- package/src/phaser/scene-renderer.ts +138 -0
- package/src/phaser/text-style.ts +8 -0
- package/src/phaser/town-office-business-props.ts +256 -0
- package/src/phaser/town-office-environment.ts +89 -0
- package/src/phaser/town-office-furniture.ts +182 -0
- package/src/phaser/town-office-primitives.ts +53 -0
- package/src/phaser/town-office-renderer.ts +429 -0
- package/src/phaser/types.ts +67 -0
- package/src/phaser/viewport.ts +88 -0
- package/src/runtime-client.ts +435 -0
- package/src/types.ts +80 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { parseHexColor } from "../core/index.js";
|
|
2
|
+
import { officeDepth } from "./render-layers.js";
|
|
3
|
+
import type { PhaserDisplayObjectLike, PhaserTextLike } from "./scene-reconciler.js";
|
|
4
|
+
import { crispTextStyle } from "./text-style.js";
|
|
5
|
+
|
|
6
|
+
export type TownOfficeSceneLike = {
|
|
7
|
+
add: {
|
|
8
|
+
rectangle: (x: number, y: number, width: number, height: number, fillColor: number) => PhaserDisplayObjectLike;
|
|
9
|
+
text: (x: number, y: number, text: string, style?: Record<string, unknown>) => PhaserTextLike;
|
|
10
|
+
};
|
|
11
|
+
cameras?: {
|
|
12
|
+
main?: {
|
|
13
|
+
zoom: number;
|
|
14
|
+
scrollX: number;
|
|
15
|
+
scrollY: number;
|
|
16
|
+
width?: number;
|
|
17
|
+
height?: number;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
events?: {
|
|
21
|
+
on: (eventName: string, handler: (...args: any[]) => void) => unknown;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function rect(
|
|
26
|
+
scene: TownOfficeSceneLike,
|
|
27
|
+
x: number,
|
|
28
|
+
y: number,
|
|
29
|
+
width: number,
|
|
30
|
+
height: number,
|
|
31
|
+
color: string,
|
|
32
|
+
depth: number,
|
|
33
|
+
): PhaserDisplayObjectLike {
|
|
34
|
+
return scene.add.rectangle(x, y, width, height, parseHexColor(color)).setDepth?.(officeDepth(depth))
|
|
35
|
+
?? scene.add.rectangle(x, y, width, height, parseHexColor(color));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function text(
|
|
39
|
+
scene: TownOfficeSceneLike,
|
|
40
|
+
x: number,
|
|
41
|
+
y: number,
|
|
42
|
+
value: string,
|
|
43
|
+
fontSize: number,
|
|
44
|
+
color: string,
|
|
45
|
+
depth: number,
|
|
46
|
+
): void {
|
|
47
|
+
scene.add.text(x, y, value, crispTextStyle({
|
|
48
|
+
color,
|
|
49
|
+
fontSize: `${Math.max(1, fontSize)}px`,
|
|
50
|
+
fontFamily: "sans-serif",
|
|
51
|
+
fontStyle: "700",
|
|
52
|
+
})).setOrigin?.(0.5, 0.5).setDepth?.(officeDepth(depth));
|
|
53
|
+
}
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTownOfficeRoomObjects,
|
|
3
|
+
getOfficeRoomBounds,
|
|
4
|
+
measureOfficeBuildingLayoutSize,
|
|
5
|
+
normalizeOfficeBuildingLayoutForRatio,
|
|
6
|
+
type OfficeBuildingLayout,
|
|
7
|
+
type OfficeBuildingObjectDefinition,
|
|
8
|
+
type OfficeRoomBounds,
|
|
9
|
+
} from "../core/index.js";
|
|
10
|
+
import {
|
|
11
|
+
drawBattery,
|
|
12
|
+
drawBeaker,
|
|
13
|
+
drawBicycleRack,
|
|
14
|
+
drawBigScreen,
|
|
15
|
+
drawCertBadge,
|
|
16
|
+
drawChartBoard,
|
|
17
|
+
drawEVCharger,
|
|
18
|
+
drawGazebo,
|
|
19
|
+
drawHenanMap,
|
|
20
|
+
drawKpiCard,
|
|
21
|
+
drawMegaphone,
|
|
22
|
+
drawMonitorWall,
|
|
23
|
+
drawPartyFlag,
|
|
24
|
+
drawPingPongTable,
|
|
25
|
+
drawPodium,
|
|
26
|
+
drawRedBanner,
|
|
27
|
+
drawSeal,
|
|
28
|
+
drawServerRack,
|
|
29
|
+
drawSolarPanel,
|
|
30
|
+
drawTestRack,
|
|
31
|
+
drawTickerBoard,
|
|
32
|
+
drawTransmissionTower,
|
|
33
|
+
drawTree,
|
|
34
|
+
drawTV,
|
|
35
|
+
drawWindTurbine,
|
|
36
|
+
} from "./town-office-business-props.js";
|
|
37
|
+
import { drawWorldBoundsDebugOverlay, type AgentGameDebugOverlays } from "./debug-overlay.js";
|
|
38
|
+
import {
|
|
39
|
+
drawAirConditioner,
|
|
40
|
+
drawDoor,
|
|
41
|
+
drawExitSign,
|
|
42
|
+
drawLightSwitch,
|
|
43
|
+
drawPicture,
|
|
44
|
+
drawPrinter,
|
|
45
|
+
drawRoomEnvironment,
|
|
46
|
+
drawSocket,
|
|
47
|
+
drawWindow,
|
|
48
|
+
type TownOfficeRoomStyle,
|
|
49
|
+
} from "./town-office-environment.js";
|
|
50
|
+
import {
|
|
51
|
+
drawBench,
|
|
52
|
+
drawBookshelf,
|
|
53
|
+
drawBulletinBoard,
|
|
54
|
+
drawCabinet,
|
|
55
|
+
drawChair,
|
|
56
|
+
drawClock,
|
|
57
|
+
drawCoffeeMachine,
|
|
58
|
+
drawDesk,
|
|
59
|
+
drawFireExtinguisher,
|
|
60
|
+
drawFlowerPot,
|
|
61
|
+
drawLaptop,
|
|
62
|
+
drawMachine,
|
|
63
|
+
drawMonitor,
|
|
64
|
+
drawPhone,
|
|
65
|
+
drawPlant,
|
|
66
|
+
drawRugMat,
|
|
67
|
+
drawScheduleBoard,
|
|
68
|
+
drawSofa,
|
|
69
|
+
drawTable,
|
|
70
|
+
drawTrashCan,
|
|
71
|
+
drawVendingMachine,
|
|
72
|
+
drawWhiteboard,
|
|
73
|
+
} from "./town-office-furniture.js";
|
|
74
|
+
import { rect, type TownOfficeSceneLike } from "./town-office-primitives.js";
|
|
75
|
+
import { crispTextStyle } from "./text-style.js";
|
|
76
|
+
import { officeDepth } from "./render-layers.js";
|
|
77
|
+
|
|
78
|
+
export type { TownOfficeSceneLike } from "./town-office-primitives.js";
|
|
79
|
+
|
|
80
|
+
const HEADER_HEIGHT = 34;
|
|
81
|
+
const HEADER_TITLE_PADDING_X = 12;
|
|
82
|
+
const HEADER_TITLE_OFFSET_Y = 2;
|
|
83
|
+
const FLOOR_GRID_LINE_WIDTH = 2;
|
|
84
|
+
|
|
85
|
+
const ROOM_STYLES: Record<string, TownOfficeRoomStyle> = {
|
|
86
|
+
dispatch: { floor: "#e8edf4", wallTop: "#1a365d", accent: "#0d2340" },
|
|
87
|
+
trading: { floor: "#e8edf4", wallTop: "#1a365d", accent: "#0d2340" },
|
|
88
|
+
testing: { floor: "#e4ebf0", wallTop: "#1e3a5f", accent: "#0f2847" },
|
|
89
|
+
lab: { floor: "#e4ebf0", wallTop: "#1e3a5f", accent: "#0f2847" },
|
|
90
|
+
resource: { floor: "#e4f0ea", wallTop: "#1a6b4a", accent: "#0e5538" },
|
|
91
|
+
archive: { floor: "#eae6de", wallTop: "#3a3028", accent: "#2a2018" },
|
|
92
|
+
library: { floor: "#eae6de", wallTop: "#3a3028", accent: "#2a2018" },
|
|
93
|
+
cafeteria: { floor: "#f0ece4", wallTop: "#8b4513", accent: "#6b3008" },
|
|
94
|
+
lounge: { floor: "#eeebe4", wallTop: "#4a3828", accent: "#3a2818" },
|
|
95
|
+
fitness: { floor: "#e4ede8", wallTop: "#1a5a3a", accent: "#0e4a2a" },
|
|
96
|
+
meeting: { floor: "#ebe8f0", wallTop: "#2d3a5c", accent: "#1a2845" },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type TownOfficeBuildingRenderOptions = {
|
|
100
|
+
debugOverlays?: AgentGameDebugOverlays;
|
|
101
|
+
domOverlayRoot?: TownOfficeDomOverlayRoot;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export type TownOfficeDomOverlayElement = {
|
|
105
|
+
className: string;
|
|
106
|
+
textContent: string | null;
|
|
107
|
+
clientWidth?: number;
|
|
108
|
+
clientHeight?: number;
|
|
109
|
+
style: Record<string, string>;
|
|
110
|
+
appendChild?: (child: TownOfficeDomOverlayElement) => unknown;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export type TownOfficeDomOverlayRoot = TownOfficeDomOverlayElement & {
|
|
114
|
+
ownerDocument?: {
|
|
115
|
+
createElement: (tagName: string) => TownOfficeDomOverlayElement;
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export function drawTownOfficeBuildingLayout(
|
|
120
|
+
scene: TownOfficeSceneLike,
|
|
121
|
+
layout: OfficeBuildingLayout,
|
|
122
|
+
options: TownOfficeBuildingRenderOptions = {},
|
|
123
|
+
): void {
|
|
124
|
+
const normalizedLayout = normalizeOfficeBuildingLayoutForRatio(layout);
|
|
125
|
+
const size = getBuildingCanvasSize(normalizedLayout);
|
|
126
|
+
rect(scene, size.width / 2, size.height / 2, size.width, size.height, "#e8ecf0", 0);
|
|
127
|
+
if (options.debugOverlays?.worldBounds) {
|
|
128
|
+
drawWorldBoundsDebugOverlay(scene, size.width, size.height);
|
|
129
|
+
}
|
|
130
|
+
drawBackgroundPattern(scene, size.width, size.height);
|
|
131
|
+
|
|
132
|
+
for (const room of normalizedLayout.rooms) {
|
|
133
|
+
const bounds = getOfficeRoomBounds(normalizedLayout, room);
|
|
134
|
+
const roomStyleKey = room.type ?? room.id;
|
|
135
|
+
const style = ROOM_STYLES[roomStyleKey] ?? ROOM_STYLES[room.id] ?? {
|
|
136
|
+
floor: room.theme.floorColor,
|
|
137
|
+
wallTop: room.theme.titleColor,
|
|
138
|
+
accent: room.theme.borderColor,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
rect(scene, bounds.x + bounds.width / 2, bounds.y + bounds.height / 2, bounds.width, bounds.height, style.floor, 1)
|
|
142
|
+
.setStrokeStyle?.(1, 0x000000);
|
|
143
|
+
drawFloorPattern(scene, bounds, roomStyleKey);
|
|
144
|
+
rect(scene, bounds.x + bounds.width / 2, bounds.y + HEADER_HEIGHT / 2, bounds.width, HEADER_HEIGHT, style.wallTop, 2);
|
|
145
|
+
rect(scene, bounds.x + bounds.width / 2, bounds.y + HEADER_HEIGHT + 1, bounds.width, 2, style.accent, 2);
|
|
146
|
+
const titleX = bounds.x + HEADER_TITLE_PADDING_X;
|
|
147
|
+
const titleY = bounds.y + HEADER_HEIGHT / 2 + HEADER_TITLE_OFFSET_Y;
|
|
148
|
+
if (options.domOverlayRoot) {
|
|
149
|
+
createRoomTitleOverlay(scene, options.domOverlayRoot, titleX, titleY, room.name);
|
|
150
|
+
} else {
|
|
151
|
+
scene.add
|
|
152
|
+
.text(titleX, titleY, room.name, crispTextStyle({
|
|
153
|
+
color: room.theme.titleTextColor ?? "#ffffff",
|
|
154
|
+
fontSize: "14px",
|
|
155
|
+
fontFamily: "sans-serif",
|
|
156
|
+
fontStyle: "700",
|
|
157
|
+
}))
|
|
158
|
+
.setOrigin?.(0, 0.5)
|
|
159
|
+
.setDepth?.(officeDepth(3));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
drawRoomEnvironment(scene, bounds, roomStyleKey, style);
|
|
163
|
+
const objects = room.objects.length > 0 || !room.type
|
|
164
|
+
? room.objects
|
|
165
|
+
: createTownOfficeRoomObjects({
|
|
166
|
+
roomId: room.id,
|
|
167
|
+
roomType: room.type,
|
|
168
|
+
width: bounds.width,
|
|
169
|
+
height: bounds.height,
|
|
170
|
+
});
|
|
171
|
+
for (const object of objects) {
|
|
172
|
+
drawTownOfficeObject(scene, bounds, object);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function createRoomTitleOverlay(
|
|
178
|
+
scene: TownOfficeSceneLike,
|
|
179
|
+
root: TownOfficeDomOverlayRoot,
|
|
180
|
+
worldX: number,
|
|
181
|
+
worldY: number,
|
|
182
|
+
title: string,
|
|
183
|
+
): void {
|
|
184
|
+
const element = root.ownerDocument?.createElement("div") ?? globalThis.document?.createElement?.("div");
|
|
185
|
+
if (!element) {
|
|
186
|
+
throw new Error("Town office DOM overlay requires document.createElement");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
element.className = "room-dom-title";
|
|
190
|
+
element.textContent = title;
|
|
191
|
+
element.style.position = "absolute";
|
|
192
|
+
element.style.left = "0";
|
|
193
|
+
element.style.top = "0";
|
|
194
|
+
element.style.pointerEvents = "none";
|
|
195
|
+
element.style.transformOrigin = "left center";
|
|
196
|
+
root.appendChild?.(element as TownOfficeDomOverlayElement);
|
|
197
|
+
|
|
198
|
+
const update = () => {
|
|
199
|
+
positionDomElement(scene, root, element as TownOfficeDomOverlayElement, worldX, worldY);
|
|
200
|
+
};
|
|
201
|
+
scene.events?.on?.("postupdate", update);
|
|
202
|
+
update();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function positionDomElement(
|
|
206
|
+
scene: TownOfficeSceneLike,
|
|
207
|
+
root: TownOfficeDomOverlayRoot,
|
|
208
|
+
element: TownOfficeDomOverlayElement,
|
|
209
|
+
worldX: number,
|
|
210
|
+
worldY: number,
|
|
211
|
+
): void {
|
|
212
|
+
const camera = scene.cameras?.main;
|
|
213
|
+
if (!camera) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const scaleX = camera.width && root.clientWidth ? root.clientWidth / camera.width : 1;
|
|
218
|
+
const scaleY = camera.height && root.clientHeight ? root.clientHeight / camera.height : 1;
|
|
219
|
+
const x = Math.round((worldX - camera.scrollX) * camera.zoom * scaleX);
|
|
220
|
+
const y = Math.round((worldY - camera.scrollY) * camera.zoom * scaleY);
|
|
221
|
+
const scale = camera.zoom * Math.min(scaleX, scaleY);
|
|
222
|
+
element.style.transform = `translate3d(${x}px, ${y}px, 0) translateY(-50%) scale(${formatDomScale(scale)})`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function formatDomScale(scale: number): string {
|
|
226
|
+
return Number.isInteger(scale) ? String(scale) : scale.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function drawBackgroundPattern(scene: TownOfficeSceneLike, width: number, height: number): void {
|
|
230
|
+
for (let y = 0; y < height; y += 40) {
|
|
231
|
+
for (let x = 0; x < width; x += 40) {
|
|
232
|
+
const color = (x / 40 + y / 40) % 2 === 0 ? "#e4e8ec" : "#e8ecf0";
|
|
233
|
+
rect(scene, x + 20, y + 20, 40, 40, color, 0);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function drawFloorPattern(scene: TownOfficeSceneLike, bounds: OfficeRoomBounds, roomType: string): void {
|
|
239
|
+
const step = roomType === "cafeteria" || roomType === "fitness" ? 16 : 24;
|
|
240
|
+
const lineColor = roomType === "cafeteria" || roomType === "fitness" ? "#d0ccc4" : "#c8c0b4";
|
|
241
|
+
for (let x = bounds.x + step; x < bounds.x + bounds.width; x += step) {
|
|
242
|
+
rect(scene, x, bounds.y + bounds.height / 2, FLOOR_GRID_LINE_WIDTH, bounds.height - HEADER_HEIGHT, lineColor, 1);
|
|
243
|
+
}
|
|
244
|
+
for (let y = bounds.y + HEADER_HEIGHT + step; y < bounds.y + bounds.height; y += step) {
|
|
245
|
+
rect(scene, bounds.x + bounds.width / 2, y, bounds.width, FLOOR_GRID_LINE_WIDTH, lineColor, 1);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function drawTownOfficeObject(scene: TownOfficeSceneLike, bounds: OfficeRoomBounds, object: OfficeBuildingObjectDefinition): void {
|
|
250
|
+
const x = bounds.x + object.x;
|
|
251
|
+
const y = bounds.y + object.y;
|
|
252
|
+
const scale = object.scale ?? 1;
|
|
253
|
+
|
|
254
|
+
switch (object.frame) {
|
|
255
|
+
case "desk-single":
|
|
256
|
+
case "desk-long":
|
|
257
|
+
drawDesk(scene, x, y, scale, !object.id.includes("training-desk-single"));
|
|
258
|
+
break;
|
|
259
|
+
case "chair-office":
|
|
260
|
+
drawChair(scene, x, y, scale);
|
|
261
|
+
break;
|
|
262
|
+
case "whiteboard":
|
|
263
|
+
case "wall-art":
|
|
264
|
+
drawWhiteboard(scene, x - 42 * scale, y - 14 * scale, 84 * scale, 28 * scale);
|
|
265
|
+
break;
|
|
266
|
+
case "plant-small":
|
|
267
|
+
case "plant-large":
|
|
268
|
+
drawPlant(scene, x, y, scale * (object.frame === "plant-large" ? 1.4 : 1));
|
|
269
|
+
break;
|
|
270
|
+
case "monitor":
|
|
271
|
+
drawMonitor(scene, x, y, scale);
|
|
272
|
+
break;
|
|
273
|
+
case "sofa-long":
|
|
274
|
+
drawSofa(scene, x, y, scale);
|
|
275
|
+
break;
|
|
276
|
+
case "laptop":
|
|
277
|
+
drawLaptop(scene, x, y, scale);
|
|
278
|
+
break;
|
|
279
|
+
case "bookshelf":
|
|
280
|
+
drawBookshelf(scene, x, y, scale);
|
|
281
|
+
break;
|
|
282
|
+
case "filing-cabinet":
|
|
283
|
+
drawCabinet(scene, x, y, scale);
|
|
284
|
+
break;
|
|
285
|
+
case "meeting-table":
|
|
286
|
+
drawTable(scene, x, y, 72 * scale, 34 * scale);
|
|
287
|
+
break;
|
|
288
|
+
case "coffee-machine":
|
|
289
|
+
drawCoffeeMachine(scene, x, y, scale);
|
|
290
|
+
break;
|
|
291
|
+
case "water-dispenser":
|
|
292
|
+
case "locker":
|
|
293
|
+
drawMachine(scene, x, y, scale);
|
|
294
|
+
break;
|
|
295
|
+
case "vending-machine":
|
|
296
|
+
drawVendingMachine(scene, x, y, scale);
|
|
297
|
+
break;
|
|
298
|
+
case "phone":
|
|
299
|
+
drawPhone(scene, x, y, scale);
|
|
300
|
+
break;
|
|
301
|
+
case "schedule-board":
|
|
302
|
+
drawScheduleBoard(scene, x, y, scale);
|
|
303
|
+
break;
|
|
304
|
+
case "trash-can":
|
|
305
|
+
drawTrashCan(scene, x, y, scale);
|
|
306
|
+
break;
|
|
307
|
+
case "bench":
|
|
308
|
+
drawBench(scene, x, y, scale);
|
|
309
|
+
break;
|
|
310
|
+
case "flower-pot":
|
|
311
|
+
drawFlowerPot(scene, x, y, scale, "#e85080");
|
|
312
|
+
break;
|
|
313
|
+
case "clock":
|
|
314
|
+
drawClock(scene, x, y, scale);
|
|
315
|
+
break;
|
|
316
|
+
case "bulletin-board":
|
|
317
|
+
drawBulletinBoard(scene, x, y, scale);
|
|
318
|
+
break;
|
|
319
|
+
case "fire-extinguisher":
|
|
320
|
+
drawFireExtinguisher(scene, x, y, scale);
|
|
321
|
+
break;
|
|
322
|
+
case "rug-mat":
|
|
323
|
+
drawRugMat(scene, x, y, scale);
|
|
324
|
+
break;
|
|
325
|
+
case "window":
|
|
326
|
+
drawWindow(scene, x, y, scale);
|
|
327
|
+
break;
|
|
328
|
+
case "air-conditioner":
|
|
329
|
+
drawAirConditioner(scene, x, y, scale);
|
|
330
|
+
break;
|
|
331
|
+
case "exit-sign":
|
|
332
|
+
drawExitSign(scene, x, y, scale);
|
|
333
|
+
break;
|
|
334
|
+
case "picture":
|
|
335
|
+
drawPicture(scene, x, y, scale, "#5588bb");
|
|
336
|
+
break;
|
|
337
|
+
case "printer":
|
|
338
|
+
drawPrinter(scene, x, y, scale);
|
|
339
|
+
break;
|
|
340
|
+
case "door":
|
|
341
|
+
drawDoor(scene, x, y, scale);
|
|
342
|
+
break;
|
|
343
|
+
case "light-switch":
|
|
344
|
+
drawLightSwitch(scene, x, y, scale);
|
|
345
|
+
break;
|
|
346
|
+
case "socket":
|
|
347
|
+
drawSocket(scene, x, y, scale);
|
|
348
|
+
break;
|
|
349
|
+
case "server-rack":
|
|
350
|
+
drawServerRack(scene, x, y, scale);
|
|
351
|
+
break;
|
|
352
|
+
case "tv":
|
|
353
|
+
drawTV(scene, x, y, scale);
|
|
354
|
+
break;
|
|
355
|
+
case "monitor-wall":
|
|
356
|
+
drawMonitorWall(scene, x, y, scale);
|
|
357
|
+
break;
|
|
358
|
+
case "ticker-board":
|
|
359
|
+
drawTickerBoard(scene, x, y, scale);
|
|
360
|
+
break;
|
|
361
|
+
case "test-rack":
|
|
362
|
+
drawTestRack(scene, x, y, scale);
|
|
363
|
+
break;
|
|
364
|
+
case "cert-badge":
|
|
365
|
+
drawCertBadge(scene, x, y, scale);
|
|
366
|
+
break;
|
|
367
|
+
case "henan-map":
|
|
368
|
+
drawHenanMap(scene, x, y, scale);
|
|
369
|
+
break;
|
|
370
|
+
case "battery":
|
|
371
|
+
drawBattery(scene, x, y, scale);
|
|
372
|
+
break;
|
|
373
|
+
case "solar-panel":
|
|
374
|
+
drawSolarPanel(scene, x, y, scale);
|
|
375
|
+
break;
|
|
376
|
+
case "ev-charger":
|
|
377
|
+
drawEVCharger(scene, x, y, scale);
|
|
378
|
+
break;
|
|
379
|
+
case "megaphone":
|
|
380
|
+
drawMegaphone(scene, x, y, scale);
|
|
381
|
+
break;
|
|
382
|
+
case "chart-board":
|
|
383
|
+
drawChartBoard(scene, x, y, scale);
|
|
384
|
+
break;
|
|
385
|
+
case "red-banner":
|
|
386
|
+
drawRedBanner(scene, x, y, scale);
|
|
387
|
+
break;
|
|
388
|
+
case "beaker":
|
|
389
|
+
drawBeaker(scene, x, y, scale);
|
|
390
|
+
break;
|
|
391
|
+
case "ping-pong-table":
|
|
392
|
+
drawPingPongTable(scene, x, y, scale);
|
|
393
|
+
break;
|
|
394
|
+
case "podium":
|
|
395
|
+
drawPodium(scene, x, y, scale);
|
|
396
|
+
break;
|
|
397
|
+
case "party-flag":
|
|
398
|
+
drawPartyFlag(scene, x, y, scale);
|
|
399
|
+
break;
|
|
400
|
+
case "wind-turbine":
|
|
401
|
+
drawWindTurbine(scene, x, y, scale);
|
|
402
|
+
break;
|
|
403
|
+
case "transmission-tower":
|
|
404
|
+
drawTransmissionTower(scene, x, y, scale);
|
|
405
|
+
break;
|
|
406
|
+
case "tree":
|
|
407
|
+
drawTree(scene, x, y, scale);
|
|
408
|
+
break;
|
|
409
|
+
case "gazebo":
|
|
410
|
+
drawGazebo(scene, x, y, scale);
|
|
411
|
+
break;
|
|
412
|
+
case "bicycle-rack":
|
|
413
|
+
drawBicycleRack(scene, x, y, scale);
|
|
414
|
+
break;
|
|
415
|
+
case "big-screen":
|
|
416
|
+
drawBigScreen(scene, x, y, scale);
|
|
417
|
+
break;
|
|
418
|
+
case "kpi-card":
|
|
419
|
+
drawKpiCard(scene, x, y, scale);
|
|
420
|
+
break;
|
|
421
|
+
case "seal":
|
|
422
|
+
drawSeal(scene, x, y, scale);
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function getBuildingCanvasSize(layout: OfficeBuildingLayout): { width: number; height: number } {
|
|
428
|
+
return measureOfficeBuildingLayoutSize(layout);
|
|
429
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { AgentGameAssetManifest } from "../core/assets.js";
|
|
2
|
+
import type { AgentGameCommand } from "../core/commands.js";
|
|
3
|
+
import type { OfficeBuildingLayout } from "../core/office-building-layout.js";
|
|
4
|
+
import type { OfficeLayoutDefinition, OfficeObjectAtlasDefinition } from "../core/office-layout.js";
|
|
5
|
+
import type { AgentGameSceneDefinition } from "../core/scene.js";
|
|
6
|
+
import type { AgentGameSnapshot } from "../core/state.js";
|
|
7
|
+
import type { AgentGameStore } from "../core/agent-game-store.js";
|
|
8
|
+
import type { AgentGameCameraControlsController, AgentGameCameraControlsOptions } from "./camera-controls.js";
|
|
9
|
+
import type { AgentGameDebugOverlays } from "./debug-overlay.js";
|
|
10
|
+
import type { AgentGameRendererConfig, AgentGameSceneRendererRegistry } from "./scene-renderer.js";
|
|
11
|
+
import type { AgentGameViewportOptions } from "./viewport.js";
|
|
12
|
+
|
|
13
|
+
export type PhaserGameLike = {
|
|
14
|
+
destroy(removeCanvas?: boolean): void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type AgentGamePhaserOptions = {
|
|
18
|
+
scene: AgentGameSceneDefinition;
|
|
19
|
+
assets: AgentGameAssetManifest;
|
|
20
|
+
width?: number;
|
|
21
|
+
height?: number;
|
|
22
|
+
viewport?: AgentGameViewportOptions;
|
|
23
|
+
resolution?: number;
|
|
24
|
+
backgroundColor?: string;
|
|
25
|
+
backgroundImage?: {
|
|
26
|
+
key: string;
|
|
27
|
+
url: string;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
};
|
|
31
|
+
officeLayout?: {
|
|
32
|
+
atlasKey: string;
|
|
33
|
+
imageUrl: string;
|
|
34
|
+
atlasUrl: string;
|
|
35
|
+
definition: OfficeLayoutDefinition;
|
|
36
|
+
atlas: OfficeObjectAtlasDefinition;
|
|
37
|
+
};
|
|
38
|
+
officeBuilding?: {
|
|
39
|
+
atlasKey: string;
|
|
40
|
+
imageUrl: string;
|
|
41
|
+
atlasUrl: string;
|
|
42
|
+
layout: OfficeBuildingLayout;
|
|
43
|
+
atlas: OfficeObjectAtlasDefinition;
|
|
44
|
+
};
|
|
45
|
+
townOfficeBuilding?: {
|
|
46
|
+
layout: OfficeBuildingLayout;
|
|
47
|
+
};
|
|
48
|
+
debugOverlays?: AgentGameDebugOverlays;
|
|
49
|
+
showAnchorDebug?: boolean;
|
|
50
|
+
cameraControls?: AgentGameCameraControlsOptions;
|
|
51
|
+
domOverlay?: boolean;
|
|
52
|
+
movementDurationMs?: number;
|
|
53
|
+
movementSpeedPxPerSecond?: number;
|
|
54
|
+
movementMinDurationMs?: number;
|
|
55
|
+
movementMaxDurationMs?: number;
|
|
56
|
+
store?: AgentGameStore;
|
|
57
|
+
renderer?: AgentGameRendererConfig;
|
|
58
|
+
rendererRegistry?: AgentGameSceneRendererRegistry;
|
|
59
|
+
createPhaserGame?: (config: unknown) => PhaserGameLike;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type AgentGameController = {
|
|
63
|
+
dispatch(command: AgentGameCommand): AgentGameSnapshot;
|
|
64
|
+
getSnapshot(): AgentGameSnapshot;
|
|
65
|
+
getCameraControls(): AgentGameCameraControlsController | null;
|
|
66
|
+
destroy(): void;
|
|
67
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type AgentGameViewportRatio = {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type AgentGameViewportOptions = {
|
|
7
|
+
ratio: AgentGameViewportRatio;
|
|
8
|
+
baseWidth?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type AgentGameViewportSizeInput = {
|
|
12
|
+
width?: number;
|
|
13
|
+
height?: number;
|
|
14
|
+
viewport?: AgentGameViewportOptions;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type AgentGameViewportSize = {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function resolveAgentGameViewportSize(input: AgentGameViewportSizeInput): AgentGameViewportSize {
|
|
23
|
+
if (!isPlainObject(input)) {
|
|
24
|
+
throw new Error("Agent game viewport input must be an object");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
validateExplicitSize(input.width, "width");
|
|
28
|
+
validateExplicitSize(input.height, "height");
|
|
29
|
+
|
|
30
|
+
if ((input.width === undefined) !== (input.height === undefined)) {
|
|
31
|
+
throw new Error("Agent game viewport requires both width and height when using explicit sizing");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (input.viewport !== undefined) {
|
|
35
|
+
validateViewport(input.viewport);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (input.width !== undefined && input.height !== undefined) {
|
|
39
|
+
return { width: input.width, height: input.height };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (input.viewport === undefined) {
|
|
43
|
+
return { width: 800, height: 480 };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const width = input.viewport.baseWidth ?? 800;
|
|
47
|
+
const height = Math.round((width * input.viewport.ratio.height) / input.viewport.ratio.width);
|
|
48
|
+
if (height <= 0) {
|
|
49
|
+
throw new Error("Agent game viewport resolved height must be a positive integer");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { width, height };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function validateViewport(viewport: AgentGameViewportOptions): void {
|
|
56
|
+
if (!isPlainObject(viewport)) {
|
|
57
|
+
throw new Error("Agent game viewport must be an object");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!viewport.ratio) {
|
|
61
|
+
throw new Error("Agent game viewport.ratio is required");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
validatePositiveInteger(viewport.ratio.width, "viewport.ratio.width");
|
|
65
|
+
validatePositiveInteger(viewport.ratio.height, "viewport.ratio.height");
|
|
66
|
+
|
|
67
|
+
if (viewport.baseWidth !== undefined) {
|
|
68
|
+
validatePositiveInteger(viewport.baseWidth, "viewport.baseWidth");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function validateExplicitSize(value: number | undefined, field: string): void {
|
|
73
|
+
if (value === undefined) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
validatePositiveInteger(value, field);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function validatePositiveInteger(value: number, field: string): void {
|
|
81
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
|
|
82
|
+
throw new Error(`Agent game ${field} must be a positive integer`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
87
|
+
return typeof value === "object" && value !== null;
|
|
88
|
+
}
|