DOOM
Qwen 3.6 27B Fable Fusion 711 Uncensored Heretic NM DAU NEO MAX MTP GGUF · typescript

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 48,427 bytes · SHA-256 ae95bae62585
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dungeon Doom - Retro FPS</title>
<style>
/* ── css: layout ── */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: 'Courier New', monospace;
}
#game-container {
position: relative;
width: 960px;
height: 640px;
border: 3px solid #333;
image-rendering: pixelated;
}
#game-canvas {
width: 100%;
height: 100%;
display: block;
background: #000;
}
/* ── css: hud ── */
#hud {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 70px;
background: linear-gradient(to top, rgba(0,0,0,0.9), rgba(0,0,0,0.6));
border-top: 2px solid #444;
display: flex;
align-items: center;
justify-content: space-around;
padding: 0 20px;
color: #fff;
font-size: 14px;
}
#health-bar {
display: flex;
flex-direction: column;
gap: 3px;
min-width: 120px;
}
#health-bar span {
font-size: 11px;
color: #aaa;
text-transform: uppercase;
}
#health-fill {
height: 16px;
width: 100%;
background: #4caf50;
border: 1px solid #333;
transition: width 0.2s, background-color 0.3s;
}
#weapon-display {
font-size: 16px;
font-weight: bold;
color: #ff9800;
text-shadow: 0 0 10px rgba(255,152,0,0.5);
}
#ammo-display {
font-size: 16px;
color: #fff;
}
#score-display {
font-size: 16px;
color: #ff4444;
}
/* ── css: overlay ── */
#message-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
pointer-events: none;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 80px;
}
#message-text {
font-size: 16px;
font-weight: bold;
color: #ff0;
text-shadow: 0 0 10px rgba(255,255,0,0.8), 2px 2px 0 #000;
text-align: center;
max-width: 600px;
}
/* Controls hint */
#controls-hint {
position: absolute;
top: 10px;
left: 10px;
color: rgba(255,255,255,0.4);
font-size: 10px;
line-height: 1.4;
pointer-events: none;
}
</style>
</head>
<body>
<div id="game-container"><div id="controls-hint">
WASD/Arrows: Move & Turn<br>
Space: Shoot<br>
1-3: Switch Weapons<br>
R: Respawn<br>
Click to lock mouse
</div>
<canvas id="game-canvas"></canvas>
<div id="hud">
<div id="health-bar">
<span>HEALTH</span>
<div id="health-fill"></div>
</div>
<div id="weapon-display">PISTOL</div>
<div id="ammo-display">Ammo: 50</div>
<div id="score-display">Kills: 0</div>
</div>
<div id="message-overlay">
<div id="message-text"></div>
</div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./level":"src/level.ts","./renderer":"src/renderer.ts","./game":"src/game.ts","./types":"src/types.ts"},"src/level.ts":{"./types":"src/types.ts"},"src/renderer.ts":{"./types":"src/types.ts","./level":"src/level.ts"},"src/game.ts":{"./types":"src/types.ts","./level":"src/level.ts"}};
function __require(id) {
if (__cache[id]) return __cache[id].exports;
var module = __cache[id] = { exports: {} };
var factory = __mods[id];
if (!factory) throw new Error("Module not found: " + id);
factory(module.exports, function (spec) {
var target = (__map[id] && __map[id][spec]) || spec;
return __require(target);
}, module);
return module.exports;
}
// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const level_1 = require("./level");
const renderer_1 = require("./renderer");
const game_1 = require("./game");
let canvas = document.getElementById('game-canvas');
let ctx = canvas.getContext('2d', { alpha: false });
// Use low internal resolution for retro feel
const GAME_WIDTH = 320;
const GAME_HEIGHT = 240;
canvas.width = GAME_WIDTH;
canvas.height = GAME_HEIGHT;
let gameState = (0, level_1.createInitialState)();
let input = {
forward: false,
backward: false,
turnLeft: false,
turnRight: false,
shooting: false,
weapon1: false,
weapon2: false,
weapon3: false,
respawn: false,
};
let weaponStates = {
0: (0, game_1.createWeaponState)(),
};
let frameCount = 0;
let lastShootState = false;
let keysJustPressed = new Set();
// Input handling
window.addEventListener('keydown', (e) => {
keysJustPressed.add(e.key.toLowerCase());
switch (e.key.toLowerCase()) {
case 'w':
case 'arrowup':
input.forward = true;
break;
case 's':
case 'arrowdown':
input.backward = true;
break;
case 'a':
case 'arrowleft':
input.turnLeft = true;
break;
case 'd':
case 'arrowright':
input.turnRight = true;
break;
case ' ':
input.shooting = true;
break;
case '1':
input.weapon1 = true;
break;
case '2':
input.weapon2 = true;
break;
case '3':
input.weapon3 = true;
break;
case 'r':
input.respawn = true;
break;
}
});
window.addEventListener('keyup', (e) => {
switch (e.key.toLowerCase()) {
case 'w':
case 'arrowup':
input.forward = false;
break;
case 's':
case 'arrowdown':
input.backward = false;
break;
case 'a':
case 'arrowleft':
input.turnLeft = false;
break;
case 'd':
case 'arrowright':
input.turnRight = false;
break;
case ' ':
input.shooting = false;
lastShootState = false;
break;
case '1':
input.weapon1 = false;
break;
case '2':
input.weapon2 = false;
break;
case '3':
input.weapon3 = false;
break;
case 'r':
input.respawn = false;
break;
}
});
// Mouse lock for turning
canvas.addEventListener('click', () => {
canvas.requestPointerLock?.();
});
document.addEventListener('pointerlockchange', () => {
// Pointer lock state handled below
});
document.addEventListener('mousemove', (e) => {
if (document.pointerLockElement === canvas) {
gameState.player.angle += e.movementX * 0.002;
}
});
// Update HUD
function updateHUD() {
const player = gameState.player;
// Health bar
const healthFill = document.getElementById('health-fill');
if (healthFill) {
const healthPercent = Math.max(0, (player.health / player.maxHealth) * 100);
healthFill.style.width = healthPercent + '%';
if (healthPercent > 60) {
healthFill.style.backgroundColor = '#4caf50';
}
else if (healthPercent > 30) {
healthFill.style.backgroundColor = '#ff9800';
}
else {
healthFill.style.backgroundColor = '#f44336';
}
}
// Weapon display
const weaponDisplay = document.getElementById('weapon-display');
if (weaponDisplay && player.weapons[player.currentWeapon]) {
const weapon = player.weapons[player.currentWeapon];
const weaponName = { 0: 'PISTOL', 1: 'SHOTGUN', 2: 'MACHINE GUN' }[weapon.type] || 'PISTOL';
weaponDisplay.textContent = weaponName;
}
// Ammo display
const ammoDisplay = document.getElementById('ammo-display');
if (ammoDisplay && player.weapons[player.currentWeapon]) {
const weapon = player.weapons[player.currentWeapon];
ammoDisplay.textContent = `Ammo: ${weapon.ammo}`;
}
// Score display
const scoreDisplay = document.getElementById('score-display');
if (scoreDisplay) {
scoreDisplay.textContent = `Kills: ${player.kills}`;
}
}
// Main game loop
function gameLoop(timestamp) {
// Process
gameState.frameCount++;
// Weapon state cleanup for removed weapons
gameState.player.weapons.forEach((w, i) => {
if (!weaponStates[i]) {
weaponStates[i] = (0, game_1.createWeaponState)();
}
});
(0, game_1.processInput)(input, gameState, 16);
(0, game_1.updateWeapons)(input, gameState, weaponStates);
(0, game_1.updateEnemies)(gameState);
(0, game_1.updatePickups)(gameState);
(0, game_1.updateMessages)(gameState);
// Get current weapon state
const currentWs = weaponStates[gameState.player.currentWeapon] || (0, game_1.createWeaponState)();
// Render
(0, renderer_1.renderGame)(ctx, gameState, GAME_WIDTH, GAME_HEIGHT, frameCount, currentWs.muzzleFlash, currentWs.flashTimer);
updateHUD();
frameCount++;
requestAnimationFrame(gameLoop);
}
// Start
requestAnimationFrame(gameLoop);
};
// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ENEMY_CONFIG = exports.PICKUP_CONFIG = exports.WEAPON_CONFIG = exports.PickupType = exports.EnemyType = exports.WeaponType = void 0;
var WeaponType;
(function (WeaponType) {
WeaponType[WeaponType["Pistol"] = 0] = "Pistol";
WeaponType[WeaponType["Shotgun"] = 1] = "Shotgun";
WeaponType[WeaponType["MachineGun"] = 2] = "MachineGun";
})(WeaponType || (exports.WeaponType = WeaponType = {}));
var EnemyType;
(function (EnemyType) {
EnemyType[EnemyType["Grunt"] = 0] = "Grunt";
EnemyType[EnemyType["Soldier"] = 1] = "Soldier";
EnemyType[EnemyType["Brute"] = 2] = "Brute";
})(EnemyType || (exports.EnemyType = EnemyType = {}));
var PickupType;
(function (PickupType) {
PickupType[PickupType["HealthSmall"] = 0] = "HealthSmall";
PickupType[PickupType["HealthLarge"] = 1] = "HealthLarge";
PickupType[PickupType["AmmoPistol"] = 2] = "AmmoPistol";
PickupType[PickupType["AmmoShotgun"] = 3] = "AmmoShotgun";
PickupType[PickupType["AmmoMachineGun"] = 4] = "AmmoMachineGun";
PickupType[PickupType["WeaponShotgun"] = 5] = "WeaponShotgun";
PickupType[PickupType["WeaponMachineGun"] = 6] = "WeaponMachineGun";
})(PickupType || (exports.PickupType = PickupType = {}));
exports.WEAPON_CONFIG = {
[WeaponType.Pistol]: { name: 'PISTOL', damage: 15, fireRate: 20, spread: 0.03, pellets: 1, reloadTime: 15, autoFire: false },
[WeaponType.Shotgun]: { name: 'SHOTGUN', damage: 8, fireRate: 35, spread: 0.12, pellets: 8, reloadTime: 30, autoFire: false },
[WeaponType.MachineGun]: { name: 'MACHINE GUN', damage: 10, fireRate: 5, spread: 0.08, pellets: 1, reloadTime: 0, autoFire: true }
};
exports.PICKUP_CONFIG = {
[PickupType.HealthSmall]: { name: 'Health +25', healthAmount: 25, ammoAmount: 0 },
[PickupType.HealthLarge]: { name: 'Health +100', healthAmount: 100, ammoAmount: 0 },
[PickupType.AmmoPistol]: { name: 'Pistol Ammo', healthAmount: 0, ammoAmount: 30 },
[PickupType.AmmoShotgun]: { name: 'Shotgun Shells', healthAmount: 0, ammoAmount: 10 },
[PickupType.AmmoMachineGun]: { name: 'Machine Gun Ammo', healthAmount: 0, ammoAmount: 50 },
[PickupType.WeaponShotgun]: { name: 'Shotgun', healthAmount: 0, ammoAmount: 10 },
[PickupType.WeaponMachineGun]: { name: 'Machine Gun', healthAmount: 0, ammoAmount: 50 }
};
exports.ENEMY_CONFIG = {
[EnemyType.Grunt]: { name: 'Grunt', health: 40, speed: 0.02, damage: 8, attackRange: 1.5, attackRate: 40 },
[EnemyType.Soldier]: { name: 'Soldier', health: 80, speed: 0.025, damage: 15, attackRange: 3.0, attackRate: 30 },
[EnemyType.Brute]: { name: 'Brute', health: 150, speed: 0.015, damage: 25, attackRange: 1.8, attackRate: 50 }
};
};
// ── module: src/level.ts ──
__mods["src/level.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.levelMap = exports.MAP_HEIGHT = exports.MAP_WIDTH = void 0;
exports.createInitialState = createInitialState;
exports.isWall = isWall;
exports.distance = distance;
exports.canSee = canSee;
const types_1 = require("./types");
exports.MAP_WIDTH = 24;
exports.MAP_HEIGHT = 24;
exports.levelMap = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1],
[1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
];
// Enemy spawn points (x, y in map coordinates)
const enemySpawns = [
{ x: 5.5, y: 5.5, type: types_1.EnemyType.Grunt },
{ x: 18.5, y: 5.5, type: types_1.EnemyType.Grunt },
{ x: 3.5, y: 9.5, type: types_1.EnemyType.Grunt },
{ x: 20.5, y: 9.5, type: types_1.EnemyType.Soldier },
{ x: 10.5, y: 8.5, type: types_1.EnemyType.Grunt },
{ x: 14.5, y: 15.5, type: types_1.EnemyType.Grunt },
{ x: 5.5, y: 18.5, type: types_1.EnemyType.Soldier },
{ x: 18.5, y: 18.5, type: types_1.EnemyType.Soldier },
{ x: 11.5, y: 19.5, type: types_1.EnemyType.Brute },
{ x: 12.5, y: 19.5, type: types_1.EnemyType.Brute },
];
// Pickup spawn points
const pickupSpawns = [
{ x: 2.5, y: 2.5, type: types_1.PickupType.HealthSmall },
{ x: 11.5, y: 2.5, type: types_1.PickupType.AmmoPistol },
{ x: 21.5, y: 2.5, type: types_1.PickupType.HealthSmall },
{ x: 6.5, y: 7.5, type: types_1.PickupType.WeaponShotgun },
{ x: 17.5, y: 7.5, type: types_1.PickupType.AmmoShotgun },
{ x: 2.5, y: 14.5, type: types_1.PickupType.WeaponMachineGun },
{ x: 21.5, y: 14.5, type: types_1.PickupType.AmmoMachineGun },
{ x: 11.5, y: 13.5, type: types_1.PickupType.HealthLarge },
{ x: 19.5, y: 21.5, type: types_1.PickupType.HealthLarge },
];
function createEnemy(spawn) {
const config = {
[types_1.EnemyType.Grunt]: { health: 40, speed: 0.015, damage: 8, attackRange: 1.5, attackRate: 45 },
[types_1.EnemyType.Soldier]: { health: 80, speed: 0.02, damage: 15, attackRange: 4.0, attackRate: 35 },
[types_1.EnemyType.Brute]: { health: 180, speed: 0.01, damage: 25, attackRange: 1.8, attackRate: 60 },
}[spawn.type];
return {
x: spawn.x,
y: spawn.y,
health: config.health,
maxHealth: config.health,
alive: true,
type: spawn.type,
speed: config.speed,
damage: config.damage,
attackRange: config.attackRange,
lastAttack: 0,
frame: 0,
state: 'idle',
};
}
function createPickup(spawn) {
return {
x: spawn.x,
y: spawn.y,
type: spawn.type,
alive: true,
bobOffset: Math.random() * Math.PI * 2,
};
}
function createInitialState() {
return {
player: {
x: 12.5,
y: 22.5,
angle: Math.PI,
health: 100,
maxHealth: 100,
weapons: [
{ type: 0, ammo: 50, maxAmmo: 999 },
],
currentWeapon: 0,
kills: 0,
alive: true,
invulnerable: 0,
},
enemies: enemySpawns.map(createEnemy),
pickups: pickupSpawns.map(createPickup),
map: exports.levelMap,
width: exports.MAP_WIDTH,
height: exports.MAP_HEIGHT,
frameCount: 0,
gameOver: false,
won: false,
message: '',
messageTimer: 0,
};
}
function isWall(x, y, map) {
const mx = Math.floor(x);
const my = Math.floor(y);
if (mx < 0 || mx >= exports.MAP_WIDTH || my < 0 || my >= exports.MAP_HEIGHT)
return true;
return map[my][mx] !== 0;
}
function distance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
function canSee(x1, y1, x2, y2, map) {
const dist = distance(x1, y1, x2, y2);
const steps = Math.ceil(dist * 8);
for (let i = 1; i < steps; i++) {
const t = i / steps;
const cx = x1 + (x2 - x1) * t;
const cy = y1 + (y2 - y1) * t;
if (isWall(cx, cy, map))
return false;
}
return true;
}
};
// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.castRay = castRay;
exports.renderGame = renderGame;
const types_1 = require("./types");
const level_1 = require("./level");
function castRay(ox, oy, angle, map) {
const sin = Math.sin(angle);
const cos = Math.cos(angle);
let x = ox;
let y = oy;
const step = 0.02;
let dist = 0;
let wallType = 0;
let side = 0; // 0 = vertical wall, 1 = horizontal wall
for (let i = 0; i < 1000; i++) {
x += cos * step;
y += sin * step;
dist += step;
const mx = Math.floor(x);
const my = Math.floor(y);
if (mx < 0 || mx >= map[0].length || my < 0 || my >= map.length) {
return { dist: 100, wallType: 1, side: 0, hitX: x, hitY: y };
}
const cell = map[my][mx];
if (cell > 0) {
// Determine side
const fracX = x - mx;
const fracY = y - my;
if (fracX < 0.05 || fracX > 0.95) {
side = 0;
}
else {
side = 1;
}
return { dist, wallType: cell, side, hitX: x, hitY: y };
}
}
return { dist: 100, wallType: 1, side: 0, hitX: x, hitY: y };
}
function renderGame(ctx, state, width, height, weaponFrame, muzzleFlash, flashFrame) {
const player = state.player;
const map = state.map;
const fov = Math.PI / 3;
const numRays = width;
const wallHeight = height;
// Clear sky (top half - dark)
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, width, height / 2);
// Clear floor (bottom half)
ctx.fillStyle = '#2a2a2a';
ctx.fillRect(0, height / 2, width, height / 2);
// Wall buffer for depth testing
const zBuffer = new Float32Array(numRays);
// Cast rays
for (let i = 0; i < numRays; i++) {
const rayAngle = player.angle - fov / 2 + (i / numRays) * fov;
const result = castRay(player.x, player.y, rayAngle, map);
// Fix fisheye
const correctedDist = result.dist * Math.cos(rayAngle - player.angle);
zBuffer[i] = correctedDist;
const wallScale = wallHeight / correctedDist;
const wallTop = (height - wallScale) / 2;
// Color based on wall type and side
let baseColor;
if (result.wallType === 1) {
baseColor = sideDarkened('#8B4513', result.side);
}
else if (result.wallType === 2) {
baseColor = sideDarkened('#4a4a6a', result.side);
}
else {
baseColor = sideDarkened('#555', result.side);
}
// Distance fog
const fogFactor = Math.min(1, correctedDist / 15);
baseColor = applyFog(baseColor, fogFactor);
ctx.fillStyle = baseColor;
ctx.fillRect(i, wallTop, 1, wallScale);
}
// Render sprites (enemies and pickups)
const sprites = [];
state.enemies.forEach(enemy => {
if (!enemy.alive)
return;
const d = (0, level_1.distance)(player.x, player.y, enemy.x, enemy.y);
sprites.push({ x: enemy.x, y: enemy.y, dist: d, type: 'enemy', data: enemy });
});
state.pickups.forEach(pickup => {
if (!pickup.alive)
return;
const d = (0, level_1.distance)(player.x, player.y, pickup.x, pickup.y);
sprites.push({ x: pickup.x, y: pickup.y, dist: d, type: 'pickup', data: pickup });
});
// Sort by distance (far first)
sprites.sort((a, b) => b.dist - a.dist);
// Render sprites
sprites.forEach(sprite => {
const dx = sprite.x - player.x;
const dy = sprite.y - player.y;
// Transform sprite position relative to player
const angleToSprite = Math.atan2(dy, dx);
let angleDiff = angleToSprite - player.angle;
while (angleDiff > Math.PI)
angleDiff -= Math.PI * 2;
while (angleDiff < -Math.PI)
angleDiff += Math.PI * 2;
if (Math.abs(angleDiff) > fov * 1.2)
return;
const correctedDist = sprite.dist * Math.cos(angleDiff);
if (correctedDist < 0.3)
return;
const screenX = (0.5 + angleDiff / fov) * width;
const spriteHeight = (height / correctedDist) * (sprite.type === 'enemy' ? 0.9 : 0.5);
const spriteWidth = spriteHeight * 0.7;
const spriteTop = (height - spriteHeight) / 2 + (sprite.type === 'pickup' ? spriteHeight * 0.15 : 0);
// Check if visible
const rayIndex = Math.floor(screenX);
if (rayIndex >= 0 && rayIndex < numRays && correctedDist < zBuffer[rayIndex] && (0, level_1.canSee)(player.x, player.y, sprite.x, sprite.y, map)) {
if (sprite.type === 'enemy') {
drawEnemySprite(ctx, sprite.data, screenX, spriteTop, spriteWidth, spriteHeight, correctedDist);
}
else {
drawPickupSprite(ctx, sprite.data, screenX, spriteTop, spriteWidth, spriteHeight, correctedDist, state.frameCount);
}
}
});
// Draw weapon
drawWeapon(ctx, width, height, player, weaponFrame, muzzleFlash, flashFrame);
// Crosshair
ctx.strokeStyle = 'rgba(0, 255, 0, 0.6)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(width / 2 - 8, height / 2);
ctx.lineTo(width / 2 + 8, height / 2);
ctx.moveTo(width / 2, height / 2 - 8);
ctx.lineTo(width / 2, height / 2 + 8);
ctx.stroke();
ctx.fillStyle = 'rgba(0, 255, 0, 0.8)';
ctx.fillRect(width / 2 - 1, height / 2 - 1, 2, 2);
// Damage flash
if (state.player.invulnerable > 0 && state.player.invulnerable % 6 < 3) {
ctx.fillStyle = 'rgba(255, 0, 0, 0.2)';
ctx.fillRect(0, 0, width, height);
}
// Message overlay
if (state.messageTimer > 0 && state.message) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, height * 0.3, width, height * 0.15);
ctx.fillStyle = '#ff0';
ctx.font = 'bold 24px "Courier New", monospace';
ctx.textAlign = 'center';
ctx.fillText(state.message, width / 2, height * 0.4);
}
// Game over / Win screens
if (state.gameOver) {
ctx.fillStyle = 'rgba(139, 0, 0, 0.7)';
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = '#fff';
ctx.font = 'bold 48px "Courier New", monospace';
ctx.textAlign = 'center';
ctx.fillText('YOU DIED', width / 2, height / 2 - 30);
ctx.font = '24px "Courier New", monospace';
ctx.fillText('Press R to Respawn', width / 2, height / 2 + 20);
ctx.font = '18px "Courier New", monospace';
ctx.fillText(`Kills: ${state.player.kills}`, width / 2, height / 2 + 55);
}
if (state.won) {
ctx.fillStyle = 'rgba(0, 50, 0, 0.7)';
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = '#0f0';
ctx.font = 'bold 48px "Courier New", monospace';
ctx.textAlign = 'center';
ctx.fillText('VICTORY!', width / 2, height / 2 - 30);
ctx.font = '24px "Courier New", monospace';
ctx.fillText('All enemies eliminated!', width / 2, height / 2 + 10);
ctx.font = '20px "Courier New", monospace';
ctx.fillText(`Final Kills: ${state.player.kills}`, width / 2, height / 2 + 45);
ctx.fillText('Press R to Play Again', width / 2, height / 2 + 80);
}
}
function sideDarkened(color, side) {
if (side === 1) {
const rgb = hexToRgb(color);
return `rgb(${rgb.r * 0.7}, ${rgb.g * 0.7}, ${rgb.b * 0.7})`;
}
return color;
}
function applyFog(color, factor) {
if (factor <= 0)
return color;
const rgb = parseColor(color);
const fog = { r: 10, g: 10, b: 20 };
const r = Math.round(rgb.r * (1 - factor) + fog.r * factor);
const g = Math.round(rgb.g * (1 - factor) + fog.g * factor);
const b = Math.round(rgb.b * (1 - factor) + fog.b * factor);
return `rgb(${r}, ${g}, ${b})`;
}
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
} : { r: 128, g: 128, b: 128 };
}
function parseColor(color) {
const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
return {
r: parseInt(rgbMatch[1]),
g: parseInt(rgbMatch[2]),
b: parseInt(rgbMatch[3]),
};
}
return hexToRgb(color);
}
function drawEnemySprite(ctx, enemy, screenX, spriteTop, width, height, dist) {
const fogFactor = Math.min(1, dist / 15);
// Different colors for enemy types
let bodyColor, headColor, eyeColor;
switch (enemy.type) {
case types_1.EnemyType.Grunt:
bodyColor = '#8B4513';
headColor = '#DEB887';
eyeColor = '#ff0000';
break;
case types_1.EnemyType.Soldier:
bodyColor = '#2F4F4F';
headColor = '#8B7355';
eyeColor = '#ff4400';
break;
case types_1.EnemyType.Brute:
bodyColor = '#8B0000';
headColor = '#CD853F';
eyeColor = '#ffff00';
break;
default:
bodyColor = '#8B4513';
headColor = '#DEB887';
eyeColor = '#ff0000';
}
// Darken with fog
const darken = (c) => {
const rgb = hexToRgb(c);
const r = Math.round(rgb.r * (1 - fogFactor));
const g = Math.round(rgb.g * (1 - fogFactor));
const b = Math.round(rgb.b * (1 - fogFactor));
return `rgb(${r}, ${g}, ${b})`;
};
const cx = screenX;
const bobOffset = Math.sin(enemy.frame * 0.1) * (height * 0.02);
// Body
ctx.fillStyle = darken(bodyColor);
ctx.fillRect(cx - width * 0.3, spriteTop + height * 0.35 + bobOffset, width * 0.6, height * 0.4);
// Head
ctx.fillStyle = darken(headColor);
ctx.beginPath();
ctx.arc(cx, spriteTop + height * 0.2 + bobOffset, width * 0.2, 0, Math.PI * 2);
ctx.fill();
// Eyes (glowing)
const eyeBrightness = 1 - fogFactor * 0.7;
ctx.fillStyle = `rgba(255, 0, 0, ${eyeBrightness})`;
ctx.fillRect(cx - width * 0.08, spriteTop + height * 0.15 + bobOffset, width * 0.06, height * 0.04);
ctx.fillRect(cx + width * 0.02, spriteTop + height * 0.15 + bobOffset, width * 0.06, height * 0.04);
// Arms
ctx.fillStyle = darken(bodyColor);
ctx.fillRect(cx - width * 0.45, spriteTop + height * 0.38 + bobOffset, width * 0.15, height * 0.15);
ctx.fillRect(cx + width * 0.3, spriteTop + height * 0.38 + bobOffset, width * 0.15, height * 0.15);
// Health bar
if (enemy.health < enemy.maxHealth) {
const barWidth = width * 0.6;
const barHeight = height * 0.03;
const barX = cx - barWidth / 2;
const barY = spriteTop + bobOffset - height * 0.05;
ctx.fillStyle = '#333';
ctx.fillRect(barX, barY, barWidth, barHeight);
const healthRatio = enemy.health / enemy.maxHealth;
ctx.fillStyle = healthRatio > 0.5 ? '#0f0' : healthRatio > 0.25 ? '#ff0' : '#f00';
ctx.fillRect(barX, barY, barWidth * healthRatio, barHeight);
}
enemy.frame++;
}
function drawPickupSprite(ctx, pickup, screenX, spriteTop, width, height, dist, frameCount) {
const bob = Math.sin((frameCount + pickup.bobOffset) * 0.05) * (height * 0.1);
const glow = Math.sin((frameCount + pickup.bobOffset) * 0.08) * 0.3 + 0.7;
const cx = screenX;
const cy = spriteTop + height * 0.5 + bob;
const config = types_1.PICKUP_CONFIG[pickup.type];
let color;
let symbol;
switch (pickup.type) {
case types_1.PickupType.HealthSmall:
case types_1.PickupType.HealthLarge:
color = '#ff3333';
symbol = '+';
break;
case types_1.PickupType.AmmoPistol:
case types_1.PickupType.AmmoShotgun:
case types_1.PickupType.AmmoMachineGun:
color = '#ffcc00';
symbol = '★';
break;
case types_1.PickupType.WeaponShotgun:
color = '#8888ff';
symbol = 'S';
break;
case types_1.PickupType.WeaponMachineGun:
color = '#88ff88';
symbol = 'M';
break;
default:
color = '#ffff00';
symbol = '?';
}
// Glow effect
ctx.fillStyle = color.replace(')', ', 0.3)').replace('rgb', 'rgba');
ctx.beginPath();
ctx.arc(cx, cy, width * 0.35 * glow, 0, Math.PI * 2);
ctx.fill();
// Box
ctx.fillStyle = color;
ctx.fillRect(cx - width * 0.25, cy - height * 0.2, width * 0.5, height * 0.4);
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.strokeRect(cx - width * 0.25, cy - height * 0.2, width * 0.5, height * 0.4);
// Symbol
ctx.fillStyle = '#000';
ctx.font = `bold ${Math.max(8, width * 0.5)}px "Courier New"`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(symbol, cx, cy);
}
function drawWeapon(ctx, width, height, player, weaponFrame, muzzleFlash, flashFrame) {
const weaponIndex = player.currentWeapon;
if (weaponIndex >= player.weapons.length)
return;
const weapon = player.weapons[weaponIndex];
const config = types_1.WEAPON_CONFIG[weapon.type];
const weaponBobX = Math.sin(weaponFrame * 0.05) * 4;
const weaponBobY = Math.abs(Math.cos(weaponFrame * 0.05)) * 3;
const baseX = width / 2 + weaponBobX;
const baseY = height - 50 + weaponBobY;
ctx.save();
ctx.translate(baseX, baseY);
switch (weapon.type) {
case types_1.WeaponType.Pistol:
drawPistol(ctx, muzzleFlash, flashFrame);
break;
case types_1.WeaponType.Shotgun:
drawShotgun(ctx, muzzleFlash, flashFrame);
break;
case types_1.WeaponType.MachineGun:
drawMachineGun(ctx, muzzleFlash, flashFrame);
break;
}
ctx.restore();
// Ammo counter for current weapon
ctx.fillStyle = '#fff';
ctx.font = '18px "Courier New", monospace';
ctx.textAlign = 'right';
ctx.fillText(`${weapon.ammo}`, width - 20, height - 80);
}
function drawPistol(ctx, muzzleFlash, flashFrame) {
// Handle
ctx.fillStyle = '#3a3a3a';
ctx.fillRect(-8, 30, 16, 40);
// Barrel
ctx.fillStyle = '#5a5a5a';
ctx.fillRect(-6, -30, 12, 60);
ctx.fillStyle = '#7a7a7a';
ctx.fillRect(-4, -30, 8, 60);
// Muzzle flash
if (muzzleFlash) {
const size = 15 + Math.sin(flashFrame * 1.5) * 5;
ctx.fillStyle = '#ffff00';
ctx.beginPath();
ctx.arc(0, -35, size, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(0, -35, size * 0.5, 0, Math.PI * 2);
ctx.fill();
}
}
function drawShotgun(ctx, muzzleFlash, flashFrame) {
// Stock
ctx.fillStyle = '#654321';
ctx.beginPath();
ctx.moveTo(-15, 50);
ctx.lineTo(15, 50);
ctx.lineTo(10, 70);
ctx.lineTo(-10, 70);
ctx.closePath();
ctx.fill();
// Body
ctx.fillStyle = '#4a4a4a';
ctx.fillRect(-20, 10, 40, 40);
// Barrels
ctx.fillStyle = '#5a5a5a';
ctx.fillRect(-12, -40, 8, 50);
ctx.fillRect(4, -40, 8, 50);
ctx.fillStyle = '#7a7a7a';
ctx.fillRect(-11, -40, 6, 50);
ctx.fillRect(5, -40, 6, 50);
// Muzzle flash (wider)
if (muzzleFlash) {
const size = 25 + Math.sin(flashFrame * 1.5) * 10;
ctx.fillStyle = '#ff8800';
ctx.beginPath();
ctx.arc(-8, -45, size, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(8, -45, size * 0.8, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(0, -48, size * 0.4, 0, Math.PI * 2);
ctx.fill();
}
}
function drawMachineGun(ctx, muzzleFlash, flashFrame) {
// Body
ctx.fillStyle = '#3a3a3a';
ctx.fillRect(-25, 20, 50, 25);
// Barrel assembly
ctx.fillStyle = '#5a5a5a';
ctx.fillRect(-18, -30, 36, 50);
ctx.fillStyle = '#6a6a6a';
ctx.fillRect(-15, -30, 30, 50);
// Barrel grooves
ctx.fillStyle = '#4a4a4a';
ctx.fillRect(-16, -15, 32, 2);
ctx.fillRect(-16, -5, 32, 2);
// Magazine
ctx.fillStyle = '#2a2a2a';
ctx.fillRect(-5, 45, 10, 25);
// Muzzle flash
if (muzzleFlash) {
const size = 12 + Math.sin(flashFrame * 2) * 6;
ctx.fillStyle = '#ffaa00';
ctx.beginPath();
ctx.arc(0, -35, size, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(0, -35, size * 0.5, 0, Math.PI * 2);
ctx.fill();
// Tracers effect
for (let i = 0; i < 3; i++) {
const angle = -0.1 + (i - 1) * 0.1;
ctx.strokeStyle = '#ffcc00';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(0, -35);
ctx.lineTo(Math.sin(angle) * 40, -35 + Math.cos(angle) * 40);
ctx.stroke();
}
}
}
};
// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWeaponState = createWeaponState;
exports.processInput = processInput;
exports.updateWeapons = updateWeapons;
exports.updateEnemies = updateEnemies;
exports.updatePickups = updatePickups;
exports.updateMessages = updateMessages;
exports.respawnGame = respawnGame;
const types_1 = require("./types");
const level_1 = require("./level");
function createWeaponState() {
return { fireTimer: 0, muzzleFlash: false, flashTimer: 0 };
}
function processInput(input, state, dt) {
const player = state.player;
// Respawn
if ((state.gameOver || state.won) && input.respawn) {
respawnGame(state);
}
if (state.gameOver || state.won || !player.alive)
return;
const moveSpeed = 0.06;
const turnSpeed = 0.04;
// Movement
let dx = 0;
let dy = 0;
if (input.forward) {
dx += Math.cos(player.angle) * moveSpeed;
dy += Math.sin(player.angle) * moveSpeed;
}
if (input.backward) {
dx -= Math.cos(player.angle) * moveSpeed * 0.7;
dy -= Math.sin(player.angle) * moveSpeed * 0.7;
}
// Collision detection with sliding
if (!(0, level_1.isWall)(player.x + dx, player.y, state.map)) {
player.x += dx;
}
if (!(0, level_1.isWall)(player.x, player.y + dy, state.map)) {
player.y += dy;
}
// Turning
if (input.turnLeft)
player.angle -= turnSpeed;
if (input.turnRight)
player.angle += turnSpeed;
// Keep player in bounds
player.x = Math.max(0.5, Math.min(state.width - 0.5, player.x));
player.y = Math.max(0.5, Math.min(state.height - 0.5, player.y));
// Invulnerability timer
if (player.invulnerable > 0) {
player.invulnerable--;
}
}
function updateWeapons(input, state, weaponStates) {
const player = state.player;
if (!player.alive)
return;
// Weapon switching
if (input.weapon1 && player.weapons[0]) {
player.currentWeapon = 0;
}
else if (input.weapon2 && player.weapons.length > 1 && player.weapons[1]) {
player.currentWeapon = 1;
}
else if (input.weapon3 && player.weapons.length > 2 && player.weapons[2]) {
player.currentWeapon = 2;
}
const weaponIndex = player.currentWeapon;
if (weaponIndex >= player.weapons.length)
return;
const weapon = player.weapons[weaponIndex];
const config = types_1.WEAPON_CONFIG[weapon.type];
const ws = weaponStates[weaponIndex] || createWeaponState();
// Update fire timer
if (ws.fireTimer > 0)
ws.fireTimer--;
if (ws.flashTimer > 0)
ws.flashTimer--;
if (ws.flashTimer <= 0)
ws.muzzleFlash = false;
// Shooting
const canShoot = ws.fireTimer <= 0 && weapon.ammo > 0;
const shouldShoot = input.shooting;
if (canShoot && shouldShoot) {
fireWeapon(state, weapon, config, ws);
ws.muzzleFlash = true;
ws.flashTimer = 3;
ws.fireTimer = config.fireRate;
if (!config.autoFire) {
// Semi-auto: need to release and press again
// Handled by input state being frame-based
}
}
// Auto-fire holds
if (config.autoFire && shouldShoot && ws.fireTimer > 0 && weapon.ammo > 0) {
// Continue firing next frame when timer expires
}
weaponStates[weaponIndex] = ws;
}
function fireWeapon(state, weapon, config, ws) {
const player = state.player;
weapon.ammo--;
for (let p = 0; p < config.pellets; p++) {
const spreadAngle = player.angle + (Math.random() - 0.5) * config.spread * 2;
// Raycast for this pellet
let hitEnemy = null;
let hitDist = Infinity;
// Check walls first
const rayResult = castRayForHit(player.x, player.y, spreadAngle, state.map);
// Check enemies
for (let i = 0; i < state.enemies.length; i++) {
const enemy = state.enemies[i];
if (!enemy.alive)
continue;
const dist = (0, level_1.distance)(player.x, player.y, enemy.x, enemy.y);
const angleToEnemy = Math.atan2(enemy.y - player.y, enemy.x - player.x);
let angleDiff = angleToEnemy - spreadAngle;
while (angleDiff > Math.PI)
angleDiff -= Math.PI * 2;
while (angleDiff < -Math.PI)
angleDiff += Math.PI * 2;
const hitRadius = 0.35; // enemy hitbox radius
const angularWidth = Math.atan2(hitRadius, dist);
if (Math.abs(angleDiff) < angularWidth && dist < rayResult.dist && (0, level_1.canSee)(player.x, player.y, enemy.x, enemy.y, state.map)) {
if (dist < hitDist) {
hitDist = dist;
hitEnemy = i;
}
}
}
if (hitEnemy !== null) {
const enemy = state.enemies[hitEnemy];
enemy.health -= config.damage;
if (enemy.health <= 0) {
enemy.alive = false;
player.kills++;
showMessage(state, `Enemy eliminated! (${player.kills}/${countEnemies(state.enemies)})`);
// Check win condition
if (countAliveEnemies(state.enemies) === 0) {
state.won = true;
showMessage(state, 'VICTORY! All enemies eliminated!');
}
}
}
}
}
function castRayForHit(ox, oy, angle, map) {
const sin = Math.sin(angle);
const cos = Math.cos(angle);
const step = 0.05;
for (let i = 0; i < 200; i++) {
const x = ox + cos * step * i;
const y = oy + sin * step * i;
const mx = Math.floor(x);
const my = Math.floor(y);
if (mx < 0 || mx >= map[0].length || my < 0 || my >= map.length) {
return { dist: i * step };
}
if (map[my][mx] > 0) {
return { dist: i * step };
}
}
return { dist: 100 };
}
function updateEnemies(state) {
const player = state.player;
state.enemies.forEach(enemy => {
if (!enemy.alive)
return;
const distToPlayer = (0, level_1.distance)(enemy.x, enemy.y, player.x, player.y);
const canSeePlayer = (0, level_1.canSee)(enemy.x, enemy.y, player.x, player.y, state.map);
// State machine
if (distToPlayer < enemy.attackRange + 2 && canSeePlayer) {
enemy.state = 'chasing';
}
else if (distToPlayer < 10 && canSeePlayer) {
enemy.state = 'chasing';
}
else {
enemy.state = 'idle';
}
if (enemy.state === 'chasing') {
// Move toward player
const angleToPlayer = Math.atan2(player.y - enemy.y, player.x - enemy.x);
if (distToPlayer > enemy.attackRange) {
const dx = Math.cos(angleToPlayer) * enemy.speed;
const dy = Math.sin(angleToPlayer) * enemy.speed;
// Simple collision with sliding
if (!(0, level_1.isWall)(enemy.x + dx, enemy.y, state.map)) {
enemy.x += dx;
}
else if (!(0, level_1.isWall)(enemy.x, enemy.y + dy, state.map)) {
enemy.y += dy;
}
}
// Attack player
if (distToPlayer <= enemy.attackRange && canSeePlayer && player.alive && player.invulnerable <= 0) {
const timeSinceLastAttack = state.frameCount - enemy.lastAttack;
const attackRate = getEnemyAttackRate(enemy.type);
if (timeSinceLastAttack >= attackRate) {
damagePlayer(state, enemy.damage);
enemy.lastAttack = state.frameCount;
enemy.state = 'attacking';
}
}
}
if (enemy.state === 'attacking') {
if (state.frameCount - enemy.lastAttack > 15) {
enemy.state = 'chasing';
}
}
enemy.frame++;
});
}
function getEnemyAttackRate(type) {
switch (type) {
case types_1.EnemyType.Grunt: return 45;
case types_1.EnemyType.Soldier: return 35;
case types_1.EnemyType.Brute: return 60;
default: return 45;
}
}
function damagePlayer(state, amount) {
const player = state.player;
if (!player.alive)
return;
if (player.invulnerable > 0)
return;
player.health -= amount;
player.invulnerable = 30;
if (player.health <= 0) {
player.health = 0;
player.alive = false;
state.gameOver = true;
showMessage(state, 'YOU DIED - Press R to Respawn');
}
}
function updatePickups(state) {
const player = state.player;
if (!player.alive)
return;
state.pickups.forEach(pickup => {
if (!pickup.alive)
return;
const dist = (0, level_1.distance)(player.x, player.y, pickup.x, pickup.y);
if (dist < 0.6) {
collectPickup(state, pickup);
}
});
}
function collectPickup(state, pickup) {
const player = state.player;
const config = types_1.PICKUP_CONFIG[pickup.type];
switch (pickup.type) {
case types_1.PickupType.HealthSmall:
case types_1.PickupType.HealthLarge:
player.health = Math.min(player.maxHealth, player.health + config.healthAmount);
showMessage(state, `+${config.healthAmount} Health`);
break;
case types_1.PickupType.AmmoPistol:
ensureWeapon(player, types_1.WeaponType.Pistol);
player.weapons[0].ammo = Math.min(player.weapons[0].maxAmmo, player.weapons[0].ammo + config.ammoAmount);
showMessage(state, `+${config.ammoAmount} Pistol Ammo`);
break;
case types_1.PickupType.AmmoShotgun:
ensureWeapon(player, types_1.WeaponType.Shotgun);
const sgIdx = player.weapons.findIndex(w => w.type === types_1.WeaponType.Shotgun);
player.weapons[sgIdx].ammo = Math.min(player.weapons[sgIdx].maxAmmo, player.weapons[sgIdx].ammo + config.ammoAmount);
showMessage(state, `+${config.ammoAmount} Shotgun Shells`);
break;
case types_1.PickupType.AmmoMachineGun:
ensureWeapon(player, types_1.WeaponType.MachineGun);
const mgIdx = player.weapons.findIndex(w => w.type === types_1.WeaponType.MachineGun);
player.weapons[mgIdx].ammo = Math.min(player.weapons[mgIdx].maxAmmo, player.weapons[mgIdx].ammo + config.ammoAmount);
showMessage(state, `+${config.ammoAmount} Machine Gun Ammo`);
break;
case types_1.PickupType.WeaponShotgun:
if (!player.weapons.find(w => w.type === types_1.WeaponType.Shotgun)) {
player.weapons.push({ type: types_1.WeaponType.Shotgun, ammo: config.ammoAmount, maxAmmo: 50 });
player.currentWeapon = player.weapons.length - 1;
}
showMessage(state, 'Acquired Shotgun!');
break;
case types_1.PickupType.WeaponMachineGun:
if (!player.weapons.find(w => w.type === types_1.WeaponType.MachineGun)) {
player.weapons.push({ type: types_1.WeaponType.MachineGun, ammo: config.ammoAmount, maxAmmo: 150 });
player.currentWeapon = player.weapons.length - 1;
}
showMessage(state, 'Acquired Machine Gun!');
break;
}
pickup.alive = false;
}
function ensureWeapon(player, type) {
if (!player.weapons.find(w => w.type === type)) {
// Auto-add weapon if picking up ammo but don't have it
player.weapons.push({ type, ammo: 0, maxAmmo: type === types_1.WeaponType.Pistol ? 999 : type === types_1.WeaponType.Shotgun ? 50 : 150 });
}
}
function updateMessages(state) {
if (state.messageTimer > 0) {
state.messageTimer--;
if (state.messageTimer <= 0) {
state.message = '';
}
}
}
function showMessage(state, msg) {
state.message = msg;
state.messageTimer = 120; // 2 seconds at 60fps
}
function countEnemies(enemies) {
return enemies.length;
}
function countAliveEnemies(enemies) {
return enemies.filter(e => e.alive).length;
}
function respawnGame(state) {
// Reset player
state.player = {
x: 12.5,
y: 22.5,
angle: Math.PI,
health: 100,
maxHealth: 100,
weapons: [{ type: types_1.WeaponType.Pistol, ammo: 50, maxAmmo: 999 }],
currentWeapon: 0,
kills: state.player.kills,
alive: true,
invulnerable: 90, // 1.5 seconds invulnerable on respawn
};
// Revive enemies
state.enemies.forEach(enemy => {
enemy.alive = true;
enemy.health = enemy.maxHealth;
});
// Restore pickups
state.pickups.forEach(pickup => {
pickup.alive = true;
});
state.gameOver = false;
state.won = false;
state.message = '';
state.messageTimer = 0;
}
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"Qwen3.6-27B-Fable-Fusion-711-Uncensored-Heretic-NM-DAU-NEO-MAX-MTP-GGUF","provider":"brigid","persona":"typescript","sessionId":"c028431f-2675-4303-b6a8-d64056b53122","tokensIn":664055,"tokensOut":20091,"tokensTotal":684146,"cost":0,"turns":34,"toolCalls":33,"failedToolCalls":0,"timestamp":"2026-08-06T23:29:13.100Z"} -->