feat(integrations): add retro doom web engine and agy proxy nginx config
This commit is contained in:
1 parent
b8db5a123d
commit
50eadb4b5e
15 files changed
+6397
No files matched your search
Executable
+458
@@ -0,0 +1,458 @@
|
||||
/**
|
||||
* DOOM 3D - Multi-Level 3D Map & Architecture
|
||||
* Multi-story buildings, stairs, ramps, catwalks, toxic slime pits,
|
||||
* elevators, flickering lights, and keycard-locked blast doors.
|
||||
*/
|
||||
|
||||
class DoomMap {
|
||||
constructor(engine, sound) {
|
||||
this.engine = engine;
|
||||
this.sound = sound;
|
||||
|
||||
// Level Geometry Colliders
|
||||
this.colliders = [];
|
||||
this.slimeZones = [];
|
||||
this.doors = [];
|
||||
this.elevators = [];
|
||||
this.spawns = {
|
||||
player: new THREE.Vector3(0, 0, 0),
|
||||
enemies: [],
|
||||
items: [],
|
||||
barrels: []
|
||||
};
|
||||
|
||||
this.buildMap();
|
||||
}
|
||||
|
||||
getColliders() {
|
||||
return this.colliders;
|
||||
}
|
||||
|
||||
isInSlimePit(pos) {
|
||||
for (let i = 0; i < this.slimeZones.length; i++) {
|
||||
const sz = this.slimeZones[i];
|
||||
if (pos.x >= sz.min.x && pos.x <= sz.max.x &&
|
||||
pos.z >= sz.min.z && pos.z <= sz.max.z &&
|
||||
pos.y >= sz.min.y - 0.5 && pos.y <= sz.max.y + 1.5) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper to add Box Mesh + Collider Box
|
||||
addBlock(x, y, z, w, h, d, material, castShadow = true, receiveShadow = true) {
|
||||
const geo = new THREE.BoxGeometry(w, h, d);
|
||||
const mesh = new THREE.Mesh(geo, material);
|
||||
mesh.position.set(x, y + h / 2, z);
|
||||
mesh.castShadow = castShadow;
|
||||
mesh.receiveShadow = receiveShadow;
|
||||
this.engine.scene.add(mesh);
|
||||
|
||||
// AABB collider
|
||||
const min = new THREE.Vector3(x - w / 2, y, z - d / 2);
|
||||
const max = new THREE.Vector3(x + w / 2, y + h, z + d / 2);
|
||||
const box = new THREE.Box3(min, max);
|
||||
this.colliders.push(box);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// Helper for Stairs (multiple small step boxes)
|
||||
addStaircase(startX, startY, startZ, width, totalHeight, depth, numSteps, dir = 'z') {
|
||||
const stepH = totalHeight / numSteps;
|
||||
const stepD = depth / numSteps;
|
||||
|
||||
for (let i = 0; i < numSteps; i++) {
|
||||
const h = stepH * (i + 1);
|
||||
let curX = startX;
|
||||
let curY = startY;
|
||||
let curZ = startZ;
|
||||
|
||||
if (dir === 'z') {
|
||||
curZ = startZ + (i * stepD);
|
||||
this.addBlock(curX, curY, curZ, width, h, stepD + 0.1, this.engine.materials.metal);
|
||||
} else if (dir === '-z') {
|
||||
curZ = startZ - (i * stepD);
|
||||
this.addBlock(curX, curY, curZ, width, h, stepD + 0.1, this.engine.materials.metal);
|
||||
} else if (dir === 'x') {
|
||||
curX = startX + (i * stepD);
|
||||
this.addBlock(curX, curY, curZ, stepD + 0.1, h, width, this.engine.materials.metal);
|
||||
} else if (dir === '-x') {
|
||||
curX = startX - (i * stepD);
|
||||
this.addBlock(curX, curY, curZ, stepD + 0.1, h, width, this.engine.materials.metal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// LEVEL CONSTRUCTION (Inspired by E1M1 & E1M2)
|
||||
// ----------------------------------------------------
|
||||
|
||||
buildMap() {
|
||||
const mat = this.engine.materials;
|
||||
|
||||
// ==========================================
|
||||
// 1. STARTING ROOM (Hangar / Briefing Room)
|
||||
// ==========================================
|
||||
// Floor
|
||||
this.addBlock(0, -1, 0, 24, 1, 24, mat.metal);
|
||||
// Ceiling
|
||||
this.addBlock(0, 7, 0, 24, 1, 24, mat.ceiling);
|
||||
// North Wall (with door opening in middle)
|
||||
this.addBlock(-8, 0, -12, 8, 7, 2, mat.tech);
|
||||
this.addBlock(8, 0, -12, 8, 7, 2, mat.tech);
|
||||
this.addBlock(0, 5, -12, 8, 2, 2, mat.tech); // door lintel
|
||||
// South Wall
|
||||
this.addBlock(0, 0, 12, 24, 7, 2, mat.brick);
|
||||
// West Wall
|
||||
this.addBlock(-12, 0, 0, 2, 7, 24, mat.tech);
|
||||
// East Wall
|
||||
this.addBlock(12, 0, 0, 2, 7, 24, mat.metal);
|
||||
|
||||
// Starting Room Pillars & Consoles
|
||||
this.addBlock(-5, 0, -4, 2, 7, 2, mat.hex);
|
||||
this.addBlock(5, 0, -4, 2, 7, 2, mat.hex);
|
||||
this.addBlock(0, 0, 6, 6, 1.4, 2, mat.tech);
|
||||
|
||||
// Flickering light in start room
|
||||
this.engine.addFlickeringLight(0, 5.5, 0, 0xffeedd, 2.2, 18);
|
||||
|
||||
// Spawn points in start room
|
||||
this.spawns.player.set(0, 0, 6);
|
||||
this.spawns.items.push({ type: 'medikit', x: -9, y: 0, z: -8 });
|
||||
this.spawns.items.push({ type: 'armor_green', x: 9, y: 0, z: -8 });
|
||||
this.spawns.items.push({ type: 'shotgun', x: 0, y: 0, z: 2 });
|
||||
this.spawns.items.push({ type: 'ammo_shells', x: -2, y: 0, z: 2 });
|
||||
this.spawns.items.push({ type: 'ammo_shells', x: 2, y: 0, z: 2 });
|
||||
|
||||
// Door 1: Start Room to Main Corridor (Sliding blast door)
|
||||
this.createDoor(0, 0, -12, 7.8, 5, 1.2, 'normal', null);
|
||||
|
||||
// ==========================================
|
||||
// 2. CENTRAL CORRIDOR & CROSSROAD
|
||||
// ==========================================
|
||||
// Floor & Ceiling
|
||||
this.addBlock(0, -1, -26, 12, 1, 26, mat.grate);
|
||||
this.addBlock(0, 7, -26, 12, 1, 26, mat.ceiling);
|
||||
// Walls
|
||||
this.addBlock(-6, 0, -26, 1, 7, 26, mat.metal);
|
||||
this.addBlock(6, 0, -26, 1, 7, 26, mat.metal);
|
||||
this.engine.addFlickeringLight(0, 5.5, -24, 0xff8833, 2.0, 16);
|
||||
|
||||
this.spawns.enemies.push({ type: 'zombie', x: -2, y: 0, z: -20 });
|
||||
this.spawns.enemies.push({ type: 'zombie', x: 2, y: 0, z: -24 });
|
||||
this.spawns.items.push({ type: 'ammo_clip', x: 4, y: 0, z: -28 });
|
||||
|
||||
// ==========================================
|
||||
// 3. TOXIC SLIME ARENA & COURTYARD (Outdoor)
|
||||
// ==========================================
|
||||
// Floor around slime
|
||||
this.addBlock(-25, -1, -65, 60, 1, 50, mat.brick);
|
||||
// Toxic Slime Basin (recessed pool in middle)
|
||||
this.addBlock(-25, -2.2, -65, 34, 1, 26, mat.slime);
|
||||
// Slime Hazard Zone definition
|
||||
this.slimeZones.push(new THREE.Box3(
|
||||
new THREE.Vector3(-42, -2.5, -78),
|
||||
new THREE.Vector3(-8, 0.2, -52)
|
||||
));
|
||||
|
||||
// Stepping stone platforms across the slime river
|
||||
this.addBlock(-32, -0.8, -65, 4, 1.2, 4, mat.hex);
|
||||
this.addBlock(-25, -0.8, -65, 4, 1.2, 4, mat.hex);
|
||||
this.addBlock(-18, -0.8, -65, 4, 1.2, 4, mat.hex);
|
||||
|
||||
// Slime pit lighting
|
||||
this.engine.addFlickeringLight(-25, 2.0, -65, 0x33ff22, 3.5, 30);
|
||||
|
||||
// Courtyard Perimeter Walls
|
||||
this.addBlock(-55, 0, -65, 2, 12, 50, mat.brick); // West high wall
|
||||
this.addBlock(5, 0, -65, 2, 12, 50, mat.brick); // East high wall
|
||||
this.addBlock(-25, 0, -90, 60, 12, 2, mat.brick); // North high wall
|
||||
|
||||
// Blue Keycard Island on the slime pit!
|
||||
this.spawns.items.push({ type: 'key_blue', x: -25, y: 0.6, z: -65 });
|
||||
this.spawns.enemies.push({ type: 'imp', x: -35, y: 0, z: -80 });
|
||||
this.spawns.enemies.push({ type: 'imp', x: -15, y: 0, z: -80 });
|
||||
this.spawns.enemies.push({ type: 'shotgunner', x: -25, y: 0, z: -84 });
|
||||
this.spawns.barrels.push({ x: -10, y: 0, z: -75 });
|
||||
this.spawns.barrels.push({ x: -8, y: 0, z: -75 });
|
||||
this.spawns.barrels.push({ x: -38, y: 0, z: -75 });
|
||||
|
||||
// ==========================================
|
||||
// 4. MULTI-STORY TECH TOWER & CATWALKS (Level 2 & 3)
|
||||
// ==========================================
|
||||
// Staircase leading from Courtyard (y=0) to 2nd Floor Catwalk (y=6)
|
||||
this.addStaircase(-2, 0, -45, 6, 6, 16, 15, '-z');
|
||||
|
||||
// 2nd Floor Catwalk (y=6)
|
||||
this.addBlock(20, 5, -65, 28, 1, 50, mat.grate);
|
||||
this.addBlock(20, 15, -65, 28, 1, 50, mat.ceiling);
|
||||
// Catwalk Railing/Balcony facing courtyard (with opening for elevator at z=-45)
|
||||
this.addBlock(6, 6, -72, 1, 1.8, 34, mat.metal);
|
||||
this.addBlock(6, 6, -37, 1, 1.8, 16, mat.metal);
|
||||
// East Wall
|
||||
this.addBlock(34, 6, -65, 2, 9, 50, mat.tech);
|
||||
// North Wall
|
||||
this.addBlock(20, 6, -90, 30, 9, 2, mat.tech);
|
||||
|
||||
// Catwalk Monsters & Items
|
||||
this.spawns.enemies.push({ type: 'shotgunner', x: 18, y: 6, z: -55 });
|
||||
this.spawns.enemies.push({ type: 'pinky', x: 22, y: 6, z: -75 });
|
||||
this.spawns.items.push({ type: 'chaingun', x: 28, y: 6, z: -80 });
|
||||
this.spawns.items.push({ type: 'ammo_box', x: 28, y: 6, z: -76 });
|
||||
|
||||
// Catwalk Light
|
||||
this.engine.addFlickeringLight(20, 11, -65, 0x4488ff, 2.8, 22);
|
||||
|
||||
// ==========================================
|
||||
// 5. BLUE KEYCARD ARMORED VAULT (Secret Armory)
|
||||
// ==========================================
|
||||
// Door at North of Catwalk (x=20, z=-90)
|
||||
this.createDoor(20, 6, -90, 8, 6, 1.2, 'blue', 'blue');
|
||||
|
||||
// Secret Armory Room behind Blue Door
|
||||
this.addBlock(20, 5, -105, 20, 1, 28, mat.hex);
|
||||
this.addBlock(20, 15, -105, 20, 1, 28, mat.ceiling);
|
||||
this.addBlock(10, 6, -105, 1, 9, 28, mat.tech);
|
||||
this.addBlock(30, 6, -105, 1, 9, 28, mat.tech);
|
||||
this.addBlock(20, 6, -119, 20, 9, 2, mat.tech);
|
||||
|
||||
// Armory Rewards
|
||||
this.spawns.items.push({ type: 'plasma', x: 20, y: 6, z: -110 });
|
||||
this.spawns.items.push({ type: 'ammo_cell', x: 16, y: 6, z: -110 });
|
||||
this.spawns.items.push({ type: 'ammo_cell', x: 24, y: 6, z: -110 });
|
||||
this.spawns.items.push({ type: 'soulsphere', x: 20, y: 6, z: -115 });
|
||||
this.spawns.items.push({ type: 'key_red', x: 20, y: 6, z: -105 }); // Red Keycard inside armory!
|
||||
|
||||
this.engine.addFlickeringLight(20, 12, -105, 0x00aaff, 3.2, 18);
|
||||
|
||||
// ==========================================
|
||||
// 6. NUCLEAR REACTOR CHAMBER (Guarded by Red Door)
|
||||
// ==========================================
|
||||
// Door leading from West Courtyard (x=-55, z=-65)
|
||||
this.createDoor(-55, 0, -65, 1.2, 6, 8, 'red', 'red');
|
||||
|
||||
// Nuclear Reactor Room
|
||||
this.addBlock(-80, -1, -65, 48, 1, 48, mat.metal);
|
||||
this.addBlock(-80, 16, -65, 48, 1, 48, mat.ceiling);
|
||||
this.addBlock(-104, 0, -65, 2, 16, 48, mat.brick);
|
||||
this.addBlock(-80, 0, -89, 48, 16, 2, mat.brick);
|
||||
this.addBlock(-80, 0, -41, 48, 16, 2, mat.brick);
|
||||
|
||||
// Central Glowing Reactor Core
|
||||
this.addBlock(-80, 0, -65, 10, 12, 10, mat.slime);
|
||||
this.slimeZones.push(new THREE.Box3(
|
||||
new THREE.Vector3(-86, -1, -71),
|
||||
new THREE.Vector3(-74, 1, -59)
|
||||
));
|
||||
this.engine.addFlickeringLight(-80, 7, -65, 0x00ff22, 4.5, 35);
|
||||
|
||||
// Reactor Boss Monsters & Flying Cacodemons!
|
||||
this.spawns.enemies.push({ type: 'caco', x: -75, y: 5, z: -60 });
|
||||
this.spawns.enemies.push({ type: 'caco', x: -85, y: 7, z: -70 });
|
||||
this.spawns.enemies.push({ type: 'pinky', x: -90, y: 0, z: -65 });
|
||||
this.spawns.enemies.push({ type: 'shotgunner', x: -70, y: 0, z: -50 });
|
||||
|
||||
// Reactor Loot & Yellow Keycard
|
||||
this.spawns.items.push({ type: 'rocket_launcher', x: -92, y: 0, z: -65 });
|
||||
this.spawns.items.push({ type: 'ammo_rocket', x: -92, y: 0, z: -61 });
|
||||
this.spawns.items.push({ type: 'ammo_rocket', x: -92, y: 0, z: -69 });
|
||||
this.spawns.items.push({ type: 'armor_blue', x: -80, y: 0, z: -80 });
|
||||
this.spawns.items.push({ type: 'key_yellow', x: -80, y: 0, z: -50 });
|
||||
|
||||
// ==========================================
|
||||
// 7. FINAL BOSS ARENA / EXIT VAULT (Yellow Door)
|
||||
// ==========================================
|
||||
// Yellow Blast Door at North of Reactor room (x=-80, z=-89)
|
||||
this.createDoor(-80, 0, -89, 8, 6, 1.2, 'yellow', 'yellow');
|
||||
|
||||
// BFG Sanctum & Exit Chamber
|
||||
this.addBlock(-80, -1, -115, 32, 1, 48, mat.brick);
|
||||
this.addBlock(-80, 18, -115, 32, 1, 48, mat.ceiling);
|
||||
this.addBlock(-96, 0, -115, 2, 18, 48, mat.brick);
|
||||
this.addBlock(-64, 0, -115, 2, 18, 48, mat.brick);
|
||||
this.addBlock(-80, 0, -139, 32, 18, 2, mat.tech);
|
||||
|
||||
// BFG 9000 Altar
|
||||
this.addBlock(-80, 0, -125, 4, 1.5, 4, mat.hex);
|
||||
this.spawns.items.push({ type: 'bfg', x: -80, y: 1.8, z: -125 });
|
||||
this.spawns.items.push({ type: 'ammo_cell', x: -75, y: 0, z: -125 });
|
||||
this.spawns.items.push({ type: 'ammo_cell', x: -85, y: 0, z: -125 });
|
||||
this.spawns.items.push({ type: 'soulsphere', x: -80, y: 0, z: -132 });
|
||||
|
||||
// Final Arena Demonic Horde
|
||||
this.spawns.enemies.push({ type: 'caco', x: -80, y: 6, z: -120 });
|
||||
this.spawns.enemies.push({ type: 'pinky', x: -75, y: 0, z: -115 });
|
||||
this.spawns.enemies.push({ type: 'pinky', x: -85, y: 0, z: -115 });
|
||||
this.spawns.enemies.push({ type: 'imp', x: -72, y: 0, z: -130 });
|
||||
this.spawns.enemies.push({ type: 'imp', x: -88, y: 0, z: -130 });
|
||||
|
||||
this.engine.addFlickeringLight(-80, 10, -125, 0xff0022, 4.0, 30);
|
||||
|
||||
// Motorized Elevator Platform in Courtyard (moves between ground y=0 and catwalk y=6)
|
||||
this.createElevator(12, 0, -45, 6, 6, -0.6, 5.2, 2.5);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// INTERACTIVE DOORS
|
||||
// ----------------------------------------------------
|
||||
|
||||
createDoor(x, y, z, w, h, d, type = 'normal', requiredKey = null) {
|
||||
let mat = this.engine.materials.doorRegular;
|
||||
if (type === 'blue') mat = this.engine.materials.doorBlue;
|
||||
if (type === 'red') mat = this.engine.materials.doorRed;
|
||||
if (type === 'yellow') mat = this.engine.materials.doorYellow;
|
||||
|
||||
const geo = new THREE.BoxGeometry(w, h, d);
|
||||
const mesh = new THREE.Mesh(geo, mat);
|
||||
mesh.position.set(x, y + h / 2, z);
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
this.engine.scene.add(mesh);
|
||||
|
||||
const min = new THREE.Vector3(x - w / 2, y, z - d / 2);
|
||||
const max = new THREE.Vector3(x + w / 2, y + h, z + d / 2);
|
||||
const box = new THREE.Box3(min, max);
|
||||
this.colliders.push(box);
|
||||
|
||||
const doorObj = {
|
||||
mesh,
|
||||
box,
|
||||
x, y, z, w, h, d,
|
||||
type,
|
||||
requiredKey,
|
||||
state: 'closed', // 'closed', 'opening', 'open', 'closing'
|
||||
openHeight: h * 0.95,
|
||||
currentOffset: 0,
|
||||
autoCloseTimer: 0
|
||||
};
|
||||
|
||||
this.doors.push(doorObj);
|
||||
return doorObj;
|
||||
}
|
||||
|
||||
interactWithDoors(playerPos, keysInventory) {
|
||||
for (let i = 0; i < this.doors.length; i++) {
|
||||
const door = this.doors[i];
|
||||
const dist = playerPos.distanceTo(door.mesh.position);
|
||||
if (dist < 5.5) {
|
||||
if (door.requiredKey && !keysInventory[door.requiredKey]) {
|
||||
if (window.game && window.game.hud) {
|
||||
window.game.hud.showNotification(`YOU NEED THE ${door.requiredKey.toUpperCase()} KEY`);
|
||||
}
|
||||
if (this.sound) this.sound.playSlimeHurt();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (door.state === 'closed' || door.state === 'closing') {
|
||||
door.state = 'opening';
|
||||
if (this.sound) this.sound.playDoor(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// MOTORIZED ELEVATOR PLATFORMS
|
||||
// ----------------------------------------------------
|
||||
|
||||
createElevator(x, y, z, w, d, minY, maxY, speed = 2.0) {
|
||||
const geo = new THREE.BoxGeometry(w, 0.8, d);
|
||||
const mesh = new THREE.Mesh(geo, this.engine.materials.hex);
|
||||
mesh.position.set(x, y + 0.4, z);
|
||||
mesh.receiveShadow = true;
|
||||
this.engine.scene.add(mesh);
|
||||
|
||||
const min = new THREE.Vector3(x - w / 2, y, z - d / 2);
|
||||
const max = new THREE.Vector3(x + w / 2, y + 0.8, z + d / 2);
|
||||
const box = new THREE.Box3(min, max);
|
||||
this.colliders.push(box);
|
||||
|
||||
this.elevators.push({
|
||||
mesh,
|
||||
box,
|
||||
x, z, w, d,
|
||||
currentY: y,
|
||||
minY,
|
||||
maxY,
|
||||
speed,
|
||||
dir: 1, // 1 = up, -1 = down
|
||||
pauseTimer: 0
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// MAP GAME LOOP UPDATE
|
||||
// ----------------------------------------------------
|
||||
|
||||
update(delta) {
|
||||
// 1. Update Doors
|
||||
for (let i = 0; i < this.doors.length; i++) {
|
||||
const door = this.doors[i];
|
||||
if (door.state === 'opening') {
|
||||
door.currentOffset += delta * 4.0;
|
||||
if (door.currentOffset >= door.openHeight) {
|
||||
door.currentOffset = door.openHeight;
|
||||
door.state = 'open';
|
||||
door.autoCloseTimer = 5.0; // stay open for 5 sec
|
||||
}
|
||||
door.mesh.position.y = (door.y + door.h / 2) + door.currentOffset;
|
||||
// Move collider out of way
|
||||
door.box.min.y = door.y + door.currentOffset;
|
||||
door.box.max.y = door.y + door.h + door.currentOffset;
|
||||
} else if (door.state === 'open') {
|
||||
door.autoCloseTimer -= delta;
|
||||
if (door.autoCloseTimer <= 0) {
|
||||
door.state = 'closing';
|
||||
if (this.sound) this.sound.playDoor(false);
|
||||
}
|
||||
} else if (door.state === 'closing') {
|
||||
door.currentOffset -= delta * 4.0;
|
||||
if (door.currentOffset <= 0) {
|
||||
door.currentOffset = 0;
|
||||
door.state = 'closed';
|
||||
}
|
||||
door.mesh.position.y = (door.y + door.h / 2) + door.currentOffset;
|
||||
door.box.min.y = door.y + door.currentOffset;
|
||||
door.box.max.y = door.y + door.h + door.currentOffset;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Update Motorized Elevators
|
||||
for (let i = 0; i < this.elevators.length; i++) {
|
||||
const el = this.elevators[i];
|
||||
if (el.pauseTimer > 0) {
|
||||
el.pauseTimer -= delta;
|
||||
continue;
|
||||
}
|
||||
|
||||
el.currentY += el.dir * el.speed * delta;
|
||||
if (el.currentY >= el.maxY) {
|
||||
el.currentY = el.maxY;
|
||||
el.dir = -1;
|
||||
el.pauseTimer = 3.0;
|
||||
} else if (el.currentY <= el.minY) {
|
||||
el.currentY = el.minY;
|
||||
el.dir = 1;
|
||||
el.pauseTimer = 3.0;
|
||||
}
|
||||
|
||||
el.mesh.position.y = el.currentY + 0.4;
|
||||
el.box.min.y = el.currentY;
|
||||
el.box.max.y = el.currentY + 0.8;
|
||||
|
||||
// Lift player if player is on elevator
|
||||
if (window.game && window.game.player) {
|
||||
const p = window.game.player;
|
||||
if (p.position.x >= el.box.min.x && p.position.x <= el.box.max.x &&
|
||||
p.position.z >= el.box.min.z && p.position.z <= el.box.max.z &&
|
||||
Math.abs(p.position.y - el.box.max.y) < 0.25) {
|
||||
p.position.y = el.box.max.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.DoomMap = DoomMap;
|
||||
Reference in new issue
Block a user