/** * DOOM 3D - Arsenal & Weapon System (CS:GO / Classic High-Detail Right-Handed Viewmodels) * 7 Iconic weapons with detailed right-handed perspective, gloved marine hands, * slide blowbacks, pump actions, spinning gatling barrels, and dynamic muzzle flashes. */ class DoomWeapons { constructor(engine, player, sound) { this.engine = engine; this.player = player; this.sound = sound; // Weapon Canvas Overlay for 1st Person Right-Handed Viewmodels this.canvas = document.createElement('canvas'); this.canvas.id = 'weapon-canvas'; this.ctx = this.canvas.getContext('2d'); document.getElementById('game-container').appendChild(this.canvas); this.resizeCanvas(); window.addEventListener('resize', () => this.resizeCanvas()); // 3D Projectiles in World this.projectiles = []; this.ejectedCasings = []; // Weapon Arsenal Definitions this.weapons = [ { id: 'fist', name: 'COMBAT KNIFE / FISTS', type: 'melee', ammoType: null, ammoCost: 0, damage: 35, fireRate: 0.35, range: 3.0 }, { id: 'pistol', name: 'COMBAT PISTOL', type: 'hitscan', ammoType: 'bullets', ammoCost: 1, damage: 18, fireRate: 0.26, spread: 0.012, pellets: 1 }, { id: 'shotgun', name: 'PUMP-ACTION SHOTGUN', type: 'hitscan', ammoType: 'shells', ammoCost: 1, damage: 16, fireRate: 0.85, spread: 0.055, pellets: 9 }, { id: 'chaingun', name: 'ROTARY CHAINGUN', type: 'hitscan', ammoType: 'bullets', ammoCost: 1, damage: 16, fireRate: 0.09, spread: 0.038, pellets: 1 }, { id: 'rocket', name: 'ROCKET LAUNCHER', type: 'projectile', ammoType: 'rockets', ammoCost: 1, damage: 160, splashRadius: 7.0, fireRate: 0.8, speed: 30.0 }, { id: 'plasma', name: 'PLASMA RIFLE', type: 'projectile', ammoType: 'cells', ammoCost: 1, damage: 38, splashRadius: 1.8, fireRate: 0.11, speed: 42.0 }, { id: 'bfg', name: 'BFG 9000', type: 'bfg', ammoType: 'cells', ammoCost: 40, damage: 650, splashRadius: 15.0, fireRate: 1.6, speed: 24.0 } ]; // Unlocked Weapons (Fists, Pistol, Shotgun initially unlocked) this.unlocked = [true, true, true, false, false, false, false]; this.currentIndex = 1; // Default to Pistol this.nextIndex = 1; // Firing & Animation States this.fireTimer = 0; this.recoilOffset = 0; this.pumpProgress = 0; this.slideOffset = 0; this.gatlingAngle = 0; this.gatlingSpinSpeed = 0; this.muzzleFlashTimer = 0; this.switchProgress = 1.0; this.isSwitching = false; this.knifeSlashTimer = 0; this.bfgCharging = false; this.bfgChargeTimer = 0; // Inertia Sway & Bobbing this.swayX = 0; this.swayY = 0; this.lastMouseX = 0; this.lastMouseY = 0; // Dynamic 3D Muzzle Light this.muzzleLight = new THREE.PointLight(0xffaa22, 0, 16, 2); this.engine.scene.add(this.muzzleLight); this.setupInput(); } resizeCanvas() { const w = window.innerWidth || 800; const h = Math.max(300, (window.innerHeight || 600) - 85); this.canvas.width = w; this.canvas.height = h; } get current() { return this.weapons[this.currentIndex]; } unlockWeapon(index) { if (index >= 0 && index < this.weapons.length) { this.unlocked[index] = true; this.selectWeapon(index); } } selectWeapon(index) { if (index < 0 || index >= this.weapons.length || !this.unlocked[index]) return; if (index === this.currentIndex && !this.isSwitching) return; const w = this.weapons[index]; if (w.ammoType && this.player.ammo[w.ammoType] < w.ammoCost) { return; } this.nextIndex = index; this.isSwitching = true; if (this.sound) this.sound.playItemPickup(); } nextWeapon() { let idx = (this.currentIndex + 1) % this.weapons.length; while (idx !== this.currentIndex) { if (this.unlocked[idx]) { const w = this.weapons[idx]; if (!w.ammoType || this.player.ammo[w.ammoType] >= w.ammoCost) { this.selectWeapon(idx); return; } } idx = (idx + 1) % this.weapons.length; } } prevWeapon() { let idx = (this.currentIndex - 1 + this.weapons.length) % this.weapons.length; while (idx !== this.currentIndex) { if (this.unlocked[idx]) { const w = this.weapons[idx]; if (!w.ammoType || this.player.ammo[w.ammoType] >= w.ammoCost) { this.selectWeapon(idx); return; } } idx = (idx - 1 + this.weapons.length) % this.weapons.length; } } setupInput() { // Mouse Wheel weapon cycle window.addEventListener('wheel', (e) => { if (e.deltaY > 0) this.nextWeapon(); else if (e.deltaY < 0) this.prevWeapon(); }); // Numeric Keys (1-7) window.addEventListener('keydown', (e) => { if (e.code >= 'Digit1' && e.code <= 'Digit7') { const num = parseInt(e.code.replace('Digit', '')) - 1; this.selectWeapon(num); } }); // Mouse move inertia tracking document.addEventListener('mousemove', (e) => { if (this.player && this.player.isPointerLocked) { const mx = e.movementX || 0; const my = e.movementY || 0; this.swayX = THREE.MathUtils.clamp(this.swayX - mx * 0.15, -25, 25); this.swayY = THREE.MathUtils.clamp(this.swayY - my * 0.15, -20, 20); } }); } // ---------------------------------------------------- // FIRING EXECUTION // ---------------------------------------------------- triggerAttack() { if (this.player.isDead || this.isSwitching || this.fireTimer > 0) return; const w = this.current; // Check ammo if (w.ammoType && this.player.ammo[w.ammoType] < w.ammoCost) { if (this.sound) this.sound.playSlimeHurt(); this.prevWeapon(); return; } // Deduct ammo if (w.ammoType) { this.player.ammo[w.ammoType] -= w.ammoCost; } this.fireTimer = w.fireRate; this.recoilOffset = 1.0; this.muzzleFlashTimer = 0.08; // HUD face attack grimace if (window.game && window.game.hud) { window.game.hud.triggerAttackState(); } if (w.id === 'fist') { this.knifeSlashTimer = 1.0; if (this.sound) this.sound.playPistol(); this.performMeleeAttack(w); } else if (w.id === 'pistol') { this.slideOffset = 1.0; if (this.sound) this.sound.playPistol(); this.triggerMuzzleLight(0xffcc00); this.spawnEjectedCasing('bullet'); this.performHitscanAttack(w); } else if (w.id === 'shotgun') { this.pumpProgress = 1.0; if (this.sound) this.sound.playShotgun(); this.triggerMuzzleLight(0xffaa00); this.spawnEjectedCasing('shell'); this.performHitscanAttack(w); } else if (w.id === 'chaingun') { this.gatlingSpinSpeed = 25.0; if (this.sound) this.sound.playChaingun(); this.triggerMuzzleLight(0xffea00); this.spawnEjectedCasing('bullet'); this.performHitscanAttack(w); } else if (w.id === 'rocket') { if (this.sound) this.sound.playRocketFire(); this.triggerMuzzleLight(0xff5500); this.launchRocketProjectile(w); } else if (w.id === 'plasma') { if (this.sound) this.sound.playPlasmaFire(); this.triggerMuzzleLight(0x00d4ff); this.launchPlasmaProjectile(w); } else if (w.id === 'bfg') { this.bfgCharging = true; this.bfgChargeTimer = 0.6; if (this.sound) this.sound.playBFGCharge(); } } triggerMuzzleLight(color) { this.muzzleLight.color.setHex(color); this.muzzleLight.intensity = 4.5; this.muzzleLight.position.copy(this.player.camera.position); } spawnEjectedCasing(type) { this.ejectedCasings.push({ type, x: 0, y: 0, vx: 3 + Math.random() * 4, vy: -4 - Math.random() * 4, rot: 0, vRot: 10 + Math.random() * 20, life: 0.6 }); } // ---------------------------------------------------- // MELEE, HITSCAN & PROJECTILE MECHANICS // ---------------------------------------------------- performMeleeAttack(w) { const cam = this.player.camera; const origin = cam.position.clone(); const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(cam.quaternion).normalize(); const hit = this.raycastAttack(origin, dir, w.range, w.damage); if (hit && this.sound) { this.sound.playSlimeHurt(); } } performHitscanAttack(w) { const cam = this.player.camera; const origin = cam.position.clone(); const baseDir = new THREE.Vector3(0, 0, -1).applyQuaternion(cam.quaternion).normalize(); for (let i = 0; i < w.pellets; i++) { const spreadX = (Math.random() - 0.5) * w.spread; const spreadY = (Math.random() - 0.5) * w.spread; const dir = baseDir.clone().add(new THREE.Vector3(spreadX, spreadY, 0).applyQuaternion(cam.quaternion)).normalize(); this.raycastAttack(origin, dir, 80, w.damage); } } raycastAttack(origin, dir, maxDist, damage) { let nearestHit = null; let nearestDist = maxDist; let hitType = null; let hitTarget = null; // Check enemies if (window.game && window.game.enemies) { const enemies = window.game.enemies.enemies; for (let i = 0; i < enemies.length; i++) { const e = enemies[i]; if (e.isDead) continue; const toEnemy = e.mesh.position.clone().sub(origin); const proj = toEnemy.dot(dir); if (proj > 0 && proj < nearestDist) { const perp = toEnemy.sub(dir.clone().multiplyScalar(proj)); if (perp.length() < e.radius) { nearestDist = proj; nearestHit = origin.clone().addScaledVector(dir, proj); hitType = 'enemy'; hitTarget = e; } } } } // Check explosive barrels if (window.game && window.game.items) { const barrels = window.game.items.barrels; for (let i = 0; i < barrels.length; i++) { const b = barrels[i]; if (b.isExploded) continue; const toBarrel = b.mesh.position.clone().sub(origin); const proj = toBarrel.dot(dir); if (proj > 0 && proj < nearestDist) { const perp = toBarrel.sub(dir.clone().multiplyScalar(proj)); if (perp.length() < 0.6) { nearestDist = proj; nearestHit = origin.clone().addScaledVector(dir, proj); hitType = 'barrel'; hitTarget = b; } } } } // Check level walls if (window.game && window.game.map) { const colliders = window.game.map.getColliders(); const ray = new THREE.Ray(origin, dir); const targetPos = new THREE.Vector3(); for (let i = 0; i < colliders.length; i++) { const box = colliders[i]; if (ray.intersectBox(box, targetPos)) { const dist = origin.distanceTo(targetPos); if (dist < nearestDist) { nearestDist = dist; nearestHit = targetPos.clone(); hitType = 'wall'; hitTarget = null; } } } } if (nearestHit) { if (hitType === 'enemy') { hitTarget.takeDamage(damage, nearestHit); this.engine.createBloodBurst(nearestHit, dir.clone().negate()); } else if (hitType === 'barrel') { hitTarget.takeDamage(damage); this.engine.createSparks(nearestHit, new THREE.Vector3(0, 1, 0)); } else if (hitType === 'wall') { this.engine.createSparks(nearestHit, dir.clone().negate()); } return true; } return false; } launchRocketProjectile(w) { const cam = this.player.camera; const origin = cam.position.clone().addScaledVector(new THREE.Vector3(0.4, -0.3, -0.6).applyQuaternion(cam.quaternion), 1); const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(cam.quaternion).normalize(); const geo = new THREE.CylinderGeometry(0.12, 0.12, 0.7, 8); const mat = new THREE.MeshStandardMaterial({ color: 0x4a5d38, roughness: 0.4 }); const mesh = new THREE.Mesh(geo, mat); mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir); mesh.position.copy(origin); this.engine.scene.add(mesh); const light = new THREE.PointLight(0xff5500, 3.5, 12, 2); light.position.copy(origin); this.engine.scene.add(light); this.projectiles.push({ type: 'rocket', mesh, light, position: origin, velocity: dir.clone().multiplyScalar(w.speed), damage: w.damage, splashRadius: w.splashRadius, life: 5.0 }); } launchPlasmaProjectile(w) { const cam = this.player.camera; const origin = cam.position.clone().addScaledVector(new THREE.Vector3(0.35, -0.25, -0.5).applyQuaternion(cam.quaternion), 1); const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(cam.quaternion).normalize(); const geo = new THREE.SphereGeometry(0.28, 8, 8); const mat = new THREE.MeshBasicMaterial({ color: 0x00d4ff }); const mesh = new THREE.Mesh(geo, mat); mesh.position.copy(origin); this.engine.scene.add(mesh); const light = new THREE.PointLight(0x00d4ff, 3.0, 10, 2); light.position.copy(origin); this.engine.scene.add(light); this.projectiles.push({ type: 'plasma', mesh, light, position: origin, velocity: dir.clone().multiplyScalar(w.speed), damage: w.damage, splashRadius: w.splashRadius, life: 4.0 }); } executeBFGFire() { const w = this.weapons[6]; if (this.sound) this.sound.playBFGFire(); this.triggerMuzzleLight(0x00ff44); const cam = this.player.camera; const origin = cam.position.clone().addScaledVector(new THREE.Vector3(0.3, -0.2, -0.8).applyQuaternion(cam.quaternion), 1); const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(cam.quaternion).normalize(); const geo = new THREE.SphereGeometry(0.75, 16, 16); const mat = new THREE.MeshStandardMaterial({ color: 0x00ff44, emissive: 0x00ff44, emissiveIntensity: 1.0, transparent: true, opacity: 0.9 }); const mesh = new THREE.Mesh(geo, mat); mesh.position.copy(origin); this.engine.scene.add(mesh); const light = new THREE.PointLight(0x00ff44, 6.0, 20, 2); light.position.copy(origin); this.engine.scene.add(light); this.projectiles.push({ type: 'bfg', mesh, light, position: origin, velocity: dir.clone().multiplyScalar(w.speed), damage: w.damage, splashRadius: w.splashRadius, life: 6.0 }); } // ---------------------------------------------------- // PROJECTILES & CASINGS UPDATE LOOP // ---------------------------------------------------- updateProjectiles(delta) { // Projectiles for (let i = this.projectiles.length - 1; i >= 0; i--) { const p = this.projectiles[i]; p.mesh.position.addScaledVector(p.velocity, delta); p.light.position.copy(p.mesh.position); p.life -= delta; if (p.type === 'rocket') { this.engine.createSmokeParticle(p.mesh.position); } let collided = false; const hitPos = p.mesh.position.clone(); // Collision with enemies if (window.game && window.game.enemies) { const enemies = window.game.enemies.enemies; for (let j = 0; j < enemies.length; j++) { const e = enemies[j]; if (e.isDead) continue; if (e.mesh.position.distanceTo(hitPos) < e.radius + 0.5) { collided = true; break; } } } // Collision with walls if (!collided && window.game && window.game.map) { const colliders = window.game.map.getColliders(); for (let j = 0; j < colliders.length; j++) { if (colliders[j].containsPoint(hitPos)) { collided = true; break; } } } if (collided || p.life <= 0) { this.detonateProjectile(p, hitPos); this.destroyProjectile(i); } } // Casings for (let i = this.ejectedCasings.length - 1; i >= 0; i--) { const c = this.ejectedCasings[i]; c.x += c.vx * delta * 60; c.y += c.vy * delta * 60; c.vy += delta * 18; // gravity c.rot += c.vRot * delta; c.life -= delta; if (c.life <= 0) { this.ejectedCasings.splice(i, 1); } } } detonateProjectile(p, pos) { if (p.type === 'rocket') { if (this.sound) this.sound.playRocketExplode(); this.engine.createExplosionFX(pos, 0xff5500); this.applySplashDamage(pos, p.splashRadius, p.damage); } else if (p.type === 'plasma') { this.engine.createExplosionFX(pos, 0x00d4ff); this.applySplashDamage(pos, p.splashRadius, p.damage); } else if (p.type === 'bfg') { if (this.sound) this.sound.playRocketExplode(); this.engine.createExplosionFX(pos, 0x00ff44); this.applySplashDamage(pos, p.splashRadius, p.damage); // BFG secondary cone trace if (window.game && window.game.enemies) { const enemies = window.game.enemies.enemies; const cam = this.player.camera; const pDir = new THREE.Vector3(0, 0, -1).applyQuaternion(cam.quaternion); for (let i = 0; i < enemies.length; i++) { const e = enemies[i]; if (e.isDead) continue; const toE = e.mesh.position.clone().sub(cam.position); const dist = toE.length(); if (dist < 40) { const angle = pDir.angleTo(toE); if (angle < Math.PI / 3) { e.takeDamage(250 + Math.floor(Math.random() * 200), e.mesh.position); this.engine.createExplosionFX(e.mesh.position, 0x00ff44); } } } } } } applySplashDamage(pos, radius, maxDamage) { if (window.game && window.game.enemies) { const enemies = window.game.enemies.enemies; for (let i = 0; i < enemies.length; i++) { const e = enemies[i]; if (e.isDead) continue; const dist = pos.distanceTo(e.mesh.position); if (dist < radius) { const factor = 1.0 - (dist / radius); e.takeDamage(Math.floor(maxDamage * factor), pos); } } } if (window.game && window.game.items) { const barrels = window.game.items.barrels; for (let i = 0; i < barrels.length; i++) { const b = barrels[i]; if (b.isExploded) continue; const dist = pos.distanceTo(b.mesh.position); if (dist < radius) { b.takeDamage(maxDamage); } } } const pDist = pos.distanceTo(this.player.position); if (pDist < radius) { const factor = 1.0 - (pDist / radius); this.player.takeDamage(Math.floor(maxDamage * 0.5 * factor), pos); } } destroyProjectile(index) { const p = this.projectiles[index]; this.engine.scene.remove(p.mesh); this.engine.scene.remove(p.light); this.projectiles.splice(index, 1); } // ---------------------------------------------------- // RIGHT-HANDED HIGH-DETAIL VIEWMODEL RENDERING (CS:GO / DOOM) // ---------------------------------------------------- update(delta) { // Fire Timer if (this.fireTimer > 0) { this.fireTimer -= delta; } // BFG Charge if (this.bfgCharging) { this.bfgChargeTimer -= delta; if (this.bfgChargeTimer <= 0) { this.bfgCharging = false; this.executeBFGFire(); } } // Full Auto Firing if (this.player.keys.shoot && this.fireTimer <= 0 && !this.isSwitching) { if (this.current.id === 'chaingun' || this.current.id === 'plasma') { this.triggerAttack(); } } // Weapon Switch Animation if (this.isSwitching) { this.switchProgress -= delta * 5.5; if (this.switchProgress <= 0) { this.currentIndex = this.nextIndex; this.isSwitching = false; this.switchProgress = 0; } } else if (this.switchProgress < 1.0) { this.switchProgress = Math.min(1.0, this.switchProgress + delta * 5.5); } // Recoil Recovery if (this.recoilOffset > 0) { this.recoilOffset = Math.max(0, this.recoilOffset - delta * 5.0); } if (this.slideOffset > 0) { this.slideOffset = Math.max(0, this.slideOffset - delta * 8.0); } if (this.pumpProgress > 0) { this.pumpProgress = Math.max(0, this.pumpProgress - delta * 2.2); } if (this.knifeSlashTimer > 0) { this.knifeSlashTimer = Math.max(0, this.knifeSlashTimer - delta * 3.5); } // Gatling Spin if (this.gatlingSpinSpeed > 0) { this.gatlingAngle += this.gatlingSpinSpeed * delta; this.gatlingSpinSpeed = Math.max(0, this.gatlingSpinSpeed - delta * 15.0); } // Muzzle Flash Timer if (this.muzzleFlashTimer > 0) { this.muzzleFlashTimer -= delta; if (this.muzzleFlashTimer <= 0) { this.muzzleLight.intensity = 0; } } // Inertia Sway damping this.swayX *= 0.88; this.swayY *= 0.88; this.updateProjectiles(delta); this.renderViewmodel(); } renderViewmodel() { const ctx = this.ctx; const w = this.canvas.width; const h = this.canvas.height; ctx.clearRect(0, 0, w, h); if (this.player.isDead) return; // Base Viewmodel Anchor (Right-Handed CS:GO style) const baseAnchorX = w * 0.68; const baseAnchorY = h; // Bobbing sway from movement const bobX = this.player.bobOffset.x * 120 + this.swayX; const bobY = Math.abs(this.player.bobOffset.y) * 90 + this.swayY; // Recoil kickback const recoilY = -this.recoilOffset * 28; const recoilX = -this.recoilOffset * 10; const recoilRot = -this.recoilOffset * 0.12; // Switch lowering offset const switchY = (1.0 - this.switchProgress) * (h * 0.6); ctx.save(); ctx.translate(baseAnchorX + bobX + recoilX, baseAnchorY + bobY + recoilY + switchY); ctx.rotate(recoilRot); const currentWpn = this.current.id; if (currentWpn === 'fist') { this.drawCombatKnife(ctx); } else if (currentWpn === 'pistol') { this.drawPistolViewmodel(ctx); } else if (currentWpn === 'shotgun') { this.drawShotgunViewmodel(ctx); } else if (currentWpn === 'chaingun') { this.drawChaingunViewmodel(ctx); } else if (currentWpn === 'rocket') { this.drawRocketViewmodel(ctx); } else if (currentWpn === 'plasma') { this.drawPlasmaViewmodel(ctx); } else if (currentWpn === 'bfg') { this.drawBFGViewmodel(ctx); } // Draw Muzzle Flash Burst if (this.muzzleFlashTimer > 0 && currentWpn !== 'fist') { this.drawMuzzleFlashBurst(ctx, currentWpn); } ctx.restore(); // Render Flying Shell Casings this.renderCasings(ctx, baseAnchorX + bobX, baseAnchorY - 140); } // ---------------------------------------------------- // DETAILED GLOVES & SLEEVES (CS:GO CT/Doom Marine Style) // ---------------------------------------------------- drawMarineHand(ctx, x, y, angle = 0, gripFingers = 4) { ctx.save(); ctx.translate(x, y); ctx.rotate(angle); // Tactical Camo / Olive Military Sleeve const sleeveGrad = ctx.createLinearGradient(-40, 60, 80, 200); sleeveGrad.addColorStop(0, '#2b3624'); sleeveGrad.addColorStop(0.5, '#1e261a'); sleeveGrad.addColorStop(1, '#11160e'); ctx.fillStyle = sleeveGrad; ctx.beginPath(); ctx.moveTo(-50, 240); ctx.lineTo(-25, 60); ctx.lineTo(65, 75); ctx.lineTo(85, 240); ctx.closePath(); ctx.fill(); // Sleeve Fabric Folds & Stitching ctx.strokeStyle = '#141811'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(-35, 120); ctx.lineTo(40, 135); ctx.moveTo(-20, 180); ctx.lineTo(55, 195); ctx.stroke(); // Glove Cuff ctx.fillStyle = '#1b1d22'; ctx.fillRect(-28, 52, 95, 18); // Combat Glove Palm & Knuckles const gloveGrad = ctx.createLinearGradient(-20, 0, 60, 60); gloveGrad.addColorStop(0, '#2e323b'); gloveGrad.addColorStop(1, '#15171b'); ctx.fillStyle = gloveGrad; ctx.beginPath(); ctx.roundRect(-24, -20, 85, 80, 8); ctx.fill(); // Carbon Fiber Reinforced Knuckle Pad ctx.fillStyle = '#0f1114'; ctx.beginPath(); ctx.roundRect(-16, -16, 70, 24, 6); ctx.fill(); ctx.strokeStyle = '#444c58'; ctx.lineWidth = 1.5; ctx.stroke(); // Gripping Fingers around Weapon ctx.fillStyle = '#22262e'; for (let i = 0; i < gripFingers; i++) { ctx.beginPath(); ctx.roundRect(-20 + i * 18, -48, 16, 36, 6); ctx.fill(); // Finger knuckle highlight ctx.fillStyle = '#3a404c'; ctx.fillRect(-18 + i * 18, -40, 12, 6); ctx.fillStyle = '#22262e'; } // Thumb wrapping over ctx.fillStyle = '#1c1f26'; ctx.beginPath(); ctx.roundRect(42, -35, 22, 45, 8); ctx.fill(); ctx.restore(); } // ---------------------------------------------------- // WEAPON DRAWING ROUTINES // ---------------------------------------------------- drawCombatKnife(ctx) { ctx.save(); let slashProg = Math.sin(this.knifeSlashTimer * Math.PI); let slashX = -slashProg * 140; let slashY = -slashProg * 80; let slashRot = -slashProg * 0.6; ctx.translate(slashX, slashY); ctx.rotate(slashRot); // Knife Blade (Titanium Black with Razor Silver Edge) ctx.fillStyle = '#181b20'; ctx.beginPath(); ctx.moveTo(-10, -260); ctx.lineTo(25, -230); ctx.lineTo(20, -70); ctx.lineTo(-10, -70); ctx.closePath(); ctx.fill(); // Serrated Spine ctx.strokeStyle = '#485260'; ctx.lineWidth = 3; ctx.stroke(); // Razor Silver Edge ctx.fillStyle = '#dce4ec'; ctx.beginPath(); ctx.moveTo(-10, -260); ctx.lineTo(0, -250); ctx.lineTo(-5, -70); ctx.lineTo(-10, -70); ctx.closePath(); ctx.fill(); // Blood Groove (Fuller) ctx.fillStyle = '#0a0c0e'; ctx.fillRect(4, -200, 5, 110); // Steel Crossguard ctx.fillStyle = '#303640'; ctx.fillRect(-22, -72, 50, 14); // Textured Grip ctx.fillStyle = '#111316'; ctx.fillRect(-14, -58, 34, 90); // Hand holding the knife this.drawMarineHand(ctx, -5, -10, 0.1, 4); ctx.restore(); } drawPistolViewmodel(ctx) { ctx.save(); // Slide Blowback Kick const slideY = this.slideOffset * 22; // Gun Frame & Barrel (Brushed Dark Gunmetal) ctx.fillStyle = '#1c1e22'; ctx.fillRect(-35, -210, 55, 160); // Moving Slide const slideGrad = ctx.createLinearGradient(-35, -220, 20, -220); slideGrad.addColorStop(0, '#3a3f48'); slideGrad.addColorStop(0.5, '#565e6c'); slideGrad.addColorStop(1, '#252930'); ctx.fillStyle = slideGrad; ctx.beginPath(); ctx.roundRect(-36, -225 + slideY, 58, 140, 4); ctx.fill(); // Slide Rear Serrations ctx.fillStyle = '#15171b'; for (let i = 0; i < 6; i++) { ctx.fillRect(-32, -125 + slideY + i * 6, 50, 3); } // Ejection Port ctx.fillStyle = '#0a0b0d'; ctx.fillRect(-8, -170 + slideY, 26, 35); if (this.slideOffset > 0.3) { // Shiny Brass Bullet in chamber ctx.fillStyle = '#e6b800'; ctx.fillRect(-4, -162 + slideY, 16, 20); } // Tactical Rail & Under-Barrel ctx.fillStyle = '#16181c'; ctx.fillRect(-30, -230, 44, 25); // Tritium 3-Dot Sights (Green glowing front & rear dots) ctx.fillStyle = '#111'; ctx.fillRect(-12, -238 + slideY, 10, 12); // Front sight post ctx.fillStyle = '#00ff44'; ctx.fillRect(-10, -236 + slideY, 6, 6); ctx.fillStyle = '#111'; ctx.fillRect(-34, -98 + slideY, 12, 10); // Rear sight left ctx.fillRect(8, -98 + slideY, 12, 10); // Rear sight right ctx.fillStyle = '#00ff44'; ctx.fillRect(-32, -96 + slideY, 6, 6); ctx.fillRect(10, -96 + slideY, 6, 6); // Ergonomic Pistol Grip ctx.fillStyle = '#111316'; ctx.beginPath(); ctx.moveTo(-30, -70); ctx.lineTo(-45, 80); ctx.lineTo(25, 95); ctx.lineTo(15, -70); ctx.closePath(); ctx.fill(); // Grip Checkering & Doom UAC Insignia ctx.fillStyle = '#22252c'; ctx.fillRect(-32, -30, 38, 70); ctx.fillStyle = '#00ff66'; ctx.fillRect(-18, 0, 10, 10); // Marine Hand gripping pistol this.drawMarineHand(ctx, -15, 0, -0.05, 4); ctx.restore(); } drawShotgunViewmodel(ctx) { ctx.save(); // Long Double/Single Ribbed Barrel (Dark Steel) const barrelGrad = ctx.createLinearGradient(-40, -320, 30, -320); barrelGrad.addColorStop(0, '#2a2e36'); barrelGrad.addColorStop(0.5, '#4f5664'); barrelGrad.addColorStop(1, '#1b1d22'); ctx.fillStyle = barrelGrad; ctx.fillRect(-28, -320, 46, 260); // Ventilated Top Heat Shield Rib ctx.fillStyle = '#16181d'; ctx.fillRect(-12, -330, 14, 270); ctx.fillStyle = '#ffaa00'; ctx.beginPath(); ctx.arc(-5, -330, 4, 0, Math.PI * 2); // Brass bead sight ctx.fill(); // Muzzle Bore ctx.fillStyle = '#08090a'; ctx.beginPath(); ctx.ellipse(-5, -320, 16, 12, 0, 0, Math.PI * 2); ctx.fill(); // Pump-Action Forend Slide (Sliding Back and Forth on pumpProgress) const pumpY = Math.sin(this.pumpProgress * Math.PI) * 45; // Wood / Heavy Polymer Pump Grip const pumpGrad = ctx.createLinearGradient(-36, -200 + pumpY, 26, -200 + pumpY); pumpGrad.addColorStop(0, '#663a1e'); pumpGrad.addColorStop(0.5, '#8c522e'); pumpGrad.addColorStop(1, '#4a2712'); ctx.fillStyle = pumpGrad; ctx.beginPath(); ctx.roundRect(-34, -220 + pumpY, 58, 85, 8); ctx.fill(); // Grooves on Pump Grip ctx.fillStyle = '#2d1608'; for (let i = 0; i < 7; i++) { ctx.fillRect(-32, -210 + pumpY + i * 10, 54, 3); } // Heavy Receiver Body ctx.fillStyle = '#1c1f26'; ctx.fillRect(-35, -70, 62, 110); ctx.fillStyle = '#0d0e12'; ctx.fillRect(-10, -50, 32, 25); // Ejection port // Left Supporting Hand on Pump Slide ctx.save(); ctx.translate(-15, -170 + pumpY); ctx.fillStyle = '#1c1f26'; ctx.beginPath(); ctx.roundRect(-30, -15, 60, 35, 6); ctx.fill(); ctx.restore(); // Right Main Hand on Receiver Grip this.drawMarineHand(ctx, -10, 20, -0.08, 4); ctx.restore(); } drawChaingunViewmodel(ctx) { ctx.save(); // Heavy Motorized Housing ctx.fillStyle = '#1a1d22'; ctx.fillRect(-50, -120, 95, 160); // Rotary Gear Housing ctx.fillStyle = '#2b303a'; ctx.beginPath(); ctx.ellipse(-5, -120, 52, 38, 0, 0, Math.PI * 2); ctx.fill(); // 6 Multi-Barrels Bundle with 3D Rotation Animation for (let i = 0; i < 6; i++) { const angle = this.gatlingAngle + (i * Math.PI) / 3; const bx = -5 + Math.cos(angle) * 32; const by = -120 + Math.sin(angle) * 22; const bGrad = ctx.createLinearGradient(bx - 10, by - 200, bx + 10, by - 200); bGrad.addColorStop(0, '#383e4a'); bGrad.addColorStop(0.5, '#687388'); bGrad.addColorStop(1, '#1e2229'); ctx.fillStyle = bGrad; ctx.fillRect(bx - 8, by - 190, 16, 190); // Muzzle bore ctx.fillStyle = '#0a0c0e'; ctx.beginPath(); ctx.ellipse(bx, by - 190, 6, 4, 0, 0, Math.PI * 2); ctx.fill(); } // Heavy Ammo Feed Chute with Golden Brass Belt Links ctx.fillStyle = '#d4af37'; for (let i = 0; i < 5; i++) { ctx.fillRect(-70 - i * 10, -60 + i * 14, 18, 12); ctx.fillStyle = '#8a7018'; ctx.fillRect(-68 - i * 10, -58 + i * 14, 14, 8); ctx.fillStyle = '#d4af37'; } // Digital Ammo Counter on Receiver ctx.fillStyle = '#050a06'; ctx.fillRect(-35, -95, 36, 20); ctx.fillStyle = '#00ff44'; ctx.font = 'bold 13px monospace'; ctx.fillText(this.player.ammo.bullets, -30, -80); // Marine Gloved Hands (Side support grip + rear trigger grip) this.drawMarineHand(ctx, -15, 30, -0.05, 4); ctx.restore(); } drawRocketViewmodel(ctx) { ctx.save(); // Large Shoulder-Mounted Launcher Tube (Olive Drab) const tubeGrad = ctx.createLinearGradient(-60, -300, 40, -300); tubeGrad.addColorStop(0, '#36442e'); tubeGrad.addColorStop(0.5, '#566b49'); tubeGrad.addColorStop(1, '#232c1e'); ctx.fillStyle = tubeGrad; ctx.fillRect(-55, -310, 95, 360); // Heavy Hazard Caution Stripes ctx.fillStyle = '#e6b800'; ctx.fillRect(-55, -270, 95, 24); ctx.fillStyle = '#111'; for (let i = 0; i < 4; i++) { ctx.beginPath(); ctx.moveTo(-50 + i * 24, -270); ctx.lineTo(-38 + i * 24, -246); ctx.lineTo(-46 + i * 24, -246); ctx.lineTo(-58 + i * 24, -270); ctx.fill(); } // Huge Launcher Bore ctx.fillStyle = '#0a0d08'; ctx.beginPath(); ctx.ellipse(-8, -310, 44, 28, 0, 0, Math.PI * 2); ctx.fill(); // Olive Missile Warhead Tip visible inside ctx.fillStyle = '#405236'; ctx.beginPath(); ctx.arc(-8, -305, 24, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#ff2200'; ctx.fillRect(-11, -318, 6, 8); // Detonator fuse tip // Tactical Sights & Grips ctx.fillStyle = '#16181b'; ctx.fillRect(-68, -220, 14, 45); // Side scope ctx.fillStyle = '#ff0033'; ctx.beginPath(); ctx.arc(-61, -198, 5, 0, Math.PI * 2); ctx.fill(); // Marine Hands holding shoulder launcher this.drawMarineHand(ctx, -15, 30, -0.05, 4); ctx.restore(); } drawPlasmaViewmodel(ctx) { ctx.save(); // Futuristic Carbine Chassis (Matte Carbon & Titanium) const frameGrad = ctx.createLinearGradient(-45, -260, 35, -260); frameGrad.addColorStop(0, '#1c222a'); frameGrad.addColorStop(0.5, '#354050'); frameGrad.addColorStop(1, '#14181e'); ctx.fillStyle = frameGrad; ctx.fillRect(-40, -280, 72, 300); // Glowing Blue Plasma Cooling Coils const pulse = 0.7 + Math.sin(performance.now() * 0.008) * 0.3; for (let i = 0; i < 4; i++) { ctx.fillStyle = `rgba(0, 212, 255, ${pulse})`; ctx.beginPath(); ctx.roundRect(-32, -240 + i * 32, 56, 18, 6); ctx.fill(); // Core electric wire ctx.fillStyle = '#ffffff'; ctx.fillRect(-24, -234 + i * 32, 40, 6); } // Dual Plasma Muzzle Emitters ctx.fillStyle = '#0a1016'; ctx.fillRect(-36, -300, 20, 25); ctx.fillRect(8, -300, 20, 25); ctx.fillStyle = '#00d4ff'; ctx.fillRect(-30, -305, 8, 8); ctx.fillRect(14, -305, 8, 8); // Marine Hand this.drawMarineHand(ctx, -10, 10, -0.06, 4); ctx.restore(); } drawBFGViewmodel(ctx) { ctx.save(); // Massive Heavy Weapon Body (Dark Iron & Heavy Armor) const bfgGrad = ctx.createLinearGradient(-80, -280, 60, -280); bfgGrad.addColorStop(0, '#152016'); bfgGrad.addColorStop(0.5, '#2e4230'); bfgGrad.addColorStop(1, '#0e160e'); ctx.fillStyle = bfgGrad; ctx.fillRect(-75, -290, 135, 320); // Giant Radioactive Containment Sphere const chargePulse = this.bfgCharging ? (1.0 - this.bfgChargeTimer / 0.6) : 0.4; const sphereGrad = ctx.createRadialGradient(-8, -160, 10, -8, -160, 55); sphereGrad.addColorStop(0, '#ffffff'); sphereGrad.addColorStop(0.4, `rgba(0, 255, 68, ${0.8 + chargePulse * 0.2})`); sphereGrad.addColorStop(1, 'rgba(0, 60, 15, 0.9)'); ctx.fillStyle = sphereGrad; ctx.beginPath(); ctx.arc(-8, -160, 52, 0, Math.PI * 2); ctx.fill(); // Biohazard Warning Logo ctx.fillStyle = '#00ff44'; ctx.font = 'bold 16px monospace'; ctx.fillText('⚡ BFG 9000 ⚡', -65, -80); // Colossal Muzzle Emitter Hood ctx.fillStyle = '#080d08'; ctx.fillRect(-65, -315, 115, 30); ctx.strokeStyle = '#00ff44'; ctx.lineWidth = 4; ctx.strokeRect(-65, -315, 115, 30); // Marine Hands gripping heavy side handles this.drawMarineHand(ctx, -20, 20, -0.05, 4); ctx.restore(); } drawMuzzleFlashBurst(ctx, weaponId) { const flashX = -8; let flashY = -240; let flashRadius = 90; if (weaponId === 'pistol') { flashY = -250; flashRadius = 75; } else if (weaponId === 'shotgun') { flashY = -345; flashRadius = 110; } else if (weaponId === 'chaingun') { flashY = -215; flashRadius = 85; } else if (weaponId === 'rocket') { flashY = -330; flashRadius = 130; } else if (weaponId === 'plasma') { flashY = -315; flashRadius = 95; } else if (weaponId === 'bfg') { flashY = -330; flashRadius = 160; } const grad = ctx.createRadialGradient(flashX, flashY, 15, flashX, flashY, flashRadius); if (weaponId === 'plasma') { grad.addColorStop(0, '#ffffff'); grad.addColorStop(0.3, '#00d4ff'); grad.addColorStop(0.7, '#0055ff'); grad.addColorStop(1, 'transparent'); } else if (weaponId === 'bfg') { grad.addColorStop(0, '#ffffff'); grad.addColorStop(0.3, '#00ff44'); grad.addColorStop(0.7, '#006611'); grad.addColorStop(1, 'transparent'); } else { grad.addColorStop(0, '#ffffff'); grad.addColorStop(0.3, '#ffea00'); grad.addColorStop(0.7, '#ff4400'); grad.addColorStop(1, 'transparent'); } ctx.fillStyle = grad; ctx.beginPath(); ctx.arc(flashX, flashY, flashRadius, 0, Math.PI * 2); ctx.fill(); // Fiery Muzzle Sparks Spike Stars ctx.fillStyle = '#ffffff'; for (let i = 0; i < 8; i++) { const angle = (i * Math.PI) / 4 + Math.random() * 0.2; const len = flashRadius * (0.8 + Math.random() * 0.5); ctx.beginPath(); ctx.moveTo(flashX, flashY); ctx.lineTo(flashX + Math.cos(angle - 0.1) * 8, flashY + Math.sin(angle - 0.1) * 8); ctx.lineTo(flashX + Math.cos(angle) * len, flashY + Math.sin(angle) * len); ctx.lineTo(flashX + Math.cos(angle + 0.1) * 8, flashY + Math.sin(angle + 0.1) * 8); ctx.closePath(); ctx.fill(); } } renderCasings(ctx, originX, originY) { for (let i = 0; i < this.ejectedCasings.length; i++) { const c = this.ejectedCasings[i]; ctx.save(); ctx.translate(originX + c.x, originY + c.y); ctx.rotate(c.rot); if (c.type === 'shell') { // Red Shotgun Shell with Brass Rim ctx.fillStyle = '#cc1100'; ctx.fillRect(-10, -4, 20, 8); ctx.fillStyle = '#e6b800'; ctx.fillRect(-14, -4, 5, 8); } else { // Golden Brass Bullet Casing ctx.fillStyle = '#d4af37'; ctx.fillRect(-8, -3, 16, 6); ctx.fillStyle = '#8c7018'; ctx.fillRect(-10, -3, 3, 6); } ctx.restore(); } } } window.DoomWeapons = DoomWeapons;