awacke1 commited on
Commit
44893d0
·
verified ·
1 Parent(s): 1e5f49f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +94 -106
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>World Grid Test</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; }
@@ -17,6 +17,11 @@
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; }
@@ -25,11 +30,14 @@
25
  .choice-button:disabled { background-color: #404040; color: #777; cursor: not-allowed; border-color: #555; opacity: 0.7; }
26
  .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;}
27
  .message-failure { color: #f88; border-left-color: #a44; }
 
28
  </style>
29
  </head>
30
  <body>
31
  <div id="game-container">
32
- <div id="scene-container"></div>
 
 
33
  <div id="ui-container">
34
  <h2 id="story-title">Initializing...</h2>
35
  <div id="story-content"><p>Loading assets...</p></div>
@@ -54,7 +62,6 @@
54
  <script type="module">
55
  import * as THREE from 'three';
56
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
57
- // Font loading removed for simplicity for now
58
 
59
  const sceneContainer = document.getElementById('scene-container');
60
  const storyTitleElement = document.getElementById('story-title');
@@ -62,11 +69,13 @@
62
  const choicesElement = document.getElementById('choices');
63
  const statsElement = document.getElementById('stats-display');
64
  const inventoryElement = document.getElementById('inventory-display');
 
65
 
66
- let scene, camera, renderer, clock, controls;
67
  let worldGroup = null;
68
- let zoneGroups = {}; // Stores the THREE.Group for loaded zones { zoneId: group }
69
  let currentMessage = "";
 
70
 
71
  const MAT = {
72
  stone: new THREE.MeshStandardMaterial({ color: 0x777788, roughness: 0.85 }),
@@ -79,22 +88,32 @@
79
  simple: new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.8 }),
80
  };
81
 
82
- // --- Game State ---
83
- let gameState = {}; // Initialized in startGame
 
 
 
 
 
 
84
 
85
- // --- Core Functions ---
 
 
86
 
87
  function initThreeJS() {
88
  scene = new THREE.Scene();
89
  scene.background = new THREE.Color(0x1a1a1a);
90
  clock = new THREE.Clock();
 
 
91
  worldGroup = new THREE.Group();
92
  scene.add(worldGroup);
93
 
94
  const width = sceneContainer.clientWidth || 1;
95
  const height = sceneContainer.clientHeight || 1;
96
  camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
97
- camera.position.set(0, 6, 12); // Slightly higher view
98
 
99
  renderer = new THREE.WebGLRenderer({ antialias: true });
100
  renderer.setSize(width, height);
@@ -111,7 +130,6 @@
111
  controls.maxPolarAngle = Math.PI / 2 - 0.05;
112
 
113
  window.addEventListener('resize', onWindowResize, false);
114
- // Removed mouse listeners for now
115
  setTimeout(onWindowResize, 50);
116
  animate();
117
  }
@@ -127,7 +145,7 @@
127
 
128
  function animate() {
129
  requestAnimationFrame(animate);
130
- controls.update(); // Need to update controls
131
  if (renderer && scene && camera) renderer.render(scene, camera);
132
  }
133
 
@@ -145,11 +163,15 @@
145
  const ground = new THREE.Mesh(geo, material);
146
  ground.rotation.x = -Math.PI / 2; ground.position.y = 0;
147
  ground.receiveShadow = true; ground.castShadow = false;
 
148
  return ground;
149
  }
150
 
