/** * DOOM 3D - Pickups, Keycards & Explosive Barrels System * 3D rotating items, bobbing animations, and chain reaction explosive toxic barrels. */ class DoomItems { constructor(engine, player, sound, map) { this.engine = engine; this.player = player; this.sound = sound; this.map = map; this.items = []; this.barrels = []; this.itemTextures = {}; this.generateItemTextures(); this.spawnMapItems(); this.spawnMapBarrels(); } generateItemTextures() { this.itemTextures.medikit = this.createMedikitTexture(); this.itemTextures.armorGreen = this.createArmorTexture('#22aa22'); this.itemTextures.armorBlue = this.createArmorTexture('#0088ff'); this.itemTextures.ammoClip = this.createAmmoTexture('CLIP'); this.itemTextures.ammoShells = this.createAmmoTexture('SHELLS'); this.itemTextures.ammoRocket = this.createAmmoTexture('ROCKETS'); this.itemTextures.ammoCell = this.createAmmoTexture('CELLS'); } createMedikitTexture() { const c = document.createElement('canvas'); c.width = 64; c.height = 64; const ctx = c.getContext('2d'); ctx.fillStyle = '#eeeeee'; ctx.fillRect(0, 0, 64, 64); ctx.fillStyle = '#cc0000'; // Red cross ctx.fillRect(24, 10, 16, 44); ctx.fillRect(10, 24, 44, 16); const tex = new THREE.CanvasTexture(c); return tex; } createArmorTexture(color) { const c = document.createElement('canvas'); c.width = 64; c.height = 64; const ctx = c.getContext('2d'); ctx.fillStyle = color; ctx.fillRect(8, 8, 48, 48); ctx.fillStyle = '#ffffff'; ctx.fillRect(18, 18, 28, 28); const tex = new THREE.CanvasTexture(c); return tex; } createAmmoTexture(label) { const c = document.createElement('canvas'); c.width = 64; c.height = 64; const ctx = c.getContext('2d'); ctx.fillStyle = '#3a3a30'; ctx.fillRect(0, 0, 64, 64); ctx.fillStyle = '#ddaa00'; ctx.font = 'bold 12px monospace'; ctx.fillText(label, 6, 36); const tex = new THREE.CanvasTexture(c); return tex; } // ---------------------------------------------------- // SPAWNING ITEMS & KEYCARDS // ---------------------------------------------------- spawnMapItems() { const spawns = (this.map && this.map.spawns) ? this.map.spawns.items : (window.game && window.game.map ? window.game.map.spawns.items : []); if (!spawns || spawns.length === 0) return; for (let i = 0; i < spawns.length; i++) { const s = spawns[i]; this.spawnItem(s.type, s.x, s.y, s.z); } } spawnItem(type, x, y, z) { let mesh = null; let light = null; let scale = 1.0; if (type.startsWith('key_')) { // 3D Keycard with colored glowing point light const colorName = type.replace('key_', ''); let colorHex = 0x0088ff; if (colorName === 'red') colorHex = 0xff1122; if (colorName === 'yellow') colorHex = 0xffbb00; const geo = new THREE.BoxGeometry(0.8, 1.2, 0.15); const mat = new THREE.MeshStandardMaterial({ color: colorHex, emissive: colorHex, emissiveIntensity: 0.8 }); mesh = new THREE.Mesh(geo, mat); light = new THREE.PointLight(colorHex, 2.5, 6, 2); light.position.set(x, y + 0.6, z); this.engine.scene.add(light); } else if (type === 'soulsphere') { // Blue glowing soulsphere const geo = new THREE.SphereGeometry(0.6, 16, 16); const mat = new THREE.MeshStandardMaterial({ color: 0x00d4ff, emissive: 0x00d4ff, emissiveIntensity: 0.9, transparent: true, opacity: 0.85 }); mesh = new THREE.Mesh(geo, mat); light = new THREE.PointLight(0x00d4ff, 3.5, 10, 2); light.position.set(x, y + 0.6, z); this.engine.scene.add(light); } else if (type === 'medikit') { const geo = new THREE.BoxGeometry(0.8, 0.6, 0.8); const mat = new THREE.MeshStandardMaterial({ map: this.itemTextures.medikit }); mesh = new THREE.Mesh(geo, mat); } else if (type.startsWith('armor_')) { const isBlue = type.includes('blue'); const geo = new THREE.BoxGeometry(0.9, 0.9, 0.9); const mat = new THREE.MeshStandardMaterial({ map: isBlue ? this.itemTextures.armorBlue : this.itemTextures.armorGreen, emissive: isBlue ? 0x0044ff : 0x008800, emissiveIntensity: 0.3 }); mesh = new THREE.Mesh(geo, mat); } else { // Ammo / Weapon box const geo = new THREE.BoxGeometry(0.7, 0.5, 0.7); const mat = new THREE.MeshStandardMaterial({ map: this.itemTextures.ammoClip }); mesh = new THREE.Mesh(geo, mat); } mesh.position.set(x, y + 0.4, z); mesh.castShadow = true; this.engine.scene.add(mesh); const itemObj = { type, mesh, light, baseY: y + 0.4, rotationSpeed: 1.5, bobTimer: Math.random() * 5, isCollected: false }; this.items.push(itemObj); return itemObj; } // ---------------------------------------------------- // EXPLOSIVE TOXIC BARRELS // ---------------------------------------------------- spawnMapBarrels() { const spawns = (this.map && this.map.spawns) ? this.map.spawns.barrels : (window.game && window.game.map ? window.game.map.spawns.barrels : []); if (!spawns || spawns.length === 0) return; for (let i = 0; i < spawns.length; i++) { const s = spawns[i]; this.spawnBarrel(s.x, s.y, s.z); } } spawnBarrel(x, y, z) { const geo = new THREE.CylinderGeometry(0.55, 0.55, 1.4, 16); const mat = this.engine.materials.barrel; const mesh = new THREE.Mesh(geo, mat); mesh.position.set(x, y + 0.7, z); mesh.castShadow = true; mesh.receiveShadow = true; this.engine.scene.add(mesh); // Add to map colliders const min = new THREE.Vector3(x - 0.55, y, z - 0.55); const max = new THREE.Vector3(x + 0.55, y + 1.4, z + 0.55); const box = new THREE.Box3(min, max); this.engine.scene.colliders = this.engine.scene.colliders || []; const barrelObj = { mesh, box, hp: 20, isExploded: false, takeDamage: (dmg) => this.damageBarrel(barrelObj, dmg) }; this.barrels.push(barrelObj); return barrelObj; } damageBarrel(b, amount) { if (b.isExploded) return; b.hp -= amount; if (b.hp <= 0) { this.explodeBarrel(b); } } explodeBarrel(b) { b.isExploded = true; const pos = b.mesh.position; this.sound.playExplosion(1.5); this.engine.spawnExplosion(pos, 5.5, 0x88ff00); // Toxic radioactive green blast! // Remove mesh this.engine.scene.remove(b.mesh); // Area Damage & Chain Reactions if (window.game && window.game.weapons) { window.game.weapons.applySplashDamage(pos, 180, 5.5); } } // ---------------------------------------------------- // ITEM COLLECTION & UPDATE // ---------------------------------------------------- update(delta) { const playerPos = this.player.position; for (let i = this.items.length - 1; i >= 0; i--) { const item = this.items[i]; if (item.isCollected) continue; // Rotation & Bobbing Animation item.bobTimer += delta * 3.0; item.mesh.rotation.y += item.rotationSpeed * delta; item.mesh.position.y = item.baseY + Math.sin(item.bobTimer) * 0.15; if (item.light) { item.light.position.copy(item.mesh.position); } // Player proximity pickup check const dist = item.mesh.position.distanceTo(playerPos); if (dist < 1.6) { this.collectItem(item, i); } } } collectItem(item, index) { let collected = false; const type = item.type; if (type === 'medikit') { collected = this.player.heal(25); if (collected) this.sound.playItemPickup('health'); } else if (type === 'soulsphere') { collected = this.player.heal(100, true); if (collected) { this.sound.playItemPickup('weapon'); if (window.game && window.game.hud) window.game.hud.showNotification('SOULSPHERE! +100 HP'); } } else if (type === 'armor_green') { collected = this.player.addArmor(100); if (collected) this.sound.playItemPickup('armor'); } else if (type === 'armor_blue') { collected = this.player.addArmor(200, true); if (collected) this.sound.playItemPickup('armor'); } else if (type === 'ammo_clip') { collected = this.player.addAmmo('bullets', 10); if (collected) this.sound.playItemPickup('ammo'); } else if (type === 'ammo_box') { collected = this.player.addAmmo('bullets', 50); if (collected) this.sound.playItemPickup('ammo'); } else if (type === 'ammo_shells') { collected = this.player.addAmmo('shells', 8); if (collected) this.sound.playItemPickup('ammo'); } else if (type === 'ammo_rocket') { collected = this.player.addAmmo('rockets', 5); if (collected) this.sound.playItemPickup('ammo'); } else if (type === 'ammo_cell') { collected = this.player.addAmmo('cells', 40); if (collected) this.sound.playItemPickup('ammo'); } else if (type === 'shotgun') { this.player.addAmmo('shells', 8); if (window.game && window.game.weapons) window.game.weapons.unlockWeapon(2); collected = true; this.sound.playItemPickup('weapon'); if (window.game && window.game.hud) window.game.hud.showNotification('YOU GOT THE SHOTGUN!'); } else if (type === 'chaingun') { this.player.addAmmo('bullets', 40); if (window.game && window.game.weapons) window.game.weapons.unlockWeapon(3); collected = true; this.sound.playItemPickup('weapon'); if (window.game && window.game.hud) window.game.hud.showNotification('YOU GOT THE CHAINGUN!'); } else if (type === 'rocket_launcher') { this.player.addAmmo('rockets', 5); if (window.game && window.game.weapons) window.game.weapons.unlockWeapon(4); collected = true; this.sound.playItemPickup('weapon'); if (window.game && window.game.hud) window.game.hud.showNotification('YOU GOT THE ROCKET LAUNCHER!'); } else if (type === 'plasma') { this.player.addAmmo('cells', 50); if (window.game && window.game.weapons) window.game.weapons.unlockWeapon(5); collected = true; this.sound.playItemPickup('weapon'); if (window.game && window.game.hud) window.game.hud.showNotification('YOU GOT THE PLASMA RIFLE!'); } else if (type === 'bfg') { this.player.addAmmo('cells', 100); if (window.game && window.game.weapons) window.game.weapons.unlockWeapon(6); collected = true; this.sound.playItemPickup('weapon'); if (window.game && window.game.hud) window.game.hud.showNotification('YOU GOT THE BFG 9000! OH YES.'); } else if (type.startsWith('key_')) { const keyColor = type.replace('key_', ''); this.player.keysInventory[keyColor] = true; collected = true; this.sound.playItemPickup('key'); if (window.game && window.game.hud) window.game.hud.showNotification(`PICKED UP THE ${keyColor.toUpperCase()} KEYCARD`); } if (collected) { item.isCollected = true; this.engine.scene.remove(item.mesh); if (item.light) this.engine.scene.remove(item.light); this.items.splice(index, 1); } } } window.DoomItems = DoomItems;