INFERNAL HARVEST

THE DARK REAPING

Initializing game...
0
SOULS
AWAKENING
E THIRD EYE
F COLLECT
R RITUAL
WASD: Move | Mouse: Look | Shift: Run | Space: Jump

TRANSFORMATION COMPLETE

THE CORRUPTION

SACRIFICE COMPLETE

You have harvested 0 souls for the Dark Lord

// Physics and collision detection - optimized for performance function detectCollisions() { // Get player position const playerPos = new THREE.Vector3(); playerGroup.getWorldPosition(playerPos); // Create a ray cast downward from player const raycaster = new THREE.Raycaster(); const rayOrigin = new THREE.Vector3(playerPos.x, playerPos.y + 0.1, playerPos.z); const rayDirection = new THREE.Vector3(0, -1, 0); raycaster.set(rayOrigin, rayDirection); // Cast ray to detect ground (only check ground for performance) const intersects = raycaster.intersectObject(ground); // Check if player is on ground if (intersects.length > 0 && intersects[0].distance < 0.2) { gameState.isGrounded = true; if (gameState.playerVelocity.y < 0) { gameState.playerVelocity.y = 0; } } else { gameState.isGrounded = false; } // Simple boundary detection to keep player within the map const boundaryLimit = groundSize / 2 - 5; if (Math.abs(playerGroup.position.x) > boundaryLimit) { playerGroup.position.x = Math.sign(playerGroup.position.x) * boundaryLimit; } if (Math.abs(playerGroup.position.z) > boundaryLimit) { playerGroup.position.z = Math.sign(playerGroup.position.z) * boundaryLimit; } // Simplified collision detection with key objects // Only check mausoleum positions for performance const mausoleumPositions = [ {x: -40, z: -40}, {x: 40, z: -40}, {x: 40, z: 40}, {x: -40, z: 40}, {x: 0, z: 0} ]; for (const pos of mausoleumPositions) { const dx = playerGroup.position.x - pos.x; const dz = playerGroup.position.z - pos.z; const distance = Math.sqrt(dx * dx + dz * dz); // If too close to mausoleum, push player away if (distance < 10) { const pushDirection = new THREE.Vector3(dx, 0, dz).normalize(); playerGroup.position.x += pushDirection.x * 0.5; playerGroup.position.z += pushDirection.z * 0.5; } } } // Animation functions function animatePlayerWalk(speed) { // Animate legs for walking const time = Date.now() * 0.003; const legAmplitude = 0.2 * speed; if (playerGroup.userData.leftLeg && playerGroup.userData.rightLeg) { playerGroup.userData.leftLeg.rotation.x = Math.sin(time * 5) * legAmplitude; playerGroup.userData.rightLeg.rotation.x = Math.sin(time * 5 + Math.PI) * legAmplitude; playerGroup.userData.leftFoot.rotation.x = Math.sin(time * 5 + Math.PI/2) * legAmplitude; playerGroup.userData.rightFoot.rotation.x = Math.sin(time * 5 + Math.PI*3/2) * legAmplitude; } // Body subtle bounce if (playerGroup.userData.body) { playerGroup.userData.body.position.y = figureHeight * 0.3 + Math.abs(Math.sin(time * 5)) * 0.05 * speed; } } function animateSouls() { const time = Date.now() * 0.001; souls.forEach(soul => { // Float up and down soul.position.y = soul.userData.originalY + Math.sin(time * soul.userData.pulseSpeed) * 0.2; // Rotate soul.rotation.y = time * 0.5; // Update glow light position if (soul.userData.glow) { soul.userData.glow.position.copy(soul.position); } }); } function animateRituals() { const time = Date.now() * 0.001; rituals.forEach(ritual => { // Animate flames if (ritual.userData.flames) { ritual.userData.flames.children.forEach(flame => { flame.position.y = flame.userData.originalY + Math.sin(time * flame.userData.speed) * 0.1; flame.scale.x = flame.userData.intensity * (0.8 + Math.sin(time * flame.userData.speed * 0.5) * 0.2); flame.scale.z = flame.userData.intensity * (0.8 + Math.sin(time * flame.userData.speed * 0.5) * 0.2); }); } // Animate light if (ritual.userData.light) { ritual.userData.light.intensity = ritual.userData.originalLightIntensity * (0.8 + Math.sin(time * 4) * 0.2); } }); } // Update camera based on player position function updateCamera() { // Get player position const playerPos = new THREE.Vector3(); playerGroup.getWorldPosition(playerPos); if (gameState.thirdPersonView) { // Calculate camera position behind player const cameraOffset = new THREE.Vector3(0, gameState.cameraHeight, gameState.cameraDistance); cameraOffset.applyQuaternion(playerGroup.quaternion); // Set camera position camera.position.copy(playerPos).add(cameraOffset); // Calculate look target (ahead of player) const lookTarget = new THREE.Vector3(0, 1, -gameState.cameraDistance); lookTarget.applyQuaternion(playerGroup.quaternion); cameraTarget.copy(playerPos).add(lookTarget); // Look at target camera.lookAt(cameraTarget); } else { // First-person view camera.position.copy(playerPos); camera.position.y += gameState.cameraHeight; // Apply mouse look camera.rotation.x = -mouseY * mouseSensitivity; camera.rotation.y = -mouseX * mouseSensitivity; // Sync player rotation with camera playerGroup.rotation.y = -mouseX * mouseSensitivity; } } function updateTransformationProgress() { let progress = 0; let nextTarget = 0; if (gameState.transformationLevel < gameState.soulsRequired.length) { nextTarget = gameState.soulsRequired[gameState.transformationLevel]; progress = (gameState.soulsCollected / nextTarget) * 100; } else { progress = 100; } // Cap at 100% progress = Math.min(progress, 100); // Update progress bar transformationProgress.style.width = `${progress}%`; // Check for transformation if (gameState.transformationLevel < gameState.soulsRequired.length && gameState.soulsCollected >= gameState.soulsRequired[gameState.transformationLevel]) { performTransformation(); } } function performTransformation() { gameState.transformationLevel++; // Update transformation text const transformationNames = ["AWAKENING", "CORRUPTION", "ASCENSION"]; transformationText.textContent = transformationNames[gameState.transformationLevel]; // Show transformation notification transformationLevel.textContent = transformationNames[gameState.transformationLevel]; transformationNotification.style.opacity = 1; // Dramatic effect - freeze controls briefly const originalCanMove = gameState.playerCanMove; gameState.playerCanMove = false; // Create transformation effect const transformLight = new THREE.PointLight(0xff0000, 5, 20); transformLight.position.copy(playerGroup.position); transformLight.position.y += 1; scene.add(transformLight); // Create particle effect (reduced particle count for performance) const particles = []; const particleCount = 25; // Reduced from 50 const particleGroup = new THREE.Group(); for (let i = 0; i < particleCount; i++) { const particleGeometry = new THREE.SphereGeometry(0.1, 4, 4); // Reduced segments const particleMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.7 }); const particle = new THREE.Mesh(particleGeometry, particleMaterial); particle.position.copy(playerGroup.position); particle.position.y += 1; // Random velocity particle.userData.velocity = new THREE.Vector3( (Math.random() - 0.5) * 0.2, Math.random() * 0.2, (Math.random() - 0.5) * 0.2 ); particleGroup.add(particle); particles.push(particle); } scene.add(particleGroup); // Animate transformation const startTime = Date.now(); const animateDuration = 3000; // 3 seconds function animateTransformation() { const elapsedTime = Date.now() - startTime; const progress = Math.min(elapsedTime / animateDuration, 1); // Update particles particles.forEach(particle => { particle.position.add(particle.userData.velocity); particle.material.opacity = 0.7 * (1 - progress); particle.scale.multiplyScalar(0.98); }); // Pulse light transformLight.intensity = 5 * (1 - progress) * (0.7 + 0.3 * Math.sin(progress * 20)); if (progress < 1) { requestAnimationFrame(animateTransformation); } else { scene.remove(particleGroup); scene.remove(transformLight); gameState.playerCanMove = originalCanMove; // Hide notification after animation completes setTimeout(() => { transformationNotification.style.opacity = 0; }, 1000); } } animateTransformation(); // Update player model based on transformation level if (gameState.transformationLevel >= 1) { playerGroup.userData.horns.visible = true; // Update abilities document.getElementById('third-eye-ability').style.borderColor = '#ff0000'; // Add glowing eyes effect const eyeLight = new THREE.PointLight(0xff0000, 0.5, 1); eyeLight.position.copy(playerGroup.position); eyeLight.position.y += 1.5; playerGroup.add(eyeLight); } if (gameState.transformationLevel >= 2) { playerGroup.userData.wings.visible = true; // Make fire effects more intense moonLight.color.set(0xff0000); moonLight.intensity = 1.2; // Add wing flame effects const leftWingLight = new THREE.PointLight(0xff3300, 0.5, 2); leftWingLight.position.set(-0.6, 1, 0); playerGroup.add(leftWingLight); const rightWingLight = new THREE.PointLight(0xff3300, 0.5, 2); rightWingLight.position.set(0.6, 1, 0); playerGroup.add(rightWingLight); } showMessage(`Transformation complete: ${transformationNames[gameState.transformationLevel]}`); } function showMessage(text) { messageElement.textContent = text; messageElement.style.opacity = 1; // Clear message after 3 seconds setTimeout(() => { messageElement.style.opacity = 0; }, 3000); } function endGame() { gameState.gameOver = true; gameState.playerCanMove = false; finalSouls.textContent = gameState.soulsCollected; gameOverScreen.style.opacity = 1; gameOverScreen.style.pointerEvents = 'all'; // Exit pointer lock document.exitPointerLock(); } function updateMinimap() { // Clear minimap minimapCtx.clearRect(0, 0, minimapCanvas.width, minimapCanvas.height); // Draw background minimapCtx.fillStyle = 'rgba(0, 0, 0, 0.5)'; minimapCtx.fillRect(0, 0, minimapCanvas.width, minimapCanvas.height); // Calculate minimap scale (convert world units to pixels) const mapScale = 0.5; const mapCenterX = minimapCanvas.width / 2; const mapCenterY = minimapCanvas.height / 2; // Get player position const playerPos = new THREE.Vector3(); playerGroup.getWorldPosition(playerPos); // Draw souls on minimap souls.forEach(soul => { // Calculate position on minimap const dx = (soul.position.x - playerPos.x) * mapScale; const dy = (soul.position.z - playerPos.z) * mapScale; // Only draw if within minimap range if (Math.abs(dx) < mapCenterX && Math.abs(dy) < mapCenterY) { // Set color based on soul type const soulColors = ['#6633aa', '#33aaaa', '#aaaa33', '#aa3333']; minimapCtx.fillStyle = soulColors[soul.userData.type]; minimapCtx.beginPath(); minimapCtx.arc(mapCenterX + dx, mapCenterY + dy, 3, 0, Math.PI * 2); minimapCtx.fill(); } }); // Draw rituals on minimap rituals.forEach(ritual => { const dx = (ritual.position.x - playerPos.x) * mapScale; const dy = (ritual.position.z - playerPos.z) * mapScale; // Only draw if within minimap range if (Math.abs(dx) < mapCenterX && Math.abs(dy) < mapCenterY) { minimapCtx.fillStyle = '#cc0000'; minimapCtx.beginPath(); minimapCtx.arc(mapCenterX + dx, mapCenterY + dy, 4, 0, Math.PI * 2); minimapCtx.fill(); } }); // Draw player indicator (last so it's on top) minimapCtx.fillStyle = '#ffffff'; minimapCtx.beginPath(); minimapCtx.arc(mapCenterX, mapCenterY, 5, 0, Math.PI * 2); minimapCtx.fill(); // Draw direction indicator const directionLength = 8; const playerRotationRadians = playerGroup.rotation.y; minimapCtx.strokeStyle = '#ffffff'; minimapCtx.lineWidth = 2; minimapCtx.beginPath(); minimapCtx.moveTo(mapCenterX, mapCenterY); minimapCtx.lineTo( mapCenterX + Math.sin(playerRotationRadians) * directionLength, mapCenterY + Math.cos(playerRotationRadians) * directionLength ); minimapCtx.stroke(); }; // DOM Elements const loadingScreen = document.getElementById('loading-screen'); const soulCount = document.getElementById('soul-count'); const transformationProgress = document.getElementById('transformation-progress'); const transformationText = document.getElementById('transformation-text'); const messageElement = document.getElementById('message'); const transformationNotification = document.getElementById('transformation-notification'); const transformationLevel = document.getElementById('transformation-level'); const gameOverScreen = document.getElementById('game-over'); const finalSouls = document.getElementById('final-souls'); const restartButton = document.getElementById('restart-button'); const thirdEyeAbility = document.getElementById('third-eye-ability'); const cameraToggle = document.getElementById('camera-toggle'); const minimapCanvas = document.getElementById('minimap-canvas'); const minimapCtx = minimapCanvas.getContext('2d'); // Set minimap canvas size minimapCanvas.width = 200; minimapCanvas.height = 200; // Three.js setup showLoadingMessage("Setting up 3D scene..."); const scene = new THREE.Scene(); scene.background = new THREE.Color(0x050505); // Add fog for creepy atmosphere scene.fog = new THREE.FogExp2(0x080808, 0.035); // Camera setup const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 2, 5); // Starting position behind player // Look target for third-person camera const cameraTarget = new THREE.Vector3(0, 1, 0); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.getElementById('game-container').appendChild(renderer.domElement); // Add Stats.js for performance monitoring with error handling let stats; try { stats = new Stats(); stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px'; stats.domElement.style.top = '0px'; const gameContainer = document.getElementById('game-container'); if (gameContainer) { gameContainer.appendChild(stats.domElement); } } catch (error) { console.warn("Could not initialize Stats.js:", error); stats = { update: function(){} }; // Fallback with empty update function } // Lights showLoadingMessage("Creating lighting..."); const ambientLight = new THREE.AmbientLight(0x330000, 0.5); scene.add(ambientLight); // Moon light (main directional light) const moonLight = new THREE.DirectionalLight(0xcc2222, 0.8); moonLight.position.set(50, 100, 50); moonLight.castShadow = true; moonLight.shadow.mapSize.width = 1024; // Reduced from 2048 for performance moonLight.shadow.mapSize.height = 1024; // Reduced from 2048 for performance moonLight.shadow.camera.near = 0.5; moonLight.shadow.camera.far = 500; moonLight.shadow.camera.left = -100; moonLight.shadow.camera.right = 100; moonLight.shadow.camera.top = 100; moonLight.shadow.camera.bottom = -100; scene.add(moonLight); // Cemetery ground showLoadingMessage("Creating environment..."); const groundSize = 200; const groundGeometry = new THREE.PlaneGeometry(groundSize, groundSize, 32, 32); // Create a texture loader const textureLoader = new THREE.TextureLoader(); // Create a procedural ground texture const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x221100, roughness: 0.9, metalness: 0.1, wireframe: false }); const ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true; ground.userData.isGround = true; scene.add(ground); // Create cemetery fence const fenceHeight = 3; const fenceThickness = 0.2; function createFence() { showLoadingMessage("Building cemetery fence..."); const fenceSize = groundSize / 2; const fenceGroup = new THREE.Group(); // Fence material const fenceMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9, metalness: 0.4 }); // Create fence posts and connecting bars const postGeometry = new THREE.BoxGeometry(fenceThickness, fenceHeight, fenceThickness); const barGeometry = new THREE.BoxGeometry(10, fenceThickness, fenceThickness); // Fence sections with gaps for gates const sections = [ // North fence (with gap in middle for gate) {start: [-fenceSize, 0, -fenceSize], end: [-5, 0, -fenceSize]}, {start: [5, 0, -fenceSize], end: [fenceSize, 0, -fenceSize]}, // East fence {start: [fenceSize, 0, -fenceSize], end: [fenceSize, 0, fenceSize]}, // South fence {start: [fenceSize, 0, fenceSize], end: [-fenceSize, 0, fenceSize]}, // West fence {start: [-fenceSize, 0, fenceSize], end: [-fenceSize, 0, -fenceSize]} ]; sections.forEach(section => { const start = new THREE.Vector3(...section.start); const end = new THREE.Vector3(...section.end); const direction = new THREE.Vector3().subVectors(end, start).normalize(); const distance = start.distanceTo(end); const postSpacing = 10; // Calculate number of posts const postCount = Math.floor(distance / postSpacing) + 1; // Add fence posts and connecting bars for (let i = 0; i < postCount; i++) { const t = i / (postCount - 1); const posX = start.x + (end.x - start.x) * t; const posZ = start.z + (end.z - start.z) * t; // Create fence post const post = new THREE.Mesh(postGeometry, fenceMaterial); post.position.set(posX, fenceHeight / 2, posZ); post.castShadow = true; post.receiveShadow = true; fenceGroup.add(post); // Add bars between posts (except for last post) if (i < postCount - 1) { // Top bar const topBar = new THREE.Mesh(barGeometry, fenceMaterial); topBar.position.set( posX + direction.x * postSpacing / 2, fenceHeight - fenceThickness / 2, posZ + direction.z * postSpacing / 2 ); // Rotate bar to align with fence direction if (direction.x === 0) { topBar.rotation.y = Math.PI / 2; } topBar.castShadow = true; topBar.receiveShadow = true; fenceGroup.add(topBar); // Middle bar const middleBar = topBar.clone(); middleBar.position.y = fenceHeight / 2; fenceGroup.add(middleBar); // Add decorative spikes on top of posts const spikeGeometry = new THREE.ConeGeometry(fenceThickness / 2, fenceHeight / 4, 4); const spike = new THREE.Mesh(spikeGeometry, fenceMaterial); spike.position.set(posX, fenceHeight + fenceHeight / 8, posZ); spike.castShadow = true; fenceGroup.add(spike); } } }); // Create cemetery gate createCemeteryGate(fenceGroup); scene.add(fenceGroup); } function createCemeteryGate(fenceGroup) { const gateMaterial = new THREE.MeshStandardMaterial({ color: 0x442222, roughness: 0.8, metalness: 0.5 }); // Gate frame const frameWidth = 10; const frameHeight = fenceHeight * 1.5; // Left post const leftPost = new THREE.Mesh( new THREE.BoxGeometry(0.4, frameHeight, 0.4), gateMaterial ); leftPost.position.set(-frameWidth/2, frameHeight/2, -groundSize/2); leftPost.castShadow = true; fenceGroup.add(leftPost); // Right post const rightPost = leftPost.clone(); rightPost.position.x = frameWidth/2; fenceGroup.add(rightPost); // Top arch const archGeometry = new THREE.TorusGeometry(frameWidth/2, 0.3, 16, 16, Math.PI); // Reduced segments const arch = new THREE.Mesh(archGeometry, gateMaterial); arch.position.set(0, frameHeight, -groundSize/2); arch.rotation.x = Math.PI / 2; arch.castShadow = true; fenceGroup.add(arch); // Gate doors (slightly open) const doorWidth = 4.5; const doorGeometry = new THREE.PlaneGeometry(doorWidth, frameHeight * 0.9, 1, 1); // Reduced segments const leftDoorMaterial = new THREE.MeshStandardMaterial({ color: 0x331111, roughness: 0.8, metalness: 0.6, side: THREE.DoubleSide }); // Left door const leftDoor = new THREE.Mesh(doorGeometry, leftDoorMaterial); leftDoor.position.set(-doorWidth/2 - 0.5, frameHeight * 0.45, -groundSize/2); leftDoor.rotation.y = Math.PI / 6; // Opened slightly leftDoor.castShadow = true; fenceGroup.add(leftDoor); // Right door const rightDoor = leftDoor.clone(); rightDoor.position.x = doorWidth/2 + 0.5; rightDoor.rotation.y = -Math.PI / 6; // Opened slightly in opposite direction fenceGroup.add(rightDoor); // Gate sign const signGeometry = new THREE.BoxGeometry(8, 1, 0.1); const signMaterial = new THREE.MeshStandardMaterial({ color: 0x221111 }); const sign = new THREE.Mesh(signGeometry, signMaterial); sign.position.set(0, frameHeight * 0.8, -groundSize/2 - 0.2); sign.castShadow = true; fenceGroup.add(sign); // Add gate spikes (fewer for performance) for (let i = -4; i <= 4; i += 2) { if (i !== 0) { // Skip center for the arch peak const spike = new THREE.Mesh( new THREE.ConeGeometry(0.15, 0.6, 4), gateMaterial ); spike.position.set(i, frameHeight + 0.3 - Math.abs(i/10), -groundSize/2); spike.castShadow = true; fenceGroup.add(spike); } } } // Create a random cemetery layout function createCemetery() { // Create mausoleums createMausoleums(); // Create tombstones (reduced count for performance) createTombstones(30); // Create statues (reduced count for performance) createStatues(5); // Create trees (reduced count for performance) createDeadTrees(10); // Create ground fog patches (reduced count for performance) createFogPatches(20); // Create fence around cemetery createFence(); } function createMausoleums() { showLoadingMessage("Building mausoleums..."); const mausoleumCount = 5; const mausoleumPositions = [ {x: -40, z: -40}, {x: 40, z: -40}, {x: 40, z: 40}, {x: -40, z: 40}, {x: 0, z: 0} ]; const buildingMaterial = new THREE.MeshStandardMaterial({ color: 0x444444, roughness: 0.9, metalness: 0.2 }); mausoleumPositions.forEach((pos, index) => { // Random size variations const width = 10 + Math.random() * 5; const depth = 8 + Math.random() * 4; const height = 6 + Math.random() * 3; const mausoleumGroup = new THREE.Group(); // Main building const building = new THREE.Mesh( new THREE.BoxGeometry(width, height, depth), buildingMaterial ); building.position.set(0, height/2, 0); building.castShadow = true; building.receiveShadow = true; mausoleumGroup.add(building); // Roof (triangular prism for a peaked roof) const roofHeight = height * 0.4; const roofGeometry = new THREE.CylinderGeometry(width/2, width/2, depth, 4, 1, false); const roofMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9, metalness: 0.3 }); const roof = new THREE.Mesh(roofGeometry, roofMaterial); roof.rotation.z = Math.PI / 2; roof.rotation.y = Math.PI / 4; roof.position.set(0, height + roofHeight/2, 0); roof.castShadow = true; mausoleumGroup.add(roof); // Door const doorWidth = 2.5; const doorHeight = 4; const doorGeometry = new THREE.PlaneGeometry(doorWidth, doorHeight); const doorMaterial = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.9, metalness: 0.5, side: THREE.DoubleSide }); const door = new THREE.Mesh(doorGeometry, doorMaterial); door.position.set(0, doorHeight/2, depth/2 + 0.01); mausoleumGroup.add(door); // Columns const columnRadius = 0.4; const columnHeight = height * 0.9; const columnGeometry = new THREE.CylinderGeometry(columnRadius, columnRadius, columnHeight, 8); const columnMaterial = new THREE.MeshStandardMaterial({ color: 0x555555, roughness: 0.8, metalness: 0.3 }); const column1 = new THREE.Mesh(columnGeometry, columnMaterial); column1.position.set(-width/2 + columnRadius, columnHeight/2, depth/2 + columnRadius); column1.castShadow = true; mausoleumGroup.add(column1); const column2 = column1.clone(); column2.position.x = width/2 - columnRadius; mausoleumGroup.add(column2); // Add decorative elements based on which mausoleum it is if (index === 4) { // Central mausoleum - more elaborate // Add dome instead of peaked roof mausoleumGroup.remove(roof); const domeGeometry = new THREE.SphereGeometry(width/2, 12, 12, 0, Math.PI * 2, 0, Math.PI / 2); const dome = new THREE.Mesh(domeGeometry, roofMaterial); dome.position.set(0, height, 0); dome.castShadow = true; mausoleumGroup.add(dome); // Add a pentagram on the door const pentagramSize = doorWidth * 0.6; const pentagramGeometry = new THREE.CircleGeometry(pentagramSize, 5); const pentagramMaterial = new THREE.MeshBasicMaterial({ color: 0xcc0000, side: THREE.DoubleSide }); const pentagram = new THREE.Mesh(pentagramGeometry, pentagramMaterial); pentagram.position.set(0, doorHeight/2, depth/2 + 0.02); pentagram.rotation.z = Math.PI / 10; mausoleumGroup.add(pentagram); // Add torches on either side of the door const torchGeometry = new THREE.CylinderGeometry(0.1, 0.1, 1, 8); const torchMaterial = new THREE.MeshStandardMaterial({ color: 0x111111 }); const torch1 = new THREE.Mesh(torchGeometry, torchMaterial); torch1.position.set(-doorWidth/2 - 0.5, doorHeight * 0.7, depth/2 + 0.3); mausoleumGroup.add(torch1); const torch2 = torch1.clone(); torch2.position.x = doorWidth/2 + 0.5; mausoleumGroup.add(torch2); // Add flame lights const flame1 = new THREE.PointLight(0xff3300, 1, 10); flame1.position.set(-doorWidth/2 - 0.5, doorHeight * 0.7 + 0.5, depth/2 + 0.3); mausoleumGroup.add(flame1); const flame2 = flame1.clone(); flame2.position.x = doorWidth/2 + 0.5; mausoleumGroup.add(flame2); } // Position the entire mausoleum mausoleumGroup.position.set(pos.x, 0, pos.z); // Random rotation mausoleumGroup.rotation.y = Math.floor(Math.random() * 4) * Math.PI / 2; scene.add(mausoleumGroup); }); } function createTombstones(count) { showLoadingMessage("Creating tombstones..."); const tombstoneTypes = [ // Simple rectangular tombstone function createBasicTombstone(x, z) { const height = 1 + Math.random() * 2; const width = 0.8 + Math.random() * 0.8; const thickness = 0.2 + Math.random() * 0.2; const tombstone = new THREE.Group(); // Randomize stone color const grayValue = 0.2 + Math.random() * 0.3; const tombstoneMaterial = new THREE.MeshStandardMaterial({ color: new THREE.Color(grayValue, grayValue, grayValue), roughness: 0.9, metalness: 0.1 }); // Main stone const stoneGeometry = new THREE.BoxGeometry(width, height, thickness); const stone = new THREE.Mesh(stoneGeometry, tombstoneMaterial); stone.position.y = height / 2; stone.castShadow = true; stone.receiveShadow = true; tombstone.add(stone); // Optional base if (Math.random() > 0.5) { const baseWidth = width * 1.2; const baseHeight = height * 0.15; const baseDepth = thickness * 1.2; const baseGeometry = new THREE.BoxGeometry(baseWidth, baseHeight, baseDepth); const base = new THREE.Mesh(baseGeometry, tombstoneMaterial); base.position.y = baseHeight / 2; base.castShadow = true; base.receiveShadow = true; tombstone.add(base); } tombstone.position.set(x, 0, z); tombstone.rotation.y = Math.random() * Math.PI * 2; scene.add(tombstone); return tombstone; }, // Rounded-top tombstone function createRoundedTombstone(x, z) { const height = 1.5 + Math.random() * 2; const width = 1 + Math.random() * 0.8; const thickness = 0.2 + Math.random() * 0.2; const tombstone = new THREE.Group(); // Randomize stone color const grayValue = 0.2 + Math.random() * 0.3; const tombstoneMaterial = new THREE.MeshStandardMaterial({ color: new THREE.Color(grayValue, grayValue, grayValue), roughness: 0.9, metalness: 0.1 }); // Main stone - rectangle with rounded top const baseHeight = height * 0.7; const roundedTopHeight = height * 0.3; // Rectangle base const baseGeometry = new THREE.BoxGeometry(width, baseHeight, thickness); const base = new THREE.Mesh(baseGeometry, tombstoneMaterial); base.position.y = baseHeight / 2; base.castShadow = true; base.receiveShadow = true; tombstone.add(base); // Rounded top const topGeometry = new THREE.CylinderGeometry(width/2, width/2, thickness, 8, 1, false, 0, Math.PI); const top = new THREE.Mesh(topGeometry, tombstoneMaterial); top.rotation.x = Math.PI / 2; top.position.set(0, baseHeight + thickness/2, 0); top.castShadow = true; top.receiveShadow = true; tombstone.add(top); tombstone.position.set(x, 0, z); tombstone.rotation.y = Math.random() * Math.PI * 2; scene.add(tombstone); return tombstone; }, // Cross tombstone function createCrossTombstone(x, z) { const height = 2 + Math.random() * 2; const width = 1.2 + Math.random() * 0.8; const thickness = 0.2 + Math.random() * 0.2; const tombstone = new THREE.Group(); // Randomize stone color const grayValue = 0.15 + Math.random() * 0.3; const tombstoneMaterial = new THREE.MeshStandardMaterial({ color: new THREE.Color(grayValue, grayValue, grayValue), roughness: 0.9, metalness: 0.1 }); // Vertical part const verticalGeometry = new THREE.BoxGeometry(thickness, height, thickness); const vertical = new THREE.Mesh(verticalGeometry, tombstoneMaterial); vertical.position.y = height / 2; vertical.castShadow = true; vertical.receiveShadow = true; tombstone.add(vertical); // Horizontal part const crossWidth = width; const crossHeight = thickness; const crossY = height * (0.7 + Math.random() * 0.2); const horizontalGeometry = new THREE.BoxGeometry(crossWidth, crossHeight, thickness); const horizontal = new THREE.Mesh(horizontalGeometry, tombstoneMaterial); horizontal.position.y = crossY; horizontal.castShadow = true; horizontal.receiveShadow = true; tombstone.add(horizontal); // Optional base if (Math.random() > 0.3) { const baseWidth = width * 0.6; const baseHeight = height * 0.12; const baseDepth = thickness * 1.5; const baseGeometry = new THREE.BoxGeometry(baseWidth, baseHeight, baseDepth); const base = new THREE.Mesh(baseGeometry, tombstoneMaterial); base.position.y = baseHeight / 2; base.castShadow = true; base.receiveShadow = true; tombstone.add(base); } tombstone.position.set(x, 0, z); tombstone.rotation.y = Math.random() * Math.PI * 2; scene.add(tombstone); return tombstone; } ]; // Create tombstones in a grid pattern with randomization for (let i = 0; i < count; i++) { // Generate position in cemetery let x, z; let validPosition = false; // Avoid placing tombstones too close to mausoleums and other structures while (!validPosition) { x = (Math.random() * 180) - 90; z = (Math.random() * 180) - 90; // Avoid central area (main mausoleum) const distanceFromCenter = Math.sqrt(x*x + z*z); // Avoid areas near the mausoleums const avoidPoints = [ {x: -40, z: -40}, {x: 40, z: -40}, {x: 40, z: 40}, {x: -40, z: 40}, {x: 0, z: 0} ]; let tooClose = false; for (const point of avoidPoints) { const distance = Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(z - point.z, 2)); if (distance < 15) { tooClose = true; break; } } // Also avoid the entrance area const entranceDistance = Math.sqrt(Math.pow(x, 2) + Math.pow(z - (-groundSize/2), 2)); validPosition = !tooClose && entranceDistance > 15; } // Choose a random tombstone type const typeIndex = Math.floor(Math.random() * tombstoneTypes.length); tombstoneTypes[typeIndex](x, z); } } function createStatues(count) { showLoadingMessage("Creating statues..."); // Define statue types const statueTypes = [ // Angel statue function createAngelStatue(x, z) { const height = 3 + Math.random() * 2; const group = new THREE.Group(); // Base const baseHeight = height * 0.2; const baseWidth = height * 0.4; const baseGeometry = new THREE.BoxGeometry(baseWidth, baseHeight, baseWidth); const baseMaterial = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.9, metalness: 0.1 }); const base = new THREE.Mesh(baseGeometry, baseMaterial); base.position.y = baseHeight / 2; base.castShadow = true; base.receiveShadow = true; group.add(base); // Figure (simplified shape) const figureHeight = height * 0.8; const figureMaterial = new THREE.MeshStandardMaterial({ color: 0x666666, roughness: 0.8, metalness: 0.2 }); // Body const bodyGeometry = new THREE.CylinderGeometry(height*0.1, height*0.15, figureHeight*0.5, 8); const body = new THREE.Mesh(bodyGeometry, figureMaterial); body.position.y = baseHeight + figureHeight*0.25; body.castShadow = true; group.add(body); // Head const headGeometry = new THREE.SphereGeometry(height*0.08, 8, 8); const head = new THREE.Mesh(headGeometry, figureMaterial); head.position.y = baseHeight + figureHeight*0.5 + height*0.08; head.castShadow = true; group.add(head); // Wings const wingGeometry = new THREE.BoxGeometry(height*0.5, figureHeight*0.3, height*0.05); const leftWing = new THREE.Mesh(wingGeometry, figureMaterial); leftWing.position.set(-height*0.25, baseHeight + figureHeight*0.35, 0); leftWing.rotation.z = Math.PI/6; leftWing.castShadow = true; group.add(leftWing); const rightWing = new THREE.Mesh(wingGeometry, figureMaterial); rightWing.position.set(height*0.25, baseHeight + figureHeight*0.35, 0); rightWing.rotation.z = -Math.PI/6; rightWing.castShadow = true; group.add(rightWing); group.position.set(x, 0, z); group.rotation.y = Math.random() * Math.PI * 2; scene.add(group); }, // Demonic statue function createDemonicStatue(x, z) { const height = 2.5 + Math.random() * 1.5; const group = new THREE.Group(); // Base with pentagram const baseHeight = height * 0.15; const baseWidth = height * 0.5; const baseGeometry = new THREE.CylinderGeometry(baseWidth/2, baseWidth/2, baseHeight, 5); const baseMaterial = new THREE.MeshStandardMaterial({ color: 0x220000, roughness: 0.9, metalness: 0.2 }); const base = new THREE.Mesh(baseGeometry, baseMaterial); base.position.y = baseHeight / 2; base.rotation.y = Math.PI / 10; // Rotate to align with pentagram points base.castShadow = true; base.receiveShadow = true; group.add(base); // Demonic figure (simplified shape) const figureHeight = height * 0.8; const figureMaterial = new THREE.MeshStandardMaterial({ color: 0x330000, roughness: 0.9, metalness: 0.3 }); // Body const bodyGeometry = new THREE.CylinderGeometry(height*0.1, height*0.15, figureHeight*0.6, 8); const body = new THREE.Mesh(bodyGeometry, figureMaterial); body.position.y = baseHeight + figureHeight*0.3; body.castShadow = true; group.add(body); // Head const headGeometry = new THREE.SphereGeometry(height*0.1, 8, 8); const head = new THREE.Mesh(headGeometry, figureMaterial); head.position.y = baseHeight + figureHeight*0.6 + height*0.05; head.castShadow = true; group.add(head); // Horns const hornGeometry = new THREE.ConeGeometry(height*0.03, height*0.2, 8); const hornMaterial = new THREE.MeshStandardMaterial({ color: 0x220000, roughness: 0.8, metalness: 0.4 }); const leftHorn = new THREE.Mesh(hornGeometry, hornMaterial); leftHorn.position.set(-height*0.08, baseHeight + figureHeight*0.6 + height*0.15, 0); leftHorn.rotation.z = -Math.PI/6; leftHorn.castShadow = true; group.add(leftHorn); const rightHorn = new THREE.Mesh(hornGeometry, hornMaterial); rightHorn.position.set(height*0.08, baseHeight + figureHeight*0.6 + height*0.15, 0); rightHorn.rotation.z = Math.PI/6; rightHorn.castShadow = true; group.add(rightHorn); // Wings const wingGeometry = new THREE.BoxGeometry(height*0.6, height*0.4, height*0.05); const leftWing = new THREE.Mesh(wingGeometry, figureMaterial); leftWing.position.set(-height*0.3, baseHeight + figureHeight*0.4, 0); leftWing.rotation.z = Math.PI/6; leftWing.castShadow = true; group.add(leftWing); const rightWing = new THREE.Mesh(wingGeometry, figureMaterial); rightWing.position.set(height*0.3, baseHeight + figureHeight*0.4, 0); rightWing.rotation.z = -Math.PI/6; rightWing.castShadow = true; group.add(rightWing); // Add red glow for eyes const eyeGeometry = new THREE.SphereGeometry(height*0.02, 8, 8); const eyeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 }); const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial); leftEye.position.set(-height*0.04, baseHeight + figureHeight*0.6 + height*0.08, height*0.08); group.add(leftEye); const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial); rightEye.position.set(height*0.04, baseHeight + figureHeight*0.6 + height*0.08, height*0.08); group.add(rightEye); // Add red point light for glow effect const glowLight = new THREE.PointLight(0xff0000, 0.5, height); glowLight.position.set(0, baseHeight + figureHeight*0.6 + height*0.1, 0); group.add(glowLight); group.position.set(x, 0, z); group.rotation.y = Math.random() * Math.PI * 2; scene.add(group); } ]; // Create statues at random positions for (let i = 0; i < count; i++) { // Generate position in cemetery let x, z; let validPosition = false; // Avoid placing statues too close to mausoleums and other structures while (!validPosition) { x = (Math.random() * 160) - 80; z = (Math.random() * 160) - 80; // Avoid areas near the mausoleums const avoidPoints = [ {x: -40, z: -40}, {x: 40, z: -40}, {x: 40, z: 40}, {x: -40, z: 40}, {x: 0, z: 0} ]; let tooClose = false; for (const point of avoidPoints) { const distance = Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(z - point.z, 2)); if (distance < 15) { tooClose = true; break; } } // Also avoid the entrance area const entranceDistance = Math.sqrt(Math.pow(x, 2) + Math.pow(z - (-groundSize/2), 2)); validPosition = !tooClose && entranceDistance > 15; } // Choose a random statue type const typeIndex = Math.floor(Math.random() * statueTypes.length); statueTypes[typeIndex](x, z); } } function createDeadTrees(count) { showLoadingMessage("Creating dead trees..."); for (let i = 0; i < count; i++) { // Generate position let x, z; let validPosition = false; while (!validPosition) { x = (Math.random() * 180) - 90; z = (Math.random() * 180) - 90; // Avoid central area const distanceFromCenter = Math.sqrt(x*x + z*z); // Avoid areas near the mausoleums const avoidPoints = [ {x: -40, z: -40}, {x: 40, z: -40}, {x: 40, z: 40}, {x: -40, z: 40}, {x: 0, z: 0} ]; let tooClose = false; for (const point of avoidPoints) { const distance = Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(z - point.z, 2)); if (distance < 10) { tooClose = true; break; } } // Also avoid the entrance area const entranceDistance = Math.sqrt(Math.pow(x, 2) + Math.pow(z - (-groundSize/2), 2)); validPosition = !tooClose && entranceDistance > 10; } // Create dead tree createDeadTree(x, z); } } function createDeadTree(x, z) { const treeHeight = 5 + Math.random() * 7; const treeGroup = new THREE.Group(); // Trunk const trunkGeometry = new THREE.CylinderGeometry( treeHeight * 0.06, treeHeight * 0.1, treeHeight, 8 ); const trunkMaterial = new THREE.MeshStandardMaterial({ color: 0x221100, roughness: 0.9, metalness: 0.1 }); const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial); trunk.position.y = treeHeight / 2; trunk.castShadow = true; treeGroup.add(trunk); // Add branches (reduced count for performance) const branchCount = 2 + Math.floor(Math.random() * 3); for (let i = 0; i < branchCount; i++) { const branchHeight = treeHeight * (0.4 + Math.random() * 0.5); const branchThickness = treeHeight * (0.02 + Math.random() * 0.04); const branchGeometry = new THREE.CylinderGeometry( branchThickness * 0.5, branchThickness, branchHeight, 6 ); const branch = new THREE.Mesh(branchGeometry, trunkMaterial); // Position branch along trunk const heightOnTrunk = treeHeight * (0.5 + Math.random() * 0.4); const angle = Math.random() * Math.PI * 2; const branchAngle = Math.PI / 4 + Math.random() * Math.PI / 4; branch.position.set( 0, heightOnTrunk, 0 ); // Rotate branch outward from trunk branch.rotation.z = branchAngle; branch.rotation.y = angle; // Move branch origin to end of cylinder to connect with trunk branch.translateY(branchHeight / 2); branch.castShadow = true; treeGroup.add(branch); // Add some smaller branches to each main branch (only one for performance) const smallBranchHeight = branchHeight * (0.4 + Math.random() * 0.4); const smallBranchThickness = branchThickness * (0.4 + Math.random() * 0.3); const smallBranchGeometry = new THREE.CylinderGeometry( smallBranchThickness * 0.5, smallBranchThickness, smallBranchHeight, 5 ); const smallBranch = new THREE.Mesh(smallBranchGeometry, trunkMaterial); // Position small branch at end of main branch smallBranch.position.copy(branch.position); smallBranch.rotation.copy(branch.rotation); // Move to end of parent branch smallBranch.translateY(branchHeight * 0.8); // Adjust angle smallBranch.rotateZ(Math.PI / 6 - Math.random() * Math.PI / 3); smallBranch.rotateY(Math.PI / 4 - Math.random() * Math.PI / 2); // Move origin to connect with parent branch smallBranch.translateY(smallBranchHeight / 2); smallBranch.castShadow = true; treeGroup.add(smallBranch); } // Position the tree treeGroup.position.set(x, 0, z); // Random rotation treeGroup.rotation.y = Math.random() * Math.PI * 2; // Add slight random tilt for more natural look treeGroup.rotation.x = (Math.random() - 0.5) * 0.2; treeGroup.rotation.z = (Math.random() - 0.5) * 0.2; scene.add(treeGroup); } function createFogPatches(count) { showLoadingMessage("Adding fog effects..."); // Create fog patches (visible when Third Eye is active) for (let i = 0; i < count; i++) { const fogGeometry = new THREE.PlaneGeometry(5 + Math.random() * 10, 5 + Math.random() * 10); const fogMaterial = new THREE.MeshBasicMaterial({ color: 0x6633aa, transparent: true, opacity: 0.05, depthWrite: false }); const fogPatch = new THREE.Mesh(fogGeometry, fogMaterial); fogPatch.rotation.x = -Math.PI / 2; // Lay flat on ground fogPatch.position.set( (Math.random() - 0.5) * 180, 0.1, // Just above ground (Math.random() - 0.5) * 180 ); scene.add(fogPatch); } } // Player (demonic chicken) showLoadingMessage("Creating player character..."); const playerGroup = new THREE.Group(); const figureHeight = 1.0; function createDemonicChicken() { // Basic chicken body const bodyGeometry = new THREE.ConeGeometry(0.4, figureHeight * 0.6, 8); const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0x330000, roughness: 0.9, metalness: 0.2 }); const chickenBody = new THREE.Mesh(bodyGeometry, bodyMaterial); chickenBody.position.y = figureHeight * 0.3; chickenBody.rotation.x = Math.PI / 10; // Tilt forward slightly chickenBody.castShadow = true; playerGroup.add(chickenBody); // Chicken head const headGeometry = new THREE.SphereGeometry(0.3, 8, 8); const headMaterial = new THREE.MeshStandardMaterial({ color: 0x220000, roughness: 0.8, metalness: 0.2 }); const chickenHead = new THREE.Mesh(headGeometry, headMaterial); chickenHead.position.set(0, figureHeight * 0.7, 0.2); chickenHead.castShadow = true; playerGroup.add(chickenHead); // Beak const beakGeometry = new THREE.ConeGeometry(0.1, 0.3, 4); const beakMaterial = new THREE.MeshStandardMaterial({ color: 0x770000 }); const beak = new THREE.Mesh(beakGeometry, beakMaterial); beak.position.set(0, figureHeight * 0.7, 0.4); beak.rotation.x = -Math.PI / 2; beak.castShadow = true; playerGroup.add(beak); // Chicken eyes (glowing red) const eyeGeometry = new THREE.SphereGeometry(0.05, 8, 8); const eyeMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 }); const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial); leftEye.position.set(-0.12, figureHeight * 0.75, 0.4); playerGroup.add(leftEye); const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial); rightEye.position.set(0.12, figureHeight * 0.75, 0.4); playerGroup.add(rightEye); // Chicken legs const legGeometry = new THREE.CylinderGeometry(0.05, 0.05, figureHeight * 0.5, 8); const legMaterial = new THREE.MeshStandardMaterial({ color: 0x440000 }); const leftLeg = new THREE.Mesh(legGeometry, legMaterial); leftLeg.position.set(-0.2, figureHeight * 0.25, 0); leftLeg.castShadow = true; playerGroup.add(leftLeg); const rightLeg = new THREE.Mesh(legGeometry, legMaterial); rightLeg.position.set(0.2, figureHeight * 0.25, 0); rightLeg.castShadow = true; playerGroup.add(rightLeg); // Chicken feet const footGeometry = new THREE.BoxGeometry(0.15, 0.05, 0.25); const footMaterial = new THREE.MeshStandardMaterial({ color: 0x550000 }); const leftFoot = new THREE.Mesh(footGeometry, footMaterial); leftFoot.position.set(-0.2, 0, 0.1); leftFoot.castShadow = true; playerGroup.add(leftFoot); const rightFoot = new THREE.Mesh(footGeometry, footMaterial); rightFoot.position.set(0.2, 0, 0.1); rightFoot.castShadow = true; playerGroup.add(rightFoot); // Transformation elements (to be added as player progresses) // Horns (for first transformation) const horns = new THREE.Group(); const hornGeometry = new THREE.ConeGeometry(0.05, 0.3, 8); const hornMaterial = new THREE.MeshStandardMaterial({ color: 0x880000 }); const leftHorn = new THREE.Mesh(hornGeometry, hornMaterial); leftHorn.position.set(-0.15, figureHeight * 0.8, 0.1); leftHorn.rotation.x = -Math.PI / 6; leftHorn.rotation.z = -Math.PI / 8; leftHorn.castShadow = true; horns.add(leftHorn); const rightHorn = new THREE.Mesh(hornGeometry, hornMaterial); rightHorn.position.set(0.15, figureHeight * 0.8, 0.1); rightHorn.rotation.x = -Math.PI / 6; rightHorn.rotation.z = Math.PI / 8; rightHorn.castShadow = true; horns.add(rightHorn); horns.visible = false; playerGroup.add(horns); // Wings (for second transformation) const wings = new THREE.Group(); const wingGeometry = new THREE.BoxGeometry(0.05, 0.7, 0.5); const wingMaterial = new THREE.MeshStandardMaterial({ color: 0xcc2222, transparent: true, opacity: 0.9 }); const leftWing = new THREE.Mesh(wingGeometry, wingMaterial); leftWing.position.set(-0.4, figureHeight * 0.5, 0); leftWing.rotation.z = Math.PI / 4; leftWing.castShadow = true; wings.add(leftWing); const rightWing = new THREE.Mesh(wingGeometry, wingMaterial); rightWing.position.set(0.4, figureHeight * 0.5, 0); rightWing.rotation.z = -Math.PI / 4; rightWing.castShadow = true; wings.add(rightWing); wings.visible = false; playerGroup.add(wings); // Add the player to the scene scene.add(playerGroup); // Save references to transformations playerGroup.userData = { horns: horns, wings: wings, leftLeg: leftLeg, rightLeg: rightLeg, leftFoot: leftFoot, rightFoot: rightFoot, body: chickenBody, head: chickenHead }; } // Create the player character createDemonicChicken(); // Create souls const souls = []; function createSoul(x, z, type = 0) { const soulGeometry = new THREE.SphereGeometry(0.3, 8, 8); // Different colors for different soul types const soulColors = [ 0x6633aa, // Lost Soul (purple) 0x33aaaa, // Bound Soul (teal) 0xaaaa33, // Righteous Soul (yellow) 0xaa3333 // Ancient Soul (red) ]; const soulMaterial = new THREE.MeshBasicMaterial({ color: soulColors[type], transparent: true, opacity: 0.7 }); const soul = new THREE.Mesh(soulGeometry, soulMaterial); soul.position.set(x, 0.5 + Math.random() * 0.5, z); soul.userData = { type: type, // 0=Lost, 1=Bound, 2=Righteous, 3=Ancient value: type + 1, requiresRitual: type > 0, minimumTransformation: type > 1 ? type - 1 : 0, pulseSpeed: 1 + Math.random() * 0.5, originalY: soul.position.y, glow: null }; // Add point light for glow effect const glowColors = [ 0x6633aa, // Purple 0x33aaaa, // Teal 0xaaaa33, // Yellow 0xaa3333 // Red ]; const glow = new THREE.PointLight(glowColors[type], 0.5, 2); glow.position.copy(soul.position); scene.add(glow); soul.userData.glow = glow; scene.add(soul); souls.push(soul); return soul; } // Create a batch of random souls function createSouls(count) { showLoadingMessage("Creating souls..."); for (let i = 0; i < count; i++) { const x = (Math.random() - 0.5) * 180; const z = (Math.random() - 0.5) * 180; // Determine soul type with probabilities let type = 0; const rand = Math.random(); if (rand > 0.9) type = 3; // 10% Ancient else if (rand > 0.7) type = 2; // 20% Righteous else if (rand > 0.4) type = 1; // 30% Bound createSoul(x, z, type); } } // Create ritual sites const rituals = []; function createRitual(x, z) { const ritualGroup = new THREE.Group(); ritualGroup.position.set(x, 0.01, z); // Pentagram on ground const pentagramGeometry = new THREE.CircleGeometry(1, 5); const pentagramMaterial = new THREE.MeshBasicMaterial({ color: 0xcc0000, transparent: true, opacity: 0.7, side: THREE.DoubleSide }); const pentagram = new THREE.Mesh(pentagramGeometry, pentagramMaterial); pentagram.rotation.x = -Math.PI / 2; pentagram.rotation.z = Math.PI / 10; ritualGroup.add(pentagram); // Add circle around pentagram const circleGeometry = new THREE.RingGeometry(1, 1.1, 32); const circleMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.8, side: THREE.DoubleSide }); const circle = new THREE.Mesh(circleGeometry, circleMaterial); circle.rotation.x = -Math.PI / 2; ritualGroup.add(circle); // Add small fire effect in center const fireLight = new THREE.PointLight(0xff3300, 1, 5); fireLight.position.set(0, 0.1, 0); ritualGroup.add(fireLight); // Add flame particles (fake with small meshes) const flameGroup = new THREE.Group(); const flameCount = 3; // Reduced for performance const flameMaterial = new THREE.MeshBasicMaterial({ color: 0xff3300, transparent: true, opacity: 0.7 }); for (let i = 0; i < flameCount; i++) { const flameSize = 0.1 + Math.random() * 0.2; const flameGeometry = new THREE.ConeGeometry(flameSize, flameSize * 2, 4); const flame = new THREE.Mesh(flameGeometry, flameMaterial); const angle = Math.random() * Math.PI * 2; const distance = Math.random() * 0.3; flame.position.set( Math.cos(angle) * distance, flameSize + Math.random() * 0.2, Math.sin(angle) * distance ); flame.rotation.x = Math.PI; flame.userData = { originalY: flame.position.y, speed: 2 + Math.random() * 3, intensity: 0.5 + Math.random() * 0.5 }; flameGroup.add(flame); } ritualGroup.add(flameGroup); ritualGroup.userData = { flames: flameGroup, light: fireLight, originalLightIntensity: 1 }; scene.add(ritualGroup); rituals.push(ritualGroup); return ritualGroup; } // Player movement controls let moveForward = false; let moveBackward = false; let moveLeft = false; let moveRight = false; let jump = false; let run = false; // Mouse look controls let mouseX = 0; let mouseY = 0; let mouseLookEnabled = false; let prevMouseX = 0; let prevMouseY = 0; let mouseSensitivity = 0.002; let playerRotation = 0; // Lock pointer when clicking on game renderer.domElement.addEventListener('click', function() { renderer.domElement.requestPointerLock(); mouseLookEnabled = true; }); // Mouse movement document.addEventListener('mousemove', function(event) { if (document.pointerLockElement === renderer.domElement && gameState.playerCanMove && mouseLookEnabled) { mouseX += event.movementX || event.mozMovementX || 0; mouseY += event.movementY || event.mozMovementY || 0; // Limit vertical look mouseY = Math.max(-600, Math.min(600, mouseY)); } }); // Key controls document.addEventListener('keydown', function(event) { if (!gameState.playerCanMove) return; switch (event.code) { case 'KeyW': moveForward = true; break; case 'KeyS': moveBackward = true; break; case 'KeyA': moveLeft = true; break; case 'KeyD': moveRight = true; break; case 'KeyE': toggleThirdEye(); break; case 'KeyF': collectSoul(); break; case 'KeyR': createPlayerRitual(); break; case 'Space': if (gameState.isGrounded) { jump = true; } break; case 'ShiftLeft': case 'ShiftRight': run = true; break; } }); document.addEventListener('keyup', function(event) { switch (event.code) { case 'KeyW': moveForward = false; break; case 'KeyS': moveBackward = false; break; case 'KeyA': moveLeft = false; break; case 'KeyD': moveRight = false; break; case 'ShiftLeft': case 'ShiftRight': run = false; break; } }); // Toggle camera view cameraToggle.addEventListener('click', function() { gameState.thirdPersonView = !gameState.thirdPersonView; if (!gameState.thirdPersonView) { // First-person view gameState.cameraHeight = 1.7; gameState.cameraDistance = 0; // Hide player model in first person playerGroup.visible = false; } else { // Third-person view gameState.cameraHeight = 2; gameState.cameraDistance = 5; // Show player model in third person playerGroup.visible = true; } }); // Function to toggle Third Eye vision function toggleThirdEye() { gameState.thirdEyeActive = !gameState.thirdEyeActive; // Visual indication thirdEyeAbility.classList.toggle('active', gameState.thirdEyeActive); // Change scene lighting for third eye effect if (gameState.thirdEyeActive) { ambientLight.color.set(0x3311aa); ambientLight.intensity = 0.7; scene.fog.density = 0.02; // Make souls more visible souls.forEach(soul => { soul.material.opacity = 1.0; if (soul.material.emissive) { soul.material.emissive = new THREE.Color(0x6633aa); soul.material.emissiveIntensity = 0.5; } // Intensify glow if (soul.userData.glow) { soul.userData.glow.intensity = 1.5; soul.userData.glow.distance = 5; } }); showMessage("Third Eye activated - souls revealed"); } else { ambientLight.color.set(0x330000); ambientLight.intensity = 0.5; scene.fog.density = 0.035; // Reset soul visibility souls.forEach(soul => { soul.material.opacity = 0.7; if (soul.material.emissive) { soul.material.emissive = new THREE.Color(0x000000); soul.material.emissiveIntensity = 0; } // Reduce glow if (soul.userData.glow) { soul.userData.glow.intensity = 0.5; soul.userData.glow.distance = 2; } }); showMessage("Third Eye deactivated"); } } // Player ritual creation let playerRitual = null; function createPlayerRitual() { // Remove existing ritual if any if (playerRitual) { scene.remove(playerRitual); rituals.splice(rituals.indexOf(playerRitual), 1); } // Create new ritual at player's position const playerPos = new THREE.Vector3(); playerGroup.getWorldPosition(playerPos); // Place ritual on ground, in front of player const direction = new THREE.Vector3(0, 0, -1); direction.applyQuaternion(playerGroup.quaternion); direction.y = 0; direction.normalize(); playerPos.x += direction.x * 2; playerPos.z += direction.z * 2; playerPos.y = 0.01; playerRitual = createRitual(playerPos.x, playerPos.z); gameState.ritualActive = true; showMessage("Ritual circle created"); } // Soul collection function collectSoul() { if (!gameState.thirdEyeActive) { showMessage("Activate Third Eye to see souls (E key)"); return; } // Check for nearby souls const playerPos = new THREE.Vector3(); playerGroup.getWorldPosition(playerPos); let nearestSoul = null; let nearestDistance = 3; // Collection range souls.forEach(soul => { const distance = playerPos.distanceTo(soul.position); if (distance < nearestDistance) { // Check if soul requires ritual if (soul.userData.requiresRitual && !gameState.ritualActive) { showMessage("This soul requires a ritual circle (R key)"); return; } // Check for minimum transformation level if (soul.userData.minimumTransformation > gameState.transformationLevel) { showMessage(`Requires transformation level ${soul.userData.minimumTransformation + 1}`); return; } nearestSoul = soul; nearestDistance = distance; } }); if (nearestSoul) { // Collection effect - create pulse of light const collectEffect = new THREE.PointLight(0xff0000, 3, 10); collectEffect.position.copy(nearestSoul.position); scene.add(collectEffect); // Animate the effect with shrinking sphere const effectGeometry = new THREE.SphereGeometry(0.5, 8, 8); const effectMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.7 }); const effectMesh = new THREE.Mesh(effectGeometry, effectMaterial); effectMesh.position.copy(nearestSoul.position); scene.add(effectMesh); // Create animation for collection effect const startTime = Date.now(); const animateDuration = 1000; // 1 second function animateCollect() { const elapsedTime = Date.now() - startTime; const progress = Math.min(elapsedTime / animateDuration, 1); // Scale up and fade out effectMesh.scale.set(1 + progress * 3, 1 + progress * 3, 1 + progress * 3); effectMesh.material.opacity = 0.7 * (1 - progress); collectEffect.intensity = 3 * (1 - progress); if (progress < 1) { requestAnimationFrame(animateCollect); } else { scene.remove(effectMesh); scene.remove(collectEffect); } } animateCollect(); // Collect the soul showMessage(`Soul collected: +${nearestSoul.userData.value}`); // Add soul value to collected count gameState.soulsCollected += nearestSoul.userData.value; soulCount.textContent = gameState.soulsCollected; // Update transformation progress updateTransformationProgress(); // Remove soul and its glow from scene if (nearestSoul.userData.glow) { scene.remove(nearestSoul.userData.glow); } scene.remove(nearestSoul); souls.splice(souls.indexOf(nearestSoul), 1); // If a ritual was used, remove it if (nearestSoul.userData.requiresRitual && gameState.ritualActive) { scene.remove(playerRitual); rituals.splice(rituals.indexOf(playerRitual), 1); playerRitual = null; gameState.ritualActive = false; } // Check for game completion - collect 100 souls if (gameState.soulsCollected >= 100) { endGame(); } // Add more souls periodically if (souls.length < 10) { createSouls(5); // Reduced count for better performance } } else { showMessage("No souls within range"); } }