151
  function setupLighting(type = 'default') {
152
- worldGroup.children.filter(c => c.isLight).forEach(light => worldGroup.remove(light)); // Remove old lights from worldGroup
 
 
 
153
 
154
  let ambientIntensity = 0.5;
155
  let dirIntensity = 1.0;
@@ -157,11 +179,19 @@
157
  let dirPosition = new THREE.Vector3(10, 15, 8);
158
 
159
  if (type === 'forest') { ambientIntensity = 0.4; dirIntensity = 0.8; dirColor = 0xccffcc; dirPosition = new THREE.Vector3(5, 10, 5); }
160
- if (type === 'cave') { ambientIntensity = 0.2; dirIntensity = 0; } // Rely on point light added in zone creator
 
 
 
 
 
 
 
161
  if (type === 'ruins') { ambientIntensity = 0.4; dirIntensity = 0.7; dirColor = 0xaaaaff; dirPosition = new THREE.Vector3(-8, 12, -5); }
162
 
163
  const ambientLight = new THREE.AmbientLight(0xffffff, ambientIntensity);
164
- worldGroup.add(ambientLight); // Add lights to worldGroup
 
165
 
166
  if (dirIntensity > 0) {
167
  const directionalLight = new THREE.DirectionalLight(dirColor, dirIntensity);
@@ -169,15 +199,15 @@
169
  directionalLight.castShadow = true;
170
  directionalLight.shadow.mapSize.set(1024, 1024);
171
  directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 50;
172
- const sb = 20; // shadow bounds
173
  directionalLight.shadow.camera.left = -sb; directionalLight.shadow.camera.right = sb;
174
  directionalLight.shadow.camera.top = sb; directionalLight.shadow.camera.bottom = -sb;
175
  directionalLight.shadow.bias = -0.0005;
176
- worldGroup.add(directionalLight);
 
177
  }
178
  }
179
 
180
- // --- Zone Creation Functions ---
181
  function createFieldZone(zoneId) {
182
  const group = new THREE.Group();
183
  group.add(createGround(MAT.grass, 30));
@@ -186,13 +216,7 @@
186
  group.add(createMesh(rockGeo, MAT.stone, {x: (Math.random()-0.5)*25, y:0.3, z: (Math.random()-0.5)*25}));
187
  }
188
  group.visible = false;
189
- return {
190
- group,
191
- lighting: 'default',
192
- title: "Open Field",
193
- entryText: "You stand in an open, grassy field.",
194
- options: [] // Neighbors added dynamically
195
- };
196
  }
197
 
198
  function createForestZone(zoneId) {
@@ -213,15 +237,7 @@
213
  group.add(tree);
214
  }
215
  group.visible = false;
216
- return {
217
- group,
218
- lighting: 'forest',
219
- title: "Dark Forest",
220
- entryText: "Sunlight is sparse beneath the thick canopy.",
221
- options: [
222
- { text: "Search for tracks (TBC)", action: "search"} // Placeholder for future skill check
223
- ]
224
- };
225
  }
226
 
227
  function createCaveZone(zoneId) {
@@ -236,21 +252,13 @@
236
  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});
237
  group.add(st);
238
  }
239
- // Add point light specific to cave
240
- const ptLight = new THREE.PointLight(0xffaa55, 1.5, 12, 1);
241
  ptLight.position.set(0, 3, 0);
242
  ptLight.castShadow = true;
243
  ptLight.shadow.mapSize.set(512, 512);
244
- group.add(ptLight);
245
-
246
  group.visible = false;
247
- return {
248
- group,
249
- lighting: 'cave',
250
- title: "Dim Cave",
251
- entryText: "It's dark and damp in here. Water drips.",
252
- options: []
253
- };
254
  }
255
 
256
  function createRuinsZone(zoneId) {
@@ -268,35 +276,23 @@
268
  group.add(wall);
269
  }
270
  group.visible = false;
271
- return {
272
- group,
273
- lighting: 'ruins',
274
- title: "Crumbling Ruins",
275
- entryText: "The wind whistles through broken walls.",
276
- options: [
277
- { text: "Examine rubble (TBC)", action: "examine_rubble"}
278
- ]
279
- };
280
  }
281
 
282
- // --- Map Grid & Zone Definitions ---
283
- const MAP_ROWS = 3;
284
- const MAP_COLS = 4;
285
- const zoneCreators = {}; // { "zone_0_0": createFunction, ... }
286
 
