let-them-talk 4.3.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Fast Travel — Teleport between city districts.
3
+ * HTML overlay menu triggered by T key or HUD button.
4
+ * Moves player to district center with a brief transition effect.
5
+ */
6
+
7
+ let menuEl = null;
8
+ let visible = false;
9
+ let onTeleport = null; // callback(districtName, x, z)
10
+
11
+ const DISTRICTS = [
12
+ { name: 'Downtown', icon: '\u{1F3D9}', desc: 'Business center — tall offices, active agents', x: 0, z: 0, color: '#4a90d9' },
13
+ { name: 'Industrial', icon: '\u{1F3ED}', desc: 'Factories — heavy compute, smoke effects', x: 80, z: 0, color: '#8a7755' },
14
+ { name: 'Residential', icon: '\u{1F3E0}', desc: 'Quiet homes — agents rest here off-duty', x: 0, z: 80, color: '#cc9977' },
15
+ { name: 'Campus', icon: '\u{1F3EB}', desc: 'Research labs — agent training ground', x: -80, z: 0, color: '#55aa77' },
16
+ { name: 'Commercial', icon: '\u{1F6CD}', desc: 'Shops & cafes — upgrade store, social area', x: 0, z: -80, color: '#aa6688' },
17
+ ];
18
+
19
+ const FT_STYLES = `
20
+ .ft-menu {
21
+ position: fixed;
22
+ top: 50%;
23
+ left: 50%;
24
+ transform: translate(-50%, -50%);
25
+ background: rgba(15,15,20,0.95);
26
+ border: 1px solid rgba(212,175,55,0.5);
27
+ border-radius: 14px;
28
+ padding: 20px 24px;
29
+ min-width: 340px;
30
+ z-index: 200;
31
+ pointer-events: auto;
32
+ backdrop-filter: blur(14px);
33
+ font-family: 'Segoe UI', sans-serif;
34
+ color: #fff;
35
+ display: none;
36
+ }
37
+ .ft-menu.open { display: block; }
38
+ .ft-title {
39
+ font-size: 16px;
40
+ font-weight: 700;
41
+ color: #FFD700;
42
+ margin-bottom: 14px;
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ }
47
+ .ft-close {
48
+ cursor: pointer;
49
+ font-size: 18px;
50
+ color: #888;
51
+ background: none;
52
+ border: none;
53
+ padding: 4px 8px;
54
+ }
55
+ .ft-close:hover { color: #fff; }
56
+ .ft-hint {
57
+ font-size: 10px;
58
+ color: #888;
59
+ margin-bottom: 12px;
60
+ }
61
+ .ft-district {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 12px;
65
+ padding: 10px 12px;
66
+ border-radius: 8px;
67
+ cursor: pointer;
68
+ margin-bottom: 4px;
69
+ transition: background 0.15s ease;
70
+ }
71
+ .ft-district:hover { background: rgba(212,175,55,0.15); }
72
+ .ft-district-icon {
73
+ font-size: 24px;
74
+ min-width: 32px;
75
+ text-align: center;
76
+ }
77
+ .ft-district-info { flex: 1; }
78
+ .ft-district-name {
79
+ font-size: 14px;
80
+ font-weight: 600;
81
+ color: #eee;
82
+ }
83
+ .ft-district-desc {
84
+ font-size: 11px;
85
+ color: #888;
86
+ margin-top: 2px;
87
+ }
88
+ .ft-district-badge {
89
+ width: 8px;
90
+ height: 8px;
91
+ border-radius: 50%;
92
+ flex-shrink: 0;
93
+ }
94
+ .ft-flash {
95
+ position: fixed;
96
+ top: 0;
97
+ left: 0;
98
+ right: 0;
99
+ bottom: 0;
100
+ background: white;
101
+ z-index: 250;
102
+ pointer-events: none;
103
+ animation: ftFlash 0.6s ease-out forwards;
104
+ }
105
+ @keyframes ftFlash {
106
+ 0% { opacity: 0.8; }
107
+ 100% { opacity: 0; }
108
+ }
109
+ `;
110
+
111
+ /**
112
+ * Initialize fast travel UI.
113
+ * @param {Function} teleportCallback - Called with (districtName, x, z) when user selects a district
114
+ */
115
+ export function initFastTravel(teleportCallback) {
116
+ if (menuEl) return;
117
+ onTeleport = teleportCallback;
118
+
119
+ const style = document.createElement('style');
120
+ style.textContent = FT_STYLES;
121
+ document.head.appendChild(style);
122
+
123
+ menuEl = document.createElement('div');
124
+ menuEl.className = 'ft-menu';
125
+ menuEl.innerHTML = `
126
+ <div class="ft-title">
127
+ <span>Fast Travel</span>
128
+ <button class="ft-close" id="ft-close">&times;</button>
129
+ </div>
130
+ <div class="ft-hint">Press <kbd style="background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.2);border-radius:3px;padding:1px 5px;font-family:monospace;color:#ccc">T</kbd> to toggle &bull; Select a district to teleport</div>
131
+ <div id="ft-list"></div>
132
+ `;
133
+ document.body.appendChild(menuEl);
134
+
135
+ document.getElementById('ft-close').addEventListener('click', closeFastTravel);
136
+ renderDistricts();
137
+ }
138
+
139
+ function renderDistricts() {
140
+ const list = document.getElementById('ft-list');
141
+ if (!list) return;
142
+ list.innerHTML = DISTRICTS.map(function(d) {
143
+ return '<div class="ft-district" onclick="window._ftTeleport(\'' + d.name + '\',' + d.x + ',' + d.z + ')">' +
144
+ '<span class="ft-district-icon">' + d.icon + '</span>' +
145
+ '<div class="ft-district-info">' +
146
+ '<div class="ft-district-name">' + d.name + '</div>' +
147
+ '<div class="ft-district-desc">' + d.desc + '</div>' +
148
+ '</div>' +
149
+ '<div class="ft-district-badge" style="background:' + d.color + '"></div>' +
150
+ '</div>';
151
+ }).join('');
152
+ }
153
+
154
+ window._ftTeleport = function(name, x, z) {
155
+ closeFastTravel();
156
+ showFlash();
157
+ if (onTeleport) onTeleport(name, x, z);
158
+ };
159
+
160
+ function showFlash() {
161
+ const flash = document.createElement('div');
162
+ flash.className = 'ft-flash';
163
+ document.body.appendChild(flash);
164
+ setTimeout(function() { flash.remove(); }, 600);
165
+ }
166
+
167
+ /**
168
+ * Open the fast travel menu.
169
+ */
170
+ export function openFastTravel() {
171
+ if (!menuEl) return;
172
+ menuEl.classList.add('open');
173
+ visible = true;
174
+ }
175
+
176
+ /**
177
+ * Close the fast travel menu.
178
+ */
179
+ export function closeFastTravel() {
180
+ if (menuEl) menuEl.classList.remove('open');
181
+ visible = false;
182
+ }
183
+
184
+ /**
185
+ * Toggle the fast travel menu.
186
+ */
187
+ export function toggleFastTravel() {
188
+ if (visible) closeFastTravel();
189
+ else openFastTravel();
190
+ }
191
+
192
+ /**
193
+ * Is the fast travel menu currently open?
194
+ * @returns {boolean}
195
+ */
196
+ export function isFastTravelOpen() {
197
+ return visible;
198
+ }
199
+
200
+ /**
201
+ * Get district list (for other modules).
202
+ * @returns {Array<{ name: string, x: number, z: number }>}
203
+ */
204
+ export function getDistricts() {
205
+ return DISTRICTS.map(d => ({ name: d.name, x: d.x, z: d.z }));
206
+ }
207
+
208
+ /**
209
+ * Dispose fast travel UI.
210
+ */
211
+ export function disposeFastTravel() {
212
+ closeFastTravel();
213
+ if (menuEl) { menuEl.remove(); menuEl = null; }
214
+ delete window._ftTeleport;
215
+ }
@@ -0,0 +1,295 @@
1
+ import * as THREE from 'three';
2
+ import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
3
+ import { S } from './state.js';
4
+
5
+ // ============================================================
6
+ // HQ BUILDING — premium headquarters where agents work
7
+ // Each agent gets a named workstation with executive desk
8
+ // Glass walls, marble floors, visible from outside
9
+ // ============================================================
10
+
11
+ var HQ_W = 18;
12
+ var HQ_D = 14;
13
+ var HQ_FLOORS = 2;
14
+ var FLOOR_H = 3.5;
15
+ var hqGroup = null;
16
+ var agentDesks = {}; // { agentName: { x, y, z } } — world coordinates
17
+
18
+ // Premium materials
19
+ var mats = {};
20
+ function initMats() {
21
+ mats.marble = new THREE.MeshStandardMaterial({ color: 0xe8e0d4, roughness: 0.15, metalness: 0.05 });
22
+ mats.darkMarble = new THREE.MeshStandardMaterial({ color: 0x2a2a35, roughness: 0.2, metalness: 0.1 });
23
+ mats.glass = new THREE.MeshStandardMaterial({ color: 0x99ccee, transparent: true, opacity: 0.18, roughness: 0.0, metalness: 0.3, side: THREE.DoubleSide, depthWrite: false });
24
+ mats.walnut = new THREE.MeshStandardMaterial({ color: 0x5c3a1e, roughness: 0.5 });
25
+ mats.walnutLight = new THREE.MeshStandardMaterial({ color: 0x8B6840, roughness: 0.45 });
26
+ mats.chrome = new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.05, metalness: 0.9 });
27
+ mats.leather = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.6 });
28
+ mats.leatherBrown = new THREE.MeshStandardMaterial({ color: 0x4a2a1a, roughness: 0.55 });
29
+ mats.screen = new THREE.MeshStandardMaterial({ color: 0x112244, emissive: 0x1133aa, emissiveIntensity: 1.0, roughness: 0.05 });
30
+ mats.screenFrame = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.3, metalness: 0.5 });
31
+ mats.gold = new THREE.MeshStandardMaterial({ color: 0xd4af37, roughness: 0.25, metalness: 0.7 });
32
+ mats.ceiling = new THREE.MeshStandardMaterial({ color: 0xf0f0f0, roughness: 0.8, side: THREE.DoubleSide });
33
+ mats.ceilingLight = new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0xeeeeff, emissiveIntensity: 0.8 });
34
+ mats.plant = new THREE.MeshStandardMaterial({ color: 0x2a7a2a, roughness: 0.8 });
35
+ mats.pot = new THREE.MeshStandardMaterial({ color: 0x6a4a2a, roughness: 0.6 });
36
+ mats.nameplate = new THREE.MeshStandardMaterial({ color: 0xd4af37, roughness: 0.2, metalness: 0.6 });
37
+ mats.wall = new THREE.MeshStandardMaterial({ color: 0xddd8cc, roughness: 0.6, side: THREE.DoubleSide });
38
+ mats.accent = new THREE.MeshStandardMaterial({ color: 0x334455, roughness: 0.4 });
39
+ }
40
+
41
+ // Build one executive workstation
42
+ function buildWorkstation(group, x, y, z, agentName, facing) {
43
+ // Executive L-shaped desk
44
+ var deskW = 2.0, deskD = 0.8, deskH = 0.75;
45
+ var top = new THREE.Mesh(new THREE.BoxGeometry(deskW, 0.05, deskD), mats.walnut);
46
+ top.position.set(x, y + deskH, z);
47
+ group.add(top);
48
+
49
+ // Side extension (L-shape)
50
+ var sideTop = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.05, 1.2), mats.walnut);
51
+ sideTop.position.set(x + deskW / 2 + 0.35, y + deskH, z + 0.2);
52
+ group.add(sideTop);
53
+
54
+ // Desk legs (chrome)
55
+ [[-0.9, -0.35], [0.9, -0.35], [0.9, 0.35], [-0.9, 0.35]].forEach(function(lp) {
56
+ var leg = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.03, deskH, 6), mats.chrome);
57
+ leg.position.set(x + lp[0], y + deskH / 2, z + lp[1]);
58
+ group.add(leg);
59
+ });
60
+
61
+ // Desk panel (front modesty panel — walnut)
62
+ var panel = new THREE.Mesh(new THREE.BoxGeometry(deskW - 0.2, deskH * 0.6, 0.03), mats.walnutLight);
63
+ panel.position.set(x, y + deskH * 0.35, z - deskD / 2 + 0.02);
64
+ group.add(panel);
65
+
66
+ // Dual monitors
67
+ [-0.35, 0.35].forEach(function(mx) {
68
+ // Screen frame
69
+ var frame = new THREE.Mesh(new THREE.BoxGeometry(0.55, 0.38, 0.025), mats.screenFrame);
70
+ frame.position.set(x + mx, y + deskH + 0.25, z - 0.15);
71
+ group.add(frame);
72
+ // Screen (emissive)
73
+ var scr = new THREE.Mesh(new THREE.PlaneGeometry(0.5, 0.33), mats.screen);
74
+ scr.position.set(x + mx, y + deskH + 0.25, z - 0.16);
75
+ scr.rotation.y = Math.PI;
76
+ group.add(scr);
77
+ // Stand
78
+ var stand = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.12, 0.08), mats.chrome);
79
+ stand.position.set(x + mx, y + deskH + 0.06, z - 0.15);
80
+ group.add(stand);
81
+ });
82
+
83
+ // Keyboard
84
+ var kb = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.015, 0.15), mats.darkMarble);
85
+ kb.position.set(x, y + deskH + 0.01, z + 0.1);
86
+ group.add(kb);
87
+
88
+ // Executive chair (leather + chrome)
89
+ var chairBase = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.3, 0.05, 8), mats.chrome);
90
+ chairBase.position.set(x, y + 0.15, z + 0.6);
91
+ group.add(chairBase);
92
+ var chairPole = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.03, 0.3, 6), mats.chrome);
93
+ chairPole.position.set(x, y + 0.32, z + 0.6);
94
+ group.add(chairPole);
95
+ var chairSeat = new THREE.Mesh(new THREE.BoxGeometry(0.45, 0.06, 0.45), mats.leatherBrown);
96
+ chairSeat.position.set(x, y + 0.48, z + 0.6);
97
+ group.add(chairSeat);
98
+ var chairBack = new THREE.Mesh(new THREE.BoxGeometry(0.45, 0.5, 0.06), mats.leatherBrown);
99
+ chairBack.position.set(x, y + 0.73, z + 0.85);
100
+ group.add(chairBack);
101
+ // Armrests
102
+ [-0.22, 0.22].forEach(function(ax) {
103
+ var arm = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.04, 0.3), mats.chrome);
104
+ arm.position.set(x + ax, y + 0.58, z + 0.7);
105
+ group.add(arm);
106
+ });
107
+
108
+ // Gold nameplate on desk
109
+ var plateMesh = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.08, 0.02), mats.nameplate);
110
+ plateMesh.position.set(x, y + deskH + 0.04, z - deskD / 2 + 0.01);
111
+ group.add(plateMesh);
112
+
113
+ // Nameplate text (CSS2D)
114
+ var nameDiv = document.createElement('div');
115
+ nameDiv.textContent = agentName;
116
+ nameDiv.style.cssText = 'color:#d4af37;font-size:8px;font-weight:bold;font-family:serif;letter-spacing:1px;text-shadow:0 0 4px rgba(212,175,55,0.5);';
117
+ var nameLabel = new CSS2DObject(nameDiv);
118
+ nameLabel.position.set(x, y + deskH + 0.15, z - deskD / 2 - 0.05);
119
+ group.add(nameLabel);
120
+
121
+ // Coffee mug
122
+ var mug = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.035, 0.08, 8), new THREE.MeshStandardMaterial({ color: 0xeeeeee, roughness: 0.4 }));
123
+ mug.position.set(x + 0.7, y + deskH + 0.04, z + 0.2);
124
+ group.add(mug);
125
+
126
+ // Store desk position for agent assignment (world coords)
127
+ agentDesks[agentName] = { x: x, y: y, z: z + 0.6 }; // chair position
128
+ }
129
+
130
+ // ============================================================
131
+ // BUILD HQ — main headquarters building
132
+ // ============================================================
133
+
134
+ export function buildHQ(cx, cz, agentNames) {
135
+ initMats();
136
+ hqGroup = new THREE.Group();
137
+ hqGroup.position.set(cx, 0, cz);
138
+ agentDesks = {};
139
+
140
+ var height = HQ_FLOORS * FLOOR_H;
141
+
142
+ // === EXTERIOR SHELL ===
143
+ // Glass curtain walls (all 4 sides)
144
+ var wallThick = 0.12;
145
+ [
146
+ { w: HQ_W, d: wallThick, px: 0, pz: HQ_D / 2 }, // front
147
+ { w: HQ_W, d: wallThick, px: 0, pz: -HQ_D / 2 }, // back
148
+ { w: wallThick, d: HQ_D, px: -HQ_W / 2, pz: 0 }, // left
149
+ { w: wallThick, d: HQ_D, px: HQ_W / 2, pz: 0 }, // right
150
+ ].forEach(function(wp) {
151
+ // Structural frame (dark accent)
152
+ var frame = new THREE.Mesh(new THREE.BoxGeometry(wp.w, height, wp.d), mats.accent);
153
+ frame.position.set(wp.px, height / 2, wp.pz);
154
+ hqGroup.add(frame);
155
+ // Glass panels per floor
156
+ for (var f = 0; f < HQ_FLOORS; f++) {
157
+ var gw = wp.w > wallThick ? wp.w * 0.88 : wp.d * 0.88;
158
+ var gh = FLOOR_H * 0.65;
159
+ var gy = f * FLOOR_H + FLOOR_H * 0.55;
160
+ var glassGeo = new THREE.PlaneGeometry(gw, gh);
161
+ var glass = new THREE.Mesh(glassGeo, mats.glass);
162
+ if (wp.w > wallThick) {
163
+ glass.position.set(wp.px, gy, wp.pz + (wp.pz > 0 ? 0.01 : -0.01));
164
+ if (wp.pz < 0) glass.rotation.y = Math.PI;
165
+ } else {
166
+ glass.position.set(wp.px + (wp.px > 0 ? 0.01 : -0.01), gy, wp.pz);
167
+ glass.rotation.y = wp.px > 0 ? Math.PI / 2 : -Math.PI / 2;
168
+ }
169
+ hqGroup.add(glass);
170
+ }
171
+ });
172
+
173
+ // Roof
174
+ var roof = new THREE.Mesh(new THREE.BoxGeometry(HQ_W + 0.5, 0.2, HQ_D + 0.5), mats.accent);
175
+ roof.position.set(0, height + 0.1, 0);
176
+ hqGroup.add(roof);
177
+
178
+ // Gold "LET THEM TALK" sign on roof
179
+ var signDiv = document.createElement('div');
180
+ signDiv.textContent = 'LET THEM TALK HQ';
181
+ signDiv.style.cssText = 'color:#ffd700;font-size:12px;font-weight:bold;text-shadow:0 0 10px rgba(255,215,0,0.6);font-family:monospace;letter-spacing:3px;';
182
+ var sign = new CSS2DObject(signDiv);
183
+ sign.position.set(0, height + 1.5, 0);
184
+ hqGroup.add(sign);
185
+
186
+ // Entrance door
187
+ var door = new THREE.Mesh(new THREE.BoxGeometry(2.0, 2.8, 0.08), mats.walnut);
188
+ door.position.set(0, 1.4, HQ_D / 2 + 0.05);
189
+ hqGroup.add(door);
190
+ // Door glass
191
+ var doorGlass = new THREE.Mesh(new THREE.PlaneGeometry(0.7, 2.2), mats.glass);
192
+ doorGlass.position.set(-0.4, 1.3, HQ_D / 2 + 0.07);
193
+ hqGroup.add(doorGlass);
194
+ var doorGlass2 = new THREE.Mesh(new THREE.PlaneGeometry(0.7, 2.2), mats.glass);
195
+ doorGlass2.position.set(0.4, 1.3, HQ_D / 2 + 0.07);
196
+ hqGroup.add(doorGlass2);
197
+
198
+ // Entrance canopy
199
+ var canopy = new THREE.Mesh(new THREE.BoxGeometry(4, 0.12, 1.5), mats.accent);
200
+ canopy.position.set(0, 3.0, HQ_D / 2 + 0.7);
201
+ hqGroup.add(canopy);
202
+
203
+ // === FLOOR INTERIORS ===
204
+ for (var floor = 0; floor < HQ_FLOORS; floor++) {
205
+ var fy = floor * FLOOR_H;
206
+
207
+ // Floor slab (marble)
208
+ var floorMat = floor === 0 ? mats.marble : mats.darkMarble;
209
+ var floorMesh = new THREE.Mesh(new THREE.BoxGeometry(HQ_W - 0.3, 0.08, HQ_D - 0.3), floorMat);
210
+ floorMesh.position.set(0, fy + 0.04, 0);
211
+ hqGroup.add(floorMesh);
212
+
213
+ // Ceiling
214
+ var ceil = new THREE.Mesh(new THREE.BoxGeometry(HQ_W - 0.3, 0.06, HQ_D - 0.3), mats.ceiling);
215
+ ceil.position.set(0, fy + FLOOR_H - 0.03, 0);
216
+ hqGroup.add(ceil);
217
+
218
+ // Ceiling light strips
219
+ [-3, 0, 3].forEach(function(lx) {
220
+ var light = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.04, HQ_D * 0.7), mats.ceilingLight);
221
+ light.position.set(lx, fy + FLOOR_H - 0.08, 0);
222
+ hqGroup.add(light);
223
+ });
224
+
225
+ // Gold accent strip along front wall
226
+ var accentStrip = new THREE.Mesh(new THREE.BoxGeometry(HQ_W * 0.9, 0.05, 0.03), mats.gold);
227
+ accentStrip.position.set(0, fy + 1.0, -HQ_D / 2 + 0.15);
228
+ hqGroup.add(accentStrip);
229
+ }
230
+
231
+ // === AGENT WORKSTATIONS ===
232
+ var desksPerFloor = Math.ceil(agentNames.length / HQ_FLOORS);
233
+ var deskSpacing = (HQ_W - 4) / Math.max(desksPerFloor, 1);
234
+
235
+ for (var i = 0; i < agentNames.length; i++) {
236
+ var floorIdx = Math.floor(i / desksPerFloor);
237
+ var deskOnFloor = i % desksPerFloor;
238
+ var fy2 = floorIdx * FLOOR_H;
239
+ var dx = -HQ_W / 2 + 2.5 + deskOnFloor * deskSpacing;
240
+ var dz = -1.5; // facing back wall (screens away from windows)
241
+
242
+ buildWorkstation(hqGroup, dx, fy2, dz, agentNames[i], 0);
243
+ }
244
+
245
+ // === DECORATIONS ===
246
+ // Potted plants at corners
247
+ [[HQ_W/2 - 1, 0, HQ_D/2 - 1], [-HQ_W/2 + 1, 0, HQ_D/2 - 1], [HQ_W/2 - 1, 0, -HQ_D/2 + 1], [-HQ_W/2 + 1, 0, -HQ_D/2 + 1]].forEach(function(p) {
248
+ var pot = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0.15, 0.3, 8), mats.pot);
249
+ pot.position.set(p[0], p[1] + 0.15, p[2]);
250
+ hqGroup.add(pot);
251
+ var plant = new THREE.Mesh(new THREE.SphereGeometry(0.4, 8, 6), mats.plant);
252
+ plant.position.set(p[0], p[1] + 0.55, p[2]);
253
+ hqGroup.add(plant);
254
+ });
255
+
256
+ // Reception desk (ground floor, near entrance)
257
+ var recDesk = new THREE.Mesh(new THREE.BoxGeometry(3, 1.0, 0.6), mats.walnut);
258
+ recDesk.position.set(0, 0.5, HQ_D / 2 - 2);
259
+ hqGroup.add(recDesk);
260
+ var recTop = new THREE.Mesh(new THREE.BoxGeometry(3.2, 0.04, 0.7), mats.marble);
261
+ recTop.position.set(0, 1.02, HQ_D / 2 - 2);
262
+ hqGroup.add(recTop);
263
+
264
+ // Water cooler
265
+ var cooler = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 0.9, 8), new THREE.MeshStandardMaterial({ color: 0x88bbcc, roughness: 0.2 }));
266
+ cooler.position.set(HQ_W / 2 - 1.5, 0.45, 0);
267
+ hqGroup.add(cooler);
268
+
269
+ S.furnitureGroup.add(hqGroup);
270
+
271
+ return {
272
+ group: hqGroup,
273
+ desks: agentDesks,
274
+ position: { x: cx, z: cz },
275
+ width: HQ_W,
276
+ depth: HQ_D,
277
+ height: height,
278
+ };
279
+ }
280
+
281
+ export function getAgentDeskPosition(agentName) {
282
+ var desk = agentDesks[agentName];
283
+ if (!desk) return null;
284
+ // Convert local to world (group is at cx, cz)
285
+ if (hqGroup) {
286
+ return {
287
+ x: hqGroup.position.x + desk.x,
288
+ y: desk.y,
289
+ z: hqGroup.position.z + desk.z,
290
+ };
291
+ }
292
+ return desk;
293
+ }
294
+
295
+ export function getAllDeskPositions() { return agentDesks; }