awacke1 commited on
Commit
5de4218
·
verified ·
1 Parent(s): ade1218

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +53 -44
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>Persistent Procedural World</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; }
@@ -36,7 +36,7 @@
36
  .message-failure { color: #f88; border-left-color: #a44; }
37
  .message-info { color: #aaa; border-left-color: #666; }
38
  .message-item { color: #8bf; border-left-color: #46a; }
39
- #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: none; }
40
 
41
  </style>
42
  </head>
@@ -68,7 +68,7 @@
68
 
69
  <script type="module">
70
  import * as THREE from 'three';
71
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // Using controls for navigation now
72
 
73
  const sceneContainer = document.getElementById('scene-container');
74
  const storyTitleElement = document.getElementById('story-title');
@@ -78,12 +78,12 @@
78
  const inventoryElement = document.getElementById('inventory-display');
79
  const actionInfoElement = document.getElementById('action-info');
80
 
81
-
82
  let scene, camera, renderer, clock, controls, raycaster, mouse;
83
- let worldGroup = null; // Parent for all world objects
84
- let locationGroups = {}; // Store groups for each location/zone { locationId: group }
85
- let currentMessage = ""; // To accumulate UI messages
86
- let currentPlacingItem = null; // Item name being placed
 
87
 
88
  const MAT = {
89
  stone: new THREE.MeshStandardMaterial({ color: 0x777788, roughness: 0.85, metalness: 0.1 }),
@@ -127,7 +127,7 @@
127
  controls.enableDamping = true;
128
  controls.dampingFactor = 0.1;
129
  controls.target.set(0, 1, 0);
130
- controls.maxPolarAngle = Math.PI / 2 - 0.05; // Prevent looking below ground
131
 
132
  window.addEventListener('resize', onWindowResize, false);
133
  renderer.domElement.addEventListener('mousemove', onMouseMove, false);
@@ -146,8 +146,9 @@
146
  }
147
 
148
  function onMouseMove( event ) {
149
- mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
150
- mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
 
151
  }
152
 
153
  function onMouseClick( event ) {
@@ -183,12 +184,14 @@
183
  const ground = new THREE.Mesh(geo, material);
184
  ground.rotation.x = -Math.PI / 2; ground.position.y = 0;
185
  ground.receiveShadow = true; ground.castShadow = false;
186
- ground.userData.isGround = true; // Mark for raycasting
187
  return ground;
188
  }
189
 
190
  function setupLighting(type = 'default') {
191
- currentLights.forEach(light => scene.remove(light));
 
 
192
  currentLights = [];
193
 
194
  let ambientIntensity = 0.4;
@@ -231,14 +234,14 @@
231
  }
232
  }
233
 
234
- function createDefaultZone() {
235
  const group = new THREE.Group();
236
  group.add(createGround(MAT.dirt, 20));
237
  const boxGeo = new THREE.BoxGeometry(1, 1, 1);
238
  const interactBox = createMesh(boxGeo, MAT.stone, {y: 0.5, x: 2, z: 2});
239
  interactBox.userData = { isPickupable: true, itemName: "Mysterious Cube", description: "A plain stone cube."};
240
  group.add(interactBox);
241
- group.visible = false; // Start hidden
242
  return { group, lighting: 'default', entryText: "You are in a default, featureless area.", cameraPos: {x:0, y:5, z:10}, targetPos: {x:0, y:1, z:0} };
243
  }
244
 
@@ -287,7 +290,6 @@
287
  return { group, lighting: 'cave', entryText: "It's dark and damp in here.", cameraPos: {x:0, y:4, z:8}, targetPos: {x:0, y:1, z:0} };
288
  }
289
 
290
-
291
  const locationData = {
292
  'start': { creator: createDefaultZone },
293
  'forest': { creator: createForestZone },
@@ -297,11 +299,11 @@
297
  const pageGraph = {
298
  'start': {
299
  title: "The Crossroads",
300
- options: [ { text: "Enter Forest", transitionTo: 'forest' } ]
301
  },
302
  'forest': {
303
  title: "Dark Forest",
304
- options: [ { text: "Return to Crossroads", transitionTo: 'start' }, { text: "Explore Deeper (TBC)", transitionTo: 'forest' }]
305
  },
306
  'cave': {
307
  title: "Dim Cave",
@@ -322,7 +324,7 @@
322
  const defaultChar = {
323
  name: "Player",
324
  stats: { hp: 20, maxHp: 20, xp: 0 },
325
- inventory: []
326
  };
327
  gameState = {
328
  currentLocationId: null,
@@ -335,7 +337,7 @@
335
  function transitionToLocation(newLocationId) {
336
  console.log(`Transitioning from ${gameState.currentLocationId} to ${newLocationId}`);
337
  currentMessage = "";
338
- currentPlacingItem = null; // Cancel placement on transition
339
 
340
  if (gameState.currentLocationId && locationGroups[gameState.currentLocationId]) {
341
  locationGroups[gameState.currentLocationId].visible = false;
@@ -356,12 +358,14 @@
356
  worldGroup.add(newGroup);
357
  newGroup.visible = true;
358
  } else {
359
- console.error(`Location data or creator missing for ID: ${newLocationId}`);
360
- locationInfo = locationData['start'].creator();
361
- newGroup = locationInfo.group;
362
- locationGroups['start'] = newGroup;
363
- locationData['start'].cachedInfo = locationInfo;
364
- worldGroup.add(newGroup);
 
 
365
  newGroup.visible = true;
366
  newLocationId = 'start';
367
  currentMessage += `<p class="message message-failure">Error: Couldn't load target location, returned to start.</p>`;
@@ -390,6 +394,7 @@
390
  choicesElement.innerHTML = `<button class="choice-button" onclick="handleTransition({transitionTo: 'start'})">Return to Start</button>`;
391
  updateStatsDisplay();
392
  updateInventoryDisplay();
 
393
  return;
394
  }
395
 
@@ -421,7 +426,7 @@
421
  console.warn("Choice option has no transitionTo property:", option);
422
  }
423
  }
424
- window.handleTransition = handleTransition; // Make accessible from inline onclick
425
 
426
  function updateStatsDisplay() {
427
  const { hp, maxHp, xp } = gameState.character.stats;
@@ -438,13 +443,16 @@
438
  const itemDef = itemsData[item] || { type: 'unknown', description: '???' };
439
  const itemClass = `item-${itemDef.type || 'unknown'}`;
440
  const placingClass = (item === currentPlacingItem) ? ' placing' : '';
441
- invHtml += `<span class="item-tag ${itemClass}${placingClass}" title="${itemDef.description}" data-itemname="${item}">${item}</span>`;
442
  });
443
  }
444
  inventoryElement.innerHTML = invHtml;
445
 
446
  inventoryElement.querySelectorAll('.item-tag').forEach(tag => {
447
- tag.onclick = () => { togglePlacementMode(tag.dataset.itemname); };
 
 
 
448
  });
449
  }
450
 
@@ -453,19 +461,20 @@
453
  actionInfoElement.textContent = `Placing: ${currentPlacingItem} (Click ground)`;
454
  actionInfoElement.style.display = 'block';
455
  } else {
456
- actionInfoElement.textContent = `Mode: Explore (Click items)`;
457
- actionInfoElement.style.display = 'block'; // Keep it visible
458
  }
