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.
- package/CHANGELOG.md +640 -582
- package/README.md +592 -415
- package/cli.js +1089 -589
- package/conversation-templates/autonomous-feature.json +22 -0
- package/conversation-templates/code-review.json +21 -11
- package/conversation-templates/debug-squad.json +21 -11
- package/conversation-templates/feature-build.json +21 -11
- package/conversation-templates/research-write.json +21 -11
- package/dashboard.html +9250 -7964
- package/dashboard.js +1071 -29
- package/office/building-interior.js +261 -0
- package/office/car-hud.js +368 -0
- package/office/daynight.js +221 -0
- package/office/economy-hud.js +432 -0
- package/office/economy-ui.js +238 -0
- package/office/environment.js +818 -808
- package/office/fast-travel.js +215 -0
- package/office/hq-building.js +295 -0
- package/office/index.js +1095 -1046
- package/office/instancing.js +160 -0
- package/office/lod-manager.js +165 -0
- package/office/multiplayer-hud.js +428 -0
- package/office/net-client.js +299 -0
- package/office/particles.js +172 -0
- package/office/player.js +658 -658
- package/office/post-processing.js +82 -0
- package/office/sky.js +319 -0
- package/office/street-furniture.js +308 -0
- package/office/vehicle.js +455 -0
- package/package.json +59 -59
- package/server.js +7190 -4685
- package/conversation-templates/managed-team.json +0 -12
|
@@ -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">×</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 • 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; }
|