tactical-legend-1-0 / index.html
jurgenpaul82's picture
generate a full action video game similar as black opps : https://github.com/jurgen-paul/tactical-legends.git - Initial Deployment
16a4683 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tactical Legends | Modern Warfare</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Montserrat:wght@400;600&display=swap');
body {
font-family: 'Montserrat', sans-serif;
background-color: #0a0a0a;
color: #e0e0e0;
overflow: hidden;
margin: 0;
padding: 0;
}
#gameCanvas {
display: block;
background-color: #111;
margin: 0 auto;
border: 2px solid #333;
}
.digital-font {
font-family: 'Orbitron', sans-serif;
}
.weapon-selector {
background: rgba(20, 20, 20, 0.7);
border: 1px solid #444;
border-radius: 4px;
}
.health-bar {
background: linear-gradient(to right, #e74c3c, #f39c12);
height: 100%;
transition: width 0.3s;
}
.ammo-count {
text-shadow: 0 0 5px #fff, 0 0 10px #fff;
}
.enemy {
position: absolute;
background-color: #c0392b;
border-radius: 50%;
transform: translate(-50%, -50%);
}
.bullet {
position: absolute;
background-color: #f1c40f;
border-radius: 50%;
transform: translate(-50%, -50%);
}
.explosion {
position: absolute;
background: radial-gradient(circle, #f39c12, #e74c3c, transparent 70%);
border-radius: 50%;
transform: translate(-50%, -50%);
opacity: 0;
animation: explode 0.5s forwards;
}
@keyframes explode {
0% { transform: translate(-50%, -50%) scale(0.1); opacity: 1; }
100% { transform: translate(-50%, -50%) scale(2); opacity: 0; }
}
.blood-splatter {
position: absolute;
background-color: rgba(231, 76, 60, 0.7);
border-radius: 50%;
transform: translate(-50%, -50%);
opacity: 0;
animation: fadeOut 1s forwards;
}
@keyframes fadeOut {
0% { opacity: 1; transform: translate(-50%, -50%) scale(0.5); }
100% { opacity: 0; transform: translate(-50%, -50%) scale(1.5); }
}
.mission-briefing {
background: rgba(10, 10, 10, 0.9);
border: 2px solid #444;
box-shadow: 0 0 20px rgba(0, 150, 255, 0.5);
}
.objective-item.completed {
color: #2ecc71;
text-decoration: line-through;
}
.hit-marker {
position: absolute;
width: 20px;
height: 20px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23ffffff"><path d="M12 2L4 12l8 10 8-10z"/></svg>');
background-size: contain;
opacity: 0;
transform: translate(-50%, -50%);
}
.hit-animation {
animation: hitMarker 0.3s forwards;
}
@keyframes hitMarker {
0% { transform: translate(-50%, -50%) scale(0.5); opacity: 0.8; }
100% { transform: translate(-50%, -50%) scale(1.5); opacity: 0; }
}
.night-vision {
filter: hue-rotate(90deg) contrast(2) brightness(0.7);
box-shadow: 0 0 100px rgba(0, 255, 0, 0.3);
}
.night-vision-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(ellipse at center, rgba(0,255,0,0.1) 0%, rgba(0,255,0,0.03) 50%, rgba(0,0,0,0.7) 100%);
pointer-events: none;
z-index: 100;
display: none;
}
.scope-overlay {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 200px;
height: 200px;
border: 2px solid rgba(255, 255, 255, 0.5);
border-radius: 50%;
pointer-events: none;
z-index: 101;
display: none;
}
.scope-reticle {
position: absolute;
top: 50%;
left: 50%;
width: 10px;
height: 10px;
background-color: rgba(255, 0, 0, 0.8);
border-radius: 50%;
transform: translate(-50%, -50%);
}
.scope-hairs {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.scope-hair {
position: absolute;
background-color: rgba(255, 255, 255, 0.5);
}
.scope-hair.horizontal {
width: 100%;
height: 1px;
top: 50%;
}
.scope-hair.vertical {
width: 1px;
height: 100%;
left: 50%;
}
</style>
</head>
<body class="bg-black text-white">
<!-- Night Vision Overlay -->
<div id="nightVisionOverlay" class="night-vision-overlay"></div>
<!-- Scope Overlay -->
<div id="scopeOverlay" class="scope-overlay">
<div class="scope-hairs">
<div class="scope-hair horizontal"></div>
<div class="scope-hair vertical"></div>
</div>
<div class="scope-reticle"></div>
</div>
<!-- Main Menu -->
<div id="mainMenu" class="fixed inset-0 flex flex-col items-center justify-center bg-black bg-opacity-90 z-50">
<h1 class="digital-font text-6xl md:text-8xl text-white mb-8 text-center">
<span class="text-blue-400">TACTICAL</span>
<span class="text-red-500">LEGENDS</span>
</h1>
<div class="flex flex-col space-y-4 w-full max-w-md px-4">
<button id="startGameBtn" class="bg-gray-800 hover:bg-gray-700 text-white digital-font py-4 px-6 rounded-lg border border-gray-600 text-xl transition-all duration-300 hover:scale-105">
OPERATION: IRON FIST
</button>
<button id="weaponsBtn" class="bg-gray-800 hover:bg-gray-700 text-white digital-font py-4 px-6 rounded-lg border border-gray-600 text-xl transition-all duration-300 hover:scale-105">
ARMORY
</button>
<button id="settingsBtn" class="bg-gray-800 hover:bg-gray-700 text-white digital-font py-4 px-6 rounded-lg border border-gray-600 text-xl transition-all duration-300 hover:scale-105">
TACTICAL SETTINGS
</button>
<button id="creditsBtn" class="bg-gray-800 hover:bg-gray-700 text-white digital-font py-4 px-6 rounded-lg border border-gray-600 text-xl transition-all duration-300 hover:scale-105">
CREDITS
</button>
</div>
<div class="mt-12 text-gray-400 text-sm">
<p>Use WASD to move | Mouse to aim | Left Click to fire | Space to jump</p>
<p class="mt-2">Press ESC to pause | F to toggle night vision | R to reload</p>
</div>
</div>
<!-- Mission Briefing -->
<div id="missionBriefing" class="fixed inset-0 bg-black bg-opacity-90 z-50 hidden flex items-center justify-center">
<div class="mission-briefing p-8 max-w-3xl w-full mx-4 rounded-lg">
<h2 class="digital-font text-3xl text-blue-400 mb-6">OPERATION: IRON FIST</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-gray-900 p-4 rounded-lg">
<h3 class="text-yellow-400 digital-font mb-2">LOCATION</h3>
<p>Blacksite Facility, Eastern Europe</p>
</div>
<div class="bg-gray-900 p-4 rounded-lg">
<h3 class="text-yellow-400 digital-font mb-2">TIME</h3>
<p>0300 Hours</p>
</div>
<div class="bg-gray-900 p-4 rounded-lg">
<h3 class="text-yellow-400 digital-font mb-2">WEATHER</h3>
<p>Heavy Rain, Low Visibility</p>
</div>
</div>
<h3 class="text-xl text-white mb-4">SITUATION</h3>
<p class="text-gray-300 mb-6">
Intelligence reports indicate hostile forces have taken control of a blacksite facility
containing experimental weapons technology. Your mission is to infiltrate the compound,
neutralize all hostiles, and secure the prototype device before it falls into enemy hands.
</p>
<h3 class="text-xl text-white mb-4">OBJECTIVES</h3>
<ul id="missionObjectives" class="space-y-2 mb-8">
<li class="objective-item flex items-start">
<span class="text-yellow-400 mr-2"></span>
<span>Infiltrate the blacksite compound</span>
</li>
<li class="objective-item flex items-start">
<span class="text-yellow-400 mr-2"></span>
<span>Neutralize all enemy combatants</span>
</li>
<li class="objective-item flex items-start">
<span class="text-yellow-400 mr-2"></span>
<span>Secure the prototype weapon</span>
</li>
<li class="objective-item flex items-start">
<span class="text-yellow-400 mr-2"></span>
<span>Exfiltrate to the LZ</span>
</li>
</ul>
<div class="flex justify-between mt-8">
<button id="backToMenuBtn" class="bg-gray-800 hover:bg-gray-700 text-white digital-font py-3 px-6 rounded-lg border border-gray-600 transition-all duration-300">
BACK
</button>
<button id="startMissionBtn" class="bg-blue-600 hover:bg-blue-500 text-white digital-font py-3 px-8 rounded-lg border border-blue-400 transition-all duration-300 hover:scale-105">
DEPLOY
</button>
</div>
</div>
</div>
<!-- Armory Screen -->
<div id="armoryScreen" class="fixed inset-0 bg-black bg-opacity-90 z-50 hidden flex items-center justify-center">
<div class="w-full max-w-4xl p-6 bg-gray-900 rounded-lg">
<h2 class="digital-font text-3xl text-blue-400 mb-8">ARMORY</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Primary Weapons -->
<div class="weapon-selector p-4">
<h3 class="text-yellow-400 digital-font mb-4">PRIMARY</h3>
<div class="space-y-3">
<div class="weapon-option flex items-center p-2 hover:bg-gray-800 rounded cursor-pointer border border-transparent hover:border-gray-600" data-weapon="m4a1">
<div class="w-16 h-12 bg-gray-700 mr-3 flex items-center justify-center">M4</div>
<div>
<h4 class="text-white">M4A1</h4>
<p class="text-gray-400 text-sm">Assault Rifle</p>
</div>
</div>
<div class="weapon-option flex items-center p-2 hover:bg-gray-800 rounded cursor-pointer border border-transparent hover:border-gray-600" data-weapon="ak47">
<div class="w-16 h-12 bg-gray-700 mr-3 flex items-center justify-center">AK</div>
<div>
<h4 class="text-white">AK-47</h4>
<p class="text-gray-400 text-sm">Assault Rifle</p>
</div>
</div>
<div class="weapon-option flex items-center p-2 hover:bg-gray-800 rounded cursor-pointer border border-transparent hover:border-gray-600" data-weapon="scar">
<div class="w-16 h-12 bg-gray-700 mr-3 flex items-center justify-center">SCAR</div>
<div>
<h4 class="text-white">SCAR-H</h4>
<p class="text-gray-400 text-sm">Battle Rifle</p>
</div>
</div>
</div>
</div>
<!-- Secondary Weapons -->
<div class="weapon-selector p-4">
<h3 class="text-yellow-400 digital-font mb-4">SECONDARY</h3>
<div class="space-y-3">
<div class="weapon-option flex items-center p-2 hover:bg-gray-800 rounded cursor-pointer border border-transparent hover:border-gray-600" data-weapon="glock">
<div class="w-16 h-12 bg-gray-700 mr-3 flex items-center justify-center">G17</div>
<div>
<h4 class="text-white">Glock 17</h4>
<p class="text-gray-400 text-sm">Pistol</p>
</div>
</div>
<div class="weapon-option flex items-center p-2 hover:bg-gray-800 rounded cursor-pointer border border-transparent hover:border-gray-600" data-weapon="desertEagle">
<div class="w-16 h-12 bg-gray-700 mr-3 flex items-center justify-center">DE</div>
<div>
<h4 class="text-white">Desert Eagle</h4>
<p class="text-gray-400 text-sm">Pistol</p>
</div>
</div>
<div class="weapon-option flex items-center p-2 hover:bg-gray-800 rounded cursor-pointer border border-transparent hover:border-gray-600" data-weapon="mp5">
<div class="w-16 h-12 bg-gray-700 mr-3 flex items-center justify-center">MP5</div>
<div>
<h4 class="text-white">MP5</h4>
<p class="text-gray-400 text-sm">SMG</p>
</div>
</div>
</div>
</div>
<!-- Equipment -->
<div class="weapon-selector p-4">
<h3 class="text-yellow-400 digital-font mb-4">EQUIPMENT</h3>
<div class="space-y-3">
<div class="weapon-option flex items-center p-2 hover:bg-gray-800 rounded cursor-pointer border border-transparent hover:border-gray-600" data-weapon="frag">
<div class="w-16 h-12 bg-gray-700 mr-3 flex items-center justify-center">FRAG</div>
<div>
<h4 class="text-white">Frag Grenade</h4>
<p class="text-gray-400 text-sm">Lethal</p>
</div>
</div>
<div class="weapon-option flex items-center p-2 hover:bg-gray-800 rounded cursor-pointer border border-transparent hover:border-gray-600" data-weapon="flash">
<div class="w-16 h-12 bg-gray-700 mr-3 flex items-center justify-center">FLASH</div>
<div>
<h4 class="text-white">Flashbang</h4>
<p class="text-gray-400 text-sm">Tactical</p>
</div>
</div>
<div class="weapon-option flex items-center p-2 hover:bg-gray-800 rounded cursor-pointer border border-transparent hover:border-gray-600" data-weapon="knife">
<div class="w-16 h-12 bg-gray-700 mr-3 flex items-center justify-center">KNIFE</div>
<div>
<h4 class="text-white">Combat Knife</h4>
<p class="text-gray-400 text-sm">Melee</p>
</div>
</div>
</div>
</div>
</div>
<div class="mt-8 flex justify-end">
<button id="backFromArmoryBtn" class="bg-gray-800 hover:bg-gray-700 text-white digital-font py-3 px-6 rounded-lg border border-gray-600 transition-all duration-300">
BACK TO MENU
</button>
</div>
</div>
</div>
<!-- Pause Menu -->
<div id="pauseMenu" class="fixed inset-0 bg-black bg-opacity-80 z-50 hidden flex flex-col items-center justify-center">
<h1 class="digital-font text-5xl text-red-500 mb-8">PAUSED</h1>
<div class="flex flex-col space-y-4 w-full max-w-md px-4">
<button id="resumeGameBtn" class="bg-blue-600 hover:bg-blue-500 text-white digital-font py-4 px-6 rounded-lg border border-blue-400 text-xl transition-all duration-300 hover:scale-105">
RESUME MISSION
</button>
<button id="restartGameBtn" class="bg-gray-800 hover:bg-gray-700 text-white digital-font py-4 px-6 rounded-lg border border-gray-600 text-xl transition-all duration-300 hover:scale-105">
RESTART MISSION
</button>
<button id="quitToMenuBtn" class="bg-gray-800 hover:bg-gray-700 text-white digital-font py-4 px-6 rounded-lg border border-gray-600 text-xl transition-all duration-300 hover:scale-105">
QUIT TO MENU
</button>
</div>
</div>
<!-- Game Over Screen -->
<div id="gameOverScreen" class="fixed inset-0 bg-black bg-opacity-90 z-50 hidden flex flex-col items-center justify-center">
<h1 class="digital-font text-6xl text-red-500 mb-4">MISSION FAILED</h1>
<p class="text-xl text-gray-300 mb-8">Operative down. Mission compromised.</p>
<div class="stats bg-gray-900 p-6 rounded-lg mb-8 w-full max-w-md">
<div class="grid grid-cols-2 gap-4">
<div>
<p class="text-gray-400">ENEMIES ELIMINATED</p>
<p class="text-2xl text-white" id="killsStat">0</p>
</div>
<div>
<p class="text-gray-400">ACCURACY</p>
<p class="text-2xl text-white" id="accuracyStat">0%</p>
</div>
<div>
<p class="text-gray-400">OBJECTIVES</p>
<p class="text-2xl text-white" id="objectivesStat">0/4</p>
</div>
<div>
<p class="text-gray-400">TIME</p>
<p class="text-2xl text-white" id="timeStat">0:00</p>
</div>
</div>
</div>
<div class="flex space-x-4">
<button id="restartAfterGameOverBtn" class="bg-blue-600 hover:bg-blue-500 text-white digital-font py-3 px-8 rounded-lg border border-blue-400 transition-all duration-300 hover:scale-105">
RETRY
</button>
<button id="quitAfterGameOverBtn" class="bg-gray-800 hover:bg-gray-700 text-white digital-font py-3 px-8 rounded-lg border border-gray-600 transition-all duration-300 hover:scale-105">
MAIN MENU
</button>
</div>
</div>
<!-- Mission Complete Screen -->
<div id="missionCompleteScreen" class="fixed inset-0 bg-black bg-opacity-90 z-50 hidden flex flex-col items-center justify-center">
<h1 class="digital-font text-6xl text-green-500 mb-4">MISSION ACCOMPLISHED</h1>
<p class="text-xl text-gray-300 mb-8">Objective secured. Good work, operative.</p>
<div class="stats bg-gray-900 p-6 rounded-lg mb-8 w-full max-w-md">
<div class="grid grid-cols-2 gap-4">
<div>
<p class="text-gray-400">ENEMIES ELIMINATED</p>
<p class="text-2xl text-white" id="killsStatComplete">0</p>
</div>
<div>
<p class="text-gray-400">ACCURACY</p>
<p class="text-2xl text-white" id="accuracyStatComplete">0%</p>
</div>
<div>
<p class="text-gray-400">OBJECTIVES</p>
<p class="text-2xl text-white" id="objectivesStatComplete">4/4</p>
</div>
<div>
<p class="text-gray-400">TIME</p>
<p class="text-2xl text-white" id="timeStatComplete">0:00</p>
</div>
</div>
</div>
<div class="flex space-x-4">
<button id="nextMissionBtn" class="bg-blue-600 hover:bg-blue-500 text-white digital-font py-3 px-8 rounded-lg border border-blue-400 transition-all duration-300 hover:scale-105">
NEXT MISSION
</button>
<button id="quitAfterCompleteBtn" class="bg-gray-800 hover:bg-gray-700 text-white digital-font py-3 px-8 rounded-lg border border-gray-600 transition-all duration-300 hover:scale-105">
MAIN MENU
</button>
</div>
</div>
<!-- Game UI -->
<div id="gameContainer" class="relative hidden">
<canvas id="gameCanvas" width="800" height="600"></canvas>
<!-- HUD Elements -->
<div class="absolute top-4 left-4">
<div class="health-container bg-gray-900 bg-opacity-70 p-2 rounded-lg">
<div class="flex items-center mb-1">
<div class="w-32 h-4 bg-gray-800 rounded-full overflow-hidden">
<div id="healthBar" class="health-bar rounded-full" style="width: 100%"></div>
</div>
<span id="healthText" class="ml-2 digital-font text-white">100%</span>
</div>
<div class="text-xs text-gray-300">OPERATIVE STATUS</div>
</div>
</div>
<div class="absolute top-4 right-4">
<div class="ammo-container bg-gray-900 bg-opacity-70 p-2 rounded-lg text-right">
<div id="ammoCount" class="ammo-count digital-font text-2xl text-white">30/90</div>
<div class="text-xs text-gray-300">AMMUNITION</div>
</div>
</div>
<div class="absolute bottom-4 left-4">
<div class="weapon-info bg-gray-900 bg-opacity-70 p-3 rounded-lg flex items-center">
<div class="w-12 h-12 bg-gray-800 mr-3 flex items-center justify-center">M4</div>
<div>
<div id="weaponName" class="digital-font text-white">M4A1</div>
<div id="fireMode" class="text-xs text-gray-300">AUTO</div>
</div>
</div>
</div>
<div class="absolute bottom-4 right-4">
<div class="objective-container bg-gray-900 bg-opacity-70 p-3 rounded-lg">
<div class="text-xs text-gray-300 mb-1">CURRENT OBJECTIVE</div>
<div id="currentObjective" class="text-sm text-white">Infiltrate the blacksite compound</div>
</div>
</div>
<div id="hitMarker" class="hit-marker"></div>
<!-- Kill Feed -->
<div id="killFeed" class="absolute top-20 left-4 w-64 space-y-1"></div>
<!-- Damage Indicator -->
<div id="damageIndicator" class="absolute inset-0 pointer-events-none opacity-0 transition-opacity duration-300"></div>
</div>
<script>
// Game State
const gameState = {
player: {
x: 400,
y: 300,
width: 30,
height: 50,
speed: 5,
health: 100,
maxHealth: 100,
direction: 0, // angle in radians
isMoving: false,
isShooting: false,
isReloading: false,
lastShotTime: 0,
kills: 0,
shotsFired: 0,
shotsHit: 0,
startTime: 0,
currentObjective: 0,
objectivesCompleted: 0,
nightVision: false,
weapons: {
primary: {
name: "M4A1",
type: "assault",
damage: 25,
fireRate: 600, // rounds per minute
magSize: 30,
ammo: 90,
currentMag: 30,
reloadTime: 2000, // ms
spread: 0.05,
range: 500,
auto: true
},
secondary: {
name: "Glock 17",
type: "pistol",
damage: 20,
fireRate: 400,
magSize: 15,
ammo: 45,
currentMag: 15,
reloadTime: 1500,
spread: 0.08,
range: 300,
auto: false
},
equipment: {
name: "Frag Grenade",
type: "grenade",
damage: 100,
radius: 100,
count: 3
}
},
currentWeapon: "primary"
},
enemies: [],
bullets: [],
explosions: [],
bloodSplatters: [],
keys: {
w: false,
a: false,
s: false,
d: false,
space: false
},
mouse: {
x: 0,
y: 0,
isDown: false
},
gameTime: 0,
gamePaused: false,
gameOver: false,
missionComplete: false,
map: {
width: 2000,
height: 2000,
obstacles: []
}
};
// DOM Elements
const gameCanvas = document.getElementById('gameCanvas');
const ctx = gameCanvas.getContext('2d');
const gameContainer = document.getElementById('gameContainer');
const mainMenu = document.getElementById('mainMenu');
const missionBriefing = document.getElementById('missionBriefing');
const armoryScreen = document.getElementById('armoryScreen');
const pauseMenu = document.getElementById('pauseMenu');
const gameOverScreen = document.getElementById('gameOverScreen');
const missionCompleteScreen = document.getElementById('missionCompleteScreen');
const healthBar = document.getElementById('healthBar');
const healthText = document.getElementById('healthText');
const ammoCount = document.getElementById('ammoCount');
const weaponName = document.getElementById('weaponName');
const fireMode = document.getElementById('fireMode');
const currentObjective = document.getElementById('currentObjective');
const hitMarker = document.getElementById('hitMarker');
const killFeed = document.getElementById('killFeed');
const damageIndicator = document.getElementById('damageIndicator');
const nightVisionOverlay = document.getElementById('nightVisionOverlay');
const scopeOverlay = document.getElementById('scopeOverlay');
// Buttons
const startGameBtn = document.getElementById('startGameBtn');
const weaponsBtn = document.getElementById('weaponsBtn');
const settingsBtn = document.getElementById('settingsBtn');
const creditsBtn = document.getElementById('creditsBtn');
const startMissionBtn = document.getElementById('startMissionBtn');
const backToMenuBtn = document.getElementById('backToMenuBtn');
const backFromArmoryBtn = document.getElementById('backFromArmoryBtn');
const resumeGameBtn = document.getElementById('resumeGameBtn');
const restartGameBtn = document.getElementById('restartGameBtn');
const quitToMenuBtn = document.getElementById('quitToMenuBtn');
const restartAfterGameOverBtn = document.getElementById('restartAfterGameOverBtn');
const quitAfterGameOverBtn = document.getElementById('quitAfterGameOverBtn');
const nextMissionBtn = document.getElementById('nextMissionBtn');
const quitAfterCompleteBtn = document.getElementById('quitAfterCompleteBtn');
// Event Listeners
startGameBtn.addEventListener('click', () => {
mainMenu.classList.add('hidden');
missionBriefing.classList.remove('hidden');
});
weaponsBtn.addEventListener('click', () => {
mainMenu.classList.add('hidden');
armoryScreen.classList.remove('hidden');
});
settingsBtn.addEventListener('click', () => {
// In a full game, this would open settings
alert('Settings would be configured here');
});
creditsBtn.addEventListener('click', () => {
// In a full game, this would show credits
alert('Game developed by Tactical Legends Team');
});
startMissionBtn.addEventListener('click', startGame);
backToMenuBtn.addEventListener('click', () => {
missionBriefing.classList.add('hidden');
mainMenu.classList.remove('hidden');
});
backFromArmoryBtn.addEventListener('click', () => {
armoryScreen.classList.add('hidden');
mainMenu.classList.remove('hidden');
});
resumeGameBtn.addEventListener('click', () => {
pauseMenu.classList.add('hidden');
gameState.gamePaused = false;
});
restartGameBtn.addEventListener('click', () => {
pauseMenu.classList.add('hidden');
startGame();
});
quitToMenuBtn.addEventListener('click', () => {
pauseMenu.classList.add('hidden');
gameContainer.classList.add('hidden');
mainMenu.classList.remove('hidden');
});
restartAfterGameOverBtn.addEventListener('click', () => {
gameOverScreen.classList.add('hidden');
startGame();
});
quitAfterGameOverBtn.addEventListener('click', () => {
gameOverScreen.classList.add('hidden');
mainMenu.classList.remove('hidden');
});
nextMissionBtn.addEventListener('click', () => {
missionCompleteScreen.classList.add('hidden');
// In a full game, this would load the next mission
alert('Next mission would load here');
mainMenu.classList.remove('hidden');
});
quitAfterCompleteBtn.addEventListener('click', () => {
missionCompleteScreen.classList.add('hidden');
mainMenu.classList.remove('hidden');
});
document.addEventListener('keydown', (e) => {
if (gameState.gamePaused || gameState.gameOver || gameState.missionComplete) return;
switch(e.key.toLowerCase()) {
case 'w': gameState.keys.w = true; break;
case 'a': gameState.keys.a = true; break;
case 's': gameState.keys.s = true; break;
case 'd': gameState.keys.d = true; break;
case ' ': gameState.keys.space = true; break;
case 'r': reloadWeapon(); break;
case 'f': toggleNightVision(); break;
case '1': switchWeapon('primary'); break;
case '2': switchWeapon('secondary'); break;
case '3': switchWeapon('equipment'); break;
case 'escape': pauseGame(); break;
}
});
document.addEventListener('keyup', (e) => {
switch(e.key.toLowerCase()) {
case 'w': gameState.keys.w = false; break;
case 'a': gameState.keys.a = false; break;
case 's': gameState.keys.s = false; break;
case 'd': gameState.keys.d = false; break;
case ' ': gameState.keys.space = false; break;
}
});
gameCanvas.addEventListener('mousemove', (e) => {
if (gameState.gamePaused) return;
const rect = gameCanvas.getBoundingClientRect();
gameState.mouse.x = e.clientX - rect.left;
gameState.mouse.y = e.clientY - rect.top;
// Calculate player direction based on mouse position
const dx = gameState.mouse.x - gameCanvas.width / 2;
const dy = gameState.mouse.y - gameCanvas.height / 2;
gameState.player.direction = Math.atan2(dy, dx);
});
gameCanvas.addEventListener('mousedown', (e) => {
if (gameState.gamePaused || gameState.gameOver || gameState.missionComplete) return;
gameState.mouse.isDown = true;
if (e.button === 0) { // Left click
gameState.player.isShooting = true;
fireWeapon();
}
});
gameCanvas.addEventListener('mouseup', (e) => {
gameState.mouse.isDown = false;
if (e.button === 0) {
gameState.player.isShooting = false;
}
});
gameCanvas.addEventListener('click', (e) => {
if (gameState.gamePaused || gameState.gameOver || gameState.missionComplete) return;
if (e.button === 0) {
const weapon = gameState.player.weapons[gameState.player.currentWeapon];
if (!weapon.auto) {
fireWeapon();
}
}
});
// Weapon selection in armory
document.querySelectorAll('.weapon-option').forEach(option => {
option.addEventListener('click', () => {
const weaponType = option.parentElement.querySelector('h3').textContent;
const weaponId = option.getAttribute('data-weapon');
// In a full game, this would change the player's weapon loadout
alert(`Selected ${weaponType}: ${weaponId}`);
});
});
// Game Functions
function startGame() {
// Reset game state
gameState.player = {
...gameState.player,
x: 400,
y: 300,
health: 100,
kills: 0,
shotsFired: 0,
shotsHit: 0,
startTime: Date.now(),
currentObjective: 0,
objectivesCompleted: 0,
weapons: {
primary: {
...gameState.player.weapons.primary,
currentMag: 30,
ammo: 90
},
secondary: {
...gameState.player.weapons.secondary,
currentMag: 15,
ammo: 45
},
equipment: {
...gameState.player.weapons.equipment,
count: 3
}
},
currentWeapon: 'primary'
};
gameState.enemies = [];
gameState.bullets = [];
gameState.explosions = [];
gameState.bloodSplatters = [];
gameState.gameTime = 0;
gameState.gamePaused = false;
gameState.gameOver = false;
gameState.missionComplete = false;
// Generate enemies
for (let i = 0; i < 20; i++) {
const angle = Math.random() * Math.PI * 2;
const distance = 300 + Math.random() * 500;
gameState.enemies.push({
x: gameState.player.x + Math.cos(angle) * distance,
y: gameState.player.y + Math.sin(angle) * distance,
width: 25,
height: 40,
health: 100,
speed: 1 + Math.random() * 2,
lastShotTime: 0,
fireRate: 1000 + Math.random() * 2000,
damage: 10,
type: Math.random() > 0.8 ? 'heavy' : 'normal'
});
}
// Generate obstacles
gameState.map.obstacles = [];
for (let i = 0; i < 30; i++) {
gameState.map.obstacles.push({
x: Math.random() * gameState.map.width,
y: Math.random() * gameState.map.height,
width: 50 + Math.random() * 100,
height: 50 + Math.random() * 100
});
}
// Update UI
updateHealthUI();
updateAmmoUI();
updateWeaponUI();
updateObjectiveUI();
// Clear kill feed
killFeed.innerHTML = '';
// Show game
mainMenu.classList.add('hidden');
missionBriefing.classList.add('hidden');
gameContainer.classList.remove('hidden');
// Start game loop
requestAnimationFrame(gameLoop);
}
function gameLoop(timestamp) {
if (gameState.gamePaused || gameState.gameOver || gameState.missionComplete) return;
// Calculate delta time for smooth movement
const deltaTime = timestamp - (gameState.lastFrameTime || timestamp);
gameState.lastFrameTime = timestamp;
gameState.gameTime = (Date.now() - gameState.player.startTime) / 1000;
// Update game state
updatePlayer(deltaTime);
updateEnemies(deltaTime);
updateBullets();
updateExplosions();
updateBloodSplatters();
// Check for auto-fire weapons
if (gameState.player.isShooting) {
const weapon = gameState.player.weapons[gameState.player.currentWeapon];
if (weapon.auto && Date.now() - gameState.player.lastShotTime > 60000 / weapon.fireRate) {
fireWeapon();
}
}
// Check objectives
checkObjectives();
// Render game
render();
// Continue game loop
requestAnimationFrame(gameLoop);
}
function updatePlayer(deltaTime) {
// Movement
let dx = 0;
let dy = 0;
if (gameState.keys.w) dy -= 1;
if (gameState.keys.s) dy += 1;
if (gameState.keys.a) dx -= 1;
if (gameState.keys.d) dx += 1;
// Normalize diagonal movement
if (dx !== 0 && dy !== 0) {
const invSqrt2 = 0.7071; // 1/sqrt(2)
dx *= invSqrt2;
dy *= invSqrt2;
}
// Apply movement
const moveSpeed = gameState.player.speed * (deltaTime / 16); // Normalize to ~60fps
gameState.player.x += dx * moveSpeed;
gameState.player.y += dy * moveSpeed;
// Boundary check
gameState.player.x = Math.max(0, Math.min(gameState.map.width, gameState.player.x));
gameState.player.y = Math.max(0, Math.min(gameState.map.height, gameState.player.y));
// Check for jumping
if (gameState.keys.space) {
// In a more complete game, this would handle jumping physics
}
// Check for obstacle collisions
gameState.map.obstacles.forEach(obstacle => {
if (checkCollision(gameState.player, obstacle)) {
// Simple collision response - push player out
const playerCenter = {
x: gameState.player.x + gameState.player.width / 2,
y: gameState.player.y + gameState.player.height / 2
};
const obstacleCenter = {
x: obstacle.x + obstacle.width / 2,
y: obstacle.y + obstacle.height / 2
};
const dx = playerCenter.x - obstacleCenter.x;
const dy = playerCenter.y - obstacleCenter.y;
if (Math.abs(dx) > Math.abs(dy)) {
gameState.player.x += dx > 0 ? 2 : -2;
} else {
gameState.player.y += dy > 0 ? 2 : -2;
}
}
});
}
function updateEnemies(deltaTime) {
gameState.enemies.forEach((enemy, index) => {
if (enemy.health <= 0) return;
// Simple AI: move toward player and shoot
const dx = gameState.player.x - enemy.x;
const dy = gameState.player.y - enemy.y;
const distance = Math.sqrt(dx * dx + dy * dy);
// Move toward player but maintain some distance
if (distance > 200) {
const moveSpeed = enemy.speed * (deltaTime / 16);
enemy.x += (dx / distance) * moveSpeed;
enemy.y += (dy / distance) * moveSpeed;
} else if (distance < 150) {
// Back away if too close
const moveSpeed = enemy.speed * (deltaTime / 16);
enemy.x -= (dx / distance) * moveSpeed;
enemy.y -= (dy / distance) * moveSpeed;
}
// Shoot at player
if (Date.now() - enemy.lastShotTime > enemy.fireRate) {
enemy.lastShotTime = Date.now();
// Calculate direction to player
const angle = Math.atan2(dy, dx);
// Add some inaccuracy
const inaccuracy = Math.random() * 0.2 - 0.1;
const bulletAngle = angle + inaccuracy;
// Create bullet
gameState.bullets.push({
x: enemy.x + enemy.width / 2,
y: enemy.y + enemy.height / 2,
dx: Math.cos(bulletAngle) * 10,
dy: Math.sin(bulletAngle) * 10,
damage: enemy.damage,
isPlayer: false,
owner: index
});
}
// Check for obstacle collisions
gameState.map.obstacles.forEach(obstacle => {
if (checkCollision(enemy, obstacle)) {
// Simple collision response - push enemy out
const enemyCenter = {
x: enemy.x + enemy.width / 2,
y: enemy.y + enemy.height / 2
};
const obstacleCenter = {
x: obstacle.x + obstacle.width / 2,
y: obstacle.y + obstacle.height / 2
};
const dx = enemyCenter.x - obstacleCenter.x;
const dy = enemyCenter.y - obstacleCenter.y;
if (Math.abs(dx) > Math.abs(dy)) {
enemy.x += dx > 0 ? 2 : -2;
} else {
enemy.y += dy > 0 ? 2 : -2;
}
}
});
});
}
function updateBullets() {
for (let i = gameState.bullets.length - 1; i >= 0; i--) {
const bullet = gameState.bullets[i];
// Update position
bullet.x += bullet.dx;
bullet.y += bullet.dy;
// Check for out of bounds
if (bullet.x < 0 || bullet.x > gameState.map.width ||
bullet.y < 0 || bullet.y > gameState.map.height) {
gameState.bullets.splice(i, 1);
continue;
}
// Check for obstacle collisions
let hitObstacle = false;
gameState.map.obstacles.forEach(obstacle => {
if (pointInRect(bullet.x, bullet.y, obstacle)) {
hitObstacle = true;
}
});
if (hitObstacle) {
gameState.bullets.splice(i, 1);
continue;
}
// Check for player/enemy hits
if (bullet.isPlayer) {
// Check enemy hits
for (let j = 0; j < gameState.enemies.length; j++) {
const enemy = gameState.enemies[j];
if (enemy.health <= 0) continue;
if (pointInRect(bullet.x, bullet.y, enemy)) {
// Hit enemy
enemy.health -= bullet.damage;
gameState.bullets.splice(i, 1);
gameState.player.shotsHit++;
// Show hit marker
showHitMarker();
// Add blood splatter
gameState.bloodSplatters.push({
x: bullet.x,
y: bullet.y,
size: 10 + Math.random() * 20
});
// Check if enemy died
if (enemy.health <= 0) {
gameState.player.kills++;
// Add kill to feed
addKillFeedEntry('Enemy eliminated');
// Add explosion
gameState.explosions.push({
x: enemy.x + enemy.width / 2,
y: enemy.y + enemy.height / 2,
size: 30
});
}
break;
}
}
} else {
// Check player hit
if (pointInRect(bullet.x, bullet.y, gameState.player)) {
// Hit player
gameState.player.health -= bullet.damage;
gameState.bullets.splice(i, 1);
// Show damage indicator
showDamageIndicator(bullet.dx, bullet.dy);
// Update health UI
updateHealthUI();
// Check if player died
if (gameState.player.health <= 0) {
gameOver();
}
}
}
}
}
function updateExplosions() {
// Explosions are handled by CSS animations
// We just need to remove them after animation completes
for (let i = gameState.explosions.length - 1; i >= 0; i--) {
// In a more complete game, we'd track animation progress
if (gameState.explosions[i].time > 500) {
gameState.explosions.splice(i, 1);
} else {
gameState.explosions[i].time = (gameState.explosions[i].time || 0) + 16;
}
}
}
function updateBloodSplatters() {
// Blood splatters fade out via CSS animations
for (let i = gameState.bloodSplatters.length - 1; i >= 0; i--) {
if (gameState.bloodSplatters[i].time > 1000) {
gameState.bloodSplatters.splice(i, 1);
} else {
gameState.bloodSplatters[i].time = (gameState.bloodSplatters[i].time || 0) + 16;
}
}
}
function checkObjectives() {
// Simple objective progression based on kills
const totalEnemies = 20;
const aliveEnemies = gameState.enemies.filter(e => e.health > 0).length;
const killedEnemies = totalEnemies - aliveEnemies;
// Objective 1: Infiltrate (starts complete)
if (gameState.player.currentObjective === 0 && gameState.gameTime > 5) {
completeObjective(0);
}
// Objective 2: Neutralize enemies (50%)
if (gameState.player.currentObjective === 1 && killedEnemies >= 10) {
completeObjective(1);
}
// Objective 3: Neutralize all enemies
if (gameState.player.currentObjective === 2 && killedEnemies >= totalEnemies) {
completeObjective(2);
}
// Objective 4: Exfiltrate (wait 5 seconds after last kill)
if (gameState.player.currentObjective === 3 && killedEnemies >= totalEnemies &&
gameState.gameTime - (gameState.lastKillTime || 0) > 5) {
completeObjective(3);
missionComplete();
}
// Track last kill time for exfiltration
if (killedEnemies > (gameState.lastKillCount || 0)) {
gameState.lastKillTime = gameState.gameTime;
gameState.lastKillCount = killedEnemies;
}
}
function completeObjective(index) {
if (index > gameState.player.currentObjective) return;
gameState.player.currentObjective = index + 1;
gameState.player.objectivesCompleted++;
// Update UI
updateObjectiveUI();
// Mark objective as completed in briefing
const objectives = document.querySelectorAll('.objective-item');
if (index < objectives.length) {
objectives[index].classList.add('completed');
}
// Show notification
addKillFeedEntry(`Objective ${index + 1} completed`);
}
function fireWeapon() {
const weapon = gameState.player.weapons[gameState.player.currentWeapon];
// Check if weapon can fire
if (weapon.currentMag <= 0) {
if (!gameState.player.isReloading) {
reloadWeapon();
}
return;
}
if (gameState.player.isReloading) return;
// Fire weapon
weapon.currentMag--;
gameState.player.shotsFired++;
gameState.player.lastShotTime = Date.now();
// Update ammo UI
updateAmmoUI();
// Create bullet
const spread = weapon.spread * (Math.random() * 2 - 1);
const angle = gameState.player.direction + spread;
gameState.bullets.push({
x: gameState.player.x + gameState.player.width / 2,
y: gameState.player.y + gameState.player.height / 2,
dx: Math.cos(angle) * 15,
dy: Math.sin(angle) * 15,
damage: weapon.damage,
isPlayer: true
});
// Recoil effect (simple version)
const recoil = 0.05;
gameState.player.direction += (Math.random() * 2 - 1) * recoil;
}
function reloadWeapon() {
const weapon = gameState.player.weapons[gameState.player.currentWeapon];
// Check if reload is needed
if (weapon.currentMag === weapon.magSize || weapon.ammo <= 0) return;
// Check if already reloading
if (gameState.player.isReloading) return;
gameState.player.isReloading = true;
// Calculate ammo to reload
const ammoNeeded = weapon.magSize - weapon.currentMag;
const ammoAvailable = Math.min(ammoNeeded, weapon.ammo);
// Set reload timeout
setTimeout(() => {
weapon.currentMag += ammoAvailable;
weapon.ammo -= ammoAvailable;
gameState.player.isReloading = false;
// Update UI
updateAmmoUI();
// Show reload complete notification
addKillFeedEntry('Reload complete');
}, weapon.reloadTime);
// Show reload notification
addKillFeedEntry('Reloading...');
}
function switchWeapon(weaponType) {
if (gameState.player.currentWeapon === weaponType || gameState.player.isReloading) return;
gameState.player.currentWeapon = weaponType;
updateWeaponUI();
// Show weapon switch notification
addKillFeedEntry(`Switched to ${gameState.player.weapons[weaponType].name}`);
}
function toggleNightVision() {
gameState.player.nightVision = !gameState.player.nightVision;
if (gameState.player.nightVision) {
gameCanvas.classList.add('night-vision');
nightVisionOverlay.style.display = 'block';
addKillFeedEntry('Night vision activated');
} else {
gameCanvas.classList.remove('night-vision');
nightVisionOverlay.style.display = 'none';
addKillFeedEntry('Night vision deactivated');
}
}
function pauseGame() {
gameState.gamePaused = true;
pauseMenu.classList.remove('hidden');
}
function gameOver() {
gameState.gameOver = true;
// Update stats
document.getElementById('killsStat').textContent = gameState.player.kills;
const accuracy = gameState.player.shotsFired > 0
? Math.round((gameState.player.shotsHit / gameState.player.shotsFired) * 100)
: 0;
document.getElementById('accuracyStat').textContent = `${accuracy}%`;
document.getElementById('objectivesStat').textContent =
`${gameState.player.objectivesCompleted}/4`;
const minutes = Math.floor(gameState.gameTime / 60);
const seconds = Math.floor(gameState.gameTime % 60);
document.getElementById('timeStat').textContent =
`${minutes}:${seconds.toString().padStart(2, '0')}`;
// Show game over screen
gameOverScreen.classList.remove('hidden');
}
function missionComplete() {
gameState.missionComplete = true;
// Update stats
document.getElementById('killsStatComplete').textContent = gameState.player.kills;
const accuracy = gameState.player.shotsFired > 0
? Math.round((gameState.player.shotsHit / gameState.player.shotsFired) * 100)
: 0;
document.getElementById('accuracyStatComplete').textContent = `${accuracy}%`;
document.getElementById('objectivesStatComplete').textContent = '4/4';
const minutes = Math.floor(gameState.gameTime / 60);
const seconds = Math.floor(gameState.gameTime % 60);
document.getElementById('timeStatComplete').textContent =
`${minutes}:${seconds.toString().padStart(2, '0')}`;
// Show mission complete screen
missionCompleteScreen.classList.remove('hidden');
}
// UI Functions
function updateHealthUI() {
const healthPercent = (gameState.player.health / gameState.player.maxHealth) * 100;
healthBar.style.width = `${healthPercent}%`;
healthText.textContent = `${Math.round(healthPercent)}%`;
// Change color based on health
if (healthPercent < 30) {
healthBar.style.background = 'linear-gradient(to right, #e74c3c, #c0392b)';
} else if (healthPercent < 60) {
healthBar.style.background = 'linear-gradient(to right, #f39c12, #e67e22)';
} else {
healthBar.style.background = 'linear-gradient(to right, #e74c3c, #f39c12)';
}
}
function updateAmmoUI() {
const weapon = gameState.player.weapons[gameState.player.currentWeapon];
ammoCount.textContent = `${weapon.currentMag}/${weapon.ammo}`;
// Flash red if low ammo
if (weapon.currentMag === 0) {
ammoCount.classList.add('text-red-500');
ammoCount.classList.remove('text-white');
} else if (weapon.currentMag <= weapon.magSize * 0.3) {
ammoCount.classList.add('text-yellow-500');
ammoCount.classList.remove('text-white');
} else {
ammoCount.classList.remove('text-red-500', 'text-yellow-500');
ammoCount.classList.add('text-white');
}
}
function updateWeaponUI() {
const weapon = gameState.player.weapons[gameState.player.currentWeapon];
weaponName.textContent = weapon.name;
fireMode.textContent = weapon.auto ? 'AUTO' : 'SEMI';
}
function updateObjectiveUI() {
const objectives = [
'Infiltrate the blacksite compound',
'Neutralize enemy combatants',
'Eliminate all hostiles',
'Exfiltrate to the LZ'
];
if (gameState.player.currentObjective < objectives.length) {
currentObjective.textContent = objectives[gameState.player.currentObjective];
} else {
currentObjective.textContent = 'Mission complete';
}
}
function showHitMarker() {
hitMarker.style.left = '50%';
hitMarker.style.top = '50%';
hitMarker.classList.add('hit-animation');
setTimeout(() => {
hitMarker.classList.remove('hit-animation');
}, 300);
}
function showDamageIndicator(dx, dy) {
// Calculate direction of damage
const angle = Math.atan2(dy, dx);
const degrees = angle * (180 / Math.PI);
// Position damage indicator
damageIndicator.style.background = `radial-gradient(circle at ${50 + Math.cos(angle) * 30}% ${50 + Math.sin(angle) * 30}%, rgba(255, 0, 0, 0.7), transparent 70%)`;
damageIndicator.style.opacity = '0.7';
// Fade out
setTimeout(() => {
damageIndicator.style.opacity = '0';
}, 300);
}
function addKillFeedEntry(text) {
const entry = document.createElement('div');
entry.className = 'bg-black bg-opacity-70 p-2 text-sm text-white rounded';
entry.textContent = text;
killFeed.insertBefore(entry, killFeed.firstChild);
// Limit to 5 entries
if (killFeed.children.length > 5) {
killFeed.removeChild(killFeed.lastChild);
}
// Fade out after 3 seconds
setTimeout(() => {
entry.style.opacity = '0';
setTimeout(() => {
if (entry.parentNode) {
killFeed.removeChild(entry);
}
}, 300);
}, 3000);
}
// Utility Functions
function checkCollision(rect1, rect2) {
return rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y;
}
function pointInRect(x, y, rect) {
return x >= rect.x && x <= rect.x + rect.width &&
y >= rect.y && y <= rect.y + rect.height;
}
// Rendering
function render() {
// Clear canvas
ctx.clearRect(0, 0, gameCanvas.width, gameCanvas.height);
// Draw background (simple grid for a tactical map)
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, gameCanvas.width, gameCanvas.height);
ctx.strokeStyle = '#222';
ctx.lineWidth = 1;
const gridSize = 50;
const offsetX = -gameState.player.x % gridSize;
const offsetY = -gameState.player.y % gridSize;
for (let x = offsetX; x < gameCanvas.width; x += gridSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, gameCanvas.height);
ctx.stroke();
}
for (let y = offsetY; y < gameCanvas.height; y += gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(gameCanvas.width, y);
ctx.stroke();
}
// Calculate camera offset (center on player)
const cameraX = gameCanvas.width / 2 - gameState.player.x;
const cameraY = gameCanvas.height / 2 - gameState.player.y;
// Save context for camera transform
ctx.save();
ctx.translate(cameraX, cameraY);
// Draw obstacles
ctx.fillStyle = '#333';
gameState.map.obstacles.forEach(obstacle => {
ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
ctx.strokeStyle = '#444';
ctx.strokeRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
});
// Draw enemies
gameState.enemies.forEach(enemy => {
if (enemy.health <= 0) return;
// Draw enemy
ctx.fillStyle = enemy.type === 'heavy' ? '#8B0000' : '#c0392b';
ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
// Draw health bar
const healthPercent = enemy.health / 100;
ctx.fillStyle = '#555';
ctx.fillRect(enemy.x, enemy.y - 10, enemy.width, 5);
ctx.fillStyle = healthPercent > 0.6 ? '#2ecc71' :
healthPercent > 0.3 ? '#f39c12' : '#e74c3c';
ctx.fillRect(enemy.x, enemy.y - 10, enemy.width * healthPercent, 5);
// Draw enemy type indicator
if (enemy.type === 'heavy') {
ctx.fillStyle = '#f1c40f';
ctx.font = 'bold 10px Arial';
ctx.fillText('HEAVY', enemy.x + 2, enemy.y - 15);
}
});
// Draw player
ctx.fillStyle = '#3498db';
ctx.fillRect(gameState.player.x, gameState.player.y, gameState.player.width, gameState.player.height);
// Draw bullets
ctx.fillStyle = '#f1c40f';
gameState.bullets.forEach(bullet => {
ctx.beginPath();
ctx.arc(bullet.x, bullet.y, 3, 0, Math.PI * 2);
ctx.fill();
});
// Restore context (remove camera transform)
ctx.restore();
// Draw explosions (handled by CSS)
// Draw blood splatters (handled by CSS)
// Draw crosshair
const crosshairSize = 20;
const crosshairGap = 5;
const centerX = gameCanvas.width / 2;
const centerY = gameCanvas.height / 2;
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
// Horizontal line
ctx.beginPath();
ctx.moveTo(centerX - crosshairSize - crosshairGap, centerY);
ctx.lineTo(centerX - crosshairGap, centerY);
ctx.moveTo(centerX + crosshairGap, centerY);
ctx.lineTo(centerX + crosshairSize + crosshairGap, centerY);
ctx.stroke();
// Vertical line
ctx.beginPath();
ctx.moveTo(centerX, centerY - crosshairSize - crosshairGap);
ctx.lineTo(centerX, centerY - crosshairGap);
ctx.moveTo(centerX, centerY + crosshairGap);
ctx.lineTo(centerX, centerY + crosshairSize + crosshairGap);
ctx.stroke();
// Center dot
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.arc(centerX, centerY, 2, 0, Math.PI * 2);
ctx.fill();
// Draw reload indicator
if (gameState.player.isReloading) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(centerX - 50, centerY + 30, 100, 10);
const weapon = gameState.player.weapons[gameState.player.currentWeapon];
const reloadProgress = (Date.now() - gameState.player.lastShotTime) / weapon.reloadTime;
ctx.fillStyle = '#f39c12';
ctx.fillRect(centerX - 50, centerY + 30, 100 * Math.min(1, reloadProgress), 10);
ctx.fillStyle = '#fff';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.fillText('RELOADING', centerX, centerY + 50);
}
}
// Initialize game
document.addEventListener('DOMContentLoaded', () => {
// Set up weapon UI
updateWeaponUI();
// Create DOM elements for explosions and blood splatters
const gameContainer = document.getElementById('gameContainer');
// In a full game, we'd pre-create these elements for performance
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=jurgenpaul82/tactical-legend-1-0" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>