awacke1 commited on
Commit
f949e46
·
verified ·
1 Parent(s): e172c2b

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +101 -95
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>Wacky Shapes Adventure!</title>
7
  <style>
8
  /* --- Base Styles (Similar to previous, slightly tweaked) --- */
9
  body { font-family: 'Verdana', sans-serif; background-color: #2a3a4a; color: #f0f0f0; margin: 0; padding: 0; overflow: hidden; display: flex; flex-direction: column; height: 100vh; }
@@ -39,21 +39,25 @@
39
  .message-xp { color: #af8; border-left-color: #6a4; }
40
  .message-warning { color: #f99; border-left-color: #c66; }
41
 
 
 
42
  </style>
43
  </head>
44
  <body>
45
  <div id="game-container">
46
- <div id="scene-container"></div>
 
 
47
  <div id="ui-container">
48
- <h2 id="story-title">Adventure Awaits!</h2>
49
- <div id="story-content"><p>Getting ready...</p></div>
50
  <div id="stats-inventory-container">
51
- <div id="stats-display"></div>
52
- <div id="inventory-display"></div>
53
  </div>
54
  <div id="choices-container">
55
- <h3>What adventure will you choose?</h3>
56
- <div id="choices"></div>
57
  </div>
58
  </div>
59
  </div>
@@ -70,19 +74,25 @@
70
 
71
  console.log("Script module execution started.");
72
 
 
73
  const sceneContainer = document.getElementById('scene-container');
74
  const storyTitleElement = document.getElementById('story-title');
75
  const storyContentElement = document.getElementById('story-content');
76
  const choicesElement = document.getElementById('choices');
77
  const statsElement = document.getElementById('stats-display');
78
  const inventoryElement = document.getElementById('inventory-display');
 
 
 
79
 
 
80
  let scene, camera, renderer, clock;
81
- let currentSceneGroup = null; // Holds the group for the current scene's objects
82
  let currentLights = [];
83
- let currentMessage = ""; // Accumulates messages for UI update
84
 
85
- const MAT = { // Material library
 
86
  stone_grey: new THREE.MeshStandardMaterial({ color: 0x8a8a8a, roughness: 0.8 }),
87
  stone_brown: new THREE.MeshStandardMaterial({ color: 0x9d8468, roughness: 0.85 }),
88
  wood_light: new THREE.MeshStandardMaterial({ color: 0xcdaa7d, roughness: 0.7 }),
@@ -101,7 +111,7 @@
101
  };
102
 
103
  // --- Game State ---
104
- let gameState = {}; // Initialized in startGame
105
 
106
  // --- Item Data ---
107
  const itemsData = {
@@ -109,6 +119,7 @@
109
  "Shiny Sprocket": {type:"treasure", description:"A gear that gleams."},
110
  "Bouncy Mushroom": {type:"food", description:"Seems edible... maybe?"},
111
  "Sturdy Stick": {type:"tool", description:"Good for poking things."},
 
112
  };
113
 
114
  // --- Procedural Assembly Shapes ---
@@ -124,7 +135,7 @@
124
  'gem_shape': (size) => new THREE.OctahedronGeometry(size * 0.6, 0),
125
  'basic_tetra': (size) => new THREE.TetrahedronGeometry(size * 0.7, 0),
126
  'squashed_ball': (size) => new THREE.SphereGeometry(size * 0.6, 12, 8).scale(1, 0.6, 1),
127
- 'holed_box': (size) => { // Example of more complex primitive combo
128
  const shape = new THREE.Shape();
129
  shape.moveTo(-size/2, -size/2); shape.lineTo(-size/2, size/2); shape.lineTo(size/2, size/2); shape.lineTo(size/2, -size/2); shape.closePath();
130
  const hole = new THREE.Path();
@@ -180,8 +191,8 @@
180
  title: "Tree's Secret",
181
  content: "<p>Aha! Tucked behind the tree trunk, you found a Sturdy Stick!</p>",
182
  options: [ { text: "Awesome! Go back.", next: 2 } ],
183
- reward: { addItem: "Sturdy Stick" },
184
- assemblyParams: { // Same as page 2, maybe slightly zoomed?
185
  baseShape: 'ground_dirt', baseSize: 25,
186
  mainShapes: ['tall_cylinder', 'basic_tetra', 'round_blob'],
187
  accents: ['spiky_ball'],
@@ -207,7 +218,7 @@
207
  title: "Picked a Gem!",
208
  content: "<p>Success! You plucked a pretty crystal from the ground. It feels warm.</p>",
209
  options: [ { text: "Cool! Go back.", next: 1 } ],
210
- reward: { addItem: "Cave Crystal" }, // Re-use item name
211
  assemblyParams: { // Same as page 3
212
  baseShape: 'ground_grass', baseSize: 15,
213
  mainShapes: ['gem_shape', 'basic_tetra'],
@@ -221,7 +232,7 @@
221
  title: "Adventure Paused!",
222
  content: "<p>Wow, what an adventure! That's all for now, but maybe more fun awaits another day?</p>",
223
  options: [ { text: "Start Over?", next: 1 } ],
224
- assemblyParams: { // Simple ending scene
225
  baseShape: 'flat_plate', baseSize: 10,
226
  mainShapes: ['basic_tetra'],
227
  accents: [],
@@ -237,7 +248,7 @@
237
  function initThreeJS() {
238
  console.log("initThreeJS started.");
239
  scene = new THREE.Scene();
240
- scene.background = new THREE.Color(0x2a3a4a); // Match body background
241
  clock = new THREE.Clock();
242
 
243
  const width = sceneContainer.clientWidth || 1;
@@ -298,11 +309,11 @@
298
  currentLights.forEach(light => { if (light.parent) light.parent.remove(light); if(scene.children.includes(light)) scene.remove(light); });
299
  currentLights = [];
300
 
301
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.6); // Slightly brighter ambient
302
  scene.add(ambientLight);
303
  currentLights.push(ambientLight);
304
 
305
- const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0); // Stronger directional
306
  directionalLight.position.set(8, 15, 10);
307
  directionalLight.castShadow = true;
308
  directionalLight.shadow.mapSize.set(1024, 1024);
@@ -315,7 +326,6 @@
315
  currentLights.push(directionalLight);
316
  }
317
 
318
- // --- Procedural Assembly Function ---
319
  function createProceduralAssembly(params) {
320
  console.log("Creating procedural assembly with params:", params);
321
  const group = new THREE.Group();
@@ -324,146 +334,147 @@
324
  mainShapes = ['boxy_chunk'], accents = ['pointy_cone'],
325
  count = 10, scaleRange = [0.5, 1.5],
326
  colorTheme = [MAT.stone_grey, MAT.wood_light, MAT.leaf_green],
327
- arrangement = 'scatter', // scatter, cluster, stack, patch, center_stack
328
- stackHeight = 5, // for stack/center_stack
329
- clusterRadius = 5, // for cluster
330
- patchPos = {x:0, y:0, z:0}, patchRadius = 3 // for patch
331
  } = params;
332
 
333
- // 1. Create Base
334
  let baseMesh;
335
  if (baseShape.startsWith('ground_')) {
336
- baseMesh = createGround(MAT[baseShape] || MAT.ground_grass, baseSize);
 
 
 
 
337
  group.add(baseMesh);
338
  } else {
339
- const baseGeo = SHAPE_GENERATORS[baseShape] ? SHAPE_GENERATORS[baseShape](baseSize) : new THREE.BoxGeometry(baseSize, 0.2, baseSize);
 
340
  baseMesh = createMesh(baseGeo, colorTheme[0] || MAT.stone_grey, {y:0.1});
341
  baseMesh.receiveShadow = true; baseMesh.castShadow = false;
342
  group.add(baseMesh);
343
  }
344
 
345
- // 2. Generate Objects
346
  const allShapes = [...mainShapes, ...accents];
347
  let lastY = 0; // For stacking
 
348
 
349
  for (let i = 0; i < count; i++) {
 
350
  const shapeKey = allShapes[Math.floor(Math.random() * allShapes.length)];
351
  const geoFunc = SHAPE_GENERATORS[shapeKey];
352
  if (!geoFunc) { console.warn(`Shape generator not found for key: ${shapeKey}`); continue; }
353
 
354
- const scale = scaleRange[0] + Math.random() * (scaleRange[1] - scaleRange[0]);
355
- const geometry = geoFunc(scale);
356
  const material = colorTheme[Math.floor(Math.random() * colorTheme.length)];
357
  const mesh = createMesh(geometry, material);
358
 
359
- // Calculate position based on arrangement
360
- let position = { x:0, y:0, z:0 };
361
  geometry.computeBoundingBox();
362
  const height = (geometry.boundingBox.max.y - geometry.boundingBox.min.y) * mesh.scale.y;
 
363
 
364
  switch(arrangement) {
365
  case 'stack':
366
- position.y = lastY + height / 2;
367
- position.x = (Math.random() - 0.5) * 0.5; // Slight offset
368
- position.z = (Math.random() - 0.5) * 0.5;
369
- lastY += height * 0.9; // Stack slightly overlapping
370
- if (j > stackHeight) break; // Limit height
 
 
371
  break;
372
  case 'center_stack':
373
- position.y = lastY + height / 2;
374
- lastY += height * 0.95;
375
- if (i >= stackHeight) { // Only stack up to height, then stop adding
376
- continue; // Skip adding more meshes if stack is full
377
- }
378
  break;
379
  case 'cluster':
380
  const angle = Math.random() * Math.PI * 2;
381
  const radius = Math.random() * clusterRadius;
382
  position.x = Math.cos(angle) * radius;
383
  position.z = Math.sin(angle) * radius;
384
- position.y = height / 2; // Place on ground
385
  break;
386
  case 'patch':
387
  const pAngle = Math.random() * Math.PI * 2;
388
  const pRadius = Math.random() * patchRadius;
389
  position.x = patchPos.x + Math.cos(pAngle) * pRadius;
390
  position.z = patchPos.z + Math.sin(pAngle) * pRadius;
391
- position.y = (patchPos.y || 0) + height / 2; // Place relative to patch center Y
392
  break;
393
  case 'scatter':
394
  default:
395
  position.x = (Math.random() - 0.5) * baseSize * 0.8;
396
  position.z = (Math.random() - 0.5) * baseSize * 0.8;
397
- position.y = height / 2; // Place on ground
398
  break;
399
  }
400
  mesh.position.set(position.x, position.y, position.z);
401
 
402
- // Random rotation
403
  mesh.rotation.set(
404
- Math.random() * (arrangement === 'stack' || arrangement === 'center_stack' ? 0.1 : Math.PI), // Less random rotation if stacked
405
  Math.random() * Math.PI * 2,
406
- Math.random() * (arrangement === 'stack' || arrangement === 'center_stack' ? 0.1 : Math.PI)
407
  );
408
 
409
- // Add simple animation (optional)
410
- if (Math.random() < 0.3) { // 30% chance of animation
411
- mesh.userData.rotSpeed = (Math.random() - 0.5) * 0.5; // Slow rotation
412
- mesh.userData.update = (time) => {
413
- mesh.rotation.y += mesh.userData.rotSpeed * clock.getDelta();
 
 
 
414
  };
415
  }
416
 
417
  group.add(mesh);
418
  }
419
-
420
  return group;
421
  }
422
 
423
-
424
- // --- Scene Management (Back to Page-Based) ---
425
  function updateScene(assemblyParams) {
426
- console.log("updateScene called");
427
- // Clear previous scene objects
428
  if (currentSceneGroup) {
429
  currentSceneGroup.traverse(child => {
430
  if (child.isMesh) {
431
  if(child.geometry) child.geometry.dispose();
432
- // Assume materials in MAT are shared, don't dispose them unless cloned per object
433
  }
434
  });
435
- scene.remove(currentSceneGroup); // Remove the group from the main scene
436
  currentSceneGroup = null;
437
  }
438
- // Clear previous lights (except maybe a default ambient?)
439
- setupLighting('default'); // Reset to default lighting for each scene load
440
 
441
  if (!assemblyParams) {
442
  console.warn("No assemblyParams provided, creating default scene.");
443
- assemblyParams = { baseShape: 'ground_dirt', count: 5 }; // Basic default
444
  }
445
 
446
  try {
447
  currentSceneGroup = createProceduralAssembly(assemblyParams);
448
- scene.add(currentSceneGroup); // Add the new group
449
  console.log("New scene group added.");
450
  } catch (error) {
451
  console.error("Error creating procedural assembly:", error);
452
- // Optionally add an error indicator scene
453
  }
454
  }
455
 
456
 
457
- // --- Game Logic ---
458
  function startGame() {
459
  console.log("startGame called.");
460
  const defaultChar = {
461
  name: "Player",
462
  stats: { hp: 20, maxHp: 20, xp: 0, strength: 10, dexterity: 10, constitution: 10, intelligence: 10, wisdom: 10, charisma: 10 },
463
- inventory: ["Sturdy Stick"] // Start with an item
464
  };
465
  gameState = {
466
- currentPageId: 1, // Start at page 1
467
  character: JSON.parse(JSON.stringify(defaultChar))
468
  };
469
  renderPage(gameState.currentPageId);
@@ -472,7 +483,7 @@
472
 
473
  function handleChoiceClick(choiceData) {
474
  console.log("Choice clicked:", choiceData);
475
- currentMessage = ""; // Clear previous messages
476
 
477
  let nextPageId = parseInt(choiceData.next);
478
  const currentPageData = gameData[gameState.currentPageId];
@@ -481,12 +492,11 @@
481
  if (!targetPageData) {
482
  console.error(`Invalid next page ID: ${nextPageId}`);
483
  currentMessage = `<p class="message message-warning">Oops! That path seems to lead nowhere yet.</p>`;
484
- nextPageId = gameState.currentPageId; // Stay on current page on error
485
  }
486
 
487
- // Apply rewards from *current* page choice before transitioning
488
  if (currentPageData && currentPageData.options) {
489
- const chosenOption = currentPageData.options.find(opt => opt.next === choiceData.next); // Find the specific option clicked
490
  if (chosenOption && chosenOption.reward) {
491
  console.log("Applying reward:", chosenOption.reward);
492
  if(chosenOption.reward.addItem && itemsData[chosenOption.reward.addItem]) {
@@ -494,18 +504,16 @@
494
  gameState.character.inventory.push(chosenOption.reward.addItem);
495
  currentMessage += `<p class="message message-item">You found a ${chosenOption.reward.addItem}!</p>`;
496
  } else {
497
- currentMessage += `<p class="message message-info">You found another ${chosenOption.reward.addItem}, but can't carry more.</p>`;
498
  }
499
  }
500
  if(chosenOption.reward.xp) {
501
  gameState.character.stats.xp += chosenOption.reward.xp;
502
  currentMessage += `<p class="message message-xp">You gained ${chosenOption.reward.xp} XP!</p>`;
503
  }
504
- // Add stat gain logic here if needed for specific rewards
505
  }
506
  }
507
 
508
-
509
  gameState.currentPageId = nextPageId;
510
  renderPage(nextPageId);
511
  }
@@ -516,31 +524,30 @@
516
 
517
  if (!pageData) {
518
  console.error(`No page data for ID: ${pageId}`);
519
- storyTitleElement.textContent = "Lost!";
520
- storyContentElement.innerHTML = currentMessage + `<p>Somehow you've wandered off the map!</p>`;
521
- choicesElement.innerHTML = `<button class="choice-button" onclick="handleChoiceClick({ next: 1 })">Go Back to Start</button>`;
522
  updateStatsDisplay(); updateInventoryDisplay(); updateActionInfo();
523
- updateScene({ baseShape: 'ground_dirt', count: 1, mainShapes: ['basic_tetra'], colorTheme: [MAT.metal_rusty] }); // Simple error scene
524
  return;
525
  }
526
 
527
  storyTitleElement.textContent = pageData.title || "An Unnamed Place";
528
- storyContentElement.innerHTML = currentMessage + (pageData.content || "<p>...</p>");
529
- choicesElement.innerHTML = ''; // Clear previous choices
530
 
531
  if (pageData.options && pageData.options.length > 0) {
532
  pageData.options.forEach(option => {
533
  const button = document.createElement('button');
534
  button.classList.add('choice-button');
535
  button.textContent = option.text;
536
- button.onclick = () => handleChoiceClick(option); // Pass the whole option
537
  choicesElement.appendChild(button);
538
  });
539
  } else {
540
- // Maybe always add a restart button if no other options?
541
  const button = document.createElement('button');
542
  button.classList.add('choice-button');
543
- button.textContent = "The End (Restart?)";
544
  button.onclick = () => handleChoiceClick({ next: 1 });
545
  choicesElement.appendChild(button);
546
  }
@@ -548,16 +555,14 @@
548
  updateStatsDisplay();
549
  updateInventoryDisplay();
550
  updateActionInfo();
551
- updateScene(pageData.assemblyParams); // Update 3D scene based on page params
552
  }
553
- // Make handleChoiceClick globally accessible for inline handlers
554
  window.handleChoiceClick = handleChoiceClick;
555
 
556
  function updateStatsDisplay() {
557
  if (!gameState.character || !statsElement) return;
558
  const stats = gameState.character.stats;
559
  const hpColor = stats.hp / stats.maxHp < 0.3 ? '#f88' : (stats.hp / stats.maxHp < 0.6 ? '#fd5' : '#8f8');
560
-
561
  statsElement.innerHTML = `<strong>Stats:</strong>
562
  <span style="color:${hpColor}">HP: ${stats.hp}/${stats.maxHp}</span> <span>XP: ${stats.xp}</span><br>
563
  <span>Str: ${stats.strength}</span> <span>Dex: ${stats.dexterity}</span> <span>Con: ${stats.constitution}</span>
@@ -580,26 +585,27 @@
580
  }
581
 
582
  function updateActionInfo() {
583
- if (!actionInfoElement || !gameState ) return;
584
- actionInfoElement.textContent = `Location: ${gameData[gameState.currentPageId]?.title || 'Unknown'}`;
585
  }
586
 
587
 
588
- // --- Initialization ---
589
  document.addEventListener('DOMContentLoaded', () => {
590
- console.log("DOM Ready - Initializing Wacky Shapes Adventure.");
591
  try {
592
  initThreeJS();
593
  if (!scene || !camera || !renderer) throw new Error("Three.js failed to initialize.");
594
- startGame(); // Start game directly
595
  console.log("Game world initialized and started.");
596
  } catch (error) {
597
  console.error("Initialization failed:", error);
598
  storyTitleElement.textContent = "Initialization Error";
599
  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>`;
600
  if(sceneContainer) sceneContainer.innerHTML = '<p style="color:red; padding: 20px;">3D Scene Failed</p>';
601
- document.getElementById('stats-inventory-container').style.display = 'none';
602
- document.getElementById('choices-container').style.display = 'none';
 
 
603
  }
604
  });
605
 
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Wacky Shapes Adventure! (Fixed)</title>
7
  <style>
8
  /* --- Base Styles (Similar to previous, slightly tweaked) --- */
9
  body { font-family: 'Verdana', sans-serif; background-color: #2a3a4a; color: #f0f0f0; margin: 0; padding: 0; overflow: hidden; display: flex; flex-direction: column; height: 100vh; }
 
39
  .message-xp { color: #af8; border-left-color: #6a4; }
40
  .message-warning { color: #f99; border-left-color: #c66; }
41
 
42
+ /* --- Action Info --- */
43
+ #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;} /* Keep visible */
44
  </style>
45
  </head>
46
  <body>
47
  <div id="game-container">
48
+ <div id="scene-container">
49
+ <div id="action-info">Initializing...</div>
50
+ </div>
51
  <div id="ui-container">
52
+ <h2 id="story-title">Initializing...</h2>
53
+ <div id="story-content"><p>Loading assets...</p></div>
54
  <div id="stats-inventory-container">
55
+ <div id="stats-display">Loading Stats...</div>
56
+ <div id="inventory-display">Inventory: ...</div>
57
  </div>
58
  <div id="choices-container">
59
+ <h3>What will you do?</h3>
60
+ <div id="choices">Loading...</div>
61
  </div>
62
  </div>
63
  </div>
 
74
 
75
  console.log("Script module execution started.");
76
 
77
+ // --- DOM Elements ---
78
  const sceneContainer = document.getElementById('scene-container');
79
  const storyTitleElement = document.getElementById('story-title');
80
  const storyContentElement = document.getElementById('story-content');
81
  const choicesElement = document.getElementById('choices');
82
  const statsElement = document.getElementById('stats-display');
83
  const inventoryElement = document.getElementById('inventory-display');
84
+ const actionInfoElement = document.getElementById('action-info'); // <<< Added back the declaration
85
+
86
+ console.log("DOM elements obtained.");
87
 
88
+ // --- Core Three.js Variables ---
89
  let scene, camera, renderer, clock;
90
+ let currentSceneGroup = null;
91
  let currentLights = [];
92
+ let currentMessage = "";
93
 
94
+ // --- Materials ---
95
+ const MAT = {
96
  stone_grey: new THREE.MeshStandardMaterial({ color: 0x8a8a8a, roughness: 0.8 }),
97
  stone_brown: new THREE.MeshStandardMaterial({ color: 0x9d8468, roughness: 0.85 }),
98
  wood_light: new THREE.MeshStandardMaterial({ color: 0xcdaa7d, roughness: 0.7 }),
 
111
  };
112
 
113
  // --- Game State ---
114
+ let gameState = {};
115
 
116
  // --- Item Data ---
117
  const itemsData = {
 
119
  "Shiny Sprocket": {type:"treasure", description:"A gear that gleams."},
120
  "Bouncy Mushroom": {type:"food", description:"Seems edible... maybe?"},
121
  "Sturdy Stick": {type:"tool", description:"Good for poking things."},
122
+ "Cave Crystal": {type:"unknown", description:"A faintly glowing crystal shard."} // Added from previous example
123
  };
124
 
125
  // --- Procedural Assembly Shapes ---
 
135
  'gem_shape': (size) => new THREE.OctahedronGeometry(size * 0.6, 0),
136
  'basic_tetra': (size) => new THREE.TetrahedronGeometry(size * 0.7, 0),
137
  'squashed_ball': (size) => new THREE.SphereGeometry(size * 0.6, 12, 8).scale(1, 0.6, 1),
138
+ 'holed_box': (size) => {
139
  const shape = new THREE.Shape();
140
  shape.moveTo(-size/2, -size/2); shape.lineTo(-size/2, size/2); shape.lineTo(size/2, size/2); shape.lineTo(size/2, -size/2); shape.closePath();
141
  const hole = new THREE.Path();
 
191
  title: "Tree's Secret",
192
  content: "<p>Aha! Tucked behind the tree trunk, you found a Sturdy Stick!</p>",
193
  options: [ { text: "Awesome! Go back.", next: 2 } ],
194
+ reward: { addItem: "Sturdy Stick", xp: 5 }, // Add reward
195
+ assemblyParams: { // Same as page 2
196
  baseShape: 'ground_dirt', baseSize: 25,
197
  mainShapes: ['tall_cylinder', 'basic_tetra', 'round_blob'],
198
  accents: ['spiky_ball'],
 
218
  title: "Picked a Gem!",
219
  content: "<p>Success! You plucked a pretty crystal from the ground. It feels warm.</p>",
220
  options: [ { text: "Cool! Go back.", next: 1 } ],
221
+ reward: { addItem: "Cave Crystal", xp: 10 }, // Add reward
222
  assemblyParams: { // Same as page 3
223
  baseShape: 'ground_grass', baseSize: 15,
224
  mainShapes: ['gem_shape', 'basic_tetra'],
 
232
  title: "Adventure Paused!",
233
  content: "<p>Wow, what an adventure! That's all for now, but maybe more fun awaits another day?</p>",
234
  options: [ { text: "Start Over?", next: 1 } ],
235
+ assemblyParams: {
236
  baseShape: 'flat_plate', baseSize: 10,
237
  mainShapes: ['basic_tetra'],
238
  accents: [],
 
248
  function initThreeJS() {
249
  console.log("initThreeJS started.");
250
  scene = new THREE.Scene();
251
+ scene.background = new THREE.Color(0x2a3a4a);
252
  clock = new THREE.Clock();
253
 
254
  const width = sceneContainer.clientWidth || 1;
 
309
  currentLights.forEach(light => { if (light.parent) light.parent.remove(light); if(scene.children.includes(light)) scene.remove(light); });
310
  currentLights = [];
311
 
312
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
313
  scene.add(ambientLight);
314
  currentLights.push(ambientLight);
315
 
316
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
317
  directionalLight.position.set(8, 15, 10);
318
  directionalLight.castShadow = true;
319
  directionalLight.shadow.mapSize.set(1024, 1024);
 
326
  currentLights.push(directionalLight);
327
  }
328
 
 
329
  function createProceduralAssembly(params) {
330
  console.log("Creating procedural assembly with params:", params);
331
  const group = new THREE.Group();
 
334
  mainShapes = ['boxy_chunk'], accents = ['pointy_cone'],
335
  count = 10, scaleRange = [0.5, 1.5],
336
  colorTheme = [MAT.stone_grey, MAT.wood_light, MAT.leaf_green],
337
+ arrangement = 'scatter',
338
+ stackHeight = 5,
339
+ clusterRadius = 5,
340
+ patchPos = {x:0, y:0, z:0}, patchRadius = 3
341
  } = params;
342
 
 
343
  let baseMesh;
344
  if (baseShape.startsWith('ground_')) {
345
+ const groundMat = MAT[baseShape] || MAT.ground_grass;
346
+ const groundGeo = new THREE.PlaneGeometry(baseSize, baseSize);
347
+ baseMesh = new THREE.Mesh(groundGeo, groundMat);
348
+ baseMesh.rotation.x = -Math.PI / 2; baseMesh.position.y = 0;
349
+ baseMesh.receiveShadow = true; baseMesh.castShadow = false;
350
  group.add(baseMesh);
351
  } else {
352
+ const baseGeoFunc = SHAPE_GENERATORS[baseShape] || SHAPE_GENERATORS['flat_plate'];
353
+ const baseGeo = baseGeoFunc(baseSize);
354
  baseMesh = createMesh(baseGeo, colorTheme[0] || MAT.stone_grey, {y:0.1});
355
  baseMesh.receiveShadow = true; baseMesh.castShadow = false;
356
  group.add(baseMesh);
357
  }
358
 
 
359
  const allShapes = [...mainShapes, ...accents];
360
  let lastY = 0; // For stacking
361
+ let stackCount = 0; // Track items in current stack
362
 
363
  for (let i = 0; i < count; i++) {
364
+ if(allShapes.length === 0) break; // Avoid errors if no shapes defined
365
  const shapeKey = allShapes[Math.floor(Math.random() * allShapes.length)];
366
  const geoFunc = SHAPE_GENERATORS[shapeKey];
367
  if (!geoFunc) { console.warn(`Shape generator not found for key: ${shapeKey}`); continue; }
368
 
369
+ const scaleFactor = scaleRange[0] + Math.random() * (scaleRange[1] - scaleRange[0]);
370
+ const geometry = geoFunc(scaleFactor);
371
  const material = colorTheme[Math.floor(Math.random() * colorTheme.length)];
372
  const mesh = createMesh(geometry, material);
373
 
 
 
374
  geometry.computeBoundingBox();
375
  const height = (geometry.boundingBox.max.y - geometry.boundingBox.min.y) * mesh.scale.y;
376
+ let position = { x:0, y:0, z:0 };
377
 
378
  switch(arrangement) {
379
  case 'stack':
380
+ if (stackCount < stackHeight) {
381
+ position.y = lastY + height / 2;
382
+ position.x = (Math.random() - 0.5) * 0.5 * scaleFactor;
383
+ position.z = (Math.random() - 0.5) * 0.5 * scaleFactor;
384
+ lastY += height * 0.9;
385
+ stackCount++;
386
+ } else continue; // Skip if stack is full
387
  break;
388
  case 'center_stack':
389
+ if (stackCount < stackHeight) {
390
+ position.y = lastY + height / 2;
391
+ lastY += height * 0.95;
392
+ stackCount++;
393
+ } else continue;
394
  break;
395
  case 'cluster':
396
  const angle = Math.random() * Math.PI * 2;
397
  const radius = Math.random() * clusterRadius;
398
  position.x = Math.cos(angle) * radius;
399
  position.z = Math.sin(angle) * radius;
400
+ position.y = height / 2;
401
  break;
402
  case 'patch':
403
  const pAngle = Math.random() * Math.PI * 2;
404
  const pRadius = Math.random() * patchRadius;
405
  position.x = patchPos.x + Math.cos(pAngle) * pRadius;
406
  position.z = patchPos.z + Math.sin(pAngle) * pRadius;
407
+ position.y = (patchPos.y || 0) + height / 2;
408
  break;
409
  case 'scatter':
410
  default:
411
  position.x = (Math.random() - 0.5) * baseSize * 0.8;
412
  position.z = (Math.random() - 0.5) * baseSize * 0.8;
413
+ position.y = height / 2;
414
  break;
415
  }
416
  mesh.position.set(position.x, position.y, position.z);
417
 
 
418
  mesh.rotation.set(
419
+ Math.random() * (arrangement.includes('stack') ? 0.2 : Math.PI), // Less x/z rotation if stacked
420
  Math.random() * Math.PI * 2,
421
+ Math.random() * (arrangement.includes('stack') ? 0.2 : Math.PI)
422
  );
423
 
424
+ if (Math.random() < 0.3) {
425
+ mesh.userData.rotSpeed = (Math.random() - 0.5) * 0.4;
426
+ mesh.userData.bobSpeed = 1 + Math.random();
427
+ mesh.userData.bobAmount = 0.05 + Math.random() * 0.05;
428
+ mesh.userData.startY = mesh.position.y;
429
+ mesh.userData.update = (time, delta) => {
430
+ mesh.rotation.y += mesh.userData.rotSpeed * delta;
431
+ mesh.position.y = mesh.userData.startY + Math.sin(time * mesh.userData.bobSpeed) * mesh.userData.bobAmount;
432
  };
433
  }
434
 
435
  group.add(mesh);
436
  }
437
+ console.log(`Assembly created with ${group.children.length} objects.`);
438
  return group;
439
  }
440
 
 
 
441
  function updateScene(assemblyParams) {
442
+ console.log("updateScene called with params:", assemblyParams);
 
443
  if (currentSceneGroup) {
444
  currentSceneGroup.traverse(child => {
445
  if (child.isMesh) {
446
  if(child.geometry) child.geometry.dispose();
 
447
  }
448
  });
449
+ scene.remove(currentSceneGroup);
450
  currentSceneGroup = null;
451
  }
452
+ setupLighting('default'); // Reset lighting
 
453
 
454
  if (!assemblyParams) {
455
  console.warn("No assemblyParams provided, creating default scene.");
456
+ assemblyParams = { baseShape: 'ground_dirt', count: 5, mainShapes: ['boxy_chunk'] };
457
  }
458
 
459
  try {
460
  currentSceneGroup = createProceduralAssembly(assemblyParams);
461
+ scene.add(currentSceneGroup);
462
  console.log("New scene group added.");
463
  } catch (error) {
464
  console.error("Error creating procedural assembly:", error);
 
465
  }
466
  }
467
 
468
 
 
469
  function startGame() {
470
  console.log("startGame called.");
471
  const defaultChar = {
472
  name: "Player",
473
  stats: { hp: 20, maxHp: 20, xp: 0, strength: 10, dexterity: 10, constitution: 10, intelligence: 10, wisdom: 10, charisma: 10 },
474
+ inventory: ["Sturdy Stick"]
475
  };
476
  gameState = {
477
+ currentPageId: 1,
478
  character: JSON.parse(JSON.stringify(defaultChar))
479
  };
480
  renderPage(gameState.currentPageId);
 
483
 
484
  function handleChoiceClick(choiceData) {
485
  console.log("Choice clicked:", choiceData);
486
+ currentMessage = "";
487
 
488
  let nextPageId = parseInt(choiceData.next);
489
  const currentPageData = gameData[gameState.currentPageId];
 
492
  if (!targetPageData) {
493
  console.error(`Invalid next page ID: ${nextPageId}`);
494
  currentMessage = `<p class="message message-warning">Oops! That path seems to lead nowhere yet.</p>`;
495
+ nextPageId = gameState.currentPageId;
496
  }
497
 
 
498
  if (currentPageData && currentPageData.options) {
499
+ const chosenOption = currentPageData.options.find(opt => opt.next === choiceData.next);
500
  if (chosenOption && chosenOption.reward) {
501
  console.log("Applying reward:", chosenOption.reward);
502
  if(chosenOption.reward.addItem && itemsData[chosenOption.reward.addItem]) {
 
504
  gameState.character.inventory.push(chosenOption.reward.addItem);
505
  currentMessage += `<p class="message message-item">You found a ${chosenOption.reward.addItem}!</p>`;
506
  } else {
507
+ currentMessage += `<p class="message message-info">You found another ${chosenOption.reward.addItem}, but your pockets are full!</p>`;
508
  }
509
  }
510
  if(chosenOption.reward.xp) {
511
  gameState.character.stats.xp += chosenOption.reward.xp;
512
  currentMessage += `<p class="message message-xp">You gained ${chosenOption.reward.xp} XP!</p>`;
513
  }
 
514
  }
515
  }
516
 
 
517
  gameState.currentPageId = nextPageId;
518
  renderPage(nextPageId);
519
  }
 
524
 
525
  if (!pageData) {
526
  console.error(`No page data for ID: ${pageId}`);
527
+ storyTitleElement.textContent = "Uh Oh!";
528
+ storyContentElement.innerHTML = currentMessage + `<p>Where did the world go? Maybe try going back?</p>`;
529
+ choicesElement.innerHTML = `<button class="choice-button" onclick="window.handleChoiceClick({ next: 1 })">Go Back to Start</button>`;
530
  updateStatsDisplay(); updateInventoryDisplay(); updateActionInfo();
531
+ updateScene({ baseShape: 'ground_dirt', count: 1, mainShapes: ['basic_tetra'], colorTheme: [MAT.metal_rusty] });
532
  return;
533
  }
534
 
535
  storyTitleElement.textContent = pageData.title || "An Unnamed Place";
536
+ storyContentElement.innerHTML = currentMessage + (pageData.content || "<p>It's... a place. Things exist here.</p>");
537
+ choicesElement.innerHTML = '';
538
 
539
  if (pageData.options && pageData.options.length > 0) {
540
  pageData.options.forEach(option => {
541
  const button = document.createElement('button');
542
  button.classList.add('choice-button');
543
  button.textContent = option.text;
544
+ button.onclick = () => handleChoiceClick(option);
545
  choicesElement.appendChild(button);
546
  });
547
  } else {
 
548
  const button = document.createElement('button');
549
  button.classList.add('choice-button');
550
+ button.textContent = "The End (Start Over?)";
551
  button.onclick = () => handleChoiceClick({ next: 1 });
552
  choicesElement.appendChild(button);
553
  }
 
555
  updateStatsDisplay();
556
  updateInventoryDisplay();
557
  updateActionInfo();
558
+ updateScene(pageData.assemblyParams);
559
  }
 
560
  window.handleChoiceClick = handleChoiceClick;
561
 
562
  function updateStatsDisplay() {
563
  if (!gameState.character || !statsElement) return;
564
  const stats = gameState.character.stats;
565
  const hpColor = stats.hp / stats.maxHp < 0.3 ? '#f88' : (stats.hp / stats.maxHp < 0.6 ? '#fd5' : '#8f8');
 
566
  statsElement.innerHTML = `<strong>Stats:</strong>
567
  <span style="color:${hpColor}">HP: ${stats.hp}/${stats.maxHp}</span> <span>XP: ${stats.xp}</span><br>
568
  <span>Str: ${stats.strength}</span> <span>Dex: ${stats.dexterity}</span> <span>Con: ${stats.constitution}</span>
 
585
  }
586
 
587
  function updateActionInfo() {
588
+ if (!actionInfoElement || !gameState ) return;
589
+ actionInfoElement.textContent = `Location: ${gameData[gameState.currentPageId]?.title || 'Unknown'}`;
590
  }
591
 
592
 
 
593
  document.addEventListener('DOMContentLoaded', () => {
594
+ console.log("DOM Ready - Initializing Wacky Shapes Adventure!");
595
  try {
596
  initThreeJS();
597
  if (!scene || !camera || !renderer) throw new Error("Three.js failed to initialize.");
598
+ startGame();
599
  console.log("Game world initialized and started.");
600
  } catch (error) {
601
  console.error("Initialization failed:", error);
602
  storyTitleElement.textContent = "Initialization Error";
603
  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>`;
604
  if(sceneContainer) sceneContainer.innerHTML = '<p style="color:red; padding: 20px;">3D Scene Failed</p>';
605
+ const statsInvContainer = document.getElementById('stats-inventory-container');
606
+ const choicesCont = document.getElementById('choices-container');
607
+ if (statsInvContainer) statsInvContainer.style.display = 'none';
608
+ if (choicesCont) choicesCont.style.display = 'none';
609
  }
610
  });
611