287
  function getZoneId(row, col) { return `zone_${row}_${col}`; }
288
 
289
  function populateZoneCreators() {
 
290
  for (let r = 0; r < MAP_ROWS; r++) {
291
  for (let c = 0; c < MAP_COLS; c++) {
292
  const zoneId = getZoneId(r, c);
293
  let creatorFunc;
294
- // Simple pattern for variety
295
  if (r === 0) creatorFunc = createForestZone;
296
  else if (r === 1 && c % 2 === 0) creatorFunc = createFieldZone;
297
  else if (r === 1 && c % 2 !== 0) creatorFunc = createRuinsZone;
298
  else creatorFunc = createCaveZone;
299
- zoneCreators[zoneId] = () => creatorFunc(zoneId); // Store function that calls creator with ID
300
  }
301
  }
302
  console.log("Zone creators populated.");
@@ -308,10 +304,10 @@
308
  const r = parseInt(parts[1]);
309
  const c = parseInt(parts[2]);
310
  const neighbors = {};
311
- if (r > 0) neighbors.north = getZoneId(r - 1, c);
312
- if (r < MAP_ROWS - 1) neighbors.south = getZoneId(r + 1, c);
313
- if (c > 0) neighbors.west = getZoneId(r, c - 1);
314
- if (c < MAP_COLS - 1) neighbors.east = getZoneId(r, c + 1);
315
  return neighbors;
316
  }
317
 
@@ -326,35 +322,40 @@
326
  character: JSON.parse(JSON.stringify(defaultChar))
327
  };
328
  populateZoneCreators();
 
 
 
 
 
329
  console.log("Starting new game:", gameState);
330
- transitionToZone(getZoneId(1, 1)); // Start in a central zone
331
  }
332
 
