feat(integrations): add retro doom web engine and agy proxy nginx config

This commit is contained in:
Antigravity committed 2026-09-22 09:25:23 +00:00
1 parent b8db5a123d
commit 50eadb4b5e
15 files changed
+6397

No files matched your search

+498
View File
@@ -0,0 +1,498 @@
/**
* DOOM 3D - First Person Player Controller & Physics
* Smooth movement, Pointer Lock look, step-climbing physics, stats, inventory and cheat codes.
*/
class DoomPlayer {
constructor(engine, sound) {
this.engine = engine;
this.sound = sound;
this.camera = engine.camera;
// Position & Physics
this.position = new THREE.Vector3(0, 2.0, 0);
this.velocity = new THREE.Vector3();
this.euler = new THREE.Euler(0, 0, 0, 'YXZ');
this.isGrounded = false;
this.height = 1.8;
this.radius = 0.5;
this.stepHeight = 0.95;
// Speeds
this.walkSpeed = 11.0;
this.runSpeed = 19.0;
this.jumpForce = 12.0;
this.gravity = 26.0;
this.mouseSensitivity = 0.0022;
// Controls Input State
this.keys = {
forward: false,
backward: false,
left: false,
right: false,
sprint: false,
jump: false,
interact: false,
shoot: false
};
this.isPointerLocked = false;
// Player Stats
this.maxHealth = 100;
this.health = 100;
this.maxArmor = 100;
this.armor = 0;
this.isDead = false;
this.godMode = false;
this.noclip = false;
// Ammo Reserves
this.ammo = {
bullets: 50,
shells: 0,
rockets: 0,
cells: 0
};
this.maxAmmo = {
bullets: 200,
shells: 50,
rockets: 50,
cells: 300
};
// Keys & Inventory
this.keysInventory = {
blue: false,
red: false,
yellow: false
};
// Head Bobbing
this.bobTimer = 0;
this.bobOffset = new THREE.Vector2();
// Slime Damage Timer
this.slimeTimer = 0;
// Cheat Input Buffer
this.cheatBuffer = '';
this.setupEventListeners();
}
setupEventListeners() {
// Keyboard down
window.addEventListener('keydown', (e) => {
this.onKeyDown(e);
});
// Keyboard up
window.addEventListener('keyup', (e) => {
this.onKeyUp(e);
});
// Mouse move (Pointer Lock)
document.addEventListener('mousemove', (e) => {
if (!this.isPointerLocked || this.isDead) return;
const movementX = e.movementX || e.mozMovementX || e.webkitMovementX || 0;
const movementY = e.movementY || e.mozMovementY || e.webkitMovementY || 0;
this.euler.y -= movementX * this.mouseSensitivity;
this.euler.x -= movementY * this.mouseSensitivity;
// Clamp pitch look angle to [-89 deg, +89 deg]
const maxPitch = Math.PI / 2 - 0.02;
this.euler.x = Math.max(-maxPitch, Math.min(maxPitch, this.euler.x));
this.camera.quaternion.setFromEuler(this.euler);
});
// Pointer lock state changes
document.addEventListener('pointerlockchange', () => {
this.isPointerLocked = (document.pointerLockElement === this.engine.renderer.domElement);
});
// Canvas click request pointer lock
this.engine.renderer.domElement.addEventListener('click', () => {
if (!this.isPointerLocked && !this.isDead) {
this.requestPointerLock();
}
});
// Mouse click to shoot
window.addEventListener('mousedown', (e) => {
if (e.button === 0 && this.isPointerLocked && !this.isDead) {
this.keys.shoot = true;
if (window.game && window.game.weapons) {
window.game.weapons.triggerAttack();
}
}
});
window.addEventListener('mouseup', (e) => {
if (e.button === 0) {
this.keys.shoot = false;
}
});
}
requestPointerLock() {
try {
this.engine.renderer.domElement.requestPointerLock();
if (this.sound) this.sound.resume();
} catch (e) {
console.warn('Pointer lock request error:', e);
}
}
exitPointerLock() {
try {
document.exitPointerLock();
} catch (e) {}
}
onKeyDown(e) {
const code = e.code;
if (code === 'KeyW' || code === 'ArrowUp') this.keys.forward = true;
if (code === 'KeyS' || code === 'ArrowDown') this.keys.backward = true;
if (code === 'KeyA' || code === 'ArrowLeft') this.keys.left = true;
if (code === 'KeyD' || code === 'ArrowRight') this.keys.right = true;
if (code === 'ShiftLeft' || code === 'ShiftRight') this.keys.sprint = true;
if (code === 'Space') {
this.keys.jump = true;
e.preventDefault();
}
if (code === 'KeyE' || code === 'Space') {
this.keys.interact = true;
this.checkInteractions();
}
// Weapon hotkeys (1-7)
if (window.game && window.game.weapons) {
if (code === 'Digit1') window.game.weapons.selectWeapon(0); // Fists
if (code === 'Digit2') window.game.weapons.selectWeapon(1); // Pistol
if (code === 'Digit3') window.game.weapons.selectWeapon(2); // Shotgun
if (code === 'Digit4') window.game.weapons.selectWeapon(3); // Chaingun
if (code === 'Digit5') window.game.weapons.selectWeapon(4); // Rocket Launcher
if (code === 'Digit6') window.game.weapons.selectWeapon(5); // Plasma Gun
if (code === 'Digit7') window.game.weapons.selectWeapon(6); // BFG 9000
}
// Classic DOOM Cheats
this.checkCheatCode(e.key);
}
onKeyUp(e) {
const code = e.code;
if (code === 'KeyW' || code === 'ArrowUp') this.keys.forward = false;
if (code === 'KeyS' || code === 'ArrowDown') this.keys.backward = false;
if (code === 'KeyA' || code === 'ArrowLeft') this.keys.left = false;
if (code === 'KeyD' || code === 'ArrowRight') this.keys.right = false;
if (code === 'ShiftLeft' || code === 'ShiftRight') this.keys.sprint = false;
if (code === 'Space') this.keys.jump = false;
if (code === 'KeyE') this.keys.interact = false;
}
checkCheatCode(char) {
if (!char || char.length > 1) return;
this.cheatBuffer = (this.cheatBuffer + char.toLowerCase()).slice(-8);
// IDDQD (God Mode)
if (this.cheatBuffer.endsWith('iddqd')) {
this.godMode = !this.godMode;
this.health = 100;
if (window.game && window.game.hud) {
window.game.hud.showNotification(this.godMode ? 'DEGNAMELESS INVULNERABILITY ON' : 'DEGNAMELESS INVULNERABILITY OFF');
}
if (this.sound) this.sound.playItemPickup('weapon');
}
// IDKFA (All Weapons, Full Ammo & Keys)
else if (this.cheatBuffer.endsWith('idkfa')) {
this.ammo.bullets = this.maxAmmo.bullets;
this.ammo.shells = this.maxAmmo.shells;
this.ammo.rockets = this.maxAmmo.rockets;
this.ammo.cells = this.maxAmmo.cells;
this.armor = 200;
this.keysInventory.blue = true;
this.keysInventory.red = true;
this.keysInventory.yellow = true;
if (window.game && window.game.weapons) {
window.game.weapons.unlocked = [true, true, true, true, true, true, true];
}
if (window.game && window.game.hud) {
window.game.hud.showNotification('VERY HAPPY AMMO ADDED');
}
if (this.sound) this.sound.playItemPickup('weapon');
}
// IDCLIP (Noclip)
else if (this.cheatBuffer.endsWith('idclip')) {
this.noclip = !this.noclip;
if (window.game && window.game.hud) {
window.game.hud.showNotification(this.noclip ? 'NOCLIP ON' : 'NOCLIP OFF');
}
}
}
checkInteractions() {
if (!window.game || !window.game.map) return;
window.game.map.interactWithDoors(this.position, this.keysInventory);
}
// ----------------------------------------------------
// DAMAGE & COMBAT LOGIC
// ----------------------------------------------------
takeDamage(amount, sourcePos = null) {
if (this.isDead || this.godMode) return;
// Armor absorbs 1/3 to 1/2 of damage
let dmgToHealth = amount;
if (this.armor > 0) {
const absorb = Math.min(this.armor, Math.floor(amount * 0.5));
this.armor -= absorb;
dmgToHealth = amount - absorb;
}
this.health = Math.max(0, this.health - dmgToHealth);
if (this.sound) {
this.sound.playPlayerPain();
}
if (window.game && window.game.hud) {
window.game.hud.triggerDamageFlash();
}
if (this.health <= 0) {
this.die();
}
}
heal(amount, isMega = false) {
if (this.isDead) return false;
const cap = isMega ? 200 : this.maxHealth;
if (this.health >= cap) return false;
this.health = Math.min(cap, this.health + amount);
return true;
}
addArmor(amount, isMega = false) {
if (this.isDead) return false;
const cap = isMega ? 200 : this.maxArmor;
if (this.armor >= cap) return false;
this.armor = Math.min(cap, this.armor + amount);
return true;
}
addAmmo(type, count) {
if (!this.ammo[type]) return false;
if (this.ammo[type] >= this.maxAmmo[type]) return false;
this.ammo[type] = Math.min(this.maxAmmo[type], this.ammo[type] + count);
return true;
}
die() {
this.isDead = true;
this.health = 0;
if (window.game && window.game.hud) {
window.game.hud.showGameOver();
}
}
respawn(spawnPos) {
this.isDead = false;
this.health = 100;
this.armor = 0;
this.ammo.bullets = 50;
this.ammo.shells = 0;
this.ammo.rockets = 0;
this.ammo.cells = 0;
this.position.copy(spawnPos || new THREE.Vector3(0, 2, 0));
this.velocity.set(0, 0, 0);
this.euler.set(0, 0, 0, 'YXZ');
this.camera.quaternion.setFromEuler(this.euler);
}
// ----------------------------------------------------
// PHYSICS & COLLISION UPDATE
// ----------------------------------------------------
update(delta) {
if (this.isDead) {
// Death cam sinking to floor
if (this.camera.position.y > this.position.y - 1.2) {
this.camera.position.y -= delta * 3;
}
return;
}
// 1. Calculate horizontal movement direction relative to camera yaw
const moveDir = new THREE.Vector3();
if (this.keys.forward) moveDir.z -= 1;
if (this.keys.backward) moveDir.z += 1;
if (this.keys.left) moveDir.x -= 1;
if (this.keys.right) moveDir.x += 1;
if (moveDir.lengthSq() > 0) {
moveDir.normalize();
// Rotate move direction by camera's horizontal yaw (euler.y)
moveDir.applyAxisAngle(new THREE.Vector3(0, 1, 0), this.euler.y);
}
// Speed
const curSpeed = this.keys.sprint ? this.runSpeed : this.walkSpeed;
const targetVelX = moveDir.x * curSpeed;
const targetVelZ = moveDir.z * curSpeed;
// Smooth acceleration/damping
this.velocity.x += (targetVelX - this.velocity.x) * Math.min(1, delta * 14);
this.velocity.z += (targetVelZ - this.velocity.z) * Math.min(1, delta * 14);
// 2. Jump & Gravity
if (!this.noclip) {
if (this.keys.jump && this.isGrounded) {
this.velocity.y = this.jumpForce;
this.isGrounded = false;
}
this.velocity.y -= this.gravity * delta;
} else {
this.velocity.y = 0;
if (this.keys.jump) this.position.y += curSpeed * delta;
if (this.keys.sprint) this.position.y -= curSpeed * delta;
}
// 3. Collision Resolution against Level Geometry
if (!this.noclip && window.game && window.game.map) {
this.resolveCollisions(delta);
} else {
this.position.addScaledVector(this.velocity, delta);
}
// 4. Update Camera position with Head Bobbing
const isMoving = (this.keys.forward || this.keys.backward || this.keys.left || this.keys.right) && this.isGrounded;
if (isMoving) {
const bobSpeed = this.keys.sprint ? 16 : 11;
this.bobTimer += delta * bobSpeed;
this.bobOffset.x = Math.cos(this.bobTimer * 0.5) * 0.05;
this.bobOffset.y = Math.sin(this.bobTimer) * 0.08;
} else {
this.bobOffset.x *= 0.85;
this.bobOffset.y *= 0.85;
}
this.camera.position.set(
this.position.x + this.bobOffset.x,
this.position.y + this.height + this.bobOffset.y,
this.position.z
);
// 5. Slime Pit Hazard Damage
if (window.game && window.game.map) {
if (window.game.map.isInSlimePit(this.position)) {
this.slimeTimer += delta;
if (this.slimeTimer >= 0.6) {
this.slimeTimer = 0;
this.takeDamage(6);
if (this.sound) this.sound.playSlimeHurt();
}
} else {
this.slimeTimer = 0;
}
}
}
resolveCollisions(delta) {
const colliders = window.game.map.getColliders();
// Horizontal move step X
this.position.x += this.velocity.x * delta;
this.collideHorizontal(colliders, 'x');
// Horizontal move step Z
this.position.z += this.velocity.z * delta;
this.collideHorizontal(colliders, 'z');
// Vertical move step Y
this.position.y += this.velocity.y * delta;
this.collideVertical(colliders);
}
collideHorizontal(colliders, axis) {
const pMinX = this.position.x - this.radius;
const pMaxX = this.position.x + this.radius;
const pMinZ = this.position.z - this.radius;
const pMaxZ = this.position.z + this.radius;
const pMinY = this.position.y;
const pMaxY = this.position.y + this.height;
for (let i = 0; i < colliders.length; i++) {
const box = colliders[i];
// Check AABB overlap
if (pMaxX > box.min.x && pMinX < box.max.x &&
pMaxZ > box.min.z && pMinZ < box.max.z &&
pMaxY > box.min.y && pMinY < box.max.y) {
// Check if step-climbing is possible (stairs, curbs, low ledges)
const stepUp = box.max.y - pMinY;
if (stepUp > 0 && stepUp <= this.stepHeight && (this.isGrounded || this.velocity.y <= 1.0)) {
this.position.y = box.max.y + 0.02;
this.isGrounded = true;
this.velocity.y = 0;
continue;
}
// Push back on axis
if (axis === 'x') {
if (this.velocity.x > 0) {
this.position.x = box.min.x - this.radius;
} else if (this.velocity.x < 0) {
this.position.x = box.max.x + this.radius;
}
this.velocity.x = 0;
} else if (axis === 'z') {
if (this.velocity.z > 0) {
this.position.z = box.min.z - this.radius;
} else if (this.velocity.z < 0) {
this.position.z = box.max.z + this.radius;
}
this.velocity.z = 0;
}
}
}
}
collideVertical(colliders) {
const pMinX = this.position.x - this.radius;
const pMaxX = this.position.x + this.radius;
const pMinZ = this.position.z - this.radius;
const pMaxZ = this.position.z + this.radius;
const pMinY = this.position.y;
const pMaxY = this.position.y + this.height;
let onGround = false;
for (let i = 0; i < colliders.length; i++) {
const box = colliders[i];
if (pMaxX > box.min.x && pMinX < box.max.x &&
pMaxZ > box.min.z && pMinZ < box.max.z &&
pMaxY > box.min.y && pMinY < box.max.y) {
if (this.velocity.y < 0) {
// Falling onto platform / floor
this.position.y = box.max.y;
this.velocity.y = 0;
onGround = true;
} else if (this.velocity.y > 0) {
// Hitting ceiling
this.position.y = box.min.y - this.height;
this.velocity.y = 0;
}
}
}
this.isGrounded = onGround;
}
}
window.DoomPlayer = DoomPlayer;