awacke1 commited on
Commit
c7dea45
·
verified ·
1 Parent(s): 062181d

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +331 -86
index.html CHANGED
@@ -3,11 +3,12 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>World Grid Test (Startup Debug)</title>
7
  <style>
 
8
  body { font-family: 'Courier New', monospace; background-color: #111; color: #eee; margin: 0; padding: 0; overflow: hidden; display: flex; flex-direction: column; height: 100vh; }
9
  #game-container { display: flex; flex-grow: 1; overflow: hidden; }
10
- #scene-container { flex-grow: 3; position: relative; border-right: 2px solid #444; min-width: 250px; background-color: #000; height: 100%; box-sizing: border-box; overflow: hidden; cursor: default; }
11
  #ui-container { flex-grow: 2; padding: 25px; overflow-y: auto; background-color: #2b2b2b; min-width: 320px; height: 100%; box-sizing: border-box; display: flex; flex-direction: column; }
12
  #scene-container canvas { display: block; }
13
  #story-title { color: #f0c060; margin: 0 0 15px 0; padding-bottom: 10px; border-bottom: 1px solid #555; font-size: 1.6em; text-shadow: 1px 1px 1px #000; }
@@ -15,17 +16,26 @@
15
  #stats-inventory-container { margin-bottom: 25px; padding: 15px; border: 1px solid #444; border-radius: 4px; background-color: #333; font-size: 0.95em; }
16
  #stats-display, #inventory-display { margin-bottom: 10px; line-height: 1.8; }
17
  #stats-display span, #inventory-display .item-tag { display: inline-block; background-color: #484848; padding: 3px 9px; border-radius: 15px; margin: 0 8px 5px 0; border: 1px solid #6a6a6a; white-space: nowrap; box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); }
 
 
 
18
  #stats-display strong, #inventory-display strong { color: #ccc; margin-right: 6px; }
19
  #inventory-display em { color: #888; font-style: normal; }
 
 
 
 
 
20
  #choices-container { margin-top: auto; padding-top: 20px; border-top: 1px solid #555; }
21
  #choices-container h3 { margin-top: 0; margin-bottom: 12px; color: #ccc; font-size: 1.1em; }
22
  #choices { display: flex; flex-direction: column; gap: 12px; }
23
  .choice-button { display: block; width: 100%; padding: 12px 15px; margin-bottom: 0; background-color: #555; color: #eee; border: 1px solid #777; border-radius: 4px; cursor: pointer; text-align: left; font-family: 'Courier New', monospace; font-size: 1.05em; transition: background-color 0.2s, border-color 0.2s, box-shadow 0.1s; box-sizing: border-box; }
24
  .choice-button:hover:not(:disabled) { background-color: #e0b050; color: #111; border-color: #c89040; box-shadow: 0 0 5px rgba(255, 200, 100, 0.5); }
 
25
  .message { padding: 8px 12px; margin-bottom: 1em; border-left-width: 3px; border-left-style: solid; font-size: 0.95em; background-color: rgba(255, 255, 255, 0.05); border-color: #666; color: #aaa;}
26
  .message-failure { color: #f88; border-left-color: #a44; }
 
27
  #action-info { position: absolute; bottom: 10px; left: 10px; background-color: rgba(0,0,0,0.7); color: #ffcc66; padding: 5px 10px; border-radius: 3px; font-size: 0.9em; display: block; z-index: 10;}
28
-
29
  </style>
30
  </head>
31
  <body>
@@ -56,7 +66,7 @@
56
 
57
  <script type="module">
58
  import * as THREE from 'three';
59
- // import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // Temporarily removed
60
 
61
  console.log("Script module execution started.");
62
 
@@ -70,25 +80,31 @@
70
 
71
  console.log("DOM elements obtained.");
72
 
73
- let scene, camera, renderer, clock; // Removed controls, raycaster, mouse
74
  let worldGroup = null;
75
  let zoneGroups = {};
76
  let currentMessage = "";
77
  let currentLights = [];
 
78
 
79
- const MAT = { // Simplified materials
80
  stone: new THREE.MeshStandardMaterial({ color: 0x777788, roughness: 0.85 }),
81
  wood: new THREE.MeshStandardMaterial({ color: 0x9F6633, roughness: 0.75 }),
82
  leaf: new THREE.MeshStandardMaterial({ color: 0x3E9B4E, roughness: 0.6, side: THREE.DoubleSide }),
83
  ground: new THREE.MeshStandardMaterial({ color: 0x556B2F, roughness: 0.95 }),
84
  dirt: new THREE.MeshStandardMaterial({ color: 0x8B5E3C, roughness: 0.9 }),
85
  grass: new THREE.MeshStandardMaterial({ color: 0x4CB781, roughness: 0.85 }),
 
 
86
  };
87
 
88
  let gameState = {};
89
 
90
  const itemsData = {
91
- "Rock": {type:"unknown", description:"A simple rock."} // Minimal items
 
 
 
92
  };
93
 
94
  const zoneCreators = {};
@@ -101,11 +117,13 @@
101
  scene = new THREE.Scene();
102
  scene.background = new THREE.Color(0x1a1a1a);
103
  clock = new THREE.Clock();
 
 
104
  worldGroup = new THREE.Group();
105
  scene.add(worldGroup);
106
  console.log("Scene and worldGroup created.");
107
 
108
- const width = sceneContainer.clientWidth || 300; // Default size if 0
109
  const height = sceneContainer.clientHeight || 200;
110
  console.log(`Renderer dimensions: ${width}x${height}`);
111
 
@@ -116,21 +134,27 @@
116
 
117
  renderer = new THREE.WebGLRenderer({ antialias: true });
118
  renderer.setSize(width, height);
119
- renderer.shadowMap.enabled = true; // Basic shadows
120
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
121
  sceneContainer.appendChild(renderer.domElement);
122
  console.log("Renderer created and appended.");
123
 
124
- // controls = new OrbitControls(camera, renderer.domElement); // Removed
125
- // controls.target.set(0, 1, 0);
 
 
 
 
126
 
127
  window.addEventListener('resize', onWindowResize, false);
128
- setTimeout(onWindowResize, 100); // Try later resize
 
 
129
  animate();
130
  console.log("initThreeJS finished successfully.");
131
  } catch (error) {
132
  console.error("Error during initThreeJS:", error);
133
- throw error; // Re-throw to be caught by DOMContentLoaded listener
134
  }
135
  }
136
 
@@ -149,12 +173,27 @@
149
  }
150
  }
151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  let frameCount = 0;
153
  function animate() {
154
  frameCount++;
155
- if(frameCount % 300 === 0) console.log("Animate loop running..."); // Log every ~5 seconds
156
  requestAnimationFrame(animate);
157
- // controls?.update(); // Removed controls
158
  if (renderer && scene && camera) renderer.render(scene, camera);
159
  }
160
 
@@ -179,15 +218,19 @@
179
  function setupLighting(type = 'default') {
180
  console.log(`Setting up lighting for type: ${type}`);
181
  currentLights.forEach(light => {
182
- if (light && light.parent) light.parent.remove(light); // Remove from parent if attached
183
- if (light && scene.children.includes(light)) scene.remove(light); // Ensure removed from scene
184
  });
185
  currentLights = [];
186
 
187
- let ambientIntensity = 0.6; // Brighter default ambient
188
  let dirIntensity = 1.0;
189
  let dirColor = 0xffffff;
190
- let dirPosition = new THREE.Vector3(5, 10, 7); // Adjusted angle
 
 
 
 
191
 
192
  const ambientLight = new THREE.AmbientLight(0xffffff, ambientIntensity);
193
  scene.add(ambientLight);
@@ -197,38 +240,128 @@
197
  const directionalLight = new THREE.DirectionalLight(dirColor, dirIntensity);
198
  directionalLight.position.copy(dirPosition);
199
  directionalLight.castShadow = true;
200
- directionalLight.shadow.mapSize.set(512, 512); // Smaller shadow map for testing
 
 
 
 
 
201
  scene.add(directionalLight);
202
  currentLights.push(directionalLight);
203
  }
204
- console.log(`Lighting setup complete. Lights: ${currentLights.length}`);
 
 
 
 
 
 
 
205
  }
