336 lines
12 KiB
JavaScript
Executable File
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;
|