459
  }
460
 
461
-
462
  function pickupItem() {
 
 
463
  raycaster.setFromCamera(mouse, camera);
464
  const currentGroup = locationGroups[gameState.currentLocationId];
465
  if (!currentGroup) return;
466
 
467
  const pickupables = [];
468
- currentGroup.traverse(child => {
469
  if (child.userData.isPickupable) {
470
  pickupables.push(child);
471
  }
@@ -487,8 +496,8 @@
487
  currentMessage += `<p class="message message-info"><em>(You already had one)</em></p>`;
488
  }
489
 
490
- clickedObject.visible = false; // Hide it instead of removing, simpler state
491
- clickedObject.userData.isPickupable = false; // Prevent re-picking
492
 
493
  renderCurrentPageUI();
494
  }
@@ -496,14 +505,15 @@
496
  }
497
 
498
  function togglePlacementMode(itemName) {
 
499
  if (currentPlacingItem === itemName) {
500
- currentPlacingItem = null; // Cancel placement
501
  console.log("Placement cancelled.");
502
  } else {
503
  currentPlacingItem = itemName;
504
  console.log(`Ready to place: ${itemName}`);
505
  }
506
- updateInventoryDisplay(); // Update highlighting
507
  updateActionInfo();
508
  }
509
 
@@ -515,7 +525,7 @@
515
  if (!currentGroup) return;
