library CB-Library; library CB-Library.FPS; library CB-Library.Graphics; library CB-Library.Threaded; library CB-UI; library CB-Render; library CB-Math; library CB-Time; fn draw_crosshair() { draw_line(466, 270, 482, 270, 0xEFFFF8, 2); draw_line(498, 270, 514, 270, 0xEFFFF8, 2); draw_line(480, 256, 480, 266, 0xEFFFF8, 2); draw_line(480, 274, 480, 284, 0xEFFFF8, 2); draw_circle(476, 266, 8, 0x56F0A6); } fn draw_hud(i256 health, i256 ammo, i256 score, string state) { CB-Render.panel(12, 12, 254, 84, 0x061018, 0x65CBD2, 2); draw_text(24, 22, "CLOSEDBIT FPS LAB", 0xCEE55C, 16); draw_text(24, 46, "Health", 0xFFFFFF, 12); CB-Render.bar(78, 47, 150, 12, health, 100, 0x56F0A6, 0x23343A); draw_text(24, 66, "Ammo " + ammo + " / 36", 0xFFFFFF, 12); draw_text(150, 66, "Score " + score, 0xFFFFFF, 12); draw_text(710, 18, state, 0xCEE55C, 14); } fn draw_arena(u256 frame) { CB-Render.scene_begin(0x061018); CB-Render.starfield(500, frame, 0xCDEB69); CB-Render.grid(0, 390, 28, 5, 54, 34, 0x23694E); draw_line(0, 390, 960, 390, 0x65CBD2, 2); draw_line(64, 500, 64, 308, 0xFFFFFF, 4); draw_line(64, 500, 920, 500, 0xFFFFFF, 4); draw_line(920, 500, 920, 96, 0xFFFFFF, 4); draw_line(920, 96, 760, 96, 0xFFFFFF, 4); draw_line(760, 96, 760, 172, 0x65CBD2, 4); draw_line(108, 234, 804, 234, 0x65CBD2, 2); draw_text(24, 522, "Grid floor is rendered by CB-Render.grid at runtime, not only in the editor.", 0x9FE7DC, 12); } fn draw_enemy(f64 ex, f64 ez, i256 hp, f64 px, f64 pz, f64 yaw, f64 pitch, u256 color, string name) { if (hp <= 0) { return; } f64 rad = yaw * 0.017453292519943; f64 s = CB-Math.sin(rad); f64 c = CB-Math.cos(rad); f64 dx = ex - px; f64 dz = ez - pz; f64 side = dx * c - dz * s; f64 depth = dx * s + dz * c; if (depth <= 0.35) { return; } if (depth > 28.0) { return; } f64 scale = 180.0 / depth; f64 sx = 480.0 + side * scale * 1.65; f64 sy = 300.0 + pitch * 2.0 - scale * 0.15; f64 w = CB-Math.clamp(scale * 0.58, 12, 82); f64 h = CB-Math.clamp(scale * 0.92, 20, 118); draw_rect(sx - w / 2.0, sy - h, w, h, color); draw_rect(sx - w / 4.0, sy - h - 12.0, w / 2.0, 12, 0xFFD36A); draw_line(sx - w / 2.0, sy - h - 18.0, sx + w / 2.0, sy - h - 18.0, 0x26343A, 4); draw_line(sx - w / 2.0, sy - h - 18.0, sx - w / 2.0 + w * hp / 60.0, sy - h - 18.0, 0x56F0A6, 4); draw_text(sx - w / 2.0, sy - h - 36.0, name + " HP " + hp, 0xFFFFFF, 11); } fn enemy_in_crosshair(f64 ex, f64 ez, i256 hp, f64 px, f64 pz, f64 yaw, f64 pitch) -> bool { if (hp <= 0) { return false; } f64 rad = yaw * 0.017453292519943; f64 s = CB-Math.sin(rad); f64 c = CB-Math.cos(rad); f64 dx = ex - px; f64 dz = ez - pz; f64 side = dx * c - dz * s; f64 depth = dx * s + dz * c; if (depth <= 0.5) { return false; } if (depth > 22.0) { return false; } f64 aim = CB-Math.abs(side) / depth; if (aim < 0.085 && pitch > -30.0 && pitch < 35.0) { return true; } return false; } fn enemy_pressure(f64 ex, f64 ez, i256 hp, f64 px, f64 pz) -> bool { if (hp <= 0) { return false; } f64 dx = ex - px; f64 dz = ez - pz; f64 range2 = dx * dx + dz * dz; return range2 < 2.25; } fn main() { init_window(960, 540, "ClosedBit FPS Lab - First Person Shooter"); set_background_color(0x061018); f64 px = 0.0; f64 pz = 0.0; f64 yaw = 0.0; f64 pitch = 0.0; i256 health = 100; i256 ammo = 36; i256 score = 0; u256 frame = 0; f64 shotCooldown = 0.0; f64 hurtCooldown = 0.0; u256 mode = 0; u256 lastMs = time.nowMs(); f64 e1x = -1.6; f64 e1z = 8.0; i256 e1hp = 60; f64 e2x = 2.2; f64 e2z = 12.0; i256 e2hp = 60; f64 e3x = -3.2; f64 e3z = 17.0; i256 e3hp = 60; f64 e4x = 3.8; f64 e4z = 22.0; i256 e4hp = 60; while (!should_close()) { handle_events(); u256 nowMs = time.nowMs(); u256 elapsedMs = nowMs - lastMs; lastMs = nowMs; if (elapsedMs == 0) { elapsedMs = 1; } f64 dt = elapsedMs / 16.0; if (dt > 3.0) { dt = 3.0; } if (shotCooldown > 0.0) { shotCooldown -= dt; } if (hurtCooldown > 0.0) { hurtCooldown -= dt; } if (mode == 0) { lock_cursor(false); draw_arena(frame); CB-Render.panel(292, 122, 376, 232, 0x061018, 0x65CBD2, 2); draw_text(330, 150, "CLOSEDBIT FIRST PERSON SHOOTER", 0xCEE55C, 18); draw_text(330, 192, "Enter or left click: start", 0xFFFFFF, 15); draw_text(330, 220, "WASD: move Mouse: look", 0xFFFFFF, 14); draw_text(330, 246, "Left click: rapid fire R: reload", 0xFFFFFF, 14); draw_text(330, 272, "Esc: pause menu", 0xFFFFFF, 14); draw_text(330, 308, "This is a real .cbp runtime app.", 0x56F0A6, 14); if (is_key_pressed("Enter") || is_mouse_down()) { mode = 1; } } else if (mode == 2) { lock_cursor(false); draw_arena(frame); CB-Render.panel(318, 142, 322, 178, 0x061018, 0xFFD36A, 2); draw_text(390, 172, "PAUSED", 0xFFD36A, 28); draw_text(350, 222, "Enter: resume", 0xFFFFFF, 15); draw_text(350, 250, "R: restart wave", 0xFFFFFF, 15); draw_text(350, 278, "Esc: back to game", 0xFFFFFF, 15); if (is_key_pressed("Enter") || is_key_pressed("Escape")) { mode = 1; } if (is_key_pressed("R")) { health = 100; ammo = 36; e1hp = 60; e2hp = 60; e3hp = 60; e4hp = 60; px = 0.0; pz = 0.0; yaw = 0.0; pitch = 0.0; } } else if (mode == 3) { lock_cursor(false); draw_arena(frame); CB-Render.panel(320, 142, 320, 170, 0x061018, 0xFF6B5B, 2); draw_text(390, 172, "ARENA CLEAR", 0xCEE55C, 24); if (health <= 0) { draw_text(406, 172, "YOU LOST", 0xFF6B5B, 24); } draw_text(354, 224, "Score " + score, 0xFFFFFF, 16); draw_text(354, 254, "Press R to restart", 0xFFFFFF, 14); if (is_key_pressed("R") || is_mouse_down()) { mode = 0; health = 100; ammo = 36; score = 0; e1hp = 60; e2hp = 60; e3hp = 60; e4hp = 60; px = 0.0; pz = 0.0; yaw = 0.0; pitch = 0.0; } } else { lock_cursor(true); if (is_key_pressed("Escape")) { mode = 2; } yaw += mouse_dx() * 0.12; pitch += mouse_dy() * 0.10; pitch = CB-Math.clamp(pitch, -32.0, 34.0); f64 rad = yaw * 0.017453292519943; f64 forwardX = CB-Math.sin(rad); f64 forwardZ = CB-Math.cos(rad); f64 rightX = CB-Math.cos(rad); f64 rightZ = -CB-Math.sin(rad); f64 move = 0.070 * dt; if (is_key_down("W")) { px += forwardX * move; pz += forwardZ * move; } if (is_key_down("S")) { px -= forwardX * move; pz -= forwardZ * move; } if (is_key_down("D")) { px += rightX * move; pz += rightZ * move; } if (is_key_down("A")) { px -= rightX * move; pz -= rightZ * move; } px = CB-Math.clamp(px, -5.0, 5.0); pz = CB-Math.clamp(pz, -2.0, 24.0); if (is_key_pressed("R")) { ammo = 36; } if (is_mouse_down() && shotCooldown <= 0.0 && ammo > 0) { shotCooldown = 3.0; ammo--; if (enemy_in_crosshair(e1x, e1z, e1hp, px, pz, yaw, pitch)) { e1hp -= 20; score += 10; } if (enemy_in_crosshair(e2x, e2z, e2hp, px, pz, yaw, pitch)) { e2hp -= 20; score += 10; } if (enemy_in_crosshair(e3x, e3z, e3hp, px, pz, yaw, pitch)) { e3hp -= 20; score += 10; } if (enemy_in_crosshair(e4x, e4z, e4hp, px, pz, yaw, pitch)) { e4hp -= 20; score += 10; } } if (e1hp > 0) { e1z -= 0.012 * dt; } if (e2hp > 0) { e2z -= 0.010 * dt; } if (e3hp > 0) { e3z -= 0.008 * dt; } if (e4hp > 0) { e4z -= 0.006 * dt; } if (hurtCooldown <= 0.0 && (enemy_pressure(e1x, e1z, e1hp, px, pz) || enemy_pressure(e2x, e2z, e2hp, px, pz) || enemy_pressure(e3x, e3z, e3hp, px, pz) || enemy_pressure(e4x, e4z, e4hp, px, pz))) { health -= 8; hurtCooldown = 18.0; } draw_arena(frame); draw_enemy(e4x, e4z, e4hp, px, pz, yaw, pitch, 0xFF6B5B, "RAIDER"); draw_enemy(e3x, e3z, e3hp, px, pz, yaw, pitch, 0xFF6BCB, "BLITZ"); draw_enemy(e2x, e2z, e2hp, px, pz, yaw, pitch, 0xFFD36A, "SENTRY"); draw_enemy(e1x, e1z, e1hp, px, pz, yaw, pitch, 0x65EFE0, "DRONE"); draw_crosshair(); draw_hud(health, ammo, score, "Mouse locked | Esc pauses"); if (shotCooldown > 0.0) { draw_line(480, 270, 480, 156, 0xCEE55C, 2); } if (ammo <= 0) { draw_text(412, 320, "RELOAD", 0xFFD36A, 24); } if (health <= 0) { mode = 3; } if (e1hp <= 0 && e2hp <= 0 && e3hp <= 0 && e4hp <= 0) { mode = 3; } } swap_buffers(); frame++; } lock_cursor(false); cleanup(); }