206
 
207
- // Simplified Zone Creation
208
  function createFieldZone(zoneId) {
209
  console.log(`Creating zone: ${zoneId} (Field)`);
210
  const group = new THREE.Group();
211
  group.add(createGround(MAT.grass, 30));
212
- const rockGeo = new THREE.IcosahedronGeometry(1, 0); // Bigger, simpler rock
213
- group.add(createMesh(rockGeo, MAT.stone, {x: 5, y:0.5, z: 5}));
 
 
 
 
214
  group.visible = false;
215
- return { group, lighting: 'default', title: "Open Field", entryText: `You are in ${zoneId}. It's grassy.`, options: [], zoneId: zoneId };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  }
217
- // Use Field for all zones for now
218
- const createForestZone = (zoneId) => createFieldZone(zoneId);
219
- const createCaveZone = (zoneId) => createFieldZone(zoneId);
220
- const createRuinsZone = (zoneId) => createFieldZone(zoneId);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
  function getZoneId(row, col) { return `zone_${row}_${col}`; }
223
 
224
  function populateZoneCreators() {
225
  console.log("Populating zone creators...");
226
- // No reassignment here
227
  for (let r = 0; r < MAP_ROWS; r++) {
228
  for (let c = 0; c < MAP_COLS; c++) {
229
  const zoneId = getZoneId(r, c);
230
- // Force all to be FieldZone for testing stability
231
- zoneCreators[zoneId] = () => createFieldZone(zoneId);
 
 
 
 
232
  }
233
  }
234
  console.log(`Zone creators populated with ${Object.keys(zoneCreators).length} entries.`);
@@ -252,7 +385,7 @@
252
  const defaultChar = {
253
  name: "Player",
254
  stats: { hp: 20, maxHp: 20, xp: 0 },
255
- inventory: []
256
  };
257
  gameState = {
258
  currentZoneId: null,
@@ -264,13 +397,14 @@
264
  worldGroup.remove(worldGroup.children[0]);
265
  }
266
  console.log("Starting new game state:", gameState);
267
- transitionToZone(getZoneId(1, 1));
268
  console.log("startGame finished.");
269
  }
270
 
271
  function transitionToZone(newZoneId) {
272
  console.log(`Attempting transition to ${newZoneId}`);
273
  currentMessage = "";
 
274
 
275
  if (gameState.currentZoneId && zoneGroups[gameState.currentZoneId]) {
276
  console.log(`Hiding old zone: ${gameState.currentZoneId}`);
@@ -296,72 +430,66 @@
296
  } catch (creationError) {
297
  console.error(`Error creating zone ${newZoneId}:`, creationError);
298
  currentMessage = `<p class="message message-failure">Error creating zone ${newZoneId}.</p>`;
299
- // Fallback to start or previous state might be needed here
300
- // For now, just log and potentially break rendering
301
- renderCurrentPageUI(); // Render even if zone creation failed to show error
302
- return; // Stop transition
303
  }
304
  } else {
305
  console.error(`No creator found for zone ID: ${newZoneId}, critical error.`);
306
  currentMessage = `<p class="message message-failure">Error: Zone creator missing for ${newZoneId}. Cannot proceed.</p>`;
307
- // Render minimal UI or stop
308
  storyTitleElement.textContent = "Fatal Error";
309
  storyContentElement.innerHTML = currentMessage;
310
  choicesElement.innerHTML = '';
311
- return; // Stop transition
312
  }
313
  }
314
 
315
  gameState.currentZoneId = newZoneId;
316
  setupLighting(zoneInfo.lighting || 'default');
317
 
318
- // Add point lights from lighting setup to the current group
 
319
  currentLights.forEach(light => {
320
- if (light && light.isPointLight) {
321
- if(light.parent) light.parent.remove(light); // Ensure it's not attached elsewhere
322
- zoneInfo.group.add(light);
323
- console.log("Added point light to zone group:", newZoneId);
324
- }
 
 
 
 
325
  });
326
 
327
  camera.position.set(0, 6, 12);
328
- // controls.target.set(0, 1, 0); // Controls removed
329
- // controls.update();
330
- camera.lookAt(0, 1, 0); // Look at center
331
 
332
  console.log(`Transition to ${newZoneId} complete. Rendering UI.`);
333
  renderCurrentPageUI();
334
  }