516
 
517
  const grounds = [];
518
- currentGroup.traverse(child => { if(child.userData.isGround) grounds.push(child); });
519
 
520
  const intersects = raycaster.intersectObjects(grounds);
521
 
@@ -526,30 +536,29 @@
526
 
527
  console.log(`Placing ${itemName} at ${point.x.toFixed(1)}, ${point.z.toFixed(1)}`);
528
 
529
- const itemGeo = new THREE.BoxGeometry(0.5, 0.5, 0.5); // Simple representation
530
  const itemMat = MAT.simple.clone();
531
  if(itemDef.type === 'weapon') itemMat.color.setHex(0xaa4444);
532
  else if(itemDef.type === 'consumable') itemMat.color.setHex(0xaa7744);
533
  else itemMat.color.setHex(0x8888aa);
534
 
535
- const placedMesh = createMesh(itemGeo, itemMat, {x: point.x, y: 0.25, z: point.z});
536
- placedMesh.userData = { isPlacedItem: true, itemName: itemName };
537
  currentGroup.add(placedMesh);
538
 
539
  gameState.character.inventory = gameState.character.inventory.filter(i => i !== itemName);
540
  currentMessage = `<p class="message message-item"><em>Placed ${itemName}.</em></p>`;
541
- currentPlacingItem = null; // Exit placement mode
542
  renderCurrentPageUI();
543
 
544
  } else {
545
  console.log("Placement click missed ground.");
546
  currentMessage = `<p class="message message-failure"><em>Cannot place item there.</em></p>`;
547
- currentPlacingItem = null; // Exit placement mode on miss
548
  renderCurrentPageUI();
549
  }
550
  }
551
 