333
  function transitionToZone(newZoneId) {
334
  console.log(`Transitioning to ${newZoneId}`);
335
  currentMessage = "";
336
 
337
- // Hide current zone if exists
338
  if (gameState.currentZoneId && zoneGroups[gameState.currentZoneId]) {
339
- zoneGroups[gameState.currentZoneId].visible = false;
340
  }
341
 
342
  let zoneInfo;
343
- // Load or unhide new zone
344
  if (zoneGroups[newZoneId]) {
345
  zoneInfo = zoneGroups[newZoneId];
346
  zoneInfo.group.visible = true;
 
347
  } else {
348
  const creator = zoneCreators[newZoneId];
349
  if (creator) {
350
- zoneInfo = creator(); // Create the zone { group, lighting, title, entryText, options }
351
- zoneGroups[newZoneId] = zoneInfo; // Store the created zone data
352
  worldGroup.add(zoneInfo.group);
353
  zoneInfo.group.visible = true;
 
354
  } else {
355
- console.error(`No creator found for zone ID: ${newZoneId}, going to default`);
356
  const fallbackId = getZoneId(1, 1);
357
- if (!zoneGroups[fallbackId]) { // Ensure fallback exists
358
  zoneGroups[fallbackId] = zoneCreators[fallbackId]();
359
  worldGroup.add(zoneGroups[fallbackId].group);
360
  }
@@ -368,7 +369,14 @@
368
  gameState.currentZoneId = newZoneId;
369
  setupLighting(zoneInfo.lighting || 'default');
370
 
371
- // Basic camera reset
 
 
 
 
 
 
 
372
  camera.position.set(0, 6, 12);
373
  controls.target.set(0, 1, 0);
374
  controls.update();
@@ -379,14 +387,16 @@
379
 
380
  function renderCurrentPageUI() {
381
  const zoneInfo = zoneGroups[gameState.currentZoneId];
 
382
 
383
  if (!zoneInfo) {
384
- console.error(`No zone info loaded for ${gameState.currentZoneId}`);
385
  storyTitleElement.textContent = "Error";
386
  storyContentElement.innerHTML = currentMessage + "<p>Cannot render current zone.</p>";
387
  choicesElement.innerHTML = `<button class="choice-button" onclick="transitionToZone('${getZoneId(1, 1)}')">Return to Start</button>`;
388
  updateStatsDisplay();
389
  updateInventoryDisplay();
 
390
  return;
391
  }
392
 
@@ -394,8 +404,7 @@
394
  storyContentElement.innerHTML = currentMessage + (zoneInfo.entryText ? `<p>${zoneInfo.entryText}</p>` : '');
395
  choicesElement.innerHTML = '';
396
 
397
- // Add Navigation Options
398
- const neighbors = getZoneNeighbors(gameState.currentZoneId);
399
  const directions = {'north': 'North', 'south': 'South', 'east': 'East', 'west': 'West'};
400
  for(const dir in neighbors) {
401
  const neighborId = neighbors[dir];
@@ -406,13 +415,12 @@
406
  choicesElement.appendChild(button);
407
  }
408
 
409
- // Add Zone Specific Options
410
  if (zoneInfo.options && zoneInfo.options.length > 0) {
411
  zoneInfo.options.forEach(option => {
412
  const button = document.createElement('button');
413
  button.classList.add('choice-button');
414
  button.textContent = option.text;
415
- // Add onclick based on option.action or other properties later
416
  choicesElement.appendChild(button);
417
  });
418
  }
@@ -422,6 +430,9 @@
422
  updateActionInfo();
423
  }
424
 
 
 
 
425
  function updateStatsDisplay() {
426
  const { hp, maxHp, xp } = gameState.character.stats;
427
  const hpColor = hp / maxHp < 0.3 ? '#f88' : (hp / maxHp < 0.6 ? '#fd5' : '#8f8');
@@ -446,42 +457,19 @@
446
  actionInfoElement.textContent = `Zone: ${gameState.currentZoneId || 'None'}`;
447
  }
448
 
449
- // --- Pickup/Placement (Removed for now) ---
450
- function pickupItem() { console.log("Pickup disabled in this version."); }
451
- function placeItem() { console.log("Placement disabled in this version."); }
452
- function togglePlacementMode() { console.log("Placement disabled in this version.");}
453
-
454
- // --- Combat/Checks (Placeholders) ---
455
- function startCombat(enemyTypeId) {
456
- console.log("Combat Started (Placeholder):", enemyTypeId);
457
- currentMessage = `<p class="message message-combat">Placeholder: Encountered a ${enemyTypeId}!</p>`;
458
- renderCurrentPageUI(); // Re-render to show message
459
- }
460
- function handleCombatAction(action) {
461
- console.log("Combat Action (Placeholder):", action);
462
- currentMessage = `<p class="message message-info">Placeholder: Combat action '${action}' executed.</p>`;
463
- // Add basic HP loss for testing
464
- gameState.character.stats.hp -= 1;
465
- gameState.character.stats.hp = Math.max(0, gameState.character.stats.hp);
466
- if(gameState.character.stats.hp === 0) {
467
- currentMessage += `<p class="message message-failure">You were defeated in placeholder combat!</p>`;
468
- // Reset HP for now instead of game over
469
- gameState.character.stats.hp = gameState.character.stats.maxHp;
470
- }
471
- renderCurrentPageUI();
472
- }
473
- // Make placeholders globally accessible if needed by inline HTML later
474
- window.startCombat = startCombat;
475
- window.handleCombatAction = handleCombatAction;
476
 
477
 
478
- // --- Initialization ---
479
  document.addEventListener('DOMContentLoaded', () => {
480
  console.log("DOM Ready - Initializing World Grid Test.");
481
  try {
482
  initThreeJS();
483
  if (!scene || !camera || !renderer) throw new Error("Three.js failed to initialize.");
484
- startGame(); // Font not needed for this simplified version
485
  console.log("Game world initialized and started.");
486
  } catch (error) {
487
  console.error("Initialization failed:", error);
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>World Grid Test (Fixed)</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; }
 
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
+ .item-quest { background-color: #666030; border-color: #999048;}
21
+ .item-weapon { background-color: #663030; border-color: #994848;}
22
+ .item-armor { background-color: #306630; border-color: #489948;}
23
+ .item-consumable { background-color: #664430; border-color: #996648;}
24
+ .item-unknown { background-color: #555; border-color: #777;}
25
  #choices-container { margin-top: auto; padding-top: 20px; border-top: 1px solid #555; }
26
  #choices-container h3 { margin-top: 0; margin-bottom: 12px; color: #ccc; font-size: 1.1em; }
27
  #choices { display: flex; flex-direction: column; gap: 12px; }
 
30
  .choice-button:disabled { background-color: #404040; color: #777; cursor: not-allowed; border-color: #555; opacity: 0.7; }
31
  .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;}
32
  .message-failure { color: #f88; border-left-color: #a44; }
33
+ #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;}
34
  </style>
35
  </head>
36
  <body>
37
  <div id="game-container">
38
+ <div id="scene-container">
39
+ <div id="action-info">Initializing...</div>
40
+ </div>
41
  <div id="ui-container">
42
  <h2 id="story-title">Initializing...</h2>
43
  <div id="story-content"><p>Loading assets...</p></div>
 
62
  <script type="module">
63
  import * as THREE from 'three';
64
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
65
 
66
  const sceneContainer = document.getElementById('scene-container');
67
  const storyTitleElement = document.getElementById('story-title');
 
69
  const choicesElement = document.getElementById('choices');
70
  const statsElement = document.getElementById('stats-display');
71
  const inventoryElement = document.getElementById('inventory-display');
72
+ const actionInfoElement = document.getElementById('action-info'); // <<< Declaration was missing
73
 
74
+ let scene, camera, renderer, clock, controls, raycaster, mouse;
75
  let worldGroup = null;
76
+ let zoneGroups = {};
77
  let currentMessage = "";
78
+ let currentLights = [];
79
 
80
  const MAT = {
81
  stone: new THREE.MeshStandardMaterial({ color: 0x777788, roughness: 0.85 }),
 
88
  simple: new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.8 }),
89
  };
90
 
91
+ let gameState = {};
92
+
93
+ const itemsData = {
94
+ "Rusty Sword": {type:"weapon", description:"Old but sharp.", baseDamage: 3},
95
+ "Health Potion": {type:"consumable", description:"Restores 10 HP.", effect: { hpGain: 10 }},
96
+ "Goblin Ear": {type:"quest", description:"A gruesome trophy."},
97
+ "Cave Crystal": {type:"unknown", description:"A faintly glowing crystal shard."}
98
+ };
99
 
100
+ const zoneCreators = {};
101
+ const MAP_ROWS = 3;
102
+ const MAP_COLS = 4;
103
 
104
  function initThreeJS() {
105
  scene = new THREE.Scene();
106
  scene.background = new THREE.Color(0x1a1a1a);
107
  clock = new THREE.Clock();
108
+ raycaster = new THREE.Raycaster();
109
+ mouse = new THREE.Vector2();
110
  worldGroup = new THREE.Group();
111
  scene.add(worldGroup);
112
 
113
  const width = sceneContainer.clientWidth || 1;
114
  const height = sceneContainer.clientHeight || 1;
115
  camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
116
+ camera.position.set(0, 6, 12);
117
 
118
  renderer = new THREE.WebGLRenderer({ antialias: true });
119
  renderer.setSize(width, height);
 
130
  controls.maxPolarAngle = Math.PI / 2 - 0.05;
131
 
132
  window.addEventListener('resize', onWindowResize, false);
 
133
  setTimeout(onWindowResize, 50);
134
  animate();
135
  }
 
145
 
146
  function animate() {
147
  requestAnimationFrame(animate);
148
+ controls.update();
149
  if (renderer && scene && camera) renderer.render(scene, camera);
150
  }
151
 
 
163
  const ground = new THREE.Mesh(geo, material);
164
  ground.rotation.x = -Math.PI / 2; ground.position.y = 0;
165
  ground.receiveShadow = true; ground.castShadow = false;
166
+ ground.userData.isGround = true;
167
  return ground;
168
  }
169
 
170
  function setupLighting(type = 'default') {
171
+ currentLights.forEach(light => {
172
+ if (light) scene.remove(light); // Remove from main scene
173
+ });
174
+ currentLights = []; // Reset tracked lights
175
 
176
  let ambientIntensity = 0.5;
177
  let dirIntensity = 1.0;
 
179
  let dirPosition = new THREE.Vector3(10, 15, 8);
180
 
181
  if (type === 'forest') { ambientIntensity = 0.4; dirIntensity = 0.8; dirColor = 0xccffcc; dirPosition = new THREE.Vector3(5, 10, 5); }
182
+ if (type === 'cave') {
183
+ ambientIntensity = 0.2; dirIntensity = 0;
184
+ const ptLight = new THREE.PointLight(0xffaa55, 1.5, 12, 1);
185
+ ptLight.position.set(0, 3, 0); // Place light relative to zone origin
186
+ ptLight.castShadow = true; ptLight.shadow.mapSize.set(512, 512);
187
+ // Point light will be added to the zone group later
188
+ currentLights.push(ptLight); // Track it to add later
189
+ }
190
  if (type === 'ruins') { ambientIntensity = 0.4; dirIntensity = 0.7; dirColor = 0xaaaaff; dirPosition = new THREE.Vector3(-8, 12, -5); }
191
 
192
  const ambientLight = new THREE.AmbientLight(0xffffff, ambientIntensity);
193
+ scene.add(ambientLight); // Add ambient to main scene
194
+ currentLights.push(ambientLight); // Track ambient
195
 
196
  if (dirIntensity > 0) {
197
  const directionalLight = new THREE.DirectionalLight(dirColor, dirIntensity);
 
199
  directionalLight.castShadow = true;
200
  directionalLight.shadow.mapSize.set(1024, 1024);
201
  directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 50;
202
+ const sb = 20;
203
  directionalLight.shadow.camera.left = -sb; directionalLight.shadow.camera.right = sb;
204
  directionalLight.shadow.camera.top = sb; directionalLight.shadow.camera.bottom = -sb;
205
  directionalLight.shadow.bias = -0.0005;
206
+ scene.add(directionalLight); // Add directional to main scene
207
+ currentLights.push(directionalLight);
208
  }
209
  }
210
 
 
211
  function createFieldZone(zoneId) {
212
  const group = new THREE.Group();
213
  group.add(createGround(MAT.grass, 30));
 
216
  group.add(createMesh(rockGeo, MAT.stone, {x: (Math.random()-0.5)*25, y:0.3, z: (Math.random()-0.5)*25}));
217
  }
218
  group.visible = false;
219
+ return { group, lighting: 'default', title: "Open Field", entryText: "You stand in an open, grassy field.", options: [], zoneId: zoneId };
 
 
 
 
 
 
220
  }
221
 
222
  function createForestZone(zoneId) {
 
237
  group.add(tree);
238
  }
239
  group.visible = false;
240
+ return { group, lighting: 'forest', title: "Dark Forest", entryText: "Sunlight is sparse beneath the thick canopy.", options: [], zoneId: zoneId };
 
 
 
 
 
 
 
 
241
  }
242
 
243
  function createCaveZone(zoneId) {
 
252
  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});
253
  group.add(st);
254
  }
255
+ const ptLight = new THREE.PointLight(0xffaa55, 1.5, 12, 1); // Create cave light
 
256
  ptLight.position.set(0, 3, 0);
257
  ptLight.castShadow = true;
258
  ptLight.shadow.mapSize.set(512, 512);
259
+ group.add(ptLight); // Add light to the cave group itself
 
260
  group.visible = false;
261
+ return { group, lighting: 'cave', title: "Dim Cave", entryText: "It's dark and damp in here. Water drips.", options: [], zoneId: zoneId };
 
 
 
 
 
 
262
  }
263
 
264
  function createRuinsZone(zoneId) {
 
276
  group.add(wall);
277
  }
278
  group.visible = false;
279
+ return { group, lighting: 'ruins', title: "Crumbling Ruins", entryText: "The wind whistles through broken walls.", options: [], zoneId: zoneId };
 
 
 
 
 
 
 
 
280
  }
281
 
 
 
 
 
282
 
283
  function getZoneId(row, col) { return `zone_${row}_${col}`; }
284
 
285
  function populateZoneCreators() {
286
+ zoneCreators = {}; // Clear previous if any
287
  for (let r = 0; r < MAP_ROWS; r++) {
288
  for (let c = 0; c < MAP_COLS; c++) {
289
  const zoneId = getZoneId(r, c);
290
  let creatorFunc;
 
291
  if (r === 0) creatorFunc = createForestZone;
292
  else if (r === 1 && c % 2 === 0) creatorFunc = createFieldZone;
293
  else if (r === 1 && c % 2 !== 0) creatorFunc = createRuinsZone;
294
  else creatorFunc = createCaveZone;
295
+ zoneCreators[zoneId] = () => creatorFunc(zoneId);
296
  }
297
  }
298
  console.log("Zone creators populated.");
 
304
  const r = parseInt(parts[1]);
305
  const c = parseInt(parts[2]);
306
  const neighbors = {};
307
+ if (r > 0 && zoneCreators[getZoneId(r - 1, c)]) neighbors.north = getZoneId(r - 1, c);
308
+ if (r < MAP_ROWS - 1 && zoneCreators[getZoneId(r + 1, c)]) neighbors.south = getZoneId(r + 1, c);
309
+ if (c > 0 && zoneCreators[getZoneId(r, c - 1)]) neighbors.west = getZoneId(r, c - 1);
310
+ if (c < MAP_COLS - 1 && zoneCreators[getZoneId(r, c + 1)]) neighbors.east = getZoneId(r, c + 1);
311
  return neighbors;
312
  }
313
 
 
322
  character: JSON.parse(JSON.stringify(defaultChar))
323
  };
324
  populateZoneCreators();
325
+ zoneGroups = {}; // Clear loaded zones
326
+ // Clear existing groups from worldGroup if any
327
+ while(worldGroup.children.length > 0){
328
+ worldGroup.remove(worldGroup.children[0]);
329
+ }
330
  console.log("Starting new game:", gameState);
331
+ transitionToZone(getZoneId(1, 1)); // Start in center
332
  }