335
 
 
336
  function renderCurrentPageUI() {
337
  console.log(`renderCurrentPageUI called for zone: ${gameState.currentZoneId}`);
338
  const zoneInfo = zoneGroups[gameState.currentZoneId];
339
  const zoneId = gameState.currentZoneId;
340
 
341
- // Ensure critical elements exist before proceeding
342
  if (!storyTitleElement || !storyContentElement || !choicesElement || !statsElement || !inventoryElement || !actionInfoElement) {
343
- console.error("Crucial UI element missing!");
344
- alert("UI Error - Cannot render page. Check console.");
345
- return;
346
  }
347
-
348
  if (!zoneInfo || !zoneInfo.group) {
349
  console.error(`No zone info or group loaded for ${zoneId}`);
350
  storyTitleElement.textContent = "Error";
351
  storyContentElement.innerHTML = currentMessage + "<p>Cannot render current zone data.</p>";
352
- choicesElement.innerHTML = `<button class="choice-button" onclick="transitionToZoneWrapper('${getZoneId(1, 1)}')">Return to Start</button>`; // Use wrapper
353
- updateStatsDisplay();
354
- updateInventoryDisplay();
355
- updateActionInfo();
356
- return;
357
  }
358
  console.log(`Rendering UI for zone ${zoneId} with title "${zoneInfo.title}"`);
359
 
360
  storyTitleElement.textContent = zoneInfo.title || "Unknown Zone";
361
  storyContentElement.innerHTML = currentMessage + (zoneInfo.entryText ? `<p>${zoneInfo.entryText}</p>` : '');
362
- choicesElement.innerHTML = ''; // Clear previous choices
363
 
364
- // Add Navigation Options
365
  const neighbors = getZoneNeighbors(zoneId);
366
  const directions = {'north': 'North', 'south': 'South', 'east': 'East', 'west': 'West'};
367
  let addedChoices = 0;
@@ -370,29 +498,26 @@
370
  const neighborId = neighbors[dir];
371
  const button = document.createElement('button');
372
  button.classList.add('choice-button');
373
- button.textContent = `Go ${directions[dir]} (${neighborId})`; // Show ID for debug
374
- // Use addEventListener for cleaner separation
375
- button.addEventListener('click', () => transitionToZone(neighborId));
376
  choicesElement.appendChild(button);
377
  addedChoices++;
378
  }
379
 
380
- // Add Zone Specific Options (Simplified)
381
  if (zoneInfo.options && zoneInfo.options.length > 0) {
382
  console.log("Adding zone specific options");
383
  zoneInfo.options.forEach(option => {
384
  const button = document.createElement('button');
385
  button.classList.add('choice-button');
386
  button.textContent = option.text;
387
- // Add onclick based on option.action later (placeholder)
388
- button.addEventListener('click', () => console.log("Zone action clicked (TBC):", option.action));
389
  choicesElement.appendChild(button);
390
  addedChoices++;
391
  });
392
  }
393
 
394
- if (addedChoices === 0) {
395
- choicesElement.innerHTML = '<p><i>No exits or actions available yet.</i></p>';
396
  }
397
 
398
  console.log("Updating stats, inventory, action info...");
@@ -402,8 +527,7 @@
402
  console.log("renderCurrentPageUI finished.");
403
  }
404
 
405
- // Wrapper because inline onclick can have scope issues with modules
406
- function transitionToZoneWrapper(zoneId) {
407
  transitionToZone(zoneId);
408
  }
409
  window.transitionToZoneWrapper = transitionToZoneWrapper;
@@ -416,8 +540,8 @@
416
  statsElement.innerHTML = `<strong>Stats:</strong> <span style="color:${hpColor}">HP: ${hp}/${maxHp}</span> <span>XP: ${xp}</span>`;
417
  }
418
 
419
- function updateInventoryDisplay() {
420
- if (!gameState.character || !inventoryElement) return;
421
  let invHtml = '<strong>Inventory:</strong> ';
422
  if (gameState.character.inventory.length === 0) {
423
  invHtml += '<em>Empty</em>';
@@ -425,37 +549,158 @@
425
  gameState.character.inventory.forEach(item => {
426
  const itemDef = itemsData[item] || { type: 'unknown', description: '???' };
427
  const itemClass = `item-${itemDef.type || 'unknown'}`;
428
- invHtml += `<span class="item-tag ${itemClass}" title="${itemDef.description}">${item}</span>`;
 
429
  });
430
  }
431
  inventoryElement.innerHTML = invHtml;
 
 
 
 
 
 
 
 
432
  }
433
 
434
- function updateActionInfo() {
435
  if (!actionInfoElement || !gameState ) return;
436
- actionInfoElement.textContent = `Zone: ${gameState.currentZoneId || 'None'} | Mode: Explore`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  }
438
 
