Files

336 lines
12 KiB
JavaScript
Executable File

/**
* 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;