552
-
553
  document.addEventListener('DOMContentLoaded', () => {
554
  console.log("DOM Ready - Initializing Persistent World Adventure.");
555
  try {
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Persistent Procedural World (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; }
 
36
  .message-failure { color: #f88; border-left-color: #a44; }
37
  .message-info { color: #aaa; border-left-color: #666; }
38
  .message-item { color: #8bf; border-left-color: #46a; }
39
+ #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: none; z-index: 10;}
40
 
41
  </style>
42
  </head>
 
68
 
69
  <script type="module">
70
  import * as THREE from 'three';
71
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
72
 
73
  const sceneContainer = document.getElementById('scene-container');
74
  const storyTitleElement = document.getElementById('story-title');
 
78
  const inventoryElement = document.getElementById('inventory-display');
79
  const actionInfoElement = document.getElementById('action-info');
80
 
 
81
  let scene, camera, renderer, clock, controls, raycaster, mouse;
82
+ let worldGroup = null;
83
+ let locationGroups = {};
84
+ let currentMessage = "";
85
+ let currentPlacingItem = null;
86
+ let currentLights = []; // <<<< ENSURE THIS IS DECLARED GLOBALLY
87
 
88
  const MAT = {
89
  stone: new THREE.MeshStandardMaterial({ color: 0x777788, roughness: 0.85, metalness: 0.1 }),
 
127
  controls.enableDamping = true;
128
  controls.dampingFactor = 0.1;
129
  controls.target.set(0, 1, 0);
130
+ controls.maxPolarAngle = Math.PI / 2 - 0.05;
131
 
132
  window.addEventListener('resize', onWindowResize, false);
133
  renderer.domElement.addEventListener('mousemove', onMouseMove, false);
 
146
  }
147
 
148
  function onMouseMove( event ) {
149
+ const rect = renderer.domElement.getBoundingClientRect();
150
+ mouse.x = ( (event.clientX - rect.left) / rect.width ) * 2 - 1;
151
+ mouse.y = - ( (event.clientY - rect.top) / rect.height ) * 2 + 1;
152
  }
153
 
154
  function onMouseClick( event ) {
 
184
  const ground = new THREE.Mesh(geo, material);
185
  ground.rotation.x = -Math.PI / 2; ground.position.y = 0;
186
  ground.receiveShadow = true; ground.castShadow = false;
187
+ ground.userData.isGround = true;
188
  return ground;
189
  }
190
 
191
  function setupLighting(type = 'default') {
192
+ currentLights.forEach(light => {
193
+ if (light) scene.remove(light);
194
+ });
195
  currentLights = [];
196
 
197
  let ambientIntensity = 0.4;
 
234
  }
235
  }
236
 
237
+ function createDefaultZone() {
238
  const group = new THREE.Group();
239
  group.add(createGround(MAT.dirt, 20));
240
  const boxGeo = new THREE.BoxGeometry(1, 1, 1);
241
  const interactBox = createMesh(boxGeo, MAT.stone, {y: 0.5, x: 2, z: 2});
242
  interactBox.userData = { isPickupable: true, itemName: "Mysterious Cube", description: "A plain stone cube."};
243
  group.add(interactBox);
244
+ group.visible = false;
245
  return { group, lighting: 'default', entryText: "You are in a default, featureless area.", cameraPos: {x:0, y:5, z:10}, targetPos: {x:0, y:1, z:0} };
246
  }
247
 
 
290
  return { group, lighting: 'cave', entryText: "It's dark and damp in here.", cameraPos: {x:0, y:4, z:8}, targetPos: {x:0, y:1, z:0} };
291
  }
292
 
 
293
  const locationData = {
294
  'start': { creator: createDefaultZone },
295
  'forest': { creator: createForestZone },
 
299
  const pageGraph = {
300
  'start': {
301
  title: "The Crossroads",
302
+ options: [ { text: "Enter Forest", transitionTo: 'forest' }, { text: "Enter Cave", transitionTo: 'cave'} ]
303
  },
304
  'forest': {
305
  title: "Dark Forest",
306
+ options: [ { text: "Return to Crossroads", transitionTo: 'start' } ]
307
  },
308
  'cave': {
309
  title: "Dim Cave",
 
324
  const defaultChar = {
325
  name: "Player",
326
  stats: { hp: 20, maxHp: 20, xp: 0 },
327
+ inventory: ["Rusty Sword"] // Start with an item for testing placement
328
  };
329
  gameState = {
330
  currentLocationId: null,
 
337
  function transitionToLocation(newLocationId) {
338
  console.log(`Transitioning from ${gameState.currentLocationId} to ${newLocationId}`);
339
  currentMessage = "";
340
+ currentPlacingItem = null;
341
 
342
  if (gameState.currentLocationId && locationGroups[gameState.currentLocationId]) {
343
  locationGroups[gameState.currentLocationId].visible = false;
 
358
  worldGroup.add(newGroup);
359
  newGroup.visible = true;
360
  } else {
361
+ console.error(`Location data or creator missing for ID: ${newLocationId}, attempting fallback to 'start'`);
362
+ if (!locationGroups['start']) {
363
+ locationData['start'].cachedInfo = locationData['start'].creator();
364
+ locationGroups['start'] = locationData['start'].cachedInfo.group;
365
+ worldGroup.add(locationGroups['start']);
366
+ }
367
+ locationInfo = locationData['start'].cachedInfo;
368
+ newGroup = locationGroups['start'];
369
  newGroup.visible = true;
370
  newLocationId = 'start';
371
  currentMessage += `<p class="message message-failure">Error: Couldn't load target location, returned to start.</p>`;
 
394
  choicesElement.innerHTML = `<button class="choice-button" onclick="handleTransition({transitionTo: 'start'})">Return to Start</button>`;
395
  updateStatsDisplay();
396
  updateInventoryDisplay();
397
+ updateActionInfo();
398
  return;
399
  }
400
 
 
426
  console.warn("Choice option has no transitionTo property:", option);
427
  }
428
  }
429
+ window.handleTransition = handleTransition;
430
 
431
  function updateStatsDisplay() {
432
  const { hp, maxHp, xp } = gameState.character.stats;
 
443
  const itemDef = itemsData[item] || { type: 'unknown', description: '???' };
444
  const itemClass = `item-${itemDef.type || 'unknown'}`;
445
  const placingClass = (item === currentPlacingItem) ? ' placing' : '';
446
+ invHtml += `<span class="item-tag ${itemClass}${placingClass}" title="Click to Place: ${itemDef.description}" data-itemname="${item}">${item}</span>`;
447
  });
448
  }
449
  inventoryElement.innerHTML = invHtml;
450
 
451
  inventoryElement.querySelectorAll('.item-tag').forEach(tag => {
452
+ tag.onclick = (event) => {
453
+ event.stopPropagation(); // Prevent click from triggering pickup
454
+ togglePlacementMode(tag.dataset.itemname);
455
+ };
456
  });
457
  }
458
 
 
461
  actionInfoElement.textContent = `Placing: ${currentPlacingItem} (Click ground)`;
462
  actionInfoElement.style.display = 'block';
463
  } else {
464
+ actionInfoElement.textContent = `Mode: Explore (Click items/Use UI)`;
465
+ actionInfoElement.style.display = 'block';
466
  }
467
  }
468
 
 
469
  function pickupItem() {
470
+ if (currentPlacingItem) return; // Don't pick up while placing
471
+
472
  raycaster.setFromCamera(mouse, camera);
473
  const currentGroup = locationGroups[gameState.currentLocationId];
474
  if (!currentGroup) return;
475
 
476
  const pickupables = [];
477
+ currentGroup.traverseVisible(child => { // Only check visible objects
478
  if (child.userData.isPickupable) {
479
  pickupables.push(child);
480
  }
 
496
  currentMessage += `<p class="message message-info"><em>(You already had one)</em></p>`;
497
  }
498
 
499
+ clickedObject.visible = false;
500
+ clickedObject.userData.isPickupable = false;
501
 
502
  renderCurrentPageUI();
503
  }
 
505
  }
506
 
507
  function togglePlacementMode(itemName) {
508
+ if (!itemName) return;
509
  if (currentPlacingItem === itemName) {
510
+ currentPlacingItem = null;
511
  console.log("Placement cancelled.");
512
  } else {
513
  currentPlacingItem = itemName;
514
  console.log(`Ready to place: ${itemName}`);
515
  }
516
+ updateInventoryDisplay();
517
  updateActionInfo();
518
  }
519
 
 
525
  if (!currentGroup) return;
526
 
527
  const grounds = [];
528
+ currentGroup.traverseVisible(child => { if(child.userData.isGround) grounds.push(child); });
529
 
530
  const intersects = raycaster.intersectObjects(grounds);
531
 
 
536
 
537
  console.log(`Placing ${itemName} at ${point.x.toFixed(1)}, ${point.z.toFixed(1)}`);
538
 
539
+ const itemGeo = new THREE.BoxGeometry(0.5, 0.5, 0.5);
540
  const itemMat = MAT.simple.clone();
541
  if(itemDef.type === 'weapon') itemMat.color.setHex(0xaa4444);
542
  else if(itemDef.type === 'consumable') itemMat.color.setHex(0xaa7744);
543
  else itemMat.color.setHex(0x8888aa);
544
 
545
+ const placedMesh = createMesh(itemGeo, itemMat, {x: point.x, y: 0.25, z: point.z}); // Place slightly above ground
546
+ placedMesh.userData = { isPlacedItem: true, itemName: itemName, isPickupable: true, description: `Placed ${itemName}` }; // Make it pickupable again
547
  currentGroup.add(placedMesh);
548
 
549
  gameState.character.inventory = gameState.character.inventory.filter(i => i !== itemName);
550
  currentMessage = `<p class="message message-item"><em>Placed ${itemName}.</em></p>`;
551
+ currentPlacingItem = null;
552
  renderCurrentPageUI();
553
 
554
  } else {
555
  console.log("Placement click missed ground.");
556
  currentMessage = `<p class="message message-failure"><em>Cannot place item there.</em></p>`;
557
+ currentPlacingItem = null;
558
  renderCurrentPageUI();
559
  }
560
  }
561
 
 
562
  document.addEventListener('DOMContentLoaded', () => {
563
  console.log("DOM Ready - Initializing Persistent World Adventure.");
564
  try {