333
 
334
  function transitionToZone(newZoneId) {
335
  console.log(`Transitioning to ${newZoneId}`);
336
  currentMessage = "";
337
 
 
338
  if (gameState.currentZoneId && zoneGroups[gameState.currentZoneId]) {
339
+ zoneGroups[gameState.currentZoneId].group.visible = false;
340
  }
341
 
342
  let zoneInfo;
 
343
  if (zoneGroups[newZoneId]) {
344
  zoneInfo = zoneGroups[newZoneId];
345
  zoneInfo.group.visible = true;
346
+ console.log(`Re-entering zone ${newZoneId}`);
347
  } else {
348
  const creator = zoneCreators[newZoneId];
349
  if (creator) {
350
+ zoneInfo = creator();
351
+ zoneGroups[newZoneId] = zoneInfo;
352
  worldGroup.add(zoneInfo.group);
353
  zoneInfo.group.visible = true;
354
+ console.log(`Created and entered zone ${newZoneId}`);
355
  } else {
356
+ console.error(`No creator found for zone ID: ${newZoneId}, going to fallback`);
357
  const fallbackId = getZoneId(1, 1);
358
+ if (!zoneGroups[fallbackId]) {
359
  zoneGroups[fallbackId] = zoneCreators[fallbackId]();
360
  worldGroup.add(zoneGroups[fallbackId].group);
361
  }
 
369
  gameState.currentZoneId = newZoneId;
370
  setupLighting(zoneInfo.lighting || 'default');
371
 
372
+ // Add zone-specific lights to the group if they exist in currentLights but not scene
373
+ currentLights.forEach(light => {
374
+ if (light.isPointLight && !light.parent) { // Only add point lights not already in scene
375
+ zoneInfo.group.add(light);
376
+ }
377
+ });
378
+
379
+
380
  camera.position.set(0, 6, 12);
381
  controls.target.set(0, 1, 0);
382
  controls.update();
 
387
 
388
  function renderCurrentPageUI() {
389
  const zoneInfo = zoneGroups[gameState.currentZoneId];
390
+ const zoneId = gameState.currentZoneId;
391
 
392
  if (!zoneInfo) {
393
+ console.error(`No zone info loaded for ${zoneId}`);
394
  storyTitleElement.textContent = "Error";
395
  storyContentElement.innerHTML = currentMessage + "<p>Cannot render current zone.</p>";
396
  choicesElement.innerHTML = `<button class="choice-button" onclick="transitionToZone('${getZoneId(1, 1)}')">Return to Start</button>`;
397
  updateStatsDisplay();
398
  updateInventoryDisplay();
399
+ updateActionInfo();
400
  return;
401
  }
402
 
 
404
  storyContentElement.innerHTML = currentMessage + (zoneInfo.entryText ? `<p>${zoneInfo.entryText}</p>` : '');
405
  choicesElement.innerHTML = '';
406
 
407
+ const neighbors = getZoneNeighbors(zoneId);
 
408
  const directions = {'north': 'North', 'south': 'South', 'east': 'East', 'west': 'West'};
409
  for(const dir in neighbors) {
410
  const neighborId = neighbors[dir];
 
415
  choicesElement.appendChild(button);
416
  }
417
 
 
418
  if (zoneInfo.options && zoneInfo.options.length > 0) {
419
  zoneInfo.options.forEach(option => {
420
  const button = document.createElement('button');
421
  button.classList.add('choice-button');
422
  button.textContent = option.text;
423
+ // Add onclick based on option.action later
424
  choicesElement.appendChild(button);
425
  });
426
  }
 
430
  updateActionInfo();
431
  }
432
 
433
+ window.transitionToZone = transitionToZone; // Make accessible from potential inline event handlers if needed
434
+
435
+
436
  function updateStatsDisplay() {
437
  const { hp, maxHp, xp } = gameState.character.stats;
438
  const hpColor = hp / maxHp < 0.3 ? '#f88' : (hp / maxHp < 0.6 ? '#fd5' : '#8f8');
 
457
  actionInfoElement.textContent = `Zone: ${gameState.currentZoneId || 'None'}`;
458
  }
459
 
460
+ function pickupItem() { console.log("Pickup disabled."); }
461
+ function placeItem() { console.log("Placement disabled."); }
462
+ function togglePlacementMode() { console.log("Placement disabled.");}
463
+ function startCombat(enemyTypeId) { console.log("Combat disabled."); }
464
+ function handleCombatAction(action) { console.log("Combat disabled."); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
 
466
 
 
467
  document.addEventListener('DOMContentLoaded', () => {
468
  console.log("DOM Ready - Initializing World Grid Test.");
469
  try {
470
  initThreeJS();
471
  if (!scene || !camera || !renderer) throw new Error("Three.js failed to initialize.");
472
+ startGame();
473
  console.log("Game world initialized and started.");
474
  } catch (error) {
475
  console.error("Initialization failed:", error);