439
- function pickupItem() { console.log("Pickup disabled."); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
 
441
 
 
442
  document.addEventListener('DOMContentLoaded', () => {
443
- console.log("DOM Ready - Initializing World Grid Test.");
444
  try {
445
- initThreeJS(); // Setup ThreeJS first
446
  if (!scene || !camera || !renderer) throw new Error("Three.js failed to initialize.");
447
- startGame(); // Now start game logic
448
- console.log("Game world initialization sequence complete.");
 
449
  } catch (error) {
450
  console.error("Initialization failed:", error);
451
  storyTitleElement.textContent = "Initialization Error";
452
  storyContentElement.innerHTML = `<p style="color:red;">Failed to start game:</p><pre style="color:red; white-space: pre-wrap;">${error.stack || error}</pre><p style="color:yellow;">Check console (F12) for details.</p>`;
453
  if(sceneContainer) sceneContainer.innerHTML = '<p style="color:red; padding: 20px;">3D Scene Failed</p>';
454
- // Hide game UI elements on error
455
- const statsInvContainer = document.getElementById('stats-inventory-container');
456
- const choicesCont = document.getElementById('choices-container');
457
- if (statsInvContainer) statsInvContainer.style.display = 'none';
458
- if (choicesCont) choicesCont.style.display = 'none';
459
  }
460
  });
461
 
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>World Grid + Interaction</title>
7
  <style>
8
+ /* Styles from previous version (unchanged) */
9
  body { font-family: 'Courier New', monospace; background-color: #111; color: #eee; margin: 0; padding: 0; overflow: hidden; display: flex; flex-direction: column; height: 100vh; }
10
  #game-container { display: flex; flex-grow: 1; overflow: hidden; }
11
+ #scene-container { flex-grow: 3; position: relative; border-right: 2px solid #444; min-width: 250px; background-color: #000; height: 100%; box-sizing: border-box; overflow: hidden; cursor: crosshair; } /* Changed cursor */
12
  #ui-container { flex-grow: 2; padding: 25px; overflow-y: auto; background-color: #2b2b2b; min-width: 320px; height: 100%; box-sizing: border-box; display: flex; flex-direction: column; }
13
  #scene-container canvas { display: block; }
14
  #story-title { color: #f0c060; margin: 0 0 15px 0; padding-bottom: 10px; border-bottom: 1px solid #555; font-size: 1.6em; text-shadow: 1px 1px 1px #000; }
 
16
  #stats-inventory-container { margin-bottom: 25px; padding: 15px; border: 1px solid #444; border-radius: 4px; background-color: #333; font-size: 0.95em; }
17
  #stats-display, #inventory-display { margin-bottom: 10px; line-height: 1.8; }
18
  #stats-display span, #inventory-display .item-tag { display: inline-block; background-color: #484848; padding: 3px 9px; border-radius: 15px; margin: 0 8px 5px 0; border: 1px solid #6a6a6a; white-space: nowrap; box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); }
19
+ #inventory-display .item-tag { cursor: pointer; transition: background-color 0.2s, border-color 0.2s; } /* Make clickable */
20
+ #inventory-display .item-tag:hover { background-color: #6a6a6a; }
21
+ #inventory-display .item-tag.placing { background-color: #a07030; border-color: #c89040; color: #fff; box-shadow: 0 0 5px #ffc040;} /* Highlight placing item */
22
  #stats-display strong, #inventory-display strong { color: #ccc; margin-right: 6px; }
23
  #inventory-display em { color: #888; font-style: normal; }
24
+ .item-quest { background-color: #666030; border-color: #999048;}
25
+ .item-weapon { background-color: #663030; border-color: #994848;}
26
+ .item-armor { background-color: #306630; border-color: #489948;}
27
+ .item-consumable { background-color: #664430; border-color: #996648;}
28
+ .item-unknown { background-color: #555; border-color: #777;}
29
  #choices-container { margin-top: auto; padding-top: 20px; border-top: 1px solid #555; }
30
  #choices-container h3 { margin-top: 0; margin-bottom: 12px; color: #ccc; font-size: 1.1em; }
31
  #choices { display: flex; flex-direction: column; gap: 12px; }
32
  .choice-button { display: block; width: 100%; padding: 12px 15px; margin-bottom: 0; background-color: #555; color: #eee; border: 1px solid #777; border-radius: 4px; cursor: pointer; text-align: left; font-family: 'Courier New', monospace; font-size: 1.05em; transition: background-color 0.2s, border-color 0.2s, box-shadow 0.1s; box-sizing: border-box; }
33
  .choice-button:hover:not(:disabled) { background-color: #e0b050; color: #111; border-color: #c89040; box-shadow: 0 0 5px rgba(255, 200, 100, 0.5); }
34
+ .choice-button:disabled { background-color: #404040; color: #777; cursor: not-allowed; border-color: #555; opacity: 0.7; }
35
  .message { padding: 8px 12px; margin-bottom: 1em; border-left-width: 3px; border-left-style: solid; font-size: 0.95em; background-color: rgba(255, 255, 255, 0.05); border-color: #666; color: #aaa;}
36
  .message-failure { color: #f88; border-left-color: #a44; }
37
+ .message-item { color: #8bf; border-left-color: #46a; }
38
  #action-info { position: absolute; bottom: 10px; left: 10px; background-color: rgba(0,0,0,0.7); color: #ffcc66; padding: 5px 10px; border-radius: 3px; font-size: 0.9em; display: block; z-index: 10;}
 
39
  </style>
40
  </head>
41
  <body>
 
66
 
67
  <script type="module">
68
  import * as THREE from 'three';
69
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // Re-enabled controls
70
 
71
  console.log("Script module execution started.");
72
 
 
80
 
81
  console.log("DOM elements obtained.");
82
 
83
+ let scene, camera, renderer, clock, controls, raycaster, mouse; // Added controls back
84
  let worldGroup = null;
85
  let zoneGroups = {};
86
  let currentMessage = "";
87
  let currentLights = [];
88
+ let currentPlacingItem = null; // Added back for placement mode
89
 
90
+ const MAT = {
91
  stone: new THREE.MeshStandardMaterial({ color: 0x777788, roughness: 0.85 }),
92
  wood: new THREE.MeshStandardMaterial({ color: 0x9F6633, roughness: 0.75 }),
93
  leaf: new THREE.MeshStandardMaterial({ color: 0x3E9B4E, roughness: 0.6, side: THREE.DoubleSide }),
94
  ground: new THREE.MeshStandardMaterial({ color: 0x556B2F, roughness: 0.95 }),
95
  dirt: new THREE.MeshStandardMaterial({ color: 0x8B5E3C, roughness: 0.9 }),
96
  grass: new THREE.MeshStandardMaterial({ color: 0x4CB781, roughness: 0.85 }),
97
+ water: new THREE.MeshStandardMaterial({ color: 0x4682B4, roughness: 0.3, transparent: true, opacity: 0.85 }),
98
+ simple: new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.8 }),
99
  };
100
 
101
  let gameState = {};
102
 
103
  const itemsData = {
104
+ "Rusty Sword": {type:"weapon", description:"Old but sharp.", baseDamage: 3},
105
+ "Rock": {type:"unknown", description:"A simple rock."},
106
+ "Torch": {type:"consumable", description:"Provides light."},
107
+ "Cave Crystal": {type:"unknown", description:"A faintly glowing crystal shard."} // Example item
108
  };
109
 
110
  const zoneCreators = {};
 
117
  scene = new THREE.Scene();
118
  scene.background = new THREE.Color(0x1a1a1a);
119
  clock = new THREE.Clock();
120
+ raycaster = new THREE.Raycaster(); // Initialize raycaster
121
+ mouse = new THREE.Vector2(); // Initialize mouse vector
122
  worldGroup = new THREE.Group();
123
  scene.add(worldGroup);
124
  console.log("Scene and worldGroup created.");
125
 
126
+ const width = sceneContainer.clientWidth || 300;
127
  const height = sceneContainer.clientHeight || 200;
128
  console.log(`Renderer dimensions: ${width}x${height}`);
129
 
 
134
 
135
  renderer = new THREE.WebGLRenderer({ antialias: true });
136
  renderer.setSize(width, height);
137
+ renderer.shadowMap.enabled = true;
138
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
139
  sceneContainer.appendChild(renderer.domElement);
140
  console.log("Renderer created and appended.");
141
 
142
+ controls = new OrbitControls(camera, renderer.domElement); // Initialize controls
143
+ controls.enableDamping = true;
144
+ controls.dampingFactor = 0.1;
145
+ controls.target.set(0, 1, 0);
146
+ controls.maxPolarAngle = Math.PI / 2 - 0.05;
147
+ console.log("OrbitControls initialized.");
148
 
149
  window.addEventListener('resize', onWindowResize, false);
150
+ renderer.domElement.addEventListener('mousemove', onMouseMove, false); // Added back
151
+ renderer.domElement.addEventListener('click', onMouseClick, false); // Added back
152
+ setTimeout(onWindowResize, 100);
153
  animate();
154
  console.log("initThreeJS finished successfully.");
155
  } catch (error) {
156
  console.error("Error during initThreeJS:", error);
157
+ throw error;
158
  }
159
  }
160
 
 
173
  }
174
  }
175
 
176
+ function onMouseMove( event ) { // Added back
177
+ const rect = renderer.domElement.getBoundingClientRect();
178
+ mouse.x = ( (event.clientX - rect.left) / rect.width ) * 2 - 1;
179
+ mouse.y = - ( (event.clientY - rect.top) / rect.height ) * 2 + 1;
180
+ }
181
+
182
+ function onMouseClick( event ) { // Added back
183
+ console.log(`Mouse click detected. Placing item: ${currentPlacingItem}`);
184
+ if (currentPlacingItem) {
185
+ placeItem();
186
+ } else {
187
+ pickupItem();
188
+ }
189
+ }
190
+
191
  let frameCount = 0;
192
  function animate() {
193
  frameCount++;
194
+ // if(frameCount % 300 === 0) console.log("Animate loop running..."); // Reduce logging frequency
195
  requestAnimationFrame(animate);
196
+ controls.update(); // Update controls
197
  if (renderer && scene && camera) renderer.render(scene, camera);
198
  }
199
 
 
218
  function setupLighting(type = 'default') {
219
  console.log(`Setting up lighting for type: ${type}`);
220
  currentLights.forEach(light => {
221
+ if (light && light.parent) light.parent.remove(light);
222
+ if (light && scene.children.includes(light)) scene.remove(light);
223
  });
224
  currentLights = [];
225
 
226
+ let ambientIntensity = 0.5;
227
  let dirIntensity = 1.0;
228
  let dirColor = 0xffffff;
229
+ let dirPosition = new THREE.Vector3(10, 15, 7);
230
+
231
+ if (type === 'forest') { ambientIntensity = 0.4; dirIntensity = 0.8; dirColor = 0xccffcc; dirPosition = new THREE.Vector3(5, 10, 5); }
232
+ if (type === 'cave') { ambientIntensity = 0.2; dirIntensity = 0; }
233
+ if (type === 'ruins') { ambientIntensity = 0.4; dirIntensity = 0.7; dirColor = 0xaaaaff; dirPosition = new THREE.Vector3(-8, 12, -5); }
234
 
235
  const ambientLight = new THREE.AmbientLight(0xffffff, ambientIntensity);
236
  scene.add(ambientLight);
 
240
  const directionalLight = new THREE.DirectionalLight(dirColor, dirIntensity);
241
  directionalLight.position.copy(dirPosition);
242
  directionalLight.castShadow = true;
243
+ directionalLight.shadow.mapSize.set(1024, 1024);
244
+ directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 50;
245
+ const sb = 20;
246
+ directionalLight.shadow.camera.left = -sb; directionalLight.shadow.camera.right = sb;
247
+ directionalLight.shadow.camera.top = sb; directionalLight.shadow.camera.bottom = -sb;
248
+ directionalLight.shadow.bias = -0.0005;
249
  scene.add(directionalLight);
250
  currentLights.push(directionalLight);
251
  }
252
+ if (type === 'cave') { // Add cave light specifically here, track it
253
+ const ptLight = new THREE.PointLight(0xffaa55, 1.5, 12, 1);
254
+ ptLight.position.set(0, 3, 0); // Position relative to zone origin
255
+ ptLight.castShadow = true;
256
+ ptLight.shadow.mapSize.set(512, 512);
257
+ currentLights.push(ptLight); // Add to list, will be added to group in transition
258
+ }
259
+ console.log(`Lighting setup complete. Lights tracked: ${currentLights.length}`);
260
  }
261
 
262
+ // --- Zone Creation Functions ---
263
  function createFieldZone(zoneId) {
264
  console.log(`Creating zone: ${zoneId} (Field)`);
265
  const group = new THREE.Group();
266
  group.add(createGround(MAT.grass, 30));
267
+ const rockGeo = new THREE.IcosahedronGeometry(0.8, 0);
268
+ for(let i=0; i<5; i++) {
269
+ const rock = createMesh(rockGeo, MAT.stone, {x: (Math.random()-0.5)*25, y:0.4, z: (Math.random()-0.5)*25});
270
+ rock.userData = { isPickupable: true, itemName: "Rock", description: "A field rock." }; // Make rocks pickupable
271
+ group.add(rock);
272
+ }
273
  group.visible = false;
274
+ return { group, lighting: 'default', title: "Open Field", entryText: `You are in ${zoneId}. It's grassy. Some rocks litter the ground.`, options: [], zoneId: zoneId };
275
+ }
276
+
277
+ function createForestZone(zoneId) {
278
+ console.log(`Creating zone: ${zoneId} (Forest)`);
279
+ const group = new THREE.Group();
280
+ group.add(createGround(MAT.ground, 30));
281
+ const trunkGeo = new THREE.CylinderGeometry(0.2, 0.3, 4, 8);
282
+ const leafGeo = new THREE.SphereGeometry(1.5, 8, 6);
283
+ for(let i=0; i<25; i++) {
284
+ const x = (Math.random() - 0.5) * 28;
285
+ const z = (Math.random() - 0.5) * 28;
286
+ if(Math.sqrt(x*x+z*z) < 1) continue;
287
+ const tree = new THREE.Group();
288
+ const trunk = createMesh(trunkGeo, MAT.wood, {y: 2});
289
+ const leaves = createMesh(leafGeo, MAT.leaf, {y: 4.5});
290
+ tree.add(trunk); tree.add(leaves);
291
+ tree.position.set(x, 0, z);
292
+ tree.rotation.y = Math.random() * Math.PI * 2;
293
+ group.add(tree);
294
+ }
295
+ const swordGeo = new THREE.BoxGeometry(0.1, 1, 0.05);
296
+ const sword = createMesh(swordGeo, MAT.metal, {x: 3, y: 0.5, z: 4}, {z: Math.PI / 4});
297
+ sword.userData = { isPickupable: true, itemName: "Rusty Sword", description: "An old sword sticking out of the ground."};
298
+ group.add(sword); // Add pickupable sword
299
+
300
+ group.visible = false;
301
+ return { group, lighting: 'forest', title: "Dark Forest", entryText: "Sunlight is sparse beneath the thick canopy.", options: [], zoneId: zoneId };
302
  }
303
+
304
+ function createCaveZone(zoneId) {
305
+ console.log(`Creating zone: ${zoneId} (Cave)`);
306
+ const group = new THREE.Group();
307
+ group.add(createGround(MAT.stone.clone().set({color: 0x555560}), 18));
308
+ const wallGeo = new THREE.SphereGeometry(12, 32, 16, 0, Math.PI*2, 0, Math.PI*0.7);
309
+ const walls = createMesh(wallGeo, MAT.stone, {y: 4});
310
+ walls.material.side = THREE.BackSide;
311
+ group.add(walls);
312
+ const coneGeo = new THREE.ConeGeometry(0.2, 1.0, 8);
313
+ for(let i=0; i<15; i++){
314
+ const st = createMesh(coneGeo, MAT.stone, {x: (Math.random()-0.5)*16, y: 6 + Math.random()*2, z: (Math.random()-0.5)*16}, {x:Math.PI});
315
+ group.add(st);
316
+ }
317
+ const crystalGeo = new THREE.IcosahedronGeometry(0.3, 0);
318
+ const crystal = createMesh(crystalGeo, MAT.simple.clone().set({color:0xaaaaff, emissive: 0x333399}), {x: 0, y: 0.3, z: -4});
319
+ crystal.userData = { isPickupable: true, itemName: "Cave Crystal", description: "A glowing crystal."};
320
+ group.add(crystal); // Add pickupable crystal
321
+
322
+ group.visible = false;
323
+ return { group, lighting: 'cave', title: "Dim Cave", entryText: "It's dark and damp in here. Water drips.", options: [], zoneId: zoneId };
324
+ }
325
+
326
+ function createRuinsZone(zoneId) {
327
+ console.log(`Creating zone: ${zoneId} (Ruins)`);
328
+ const group = new THREE.Group();
329
+ group.add(createGround(MAT.dirt, 25));
330
+ const wallGeo = new THREE.BoxGeometry(0.5, 2, 3);
331
+ for(let i=0; i<8; i++) {
332
+ const wall = createMesh(wallGeo, MAT.stone,
333
+ {x: (Math.random()-0.5)*20, y:1, z: (Math.random()-0.5)*20},
334
+ {y: Math.random() * Math.PI}
335
+ );
336
+ wall.scale.y = 0.5 + Math.random() * 0.8;
337
+ wall.rotation.x = (Math.random()-0.5)*0.1;
338
+ wall.rotation.z = (Math.random()-0.5)*0.1;
339
+ group.add(wall);
340
+ }
341
+ const torchGeo = new THREE.CylinderGeometry(0.05, 0.05, 0.6, 6);
342
+ const torch = createMesh(torchGeo, MAT.wood, {x: -2, y: 0.3, z: 3}, {z: -Math.PI / 6});
343
+ torch.userData = { isPickupable: true, itemName: "Torch", description: "An unlit torch lies in the rubble."};
344
+ group.add(torch); // Add pickupable torch
345
+
346
+ group.visible = false;
347
+ return { group, lighting: 'ruins', title: "Crumbling Ruins", entryText: "The wind whistles through broken walls.", options: [], zoneId: zoneId };
348
+ }
349
+
350
 
351
  function getZoneId(row, col) { return `zone_${row}_${col}`; }
352
 
353
  function populateZoneCreators() {
354
  console.log("Populating zone creators...");
355
+ // zoneCreators = {}; // Removed reassignment
356
  for (let r = 0; r < MAP_ROWS; r++) {
357
  for (let c = 0; c < MAP_COLS; c++) {
358
  const zoneId = getZoneId(r, c);
359
+ let creatorFunc;
360
+ if (r === 0) creatorFunc = createForestZone;
361
+ else if (r === 1 && c % 2 === 0) creatorFunc = createFieldZone;
362
+ else if (r === 1 && c % 2 !== 0) creatorFunc = createRuinsZone;
363
+ else creatorFunc = createCaveZone;
364
+ zoneCreators[zoneId] = () => creatorFunc(zoneId);
365
  }
366
  }
367
  console.log(`Zone creators populated with ${Object.keys(zoneCreators).length} entries.`);
 
385
  const defaultChar = {
386
  name: "Player",
387
  stats: { hp: 20, maxHp: 20, xp: 0 },
388
+ inventory: [] // Start with empty inventory initially
389
  };
390
  gameState = {
391
  currentZoneId: null,
 
397
  worldGroup.remove(worldGroup.children[0]);
398
  }
399
  console.log("Starting new game state:", gameState);
400
+ transitionToZone(getZoneId(1, 1)); // Start in center
401
  console.log("startGame finished.");
402
  }
403
 
404
  function transitionToZone(newZoneId) {
405
  console.log(`Attempting transition to ${newZoneId}`);
406
  currentMessage = "";
407
+ currentPlacingItem = null; // Cancel placement on zone change
408
 
409
  if (gameState.currentZoneId && zoneGroups[gameState.currentZoneId]) {
410
  console.log(`Hiding old zone: ${gameState.currentZoneId}`);
 
430
  } catch (creationError) {
431
  console.error(`Error creating zone ${newZoneId}:`, creationError);
432
  currentMessage = `<p class="message message-failure">Error creating zone ${newZoneId}.</p>`;
433
+ renderCurrentPageUI();
434
+ return;
 
 
435
  }
436
  } else {
437
  console.error(`No creator found for zone ID: ${newZoneId}, critical error.`);
438
  currentMessage = `<p class="message message-failure">Error: Zone creator missing for ${newZoneId}. Cannot proceed.</p>`;
 
439
  storyTitleElement.textContent = "Fatal Error";
440
  storyContentElement.innerHTML = currentMessage;
441
  choicesElement.innerHTML = '';
442
+ return;
443
  }
444
  }
445
 
446
  gameState.currentZoneId = newZoneId;
447
  setupLighting(zoneInfo.lighting || 'default');
448
 
449
+ // Add relevant tracked lights (like cave point light) to the new zone group
450
+ const currentZoneGroup = zoneInfo.group;
451
  currentLights.forEach(light => {
452
+ if (light && light.isPointLight && zoneInfo.lighting === 'cave') { // Example: only add point lights to caves
453
+ if (light.parent) light.parent.remove(light); // Remove from old parent if any
454
+ currentZoneGroup.add(light);
455
+ console.log("Added point light to cave group:", newZoneId);
456
+ }
457
+ // Ensure other lights (ambient, directional) are in the main scene, not the group
458
+ else if (light && !light.isPointLight && !light.parent) {
459
+ scene.add(light);
460
+ }
461
  });
462
 
463
  camera.position.set(0, 6, 12);
464
+ controls.target.set(0, 1, 0);
465
+ controls.update();
 
466
 
467
  console.log(`Transition to ${newZoneId} complete. Rendering UI.`);
468
  renderCurrentPageUI();
469
  }
470
 
471
+
472
  function renderCurrentPageUI() {
473
  console.log(`renderCurrentPageUI called for zone: ${gameState.currentZoneId}`);
474
  const zoneInfo = zoneGroups[gameState.currentZoneId];
475
  const zoneId = gameState.currentZoneId;
476
 
 
477
  if (!storyTitleElement || !storyContentElement || !choicesElement || !statsElement || !inventoryElement || !actionInfoElement) {
478
+ console.error("Crucial UI element missing!"); return;
 
 
479
  }
 
480
  if (!zoneInfo || !zoneInfo.group) {
481
  console.error(`No zone info or group loaded for ${zoneId}`);
482
  storyTitleElement.textContent = "Error";
483
  storyContentElement.innerHTML = currentMessage + "<p>Cannot render current zone data.</p>";
484
+ choicesElement.innerHTML = `<button class="choice-button" onclick="transitionToZoneWrapper('${getZoneId(1, 1)}')">Return to Start</button>`;
485
+ updateStatsDisplay(); updateInventoryDisplay(); updateActionInfo(); return;
 
 
 
486
  }
487
  console.log(`Rendering UI for zone ${zoneId} with title "${zoneInfo.title}"`);
488
 
489
  storyTitleElement.textContent = zoneInfo.title || "Unknown Zone";
490
  storyContentElement.innerHTML = currentMessage + (zoneInfo.entryText ? `<p>${zoneInfo.entryText}</p>` : '');
491
+ choicesElement.innerHTML = '';
492
 
 
493
  const neighbors = getZoneNeighbors(zoneId);
494
  const directions = {'north': 'North', 'south': 'South', 'east': 'East', 'west': 'West'};
495
  let addedChoices = 0;
 
498
  const neighborId = neighbors[dir];
499
  const button = document.createElement('button');
500
  button.classList.add('choice-button');
501
+ button.textContent = `Go ${directions[dir]}`;
502
+ button.addEventListener('click', () => transitionToZone(neighborId)); // Use event listener
 
503
  choicesElement.appendChild(button);
504
  addedChoices++;
505
  }
506
 
 
507
  if (zoneInfo.options && zoneInfo.options.length > 0) {
508
  console.log("Adding zone specific options");
509
  zoneInfo.options.forEach(option => {
510
  const button = document.createElement('button');
511
  button.classList.add('choice-button');
512
  button.textContent = option.text;
513
+ button.addEventListener('click', () => console.log("Zone action TBC:", option.action));
 
514
  choicesElement.appendChild(button);
515
  addedChoices++;
516
  });
517
  }
518
 
519
+ if (addedChoices === 0 && choicesElement.innerHTML === '') { // Check if empty after adding
520
+ choicesElement.innerHTML = '<p><i>No exits or actions defined here yet.</i></p>';
521
  }
522
 
523
  console.log("Updating stats, inventory, action info...");
 
527
  console.log("renderCurrentPageUI finished.");
528
  }
529
 
530
+ function transitionToZoneWrapper(zoneId) { // Keep wrapper for potential inline use later
 
531
  transitionToZone(zoneId);
532
  }
533
  window.transitionToZoneWrapper = transitionToZoneWrapper;
 
540
  statsElement.innerHTML = `<strong>Stats:</strong> <span style="color:${hpColor}">HP: ${hp}/${maxHp}</span> <span>XP: ${xp}</span>`;
541
  }
542
 
543
+ function updateInventoryDisplay() { // Added back placement functionality
544
+ if (!gameState.character || !inventoryElement) return;
545
  let invHtml = '<strong>Inventory:</strong> ';
546
  if (gameState.character.inventory.length === 0) {
547
  invHtml += '<em>Empty</em>';
 
549
  gameState.character.inventory.forEach(item => {
550
  const itemDef = itemsData[item] || { type: 'unknown', description: '???' };
551
  const itemClass = `item-${itemDef.type || 'unknown'}`;
552
+ const placingClass = (item === currentPlacingItem) ? ' placing' : ''; // Highlight item being placed
553
+ invHtml += `<span class="item-tag ${itemClass}${placingClass}" title="Click to Place: ${itemDef.description}" data-itemname="${item}">${item}</span>`;
554
  });
555
  }
556
  inventoryElement.innerHTML = invHtml;
557
+
558
+ // Re-attach click listeners for placement mode
559
+ inventoryElement.querySelectorAll('.item-tag').forEach(tag => {
560
+ tag.addEventListener('click', (event) => {
561
+ event.stopPropagation(); // Prevent click from triggering pickup if UI overlaps canvas
562
+ togglePlacementMode(tag.dataset.itemname);
563
+ });
564
+ });
565
  }
566
 
567
+ function updateActionInfo() { // Updated Action Info
568
  if (!actionInfoElement || !gameState ) return;
569
+ if (currentPlacingItem) {
570
+ actionInfoElement.textContent = `Placing: ${currentPlacingItem} (Click ground)`;
571
+ } else {
572
+ actionInfoElement.textContent = `Zone: ${gameState.currentZoneId || 'None'} | Mode: Explore (Click items)`;
573
+ }
574
+ actionInfoElement.style.display = 'block';
575
+ }
576
+
577
+ // --- Pickup/Placement Functions (Restored) ---
578
+ function pickupItem() {
579
+ if (currentPlacingItem) return; // Don't pick up while placing
580
+
581
+ raycaster.setFromCamera(mouse, camera);
582
+ const currentGroup = zoneGroups[gameState.currentZoneId]?.group;
583
+ if (!currentGroup) return;
584
+
585
+ const pickupables = [];
586
+ currentGroup.traverseVisible(child => {
587
+ if (child.userData.isPickupable) {
588
+ pickupables.push(child);
589
+ }
590
+ });
591
+
592
+ const intersects = raycaster.intersectObjects(pickupables, false);
593
+
594
+ if (intersects.length > 0) {
595
+ // Basic outline/highlight effect could go here if desired
596
+ const clickedObject = intersects[0].object;
597
+ const itemName = clickedObject.userData.itemName;
598
+
599
+ if (itemName && itemsData[itemName]) {
600
+ console.log(`Picked up: ${itemName}`);
601
+ currentMessage = `<p class="message message-item"><em>Picked up: ${itemName}</em></p>`;
602
+
603
+ if (!gameState.character.inventory.includes(itemName)) {
604
+ gameState.character.inventory.push(itemName);
605
+ } else {
606
+ currentMessage += `<p class="message message-info"><em>(Cannot carry more ${itemName})</em></p>`;
607
+ }
608
+
609
+ clickedObject.visible = false; // Hide the object
610
+ clickedObject.userData.isPickupable = false; // Mark as picked up
611
+ // Optionally remove from scene: clickedObject.parent.remove(clickedObject);
612
+
613
+ renderCurrentPageUI(); // Update inventory and messages
614
+ }
615
+ }
616
  }
617
 
618
+ function togglePlacementMode(itemName) { // Added back
619
+ if (!itemName || !gameState.character.inventory.includes(itemName)) return;
620
+ if (currentPlacingItem === itemName) {
621
+ currentPlacingItem = null;
622
+ console.log("Placement cancelled.");
623
+ } else {
624
+ currentPlacingItem = itemName;
625
+ console.log(`Ready to place: ${itemName}`);
626
+ }
627
+ updateInventoryDisplay(); // Update highlighting
628
+ updateActionInfo();
629
+ }
630
+
631
+ function placeItem() { // Added back
632
+ if (!currentPlacingItem) return;
633
+
634
+ raycaster.setFromCamera(mouse, camera);
635
+ const currentGroup = zoneGroups[gameState.currentZoneId]?.group;
636
+ if (!currentGroup) return;
637
+
638
+ const grounds = [];
639
+ currentGroup.traverseVisible(child => { if(child.userData.isGround) grounds.push(child); });
640
+
641
+ const intersects = raycaster.intersectObjects(grounds);
642
+
643
+ if (intersects.length > 0) {
644
+ const point = intersects[0].point;
645
+ const itemName = currentPlacingItem;
646
+ const itemDef = itemsData[itemName];
647
+
648
+ console.log(`Placing ${itemName} at ${point.x.toFixed(1)}, ${point.z.toFixed(1)}`);
649
+
650
+ // Create a visual representation of the item
651
+ const itemGeo = new THREE.BoxGeometry(0.5, 0.5, 0.5);
652
+ const itemMat = MAT.simple.clone();
653
+ if(itemDef?.type === 'weapon') itemMat.color.setHex(0xaa4444);
654
+ else if(itemDef?.type === 'consumable') itemMat.color.setHex(0xaa7744);
655
+ else itemMat.color.setHex(0x8888aa);
656
+
657
+ const placedMesh = createMesh(itemGeo, itemMat, {x: point.x, y: 0.25, z: point.z}); // Place slightly above ground
658
+ placedMesh.userData = {
659
+ isPlacedItem: true,
660
+ itemName: itemName,
661
+ isPickupable: true, // Allow picking it back up
662
+ description: `Placed ${itemName}`
663
+ };
664
+ currentGroup.add(placedMesh);
665
+
666
+ gameState.character.inventory = gameState.character.inventory.filter(i => i !== itemName); // Remove from inventory
667
+ currentMessage = `<p class="message message-item"><em>Placed ${itemName}.</em></p>`;
668
+ currentPlacingItem = null; // Exit placement mode
669
+ renderCurrentPageUI(); // Update UI
670
+
671
+ } else {
672
+ console.log("Placement click missed ground.");
673
+ currentMessage = `<p class="message message-failure"><em>Cannot place item there.</em></p>`;
674
+ currentPlacingItem = null;
675
+ renderCurrentPageUI();
676
+ }
677
+ }
678
+
679
+ // --- Combat/Checks (Placeholders) ---
680
+ function startCombat(enemyTypeId) { console.log("Combat disabled."); }
681
+ function handleCombatAction(action) { console.log("Combat disabled."); }
682
+ window.startCombat = startCombat;
683
+ window.handleCombatAction = handleCombatAction;
684
 
685
 
686
+ // --- Initialization ---
687
  document.addEventListener('DOMContentLoaded', () => {
688
+ console.log("DOM Ready - Initializing World Grid + Interaction.");
689
  try {
690
+ initThreeJS();
691
  if (!scene || !camera || !renderer) throw new Error("Three.js failed to initialize.");
692
+ // Font not needed currently, start game directly
693
+ startGame();
694
+ console.log("Game world initialized and started.");
695
  } catch (error) {
696
  console.error("Initialization failed:", error);
697
  storyTitleElement.textContent = "Initialization Error";
698
  storyContentElement.innerHTML = `<p style="color:red;">Failed to start game:</p><pre style="color:red; white-space: pre-wrap;">${error.stack || error}</pre><p style="color:yellow;">Check console (F12) for details.</p>`;
699
  if(sceneContainer) sceneContainer.innerHTML = '<p style="color:red; padding: 20px;">3D Scene Failed</p>';
700
+ const statsInvContainer = document.getElementById('stats-inventory-container');
701
+ const choicesCont = document.getElementById('choices-container');
702
+ if (statsInvContainer) statsInvContainer.style.display = 'none';
703
+ if (choicesCont) choicesCont.style.display = 'none';
 
704
  }
705
  });
706