6rz6 commited on
Commit
a32dc8b
·
1 Parent(s): dfb3704

Add Medieval Village AI Emulator

Browse files
README.md CHANGED
@@ -1,10 +1,75 @@
1
  ---
2
- title: Medieval Village AI
3
- emoji: 📊
4
- colorFrom: gray
5
- colorTo: pink
6
  sdk: static
 
7
  pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Medieval Village AI Emulator
3
+ emoji: 🏰
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: static
7
+ app_file: index.html
8
  pinned: false
9
+ license: mit
10
  ---
11
 
12
+ # Medieval Village AI Emulator
13
+
14
+ An interactive 3D simulation of a medieval village powered by artificial intelligence. Watch as AI-controlled villagers go about their daily routines, work, eat, sleep, and socialize in a dynamically generated environment.
15
+
16
+ ## Features
17
+
18
+ - **3D Visualization**: Real-time 3D rendering of villagers, buildings, and resources using Three.js
19
+ - **AI-Powered Villagers**: Villagers with complex behaviors based on needs (energy, hunger, social)
20
+ - **Dynamic Environment**: Weather system, disasters, and wildlife interactions
21
+ - **Interactive Controls**: Add villagers, trigger disasters, spawn animals, and dispatch warriors
22
+ - **LLM Integration**: Optional integration with Hugging Face models for enhanced AI behaviors
23
+
24
+ ## How to Use
25
+
26
+ 1. The simulation starts automatically with 3 villagers
27
+ 2. Use the controls panel on the left to interact with the village:
28
+ - Add Villager: Creates a new villager at a random position
29
+ - Reset Simulation: Clears all villagers and restarts the simulation
30
+ - Adjust Time Speed: Control simulation speed (0.1x to 5.0x)
31
+ - Toggle Paths/Titles: Show/hide movement paths and villager names
32
+ 3. Camera controls:
33
+ - Mouse: Look around (orbit)
34
+ - Mouse Wheel: Zoom in/out
35
+ - Right Mouse + Drag: Pan camera
36
+ 4. Click on any villager to see detailed information about their state, needs, and current activities
37
+
38
+ ## Technical Details
39
+
40
+ This simulation demonstrates several AI techniques:
41
+
42
+ - **Pathfinding**: A* algorithm for efficient navigation
43
+ - **Behavior Trees**: Decision-making system for villager actions
44
+ - **Daily Routines**: Time-based scheduling system
45
+ - **Crowd Simulation**: Steering behaviors and collision avoidance
46
+ - **Resource Management**: Villagers gather and use resources
47
+ - **Environmental Interaction**: Villagers interact with buildings and resources
48
+
49
+ ## LLM Integration
50
+
51
+ The simulator can optionally integrate with Hugging Face models for enhanced AI behaviors. To use this feature:
52
+
53
+ 1. Get a Hugging Face API token from [https://huggingface.co/settings/tokens](https://huggingface.co/settings/tokens)
54
+ 2. Set the token in the browser console: `window.HF_TOKEN = "your-token-here"`
55
+ 3. Select a model from the LLM Controls dropdown
56
+ 4. Ask questions about the simulation using the LLM query input
57
+
58
+ ## Architecture
59
+
60
+ ```
61
+ index.html # Main HTML file with UI
62
+ ├── app_new.js # Main application logic
63
+ └── src/ai/ # AI system components
64
+ ├── main.js # AI system integration
65
+ ├── routines.js # Daily routine management
66
+ ├── environment.js # Environment interaction
67
+ ├── pathfinding.js # Navigation system
68
+ ├── behavior.js # Behavior trees
69
+ ├── crowd.js # Crowd simulation
70
+ └── optimization.js # Performance optimization
71
+ ```
72
+
73
+ ## License
74
+
75
+ This project is licensed under the MIT License - see the LICENSE file for details.
app.js ADDED
@@ -0,0 +1,687 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Using globally available THREE from script tag in index.html
2
+ import VillageAISystem from './src/ai/main.js';
3
+
4
+ /**
5
+ * Medieval Village AI System - Three.js Visualization Application
6
+ *
7
+ * This application demonstrates the village AI system with real-time 3D visualization
8
+ * using Three.js. It shows villagers moving around, performing activities, and interacting
9
+ * with their environment.
10
+ */
11
+ class VillageVisualizationApp {
12
+ constructor() {
13
+ this.scene = null;
14
+ this.camera = null;
15
+ this.renderer = null;
16
+ this.controls = null;
17
+ this.aiSystem = null;
18
+
19
+ // 3D Objects
20
+ this.villagerMeshes = new Map();
21
+ this.buildingMeshes = new Map();
22
+ this.resourceMeshes = new Map();
23
+ this.pathLines = new Map();
24
+
25
+ // UI Elements
26
+ this.uiElements = {};
27
+ this.selectedVillager = null;
28
+ this.timeSpeed = 1.0;
29
+ this.showPaths = true;
30
+
31
+ // Animation
32
+ this.clock = new THREE.Clock();
33
+ this.lastTime = 0;
34
+ this.frameCount = 0;
35
+ this.fps = 0;
36
+
37
+ this.init();
38
+ }
39
+
40
+ /**
41
+ * Initialize the application
42
+ */
43
+ init() {
44
+ console.log('Initializing Village Visualization App...');
45
+ this.initThreeJS();
46
+ this.initAI();
47
+ this.initUI();
48
+ this.createEnvironment();
49
+ this.createInitialVillagers();
50
+ this.animate();
51
+ console.log('Village Visualization App initialized successfully');
52
+ }
53
+
54
+ /**
55
+ * Initialize Three.js scene, camera, and renderer
56
+ */
57
+ initThreeJS() {
58
+ console.log('Initializing Three.js...');
59
+
60
+ // Scene
61
+ this.scene = new THREE.Scene();
62
+ this.scene.background = new THREE.Color(0x87CEEB); // Sky blue
63
+ console.log('Scene created');
64
+
65
+ // Camera
66
+ this.camera = new THREE.PerspectiveCamera(
67
+ 75,
68
+ window.innerWidth / window.innerHeight,
69
+ 0.1,
70
+ 1000
71
+ );
72
+ this.camera.position.set(20, 20, 20);
73
+ this.camera.lookAt(0, 0, 0);
74
+ console.log('Camera created');
75
+
76
+ // Renderer
77
+ this.renderer = new THREE.WebGLRenderer({ antialias: true });
78
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
79
+ this.renderer.shadowMap.enabled = true;
80
+ this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
81
+ console.log('Renderer created');
82
+
83
+ // Add to DOM
84
+ const container = document.getElementById('container');
85
+ if (container) {
86
+ container.appendChild(this.renderer.domElement);
87
+ console.log('Renderer added to DOM');
88
+ } else {
89
+ console.error('Container element not found!');
90
+ }
91
+
92
+ // Controls
93
+ console.log('Initializing controls...');
94
+ console.log('THREE.OrbitControls:', typeof THREE !== 'undefined' ? THREE.OrbitControls : 'undefined');
95
+ try {
96
+ // Check if OrbitControls is available globally
97
+ if (typeof THREE !== 'undefined' && typeof THREE.OrbitControls !== 'undefined') {
98
+ console.log('Creating OrbitControls instance from global THREE object...');
99
+ this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
100
+ console.log('OrbitControls instance created:', this.controls);
101
+ this.controls.enableDamping = true;
102
+ this.controls.dampingFactor = 0.05;
103
+ this.controls.enableZoom = true;
104
+ this.controls.enablePan = true;
105
+ console.log('OrbitControls initialized successfully');
106
+ } else {
107
+ console.warn('OrbitControls is not available');
108
+ this.controls = null;
109
+ }
110
+ } catch (error) {
111
+ console.warn('Error initializing OrbitControls:', error);
112
+ this.controls = null;
113
+ }
114
+
115
+ // Lighting
116
+ this.addLighting();
117
+
118
+ // Ground plane
119
+ this.createGround();
120
+
121
+ // Grid helper
122
+ const gridHelper = new THREE.GridHelper(100, 100, 0x444444, 0x222222);
123
+ this.scene.add(gridHelper);
124
+
125
+ // Handle window resize
126
+ window.addEventListener('resize', () => this.onWindowResize());
127
+ window.addEventListener('keydown', (event) => this.onKeyDown(event));
128
+ this.renderer.domElement.addEventListener('click', (event) => this.onMouseClick(event));
129
+ }
130
+
131
+ /**
132
+ * Add lighting to the scene
133
+ */
134
+ addLighting() {
135
+ // Ambient light
136
+ const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
137
+ this.scene.add(ambientLight);
138
+
139
+ // Directional light (sun)
140
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
141
+ directionalLight.position.set(10, 10, 5);
142
+ directionalLight.castShadow = true;
143
+ directionalLight.shadow.mapSize.width = 2048;
144
+ directionalLight.shadow.mapSize.height = 2048;
145
+ directionalLight.shadow.camera.near = 0.5;
146
+ directionalLight.shadow.camera.far = 50;
147
+ this.scene.add(directionalLight);
148
+ }
149
+
150
+ /**
151
+ * Create ground plane
152
+ */
153
+ createGround() {
154
+ const groundGeometry = new THREE.PlaneGeometry(100, 100);
155
+ const groundMaterial = new THREE.MeshLambertMaterial({
156
+ color: 0x228B22,
157
+ transparent: true,
158
+ opacity: 0.8
159
+ });
160
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
161
+ ground.rotation.x = -Math.PI / 2;
162
+ ground.receiveShadow = true;
163
+ this.scene.add(ground);
164
+ }
165
+
166
+ /**
167
+ * Initialize the AI system
168
+ */
169
+ initAI() {
170
+ this.aiSystem = new VillageAISystem(this.scene);
171
+ }
172
+
173
+ /**
174
+ * Initialize UI event listeners
175
+ */
176
+ initUI() {
177
+ // Get UI elements
178
+ this.uiElements = {
179
+ addVillagerBtn: document.getElementById('add-villager-btn'),
180
+ resetBtn: document.getElementById('reset-btn'),
181
+ timeSpeed: document.getElementById('time-speed'),
182
+ timeSpeedDisplay: document.getElementById('time-speed-display'),
183
+ showPaths: document.getElementById('show-paths'),
184
+ villagerCountDisplay: document.getElementById('villager-count-display'),
185
+ villagerCountStat: document.getElementById('villager-count-stat'),
186
+ gameTime: document.getElementById('game-time'),
187
+ fps: document.getElementById('fps'),
188
+ buildingCount: document.getElementById('building-count'),
189
+ resourceCount: document.getElementById('resource-count'),
190
+ villagerList: document.getElementById('villager-list')
191
+ };
192
+
193
+ // Add event listeners
194
+ this.uiElements.addVillagerBtn.addEventListener('click', () => this.addVillager());
195
+ this.uiElements.resetBtn.addEventListener('click', () => this.resetSimulation());
196
+ this.uiElements.timeSpeed.addEventListener('input', (e) => this.updateTimeSpeed(e.target.value));
197
+ this.uiElements.showPaths.addEventListener('change', (e) => this.togglePaths(e.target.checked));
198
+ }
199
+
200
+ /**
201
+ * Create the 3D environment (buildings and resources)
202
+ */
203
+ createEnvironment() {
204
+ // Buildings are already created in the AI system
205
+ // We need to create 3D meshes for them
206
+ this.createBuildingMeshes();
207
+ this.createResourceMeshes();
208
+ }
209
+
210
+ /**
211
+ * Create 3D meshes for buildings
212
+ */
213
+ createBuildingMeshes() {
214
+ const buildingGeometry = new THREE.BoxGeometry(3, 3, 3);
215
+ const materials = {
216
+ house: new THREE.MeshLambertMaterial({ color: 0x8B4513 }),
217
+ workshop: new THREE.MeshLambertMaterial({ color: 0x696969 }),
218
+ market: new THREE.MeshLambertMaterial({ color: 0xFFD700 })
219
+ };
220
+
221
+ for (const [id, building] of this.aiSystem.environmentSystem.buildings) {
222
+ const material = materials[building.type] || materials.house;
223
+ const mesh = new THREE.Mesh(buildingGeometry, material);
224
+ mesh.position.set(building.position[0], building.position[1], building.position[2]);
225
+ mesh.position.y = 1.5; // Half height
226
+ mesh.castShadow = true;
227
+ mesh.receiveShadow = true;
228
+
229
+ // Add building label
230
+ const label = this.createTextSprite(building.type);
231
+ label.position.set(0, 2.5, 0);
232
+ mesh.add(label);
233
+
234
+ this.buildingMeshes.set(id, mesh);
235
+ this.scene.add(mesh);
236
+ }
237
+
238
+ this.updateBuildingCount();
239
+ }
240
+
241
+ /**
242
+ * Create 3D meshes for resources
243
+ */
244
+ createResourceMeshes() {
245
+ const resourceGeometry = new THREE.CylinderGeometry(0.5, 0.5, 2);
246
+ const materials = {
247
+ wood: new THREE.MeshLambertMaterial({ color: 0x8B4513 }),
248
+ stone: new THREE.MeshLambertMaterial({ color: 0x708090 }),
249
+ food: new THREE.MeshLambertMaterial({ color: 0x32CD32 })
250
+ };
251
+
252
+ for (const [id, resource] of this.aiSystem.environmentSystem.resources) {
253
+ const material = materials[resource.type] || materials.wood;
254
+ const mesh = new THREE.Mesh(resourceGeometry, material);
255
+ mesh.position.set(resource.position[0], resource.position[1], resource.position[2]);
256
+ mesh.position.y = 1; // Half height
257
+ mesh.castShadow = true;
258
+ mesh.receiveShadow = true;
259
+
260
+ // Add resource label
261
+ const label = this.createTextSprite(`${resource.type} (${resource.amount})`);
262
+ label.position.set(0, 1.5, 0);
263
+ mesh.add(label);
264
+
265
+ this.resourceMeshes.set(id, mesh);
266
+ this.scene.add(mesh);
267
+ }
268
+
269
+ this.updateResourceCount();
270
+ }
271
+
272
+ /**
273
+ * Create initial villagers
274
+ */
275
+ createInitialVillagers() {
276
+ // Create a few initial villagers at random positions
277
+ const positions = [
278
+ [0, 0, 0],
279
+ [5, 0, 5],
280
+ [-3, 0, -3]
281
+ ];
282
+
283
+ positions.forEach((position, index) => {
284
+ this.createVillager(`villager${index + 1}`, position);
285
+ });
286
+ }
287
+
288
+ /**
289
+ * Create a new villager
290
+ */
291
+ createVillager(id, position) {
292
+ const villager = this.aiSystem.createVillager(id, position);
293
+ this.createVillagerMesh(villager);
294
+ this.updateVillagerCount();
295
+ return villager;
296
+ }
297
+
298
+ /**
299
+ * Create 3D mesh for a villager
300
+ */
301
+ createVillagerMesh(villager) {
302
+ // Create villager geometry (sphere)
303
+ const geometry = new THREE.SphereGeometry(0.5, 16, 16);
304
+ const material = new THREE.MeshLambertMaterial({
305
+ color: this.getStateColor(villager.state)
306
+ });
307
+
308
+ const mesh = new THREE.Mesh(geometry, material);
309
+ mesh.position.set(villager.position[0], villager.position[1], villager.position[2]);
310
+ mesh.position.y = 0.5; // Half height
311
+ mesh.castShadow = true;
312
+ mesh.receiveShadow = true;
313
+
314
+ // Add villager ID label
315
+ const label = this.createTextSprite(villager.id);
316
+ label.position.set(0, 1.2, 0);
317
+ mesh.add(label);
318
+
319
+ // Store reference to villager in mesh
320
+ mesh.userData.villager = villager;
321
+
322
+ this.villagerMeshes.set(villager.id, mesh);
323
+ this.scene.add(mesh);
324
+ }
325
+
326
+ /**
327
+ * Get color for villager state
328
+ */
329
+ getStateColor(state) {
330
+ const colors = {
331
+ sleep: 0x7f8c8d, // Gray
332
+ work: 0xe74c3c, // Red
333
+ eat: 0xf39c12, // Orange
334
+ socialize: 0x9b59b6, // Purple
335
+ idle: 0x95a5a6 // Light gray
336
+ };
337
+ return colors[state] || colors.idle;
338
+ }
339
+
340
+ /**
341
+ * Create a text sprite for labels
342
+ */
343
+ createTextSprite(text) {
344
+ const canvas = document.createElement('canvas');
345
+ const context = canvas.getContext('2d');
346
+ canvas.width = 256;
347
+ canvas.height = 128;
348
+
349
+ context.fillStyle = 'rgba(0, 0, 0, 0.8)';
350
+ context.fillRect(0, 0, canvas.width, canvas.height);
351
+
352
+ context.fillStyle = 'white';
353
+ context.font = '32px Arial';
354
+ context.textAlign = 'center';
355
+ context.textBaseline = 'middle';
356
+ context.fillText(text, canvas.width / 2, canvas.height / 2);
357
+
358
+ const texture = new THREE.CanvasTexture(canvas);
359
+ const spriteMaterial = new THREE.SpriteMaterial({
360
+ map: texture,
361
+ transparent: true
362
+ });
363
+ const sprite = new THREE.Sprite(spriteMaterial);
364
+ sprite.scale.set(3, 1.5, 1);
365
+
366
+ return sprite;
367
+ }
368
+
369
+ /**
370
+ * Add a new villager via UI
371
+ */
372
+ addVillager() {
373
+ const villagerCount = this.villagerMeshes.size;
374
+ const id = `villager${villagerCount + 1}`;
375
+
376
+ // Random position within bounds
377
+ const position = [
378
+ (Math.random() - 0.5) * 40,
379
+ 0,
380
+ (Math.random() - 0.5) * 40
381
+ ];
382
+
383
+ this.createVillager(id, position);
384
+ }
385
+
386
+ /**
387
+ * Reset the simulation
388
+ */
389
+ resetSimulation() {
390
+ // Clear all villagers
391
+ for (const [id, mesh] of this.villagerMeshes) {
392
+ this.scene.remove(mesh);
393
+ }
394
+ this.villagerMeshes.clear();
395
+
396
+ // Clear path lines
397
+ for (const [id, line] of this.pathLines) {
398
+ this.scene.remove(line);
399
+ }
400
+ this.pathLines.clear();
401
+
402
+ // Reset AI system
403
+ this.aiSystem = new VillageAISystem(this.scene);
404
+ this.createEnvironment();
405
+
406
+ // Clear selection
407
+ this.selectedVillager = null;
408
+ this.updateVillagerInfo();
409
+
410
+ this.updateVillagerCount();
411
+ }
412
+
413
+ /**
414
+ * Update time speed
415
+ */
416
+ updateTimeSpeed(speed) {
417
+ this.timeSpeed = parseFloat(speed);
418
+ this.uiElements.timeSpeedDisplay.textContent = `${speed}x`;
419
+ }
420
+
421
+ /**
422
+ * Toggle path visibility
423
+ */
424
+ togglePaths(show) {
425
+ this.showPaths = show;
426
+ for (const [id, line] of this.pathLines) {
427
+ line.visible = show;
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Handle window resize
433
+ */
434
+ onWindowResize() {
435
+ this.camera.aspect = window.innerWidth / window.innerHeight;
436
+ this.camera.updateProjectionMatrix();
437
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
438
+ }
439
+
440
+ /**
441
+ * Handle keyboard input
442
+ */
443
+ onKeyDown(event) {
444
+ console.log('Key pressed:', event.code);
445
+ const speed = 0.5;
446
+
447
+ // Handle keyboard input directly for camera movement
448
+ // Even when OrbitControls are available, we want to handle WASD keys
449
+ switch (event.code) {
450
+ case 'KeyW':
451
+ this.camera.position.z -= speed;
452
+ this.camera.lookAt(0, 0, 0);
453
+ console.log('Moved camera forward');
454
+ break;
455
+ case 'KeyS':
456
+ this.camera.position.z += speed;
457
+ this.camera.lookAt(0, 0, 0);
458
+ console.log('Moved camera backward');
459
+ break;
460
+ case 'KeyA':
461
+ this.camera.position.x -= speed;
462
+ this.camera.lookAt(0, 0, 0);
463
+ console.log('Moved camera left');
464
+ break;
465
+ case 'KeyD':
466
+ this.camera.position.x += speed;
467
+ this.camera.lookAt(0, 0, 0);
468
+ console.log('Moved camera right');
469
+ break;
470
+ case 'Space':
471
+ this.camera.position.y += speed;
472
+ this.camera.lookAt(0, 0, 0);
473
+ console.log('Moved camera up');
474
+ break;
475
+ case 'ShiftLeft':
476
+ this.camera.position.y -= speed;
477
+ this.camera.lookAt(0, 0, 0);
478
+ console.log('Moved camera down');
479
+ break;
480
+ }
481
+ }
482
+
483
+ /**
484
+ * Handle mouse clicks for villager selection
485
+ */
486
+ onMouseClick(event) {
487
+ const mouse = { x: 0, y: 0 };
488
+ mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
489
+ mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
490
+
491
+ const raycaster = new THREE.Raycaster();
492
+ raycaster.setFromCamera(mouse, this.camera);
493
+
494
+ const villagerMeshes = Array.from(this.villagerMeshes.values());
495
+ const intersects = raycaster.intersectObjects(villagerMeshes);
496
+
497
+ if (intersects.length > 0) {
498
+ const selectedMesh = intersects[0].object;
499
+ this.selectedVillager = selectedMesh.userData.villager;
500
+ this.updateVillagerInfo();
501
+ }
502
+ }
503
+
504
+ /**
505
+ * Update villager count display
506
+ */
507
+ updateVillagerCount() {
508
+ const count = this.villagerMeshes.size;
509
+ this.uiElements.villagerCountDisplay.textContent = count;
510
+ this.uiElements.villagerCountStat.textContent = count;
511
+ }
512
+
513
+ /**
514
+ * Update building count display
515
+ */
516
+ updateBuildingCount() {
517
+ const count = this.buildingMeshes.size;
518
+ this.uiElements.buildingCount.textContent = count;
519
+ }
520
+
521
+ /**
522
+ * Update resource count display
523
+ */
524
+ updateResourceCount() {
525
+ const count = this.resourceMeshes.size;
526
+ this.uiElements.resourceCount.textContent = count;
527
+ }
528
+
529
+ /**
530
+ * Update villager information panel
531
+ */
532
+ updateVillagerInfo() {
533
+ const villagerList = this.uiElements.villagerList;
534
+
535
+ if (!this.selectedVillager) {
536
+ villagerList.innerHTML = '<p>No villager selected</p>';
537
+ return;
538
+ }
539
+
540
+ const villager = this.selectedVillager;
541
+ villagerList.innerHTML = `
542
+ <div class="villager-item selected">
543
+ <div><strong>${villager.id}</strong></div>
544
+ <div>State: <span class="state-indicator state-${villager.state}"></span>${villager.state}</div>
545
+ <div>Position: (${villager.position[0].toFixed(1)}, ${villager.position[1].toFixed(1)}, ${villager.position[2].toFixed(1)})</div>
546
+ <div>Energy: ${villager.energy.toFixed(1)}%</div>
547
+ <div>Hunger: ${villager.hunger.toFixed(1)}%</div>
548
+ <div>Social Need: ${villager.socialNeed.toFixed(1)}%</div>
549
+ <div>Path Points: ${villager.path.length}</div>
550
+ </div>
551
+ `;
552
+ }
553
+
554
+ /**
555
+ * Update path visualization for a villager
556
+ */
557
+ updatePathVisualization(villager) {
558
+ const villagerId = villager.id;
559
+
560
+ // Remove existing path line
561
+ if (this.pathLines.has(villagerId)) {
562
+ this.scene.remove(this.pathLines.get(villagerId));
563
+ this.pathLines.delete(villagerId);
564
+ }
565
+
566
+ // Create new path line if villager has a path
567
+ if (villager.path.length > 1) {
568
+ const geometry = new THREE.BufferGeometry();
569
+ const positions = [];
570
+
571
+ // Add current position
572
+ positions.push(villager.position[0], 0.1, villager.position[2]);
573
+
574
+ // Add path points
575
+ for (const point of villager.path) {
576
+ positions.push(point[0], 0.1, point[2]);
577
+ }
578
+
579
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
580
+
581
+ const material = new THREE.LineBasicMaterial({
582
+ color: this.getStateColor(villager.state),
583
+ linewidth: 3
584
+ });
585
+
586
+ const line = new THREE.Line(geometry, material);
587
+ line.visible = this.showPaths;
588
+
589
+ this.pathLines.set(villagerId, line);
590
+ this.scene.add(line);
591
+ }
592
+ }
593
+
594
+ /**
595
+ * Update game time display
596
+ */
597
+ updateGameTime() {
598
+ const time = this.aiSystem.routineManager.currentTime;
599
+ const hours = Math.floor(time);
600
+ const minutes = Math.floor((time - hours) * 60);
601
+ this.uiElements.gameTime.textContent = `${hours}:${minutes.toString().padStart(2, '0')}`;
602
+ }
603
+
604
+ /**
605
+ * Update FPS counter
606
+ */
607
+ updateFPS() {
608
+ this.frameCount++;
609
+ const currentTime = performance.now();
610
+
611
+ if (currentTime - this.lastTime >= 1000) {
612
+ this.fps = Math.round(this.frameCount * 1000 / (currentTime - this.lastTime));
613
+ this.frameCount = 0;
614
+ this.lastTime = currentTime;
615
+ this.uiElements.fps.textContent = this.fps;
616
+ }
617
+ }
618
+
619
+ /**
620
+ * Animation loop
621
+ */
622
+ animate() {
623
+ requestAnimationFrame(() => this.animate());
624
+
625
+ const deltaTime = this.clock.getDelta() * this.timeSpeed;
626
+
627
+ // Update AI system
628
+ this.aiSystem.update(deltaTime);
629
+
630
+ // Update 3D visualization
631
+ this.updateVillagerMeshes();
632
+ this.updatePathVisualizations();
633
+
634
+ // Update UI
635
+ this.updateGameTime();
636
+ this.updateFPS();
637
+ this.updateVillagerInfo();
638
+
639
+ // Update controls
640
+ if (this.controls) {
641
+ // console.log('Updating controls');
642
+ this.controls.update();
643
+ }
644
+
645
+ // Render scene
646
+ if (this.renderer && this.scene && this.camera) {
647
+ this.renderer.render(this.scene, this.camera);
648
+ } else {
649
+ console.error('Missing renderer, scene, or camera for rendering');
650
+ }
651
+ }
652
+
653
+ /**
654
+ * Update villager mesh positions and colors
655
+ */
656
+ updateVillagerMeshes() {
657
+ for (const [villagerId, mesh] of this.villagerMeshes) {
658
+ const villager = mesh.userData.villager;
659
+
660
+ // Update position
661
+ mesh.position.set(villager.position[0], villager.position[1], villager.position[2]);
662
+ mesh.position.y = 0.5;
663
+
664
+ // Update color based on state
665
+ const material = mesh.material;
666
+ const newColor = this.getStateColor(villager.state);
667
+ if (material.color.getHex() !== newColor) {
668
+ material.color.setHex(newColor);
669
+ }
670
+ }
671
+ }
672
+
673
+ /**
674
+ * Update all path visualizations
675
+ */
676
+ updatePathVisualizations() {
677
+ for (const [villagerId, mesh] of this.villagerMeshes) {
678
+ const villager = mesh.userData.villager;
679
+ this.updatePathVisualization(villager);
680
+ }
681
+ }
682
+ }
683
+
684
+ // Initialize the application when the page loads
685
+ document.addEventListener('DOMContentLoaded', () => {
686
+ const app = new VillageVisualizationApp();
687
+ });
app_new.js ADDED
@@ -0,0 +1,1798 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Medieval Village AI System - Three.js Visualization Application
2
+ // Using globally available THREE from script tag in index.html
3
+ import VillageAISystem from './src/ai/main.js';
4
+ import LLMHandler from './src/ai/llmHandler.js';
5
+
6
+ class VillageVisualizationApp {
7
+ constructor(hfToken = null) {
8
+ this.scene = null;
9
+ this.camera = null;
10
+ this.renderer = null;
11
+ this.controls = null;
12
+ this.aiSystem = null;
13
+
14
+ // 3D Objects
15
+ this.villagerMeshes = new Map();
16
+ this.buildingMeshes = new Map();
17
+ this.resourceMeshes = new Map();
18
+ this.pathLines = new Map();
19
+
20
+ // UI Elements
21
+ this.uiElements = {};
22
+ this.selectedVillager = null;
23
+ this.timeSpeed = 1.0;
24
+ this.showPaths = true;
25
+ this.showTitles = true;
26
+
27
+ // LLM System
28
+ this.llmHandler = new LLMHandler(hfToken);
29
+ this.llmConnected = false;
30
+
31
+ // Log token status for debugging
32
+ console.log('LLM Handler initialized with token:', hfToken ? 'Set' : 'Not set');
33
+ if (hfToken) {
34
+ console.log('Token length:', hfToken.length);
35
+ }
36
+
37
+ // Initialize LLM status indicator to red (disconnected)
38
+ this.updateLLMStatusIndicator(false);
39
+
40
+ // New systems
41
+ this.weatherSystem = {
42
+ fogIntensity: 50,
43
+ currentWeather: 'sun',
44
+ rainParticles: [],
45
+ snowParticles: []
46
+ };
47
+
48
+ this.disasterSystem = {
49
+ activeDisasters: new Map(),
50
+ fireEffects: [],
51
+ floodEffects: []
52
+ };
53
+
54
+ this.animalSystem = {
55
+ animals: new Map(),
56
+ beasts: new Map()
57
+ };
58
+
59
+ this.warriorSystem = {
60
+ warriors: new Map(),
61
+ dispatched: false
62
+ };
63
+
64
+ // Animation
65
+ this.clock = new THREE.Clock();
66
+ this.lastTime = 0;
67
+ this.frameCount = 0;
68
+ this.fps = 0;
69
+
70
+ this.init();
71
+ }
72
+
73
+ init() {
74
+ console.log('Initializing Village Visualization App...');
75
+ this.initThreeJS();
76
+ this.initAI();
77
+ this.initUI();
78
+ this.createEnvironment();
79
+ this.createInitialVillagers();
80
+ this.animate();
81
+ console.log('Village Visualization App initialized successfully');
82
+ }
83
+
84
+ initThreeJS() {
85
+ console.log('Initializing Three.js...');
86
+
87
+ // Scene
88
+ this.scene = new THREE.Scene();
89
+ // Create a more realistic sky gradient background
90
+ this.scene.background = new THREE.Color(0x87CEEB);
91
+ console.log('Scene created');
92
+
93
+ // Camera
94
+ this.camera = new THREE.PerspectiveCamera(
95
+ 75,
96
+ window.innerWidth / window.innerHeight,
97
+ 0.1,
98
+ 1000
99
+ );
100
+ this.camera.position.set(15, 12, 15);
101
+ this.camera.lookAt(0, 0, 0);
102
+ console.log('Camera created');
103
+
104
+ // Renderer
105
+ this.renderer = new THREE.WebGLRenderer({ antialias: true });
106
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
107
+ this.renderer.shadowMap.enabled = true;
108
+ this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
109
+ this.renderer.setClearColor(0x87CEEB, 1); // Set clear color to sky blue
110
+ this.renderer.gammaOutput = true;
111
+ this.renderer.gammaFactor = 2.2;
112
+ console.log('Renderer created');
113
+
114
+ // Add to DOM
115
+ const container = document.getElementById('container');
116
+ if (container) {
117
+ container.appendChild(this.renderer.domElement);
118
+ console.log('Renderer added to DOM');
119
+ } else {
120
+ console.error('Container element not found!');
121
+ }
122
+
123
+ // Lighting
124
+ this.addLighting();
125
+
126
+ // Ground plane
127
+ this.createGround();
128
+
129
+ // Grid helper
130
+ const gridHelper = new THREE.GridHelper(100, 100, 0x444444, 0x222222);
131
+ this.scene.add(gridHelper);
132
+
133
+ // Controls
134
+ console.log('Initializing controls...');
135
+ console.log('THREE.OrbitControls:', typeof THREE !== 'undefined' ? THREE.OrbitControls : 'undefined');
136
+ try {
137
+ // Check if OrbitControls is available globally
138
+ if (typeof THREE !== 'undefined' && typeof THREE.OrbitControls !== 'undefined') {
139
+ console.log('Creating OrbitControls instance from global THREE object...');
140
+ this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
141
+ console.log('OrbitControls instance created:', this.controls);
142
+ this.controls.enableDamping = true;
143
+ this.controls.dampingFactor = 0.05;
144
+ this.controls.enableZoom = true;
145
+ this.controls.enablePan = true;
146
+ console.log('OrbitControls initialized successfully');
147
+ } else {
148
+ console.warn('OrbitControls is not available');
149
+ this.controls = null;
150
+ }
151
+ } catch (error) {
152
+ console.warn('Error initializing OrbitControls:', error);
153
+ this.controls = null;
154
+ }
155
+
156
+ // Handle window resize
157
+ window.addEventListener('resize', () => this.onWindowResize());
158
+ window.addEventListener('keydown', (event) => this.onKeyDown(event));
159
+
160
+ // Handle mouse clicks for object selection
161
+ this.renderer.domElement.addEventListener('click', (event) => this.onMouseClick(event));
162
+
163
+ // Configure OrbitControls to work properly with object selection
164
+ if (this.controls) {
165
+ // Enable all controls but make sure they don't interfere with clicks
166
+ this.controls.enableRotate = true;
167
+ this.controls.enableZoom = true;
168
+ this.controls.enablePan = true;
169
+
170
+ // Disable keyboard navigation in OrbitControls to avoid conflicts
171
+ this.controls.enableKeys = false;
172
+
173
+ // Use damping for smoother controls
174
+ this.controls.enableDamping = true;
175
+ this.controls.dampingFactor = 0.05;
176
+ }
177
+ }
178
+
179
+ addLighting() {
180
+ // Ambient light
181
+ const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
182
+ this.scene.add(ambientLight);
183
+
184
+ // Directional light (sun)
185
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
186
+ directionalLight.position.set(10, 15, 5);
187
+ directionalLight.castShadow = true;
188
+ directionalLight.shadow.mapSize.width = 2048;
189
+ directionalLight.shadow.mapSize.height = 2048;
190
+ directionalLight.shadow.camera.near = 0.5;
191
+ directionalLight.shadow.camera.far = 50;
192
+ directionalLight.shadow.camera.left = -20;
193
+ directionalLight.shadow.camera.right = 20;
194
+ directionalLight.shadow.camera.top = 20;
195
+ directionalLight.shadow.camera.bottom = -20;
196
+ this.scene.add(directionalLight);
197
+
198
+ // Add a fill light
199
+ const fillLight = new THREE.DirectionalLight(0xffffff, 0.3);
200
+ fillLight.position.set(-10, 5, -10);
201
+ this.scene.add(fillLight);
202
+
203
+ // Add a hemisphere light for more natural outdoor lighting
204
+ const hemisphereLight = new THREE.HemisphereLight(0x87CEEB, 0x3a5f3a, 0.2);
205
+ this.scene.add(hemisphereLight);
206
+ }
207
+
208
+ createGround() {
209
+ // Create a more detailed ground with texture
210
+ const groundGeometry = new THREE.PlaneGeometry(100, 100, 20, 20);
211
+
212
+ // Create a more realistic ground material
213
+ const groundMaterial = new THREE.MeshLambertMaterial({
214
+ color: 0x3a5f3a,
215
+ wireframe: false
216
+ });
217
+
218
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
219
+ ground.rotation.x = -Math.PI / 2;
220
+ ground.receiveShadow = true;
221
+
222
+ // Add some variation to the ground
223
+ const vertices = ground.geometry.attributes.position.array;
224
+ for (let i = 0; i < vertices.length; i += 3) {
225
+ // Add some noise to the y position for a more natural look
226
+ vertices[i + 1] = (Math.random() - 0.5) * 0.5;
227
+ }
228
+ ground.geometry.attributes.position.needsUpdate = true;
229
+ ground.geometry.computeVertexNormals();
230
+
231
+
232
+ this.scene.add(ground);
233
+
234
+ // Add fog to the scene for a more realistic atmosphere
235
+ this.scene.fog = new THREE.Fog(0x87CEEB, 20, 50);
236
+ }
237
+ initAI() {
238
+ this.aiSystem = new VillageAISystem(this.scene);
239
+ }
240
+
241
+ initUI() {
242
+ // Get UI elements
243
+ this.uiElements = {
244
+ addVillagerBtn: document.getElementById('add-villager-btn'),
245
+ resetBtn: document.getElementById('reset-btn'),
246
+ timeSpeed: document.getElementById('time-speed'),
247
+ timeSpeedDisplay: document.getElementById('time-speed-display'),
248
+ showPaths: document.getElementById('show-paths'),
249
+ showTitles: document.getElementById('show-titles'),
250
+ fogControl: document.getElementById('fog-control'),
251
+ weatherSun: document.getElementById('weather-sun'),
252
+ weatherRain: document.getElementById('weather-rain'),
253
+ weatherSnow: document.getElementById('weather-snow'),
254
+ disasterFire: document.getElementById('disaster-fire'),
255
+ disasterHurricane: document.getElementById('disaster-hurricane'),
256
+ disasterFlood: document.getElementById('disaster-flood'),
257
+ disasterEarthquake: document.getElementById('disaster-earthquake'),
258
+ disasterPlague: document.getElementById('disaster-plague'),
259
+ spawnWolf: document.getElementById('spawn-wolf'),
260
+ spawnBear: document.getElementById('spawn-bear'),
261
+ spawnDragon: document.getElementById('spawn-dragon'),
262
+ addWarrior: document.getElementById('add-warrior'),
263
+ dispatchWarriors: document.getElementById('dispatch-warriors'),
264
+ villagerCountDisplay: document.getElementById('villager-count-display'),
265
+ villagerCountStat: document.getElementById('villager-count-stat'),
266
+ gameTime: document.getElementById('game-time'),
267
+ fps: document.getElementById('fps'),
268
+ buildingCount: document.getElementById('building-count'),
269
+ resourceCount: document.getElementById('resource-count'),
270
+ villagerList: document.getElementById('villager-list'),
271
+ // LLM UI elements
272
+ llmModel: document.getElementById('llm-model'),
273
+ llmQuery: document.getElementById('llm-query'),
274
+ llmSubmit: document.getElementById('llm-submit'),
275
+ llmResponse: document.getElementById('llm-response')
276
+ };
277
+
278
+ // Add event listeners
279
+ if (this.uiElements.addVillagerBtn) {
280
+ this.uiElements.addVillagerBtn.addEventListener('click', () => this.addVillager());
281
+ }
282
+ if (this.uiElements.resetBtn) {
283
+ this.uiElements.resetBtn.addEventListener('click', () => this.resetSimulation());
284
+ }
285
+ if (this.uiElements.timeSpeed) {
286
+ this.uiElements.timeSpeed.addEventListener('input', (e) => this.updateTimeSpeed(e.target.value));
287
+ }
288
+ if (this.uiElements.showPaths) {
289
+ this.uiElements.showPaths.addEventListener('change', (e) => this.togglePaths(e.target.checked));
290
+ }
291
+ if (this.uiElements.showTitles) {
292
+ this.uiElements.showTitles.addEventListener('change', (e) => this.toggleTitles(e.target.checked));
293
+ }
294
+
295
+ // Weather controls
296
+ if (this.uiElements.fogControl) {
297
+ this.uiElements.fogControl.addEventListener('input', (e) => this.updateFog(e.target.value));
298
+ }
299
+ if (this.uiElements.weatherSun) {
300
+ this.uiElements.weatherSun.addEventListener('click', () => this.setWeather('sun'));
301
+ }
302
+ if (this.uiElements.weatherRain) {
303
+ this.uiElements.weatherRain.addEventListener('click', () => this.setWeather('rain'));
304
+ }
305
+ if (this.uiElements.weatherSnow) {
306
+ this.uiElements.weatherSnow.addEventListener('click', () => this.setWeather('snow'));
307
+ }
308
+
309
+ // Disaster controls
310
+ if (this.uiElements.disasterFire) {
311
+ this.uiElements.disasterFire.addEventListener('click', () => this.triggerDisaster('fire'));
312
+ }
313
+ if (this.uiElements.disasterHurricane) {
314
+ this.uiElements.disasterHurricane.addEventListener('click', () => this.triggerDisaster('hurricane'));
315
+ }
316
+ if (this.uiElements.disasterFlood) {
317
+ this.uiElements.disasterFlood.addEventListener('click', () => this.triggerDisaster('flood'));
318
+ }
319
+ if (this.uiElements.disasterEarthquake) {
320
+ this.uiElements.disasterEarthquake.addEventListener('click', () => this.triggerDisaster('earthquake'));
321
+ }
322
+ if (this.uiElements.disasterPlague) {
323
+ this.uiElements.disasterPlague.addEventListener('click', () => this.triggerDisaster('plague'));
324
+ }
325
+
326
+ // Animal/Beast controls
327
+ if (this.uiElements.spawnWolf) {
328
+ this.uiElements.spawnWolf.addEventListener('click', () => this.spawnAnimal('wolf'));
329
+ }
330
+ if (this.uiElements.spawnBear) {
331
+ this.uiElements.spawnBear.addEventListener('click', () => this.spawnAnimal('bear'));
332
+ }
333
+ if (this.uiElements.spawnDragon) {
334
+ this.uiElements.spawnDragon.addEventListener('click', () => this.spawnAnimal('dragon'));
335
+ }
336
+
337
+ // Warrior controls
338
+ if (this.uiElements.addWarrior) {
339
+ this.uiElements.addWarrior.addEventListener('click', () => this.addWarrior());
340
+ }
341
+ if (this.uiElements.dispatchWarriors) {
342
+ this.uiElements.dispatchWarriors.addEventListener('click', () => this.dispatchWarriors());
343
+ }
344
+
345
+ // LLM event listeners
346
+ if (this.uiElements.llmModel) {
347
+ this.uiElements.llmModel.addEventListener('change', (e) => this.handleLLMModelChange(e.target.value));
348
+ }
349
+ if (this.uiElements.llmQuery) {
350
+ this.uiElements.llmQuery.addEventListener('keypress', (e) => {
351
+ if (e.key === 'Enter') {
352
+ this.handleLLMQuerySubmit();
353
+ }
354
+ });
355
+ }
356
+ if (this.uiElements.llmSubmit) {
357
+ this.uiElements.llmSubmit.addEventListener('click', () => this.handleLLMQuerySubmit());
358
+ }
359
+
360
+ // Test LLM connection
361
+ this.testLLMConnection();
362
+ }
363
+
364
+ /**
365
+ * Update the LLM status indicator
366
+ * @param {boolean} connected - Whether the LLM is connected
367
+ */
368
+ updateLLMStatusIndicator(connected) {
369
+ this.llmConnected = connected;
370
+ const indicator = document.getElementById('llm-status-indicator');
371
+ if (indicator) {
372
+ indicator.style.backgroundColor = connected ? 'green' : 'red';
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Test the LLM connection with an initial prompt
378
+ */
379
+ async testLLMConnection() {
380
+ // Log token status for debugging
381
+ console.log('Testing LLM connection, token set:', this.llmHandler.isApiTokenSet());
382
+ if (this.llmHandler.isApiTokenSet()) {
383
+ console.log('Token length:', this.llmHandler.getApiToken().length);
384
+ }
385
+
386
+ // Check if API token is set
387
+ if (!this.llmHandler.isApiTokenSet()) {
388
+ // Update status indicator to red (disconnected)
389
+ this.updateLLMStatusIndicator(false);
390
+
391
+ // Update UI with error message
392
+ if (this.uiElements.llmResponse) {
393
+ this.uiElements.llmResponse.textContent = "Hugging Face API token not set. Please set the HF_TOKEN environment variable or use the browser console to set the token.";
394
+ }
395
+ return;
396
+ }
397
+
398
+ // Update UI to show testing state
399
+ if (this.uiElements.llmResponse) {
400
+ this.uiElements.llmResponse.textContent = 'Testing LLM connection...';
401
+ }
402
+
403
+ try {
404
+ // Send an initial prompt to test the connection
405
+ const initialPrompt = "Provide a brief summary of a medieval village simulation with AI-controlled villagers. Include information about villager behaviors, resource management, and building types.";
406
+ const response = await this.llmHandler.sendQuery(initialPrompt);
407
+
408
+ // Update UI with response
409
+ if (this.uiElements.llmResponse) {
410
+ this.uiElements.llmResponse.textContent = response;
411
+ }
412
+
413
+ // Update status indicator to green (connected)
414
+ this.updateLLMStatusIndicator(true);
415
+ } catch (error) {
416
+ console.error('Error testing LLM connection:', error);
417
+
418
+ // Update status indicator to red (disconnected)
419
+ this.updateLLMStatusIndicator(false);
420
+
421
+ // Update UI with error message
422
+ if (this.uiElements.llmResponse) {
423
+ if (error.message.includes("API token is not set")) {
424
+ this.uiElements.llmResponse.textContent = "Please set your Hugging Face API token by setting the HF_TOKEN environment variable or using the browser console.";
425
+ } else {
426
+ this.uiElements.llmResponse.textContent = 'Error connecting to LLM: ' + error.message;
427
+ }
428
+ }
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Handle LLM model change
434
+ * @param {string} model - The selected model
435
+ */
436
+ handleLLMModelChange(model) {
437
+ console.log('LLM model changed to:', model);
438
+ this.llmHandler.setSelectedModel(model);
439
+ }
440
+
441
+ /**
442
+ * Handle LLM query submission
443
+ */
444
+ async handleLLMQuerySubmit() {
445
+ if (!this.uiElements.llmQuery || !this.uiElements.llmResponse) return;
446
+
447
+ const query = this.uiElements.llmQuery.value.trim();
448
+ if (!query) return;
449
+
450
+ console.log('Submitting LLM query:', query);
451
+ console.log('LLM Handler token set:', this.llmHandler.isApiTokenSet());
452
+ if (this.llmHandler.isApiTokenSet()) {
453
+ console.log('Token length:', this.llmHandler.getApiToken().length);
454
+ }
455
+
456
+ // Update UI to show loading state
457
+ this.uiElements.llmResponse.textContent = 'Processing your query...';
458
+ this.uiElements.llmSubmit.disabled = true;
459
+
460
+ try {
461
+ // Send query to LLM handler
462
+ const response = await this.llmHandler.sendQuery(query);
463
+
464
+ // Update UI with response
465
+ this.uiElements.llmResponse.textContent = response;
466
+
467
+ // Update status indicator to green (connected)
468
+ this.updateLLMStatusIndicator(true);
469
+ } catch (error) {
470
+ console.error('Error processing LLM query:', error);
471
+ if (error.message.includes("API token is not set")) {
472
+ this.uiElements.llmResponse.textContent = "Please set your Hugging Face API token in the HTML file to enable LLM functionality. Get one from https://huggingface.co/settings/tokens";
473
+ // Update status indicator to red (disconnected)
474
+ this.updateLLMStatusIndicator(false);
475
+ } else {
476
+ this.uiElements.llmResponse.textContent = 'Error: ' + error.message;
477
+ // Update status indicator to red (disconnected) if it's a connection error
478
+ if (error.message.includes("API request failed")) {
479
+ this.updateLLMStatusIndicator(false);
480
+ }
481
+ }
482
+ } finally {
483
+ // Re-enable submit button
484
+ this.uiElements.llmSubmit.disabled = false;
485
+ }
486
+ }
487
+
488
+ createEnvironment() {
489
+ // Buildings are already created in the AI system
490
+ this.createBuildingMeshes();
491
+ this.createResourceMeshes();
492
+ this.createRoads();
493
+ this.createTrees();
494
+ console.log('Environment created with roads and trees');
495
+ }
496
+
497
+ createBuildingMeshes() {
498
+ if (this.aiSystem && this.aiSystem.environmentSystem) {
499
+ for (const [id, building] of this.aiSystem.environmentSystem.buildings) {
500
+ let mesh = null;
501
+
502
+ // Create unique geometry and materials for each building type
503
+ switch (building.type) {
504
+ case 'house':
505
+ // Cozy cottage with sloped roof
506
+ const houseGroup = new THREE.Group();
507
+
508
+ const houseBase = new THREE.Mesh(
509
+ new THREE.BoxGeometry(3, 2, 3),
510
+ new THREE.MeshLambertMaterial({ color: 0xD2691E })
511
+ );
512
+ houseBase.position.y = 1;
513
+
514
+ const houseRoof = new THREE.Mesh(
515
+ new THREE.ConeGeometry(2.5, 1.5, 4),
516
+ new THREE.MeshLambertMaterial({ color: 0x8B4513 })
517
+ );
518
+ houseRoof.position.y = 2.75;
519
+
520
+ houseGroup.add(houseBase);
521
+ houseGroup.add(houseRoof);
522
+ mesh = houseGroup;
523
+ mesh.position.y = 0;
524
+ break;
525
+
526
+ case 'workshop':
527
+ // Industrial workshop with chimney
528
+ const workshopGroup = new THREE.Group();
529
+
530
+ const workshopBase = new THREE.Mesh(
531
+ new THREE.BoxGeometry(4, 3, 4),
532
+ new THREE.MeshLambertMaterial({ color: 0x708090 })
533
+ );
534
+ workshopBase.position.y = 1.5;
535
+
536
+ const chimney = new THREE.Mesh(
537
+ new THREE.CylinderGeometry(0.3, 0.3, 2),
538
+ new THREE.MeshLambertMaterial({ color: 0x696969 })
539
+ );
540
+ chimney.position.set(1.5, 3, 0);
541
+
542
+ workshopGroup.add(workshopBase);
543
+ workshopGroup.add(chimney);
544
+ mesh = workshopGroup;
545
+ mesh.position.y = 0;
546
+ break;
547
+
548
+ case 'market':
549
+ // Large marketplace with dome
550
+ const marketGroup = new THREE.Group();
551
+
552
+ const marketBase = new THREE.Mesh(
553
+ new THREE.CylinderGeometry(5, 5, 2, 32),
554
+ new THREE.MeshLambertMaterial({ color: 0xFFD700 })
555
+ );
556
+ marketBase.position.y = 1;
557
+
558
+ const marketDome = new THREE.Mesh(
559
+ new THREE.SphereGeometry(3, 16, 8, 0, Math.PI * 2, 0, Math.PI / 2),
560
+ new THREE.MeshLambertMaterial({ color: 0xFFA500 })
561
+ );
562
+ marketDome.position.y = 3.5;
563
+
564
+ marketGroup.add(marketBase);
565
+ marketGroup.add(marketDome);
566
+ mesh = marketGroup;
567
+ mesh.position.y = 0;
568
+ break;
569
+
570
+ case 'university':
571
+ // Academic building with tower and columns
572
+ const universityGroup = new THREE.Group();
573
+
574
+ const uniBase = new THREE.Mesh(
575
+ new THREE.BoxGeometry(6, 4, 5),
576
+ new THREE.MeshLambertMaterial({ color: 0x4169E1 })
577
+ );
578
+ uniBase.position.y = 2;
579
+
580
+ const uniTower = new THREE.Mesh(
581
+ new THREE.CylinderGeometry(1.5, 1.5, 6),
582
+ new THREE.MeshLambertMaterial({ color: 0x1E90FF })
583
+ );
584
+ uniTower.position.set(2, 5, 0);
585
+
586
+ // Add columns
587
+ for (let i = -2; i <= 2; i += 2) {
588
+ const column = new THREE.Mesh(
589
+ new THREE.CylinderGeometry(0.3, 0.3, 3),
590
+ new THREE.MeshLambertMaterial({ color: 0xF5F5F5 })
591
+ );
592
+ column.position.set(i, 1.5, 2);
593
+ universityGroup.add(column);
594
+ }
595
+
596
+ universityGroup.add(uniBase);
597
+ universityGroup.add(uniTower);
598
+ mesh = universityGroup;
599
+ mesh.position.y = 0;
600
+ break;
601
+
602
+ case 'store':
603
+ // Modern store with large windows
604
+ const storeGroup = new THREE.Group();
605
+
606
+ const storeBase = new THREE.Mesh(
607
+ new THREE.BoxGeometry(5, 3, 4),
608
+ new THREE.MeshLambertMaterial({ color: 0x32CD32 })
609
+ );
610
+ storeBase.position.y = 1.5;
611
+
612
+ // Add windows
613
+ const windowMaterial = new THREE.MeshLambertMaterial({ color: 0x87CEEB });
614
+ for (let i = -1.5; i <= 1.5; i += 1.5) {
615
+ const window = new THREE.Mesh(
616
+ new THREE.PlaneGeometry(1, 1),
617
+ windowMaterial
618
+ );
619
+ window.position.set(i, 1.5, 2.01);
620
+ storeGroup.add(window);
621
+ }
622
+
623
+ storeGroup.add(storeBase);
624
+ mesh = storeGroup;
625
+ mesh.position.y = 0;
626
+ break;
627
+
628
+ case 'bank':
629
+ // Impressive bank building
630
+ const bankGroup = new THREE.Group();
631
+
632
+ const bankBase = new THREE.Mesh(
633
+ new THREE.BoxGeometry(6, 5, 5),
634
+ new THREE.MeshLambertMaterial({ color: 0xC0C0C0 })
635
+ );
636
+ bankBase.position.y = 2.5;
637
+
638
+ // Add pillars
639
+ for (let i = -2; i <= 2; i += 2) {
640
+ const pillar = new THREE.Mesh(
641
+ new THREE.CylinderGeometry(0.4, 0.4, 4),
642
+ new THREE.MeshLambertMaterial({ color: 0xF5F5F5 })
643
+ );
644
+ pillar.position.set(i, 2, 2.5);
645
+ bankGroup.add(pillar);
646
+ }
647
+
648
+ bankGroup.add(bankBase);
649
+ mesh = bankGroup;
650
+ mesh.position.y = 0;
651
+ break;
652
+
653
+ case 'hospital':
654
+ // Medical facility with cross
655
+ const hospitalGroup = new THREE.Group();
656
+
657
+ const hospitalBase = new THREE.Mesh(
658
+ new THREE.BoxGeometry(7, 5, 5),
659
+ new THREE.MeshLambertMaterial({ color: 0xFF0000 })
660
+ );
661
+ hospitalBase.position.y = 2.5;
662
+
663
+ // Medical cross
664
+ const crossVertical = new THREE.Mesh(
665
+ new THREE.BoxGeometry(0.3, 2, 0.3),
666
+ new THREE.MeshLambertMaterial({ color: 0xFFFFFF })
667
+ );
668
+ crossVertical.position.set(0, 4.5, 2.5);
669
+
670
+ const crossHorizontal = new THREE.Mesh(
671
+ new THREE.BoxGeometry(1.5, 0.3, 0.3),
672
+ new THREE.MeshLambertMaterial({ color: 0xFFFFFF })
673
+ );
674
+ crossHorizontal.position.set(0, 4.5, 2.5);
675
+
676
+ hospitalGroup.add(hospitalBase);
677
+ hospitalGroup.add(crossVertical);
678
+ hospitalGroup.add(crossHorizontal);
679
+ mesh = hospitalGroup;
680
+ mesh.position.y = 0;
681
+ break;
682
+
683
+ case 'restaurant':
684
+ // Fancy restaurant with unique shape
685
+ const restaurantGroup = new THREE.Group();
686
+
687
+ const restaurantBase = new THREE.Mesh(
688
+ new THREE.CylinderGeometry(4, 4, 3, 32),
689
+ new THREE.MeshLambertMaterial({ color: 0xFF6347 })
690
+ );
691
+ restaurantBase.position.y = 1.5;
692
+
693
+ const restaurantRoof = new THREE.Mesh(
694
+ new THREE.ConeGeometry(3.5, 2, 8),
695
+ new THREE.MeshLambertMaterial({ color: 0x8B0000 })
696
+ );
697
+ restaurantRoof.position.y = 3.5;
698
+
699
+ restaurantGroup.add(restaurantBase);
700
+ restaurantGroup.add(restaurantRoof);
701
+ mesh = restaurantGroup;
702
+ mesh.position.y = 0;
703
+ break;
704
+
705
+ default:
706
+ // Default building
707
+ mesh = new THREE.Mesh(
708
+ new THREE.BoxGeometry(3, 3, 3),
709
+ new THREE.MeshLambertMaterial({ color: 0x8B4513 })
710
+ );
711
+ mesh.position.y = 1.5;
712
+ }
713
+
714
+ mesh.position.set(building.position[0], building.position[1], building.position[2]);
715
+ mesh.castShadow = true;
716
+ mesh.receiveShadow = true;
717
+
718
+ this.buildingMeshes.set(id, mesh);
719
+ this.scene.add(mesh);
720
+ }
721
+ }
722
+
723
+ this.updateBuildingCount();
724
+ }
725
+
726
+ createResourceMeshes() {
727
+ const resourceGeometry = new THREE.CylinderGeometry(0.5, 0.5, 2);
728
+ const materials = {
729
+ wood: new THREE.MeshLambertMaterial({ color: 0x8B4513 }),
730
+ stone: new THREE.MeshLambertMaterial({ color: 0x708090 }),
731
+ food: new THREE.MeshLambertMaterial({ color: 0x32CD32 })
732
+ };
733
+
734
+ if (this.aiSystem && this.aiSystem.environmentSystem) {
735
+ for (const [id, resource] of this.aiSystem.environmentSystem.resources) {
736
+ const material = materials[resource.type] || materials.wood;
737
+ const mesh = new THREE.Mesh(resourceGeometry, material);
738
+ mesh.position.set(resource.position[0], resource.position[1], resource.position[2]);
739
+ mesh.position.y = 1;
740
+ mesh.castShadow = true;
741
+ mesh.receiveShadow = true;
742
+
743
+ this.resourceMeshes.set(id, mesh);
744
+ this.scene.add(mesh);
745
+ }
746
+ }
747
+
748
+ this.updateResourceCount();
749
+ }
750
+
751
+ /**
752
+ * Create roads for the village
753
+ */
754
+ createRoads() {
755
+ console.log('Creating roads...');
756
+
757
+ // Create a simple crossroad in the center - very visible
758
+ const roadMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
759
+
760
+ // Horizontal road - very wide and visible
761
+ const horizontalRoad = new THREE.Mesh(
762
+ new THREE.BoxGeometry(50, 0.5, 6),
763
+ roadMaterial
764
+ );
765
+ horizontalRoad.position.set(0, 0.25, 0);
766
+ horizontalRoad.receiveShadow = true;
767
+ this.scene.add(horizontalRoad);
768
+ console.log('Added horizontal road at center');
769
+
770
+ // Vertical road - very wide and visible
771
+ const verticalRoad = new THREE.Mesh(
772
+ new THREE.BoxGeometry(6, 0.5, 50),
773
+ roadMaterial
774
+ );
775
+ verticalRoad.position.set(0, 0.25, 0);
776
+ verticalRoad.receiveShadow = true;
777
+ this.scene.add(verticalRoad);
778
+ console.log('Added vertical road at center');
779
+
780
+ // Add bright yellow road markings
781
+ const markingMaterial = new THREE.MeshLambertMaterial({ color: 0xFFFF00 });
782
+
783
+ // Horizontal road markings
784
+ for (let x = -20; x <= 20; x += 4) {
785
+ if (Math.abs(x) > 2) { // Skip center
786
+ const marking = new THREE.Mesh(
787
+ new THREE.BoxGeometry(2, 0.3, 0.5),
788
+ markingMaterial
789
+ );
790
+ marking.position.set(x, 0.4, 0);
791
+ this.scene.add(marking);
792
+ }
793
+ }
794
+
795
+ // Vertical road markings
796
+ for (let z = -20; z <= 20; z += 4) {
797
+ if (Math.abs(z) > 2) { // Skip center
798
+ const marking = new THREE.Mesh(
799
+ new THREE.BoxGeometry(0.5, 0.3, 2),
800
+ markingMaterial
801
+ );
802
+ marking.position.set(0, 0.4, z);
803
+ this.scene.add(marking);
804
+ }
805
+ }
806
+
807
+ console.log('Roads creation completed');
808
+ }
809
+
810
+ /**
811
+ * Create trees for the village
812
+ */
813
+ createTrees() {
814
+ console.log('Creating trees...');
815
+
816
+ // Create very visible trees at key positions
817
+ const treePositions = [
818
+ [-15, 0, -15], [15, 0, -15], [-15, 0, 15], [15, 0, 15],
819
+ [-25, 0, 0], [25, 0, 0], [0, 0, -25], [0, 0, 25]
820
+ ];
821
+
822
+ treePositions.forEach((pos, index) => {
823
+ // Create a very visible tree
824
+ const treeGroup = new THREE.Group();
825
+
826
+ // Large trunk
827
+ const trunk = new THREE.Mesh(
828
+ new THREE.CylinderGeometry(0.8, 1, 6, 8),
829
+ new THREE.MeshLambertMaterial({ color: 0x8B4513 })
830
+ );
831
+ trunk.position.y = 3;
832
+ trunk.castShadow = true;
833
+ trunk.receiveShadow = true;
834
+ treeGroup.add(trunk);
835
+
836
+ // Large foliage - multiple layers for visibility
837
+ const foliage1 = new THREE.Mesh(
838
+ new THREE.SphereGeometry(5, 8, 6),
839
+ new THREE.MeshLambertMaterial({ color: 0x228B22 })
840
+ );
841
+ foliage1.position.y = 7;
842
+ foliage1.castShadow = true;
843
+ foliage1.receiveShadow = true;
844
+ treeGroup.add(foliage1);
845
+
846
+ const foliage2 = new THREE.Mesh(
847
+ new THREE.SphereGeometry(3, 8, 6),
848
+ new THREE.MeshLambertMaterial({ color: 0x32CD32 })
849
+ );
850
+ foliage2.position.y = 10;
851
+ foliage2.castShadow = true;
852
+ foliage2.receiveShadow = true;
853
+ treeGroup.add(foliage2);
854
+
855
+ treeGroup.position.set(pos[0], 0, pos[2]);
856
+ this.scene.add(treeGroup);
857
+ console.log(`Added tree ${index + 1} at:`, pos);
858
+ });
859
+
860
+ console.log('Trees creation completed');
861
+ }
862
+
863
+ createInitialVillagers() {
864
+ const positions = [
865
+ [0, 0, 0],
866
+ [5, 0, 5],
867
+ [-3, 0, -3]
868
+ ];
869
+
870
+ positions.forEach((position, index) => {
871
+ this.createVillager(`villager${index + 1}`, position);
872
+ });
873
+ }
874
+
875
+ createVillager(id, position) {
876
+ if (this.aiSystem) {
877
+ const villager = this.aiSystem.createVillager(id, position);
878
+ this.createVillagerMesh(villager);
879
+ this.updateVillagerCount();
880
+ return villager;
881
+ }
882
+ }
883
+
884
+ createVillagerMesh(villager) {
885
+ // Create a more detailed villager with a body and head
886
+ const villagerGroup = new THREE.Group();
887
+
888
+ // Body
889
+ const bodyGeometry = new THREE.CylinderGeometry(0.3, 0.4, 0.8, 8);
890
+ const bodyMaterial = new THREE.MeshLambertMaterial({
891
+ color: this.getStateColor(villager.state)
892
+ });
893
+ const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
894
+ body.position.y = 0.4;
895
+ body.castShadow = true;
896
+ body.receiveShadow = true;
897
+ villagerGroup.add(body);
898
+
899
+ // Head
900
+ const headGeometry = new THREE.SphereGeometry(0.25, 16, 16);
901
+ const headMaterial = new THREE.MeshLambertMaterial({
902
+ color: 0xffd700 // Gold color for head
903
+ });
904
+ const head = new THREE.Mesh(headGeometry, headMaterial);
905
+ head.position.y = 0.9;
906
+ head.castShadow = true;
907
+ head.receiveShadow = true;
908
+ villagerGroup.add(head);
909
+
910
+ // Create villager label (sprite)
911
+ const canvas = document.createElement('canvas');
912
+ const context = canvas.getContext('2d');
913
+ canvas.width = 256;
914
+ canvas.height = 128;
915
+
916
+ context.fillStyle = 'rgba(0, 0, 0, 0.8)';
917
+ context.fillRect(0, 0, canvas.width, canvas.height);
918
+
919
+ context.fillStyle = 'white';
920
+ context.font = '32px Arial';
921
+ context.textAlign = 'center';
922
+ context.textBaseline = 'middle';
923
+ context.fillText(villager.id, canvas.width / 2, canvas.height / 2);
924
+
925
+ const texture = new THREE.CanvasTexture(canvas);
926
+ const spriteMaterial = new THREE.SpriteMaterial({
927
+ map: texture,
928
+ transparent: true
929
+ });
930
+ const label = new THREE.Sprite(spriteMaterial);
931
+ label.scale.set(3, 1.5, 1);
932
+ label.position.y = 1.5;
933
+ label.visible = this.showTitles; // Set initial visibility based on showTitles flag
934
+ villagerGroup.add(label);
935
+
936
+ villagerGroup.position.set(villager.position[0], villager.position[1], villager.position[2]);
937
+ villagerGroup.position.y = 0;
938
+ villagerGroup.castShadow = true;
939
+ villagerGroup.receiveShadow = true;
940
+
941
+ villagerGroup.userData.villager = villager;
942
+
943
+ this.villagerMeshes.set(villager.id, villagerGroup);
944
+ this.scene.add(villagerGroup);
945
+ }
946
+
947
+ getStateColor(state) {
948
+ const colors = {
949
+ sleep: 0x7f8c8d,
950
+ work: 0xe74c3c,
951
+ eat: 0xf39c12,
952
+ socialize: 0x9b59b6,
953
+ idle: 0x95a5a6
954
+ };
955
+ return colors[state] || colors.idle;
956
+ }
957
+
958
+ addVillager() {
959
+ const villagerCount = this.villagerMeshes.size;
960
+ const id = `villager${villagerCount + 1}`;
961
+
962
+ const position = [
963
+ (Math.random() - 0.5) * 40,
964
+ 0,
965
+ (Math.random() - 0.5) * 40
966
+ ];
967
+
968
+ this.createVillager(id, position);
969
+ }
970
+
971
+ resetSimulation() {
972
+ // Clear all villagers
973
+ for (const [id, mesh] of this.villagerMeshes) {
974
+ this.scene.remove(mesh);
975
+ }
976
+ this.villagerMeshes.clear();
977
+
978
+ // Clear path lines
979
+ for (const [id, line] of this.pathLines) {
980
+ this.scene.remove(line);
981
+ }
982
+ this.pathLines.clear();
983
+
984
+ // Reset AI system
985
+ this.aiSystem = new VillageAISystem(this.scene);
986
+ this.createEnvironment();
987
+
988
+ // Clear selection
989
+ this.selectedVillager = null;
990
+ this.updateVillagerInfo();
991
+
992
+ this.updateVillagerCount();
993
+ }
994
+
995
+ updateTimeSpeed(speed) {
996
+ this.timeSpeed = parseFloat(speed);
997
+ if (this.uiElements.timeSpeedDisplay) {
998
+ this.uiElements.timeSpeedDisplay.textContent = `${speed}x`;
999
+ }
1000
+ }
1001
+
1002
+ togglePaths(show) {
1003
+ console.log('Toggling paths:', show);
1004
+ this.showPaths = show;
1005
+ for (const [id, line] of this.pathLines) {
1006
+ line.visible = show;
1007
+ }
1008
+ }
1009
+
1010
+ toggleTitles(show) {
1011
+ console.log('Toggling titles:', show);
1012
+ this.showTitles = show;
1013
+ for (const [id, mesh] of this.villagerMeshes) {
1014
+ // Find the text sprite (label) in the mesh children
1015
+ // The label is the third child (index 2) - body, head, label
1016
+ if (mesh.children.length >= 3) {
1017
+ const label = mesh.children[2]; // Label is the third child
1018
+ if (label instanceof THREE.Sprite) {
1019
+ label.visible = show;
1020
+ }
1021
+ }
1022
+ }
1023
+ }
1024
+
1025
+ updateFog(intensity) {
1026
+ console.log('Updating fog intensity:', intensity);
1027
+ this.weatherSystem.fogIntensity = intensity;
1028
+
1029
+ // Update fog in the scene
1030
+ if (this.scene.fog) {
1031
+ // Convert intensity (0-100) to fog density
1032
+ const near = 10 + (100 - intensity); // More intensity = less near distance
1033
+ const far = 30 + (100 - intensity) * 2; // More intensity = less far distance
1034
+ this.scene.fog.near = near;
1035
+ this.scene.fog.far = far;
1036
+ }
1037
+ }
1038
+
1039
+ setWeather(weatherType) {
1040
+ console.log('Setting weather to:', weatherType);
1041
+ this.weatherSystem.currentWeather = weatherType;
1042
+
1043
+ // Update scene based on weather
1044
+ switch (weatherType) {
1045
+ case 'sun':
1046
+ this.scene.background = new THREE.Color(0x87CEEB); // Sky blue
1047
+ if (this.scene.fog) {
1048
+ this.scene.fog.color = new THREE.Color(0x87CEEB);
1049
+ }
1050
+ break;
1051
+ case 'rain':
1052
+ this.scene.background = new THREE.Color(0x778899); // Gray
1053
+ if (this.scene.fog) {
1054
+ this.scene.fog.color = new THREE.Color(0x778899);
1055
+ }
1056
+ this.createRainEffect();
1057
+ break;
1058
+ case 'snow':
1059
+ this.scene.background = new THREE.Color(0xE0E6EF); // Light gray
1060
+ if (this.scene.fog) {
1061
+ this.scene.fog.color = new THREE.Color(0xE0E6EF);
1062
+ }
1063
+ this.createSnowEffect();
1064
+ break;
1065
+ }
1066
+ }
1067
+
1068
+ createRainEffect() {
1069
+ // Clear existing rain particles
1070
+ this.weatherSystem.rainParticles.forEach(particle => {
1071
+ this.scene.remove(particle);
1072
+ });
1073
+ this.weatherSystem.rainParticles = [];
1074
+
1075
+ // Create new rain particles
1076
+ const rainCount = 1000;
1077
+ const rainGeometry = new THREE.BufferGeometry();
1078
+ const positions = new Float32Array(rainCount * 3);
1079
+
1080
+ for (let i = 0; i < rainCount * 3; i += 3) {
1081
+ positions[i] = (Math.random() - 0.5) * 100; // x
1082
+ positions[i + 1] = Math.random() * 50 + 10; // y
1083
+ positions[i + 2] = (Math.random() - 0.5) * 100; // z
1084
+ }
1085
+
1086
+ rainGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
1087
+
1088
+ const rainMaterial = new THREE.PointsMaterial({
1089
+ color: 0xAAAAFF,
1090
+ size: 0.1,
1091
+ transparent: true
1092
+ });
1093
+
1094
+ const rainSystem = new THREE.Points(rainGeometry, rainMaterial);
1095
+ this.scene.add(rainSystem);
1096
+ this.weatherSystem.rainParticles.push(rainSystem);
1097
+ }
1098
+
1099
+ createSnowEffect() {
1100
+ // Clear existing snow particles
1101
+ this.weatherSystem.snowParticles.forEach(particle => {
1102
+ this.scene.remove(particle);
1103
+ });
1104
+ this.weatherSystem.snowParticles = [];
1105
+
1106
+ // Create new snow particles
1107
+ const snowCount = 1000;
1108
+ const snowGeometry = new THREE.BufferGeometry();
1109
+ const positions = new Float32Array(snowCount * 3);
1110
+
1111
+ for (let i = 0; i < snowCount * 3; i += 3) {
1112
+ positions[i] = (Math.random() - 0.5) * 100; // x
1113
+ positions[i + 1] = Math.random() * 50 + 10; // y
1114
+ positions[i + 2] = (Math.random() - 0.5) * 100; // z
1115
+ }
1116
+
1117
+ snowGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
1118
+
1119
+ const snowMaterial = new THREE.PointsMaterial({
1120
+ color: 0xFFFFFF,
1121
+ size: 0.2,
1122
+ transparent: true
1123
+ });
1124
+
1125
+ const snowSystem = new THREE.Points(snowGeometry, snowMaterial);
1126
+ this.scene.add(snowSystem);
1127
+ this.weatherSystem.snowParticles.push(snowSystem);
1128
+ }
1129
+
1130
+ triggerDisaster(disasterType) {
1131
+ console.log('Triggering disaster:', disasterType);
1132
+
1133
+ // Create disaster effect
1134
+ switch (disasterType) {
1135
+ case 'fire':
1136
+ this.createFireEffect();
1137
+ break;
1138
+ case 'hurricane':
1139
+ this.createHurricaneEffect();
1140
+ break;
1141
+ case 'flood':
1142
+ this.createFloodEffect();
1143
+ break;
1144
+ case 'earthquake':
1145
+ this.createEarthquakeEffect();
1146
+ break;
1147
+ case 'plague':
1148
+ this.createPlagueEffect();
1149
+ break;
1150
+ }
1151
+
1152
+ // Add to active disasters
1153
+ this.disasterSystem.activeDisasters.set(disasterType, {
1154
+ startTime: Date.now(),
1155
+ intensity: 1.0
1156
+ });
1157
+ }
1158
+
1159
+ createFireEffect() {
1160
+ // Create fire particles at random building locations
1161
+ if (this.buildingMeshes.size > 0) {
1162
+ const buildingArray = Array.from(this.buildingMeshes.values());
1163
+ const building = buildingArray[Math.floor(Math.random() * buildingArray.length)];
1164
+
1165
+ const fireGeometry = new THREE.SphereGeometry(1, 8, 8);
1166
+ const fireMaterial = new THREE.MeshBasicMaterial({
1167
+ color: 0xFF4500,
1168
+ transparent: true,
1169
+ opacity: 0.7
1170
+ });
1171
+
1172
+ const fireEffect = new THREE.Mesh(fireGeometry, fireMaterial);
1173
+ fireEffect.position.copy(building.position);
1174
+ fireEffect.position.y = 3;
1175
+
1176
+ this.scene.add(fireEffect);
1177
+ this.disasterSystem.fireEffects.push(fireEffect);
1178
+
1179
+ // Remove fire after some time
1180
+ setTimeout(() => {
1181
+ this.scene.remove(fireEffect);
1182
+ const index = this.disasterSystem.fireEffects.indexOf(fireEffect);
1183
+ if (index > -1) {
1184
+ this.disasterSystem.fireEffects.splice(index, 1);
1185
+ }
1186
+ }, 5000);
1187
+ }
1188
+ }
1189
+
1190
+ createHurricaneEffect() {
1191
+ // Create a rotating wind effect in the center of the village
1192
+ const tornadoGeometry = new THREE.CylinderGeometry(0.5, 2, 20, 8);
1193
+ const tornadoMaterial = new THREE.MeshBasicMaterial({
1194
+ color: 0x888888,
1195
+ transparent: true,
1196
+ opacity: 0.5
1197
+ });
1198
+
1199
+ const tornado = new THREE.Mesh(tornadoGeometry, tornadoMaterial);
1200
+ tornado.position.set(0, 10, 0);
1201
+
1202
+ this.scene.add(tornado);
1203
+
1204
+ // Animate tornado
1205
+ let tornadoTime = 0;
1206
+ const animateTornado = () => {
1207
+ tornadoTime += 0.1;
1208
+ tornado.rotation.y = tornadoTime;
1209
+ tornado.position.x = Math.sin(tornadoTime) * 5;
1210
+ tornado.position.z = Math.cos(tornadoTime) * 5;
1211
+
1212
+ if (tornadoTime < 20) { // Run for 20 seconds
1213
+ requestAnimationFrame(animateTornado);
1214
+ } else {
1215
+ this.scene.remove(tornado);
1216
+ }
1217
+ };
1218
+
1219
+ animateTornado();
1220
+ }
1221
+
1222
+ createFloodEffect() {
1223
+ // Create a water plane that rises
1224
+ const waterGeometry = new THREE.PlaneGeometry(100, 100);
1225
+ const waterMaterial = new THREE.MeshBasicMaterial({
1226
+ color: 0x4169E1,
1227
+ transparent: true,
1228
+ opacity: 0.6
1229
+ });
1230
+
1231
+ const water = new THREE.Mesh(waterGeometry, waterMaterial);
1232
+ water.rotation.x = -Math.PI / 2;
1233
+ water.position.y = 0.1;
1234
+
1235
+ this.scene.add(water);
1236
+ this.disasterSystem.floodEffects.push(water);
1237
+
1238
+ // Animate water rising
1239
+ let waterLevel = 0.1;
1240
+ const raiseWater = () => {
1241
+ waterLevel += 0.1;
1242
+ water.position.y = waterLevel;
1243
+
1244
+ if (waterLevel < 3) { // Raise to 3 units
1245
+ setTimeout(raiseWater, 200);
1246
+ } else {
1247
+ // Remove water after some time
1248
+ setTimeout(() => {
1249
+ this.scene.remove(water);
1250
+ const index = this.disasterSystem.floodEffects.indexOf(water);
1251
+ if (index > -1) {
1252
+ this.disasterSystem.floodEffects.splice(index, 1);
1253
+ }
1254
+ }, 3000);
1255
+ }
1256
+ };
1257
+
1258
+ raiseWater();
1259
+ }
1260
+
1261
+ createEarthquakeEffect() {
1262
+ // Shake the camera
1263
+ const originalCameraPosition = this.camera.position.clone();
1264
+ let shakeIntensity = 0.5;
1265
+ let shakeTime = 0;
1266
+
1267
+ const shakeCamera = () => {
1268
+ shakeTime += 0.1;
1269
+ shakeIntensity *= 0.95; // Decrease intensity over time
1270
+
1271
+ this.camera.position.x = originalCameraPosition.x + (Math.random() - 0.5) * shakeIntensity;
1272
+ this.camera.position.y = originalCameraPosition.y + (Math.random() - 0.5) * shakeIntensity;
1273
+ this.camera.position.z = originalCameraPosition.z + (Math.random() - 0.5) * shakeIntensity;
1274
+
1275
+ if (shakeTime < 5) { // Shake for 5 seconds
1276
+ requestAnimationFrame(shakeCamera);
1277
+ } else {
1278
+ // Reset camera position
1279
+ this.camera.position.copy(originalCameraPosition);
1280
+ }
1281
+ };
1282
+
1283
+ shakeCamera();
1284
+ }
1285
+
1286
+ createPlagueEffect() {
1287
+ // Change villager colors to show they're sick
1288
+ for (const [id, mesh] of this.villagerMeshes) {
1289
+ if (mesh.children.length > 0) {
1290
+ const body = mesh.children[0];
1291
+ if (body.material) {
1292
+ body.material.color.setHex(0x808080); // Gray color for sick villagers
1293
+ }
1294
+ }
1295
+ }
1296
+
1297
+ // Reset colors after some time
1298
+ setTimeout(() => {
1299
+ for (const [id, mesh] of this.villagerMeshes) {
1300
+ const villager = mesh.userData.villager;
1301
+ if (mesh.children.length > 0) {
1302
+ const body = mesh.children[0];
1303
+ if (body.material) {
1304
+ body.material.color.setHex(this.getStateColor(villager.state));
1305
+ }
1306
+ }
1307
+ }
1308
+ }, 10000);
1309
+ }
1310
+
1311
+ spawnAnimal(animalType) {
1312
+ console.log('Spawning animal:', animalType);
1313
+
1314
+ // Create animal at random position
1315
+ const position = [
1316
+ (Math.random() - 0.5) * 40,
1317
+ 0,
1318
+ (Math.random() - 0.5) * 40
1319
+ ];
1320
+
1321
+ this.createAnimal(animalType, position);
1322
+ }
1323
+
1324
+ createAnimal(animalType, position) {
1325
+ let animalMesh = null;
1326
+
1327
+ switch (animalType) {
1328
+ case 'wolf':
1329
+ animalMesh = new THREE.Mesh(
1330
+ new THREE.BoxGeometry(1, 0.5, 0.5),
1331
+ new THREE.MeshLambertMaterial({ color: 0x696969 })
1332
+ );
1333
+ break;
1334
+ case 'bear':
1335
+ animalMesh = new THREE.Mesh(
1336
+ new THREE.BoxGeometry(1.5, 1, 1),
1337
+ new THREE.MeshLambertMaterial({ color: 0x8B4513 })
1338
+ );
1339
+ break;
1340
+ case 'dragon':
1341
+ // Create a more complex dragon
1342
+ const dragonGroup = new THREE.Group();
1343
+
1344
+ // Body
1345
+ const body = new THREE.Mesh(
1346
+ new THREE.CylinderGeometry(0.5, 0.8, 2, 8),
1347
+ new THREE.MeshLambertMaterial({ color: 0x8B0000 })
1348
+ );
1349
+ body.rotation.z = Math.PI / 2;
1350
+ dragonGroup.add(body);
1351
+
1352
+ // Head
1353
+ const head = new THREE.Mesh(
1354
+ new THREE.SphereGeometry(0.5, 8, 8),
1355
+ new THREE.MeshLambertMaterial({ color: 0x8B0000 })
1356
+ );
1357
+ head.position.x = 1.2;
1358
+ dragonGroup.add(head);
1359
+
1360
+ // Wings
1361
+ const leftWing = new THREE.Mesh(
1362
+ new THREE.BoxGeometry(1.5, 0.1, 0.5),
1363
+ new THREE.MeshLambertMaterial({ color: 0x8B0000 })
1364
+ );
1365
+ leftWing.position.set(-0.5, 0, 0.5);
1366
+ leftWing.rotation.z = Math.PI / 4;
1367
+ dragonGroup.add(leftWing);
1368
+
1369
+ const rightWing = new THREE.Mesh(
1370
+ new THREE.BoxGeometry(1.5, 0.1, 0.5),
1371
+ new THREE.MeshLambertMaterial({ color: 0x8B0000 })
1372
+ );
1373
+ rightWing.position.set(-0.5, 0, -0.5);
1374
+ rightWing.rotation.z = -Math.PI / 4;
1375
+ dragonGroup.add(rightWing);
1376
+
1377
+ animalMesh = dragonGroup;
1378
+ break;
1379
+ }
1380
+
1381
+ if (animalMesh) {
1382
+ animalMesh.position.set(position[0], position[1], position[2]);
1383
+ animalMesh.position.y = 0.5;
1384
+ animalMesh.castShadow = true;
1385
+ animalMesh.receiveShadow = true;
1386
+
1387
+ this.scene.add(animalMesh);
1388
+ this.animalSystem.animals.set(`animal_${Date.now()}`, {
1389
+ type: animalType,
1390
+ mesh: animalMesh,
1391
+ position: position,
1392
+ targetVillager: null
1393
+ });
1394
+ }
1395
+ }
1396
+
1397
+ addWarrior() {
1398
+ console.log('Adding warrior');
1399
+
1400
+ // Create warrior at random position
1401
+ const position = [
1402
+ (Math.random() - 0.5) * 10,
1403
+ 0,
1404
+ (Math.random() - 0.5) * 10
1405
+ ];
1406
+
1407
+ this.createWarrior(position);
1408
+ }
1409
+
1410
+ createWarrior(position) {
1411
+ // Create a warrior (similar to villager but with different color and weapon)
1412
+ const warriorGroup = new THREE.Group();
1413
+
1414
+ // Body
1415
+ const bodyGeometry = new THREE.CylinderGeometry(0.3, 0.4, 0.8, 8);
1416
+ const bodyMaterial = new THREE.MeshLambertMaterial({
1417
+ color: 0x4169E1 // Blue for warriors
1418
+ });
1419
+ const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
1420
+ body.position.y = 0.4;
1421
+ body.castShadow = true;
1422
+ body.receiveShadow = true;
1423
+ warriorGroup.add(body);
1424
+
1425
+ // Head
1426
+ const headGeometry = new THREE.SphereGeometry(0.25, 16, 16);
1427
+ const headMaterial = new THREE.MeshLambertMaterial({
1428
+ color: 0xFFD700 // Gold color for head
1429
+ });
1430
+ const head = new THREE.Mesh(headGeometry, headMaterial);
1431
+ head.position.y = 0.9;
1432
+ head.castShadow = true;
1433
+ head.receiveShadow = true;
1434
+ warriorGroup.add(head);
1435
+
1436
+ // Weapon (sword)
1437
+ const swordGeometry = new THREE.BoxGeometry(0.05, 1, 0.05);
1438
+ const swordMaterial = new THREE.MeshLambertMaterial({
1439
+ color: 0xC0C0C0 // Silver color for sword
1440
+ });
1441
+ const sword = new THREE.Mesh(swordGeometry, swordMaterial);
1442
+ sword.position.set(0.4, 0.8, 0);
1443
+ sword.rotation.z = Math.PI / 4;
1444
+ warriorGroup.add(sword);
1445
+
1446
+ // Create warrior label (sprite)
1447
+ const canvas = document.createElement('canvas');
1448
+ const context = canvas.getContext('2d');
1449
+ canvas.width = 256;
1450
+ canvas.height = 128;
1451
+
1452
+ context.fillStyle = 'rgba(0, 0, 0, 0.8)';
1453
+ context.fillRect(0, 0, canvas.width, canvas.height);
1454
+
1455
+ context.fillStyle = 'white';
1456
+ context.font = '32px Arial';
1457
+ context.textAlign = 'center';
1458
+ context.textBaseline = 'middle';
1459
+ context.fillText('Warrior', canvas.width / 2, canvas.height / 2);
1460
+
1461
+ const texture = new THREE.CanvasTexture(canvas);
1462
+ const spriteMaterial = new THREE.SpriteMaterial({
1463
+ map: texture,
1464
+ transparent: true
1465
+ });
1466
+ const label = new THREE.Sprite(spriteMaterial);
1467
+ label.scale.set(3, 1.5, 1);
1468
+ label.position.y = 1.5;
1469
+ label.visible = this.showTitles; // Set initial visibility based on showTitles flag
1470
+ warriorGroup.add(label);
1471
+
1472
+ warriorGroup.position.set(position[0], position[1], position[2]);
1473
+ warriorGroup.position.y = 0;
1474
+ warriorGroup.castShadow = true;
1475
+ warriorGroup.receiveShadow = true;
1476
+
1477
+ this.scene.add(warriorGroup);
1478
+ this.warriorSystem.warriors.set(`warrior_${Date.now()}`, {
1479
+ mesh: warriorGroup,
1480
+ position: position,
1481
+ target: null
1482
+ });
1483
+
1484
+ this.updateVillagerCount(); // Update count to include warriors
1485
+ }
1486
+
1487
+ dispatchWarriors() {
1488
+ console.log('Dispatching warriors');
1489
+ this.warriorSystem.dispatched = true;
1490
+
1491
+ // Make warriors patrol or attack animals/beasts
1492
+ for (const [id, warrior] of this.warriorSystem.warriors) {
1493
+ // Set a random patrol point
1494
+ const patrolPoint = [
1495
+ (Math.random() - 0.5) * 30,
1496
+ 0,
1497
+ (Math.random() - 0.5) * 30
1498
+ ];
1499
+
1500
+ warrior.target = patrolPoint;
1501
+ }
1502
+ }
1503
+
1504
+ onWindowResize() {
1505
+ if (this.camera && this.renderer) {
1506
+ this.camera.aspect = window.innerWidth / window.innerHeight;
1507
+ this.camera.updateProjectionMatrix();
1508
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
1509
+ }
1510
+ }
1511
+
1512
+ onKeyDown(event) {
1513
+ // WASD controls have been disabled to prevent interference with LLM chat input
1514
+ // All camera movement should now be handled by OrbitControls only
1515
+ console.log('Key pressed (WASD controls disabled):', event.code);
1516
+ }
1517
+
1518
+ onMouseClick(event) {
1519
+ const mouse = new THREE.Vector2();
1520
+ mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
1521
+ mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
1522
+
1523
+ const raycaster = new THREE.Raycaster();
1524
+ raycaster.setFromCamera(mouse, this.camera);
1525
+
1526
+ const villagerMeshes = Array.from(this.villagerMeshes.values());
1527
+
1528
+ // Use recursive intersection to handle groups
1529
+ const intersects = raycaster.intersectObjects(villagerMeshes, true);
1530
+
1531
+ if (intersects.length > 0) {
1532
+ // Find the parent group that has the villager data
1533
+ let selectedMesh = intersects[0].object;
1534
+ while (selectedMesh && !selectedMesh.userData.villager) {
1535
+ selectedMesh = selectedMesh.parent;
1536
+ }
1537
+
1538
+ if (selectedMesh && selectedMesh.userData.villager) {
1539
+ this.selectedVillager = selectedMesh.userData.villager;
1540
+ this.updateVillagerInfo();
1541
+ }
1542
+ }
1543
+ }
1544
+
1545
+ updateVillagerCount() {
1546
+ const count = this.villagerMeshes.size;
1547
+ if (this.uiElements.villagerCountDisplay) {
1548
+ this.uiElements.villagerCountDisplay.textContent = count;
1549
+ }
1550
+ if (this.uiElements.villagerCountStat) {
1551
+ this.uiElements.villagerCountStat.textContent = count;
1552
+ }
1553
+ }
1554
+
1555
+ updateBuildingCount() {
1556
+ const count = this.buildingMeshes.size;
1557
+ if (this.uiElements.buildingCount) {
1558
+ this.uiElements.buildingCount.textContent = count;
1559
+ }
1560
+ }
1561
+
1562
+ updateResourceCount() {
1563
+ const count = this.resourceMeshes.size;
1564
+ if (this.uiElements.resourceCount) {
1565
+ this.uiElements.resourceCount.textContent = count;
1566
+ }
1567
+ }
1568
+
1569
+ updateVillagerInfo() {
1570
+ const villagerList = this.uiElements.villagerList;
1571
+
1572
+ if (!villagerList) return;
1573
+
1574
+ if (!this.selectedVillager) {
1575
+ villagerList.innerHTML = '<p>No villager selected</p>';
1576
+ return;
1577
+ }
1578
+
1579
+ const villager = this.selectedVillager;
1580
+ villagerList.innerHTML = `
1581
+ <div class="villager-item selected">
1582
+ <div><strong>${villager.id}</strong></div>
1583
+ <div>State: <span class="state-indicator state-${villager.state}"></span>${villager.state}</div>
1584
+ <div>Position: (${villager.position[0].toFixed(1)}, ${villager.position[1].toFixed(1)}, ${villager.position[2].toFixed(1)})</div>
1585
+ <div>Energy: ${villager.energy.toFixed(1)}%</div>
1586
+ <div>Hunger: ${villager.hunger.toFixed(1)}%</div>
1587
+ <div>Social Need: ${villager.socialNeed.toFixed(1)}%</div>
1588
+ <div>Path Points: ${villager.path.length}</div>
1589
+ </div>
1590
+ `;
1591
+ }
1592
+
1593
+ updatePathVisualization(villager) {
1594
+ const villagerId = villager.id;
1595
+
1596
+ // Remove existing path line
1597
+ if (this.pathLines.has(villagerId)) {
1598
+ this.scene.remove(this.pathLines.get(villagerId));
1599
+ this.pathLines.delete(villagerId);
1600
+ }
1601
+
1602
+ // Create new path line if villager has a path
1603
+ if (villager.path.length > 1) {
1604
+ const geometry = new THREE.BufferGeometry();
1605
+ const positions = [];
1606
+
1607
+ // Add current position
1608
+ positions.push(villager.position[0], 0.1, villager.position[2]);
1609
+
1610
+ // Add path points
1611
+ for (const point of villager.path) {
1612
+ positions.push(point[0], 0.1, point[2]);
1613
+ }
1614
+
1615
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
1616
+
1617
+ const material = new THREE.LineBasicMaterial({
1618
+ color: this.getStateColor(villager.state),
1619
+ linewidth: 3
1620
+ });
1621
+
1622
+ const line = new THREE.Line(geometry, material);
1623
+ line.visible = this.showPaths;
1624
+
1625
+ this.pathLines.set(villagerId, line);
1626
+ this.scene.add(line);
1627
+ }
1628
+ }
1629
+
1630
+ updateGameTime() {
1631
+ if (this.aiSystem && this.aiSystem.routineManager) {
1632
+ const time = this.aiSystem.routineManager.currentTime;
1633
+ const hours = Math.floor(time);
1634
+ const minutes = Math.floor((time - hours) * 60);
1635
+ if (this.uiElements.gameTime) {
1636
+ this.uiElements.gameTime.textContent = `${hours}:${minutes.toString().padStart(2, '0')}`;
1637
+ }
1638
+ }
1639
+ }
1640
+
1641
+ updateFPS() {
1642
+ this.frameCount++;
1643
+ const currentTime = performance.now();
1644
+
1645
+ if (currentTime - this.lastTime >= 1000) {
1646
+ this.fps = Math.round(this.frameCount * 1000 / (currentTime - this.lastTime));
1647
+ this.frameCount = 0;
1648
+ this.lastTime = currentTime;
1649
+ if (this.uiElements.fps) {
1650
+ this.uiElements.fps.textContent = this.fps;
1651
+ }
1652
+ }
1653
+ }
1654
+
1655
+ updateVillagerMeshes() {
1656
+ for (const [villagerId, mesh] of this.villagerMeshes) {
1657
+ const villager = mesh.userData.villager;
1658
+
1659
+ // Update position
1660
+ mesh.position.set(villager.position[0], villager.position[1], villager.position[2]);
1661
+ mesh.position.y = 0;
1662
+
1663
+ // Update color based on state
1664
+ // Update the body color (first child)
1665
+ if (mesh.children.length > 0) {
1666
+ const body = mesh.children[0];
1667
+ const newColor = this.getStateColor(villager.state);
1668
+ if (body.material.color.getHex() !== newColor) {
1669
+ body.material.color.setHex(newColor);
1670
+ }
1671
+ }
1672
+ }
1673
+ }
1674
+
1675
+ updatePathVisualizations() {
1676
+ for (const [villagerId, mesh] of this.villagerMeshes) {
1677
+ const villager = mesh.userData.villager;
1678
+ this.updatePathVisualization(villager);
1679
+ }
1680
+ }
1681
+
1682
+ updateAnimals() {
1683
+ // Update animal positions and behaviors
1684
+ for (const [id, animal] of this.animalSystem.animals) {
1685
+ // Simple movement logic - move towards random points
1686
+ if (Math.random() < 0.02) { // 2% chance to change direction
1687
+ animal.targetPosition = [
1688
+ animal.mesh.position.x + (Math.random() - 0.5) * 10,
1689
+ animal.mesh.position.y,
1690
+ animal.mesh.position.z + (Math.random() - 0.5) * 10
1691
+ ];
1692
+ }
1693
+
1694
+ // Move towards target position
1695
+ if (animal.targetPosition) {
1696
+ const speed = 0.05;
1697
+ const dx = animal.targetPosition[0] - animal.mesh.position.x;
1698
+ const dz = animal.targetPosition[2] - animal.mesh.position.z;
1699
+
1700
+ if (Math.abs(dx) > 0.1) {
1701
+ animal.mesh.position.x += Math.sign(dx) * speed;
1702
+ }
1703
+ if (Math.abs(dz) > 0.1) {
1704
+ animal.mesh.position.z += Math.sign(dz) * speed;
1705
+ }
1706
+ }
1707
+
1708
+ // Rotate to face movement direction
1709
+ if (animal.targetPosition) {
1710
+ const dx = animal.targetPosition[0] - animal.mesh.position.x;
1711
+ const dz = animal.targetPosition[2] - animal.mesh.position.z;
1712
+ animal.mesh.rotation.y = Math.atan2(dx, dz);
1713
+ }
1714
+ }
1715
+ }
1716
+
1717
+ updateWarriors() {
1718
+ // Update warrior positions and behaviors
1719
+ for (const [id, warrior] of this.warriorSystem.warriors) {
1720
+ // If warriors are dispatched, make them patrol
1721
+ if (this.warriorSystem.dispatched) {
1722
+ // Check if warrior has reached target
1723
+ if (warrior.target) {
1724
+ const dx = warrior.target[0] - warrior.mesh.position.x;
1725
+ const dz = warrior.target[2] - warrior.mesh.position.z;
1726
+
1727
+ // If close to target, set new target
1728
+ if (Math.abs(dx) < 1 && Math.abs(dz) < 1) {
1729
+ warrior.target = [
1730
+ (Math.random() - 0.5) * 30,
1731
+ 0,
1732
+ (Math.random() - 0.5) * 30
1733
+ ];
1734
+ }
1735
+
1736
+ // Move towards target
1737
+ const speed = 0.1;
1738
+ if (Math.abs(dx) > 0.1) {
1739
+ warrior.mesh.position.x += Math.sign(dx) * speed;
1740
+ }
1741
+ if (Math.abs(dz) > 0.1) {
1742
+ warrior.mesh.position.z += Math.sign(dz) * speed;
1743
+ }
1744
+
1745
+ // Rotate to face movement direction
1746
+ warrior.mesh.rotation.y = Math.atan2(dx, dz);
1747
+ }
1748
+ }
1749
+ }
1750
+ }
1751
+
1752
+ animate() {
1753
+ requestAnimationFrame(() => this.animate());
1754
+
1755
+ const deltaTime = this.clock.getDelta() * this.timeSpeed;
1756
+
1757
+ // Update AI system
1758
+ if (this.aiSystem) {
1759
+ this.aiSystem.update(deltaTime);
1760
+ }
1761
+
1762
+ // Update 3D visualization
1763
+ this.updateVillagerMeshes();
1764
+ this.updatePathVisualizations();
1765
+ this.updateAnimals();
1766
+ this.updateWarriors();
1767
+
1768
+ // Update UI
1769
+ this.updateGameTime();
1770
+ this.updateFPS();
1771
+ this.updateVillagerInfo();
1772
+
1773
+ // Update controls
1774
+ if (this.controls) {
1775
+ this.controls.update();
1776
+ }
1777
+
1778
+ // Render scene
1779
+ if (this.renderer && this.scene && this.camera) {
1780
+ this.renderer.render(this.scene, this.camera);
1781
+ }
1782
+ }
1783
+ }
1784
+
1785
+ // Initialize the application when the page loads
1786
+ document.addEventListener('DOMContentLoaded', () => {
1787
+ // Get the Hugging Face token from the window object
1788
+ // This could be set by a server-side process that has access to the HF_TOKEN environment variable
1789
+ const hfToken = window.HF_TOKEN || null;
1790
+
1791
+ // Log token status for debugging
1792
+ console.log('HF_TOKEN from window.HF_TOKEN:', hfToken ? 'Set' : 'Not set');
1793
+ if (hfToken) {
1794
+ console.log('HF_TOKEN length:', hfToken.length);
1795
+ }
1796
+
1797
+ window.app = new VillageVisualizationApp(hfToken);
1798
+ });
index.html CHANGED
@@ -1,19 +1,398 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Medieval Village AI System - Three.js Visualization</title>
7
+ <script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
8
+ <style>
9
+ body {
10
+ margin: 0;
11
+ padding: 0;
12
+ font-family: Arial, sans-serif;
13
+ background-color: #2c3e50;
14
+ color: white;
15
+ overflow: hidden;
16
+ }
17
+
18
+ #container {
19
+ position: absolute;
20
+ top: 0;
21
+ left: 0;
22
+ width: 100%;
23
+ height: 100%;
24
+ }
25
+
26
+ #ui-panel {
27
+ position: absolute;
28
+ top: 20px;
29
+ left: 20px;
30
+ background: rgba(0, 0, 0, 0.8);
31
+ padding: 10px;
32
+ border-radius: 10px;
33
+ min-width: 200px;
34
+ max-width: 250px;
35
+ height: calc(100vh - 40px);
36
+ overflow-y: auto;
37
+ z-index: 101;
38
+ }
39
+
40
+
41
+ #stats-panel {
42
+ position: absolute;
43
+ top: 20px;
44
+ right: 20px;
45
+ background: rgba(0, 0, 0, 0.8);
46
+ padding: 20px;
47
+ border-radius: 10px;
48
+ min-width: 200px;
49
+ z-index: 100;
50
+ }
51
+
52
+ #villager-info {
53
+ position: absolute;
54
+ bottom: 20px;
55
+ right: 20px;
56
+ background: rgba(0, 0, 0, 0.8);
57
+ padding: 20px;
58
+ border-radius: 10px;
59
+ min-width: 300px;
60
+ max-height: 200px;
61
+ overflow-y: auto;
62
+ z-index: 100;
63
+ }
64
+
65
+ .control-group {
66
+ margin-bottom: 8px;
67
+ }
68
+
69
+ .control-group label {
70
+ font-size: 12px;
71
+ }
72
+
73
+ .control-group input, .control-group button {
74
+ padding: 3px;
75
+ font-size: 10px;
76
+ }
77
+
78
+ .control-group h4 {
79
+ font-size: 14px;
80
+ margin: 8px 0 4px 0;
81
+ }
82
+
83
+ .control-group button {
84
+ background-color: #3498db;
85
+ color: white;
86
+ cursor: pointer;
87
+ transition: background-color 0.3s;
88
+ border: none;
89
+ border-radius: 3px;
90
+ }
91
+
92
+ .control-group button:hover {
93
+ background-color: #2980b9;
94
+ }
95
+
96
+ .weather-btn, .animal-btn, .warrior-btn {
97
+ background-color: #3498db;
98
+ color: white;
99
+ cursor: pointer;
100
+ transition: background-color 0.3s;
101
+ margin: 1px;
102
+ padding: 2px;
103
+ font-size: 12px;
104
+ width: 30px;
105
+ height: 30px;
106
+ display: inline-block;
107
+ }
108
+
109
+ .disaster-btn {
110
+ background-color: #e74c3c;
111
+ color: white;
112
+ cursor: pointer;
113
+ transition: background-color 0.3s;
114
+ margin: 1px;
115
+ padding: 2px;
116
+ font-size: 12px;
117
+ width: 30px;
118
+ height: 30px;
119
+ display: inline-block;
120
+ }
121
+
122
+ .weather-btn:hover, .disaster-btn:hover, .animal-btn:hover, .warrior-btn:hover {
123
+ background-color: #2980b9;
124
+ }
125
+
126
+ .disaster-btn {
127
+ background-color: #e74c3c;
128
+ }
129
+
130
+ .disaster-btn:hover {
131
+ background-color: #c0392b;
132
+ }
133
+
134
+ .animal-btn {
135
+ background-color: #27ae60;
136
+ }
137
+
138
+ .animal-btn:hover {
139
+ background-color: #229954;
140
+ }
141
+
142
+ .warrior-btn {
143
+ background-color: #f39c12;
144
+ }
145
+
146
+ .warrior-btn:hover {
147
+ background-color: #d68910;
148
+ }
149
+
150
+ .villager-item {
151
+ padding: 10px;
152
+ margin-bottom: 10px;
153
+ background: rgba(255, 255, 255, 0.1);
154
+ border-radius: 5px;
155
+ cursor: pointer;
156
+ transition: background-color 0.3s;
157
+ }
158
+
159
+ .villager-item:hover {
160
+ background: rgba(255, 255, 255, 0.2);
161
+ }
162
+
163
+ .villager-item.selected {
164
+ background: rgba(52, 152, 219, 0.3);
165
+ border: 2px solid #3498db;
166
+ }
167
+
168
+ .state-indicator {
169
+ display: inline-block;
170
+ width: 12px;
171
+ height: 12px;
172
+ border-radius: 50%;
173
+ margin-right: 8px;
174
+ }
175
+
176
+ .state-sleep { background-color: #7f8c8d; }
177
+ .state-work { background-color: #e74c3c; }
178
+ .state-eat { background-color: #f39c12; }
179
+ .state-socialize { background-color: #9b59b6; }
180
+ .state-idle { background-color: #95a5a6; }
181
+
182
+ #instructions {
183
+ position: absolute;
184
+ bottom: 250px;
185
+ right: 20px;
186
+ background: rgba(0, 0, 0, 0.8);
187
+ padding: 15px;
188
+ border-radius: 10px;
189
+ max-width: 300px;
190
+ font-size: 12px;
191
+ z-index: 100;
192
+ }
193
+ </style>
194
+ </head>
195
+ <body>
196
+ <div id="container"></div>
197
+
198
+ <!-- UI Controls Panel -->
199
+ <div id="ui-panel">
200
+ <h3>Village Controls</h3>
201
+
202
+ <div class="control-group">
203
+ <label for="villager-count">Villager Count: <span id="villager-count-display">0</span></label>
204
+ </div>
205
+
206
+ <div class="control-group">
207
+ <label for="add-villager-btn">Add Villager</label>
208
+ <button id="add-villager-btn" title="Add New Villager">👨‍🌾</button>
209
+ </div>
210
+
211
+ <div class="control-group">
212
+ <label for="reset-btn">Reset Simulation</label>
213
+ <button id="reset-btn" title="Reset All">🔄</button>
214
+ </div>
215
+
216
+ <div class="control-group">
217
+ <label for="time-speed">Time Speed: <span id="time-speed-display">1.0x</span></label>
218
+ <input type="range" id="time-speed" min="0.1" max="5" step="0.1" value="1.0">
219
+ </div>
220
+
221
+ <div class="control-group">
222
+ <label for="show-paths">Show Movement Paths</label>
223
+ <input type="checkbox" id="show-paths" checked>
224
+ </div>
225
+
226
+ <div class="control-group">
227
+ <label for="show-titles">Show Villager Titles</label>
228
+ <input type="checkbox" id="show-titles" checked>
229
+ </div>
230
+
231
+ <!-- Weather Controls -->
232
+ <div class="control-group">
233
+ <h4>Weather Controls</h4>
234
+ </div>
235
+
236
+ <div class="control-group">
237
+ <label for="fog-control">Fog Intensity</label>
238
+ <input type="range" id="fog-control" min="0" max="100" value="50">
239
+ </div>
240
+
241
+ <div class="control-group">
242
+ <button id="weather-sun" class="weather-btn" title="Sunny Weather">☀️</button>
243
+ <button id="weather-rain" class="weather-btn" title="Rain Weather">🌧️</button>
244
+ <button id="weather-snow" class="weather-btn" title="Snow Weather">❄️</button>
245
+ </div>
246
+
247
+ <!-- Disaster Controls -->
248
+ <div class="control-group">
249
+ <h4>Disaster Controls</h4>
250
+ </div>
251
+
252
+ <div class="control-group">
253
+ <button id="disaster-fire" class="disaster-btn" title="Fire Disaster">🔥</button>
254
+ <button id="disaster-hurricane" class="disaster-btn" title="Hurricane Disaster">🌪️</button>
255
+ <button id="disaster-flood" class="disaster-btn" title="Flood Disaster">🌊</button>
256
+ <button id="disaster-earthquake" class="disaster-btn" title="Earthquake Disaster">🌍</button>
257
+ <button id="disaster-plague" class="disaster-btn" title="Plague Disaster">🦠</button>
258
+ </div>
259
+
260
+ <!-- Animal/Beast Controls -->
261
+ <div class="control-group">
262
+ <h4>Animal/Beast Controls</h4>
263
+ </div>
264
+
265
+ <div class="control-group">
266
+ <button id="spawn-wolf" class="animal-btn" title="Spawn Wolf">🐺</button>
267
+ <button id="spawn-bear" class="animal-btn" title="Spawn Bear">🐻</button>
268
+ <button id="spawn-dragon" class="animal-btn" title="Spawn Dragon">🐉</button>
269
+ </div>
270
+
271
+ <!-- Warrior Controls -->
272
+ <div class="control-group">
273
+ <h4>Warrior Controls</h4>
274
+ </div>
275
+
276
+ <div class="control-group">
277
+ <button id="add-warrior" class="warrior-btn" title="Add Warrior">⚔️</button>
278
+ <button id="dispatch-warriors" class="warrior-btn" title="Dispatch Warriors">🛡️</button>
279
+ </div>
280
+
281
+ <!-- LLM Controls -->
282
+ <div class="control-group">
283
+ <h4>LLM Controls <span id="llm-status-indicator" style="width: 10px; height: 10px; border-radius: 50%; display: inline-block; margin-left: 10px;"></span></h4>
284
+ </div>
285
+
286
+ <div class="control-group">
287
+ <label for="llm-model">Select LLM Model:</label>
288
+ <select id="llm-model" style="width: 100%; padding: 2px; font-size: 11px;">
289
+ <option value="meta-llama/Llama-3.1-8B-Instruct">Llama-3.1-8B-Instruct</option>
290
+ <option value="google/gemma-3-270m-it">Gemma-3-270m-it</option>
291
+ <option value="google/gemma-3-4b-it">Gemma-3-4b-it</option>
292
+ <option value="google/gemma-3-27b-it">Gemma-3-27b-it</option>
293
+ <option value="Qwen/Qwen3-4B-Instruct-2507">Qwen3-4B-Instruct</option>
294
+ <option value="Qwen/Qwen3-8B">Qwen3-8B</option>
295
+ <option value="mistralai/Mistral-7B-Instruct-v0.3">Mistral-7B-Instruct</option>
296
+ <option value="HuggingFaceH4/zephyr-7b-beta">Zephyr-7b-beta</option>
297
+ <option value="TinyLlama/TinyLlama-1.1B-Chat-v1.0">TinyLlama-1.1B-Chat</option>
298
+ <option value="microsoft/Phi-3-mini-4k-instruct">Phi-3-mini-4k</option>
299
+ <option value="stabilityai/stablelm-2-1_6b">StableLM-2-1_6b</option>
300
+ <option value="NousResearch/Hermes-2-Pro-Llama-3-8B">Hermes-2-Pro-Llama-3</option>
301
+ <option value="CohereForAI/c4ai-command-r-v01">C4AI-Command-R</option>
302
+ <option value="nvidia/Nemotron-Research-Reasoning-Qwen-1.5B">Nemotron-Qwen-1.5B</option>
303
+ <option value="inclusionAI/AReaL-boba-2-8B">AReaL-boba-2-8B</option>
304
+ </select>
305
+ </div>
306
+
307
+ <div class="control-group">
308
+ <label for="llm-query">Ask the LLM:</label>
309
+ <input type="text" id="llm-query" placeholder="Enter your question..." style="width: 100%; padding: 2px; font-size: 11px;">
310
+ </div>
311
+
312
+ <div class="control-group">
313
+ <button id="llm-submit" style="width: 100%; padding: 3px; font-size: 11px;">Submit Query</button>
314
+ </div>
315
+
316
+ <div class="control-group">
317
+ <label for="llm-response">LLM Response:</label>
318
+ <div id="llm-response" style="background: rgba(255, 255, 255, 0.1); padding: 5px; border-radius: 5px; min-height: 50px; max-height: 100px; overflow-y: auto; font-size: 11px;">
319
+ No response yet. Submit a query to get started.
320
+ </div>
321
+ </div>
322
+
323
+ <div class="control-group">
324
+ <div style="background: rgba(255, 255, 0, 0.1); padding: 5px; border-radius: 5px; font-size: 9px; margin-top: 5px;">
325
+ <strong>API Token Required:</strong> To use the LLM functionality, you need to set your Hugging Face API token.
326
+ Get one from <a href="https://huggingface.co/settings/tokens" target="_blank" style="color: #3498db;">Hugging Face</a>.
327
+ <br><br>
328
+ If running this application with a server that has access to the HF_TOKEN environment variable, the token will be automatically used.
329
+ <br><br>
330
+ For manual setup, you can set the token in the browser console with:
331
+ <code>window.HF_TOKEN = 'your-actual-token-here'</code>
332
+ <br>or<br>
333
+ <code>app.llmHandler.setApiToken('your-actual-token-here')</code>
334
+ <br><br>
335
+ Check the browser console for debugging information about the token status.
336
+ </div>
337
+ </div>
338
+ </div>
339
+
340
+ <!-- Stats Panel -->
341
+ <div id="stats-panel">
342
+ <h3>Simulation Stats</h3>
343
+ <div id="stats-content">
344
+ <div>Time: <span id="game-time">0:00</span></div>
345
+ <div>FPS: <span id="fps">0</span></div>
346
+ <div>Villagers: <span id="villager-count-stat">0</span></div>
347
+ <div>Buildings: <span id="building-count">0</span></div>
348
+ <div>Resources: <span id="resource-count">0</span></div>
349
+ </div>
350
+ </div>
351
+
352
+ <!-- Villager Information Panel -->
353
+ <div id="villager-info">
354
+ <h3>Villager Information</h3>
355
+ <div id="villager-list">
356
+ <p>No villagers selected</p>
357
+ </div>
358
+ </div>
359
+
360
+ <!-- Instructions Panel -->
361
+ <div id="instructions">
362
+ <h4>Controls:</h4>
363
+ <ul>
364
+ <li><strong>Mouse:</strong> Look around</li>
365
+ <li><strong>WASD:</strong> Move camera</li>
366
+ <li><strong>Space:</strong> Up</li>
367
+ <li><strong>Shift:</strong> Down</li>
368
+ <li><strong>Click villager:</strong> Select for info</li>
369
+ </ul>
370
+ <h4>Legend:</h4>
371
+ <ul>
372
+ <li><span class="state-indicator state-sleep"></span>Sleep</li>
373
+ <li><span class="state-indicator state-work"></span>Work</li>
374
+ <li><span class="state-indicator state-eat"></span>Eat</li>
375
+ <li><span class="state-indicator state-socialize"></span>Socialize</li>
376
+ </ul>
377
+ </div>
378
+
379
+ <!-- Hugging Face Token -->
380
+ <script>
381
+ // In a server-side implementation, the HF_TOKEN environment variable would be injected here
382
+ // For example, a Node.js server could inject it like this:
383
+ // window.HF_TOKEN = process.env.HF_TOKEN;
384
+ //
385
+ // For client-side usage, users can set the token in the browser console:
386
+ // window.HF_TOKEN = "your-actual-hugging-face-token";
387
+ //
388
+ // For testing purposes, you can uncomment the line below and replace with your actual token:
389
+ // window.HF_TOKEN = "your-actual-hugging-face-token";
390
+ window.HF_TOKEN = null;
391
+ </script>
392
+
393
+ <!-- Three.js and Application Scripts -->
394
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js?v=1"></script>
395
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js?v=1"></script>
396
+ <script type="module" src="app_new.js?v=1"></script>
397
+ </body>
398
+ </html>
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio==4.36.1
simple_app.js ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Simple Three.js test application
2
+ import * as THREE from 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
3
+
4
+ class SimpleVillageApp {
5
+ constructor() {
6
+ this.scene = null;
7
+ this.camera = null;
8
+ this.renderer = null;
9
+ this.controls = null;
10
+
11
+ this.init();
12
+ }
13
+
14
+ init() {
15
+ console.log('Initializing Simple Village App...');
16
+ this.initThreeJS();
17
+ this.createEnvironment();
18
+ this.animate();
19
+ console.log('Simple Village App initialized successfully');
20
+ }
21
+
22
+ initThreeJS() {
23
+ // Scene
24
+ this.scene = new THREE.Scene();
25
+ this.scene.background = new THREE.Color(0x87CEEB);
26
+
27
+ // Camera
28
+ this.camera = new THREE.PerspectiveCamera(
29
+ 75,
30
+ window.innerWidth / window.innerHeight,
31
+ 0.1,
32
+ 1000
33
+ );
34
+ this.camera.position.set(20, 20, 20);
35
+ this.camera.lookAt(0, 0, 0);
36
+
37
+ // Renderer
38
+ this.renderer = new THREE.WebGLRenderer({ antialias: true });
39
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
40
+ this.renderer.shadowMap.enabled = true;
41
+
42
+ const container = document.getElementById('container');
43
+ if (container) {
44
+ container.appendChild(this.renderer.domElement);
45
+ console.log('Renderer added to DOM');
46
+ }
47
+
48
+ // Lighting
49
+ const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
50
+ this.scene.add(ambientLight);
51
+
52
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
53
+ directionalLight.position.set(10, 10, 5);
54
+ directionalLight.castShadow = true;
55
+ this.scene.add(directionalLight);
56
+
57
+ // Ground plane
58
+ const groundGeometry = new THREE.PlaneGeometry(100, 100);
59
+ const groundMaterial = new THREE.MeshLambertMaterial({
60
+ color: 0x228B22,
61
+ transparent: true,
62
+ opacity: 0.8
63
+ });
64
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
65
+ ground.rotation.x = -Math.PI / 2;
66
+ ground.receiveShadow = true;
67
+ this.scene.add(ground);
68
+
69
+ // Grid helper
70
+ const gridHelper = new THREE.GridHelper(100, 100, 0x444444, 0x222222);
71
+ this.scene.add(gridHelper);
72
+
73
+ window.addEventListener('resize', () => this.onWindowResize());
74
+ }
75
+
76
+ createEnvironment() {
77
+ // Create some simple buildings
78
+ this.createBuilding(5, 0, 5, 0x8B4513);
79
+ this.createBuilding(-5, 0, -5, 0x696969);
80
+ this.createBuilding(0, 0, 0, 0xFFD700);
81
+
82
+ // Create some villagers
83
+ this.createVillager(0, 0, 0, 0xff0000);
84
+ this.createVillager(5, 0, 5, 0x00ff00);
85
+ this.createVillager(-3, 0, -3, 0x0000ff);
86
+ }
87
+
88
+ createBuilding(x, y, z, color) {
89
+ const geometry = new THREE.BoxGeometry(3, 3, 3);
90
+ const material = new THREE.MeshLambertMaterial({ color: color });
91
+ const mesh = new THREE.Mesh(geometry, material);
92
+ mesh.position.set(x, y + 1.5, z);
93
+ mesh.castShadow = true;
94
+ mesh.receiveShadow = true;
95
+ this.scene.add(mesh);
96
+ }
97
+
98
+ createVillager(x, y, z, color) {
99
+ const geometry = new THREE.SphereGeometry(0.5, 16, 16);
100
+ const material = new THREE.MeshLambertMaterial({ color: color });
101
+ const mesh = new THREE.Mesh(geometry, material);
102
+ mesh.position.set(x, y + 0.5, z);
103
+ mesh.castShadow = true;
104
+ mesh.receiveShadow = true;
105
+ this.scene.add(mesh);
106
+ }
107
+
108
+ onWindowResize() {
109
+ this.camera.aspect = window.innerWidth / window.innerHeight;
110
+ this.camera.updateProjectionMatrix();
111
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
112
+ }
113
+
114
+ animate() {
115
+ requestAnimationFrame(() => this.animate());
116
+
117
+ // Simple rotation for testing
118
+ this.scene.rotation.y += 0.001;
119
+
120
+ this.renderer.render(this.scene, this.camera);
121
+ }
122
+ }
123
+
124
+ // Initialize the application when the page loads
125
+ document.addEventListener('DOMContentLoaded', () => {
126
+ new SimpleVillageApp();
127
+ });
src/ai/README.md ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Medieval Village AI System
2
+
3
+ This directory contains the AI system implementation for a medieval village simulator. The system includes various components for managing villager behavior, movement, and interactions.
4
+
5
+ ## Components
6
+
7
+ ### Pathfinding (`pathfinding.js`)
8
+ Implements A* pathfinding using navigation meshes for efficient village-scale navigation in 3D space.
9
+
10
+ Key features:
11
+ - Navigation mesh support for complex 3D environments
12
+ - Path calculation between any two points in the village
13
+ - Entity movement along calculated paths
14
+
15
+ ### Behavior Trees (`behavior.js`)
16
+ Implements a behavior tree system for complex villager decision-making.
17
+
18
+ Key features:
19
+ - Behavior tree nodes (Selector, Sequence, Action, Condition)
20
+ - Villager behavior management for daily routines
21
+ - Extensible behavior system for custom actions
22
+
23
+ ### Crowd Simulation (`crowd.js`)
24
+ Implements crowd simulation with collision avoidance for multiple villagers.
25
+
26
+ Key features:
27
+ - Steering behaviors for natural movement
28
+ - Spatial partitioning for efficient neighbor queries
29
+ - Collision avoidance to prevent villagers from walking through each other
30
+
31
+ ### Daily Routines (`routines.js`)
32
+ Manages villager daily routines including work, rest, and social interactions.
33
+
34
+ Key features:
35
+ - Schedule-based routine system
36
+ - Activity management (sleep, work, eat, socialize)
37
+ - Need-based systems (energy, hunger, social)
38
+
39
+ ### Environmental Interaction (`environment.js`)
40
+ Handles how villagers interact with their environment, including resource gathering and building usage.
41
+
42
+ Key features:
43
+ - Resource management system
44
+ - Building interaction system
45
+ - Inventory system for villagers
46
+
47
+ ### Performance Optimization (`optimization.js`)
48
+ Implements various optimization techniques for managing multiple AI entities efficiently.
49
+
50
+ Key features:
51
+ - Entity manager with update queuing
52
+ - Spatial partitioning for efficient queries
53
+ - Object pooling to reduce garbage collection
54
+
55
+ ### Main System (`main.js`)
56
+ Integrates all components into a cohesive AI system for the village simulator.
57
+
58
+ ## Integration with Three.js
59
+
60
+ The AI system is designed to integrate with Three.js for 3D rendering:
61
+
62
+ 1. Pathfinding uses Three.js Vector3 for positions
63
+ 2. Crowd simulation uses Three.js Vector3 for positions and velocities
64
+ 3. Environmental interactions use Three.js Vector3 for positions
65
+ 4. The main system takes a Three.js scene object for integration
66
+
67
+ ## Usage Example
68
+
69
+ ```javascript
70
+ import VillageAISystem from './ai/main.js';
71
+ import * as THREE from 'three';
72
+
73
+ // Create a Three.js scene
74
+ const scene = new THREE.Scene();
75
+
76
+ // Initialize the AI system
77
+ const aiSystem = new VillageAISystem(scene);
78
+
79
+ // Create villagers
80
+ const villager1 = aiSystem.createVillager('villager1', new THREE.Vector3(0, 0, 0));
81
+ const villager2 = aiSystem.createVillager('villager2', new THREE.Vector3(5, 0, 5));
82
+
83
+ // In your animation loop
84
+ function animate(deltaTime) {
85
+ // Update the AI system
86
+ aiSystem.update(deltaTime);
87
+
88
+ // Render the scene
89
+ renderer.render(scene, camera);
90
+
91
+ requestAnimationFrame(animate);
92
+ }
93
+ ```
94
+
95
+ ## Performance Considerations
96
+
97
+ 1. **Entity Updates**: Uses a queuing system to limit the number of entities updated per frame
98
+ 2. **Spatial Partitioning**: Implements spatial partitioning for efficient neighbor queries
99
+ 3. **Object Pooling**: Reduces garbage collection through object pooling
100
+ 4. **Level of Detail**: In a full implementation, you would add LOD systems to reduce AI complexity for distant entities
101
+
102
+ ## Extending the System
103
+
104
+ The system is designed to be extensible:
105
+
106
+ 1. Add new behavior tree nodes for custom actions
107
+ 2. Extend the villager class for specialized villager types
108
+ 3. Add new resource and building types
109
+ 4. Implement additional optimization techniques as needed
src/ai/behavior.js ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Behavior Tree Implementation for Villager AI
3
+ *
4
+ * This implementation uses a behavior tree approach to manage complex villager behaviors
5
+ * including decision-making for daily routines, work, rest, and social interactions.
6
+ */
7
+
8
+ // Base Node class for behavior tree
9
+ class Node {
10
+ constructor() {
11
+ this.status = 'INVALID'; // INVALID, SUCCESS, FAILURE, RUNNING
12
+ }
13
+
14
+ execute(agent, deltaTime) {
15
+ throw new Error('Execute method must be implemented');
16
+ }
17
+ }
18
+
19
+ // Composite node that can have children
20
+ class CompositeNode extends Node {
21
+ constructor() {
22
+ super();
23
+ this.children = [];
24
+ }
25
+
26
+ addChild(child) {
27
+ this.children.push(child);
28
+ }
29
+ }
30
+
31
+ // Selector node - tries children in order until one succeeds
32
+ class SelectorNode extends CompositeNode {
33
+ constructor() {
34
+ super();
35
+ this.currentChildIndex = 0;
36
+ }
37
+
38
+ execute(agent, deltaTime) {
39
+ // If we were running a child previously, continue with it
40
+ if (this.status === 'RUNNING' && this.currentChildIndex < this.children.length) {
41
+ const result = this.children[this.currentChildIndex].execute(agent, deltaTime);
42
+ if (result === 'RUNNING') {
43
+ return 'RUNNING';
44
+ } else if (result === 'SUCCESS') {
45
+ this.status = 'SUCCESS';
46
+ this.currentChildIndex = 0;
47
+ return 'SUCCESS';
48
+ }
49
+ // If child failed, try next child
50
+ }
51
+
52
+ // Try children in order
53
+ for (let i = 0; i < this.children.length; i++) {
54
+ const result = this.children[i].execute(agent, deltaTime);
55
+ if (result === 'RUNNING') {
56
+ this.status = 'RUNNING';
57
+ this.currentChildIndex = i;
58
+ return 'RUNNING';
59
+ } else if (result === 'SUCCESS') {
60
+ this.status = 'SUCCESS';
61
+ this.currentChildIndex = 0;
62
+ return 'SUCCESS';
63
+ }
64
+ // If child failed, try next child
65
+ }
66
+
67
+ // All children failed
68
+ this.status = 'FAILURE';
69
+ this.currentChildIndex = 0;
70
+ return 'FAILURE';
71
+ }
72
+ }
73
+
74
+ // Sequence node - executes children in order until one fails
75
+ class SequenceNode extends CompositeNode {
76
+ constructor() {
77
+ super();
78
+ this.currentChildIndex = 0;
79
+ }
80
+
81
+ execute(agent, deltaTime) {
82
+ // If we were running a child previously, continue with it
83
+ if (this.status === 'RUNNING' && this.currentChildIndex < this.children.length) {
84
+ const result = this.children[this.currentChildIndex].execute(agent, deltaTime);
85
+ if (result === 'RUNNING') {
86
+ return 'RUNNING';
87
+ } else if (result === 'FAILURE') {
88
+ this.status = 'FAILURE';
89
+ this.currentChildIndex = 0;
90
+ return 'FAILURE';
91
+ }
92
+ // If child succeeded, try next child
93
+ }
94
+
95
+ // Try children in order
96
+ for (let i = this.currentChildIndex; i < this.children.length; i++) {
97
+ const result = this.children[i].execute(agent, deltaTime);
98
+ if (result === 'RUNNING') {
99
+ this.status = 'RUNNING';
100
+ this.currentChildIndex = i;
101
+ return 'RUNNING';
102
+ } else if (result === 'FAILURE') {
103
+ this.status = 'FAILURE';
104
+ this.currentChildIndex = 0;
105
+ return 'FAILURE';
106
+ }
107
+ // If child succeeded, continue to next child
108
+ }
109
+
110
+ // All children succeeded
111
+ this.status = 'SUCCESS';
112
+ this.currentChildIndex = 0;
113
+ return 'SUCCESS';
114
+ }
115
+ }
116
+
117
+ // Leaf node for specific actions
118
+ class ActionNode extends Node {
119
+ constructor(action) {
120
+ super();
121
+ this.action = action;
122
+ }
123
+
124
+ execute(agent, deltaTime) {
125
+ return this.action(agent, deltaTime);
126
+ }
127
+ }
128
+
129
+ // Condition node to check specific conditions
130
+ class ConditionNode extends Node {
131
+ constructor(condition) {
132
+ super();
133
+ this.condition = condition;
134
+ }
135
+
136
+ execute(agent, deltaTime) {
137
+ return this.condition(agent, deltaTime) ? 'SUCCESS' : 'FAILURE';
138
+ }
139
+ }
140
+
141
+ // Villager behavior tree implementation
142
+ class VillagerBehaviorTree {
143
+ constructor() {
144
+ this.root = this.createBehaviorTree();
145
+ }
146
+
147
+ createBehaviorTree() {
148
+ // Root selector - decides what the villager should do
149
+ const root = new SelectorNode();
150
+
151
+ // Check if it's time to sleep
152
+ const sleepSequence = new SequenceNode();
153
+ sleepSequence.addChild(new ConditionNode((agent) => agent.isTired()));
154
+ sleepSequence.addChild(new ActionNode((agent) => agent.sleep()));
155
+
156
+ // Check if it's time to work
157
+ const workSequence = new SequenceNode();
158
+ workSequence.addChild(new ConditionNode((agent) => agent.shouldWork()));
159
+ workSequence.addChild(new ActionNode((agent) => agent.work()));
160
+
161
+ // Check if it's time to socialize
162
+ const socializeSequence = new SequenceNode();
163
+ socializeSequence.addChild(new ConditionNode((agent) => agent.shouldSocialize()));
164
+ socializeSequence.addChild(new ActionNode((agent) => agent.socialize()));
165
+
166
+ // Default action - idle
167
+ const idleAction = new ActionNode((agent) => agent.idle());
168
+
169
+ // Add all sequences to root
170
+ root.addChild(sleepSequence);
171
+ root.addChild(workSequence);
172
+ root.addChild(socializeSequence);
173
+ root.addChild(idleAction);
174
+
175
+ return root;
176
+ }
177
+
178
+ update(agent, deltaTime) {
179
+ this.root.execute(agent, deltaTime);
180
+ }
181
+ }
182
+
183
+ export { VillagerBehaviorTree, Node, CompositeNode, SelectorNode, SequenceNode, ActionNode, ConditionNode };
src/ai/crowd.js ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // No direct THREE.js import needed
2
+
3
+ /**
4
+ * Crowd Simulation with Collision Avoidance for Villagers
5
+ *
6
+ * This implementation uses a combination of steering behaviors and spatial partitioning
7
+ * to efficiently manage crowd simulation with collision avoidance for multiple villagers.
8
+ */
9
+
10
+ class CrowdManager {
11
+ constructor() {
12
+ this.villagers = [];
13
+ this.spatialGrid = new SpatialGrid(100, 100, 5); // 100x100 grid with 5 unit cells
14
+ }
15
+
16
+ /**
17
+ * Add a villager to the crowd simulation
18
+ * @param {Villager} villager - The villager to add
19
+ */
20
+ addVillager(villager) {
21
+ this.villagers.push(villager);
22
+ this.spatialGrid.insert(villager);
23
+ }
24
+
25
+ /**
26
+ * Update all villagers in the crowd simulation
27
+ * @param {number} deltaTime - Time since last frame
28
+ */
29
+ update(deltaTime) {
30
+ // Update spatial grid
31
+ this.spatialGrid.clear();
32
+ for (const villager of this.villagers) {
33
+ this.spatialGrid.insert(villager);
34
+ }
35
+
36
+ // Update each villager
37
+ for (const villager of this.villagers) {
38
+ // Get nearby villagers for collision avoidance
39
+ const nearby = this.spatialGrid.query(villager.position, 10);
40
+
41
+ // Calculate steering forces
42
+ const avoidanceForce = this.calculateAvoidanceForce(villager, nearby);
43
+ const steeringForce = this.calculateSteeringForce(villager);
44
+
45
+ // Apply forces to velocity (using arrays)
46
+ this.addScaledVector(villager.velocity, avoidanceForce, deltaTime);
47
+ this.addScaledVector(villager.velocity, steeringForce, deltaTime);
48
+
49
+ // Limit velocity
50
+ const speed = this.calculateVectorLength(villager.velocity);
51
+ if (speed > villager.maxSpeed) {
52
+ this.normalizeVector(villager.velocity);
53
+ this.scaleVectorInPlace(villager.velocity, villager.maxSpeed);
54
+ }
55
+
56
+ // Update position
57
+ const moveVector = this.scaleVector(villager.velocity, deltaTime);
58
+ this.addVectors(villager.position, moveVector);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Calculate avoidance force to prevent collisions
64
+ * @param {Villager} villager - The villager to calculate for
65
+ * @param {Array<Villager>} nearby - Nearby villagers
66
+ * @returns {Array} Avoidance force as [x, y, z]
67
+ */
68
+ calculateAvoidanceForce(villager, nearby) {
69
+ const force = [0, 0, 0];
70
+
71
+ for (const neighbor of nearby) {
72
+ if (neighbor === villager) continue;
73
+
74
+ const distance = this.calculateDistance(villager.position, neighbor.position);
75
+ if (distance < villager.avoidanceRadius && distance > 0) {
76
+ // Calculate avoidance direction (away from neighbor)
77
+ const direction = this.subtractVectors(villager.position, neighbor.position);
78
+ this.normalizeVector(direction);
79
+
80
+ // Scale force by inverse of distance (stronger when closer)
81
+ const strength = (villager.avoidanceRadius - distance) / villager.avoidanceRadius;
82
+ this.addScaledVector(force, direction, strength);
83
+ }
84
+ }
85
+
86
+ return this.scaleVector(force, villager.avoidanceWeight);
87
+ }
88
+
89
+ /**
90
+ * Calculate distance between two positions
91
+ */
92
+ calculateDistance(pos1, pos2) {
93
+ const dx = pos1[0] - pos2[0];
94
+ const dy = pos1[1] - pos2[1];
95
+ const dz = pos1[2] - pos2[2];
96
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
97
+ }
98
+
99
+ /**
100
+ * Subtract two vectors
101
+ */
102
+ subtractVectors(vec1, vec2) {
103
+ return [
104
+ vec1[0] - vec2[0],
105
+ vec1[1] - vec2[1],
106
+ vec1[2] - vec2[2]
107
+ ];
108
+ }
109
+
110
+ /**
111
+ * Normalize a vector
112
+ */
113
+ normalizeVector(vec) {
114
+ const length = Math.sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
115
+ if (length > 0) {
116
+ vec[0] /= length;
117
+ vec[1] /= length;
118
+ vec[2] /= length;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Add scaled vector to another vector
124
+ */
125
+ addScaledVector(target, source, scale) {
126
+ target[0] += source[0] * scale;
127
+ target[1] += source[1] * scale;
128
+ target[2] += source[2] * scale;
129
+ }
130
+
131
+ /**
132
+ * Scale a vector
133
+ */
134
+ scaleVector(vec, scale) {
135
+ return [
136
+ vec[0] * scale,
137
+ vec[1] * scale,
138
+ vec[2] * scale
139
+ ];
140
+ }
141
+
142
+ /**
143
+ * Calculate vector length
144
+ */
145
+ calculateVectorLength(vec) {
146
+ return Math.sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
147
+ }
148
+
149
+ /**
150
+ * Scale vector in place
151
+ */
152
+ scaleVectorInPlace(vec, scale) {
153
+ vec[0] *= scale;
154
+ vec[1] *= scale;
155
+ vec[2] *= scale;
156
+ }
157
+
158
+ /**
159
+ * Add two vectors
160
+ */
161
+ addVectors(target, source) {
162
+ target[0] += source[0];
163
+ target[1] += source[1];
164
+ target[2] += source[2];
165
+ }
166
+
167
+ /**
168
+ * Calculate steering force to follow path
169
+ * @param {Villager} villager - The villager to calculate for
170
+ * @returns {Array} Steering force as [x, y, z]
171
+ */
172
+ calculateSteeringForce(villager) {
173
+ if (villager.path.length === 0) return [0, 0, 0];
174
+
175
+ const target = villager.path[0];
176
+ const direction = this.subtractVectors(target, villager.position);
177
+ this.normalizeVector(direction);
178
+ const desired = this.scaleVector(direction, villager.maxSpeed);
179
+ const steer = this.subtractVectors(desired, villager.velocity);
180
+
181
+ return this.scaleVector(steer, villager.steeringWeight);
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Spatial Grid for efficient neighbor queries
187
+ */
188
+ class SpatialGrid {
189
+ constructor(width, height, cellSize) {
190
+ this.width = width;
191
+ this.height = height;
192
+ this.cellSize = cellSize;
193
+ this.grid = [];
194
+
195
+ // Initialize grid
196
+ const cols = Math.ceil(width / cellSize);
197
+ const rows = Math.ceil(height / cellSize);
198
+ for (let i = 0; i < cols * rows; i++) {
199
+ this.grid.push([]);
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Get cell index for a position
205
+ * @param {Array} position - Position to get cell for [x, y, z]
206
+ * @returns {number} Cell index
207
+ */
208
+ getCellIndex(position) {
209
+ const col = Math.floor(position[0] / this.cellSize);
210
+ const row = Math.floor(position[2] / this.cellSize);
211
+ return row * Math.ceil(this.width / this.cellSize) + col;
212
+ }
213
+
214
+ /**
215
+ * Insert an object into the grid
216
+ * @param {Object} obj - Object to insert
217
+ */
218
+ insert(obj) {
219
+ const index = this.getCellIndex(obj.position);
220
+ if (index >= 0 && index < this.grid.length) {
221
+ this.grid[index].push(obj);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Clear the grid
227
+ */
228
+ clear() {
229
+ for (let i = 0; i < this.grid.length; i++) {
230
+ this.grid[i] = [];
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Query objects within a radius
236
+ * @param {Array} position - Center position [x, y, z]
237
+ * @param {number} radius - Query radius
238
+ * @returns {Array<Object>} Objects within radius
239
+ */
240
+ query(position, radius) {
241
+ const results = [];
242
+ const col = Math.floor(position[0] / this.cellSize);
243
+ const row = Math.floor(position[2] / this.cellSize);
244
+ const radiusCells = Math.ceil(radius / this.cellSize);
245
+
246
+ const cols = Math.ceil(this.width / this.cellSize);
247
+ const rows = Math.ceil(this.height / this.cellSize);
248
+
249
+ // Check surrounding cells
250
+ for (let r = Math.max(0, row - radiusCells); r <= Math.min(rows - 1, row + radiusCells); r++) {
251
+ for (let c = Math.max(0, col - radiusCells); c <= Math.min(cols - 1, col + radiusCells); c++) {
252
+ const index = r * cols + c;
253
+ if (index >= 0 && index < this.grid.length) {
254
+ results.push(...this.grid[index]);
255
+ }
256
+ }
257
+ }
258
+
259
+ return results;
260
+ }
261
+ }
262
+
263
+ export default CrowdManager;
src/ai/environment.js ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Villager } from './routines.js';
2
+
3
+ /**
4
+ * Environmental Interaction System for Villager AI
5
+ *
6
+ * This implementation handles how villagers interact with their environment,
7
+ * including resource gathering and building usage.
8
+ */
9
+ class EnvironmentInteractionSystem {
10
+ constructor() {
11
+ this.resources = new Map(); // Map of resource IDs to resource objects
12
+ this.buildings = new Map(); // Map of building IDs to building objects
13
+ }
14
+
15
+ /**
16
+ * Add a resource to the environment
17
+ * @param {string} id - Resource identifier
18
+ * @param {Resource} resource - Resource object
19
+ */
20
+ addResource(id, resource) {
21
+ this.resources.set(id, resource);
22
+ }
23
+
24
+ /**
25
+ * Add a building to the environment
26
+ * @param {string} id - Building identifier
27
+ * @param {Building} building - Building object
28
+ */
29
+ addBuilding(id, building) {
30
+ this.buildings.set(id, building);
31
+ }
32
+
33
+ /**
34
+ * Find the nearest resource of a specific type
35
+ * @param {Array} position - Search position [x, y, z]
36
+ * @param {string} type - Resource type
37
+ * @returns {Resource|null} Nearest resource or null if none found
38
+ */
39
+ findNearestResource(position, type) {
40
+ let nearest = null;
41
+ let minDistance = Infinity;
42
+
43
+ for (const [id, resource] of this.resources) {
44
+ if (resource.type === type && resource.amount > 0) {
45
+ const distance = this.calculateDistance(position, resource.position);
46
+ if (distance < minDistance) {
47
+ minDistance = distance;
48
+ nearest = resource;
49
+ }
50
+ }
51
+ }
52
+
53
+ return nearest;
54
+ }
55
+
56
+ /**
57
+ * Find the nearest building of a specific type
58
+ * @param {Array} position - Search position [x, y, z]
59
+ * @param {string} type - Building type
60
+ * @returns {Building|null} Nearest building or null if none found
61
+ */
62
+ findNearestBuilding(position, type) {
63
+ let nearest = null;
64
+ let minDistance = Infinity;
65
+
66
+ for (const [id, building] of this.buildings) {
67
+ if (building.type === type) {
68
+ const distance = this.calculateDistance(position, building.position);
69
+ if (distance < minDistance) {
70
+ minDistance = distance;
71
+ nearest = building;
72
+ }
73
+ }
74
+ }
75
+
76
+ return nearest;
77
+ }
78
+
79
+ /**
80
+ * Calculate distance between two points
81
+ * @param {Array} pos1 - First position [x, y, z]
82
+ * @param {Array} pos2 - Second position [x, y, z]
83
+ * @returns {number} Distance between points
84
+ */
85
+ calculateDistance(pos1, pos2) {
86
+ return Math.sqrt(
87
+ Math.pow(pos1[0] - pos2[0], 2) +
88
+ Math.pow(pos1[1] - pos2[1], 2) +
89
+ Math.pow(pos1[2] - pos2[2], 2)
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Gather a resource
95
+ * @param {Villager} villager - Villager gathering the resource
96
+ * @param {Resource} resource - Resource to gather
97
+ * @returns {boolean} True if successful
98
+ */
99
+ gatherResource(villager, resource) {
100
+ if (!resource || resource.amount <= 0) return false;
101
+
102
+ // Check if villager is close enough to gather
103
+ if (this.calculateDistance(villager.position, resource.position) > resource.gatherRadius) {
104
+ return false;
105
+ }
106
+
107
+ // Gather the resource
108
+ const amount = Math.min(resource.gatherAmount, resource.amount);
109
+ resource.amount -= amount;
110
+ villager.inventory.add(resource.type, amount);
111
+
112
+ return true;
113
+ }
114
+
115
+ /**
116
+ * Use a building
117
+ * @param {Villager} villager - Villager using the building
118
+ * @param {Building} building - Building to use
119
+ * @returns {boolean} True if successful
120
+ */
121
+ useBuilding(villager, building) {
122
+ if (!building) return false;
123
+
124
+ // Check if villager is close enough to use
125
+ if (this.calculateDistance(villager.position, building.position) > building.useRadius) {
126
+ return false;
127
+ }
128
+
129
+ // Use the building
130
+ return building.use(villager);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Resource class
136
+ */
137
+ class Resource {
138
+ constructor(id, type, position, amount, gatherRadius = 2, gatherAmount = 1) {
139
+ this.id = id;
140
+ this.type = type; // 'wood', 'stone', 'food', etc.
141
+ this.position = position;
142
+ this.amount = amount;
143
+ this.gatherRadius = gatherRadius;
144
+ this.gatherAmount = gatherAmount;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Building class
150
+ */
151
+ class Building {
152
+ constructor(id, type, position, useRadius = 3) {
153
+ this.id = id;
154
+ this.type = type; // 'house', 'workshop', 'market', etc.
155
+ this.position = position;
156
+ this.useRadius = useRadius;
157
+ this.occupancy = 0;
158
+ this.maxOccupancy = 1;
159
+ }
160
+
161
+ /**
162
+ * Use the building
163
+ * @param {Villager} villager - Villager using the building
164
+ * @returns {boolean} True if successful
165
+ */
166
+ use(villager) {
167
+ if (this.occupancy >= this.maxOccupancy) return false;
168
+
169
+ this.occupancy++;
170
+ // Simulate building usage
171
+ setTimeout(() => {
172
+ this.occupancy--;
173
+ }, 5000); // Occupied for 5 seconds
174
+
175
+ return true;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Villager inventory system
181
+ */
182
+ class Inventory {
183
+ constructor() {
184
+ this.items = new Map(); // Map of item types to quantities
185
+ }
186
+
187
+ /**
188
+ * Add items to inventory
189
+ * @param {string} type - Item type
190
+ * @param {number} amount - Amount to add
191
+ */
192
+ add(type, amount) {
193
+ if (this.items.has(type)) {
194
+ this.items.set(type, this.items.get(type) + amount);
195
+ } else {
196
+ this.items.set(type, amount);
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Remove items from inventory
202
+ * @param {string} type - Item type
203
+ * @param {number} amount - Amount to remove
204
+ * @returns {boolean} True if successful
205
+ */
206
+ remove(type, amount) {
207
+ if (!this.items.has(type)) return false;
208
+
209
+ const current = this.items.get(type);
210
+ if (current < amount) return false;
211
+
212
+ this.items.set(type, current - amount);
213
+ if (this.items.get(type) === 0) {
214
+ this.items.delete(type);
215
+ }
216
+
217
+ return true;
218
+ }
219
+
220
+ /**
221
+ * Get item count
222
+ * @param {string} type - Item type
223
+ * @returns {number} Item count
224
+ */
225
+ getCount(type) {
226
+ return this.items.get(type) || 0;
227
+ }
228
+ }
229
+
230
+ // Add inventory to Villager class
231
+ Villager.prototype.inventory = new Inventory();
232
+
233
+ export { EnvironmentInteractionSystem, Resource, Building, Inventory, Villager };
src/ai/llmHandler.js ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // llmHandler.js - Module for handling LLM interactions
2
+ class LLMHandler {
3
+ constructor(apiToken = null) {
4
+ // List of free Hugging Face models
5
+ this.freeLLMs = [
6
+ "meta-llama/Llama-3.1-8B-Instruct",
7
+ "google/gemma-3-270m-it",
8
+ "google/gemma-3-4b-it",
9
+ "google/gemma-3-27b-it",
10
+ "Qwen/Qwen3-4B-Instruct-2507",
11
+ "Qwen/Qwen3-8B",
12
+ "mistralai/Mistral-7B-Instruct-v0.3",
13
+ "HuggingFaceH4/zephyr-7b-beta",
14
+ "TinyLlama/TinyLlama-1.1B-Chat-v1.0",
15
+ "microsoft/Phi-3-mini-4k-instruct",
16
+ "stabilityai/stablelm-2-1_6b",
17
+ "NousResearch/Hermes-2-Pro-Llama-3-8B",
18
+ "CohereForAI/c4ai-command-r-v01",
19
+ "nvidia/Nemotron-Research-Reasoning-Qwen-1.5B",
20
+ "inclusionAI/AReaL-boba-2-8B"
21
+ ];
22
+
23
+ // Default selected model
24
+ this.selectedModel = this.freeLLMs[0];
25
+
26
+ // Hugging Face Inference API endpoint
27
+ this.apiEndpoint = "https://api-inference.huggingface.co/models/";
28
+
29
+ // API token (can be set in constructor or externally)
30
+ this.apiToken = apiToken;
31
+
32
+ // Flag to indicate if we should use simulated responses
33
+ // If we have a token, we'll use real responses by default
34
+ this.useSimulatedResponses = !apiToken;
35
+ }
36
+
37
+ /**
38
+ * Set the API token for Hugging Face Inference API
39
+ * @param {string} token - The API token
40
+ */
41
+ setApiToken(token) {
42
+ this.apiToken = token;
43
+ // When a real API token is set, disable simulated responses
44
+ this.useSimulatedResponses = !token;
45
+ }
46
+
47
+ /**
48
+ * Set whether to use simulated responses
49
+ * @param {boolean} useSimulated - Whether to use simulated responses
50
+ */
51
+ setUseSimulatedResponses(useSimulated) {
52
+ this.useSimulatedResponses = useSimulated;
53
+ }
54
+
55
+ /**
56
+ * Set the selected model
57
+ * @param {string} model - The model identifier
58
+ */
59
+ setSelectedModel(model) {
60
+ if (this.freeLLMs.includes(model)) {
61
+ this.selectedModel = model;
62
+ } else {
63
+ console.warn(`Model ${model} is not in the list of free LLMs`);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Send a query to the selected LLM
69
+ * @param {string} query - The query to send to the LLM
70
+ * @returns {Promise<string>} - The response from the LLM
71
+ */
72
+ async sendQuery(query) {
73
+ // If we don't have an API token, throw an error
74
+ if (!this.apiToken) {
75
+ throw new Error("API token is not set. Please set your Hugging Face API token to use the LLM functionality.");
76
+ }
77
+
78
+ // If we're using simulated responses, return a simulated response
79
+ if (this.useSimulatedResponses) {
80
+ console.log("Using simulated response for query:", query);
81
+ // Simulate API delay
82
+ await new Promise(resolve => setTimeout(resolve, 1000));
83
+ return this.simulateLLMResponse(query);
84
+ }
85
+
86
+ // Prepare the API request
87
+ const url = this.apiEndpoint + this.selectedModel;
88
+ const payload = {
89
+ inputs: query,
90
+ parameters: {
91
+ max_new_tokens: 200,
92
+ temperature: 0.7,
93
+ top_p: 0.9,
94
+ do_sample: true
95
+ }
96
+ };
97
+
98
+ // Make the API request
99
+ try {
100
+ const response = await fetch(url, {
101
+ method: 'POST',
102
+ headers: {
103
+ 'Authorization': `Bearer ${this.apiToken}`,
104
+ 'Content-Type': 'application/json'
105
+ },
106
+ body: JSON.stringify(payload)
107
+ });
108
+
109
+ // Check if the request was successful
110
+ if (!response.ok) {
111
+ const errorData = await response.json();
112
+ throw new Error(`API request failed with status ${response.status}: ${errorData.error || 'Unknown error'}`);
113
+ }
114
+
115
+ // Parse the response
116
+ const data = await response.json();
117
+
118
+ // Extract the generated text from the response
119
+ if (Array.isArray(data) && data.length > 0 && data[0].generated_text) {
120
+ return data[0].generated_text;
121
+ } else if (data.generated_text) {
122
+ return data.generated_text;
123
+ } else {
124
+ throw new Error("Unexpected response format from the API");
125
+ }
126
+ } catch (error) {
127
+ console.error("Error sending query to LLM:", error);
128
+ throw error;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Simulate an LLM response (for testing purposes)
134
+ * @param {string} query - The query to simulate a response for
135
+ * @returns {string} - A simulated response
136
+ */
137
+ simulateLLMResponse(query) {
138
+ const lowerQuery = query.toLowerCase();
139
+
140
+ if (lowerQuery.includes("villager") && lowerQuery.includes("behavior")) {
141
+ return "Villagers in the medieval village simulation exhibit complex behaviors based on their needs and the time of day. They cycle through states like sleeping, working, eating, and socializing. Their decisions are influenced by factors such as energy levels, hunger, and social needs.";
142
+ } else if (lowerQuery.includes("resource") && lowerQuery.includes("management")) {
143
+ return "Resource management in the village is critical for sustainability. Villagers collect resources like wood and stone from designated areas. Proper allocation of resources to buildings and villagers ensures the village's growth and resilience against disasters.";
144
+ } else if (lowerQuery.includes("disaster") || lowerQuery.includes("emergency")) {
145
+ return "The village simulation includes various disasters like fires, floods, and plagues. These events test the village's resilience and require strategic planning to mitigate their effects. Warriors can be dispatched to help protect the village from certain threats.";
146
+ } else if (lowerQuery.includes("ai") || lowerQuery.includes("artificial intelligence")) {
147
+ return "This simulation uses several AI techniques including finite state machines for villager behavior, pathfinding algorithms for navigation, and rule-based systems for decision making. The emergent behaviors arise from the interaction of these systems.";
148
+ } else if (lowerQuery.includes("building") || lowerQuery.includes("structure")) {
149
+ return "The village features various building types, each with unique functions: houses for living, workshops for crafting, markets for trading, and specialized buildings like universities and hospitals. Buildings are placed strategically to optimize villager workflows.";
150
+ } else {
151
+ return `I've received your query about "${query}". In a full implementation with API access, I would provide a detailed response based on the selected LLM model. For now, try asking about villagers, resources, disasters, AI systems, or buildings in the village.`;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Get the list of free LLMs
157
+ * @returns {string[]} - Array of free LLM model identifiers
158
+ */
159
+ getFreeLLMs() {
160
+ return this.freeLLMs;
161
+ }
162
+
163
+ /**
164
+ * Get the currently selected model
165
+ * @returns {string} - The currently selected model identifier
166
+ */
167
+ getSelectedModel() {
168
+ return this.selectedModel;
169
+ }
170
+
171
+ /**
172
+ * Check if the API token is set
173
+ * @returns {boolean} - Whether the API token is set
174
+ */
175
+ isApiTokenSet() {
176
+ return !!this.apiToken;
177
+ }
178
+
179
+ /**
180
+ * Get the API token (for debugging purposes)
181
+ * @returns {string|null} - The API token or null if not set
182
+ */
183
+ getApiToken() {
184
+ return this.apiToken;
185
+ }
186
+ }
187
+
188
+ // Export the LLMHandler class
189
+ export default LLMHandler;
src/ai/main.js ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import VillagePathfinder from './pathfinding.js';
2
+ import { VillagerBehaviorTree } from './behavior.js';
3
+ import CrowdManager from './crowd.js';
4
+ import { DailyRoutineManager, Villager } from './routines.js';
5
+ import { EnvironmentInteractionSystem, Resource, Building } from './environment.js';
6
+ import { AIEntityManager } from './optimization.js';
7
+
8
+ /**
9
+ * Main AI System for Medieval Village Simulator
10
+ *
11
+ * This file integrates all AI components into a cohesive system for managing
12
+ * villager AI in a medieval town simulator.
13
+ */
14
+
15
+ class VillageAISystem {
16
+ constructor(scene) {
17
+ this.scene = scene;
18
+ this.pathfinder = new VillagePathfinder();
19
+ this.crowdManager = new CrowdManager();
20
+ this.routineManager = new DailyRoutineManager();
21
+ this.environmentSystem = new EnvironmentInteractionSystem();
22
+ this.entityManager = new AIEntityManager();
23
+
24
+ // Initialize systems
25
+ this.initSystems();
26
+ }
27
+
28
+ /**
29
+ * Initialize all AI systems
30
+ */
31
+ initSystems() {
32
+ // Initialize pathfinding with a simple plane for now
33
+ // In a real implementation, this would use a navigation mesh
34
+ // Note: We're not using THREE.js directly in the AI system anymore
35
+ // The nav mesh is just a placeholder object
36
+ this.pathfinder.initNavMesh({});
37
+
38
+ // Create some sample resources
39
+ this.createSampleResources();
40
+
41
+ // Create some sample buildings
42
+ this.createSampleBuildings();
43
+ }
44
+
45
+ /**
46
+ * Create sample resources for the village
47
+ */
48
+ createSampleResources() {
49
+ const woodResource = new Resource('wood1', 'wood', [10, 0, 10], 100);
50
+ const stoneResource = new Resource('stone1', 'stone', [-10, 0, -10], 100);
51
+ const foodResource = new Resource('food1', 'food', [0, 0, 15], 100);
52
+
53
+ this.environmentSystem.addResource('wood1', woodResource);
54
+ this.environmentSystem.addResource('stone1', stoneResource);
55
+ this.environmentSystem.addResource('food1', foodResource);
56
+ }
57
+
58
+ /**
59
+ * Create sample buildings for the village
60
+ */
61
+ createSampleBuildings() {
62
+ // Create a proper square village layout with buildings around a central square
63
+ const squareSize = 20; // Size of the central square
64
+ const buildingSpacing = 8; // Distance between buildings
65
+ let buildingId = 1;
66
+
67
+ // Define building types and their positions around the square
68
+ const buildingConfigs = [
69
+ { type: 'university', x: -squareSize, z: -squareSize, maxOccupancy: 8, useRadius: 6 },
70
+ { type: 'store', x: squareSize, z: -squareSize, maxOccupancy: 4, useRadius: 4 },
71
+ { type: 'bank', x: -squareSize, z: squareSize, maxOccupancy: 3, useRadius: 4 },
72
+ { type: 'hospital', x: squareSize, z: squareSize, maxOccupancy: 5, useRadius: 5 },
73
+ { type: 'market', x: 0, z: -squareSize - buildingSpacing, maxOccupancy: 10, useRadius: 8 },
74
+ { type: 'restaurant', x: 0, z: squareSize + buildingSpacing, maxOccupancy: 6, useRadius: 5 },
75
+ { type: 'workshop', x: -squareSize - buildingSpacing, z: 0, maxOccupancy: 4, useRadius: 4 },
76
+ { type: 'house', x: squareSize + buildingSpacing, z: 0, maxOccupancy: 3, useRadius: 3 }
77
+ ];
78
+
79
+ // Add buildings around the square
80
+ buildingConfigs.forEach(config => {
81
+ const building = new Building(`building${buildingId}`, config.type, [config.x, 0, config.z]);
82
+ building.maxOccupancy = config.maxOccupancy;
83
+ building.useRadius = config.useRadius;
84
+ this.environmentSystem.addBuilding(`building${buildingId}`, building);
85
+ buildingId++;
86
+ });
87
+
88
+ // Add houses around the perimeter
89
+ const housePositions = [
90
+ [-squareSize, 0, -squareSize + buildingSpacing],
91
+ [-squareSize + buildingSpacing, 0, -squareSize],
92
+ [squareSize - buildingSpacing, 0, -squareSize],
93
+ [squareSize, 0, -squareSize + buildingSpacing],
94
+ [-squareSize, 0, squareSize - buildingSpacing],
95
+ [-squareSize + buildingSpacing, 0, squareSize],
96
+ [squareSize - buildingSpacing, 0, squareSize],
97
+ [squareSize, 0, squareSize - buildingSpacing]
98
+ ];
99
+
100
+ housePositions.forEach((pos, index) => {
101
+ const house = new Building(`house${index + 1}`, 'house', pos);
102
+ house.maxOccupancy = 3;
103
+ house.useRadius = 3;
104
+ this.environmentSystem.addBuilding(`house${index + 1}`, house);
105
+ });
106
+
107
+ // Add some resources scattered around
108
+ const resources = [
109
+ new Resource('wood1', 'wood', [-squareSize - 5, 0, -squareSize - 5], 100),
110
+ new Resource('wood2', 'wood', [squareSize + 5, 0, squareSize + 5], 100),
111
+ new Resource('stone1', 'stone', [-squareSize - 5, 0, squareSize + 5], 100),
112
+ new Resource('stone2', 'stone', [squareSize + 5, 0, -squareSize - 5], 100),
113
+ new Resource('food1', 'food', [0, 0, -squareSize - 10], 100),
114
+ new Resource('food2', 'food', [0, 0, squareSize + 10], 100)
115
+ ];
116
+
117
+ resources.forEach(resource => {
118
+ this.environmentSystem.addResource(resource.id, resource);
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Create a villager and add it to the simulation
124
+ * @param {string} id - Villager ID
125
+ * @param {Array} position - Initial position [x, y, z]
126
+ * @returns {Villager} Created villager
127
+ */
128
+ createVillager(id, position) {
129
+ // Convert THREE.Vector3 to array if needed
130
+ let posArray = position;
131
+ if (position && typeof position.x !== 'undefined') {
132
+ posArray = [position.x, position.y, position.z];
133
+ }
134
+
135
+ const villager = new Villager(id, posArray);
136
+
137
+ // Add to systems
138
+ this.crowdManager.addVillager(villager);
139
+ this.entityManager.addEntity(villager);
140
+
141
+ // Create a schedule for the villager
142
+ const workBuilding = this.environmentSystem.findNearestBuilding(posArray, 'workshop');
143
+ const homeBuilding = this.environmentSystem.findNearestBuilding(posArray, 'house');
144
+ const marketBuilding = this.environmentSystem.findNearestBuilding(posArray, 'market');
145
+
146
+ const workLocation = workBuilding ? workBuilding.position : [0, 0, 0];
147
+ const homeLocation = homeBuilding ? homeBuilding.position : [0, 0, 0];
148
+ const socialLocations = marketBuilding ? [marketBuilding.position] : [[0, 0, 0]];
149
+
150
+ this.routineManager.createDefaultSchedule(id, workLocation, homeLocation, socialLocations);
151
+
152
+ return villager;
153
+ }
154
+
155
+ /**
156
+ * Update the AI system
157
+ * @param {number} deltaTime - Time since last frame
158
+ */
159
+ update(deltaTime) {
160
+ // Update routine manager
161
+ this.routineManager.update(deltaTime);
162
+
163
+ // Update entity manager (includes optimization techniques)
164
+ this.entityManager.update(deltaTime);
165
+
166
+ // Update crowd manager
167
+ this.crowdManager.update(deltaTime);
168
+
169
+ // Update each villager
170
+ for (const villager of this.crowdManager.villagers) {
171
+ // Update villager with routine manager and pathfinder
172
+ villager.update(this.routineManager, this.pathfinder, deltaTime);
173
+
174
+ // Handle environmental interactions
175
+ if (villager.state === 'work' && villager.path.length === 0) {
176
+ // Try to gather resources
177
+ const nearestWood = this.environmentSystem.findNearestResource(villager.position, 'wood');
178
+ if (nearestWood && this.environmentSystem.calculateDistance(villager.position, nearestWood.position) <= nearestWood.gatherRadius) {
179
+ this.environmentSystem.gatherResource(villager, nearestWood);
180
+ }
181
+ }
182
+
183
+ if (villager.state === 'socialize' && villager.path.length === 0) {
184
+ // Try to use market
185
+ const market = this.environmentSystem.findNearestBuilding(villager.position, 'market');
186
+ if (market) {
187
+ this.environmentSystem.useBuilding(villager, market);
188
+ }
189
+ }
190
+
191
+ // Move along path if there is one
192
+ if (villager.path.length > 0) {
193
+ this.pathfinder.moveAlongPath(villager, villager.path, villager.maxSpeed, deltaTime);
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ export default VillageAISystem;
src/ai/optimization.js ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Performance Optimization Techniques for Multiple AI Entities
3
+ *
4
+ * This implementation includes various optimization techniques to efficiently
5
+ * manage multiple AI entities in a village simulation.
6
+ */
7
+
8
+ class AIEntityManager {
9
+ constructor() {
10
+ this.entities = [];
11
+ this.activeEntities = new Set(); // Set of currently active entities
12
+ this.updateQueue = []; // Queue for entity updates
13
+ this.maxUpdatesPerFrame = 10; // Maximum entities to update per frame
14
+ this.spatialPartitioning = new SpatialPartitioning(100, 100, 10); // 100x100 grid with 10 unit cells
15
+ }
16
+
17
+ /**
18
+ * Add an entity to the manager
19
+ * @param {Object} entity - Entity to add
20
+ */
21
+ addEntity(entity) {
22
+ this.entities.push(entity);
23
+ this.spatialPartitioning.insert(entity);
24
+ }
25
+
26
+ /**
27
+ * Remove an entity from the manager
28
+ * @param {Object} entity - Entity to remove
29
+ */
30
+ removeEntity(entity) {
31
+ const index = this.entities.indexOf(entity);
32
+ if (index !== -1) {
33
+ this.entities.splice(index, 1);
34
+ }
35
+ this.spatialPartitioning.remove(entity);
36
+ this.activeEntities.delete(entity);
37
+ }
38
+
39
+ /**
40
+ * Update all entities with optimization techniques
41
+ * @param {number} deltaTime - Time since last frame
42
+ */
43
+ update(deltaTime) {
44
+ // Update spatial partitioning
45
+ this.spatialPartitioning.update();
46
+
47
+ // Reset active entities
48
+ this.activeEntities.clear();
49
+
50
+ // Determine active entities (those near the camera or player)
51
+ this.determineActiveEntities();
52
+
53
+ // Update entity queue
54
+ this.updateEntityQueue();
55
+
56
+ // Update entities
57
+ const updatesThisFrame = Math.min(this.maxUpdatesPerFrame, this.updateQueue.length);
58
+ for (let i = 0; i < updatesThisFrame; i++) {
59
+ const entity = this.updateQueue.shift();
60
+ if (entity) {
61
+ // Note: Entity updates are handled by the main system
62
+ // This is just for optimization management
63
+ this.updateQueue.push(entity); // Put back at end of queue
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Determine which entities are active based on proximity to camera
70
+ */
71
+ determineActiveEntities() {
72
+ // In a real implementation, this would use the camera position
73
+ // For now, we'll mark all entities as active
74
+ for (const entity of this.entities) {
75
+ this.activeEntities.add(entity);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Update the entity queue with active entities
81
+ */
82
+ updateEntityQueue() {
83
+ // Add newly active entities to queue
84
+ for (const entity of this.activeEntities) {
85
+ if (!this.updateQueue.includes(entity)) {
86
+ this.updateQueue.push(entity);
87
+ }
88
+ }
89
+
90
+ // Remove inactive entities from queue
91
+ this.updateQueue = this.updateQueue.filter(entity => this.activeEntities.has(entity));
92
+ }
93
+
94
+ /**
95
+ * Get nearby entities for a position
96
+ * @param {Array} position - Position to check [x, y, z]
97
+ * @param {number} radius - Radius to check
98
+ * @returns {Array} Nearby entities
99
+ */
100
+ getNearbyEntities(position, radius) {
101
+ return this.spatialPartitioning.query(position, radius);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Spatial Partitioning for efficient entity queries
107
+ */
108
+ class SpatialPartitioning {
109
+ constructor(width, height, cellSize) {
110
+ this.width = width;
111
+ this.height = height;
112
+ this.cellSize = cellSize;
113
+ this.grid = new Map(); // Map of cell keys to arrays of entities
114
+ this.entityToCell = new Map(); // Map of entities to their current cell
115
+ }
116
+
117
+ /**
118
+ * Get cell key for a position
119
+ * @param {Array} position - Position to get cell for [x, y, z]
120
+ * @returns {string} Cell key
121
+ */
122
+ getCellKey(position) {
123
+ const col = Math.floor(position[0] / this.cellSize);
124
+ const row = Math.floor(position[2] / this.cellSize);
125
+ return `${col},${row}`;
126
+ }
127
+
128
+ /**
129
+ * Insert an entity into the spatial partitioning
130
+ * @param {Object} entity - Entity to insert
131
+ */
132
+ insert(entity) {
133
+ const key = this.getCellKey(entity.position);
134
+ if (!this.grid.has(key)) {
135
+ this.grid.set(key, []);
136
+ }
137
+ this.grid.get(key).push(entity);
138
+ this.entityToCell.set(entity, key);
139
+ }
140
+
141
+ /**
142
+ * Remove an entity from the spatial partitioning
143
+ * @param {Object} entity - Entity to remove
144
+ */
145
+ remove(entity) {
146
+ const key = this.entityToCell.get(entity);
147
+ if (key && this.grid.has(key)) {
148
+ const cell = this.grid.get(key);
149
+ const index = cell.indexOf(entity);
150
+ if (index !== -1) {
151
+ cell.splice(index, 1);
152
+ }
153
+ }
154
+ this.entityToCell.delete(entity);
155
+ }
156
+
157
+ /**
158
+ * Update an entity's position in the spatial partitioning
159
+ * @param {Object} entity - Entity to update
160
+ */
161
+ updateEntity(entity) {
162
+ const oldKey = this.entityToCell.get(entity);
163
+ const newKey = this.getCellKey(entity.position);
164
+
165
+ if (oldKey !== newKey) {
166
+ // Remove from old cell
167
+ if (oldKey && this.grid.has(oldKey)) {
168
+ const oldCell = this.grid.get(oldKey);
169
+ const index = oldCell.indexOf(entity);
170
+ if (index !== -1) {
171
+ oldCell.splice(index, 1);
172
+ }
173
+ }
174
+
175
+ // Add to new cell
176
+ if (!this.grid.has(newKey)) {
177
+ this.grid.set(newKey, []);
178
+ }
179
+ this.grid.get(newKey).push(entity);
180
+ this.entityToCell.set(entity, newKey);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Update all entities in the spatial partitioning
186
+ */
187
+ update() {
188
+ for (const entity of this.entityToCell.keys()) {
189
+ this.updateEntity(entity);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Query entities within a radius
195
+ * @param {Array} position - Center position [x, y, z]
196
+ * @param {number} radius - Query radius
197
+ * @returns {Array} Entities within radius
198
+ */
199
+ query(position, radius) {
200
+ const results = [];
201
+ const col = Math.floor(position[0] / this.cellSize);
202
+ const row = Math.floor(position[2] / this.cellSize);
203
+ const radiusCells = Math.ceil(radius / this.cellSize);
204
+
205
+ // Check surrounding cells
206
+ for (let r = row - radiusCells; r <= row + radiusCells; r++) {
207
+ for (let c = col - radiusCells; c <= col + radiusCells; c++) {
208
+ const key = `${c},${r}`;
209
+ if (this.grid.has(key)) {
210
+ const cell = this.grid.get(key);
211
+ for (const entity of cell) {
212
+ if (this.calculateDistance(position, entity.position) <= radius) {
213
+ results.push(entity);
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ return results;
221
+ }
222
+
223
+ /**
224
+ * Calculate distance between two positions
225
+ * @param {Array} pos1 - First position [x, y, z]
226
+ * @param {Array} pos2 - Second position [x, y, z]
227
+ * @returns {number} Distance between positions
228
+ */
229
+ calculateDistance(pos1, pos2) {
230
+ return Math.sqrt(
231
+ Math.pow(pos1[0] - pos2[0], 2) +
232
+ Math.pow(pos1[1] - pos2[1], 2) +
233
+ Math.pow(pos1[2] - pos2[2], 2)
234
+ );
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Object pooling for AI entities to reduce garbage collection
240
+ */
241
+ class ObjectPool {
242
+ constructor(createFn, resetFn, initialSize = 10) {
243
+ this.createFn = createFn;
244
+ this.resetFn = resetFn;
245
+ this.pool = [];
246
+
247
+ // Pre-populate pool
248
+ for (let i = 0; i < initialSize; i++) {
249
+ this.pool.push(this.createFn());
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Get an object from the pool
255
+ * @returns {Object} Object from pool or new object if pool is empty
256
+ */
257
+ acquire() {
258
+ if (this.pool.length > 0) {
259
+ return this.pool.pop();
260
+ }
261
+ return this.createFn();
262
+ }
263
+
264
+ /**
265
+ * Return an object to the pool
266
+ * @param {Object} object - Object to return
267
+ */
268
+ release(object) {
269
+ this.resetFn(object);
270
+ this.pool.push(object);
271
+ }
272
+ }
273
+
274
+ export { AIEntityManager, SpatialPartitioning, ObjectPool };
src/ai/pathfinding.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Note: three-pathfinding is not available via CDN, using basic pathfinding instead
2
+
3
+ /**
4
+ * A* Pathfinding Implementation for Village Navigation
5
+ *
6
+ * This implementation uses a navigation mesh approach for efficient pathfinding
7
+ * in complex 3D environments like medieval villages.
8
+ */
9
+ class VillagePathfinder {
10
+ constructor() {
11
+ // Simple pathfinding implementation without external library
12
+ this.zoneName = 'village';
13
+ }
14
+
15
+ /**
16
+ * Initialize the navigation mesh
17
+ * @param {Object} navMeshGeometry - The navigation mesh geometry
18
+ */
19
+ initNavMesh(navMeshGeometry) {
20
+ // Store the navigation mesh for future use
21
+ this.navMesh = navMeshGeometry;
22
+ console.log('Navigation mesh initialized');
23
+ }
24
+
25
+ /**
26
+ * Find a path between two points
27
+ * @param {Array} start - Start position [x, y, z]
28
+ * @param {Array} end - End position [x, y, z]
29
+ * @returns {Array<Array>} Path as array of points [[x, y, z], ...]
30
+ */
31
+ findPath(start, end) {
32
+ // Simple direct path for now
33
+ // In a real implementation, this would use A* algorithm
34
+ return [
35
+ [start[0], start[1], start[2]],
36
+ [end[0], end[1], end[2]]
37
+ ];
38
+ }
39
+
40
+ /**
41
+ * Move an entity along a path
42
+ * @param {Object} entity - The entity to move
43
+ * @param {Array<Array>} path - Path to follow [[x, y, z], ...]
44
+ * @param {number} speed - Movement speed
45
+ * @param {number} deltaTime - Time since last frame
46
+ */
47
+ moveAlongPath(entity, path, speed, deltaTime) {
48
+ if (path.length === 0) return;
49
+
50
+ const target = path[0];
51
+ const direction = this.normalize([
52
+ target[0] - entity.position[0],
53
+ target[1] - entity.position[1],
54
+ target[2] - entity.position[2]
55
+ ]);
56
+ const moveDistance = speed * deltaTime;
57
+
58
+ // Move towards the target
59
+ entity.position[0] += direction[0] * moveDistance;
60
+ entity.position[1] += direction[1] * moveDistance;
61
+ entity.position[2] += direction[2] * moveDistance;
62
+
63
+ // Check if we've reached the target point
64
+ const distance = Math.sqrt(
65
+ Math.pow(entity.position[0] - target[0], 2) +
66
+ Math.pow(entity.position[1] - target[1], 2) +
67
+ Math.pow(entity.position[2] - target[2], 2)
68
+ );
69
+
70
+ if (distance < 0.1) {
71
+ path.shift(); // Remove the reached point
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Normalize a vector
77
+ * @param {Array} vector - Vector to normalize [x, y, z]
78
+ * @returns {Array} Normalized vector [x, y, z]
79
+ */
80
+ normalize(vector) {
81
+ const length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
82
+ if (length === 0) return [0, 0, 0];
83
+ return [vector[0] / length, vector[1] / length, vector[2] / length];
84
+ }
85
+ }
86
+
87
+ export default VillagePathfinder;
src/ai/routines.js ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // No direct THREE.js import needed
2
+
3
+ /**
4
+ * Daily Routine System for Villager AI
5
+ *
6
+ * This implementation manages villager daily routines including work, rest, and social interactions.
7
+ * It uses a schedule-based approach with some randomness to create natural-looking behavior.
8
+ */
9
+
10
+ class DailyRoutineManager {
11
+ constructor() {
12
+ this.schedules = new Map(); // Map of villager IDs to schedules
13
+ this.currentTime = 0; // In-game time in hours (0-24)
14
+ }
15
+
16
+ /**
17
+ * Set a schedule for a villager
18
+ * @param {string} villagerId - Villager identifier
19
+ * @param {Array} schedule - Array of schedule entries
20
+ */
21
+ setSchedule(villagerId, schedule) {
22
+ this.schedules.set(villagerId, schedule);
23
+ }
24
+
25
+ /**
26
+ * Update the routine manager
27
+ * @param {number} deltaTime - Time since last update in hours
28
+ */
29
+ update(deltaTime) {
30
+ this.currentTime += deltaTime;
31
+ if (this.currentTime >= 24) {
32
+ this.currentTime -= 24; // Wrap around to next day
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Get current activity for a villager
38
+ * @param {string} villagerId - Villager identifier
39
+ * @returns {Object} Current activity
40
+ */
41
+ getCurrentActivity(villagerId) {
42
+ const schedule = this.schedules.get(villagerId);
43
+ if (!schedule) return null;
44
+
45
+ // Find the current activity based on time
46
+ for (let i = schedule.length - 1; i >= 0; i--) {
47
+ if (this.currentTime >= schedule[i].startTime) {
48
+ return schedule[i];
49
+ }
50
+ }
51
+
52
+ // If no activity found, return the last one (for times before first activity)
53
+ return schedule[schedule.length - 1];
54
+ }
55
+
56
+ /**
57
+ * Create a default schedule for a villager
58
+ * @param {string} villagerId - Villager identifier
59
+ * @param {Object} workLocation - Work location
60
+ * @param {Object} homeLocation - Home location
61
+ * @param {Array} socialLocations - Social locations
62
+ */
63
+ createDefaultSchedule(villagerId, workLocation, homeLocation, socialLocations) {
64
+ const schedule = [
65
+ { startTime: 0, activity: 'sleep', location: homeLocation },
66
+ { startTime: 7, activity: 'eat', location: homeLocation },
67
+ { startTime: 8, activity: 'work', location: workLocation },
68
+ { startTime: 12, activity: 'eat', location: workLocation },
69
+ { startTime: 13, activity: 'work', location: workLocation },
70
+ { startTime: 17, activity: 'socialize', location: socialLocations[0] },
71
+ { startTime: 19, activity: 'eat', location: homeLocation },
72
+ { startTime: 20, activity: 'sleep', location: homeLocation }
73
+ ];
74
+
75
+ this.setSchedule(villagerId, schedule);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Villager class with routine-based behavior
81
+ */
82
+ class Villager {
83
+ constructor(id, position) {
84
+ this.id = id;
85
+ this.position = position;
86
+ this.velocity = [0, 0, 0]; // Simple array instead of THREE.Vector3
87
+ this.maxSpeed = 2;
88
+ this.energy = 100;
89
+ this.hunger = 0;
90
+ this.socialNeed = 0;
91
+ this.state = 'idle'; // idle, working, sleeping, eating, socializing
92
+ this.path = [];
93
+ this.avoidanceRadius = 2;
94
+ this.avoidanceWeight = 1.5;
95
+ this.steeringWeight = 1.0;
96
+ }
97
+
98
+ /**
99
+ * Calculate distance between two points
100
+ * @param {Array} pos1 - First position [x, y, z]
101
+ * @param {Array} pos2 - Second position [x, y, z]
102
+ * @returns {number} Distance between points
103
+ */
104
+ calculateDistance(pos1, pos2) {
105
+ return Math.sqrt(
106
+ Math.pow(pos1[0] - pos2[0], 2) +
107
+ Math.pow(pos1[1] - pos2[1], 2) +
108
+ Math.pow(pos1[2] - pos2[2], 2)
109
+ );
110
+ }
111
+
112
+ /**
113
+ * Update villager state
114
+ * @param {DailyRoutineManager} routineManager - Routine manager
115
+ * @param {VillagePathfinder} pathfinder - Pathfinding system
116
+ * @param {number} deltaTime - Time since last update
117
+ */
118
+ update(routineManager, pathfinder, deltaTime) {
119
+ // Update needs
120
+ this.updateNeeds(deltaTime);
121
+
122
+ // Get current activity
123
+ const activity = routineManager.getCurrentActivity(this.id);
124
+
125
+ // Update state based on activity
126
+ if (activity) {
127
+ this.state = activity.activity;
128
+
129
+ // If we have a location for this activity, pathfind to it
130
+ if (activity.location && this.calculateDistance(this.position, activity.location) > 1) {
131
+ this.path = pathfinder.findPath(this.position, activity.location);
132
+ } else {
133
+ this.path = [];
134
+ }
135
+ }
136
+
137
+ // Perform state-specific actions
138
+ switch (this.state) {
139
+ case 'sleep':
140
+ this.sleep(deltaTime);
141
+ break;
142
+ case 'work':
143
+ this.work(deltaTime);
144
+ break;
145
+ case 'eat':
146
+ this.eat(deltaTime);
147
+ break;
148
+ case 'socialize':
149
+ this.socialize(deltaTime);
150
+ break;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Update villager needs over time
156
+ * @param {number} deltaTime - Time since last update
157
+ */
158
+ updateNeeds(deltaTime) {
159
+ this.energy = Math.max(0, this.energy - 0.1 * deltaTime);
160
+ this.hunger = Math.min(100, this.hunger + 0.05 * deltaTime);
161
+ this.socialNeed = Math.min(100, this.socialNeed + 0.03 * deltaTime);
162
+ }
163
+
164
+ /**
165
+ * Check if villager is tired
166
+ * @returns {boolean} True if tired
167
+ */
168
+ isTired() {
169
+ return this.energy < 20;
170
+ }
171
+
172
+ /**
173
+ * Check if villager should work
174
+ * @returns {boolean} True if should work
175
+ */
176
+ shouldWork() {
177
+ return this.energy > 30 && this.hunger < 80;
178
+ }
179
+
180
+ /**
181
+ * Check if villager should socialize
182
+ * @returns {boolean} True if should socialize
183
+ */
184
+ shouldSocialize() {
185
+ return this.energy > 40 && this.socialNeed > 50;
186
+ }
187
+
188
+ /**
189
+ * Sleep action
190
+ * @param {number} deltaTime - Time since last update
191
+ */
192
+ sleep(deltaTime) {
193
+ this.energy = Math.min(100, this.energy + 0.5 * deltaTime);
194
+ }
195
+
196
+ /**
197
+ * Work action
198
+ * @param {number} deltaTime - Time since last update
199
+ */
200
+ work(deltaTime) {
201
+ this.energy = Math.max(0, this.energy - 0.2 * deltaTime);
202
+ }
203
+
204
+ /**
205
+ * Eat action
206
+ * @param {number} deltaTime - Time since last update
207
+ */
208
+ eat(deltaTime) {
209
+ this.hunger = Math.max(0, this.hunger - 0.8 * deltaTime);
210
+ }
211
+
212
+ /**
213
+ * Socialize action
214
+ * @param {number} deltaTime - Time since last update
215
+ */
216
+ socialize(deltaTime) {
217
+ this.socialNeed = Math.max(0, this.socialNeed - 0.6 * deltaTime);
218
+ }
219
+
220
+ /**
221
+ * Idle action
222
+ */
223
+ idle() {
224
+ // Do nothing
225
+ }
226
+ }
227
+
228
+ export { DailyRoutineManager, Villager };
src/ai/routines_simple.js ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Daily Routine System for Villager AI (Simplified Version)
3
+ *
4
+ * This implementation manages villager daily routines including work, rest, and social interactions.
5
+ * Uses simple arrays for positions instead of THREE.Vector3 to avoid import issues.
6
+ */
7
+
8
+ class DailyRoutineManager {
9
+ constructor() {
10
+ this.schedules = new Map(); // Map of villager IDs to schedules
11
+ this.currentTime = 0; // In-game time in hours (0-24)
12
+ }
13
+
14
+ /**
15
+ * Set a schedule for a villager
16
+ * @param {string} villagerId - Villager identifier
17
+ * @param {Array} schedule - Array of schedule entries
18
+ */
19
+ setSchedule(villagerId, schedule) {
20
+ this.schedules.set(villagerId, schedule);
21
+ }
22
+
23
+ /**
24
+ * Update the routine manager
25
+ * @param {number} deltaTime - Time since last update in hours
26
+ */
27
+ update(deltaTime) {
28
+ this.currentTime += deltaTime;
29
+ if (this.currentTime >= 24) {
30
+ this.currentTime -= 24; // Wrap around to next day
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Get current activity for a villager
36
+ * @param {string} villagerId - Villager identifier
37
+ * @returns {Object} Current activity
38
+ */
39
+ getCurrentActivity(villagerId) {
40
+ const schedule = this.schedules.get(villagerId);
41
+ if (!schedule) return null;
42
+
43
+ // Find the current activity based on time
44
+ for (let i = schedule.length - 1; i >= 0; i--) {
45
+ if (this.currentTime >= schedule[i].startTime) {
46
+ return schedule[i];
47
+ }
48
+ }
49
+
50
+ // If no activity found, return the last one (for times before first activity)
51
+ return schedule[schedule.length - 1];
52
+ }
53
+
54
+ /**
55
+ * Create a default schedule for a villager
56
+ * @param {string} villagerId - Villager identifier
57
+ * @param {Array} workLocation - Work location [x, y, z]
58
+ * @param {Array} homeLocation - Home location [x, y, z]
59
+ * @param {Array} socialLocations - Social locations [[x, y, z], ...]
60
+ */
61
+ createDefaultSchedule(villagerId, workLocation, homeLocation, socialLocations) {
62
+ const schedule = [
63
+ { startTime: 0, activity: 'sleep', location: homeLocation },
64
+ { startTime: 7, activity: 'eat', location: homeLocation },
65
+ { startTime: 8, activity: 'work', location: workLocation },
66
+ { startTime: 12, activity: 'eat', location: workLocation },
67
+ { startTime: 13, activity: 'work', location: workLocation },
68
+ { startTime: 17, activity: 'socialize', location: socialLocations[0] },
69
+ { startTime: 19, activity: 'eat', location: homeLocation },
70
+ { startTime: 20, activity: 'sleep', location: homeLocation }
71
+ ];
72
+
73
+ this.setSchedule(villagerId, schedule);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Villager class with routine-based behavior (Simplified Version)
79
+ */
80
+ class Villager {
81
+ constructor(id, position) {
82
+ this.id = id;
83
+ this.position = position; // [x, y, z] array
84
+ this.velocity = [0, 0, 0]; // [x, y, z] array
85
+ this.maxSpeed = 2;
86
+ this.energy = 100;
87
+ this.hunger = 0;
88
+ this.socialNeed = 0;
89
+ this.state = 'idle'; // idle, working, sleeping, eating, socializing
90
+ this.path = [];
91
+ this.avoidanceRadius = 2;
92
+ this.avoidanceWeight = 1.5;
93
+ this.steeringWeight = 1.0;
94
+ }
95
+
96
+ /**
97
+ * Update villager state
98
+ * @param {DailyRoutineManager} routineManager - Routine manager
99
+ * @param {VillagePathfinder} pathfinder - Pathfinding system
100
+ * @param {number} deltaTime - Time since last update
101
+ */
102
+ update(routineManager, pathfinder, deltaTime) {
103
+ // Update needs
104
+ this.updateNeeds(deltaTime);
105
+
106
+ // Get current activity
107
+ const activity = routineManager.getCurrentActivity(this.id);
108
+
109
+ // Update state based on activity
110
+ if (activity) {
111
+ this.state = activity.activity;
112
+
113
+ // If we have a location for this activity, pathfind to it
114
+ if (activity.location && this.distanceTo(activity.location) > 1) {
115
+ this.path = pathfinder.findPath(this.position, activity.location);
116
+ } else {
117
+ this.path = [];
118
+ }
119
+ }
120
+
121
+ // Perform state-specific actions
122
+ switch (this.state) {
123
+ case 'sleep':
124
+ this.sleep(deltaTime);
125
+ break;
126
+ case 'work':
127
+ this.work(deltaTime);
128
+ break;
129
+ case 'eat':
130
+ this.eat(deltaTime);
131
+ break;
132
+ case 'socialize':
133
+ this.socialize(deltaTime);
134
+ break;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Calculate distance to another position
140
+ * @param {Array} other - Other position [x, y, z]
141
+ * @returns {number} Distance
142
+ */
143
+ distanceTo(other) {
144
+ const dx = this.position[0] - other[0];
145
+ const dy = this.position[1] - other[1];
146
+ const dz = this.position[2] - other[2];
147
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
148
+ }
149
+
150
+ /**
151
+ * Update villager needs over time
152
+ * @param {number} deltaTime - Time since last update
153
+ */
154
+ updateNeeds(deltaTime) {
155
+ this.energy = Math.max(0, this.energy - 0.1 * deltaTime);
156
+ this.hunger = Math.min(100, this.hunger + 0.05 * deltaTime);
157
+ this.socialNeed = Math.min(100, this.socialNeed + 0.03 * deltaTime);
158
+ }
159
+
160
+ /**
161
+ * Check if villager is tired
162
+ * @returns {boolean} True if tired
163
+ */
164
+ isTired() {
165
+ return this.energy < 20;
166
+ }
167
+
168
+ /**
169
+ * Check if villager should work
170
+ * @returns {boolean} True if should work
171
+ */
172
+ shouldWork() {
173
+ return this.energy > 30 && this.hunger < 80;
174
+ }
175
+
176
+ /**
177
+ * Check if villager should socialize
178
+ * @returns {boolean} True if should socialize
179
+ */
180
+ shouldSocialize() {
181
+ return this.energy > 40 && this.socialNeed > 50;
182
+ }
183
+
184
+ /**
185
+ * Sleep action
186
+ * @param {number} deltaTime - Time since last update
187
+ */
188
+ sleep(deltaTime) {
189
+ this.energy = Math.min(100, this.energy + 0.5 * deltaTime);
190
+ }
191
+
192
+ /**
193
+ * Work action
194
+ * @param {number} deltaTime - Time since last update
195
+ */
196
+ work(deltaTime) {
197
+ this.energy = Math.max(0, this.energy - 0.2 * deltaTime);
198
+ }
199
+
200
+ /**
201
+ * Eat action
202
+ * @param {number} deltaTime - Time since last update
203
+ */
204
+ eat(deltaTime) {
205
+ this.hunger = Math.max(0, this.hunger - 0.8 * deltaTime);
206
+ }
207
+
208
+ /**
209
+ * Socialize action
210
+ * @param {number} deltaTime - Time since last update
211
+ */
212
+ socialize(deltaTime) {
213
+ this.socialNeed = Math.max(0, this.socialNeed - 0.6 * deltaTime);
214
+ }
215
+
216
+ /**
217
+ * Idle action
218
+ */
219
+ idle() {
220
+ // Do nothing
221
+ }
222
+ }
223
+
224
+ export { DailyRoutineManager, Villager };