Guest

GAME

Apr 20th, 2026
18
0
Never
Not a member of GistPad yet? Sign Up, it unlocks many cool features!
None 77.70 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>AGENT_007: INTERIOR ESCAPE</title>
  7. <style>
  8. body { margin: 0; background: #050505; display: flex; justify-content: center; align-items: center; height: 100vh; color: white; font-family: 'Courier New', monospace; overflow: hidden; }
  9. canvas { background: #0d0d14; box-shadow: 0 0 30px rgba(42, 157, 143, 0.4); border: 2px solid #2a9d8f; cursor: crosshair; }
  10. #ui { position: absolute; top: 10px; text-align: center; width: 100%; pointer-events: none; }
  11. #scoreBoard { position: absolute; top: 20px; right: 30px; font-size: 24px; color: #e9c46a; text-shadow: 0 0 10px #e9c46a; }
  12. .controls { font-size: 14px; opacity: 0.8; }
  13. </style>
  14. </head>
  15. <body>
  16.  
  17. <div id="ui">
  18. <h2 id="status">AGENT_007: INFILTRATION</h2>
  19. <p class="controls">A/D: Move (Hold 1.5s to Sprint) | W: Moon-Jump | Jump on walls to Wall-Jump<br>
  20. <b>Q: Equip Blaster | Left Click: Shoot</b></p>
  21. </div>
  22.  
  23. <div id="scoreBoard">DISTANCE: 0m</div>
  24.  
  25. <canvas id="gameCanvas" width="800" height="600"></canvas>
  26.  
  27. <script>
  28. const canvas = document.getElementById('gameCanvas');
  29. const ctx = canvas.getContext('2d');
  30. const scoreBoard = document.getElementById('scoreBoard');
  31.  
  32. let gameState = 'MENU';
  33. const camera = { x: 0, y: 0 };
  34.  
  35. let player;
  36. let platforms = [];
  37. let backgroundProps = [];
  38. let projectiles = [];
  39. let enemies = [];
  40. let particles = [];
  41. let items = [];
  42. let nextChunkX = 0;
  43. const chunkWidth = 800;
  44. let distanceScore = 0;
  45. let deathTimer = 0;
  46.  
  47. const gravity = 0.15;
  48. const friction = 0.85;
  49. const keys = [];
  50. let mouseX = 0, mouseY = 0;
  51. let lastQPress = 0;
  52.  
  53. function initGame() {
  54. player = {
  55. x: 100, y: 400, width: 28, height: 44,
  56. velX: 0, velY: 0, hp: 100, maxHp: 100,
  57. speed: 5, sprintSpeed: 9, jumpPower: 9,
  58. moveFrames: 0,
  59. grounded: false, touchingWall: null, facingRight: true, state: 'idle',
  60. hasBlasterEquipped: false
  61. };
  62. platforms = []; backgroundProps = []; projectiles = []; enemies = []; particles = []; items = [];
  63. nextChunkX = 0; distanceScore = 0; deathTimer = 0;
  64.  
  65. platforms.push({ x: -20, y: 0, w: 20, h: 600 });
  66. platforms.push({ x: 0, y: 0, w: chunkWidth, h: 40 });
  67. platforms.push({ x: 0, y: 560, w: chunkWidth, h: 40 });
  68. nextChunkX += chunkWidth;
  69.  
  70. generateRoom(); generateRoom();
  71. }
  72.  
  73. // --- INPUT HANDLING ---
  74. window.addEventListener("keydown", e => {
  75. keys[e.code] = true;
  76. if((gameState === 'MENU' || gameState === 'GAMEOVER') && e.code === 'Enter') {
  77. initGame(); gameState = 'PLAYING'; document.getElementById('ui').style.display = 'none';
  78. }
  79. if (e.code === 'KeyQ' && gameState === 'PLAYING') {
  80. const now = Date.now();
  81. if (now - lastQPress > 200) {
  82. player.hasBlasterEquipped = !player.hasBlasterEquipped;
  83. lastQPress = now;
  84. }
  85. }
  86. });
  87. window.addEventListener("keyup", e => keys[e.code] = false);
  88.  
  89. window.addEventListener('mousemove', e => {
  90. const rect = canvas.getBoundingClientRect();
  91. mouseX = e.clientX - rect.left;
  92. mouseY = e.clientY - rect.top;
  93. });
  94.  
  95. window.addEventListener('mousedown', e => {
  96. if (e.button === 0 && player.hasBlasterEquipped && gameState === 'PLAYING') {
  97. shootBlaster();
  98. }
  99. });
  100.  
  101. function shootBlaster() {
  102. const pCenterX = player.x + player.width / 2;
  103. const pCenterY = player.y + player.height / 2;
  104. const targetX = mouseX + camera.x;
  105. const angle = Math.atan2(mouseY - pCenterY, targetX - pCenterX);
  106.  
  107. player.velX -= Math.cos(angle) * 1.5;
  108.  
  109. projectiles.push({
  110. x: pCenterX + Math.cos(angle) * 30,
  111. y: pCenterY + Math.sin(angle) * 30,
  112. velX: Math.cos(angle) * 20,
  113. velY: Math.sin(angle) * 20,
  114. life: 60,
  115. owner: 'player'
  116. });
  117. }
  118.  
  119. // --- ENTITY FACTORIES ---
  120. function createGuard(x, y) {
  121. return {
  122. type: 'guard', x: x, y: y, width: 28, height: 44,
  123. velX: 0, velY: 0, hp: 50, maxHp: 50,
  124. facingRight: false, state: 'idle', cooldown: 0, aimAngle: 0
  125. };
  126. }
  127.  
  128. function createDrone(x, y) {
  129. return {
  130. type: 'drone', x: x, y: y, startY: y, width: 24, height: 20,
  131. velX: 0, velY: 0, hp: 30, maxHp: 30,
  132. facingRight: false, state: 'patrol', cooldown: 0, aimAngle: 0,
  133. hoverTimer: Math.random() * 100
  134. };
  135. }
  136.  
  137. function createMedKit(x, y) {
  138. return { x: x, y: y, width: 16, height: 16, velX: 0, velY: -3, grounded: false, type: 'medkit' };
  139. }
  140.  
  141. // --- PROCEDURAL GENERATOR ---
  142. function generateRoom() {
  143. const ox = nextChunkX;
  144. const roomType = Math.floor(Math.random() * 5);
  145.  
  146. platforms.push({ x: ox, y: 0, w: chunkWidth, h: 40 });
  147.  
  148. // General Background Details
  149. backgroundProps.push({ type: 'cautionStripe', x: ox, y: 540, w: chunkWidth, h: 20 });
  150. if (Math.random() > 0.4) backgroundProps.push({ type: 'vent', x: ox + 100 + Math.random() * 500, y: 150 + Math.random() * 150, w: 80, h: 80 });
  151. if (Math.random() > 0.3) backgroundProps.push({ type: 'wires', x: ox + Math.random() * 600, y: 0, w: 200, h: 120 });
  152.  
  153. if (roomType === 0) {
  154. platforms.push({ x: ox, y: 560, w: 300, h: 40 });
  155. platforms.push({ x: ox + 500, y: 560, w: 300, h: 40 });
  156. platforms.push({ x: ox + 250, y: 400, w: 100, h: 20 });
  157.  
  158. backgroundProps.push({ type: 'window', x: ox + 150, y: 100, w: 500, h: 200 });
  159. backgroundProps.push({ type: 'terminal', x: ox + 100, y: 510, w: 80, h: 50 });
  160. backgroundProps.push({ type: 'serverRack', x: ox + 520, y: 360, w: 80, h: 200 });
  161.  
  162. if (Math.random() < 0.6) enemies.push(createGuard(ox + 600, 560 - 44));
  163. if (Math.random() < 0.6) enemies.push(createGuard(ox + 270, 400 - 44));
  164. if (Math.random() < 0.4) enemies.push(createDrone(ox + 400, 150));
  165. }
  166. else if (roomType === 1) {
  167. platforms.push({ x: ox, y: 560, w: 100, h: 40 });
  168. platforms.push({ x: ox + 200, y: 450, w: 100, h: 20 });
  169. platforms.push({ x: ox + 450, y: 300, w: 100, h: 20 });
  170. platforms.push({ x: ox + 700, y: 450, w: 100, h: 20 });
  171.  
  172. backgroundProps.push({ type: 'pipes', x: ox, y: 50, w: chunkWidth, h: 100 });
  173. backgroundProps.push({ type: 'danger', x: ox, y: 560, w: 100, h: 10 });
  174. backgroundProps.push({ type: 'crates', x: ox + 710, y: 390, w: 60, h: 60 });
  175.  
  176. if (Math.random() < 0.35) items.push(createMedKit(ox + 732, 360));
  177.  
  178. if (Math.random() < 0.6) enemies.push(createGuard(ox + 470, 300 - 44));
  179. if (Math.random() < 0.3) enemies.push(createDrone(ox + 250, 100));
  180. }
  181. else if (roomType === 2) {
  182. platforms.push({ x: ox, y: 560, w: chunkWidth, h: 40 });
  183. platforms.push({ x: ox, y: 300, w: 250, h: 20 });
  184. platforms.push({ x: ox + 550, y: 300, w: 250, h: 20 });
  185. platforms.push({ x: ox + 350, y: 400, w: 100, h: 160 });
  186.  
  187. backgroundProps.push({ type: 'table', x: ox + 100, y: 520, w: 120, h: 40 });
  188. backgroundProps.push({ type: 'table', x: ox + 600, y: 520, w: 120, h: 40 });
  189. backgroundProps.push({ type: 'serverRack', x: ox + 20, y: 100, w: 70, h: 200 });
  190. backgroundProps.push({ type: 'serverRack', x: ox + 700, y: 100, w: 70, h: 200 });
  191.  
  192. if (Math.random() < 0.25) items.push(createMedKit(ox + 150, 480));
  193.  
  194. if (Math.random() < 0.5) enemies.push(createGuard(ox + 650, 560 - 44));
  195. if (Math.random() < 0.5) enemies.push(createGuard(ox + 100, 300 - 44));
  196. if (Math.random() < 0.4) enemies.push(createDrone(ox + 400, 200));
  197. }
  198. else if (roomType === 3) {
  199. platforms.push({ x: ox, y: 560, w: 200, h: 40 });
  200. platforms.push({ x: ox + 200, y: 480, w: 150, h: 20 });
  201. platforms.push({ x: ox + 350, y: 380, w: 150, h: 20 });
  202. platforms.push({ x: ox + 500, y: 280, w: 150, h: 20 });
  203. platforms.push({ x: ox + 650, y: 180, w: 150, h: 20 });
  204.  
  205. backgroundProps.push({ type: 'pipes', x: ox, y: 0, w: chunkWidth, h: 600 });
  206. backgroundProps.push({ type: 'vent', x: ox + 400, y: 100, w: 80, h: 80 });
  207. backgroundProps.push({ type: 'crates', x: ox + 50, y: 500, w: 60, h: 60 });
  208.  
  209. if (Math.random() < 0.35) items.push(createMedKit(ox + 70, 470));
  210.  
  211. if (Math.random() < 0.6) enemies.push(createGuard(ox + 680, 180 - 44));
  212. if (Math.random() < 0.3) enemies.push(createDrone(ox + 100, 250));
  213. }
  214. else if (roomType === 4) {
  215. platforms.push({ x: ox, y: 560, w: chunkWidth, h: 40 });
  216. platforms.push({ x: ox + 200, y: 0, w: 50, h: 400 });
  217. platforms.push({ x: ox + 500, y: 200, w: 50, h: 400 });
  218.  
  219. backgroundProps.push({ type: 'danger', x: ox, y: 560, w: chunkWidth, h: 10 });
  220. backgroundProps.push({ type: 'terminal', x: ox + 100, y: 510, w: 60, h: 50 });
  221. backgroundProps.push({ type: 'serverRack', x: ox + 600, y: 360, w: 100, h: 200 });
  222. backgroundProps.push({ type: 'crates', x: ox + 720, y: 500, w: 60, h: 60 });
  223. backgroundProps.push({ type: 'crates', x: ox + 730, y: 440, w: 50, h: 60 });
  224.  
  225. if (Math.random() < 0.25) items.push(createMedKit(ox + 745, 410));
  226.  
  227. if (Math.random() < 0.7) enemies.push(createGuard(ox + 700, 560 - 44));
  228. if (Math.random() < 0.4) enemies.push(createDrone(ox + 350, 300));
  229. }
  230.  
  231. nextChunkX += chunkWidth;
  232. }
  233.  
  234. function cleanRooms() {
  235. platforms = platforms.filter(p => p.x + p.w > camera.x - 800);
  236. backgroundProps = backgroundProps.filter(p => p.x + p.w > camera.x - 800);
  237. enemies = enemies.filter(e => e.x + e.width > camera.x - 800);
  238. items = items.filter(i => i.x + i.width > camera.x - 800);
  239. }
  240.  
  241. function checkCollision(shapeA, shapeB) {
  242. const vX = (shapeA.x + (shapeA.width / 2)) - (shapeB.x + (shapeB.w / 2));
  243. const vY = (shapeA.y + (shapeA.height / 2)) - (shapeB.y + (shapeB.h / 2));
  244. const hWidths = (shapeA.width / 2) + (shapeB.w / 2);
  245. const hHeights = (shapeA.height / 2) + (shapeB.h / 2);
  246. let colDir = null;
  247.  
  248. if (Math.abs(vX) < hWidths && Math.abs(vY) < hHeights) {
  249. const oX = hWidths - Math.abs(vX);
  250. const oY = hHeights - Math.abs(vY);
  251. if (oX >= oY) {
  252. if (vY > 0) { colDir = "t"; shapeA.y += oY; } else { colDir = "b"; shapeA.y -= oY; }
  253. } else {
  254. if (vX > 0) { colDir = "l"; shapeA.x += oX; } else { colDir = "r"; shapeA.x -= oX; }
  255. }
  256. } return colDir;
  257. }
  258.  
  259. // --- PARTICLE SYSTEM ---
  260. function createParticles(x, y, type) {
  261. let colors = [];
  262. if (type === 'player') colors = ['#00f2ff', '#0099cc', '#e63946', '#222', '#444'];
  263. else if (type === 'enemy' || type === 'drone') colors = ['#ff3333', '#aa0000', '#111', '#222', '#4a4e69'];
  264. else if (type === 'heal') colors = ['#00ff41', '#b5e48c', '#ffffff'];
  265.  
  266. let amt = type === 'heal' ? 15 : 40;
  267.  
  268. for (let i = 0; i < amt; i++) {
  269. particles.push({
  270. x: x, y: y,
  271. velX: (Math.random() - 0.5) * 16,
  272. velY: (Math.random() - 0.5) * 16 - 3,
  273. life: 1.0,
  274. color: colors[Math.floor(Math.random() * colors.length)],
  275. size: Math.random() * 6 + 4,
  276. rot: Math.random() * Math.PI * 2,
  277. rotVel: (Math.random() - 0.5) * 0.5
  278. });
  279. }
  280. }
  281.  
  282. function updateParticles() {
  283. for (let i = particles.length - 1; i >= 0; i--) {
  284. let p = particles[i];
  285. p.velY += gravity;
  286. p.x += p.velX;
  287. p.y += p.velY;
  288. p.rot += p.rotVel;
  289. p.life -= 0.015;
  290.  
  291. for (let j = 0; j < platforms.length; j++) {
  292. let plat = platforms[j];
  293. if (p.x > plat.x && p.x < plat.x + plat.w && p.y + p.size/2 > plat.y && p.y - p.size/2 < plat.y + plat.h) {
  294. if (p.velY > 0 && p.y < plat.y + 15) {
  295. p.y = plat.y - p.size/2;
  296. p.velY *= -0.4;
  297. p.velX *= 0.8;
  298. }
  299. }
  300. }
  301. if (p.life <= 0) particles.splice(i, 1);
  302. }
  303. }
  304.  
  305. function update() {
  306. if (gameState === 'MENU' || gameState === 'GAMEOVER') return;
  307.  
  308. updateParticles();
  309.  
  310. if (gameState === 'DYING') {
  311. deathTimer--;
  312. if (deathTimer <= 0) gameState = 'GAMEOVER';
  313. return;
  314. }
  315.  
  316. if (player.hp <= 0 && gameState === 'PLAYING') {
  317. gameState = 'DYING';
  318. deathTimer = 120;
  319. createParticles(player.x + player.width/2, player.y + player.height/2, 'player');
  320. return;
  321. }
  322.  
  323. if (player.y > 800) {
  324. gameState = 'GAMEOVER';
  325. return;
  326. }
  327.  
  328. if (player.hasBlasterEquipped) {
  329. player.facingRight = (mouseX + camera.x > player.x + player.width / 2);
  330. }
  331.  
  332. // --- ITEMS ---
  333. for (let i = items.length - 1; i >= 0; i--) {
  334. let item = items[i];
  335. item.velY += gravity;
  336. item.y += item.velY;
  337. item.grounded = false;
  338.  
  339. for (let j = 0; j < platforms.length; j++) {
  340. let dir = checkCollision(item, platforms[j]);
  341. if (dir === "b") { item.grounded = true; item.velY = 0; }
  342. }
  343.  
  344. if (player.x < item.x + item.width && player.x + player.width > item.x &&
  345. player.y < item.y + item.height && player.y + player.height > item.y) {
  346.  
  347. if (item.type === 'medkit') {
  348. player.hp = Math.min(player.maxHp, player.hp + 30);
  349. createParticles(item.x + item.width/2, item.y + item.height/2, 'heal');
  350. }
  351. items.splice(i, 1);
  352. continue;
  353. }
  354. if (item.y > 800) items.splice(i, 1);
  355. }
  356.  
  357. // --- PLAYER PHYSICS & INPUT ---
  358. if (keys['ArrowUp'] || keys['KeyW']) {
  359. if (player.grounded) {
  360. player.velY = -player.jumpPower;
  361. keys['ArrowUp'] = false; keys['KeyW'] = false;
  362. } else if (player.touchingWall) {
  363. player.velY = -player.jumpPower * 0.9;
  364. if (player.touchingWall === 'left') {
  365. player.velX = player.sprintSpeed;
  366. if (!player.hasBlasterEquipped) player.facingRight = true;
  367. } else if (player.touchingWall === 'right') {
  368. player.velX = -player.sprintSpeed;
  369. if (!player.hasBlasterEquipped) player.facingRight = false;
  370. }
  371. keys['ArrowUp'] = false; keys['KeyW'] = false;
  372. }
  373. }
  374.  
  375. let isMoving = false;
  376. if (keys['ArrowRight'] || keys['KeyD']) { isMoving = true; if(!player.hasBlasterEquipped) player.facingRight = true; }
  377. if (keys['ArrowLeft'] || keys['KeyA']) { isMoving = true; if(!player.hasBlasterEquipped) player.facingRight = false; }
  378.  
  379. if (isMoving && player.grounded) player.moveFrames++;
  380. else if (!isMoving) player.moveFrames = 0;
  381.  
  382. let currentMaxSpeed = player.speed;
  383. let currentAccel = 0.5;
  384.  
  385. if (player.moveFrames >= 90) {
  386. let movingBackward = (keys['ArrowLeft'] || keys['KeyA']) && player.facingRight ||
  387. (keys['ArrowRight'] || keys['KeyD']) && !player.facingRight;
  388. if (!movingBackward || !player.hasBlasterEquipped) { currentMaxSpeed = player.sprintSpeed; currentAccel = 0.8; }
  389. }
  390.  
  391. if (keys['ArrowRight'] || keys['KeyD']) { if (player.velX < currentMaxSpeed) player.velX += currentAccel; }
  392. if (keys['ArrowLeft'] || keys['KeyA']) { if (player.velX > -currentMaxSpeed) player.velX -= currentAccel; }
  393.  
  394. player.velX *= friction; player.velY += gravity;
  395. player.grounded = false; player.touchingWall = null;
  396.  
  397. player.x += player.velX; player.y += player.velY;
  398.  
  399. for (let i = 0; i < platforms.length; i++) {
  400. let dir = checkCollision(player, platforms[i]);
  401. if (dir === "l") { player.velX = 0; player.touchingWall = 'left'; }
  402. else if (dir === "r") { player.velX = 0; player.touchingWall = 'right'; }
  403. else if (dir === "b") { player.grounded = true; player.velY = 0; }
  404. else if (dir === "t") { player.velY *= -1; }
  405. }
  406.  
  407. // --- ENEMIES ---
  408. for (let i = enemies.length - 1; i >= 0; i--) {
  409. let e = enemies[i];
  410.  
  411. if (e.type === 'guard') {
  412. e.velY += gravity;
  413. e.y += e.velY;
  414. e.x += e.velX;
  415. e.grounded = false;
  416.  
  417. for (let j = 0; j < platforms.length; j++) {
  418. let dir = checkCollision(e, platforms[j]);
  419. if (dir === "b") { e.grounded = true; e.velY = 0; }
  420. else if (dir === "l" || dir === "r") { e.velX *= -1; }
  421. }
  422. } else if (e.type === 'drone') {
  423. e.hoverTimer++;
  424. e.y = e.startY + Math.sin(e.hoverTimer * 0.05) * 25; // Sine wave hovering
  425. e.x += e.velX;
  426. }
  427.  
  428. let distToPlayer = Math.hypot((player.x - e.x), (player.y - e.y));
  429.  
  430. if (distToPlayer < 450) {
  431. e.velX = 0;
  432. e.state = 'aiming';
  433. e.facingRight = player.x > e.x;
  434. e.aimAngle = Math.atan2((player.y + player.height/2) - (e.y + e.height/2), (player.x + player.width/2) - (e.x + e.width/2));
  435.  
  436. if (e.cooldown <= 0) {
  437. let projVel = e.type === 'drone' ? 8 : 12; // Drones shoot slightly slower projectiles
  438. projectiles.push({
  439. x: e.x + e.width/2 + Math.cos(e.aimAngle) * 20,
  440. y: e.y + e.height/2 + Math.sin(e.aimAngle) * 20,
  441. velX: Math.cos(e.aimAngle) * projVel,
  442. velY: Math.sin(e.aimAngle) * projVel,
  443. life: 80,
  444. owner: 'enemy',
  445. color: '#ff3333' // Now red theme
  446. });
  447. e.cooldown = e.type === 'drone' ? 120 : 100; // Drones fire slightly slower
  448. }
  449. } else {
  450. e.state = e.type === 'drone' ? 'patrol' : 'idle';
  451. }
  452.  
  453. if (e.cooldown > 0) e.cooldown--;
  454. if (e.y > 800) enemies.splice(i, 1);
  455. }
  456.  
  457. // --- PROJECTILES ---
  458. for (let i = projectiles.length - 1; i >= 0; i--) {
  459. let p = projectiles[i];
  460. p.x += p.velX; p.y += p.velY; p.life--;
  461.  
  462. let hitTarget = false; let hitPlatform = false;
  463.  
  464. for (let j = 0; j < platforms.length; j++) {
  465. let plat = platforms[j];
  466. if (p.x > plat.x && p.x < plat.x + plat.w && p.y > plat.y && p.y < plat.y + plat.h) { hitPlatform = true; break; }
  467. }
  468.  
  469. if (!hitPlatform) {
  470. if (p.owner === 'player') {
  471. for (let j = enemies.length - 1; j >= 0; j--) {
  472. let e = enemies[j];
  473. if (p.x > e.x && p.x < e.x + e.width && p.y > e.y && p.y < e.y + e.height) {
  474. e.hp -= 25;
  475. if (e.hp <= 0) {
  476. createParticles(e.x + e.width/2, e.y + e.height/2, e.type);
  477. if (Math.random() < 0.25) items.push(createMedKit(e.x + e.width/2 - 8, e.y + e.height/2 - 8));
  478. enemies.splice(j, 1);
  479. }
  480. hitTarget = true; break;
  481. }
  482. }
  483. } else if (p.owner === 'enemy') {
  484. if (p.x > player.x && p.x < player.x + player.width && p.y > player.y && p.y < player.y + player.height) {
  485. player.hp -= 15; hitTarget = true;
  486. }
  487. }
  488. }
  489.  
  490. if (p.life <= 0 || hitPlatform || hitTarget) projectiles.splice(i, 1);
  491. }
  492.  
  493. // Player Animation States
  494. if (player.touchingWall && player.velY > 0 && !player.grounded) {
  495. player.velY *= 0.7; player.state = 'wallSlide';
  496. if(!player.hasBlasterEquipped) player.facingRight = (player.touchingWall === 'left');
  497. } else if (!player.grounded && Math.abs(player.velY) > 0.5) { player.state = 'jumping'; }
  498. else if (player.moveFrames >= 90) { player.state = 'sprinting'; }
  499. else if (Math.abs(player.velX) > 0.5) { player.state = 'running'; }
  500. else { player.state = 'idle'; }
  501.  
  502. if (player.x + 1600 > nextChunkX) { generateRoom(); cleanRooms(); }
  503.  
  504. distanceScore = Math.max(distanceScore, Math.floor(player.x / 10));
  505. scoreBoard.innerText = `DISTANCE: ${distanceScore}m`;
  506. camera.x = Math.max(0, player.x - canvas.width / 2 + player.width / 2);
  507. }
  508.  
  509. // --- DRAWING FUNCTIONS ---
  510. function drawBackgroundWall() {
  511. ctx.strokeStyle = '#151520'; ctx.lineWidth = 2;
  512. const startX = camera.x - (camera.x % 100);
  513. for(let i = startX; i < camera.x + canvas.width; i += 100) { ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, canvas.height); ctx.stroke(); }
  514. for(let i = 0; i < canvas.height; i += 100) { ctx.beginPath(); ctx.moveTo(camera.x, i); ctx.lineTo(camera.x + canvas.width, i); ctx.stroke(); }
  515. }
  516.  
  517. function drawScenery() {
  518. const time = Date.now();
  519. backgroundProps.forEach(prop => {
  520. if (prop.x + prop.w > camera.x && prop.x < camera.x + canvas.width) {
  521. if (prop.type === 'window') {
  522. ctx.fillStyle = '#4a4e69'; ctx.fillRect(prop.x - 10, prop.y - 10, prop.w + 20, prop.h + 20);
  523. let grad = ctx.createLinearGradient(prop.x, prop.y, prop.x, prop.y + prop.h);
  524. grad.addColorStop(0, "#050510"); grad.addColorStop(1, "#1a1a3a");
  525. ctx.fillStyle = grad; ctx.fillRect(prop.x, prop.y, prop.w, prop.h);
  526. ctx.fillStyle = '#fff';
  527. for(let i=0; i<15; i++) { ctx.fillRect(prop.x + ((prop.x + i * 47) % prop.w), prop.y + ((prop.x + i * 31) % prop.h), 2, 2); }
  528. }
  529. else if (prop.type === 'terminal') {
  530. ctx.fillStyle = '#333'; ctx.fillRect(prop.x, prop.y + 20, prop.w, prop.h - 20);
  531. ctx.fillStyle = '#222'; ctx.fillRect(prop.x + 10, prop.y, prop.w - 20, 20);
  532. ctx.shadowBlur = 10; ctx.shadowColor = "#00ff41"; ctx.fillStyle = "#00ff41";
  533. ctx.fillRect(prop.x + 15, prop.y + 5, prop.w - 30, 10); ctx.shadowBlur = 0;
  534. }
  535. else if (prop.type === 'table') {
  536. ctx.fillStyle = '#666'; ctx.fillRect(prop.x + 40, prop.y + 10, 40, 5); ctx.fillRect(prop.x + 55, prop.y + 15, 10, 25);
  537. ctx.fillStyle = '#888'; ctx.fillRect(prop.x + 10, prop.y + 25, 15, 15); ctx.fillRect(prop.x + 95, prop.y + 25, 15, 15);
  538. }
  539. else if (prop.type === 'pipes') {
  540. ctx.strokeStyle = '#2b2b36'; ctx.lineWidth = 15; ctx.beginPath();
  541. ctx.moveTo(prop.x, prop.y + 30); ctx.lineTo(prop.x + prop.w, prop.y + 30);
  542. ctx.moveTo(prop.x, prop.y + 70); ctx.lineTo(prop.x + prop.w, prop.y + 70); ctx.stroke();
  543. }
  544. else if (prop.type === 'danger') {
  545. ctx.fillStyle = '#e9c46a'; ctx.fillRect(prop.x, prop.y, prop.w, prop.h); ctx.fillStyle = '#222';
  546. for(let i=0; i<prop.w; i+=30) {
  547. ctx.beginPath(); ctx.moveTo(prop.x + i, prop.y); ctx.lineTo(prop.x + i + 15, prop.y + prop.h);
  548. ctx.lineTo(prop.x + i + 5, prop.y + prop.h); ctx.lineTo(prop.x + i - 10, prop.y); ctx.fill();
  549. }
  550. }
  551. else if (prop.type === 'serverRack') {
  552. ctx.fillStyle = '#111'; ctx.fillRect(prop.x, prop.y, prop.w, prop.h);
  553. ctx.strokeStyle = '#222'; ctx.lineWidth = 4; ctx.strokeRect(prop.x, prop.y, prop.w, prop.h);
  554. for (let r = 10; r < prop.h - 15; r += 20) {
  555. ctx.fillStyle = '#0a0a0a'; ctx.fillRect(prop.x + 5, prop.y + r, prop.w - 10, 14);
  556. for (let c = 10; c < prop.w - 15; c += 15) {
  557. if (Math.sin(time / (100 + r * c)) > 0.5) {
  558. ctx.fillStyle = (r % 40 === 10) ? '#00f2ff' : '#00ff41';
  559. ctx.fillRect(prop.x + 5 + c, prop.y + r + 4, 6, 6);
  560. }
  561. }
  562. }
  563. }
  564. else if (prop.type === 'vent') {
  565. ctx.fillStyle = '#151515'; ctx.fillRect(prop.x, prop.y, prop.w, prop.h);
  566. ctx.strokeStyle = '#333'; ctx.lineWidth = 4; ctx.strokeRect(prop.x, prop.y, prop.w, prop.h);
  567. ctx.strokeStyle = '#050505'; ctx.lineWidth = 3;
  568. for(let i = 12; i < prop.h - 5; i += 12) { ctx.beginPath(); ctx.moveTo(prop.x + 5, prop.y + i); ctx.lineTo(prop.x + prop.w - 5, prop.y + i); ctx.stroke(); }
  569. }
  570. else if (prop.type === 'wires') {
  571. ctx.strokeStyle = '#111'; ctx.lineWidth = 4;
  572. ctx.beginPath(); ctx.moveTo(prop.x, prop.y); ctx.quadraticCurveTo(prop.x + prop.w/2, prop.y + prop.h, prop.x + prop.w, prop.y); ctx.stroke();
  573. ctx.strokeStyle = '#1a1a24'; ctx.lineWidth = 2;
  574. ctx.beginPath(); ctx.moveTo(prop.x + 30, prop.y); ctx.quadraticCurveTo(prop.x + prop.w/2 + 20, prop.y + prop.h + 30, prop.x + prop.w - 20, prop.y); ctx.stroke();
  575. }
  576. else if (prop.type === 'cautionStripe') {
  577. ctx.fillStyle = '#111'; ctx.fillRect(prop.x, prop.y, prop.w, prop.h); ctx.fillStyle = '#d4a34b';
  578. for(let i = 0; i < prop.w; i += 40) {
  579. ctx.beginPath(); ctx.moveTo(prop.x + i, prop.y); ctx.lineTo(prop.x + i + 20, prop.y);
  580. ctx.lineTo(prop.x + i + 10, prop.y + prop.h); ctx.lineTo(prop.x + i - 10, prop.y + prop.h); ctx.fill();
  581. }
  582. }
  583. else if (prop.type === 'crates') {
  584. ctx.fillStyle = '#3e2723'; ctx.fillRect(prop.x, prop.y, prop.w, prop.h);
  585. ctx.strokeStyle = '#271714'; ctx.lineWidth = 4; ctx.strokeRect(prop.x, prop.y, prop.w, prop.h);
  586. ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(prop.x, prop.y); ctx.lineTo(prop.x + prop.w, prop.y + prop.h); ctx.stroke();
  587. ctx.beginPath(); ctx.moveTo(prop.x + prop.w, prop.y); ctx.lineTo(prop.x, prop.y + prop.h); ctx.stroke();
  588. }
  589. }
  590. });
  591. }
  592.  
  593. function drawItems() {
  594. const time = Date.now() / 200;
  595. items.forEach(item => {
  596. if (item.x + item.width > camera.x && item.x < camera.x + canvas.width) {
  597. const hoverOffset = item.grounded ? Math.sin(time + item.x) * 3 : 0;
  598. ctx.save(); ctx.translate(item.x, item.y + hoverOffset);
  599. if (item.type === 'medkit') {
  600. ctx.fillStyle = '#eeeeee'; ctx.fillRect(0, 0, item.width, item.height);
  601. ctx.strokeStyle = '#999999'; ctx.lineWidth = 1; ctx.strokeRect(0, 0, item.width, item.height);
  602. ctx.fillStyle = '#e63946'; ctx.fillRect(item.width/2 - 2, 3, 4, item.height - 6); ctx.fillRect(3, item.height/2 - 2, item.width - 6, 4);
  603. ctx.shadowBlur = 15; ctx.shadowColor = '#00ff41'; ctx.fillStyle = 'rgba(0, 255, 65, 0.15)';
  604. ctx.fillRect(-5, -5, item.width + 10, item.height + 10);
  605. }
  606. ctx.restore();
  607. }
  608. });
  609. }
  610.  
  611. function drawArmoredLimb(ctx, x, y, angle, length, suitColor, armorColor, isBlasterArm = false, glowColor = "#00f2ff") {
  612. ctx.save(); ctx.translate(x, y); ctx.rotate(angle);
  613. ctx.strokeStyle = suitColor; ctx.lineWidth = 8; ctx.lineCap = 'round';
  614. ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(0, length); ctx.stroke();
  615. ctx.fillStyle = armorColor; ctx.fillRect(-4, 4, 8, length - 8);
  616. if (isBlasterArm) {
  617. ctx.fillStyle = "#111"; ctx.fillRect(-5, length - 2, 10, 14);
  618. ctx.fillStyle = "#4a4e69"; ctx.fillRect(-6, length, 2, 10);
  619. ctx.fillStyle = "#222"; ctx.fillRect(-3, length + 12, 6, 8);
  620. ctx.fillStyle = "#333"; ctx.fillRect(-4, length + 20, 8, 5);
  621. ctx.shadowBlur = 10; ctx.shadowColor = glowColor; ctx.fillStyle = glowColor; ctx.fillRect(-2, length + 21, 4, 4); ctx.shadowBlur = 0;
  622. }
  623. ctx.restore();
  624. }
  625.  
  626. function drawAgent() {
  627. ctx.save();
  628. const pCenterX = player.x + player.width / 2; const pCenterY = player.y + player.height / 2;
  629. ctx.translate(pCenterX, pCenterY); if (!player.facingRight) ctx.scale(-1, 1);
  630.  
  631. const time = Date.now() / 1000;
  632. let legAngle1 = 0, legAngle2 = 0, armAngle1 = 0, armAngle2 = 0, torsoOffset = 0, bodyTilt = 0;
  633.  
  634. if (player.state === 'sprinting') {
  635. const sprintSpeed = 13;
  636. legAngle1 = Math.sin(time * sprintSpeed) * 1.3; legAngle2 = Math.sin(time * sprintSpeed + Math.PI) * 1.3;
  637. armAngle1 = Math.sin(time * sprintSpeed + Math.PI) * 1.3; armAngle2 = Math.sin(time * sprintSpeed) * 1.3;
  638. torsoOffset = Math.abs(Math.sin(time * sprintSpeed)) * 4 + 2; bodyTilt = 0.35;
  639. } else if (player.state === 'running') {
  640. const runSpeed = 8;
  641. legAngle1 = Math.sin(time * runSpeed) * 0.8; legAngle2 = Math.sin(time * runSpeed + Math.PI) * 0.8;
  642. armAngle1 = Math.sin(time * runSpeed + Math.PI) * 0.8; armAngle2 = Math.sin(time * runSpeed) * 0.8;
  643. torsoOffset = Math.abs(Math.sin(time * runSpeed)) * 3;
  644. } else if (player.state === 'jumping') { legAngle1 = -0.6; legAngle2 = 0.4; armAngle1 = 1.5; armAngle2 = 2.2;
  645. } else if (player.state === 'wallSlide') { armAngle1 = -1.5; armAngle2 = -1.5; legAngle1 = 0.5; legAngle2 = -0.5;
  646. } else { torsoOffset = Math.sin(time * 2) * 2; armAngle1 = 0.1; armAngle2 = -0.1; }
  647.  
  648. if (player.hasBlasterEquipped) {
  649. bodyTilt = 0;
  650. const targetX = mouseX + camera.x;
  651. let rawAim = Math.atan2(mouseY - pCenterY, targetX - pCenterX);
  652. if (!player.facingRight) rawAim = Math.atan2(mouseY - pCenterY, -(targetX - pCenterX));
  653. armAngle1 = rawAim - Math.PI / 2;
  654. }
  655.  
  656. ctx.rotate(bodyTilt);
  657.  
  658. if ((player.state === 'jumping' || player.state === 'wallSlide') && player.velY < 0) {
  659. ctx.shadowBlur = 15; ctx.shadowColor = "#00f2ff"; ctx.fillStyle = "#00f2ff";
  660. ctx.beginPath(); ctx.moveTo(-10, torsoOffset - 5); ctx.lineTo(-20, torsoOffset + 15); ctx.lineTo(-5, torsoOffset + 15); ctx.fill(); ctx.shadowBlur = 0;
  661. } else if (player.state === 'sprinting' && player.grounded) {
  662. ctx.fillStyle = "rgba(0, 242, 255, 0.5)"; ctx.beginPath(); ctx.moveTo(-14, torsoOffset); ctx.lineTo(-30, torsoOffset + 10); ctx.lineTo(-14, torsoOffset + 15); ctx.fill();
  663. }
  664.  
  665. drawArmoredLimb(ctx, 0, torsoOffset - 6, armAngle2, 16, '#222', '#333');
  666. drawArmoredLimb(ctx, 0, torsoOffset + 8, legAngle2, 18, '#222', '#333');
  667.  
  668. ctx.fillStyle = '#444'; ctx.fillRect(-14, torsoOffset - 12, 8, 20); ctx.fillStyle = '#00f2ff'; ctx.fillRect(-15, torsoOffset - 8, 2, 8);
  669. ctx.fillStyle = "#222"; ctx.beginPath(); ctx.roundRect ? ctx.roundRect(-8, torsoOffset - 12, 16, 24, 4) : ctx.fillRect(-8, torsoOffset - 12, 16, 24); ctx.fill();
  670. ctx.fillStyle = "#e9c46a"; ctx.fillRect(-5, torsoOffset - 8, 12, 10);
  671. ctx.fillStyle = "#4a4e69"; ctx.fillRect(-9, torsoOffset + 8, 18, 5);
  672.  
  673. ctx.fillStyle = '#111'; ctx.beginPath(); ctx.arc(0, torsoOffset - 18, 11, 0, Math.PI * 2); ctx.fill();
  674. ctx.shadowBlur = 8; ctx.shadowColor = '#00f2ff'; ctx.fillStyle = '#00f2ff';
  675. ctx.beginPath(); ctx.roundRect ? ctx.roundRect(0, torsoOffset - 22, 10, 7, 2) : ctx.fillRect(0, torsoOffset - 22, 10, 7); ctx.fill(); ctx.shadowBlur = 0;
  676.  
  677. drawArmoredLimb(ctx, 0, torsoOffset - 6, armAngle1, 16, '#333', '#5c5c6b', player.hasBlasterEquipped, "#00f2ff");
  678. drawArmoredLimb(ctx, 0, torsoOffset + 8, legAngle1, 18, '#333', '#5c5c6b');
  679.  
  680. ctx.restore();
  681. }
  682.  
  683. function drawEnemies() {
  684. const time = Date.now() / 1000;
  685. enemies.forEach(e => {
  686. ctx.save();
  687. ctx.translate(e.x + e.width / 2, e.y + e.height / 2);
  688.  
  689. if (e.type === 'guard') {
  690. if (!e.facingRight) ctx.scale(-1, 1);
  691. let armAngle = 0, torsoOffset = Math.sin(time * 2 + e.x) * 2;
  692.  
  693. if (e.state === 'aiming') {
  694. let rawAim = e.aimAngle;
  695. if (!e.facingRight) rawAim = Math.atan2(Math.sin(e.aimAngle), -Math.cos(e.aimAngle));
  696. armAngle = rawAim - Math.PI / 2;
  697. } else { armAngle = 0.2; }
  698.  
  699. drawArmoredLimb(ctx, 0, torsoOffset - 6, -0.2, 16, '#111', '#222');
  700. drawArmoredLimb(ctx, 0, torsoOffset + 8, 0, 18, '#111', '#222');
  701.  
  702. ctx.fillStyle = "#222"; ctx.beginPath(); ctx.roundRect ? ctx.roundRect(-8, torsoOffset - 12, 16, 24, 4) : ctx.fillRect(-8, torsoOffset - 12, 16, 24); ctx.fill();
  703. ctx.fillStyle = "#aa0000"; ctx.fillRect(-5, torsoOffset - 8, 12, 10);
  704. ctx.fillStyle = '#111'; ctx.beginPath(); ctx.arc(0, torsoOffset - 18, 11, 0, Math.PI * 2); ctx.fill();
  705.  
  706. ctx.shadowBlur = 8; ctx.shadowColor = '#ff3333'; ctx.fillStyle = '#ff3333';
  707. ctx.beginPath(); ctx.roundRect ? ctx.roundRect(0, torsoOffset - 22, 10, 7, 2) : ctx.fillRect(0, torsoOffset - 22, 10, 7); ctx.fill(); ctx.shadowBlur = 0;
  708.  
  709. drawArmoredLimb(ctx, 0, torsoOffset - 6, armAngle, 16, '#222', '#444', true, "#ff3333");
  710. drawArmoredLimb(ctx, 0, torsoOffset + 8, 0.2, 18, '#222', '#444');
  711.  
  712. } else if (e.type === 'drone') {
  713. // Drone Body
  714. ctx.fillStyle = '#2b2d42';
  715. ctx.beginPath();
  716. ctx.moveTo(-12, 0); ctx.lineTo(0, -10); ctx.lineTo(12, 0); ctx.lineTo(8, 10); ctx.lineTo(-8, 10);
  717. ctx.fill();
  718.  
  719. // Drone details
  720. ctx.strokeStyle = '#111'; ctx.lineWidth = 2; ctx.stroke();
  721.  
  722. // Propeller Mount
  723. ctx.fillStyle = '#222';
  724. ctx.fillRect(-2, -14, 4, 4);
  725.  
  726. // Spinning Propeller Animation
  727. ctx.save();
  728. ctx.translate(0, -14);
  729. ctx.rotate(time * 25); // Fast spin
  730. ctx.fillStyle = '#888';
  731. ctx.fillRect(-14, -2, 28, 4); // Main blade
  732. ctx.fillStyle = '#ccc';
  733. ctx.fillRect(-14, -1, 28, 2); // Highlight
  734. ctx.restore();
  735.  
  736. // Drone Eye (Now Red)
  737. let lookOffsetX = 0;
  738. let lookOffsetY = 0;
  739. if (e.state === 'aiming') {
  740. lookOffsetX = Math.cos(e.aimAngle) * 3;
  741. lookOffsetY = Math.sin(e.aimAngle) * 3;
  742. }
  743.  
  744. ctx.fillStyle = '#111';
  745. ctx.beginPath(); ctx.arc(0, 0, 6, 0, Math.PI * 2); ctx.fill();
  746.  
  747. ctx.shadowBlur = 10; ctx.shadowColor = '#ff3333'; ctx.fillStyle = '#ff3333';
  748. ctx.beginPath(); ctx.arc(lookOffsetX, lookOffsetY, 3, 0, Math.PI * 2); ctx.fill();
  749. ctx.shadowBlur = 0;
  750. }
  751.  
  752. ctx.restore();
  753.  
  754. // HP Bar
  755. let hpColor = '#ff3333'; // Now red to match the guard
  756. ctx.fillStyle = '#333'; ctx.fillRect(e.x + e.width/2 - 20, e.y - 12, 40, 6);
  757. ctx.fillStyle = hpColor; ctx.fillRect(e.x + e.width/2 - 20, e.y - 12, 40 * (e.hp / e.maxHp), 6);
  758. });
  759. }
  760.  
  761. function drawParticles() {
  762. particles.forEach(p => {
  763. ctx.save(); ctx.translate(p.x, p.y); ctx.rotate(p.rot); ctx.globalAlpha = Math.max(0, p.life);
  764. ctx.fillStyle = p.color; ctx.fillRect(-p.size/2, -p.size/2, p.size, p.size); ctx.restore();
  765. });
  766. }
  767.  
  768. function drawUI() {
  769. if (gameState !== 'PLAYING' && gameState !== 'DYING') return;
  770. ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(20, 20, 204, 24);
  771. ctx.fillStyle = '#e63946'; ctx.fillRect(22, 22, 200, 20);
  772. ctx.fillStyle = '#00f2ff'; ctx.fillRect(22, 22, 200 * (Math.max(0, player.hp) / player.maxHp), 20);
  773. ctx.fillStyle = '#fff'; ctx.font = "14px Courier"; ctx.textAlign = "left";
  774. ctx.fillText(`INTEGRITY: ${Math.max(0, player.hp)}%`, 25, 37);
  775. }
  776.  
  777. function draw() {
  778. ctx.clearRect(0, 0, canvas.width, canvas.height);
  779.  
  780. if (gameState === 'MENU') {
  781. ctx.fillStyle = "#2a9d8f"; ctx.font = "bold 70px Courier New"; ctx.textAlign = "center"; ctx.fillText("AGENT_007", canvas.width/2, 280);
  782. ctx.font = "20px Courier New"; ctx.fillStyle = "white"; ctx.fillText("INTERIOR ESCAPE", canvas.width/2, 320);
  783. if (Math.floor(Date.now() / 500) % 2) ctx.fillText("[ PRESS ENTER TO START ]", canvas.width/2, 400); return;
  784. }
  785.  
  786. ctx.save(); ctx.translate(-camera.x, 0);
  787.  
  788. drawBackgroundWall(); drawScenery();
  789.  
  790. platforms.forEach(p => {
  791. if (p.x + p.w > camera.x && p.x < camera.x + canvas.width) {
  792. ctx.fillStyle = "#444"; ctx.fillRect(p.x, p.y, p.w, p.h);
  793. ctx.fillStyle = "#666"; ctx.fillRect(p.x, p.y, p.w, 4);
  794. ctx.strokeStyle = "#1a1a24"; ctx.lineWidth = 2; ctx.strokeRect(p.x, p.y, p.w, p.h);
  795. }
  796. });
  797.  
  798. projectiles.forEach(p => {
  799. ctx.shadowBlur = 10; ctx.lineCap = "round"; ctx.lineWidth = 3;
  800. ctx.shadowColor = p.color || "#00f2ff"; ctx.strokeStyle = p.color || "#00f2ff";
  801. ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(p.x - p.velX * 1.5, p.y - p.velY * 1.5); ctx.stroke();
  802. });
  803. ctx.shadowBlur = 0;
  804.  
  805. if (gameState === 'PLAYING' || gameState === 'DYING' || gameState === 'GAMEOVER') {
  806. drawItems(); drawEnemies();
  807. if (gameState === 'PLAYING') drawAgent();
  808. drawParticles();
  809. }
  810. ctx.restore(); drawUI();
  811.  
  812. if (gameState === 'GAMEOVER') {
  813. ctx.fillStyle = "rgba(0,0,0,0.85)"; ctx.fillRect(0,0,800,600);
  814. ctx.fillStyle = "#e63946"; ctx.font = "bold 50px Courier"; ctx.textAlign = "center"; ctx.fillText("AGENT LOST", 400, 250);
  815. ctx.fillStyle = "#fff"; ctx.font = "20px Courier"; ctx.fillText(`FINAL DISTANCE: ${distanceScore}m`, 400, 310);
  816. if (Math.floor(Date.now() / 500) % 2) { ctx.fillStyle = "#2a9d8f"; ctx.fillText("[ PRESS ENTER TO RESTART ]", 400, 400); }
  817. }
  818. }
  819.  
  820. function loop() { update(); draw(); requestAnimationFrame(loop); }
  821. loop();
  822. </script>
  823. </body>
  824. </html>
RAW Paste Data Copied