Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 39 additions & 39 deletions game/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BACKROOMS MMO</title>
<link rel="stylesheet" href="css/style.css?v=294">
<link rel="stylesheet" href="css/title-interface.css?v=294">
<link rel="stylesheet" href="css/style.css?v=295">
<link rel="stylesheet" href="css/title-interface.css?v=295">
</head>
<body>
<div id="app">
Expand Down Expand Up @@ -391,44 +391,44 @@ <h3>Tu recorrido</h3>

<!-- ?v= rompe la caché del edge (Cloudflare cachea .js/.css por defecto aunque
el origen no lo pida): SUBIR el número en cada versión, junto con VERSION_JUEGO -->
<script src="js/data.js?v=294"></script>
<script src="js/engine/rng.js?v=294"></script>
<script src="js/mapgen/mapgen.js?v=294"></script>
<script src="js/engine/tiles.js?v=294"></script>
<script src="js/engine/sprites.js?v=294"></script>
<script src="js/engine/effects.js?v=294"></script>
<script src="js/audio-manifest.js?v=294"></script>
<script src="js/assets-manifest.js?v=294"></script>
<script src="js/engine/sfx.js?v=294"></script>
<script src="js/engine/fov.js?v=294"></script>
<script src="js/systems/entities.js?v=294"></script>
<script src="js/systems/rules.js?v=294"></script>
<script src="js/engine/render.js?v=294"></script>
<script src="js/lib/three.min.js?v=294"></script>
<script src="js/lib/shaders/CopyShader.js?v=294"></script>
<script src="js/lib/shaders/LuminosityHighPassShader.js?v=294"></script>
<script src="js/lib/shaders/GammaCorrectionShader.js?v=294"></script>
<script src="js/lib/postprocessing/EffectComposer.js?v=294"></script>
<script src="js/lib/postprocessing/RenderPass.js?v=294"></script>
<script src="js/lib/postprocessing/ShaderPass.js?v=294"></script>
<script src="js/lib/postprocessing/UnrealBloomPass.js?v=294"></script>
<script src="js/engine/atmos3d.js?v=294"></script>
<script src="js/engine/render3d.js?v=294"></script>
<script src="js/systems/game.js?v=294"></script>
<script src="js/ui/icons.js?v=294"></script>
<script src="js/ui/controllers.js?v=294"></script>
<script src="js/ui/ui.js?v=294"></script>
<script src="js/ui/title-interface.js?v=294"></script>
<script src="js/ui/changelog.js?v=294"></script>
<script src="js/ui/minimap.js?v=294"></script>
<script src="js/sim/fisica.js?v=294"></script>
<script src="js/data.js?v=295"></script>
<script src="js/engine/rng.js?v=295"></script>
<script src="js/mapgen/mapgen.js?v=295"></script>
<script src="js/engine/tiles.js?v=295"></script>
<script src="js/engine/sprites.js?v=295"></script>
<script src="js/engine/effects.js?v=295"></script>
<script src="js/audio-manifest.js?v=295"></script>
<script src="js/assets-manifest.js?v=295"></script>
<script src="js/engine/sfx.js?v=295"></script>
<script src="js/engine/fov.js?v=295"></script>
<script src="js/systems/entities.js?v=295"></script>
<script src="js/systems/rules.js?v=295"></script>
<script src="js/engine/render.js?v=295"></script>
<script src="js/lib/three.min.js?v=295"></script>
<script src="js/lib/shaders/CopyShader.js?v=295"></script>
<script src="js/lib/shaders/LuminosityHighPassShader.js?v=295"></script>
<script src="js/lib/shaders/GammaCorrectionShader.js?v=295"></script>
<script src="js/lib/postprocessing/EffectComposer.js?v=295"></script>
<script src="js/lib/postprocessing/RenderPass.js?v=295"></script>
<script src="js/lib/postprocessing/ShaderPass.js?v=295"></script>
<script src="js/lib/postprocessing/UnrealBloomPass.js?v=295"></script>
<script src="js/engine/atmos3d.js?v=295"></script>
<script src="js/engine/render3d.js?v=295"></script>
<script src="js/systems/game.js?v=295"></script>
<script src="js/ui/icons.js?v=295"></script>
<script src="js/ui/controllers.js?v=295"></script>
<script src="js/ui/ui.js?v=295"></script>
<script src="js/ui/title-interface.js?v=295"></script>
<script src="js/ui/changelog.js?v=295"></script>
<script src="js/ui/minimap.js?v=295"></script>
<script src="js/sim/fisica.js?v=295"></script>
<!-- reglas del mundo COMPARTIDAS con el servidor (duales): IA + Sala; el modo
offline las ejecuta en la pestaña vía el servidor local de net/local.js -->
<script src="js/sim/entidades.js?v=294"></script>
<script src="js/sim/sala.js?v=294"></script>
<script src="js/net/local.js?v=294"></script>
<script src="js/net/otros.js?v=294"></script>
<script src="js/net/cliente.js?v=294"></script>
<script src="js/main.js?v=294"></script>
<script src="js/sim/entidades.js?v=295"></script>
<script src="js/sim/sala.js?v=295"></script>
<script src="js/net/local.js?v=295"></script>
<script src="js/net/otros.js?v=295"></script>
<script src="js/net/cliente.js?v=295"></script>
<script src="js/main.js?v=295"></script>
</body>
</html>
14 changes: 13 additions & 1 deletion game/js/engine/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@
const g = world.map.grid;
const cam = world.camera;
const dark = world.level.oscuridad;
const apagon = world.apagonIntensidad ? world.apagonIntensidad(t) : 0;

const [shx, shy] = window.NOFX ? [0, 0] : Effects.shakeOffset(t);
ctx.save();
Expand All @@ -554,7 +555,7 @@
let flicker = 1;
if (Math.random() < 0.012) flicker = 0.72;
world._flicker = world._flicker === undefined ? 1 : world._flicker * 0.85 + flicker * 0.15;
const fl = world._flicker;
const fl = world._flicker * (1 - apagon * 0.88);

const x0 = Math.max(0, Math.floor(cam.x / TILE) - 1);
const y0 = Math.max(0, Math.floor(cam.y / TILE) - 1);
Expand Down Expand Up @@ -790,6 +791,17 @@
ctx.fillRect(0, 0, W, H);
}

if (apagon > 0.001) {
const px = world.player.rx * TILE - cam.x + TILE / 2;
const py = world.player.ry * TILE - cam.y + TILE / 2;
const radio = TILE * (world.player.luz ? 4.5 : 1.8);
const sombra = ctx.createRadialGradient(px, py, 8, px, py, radio);
sombra.addColorStop(0, `rgba(0,2,7,${apagon * (world.player.luz ? 0.08 : 0.34)})`);
sombra.addColorStop(1, `rgba(0,1,5,${apagon * 0.78})`);
ctx.fillStyle = sombra;
ctx.fillRect(0, 0, W, H);
}

if (!window.NOFX) {
const vin = ctx.createRadialGradient(W / 2, H / 2, H * 0.36, W / 2, H / 2, H * 0.78);
vin.addColorStop(0, 'rgba(0,0,0,0)');
Expand Down
52 changes: 46 additions & 6 deletions game/js/engine/render3d.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
let composer = null; // postprocesado (bloom + gamma); null => render directo
let bloomPass = null; // pase de bloom para el pulso dinámico por cordura baja
let fogBase = 0.08;
let ambBase = 0.35;
let dlightBase = 0.35;
let exposureBase = 1.1;
let nivelClaro = 0; // [0,1] cuánto se pasa de CLARA la paleta del nivel (v30.2)
let glCanvas, overlay, octx, W, H;
let levelKey = null;
Expand Down Expand Up @@ -1237,10 +1240,13 @@
const cSuelo = new THREE.Color(pal.suelo);
const lumSuelo = 0.2126 * cSuelo.r + 0.7152 * cSuelo.g + 0.0722 * cSuelo.b;
nivelClaro = Math.max(0, Math.min(1, (lumSuelo - 0.55) / 0.35));
amb.intensity = esLevel0 ? 0.22
ambBase = esLevel0 ? 0.22
: Math.max(0.12, 0.55 - world.level.oscuridad * 0.4) * (1 - 0.35 * nivelClaro);
dlight.intensity = esLevel0 ? 0.14 : 0.35 * (1 - 0.3 * nivelClaro);
renderer.toneMappingExposure = esLevel0 ? 0.96 : 1.15 - 0.25 * nivelClaro;
dlightBase = esLevel0 ? 0.14 : 0.35 * (1 - 0.3 * nivelClaro);
exposureBase = esLevel0 ? 0.96 : 1.15 - 0.25 * nivelClaro;
amb.intensity = ambBase;
dlight.intensity = dlightBase;
renderer.toneMappingExposure = exposureBase;
plight.color = new THREE.Color(pal.luz);
plight.distance = (world.visionActual() + 3) * 1.6;
plight.castShadow = !esLevel0;
Expand Down Expand Up @@ -1501,6 +1507,7 @@

const p = world.player;
const px = p.rx + 0.5, pz = p.ry + 0.5;
const apagon = world.apagonIntensidad ? world.apagonIntensidad(t) : 0;
if (!playerSprite) {
playerSprite = new THREE.Sprite(new THREE.SpriteMaterial({ transparent: true }));
playerSprite.scale.set(1, SPRITE_H, 1);
Expand Down Expand Up @@ -1683,6 +1690,7 @@
const locuraFondo = new THREE.Color(0x13010b);
baseFondo.lerp(locuraFondo, sc * 0.85);
}
if (apagon > 0) baseFondo.multiplyScalar(1 - apagon * 0.78);
scene.background.copy(baseFondo);
scene.fog.color.copy(baseFondo);
}
Expand All @@ -1699,6 +1707,7 @@
targetBloom = 0.55 + sc * 0.65 + pulso;
targetThreshold = 0.82 - sc * 0.3; // con locura baja hasta 0.52: brilla, no ciega
}
targetBloom *= 1 - apagon * 0.72;
bloomPass.strength = targetBloom;
bloomPass.threshold = targetThreshold;
}
Expand All @@ -1713,7 +1722,8 @@
// en paletas casi blancas la luz del jugador quema un disco alrededor
// (albedo claro × 1.7 satura el ACES): se normaliza con nivelClaro
const luzJugador = world.level.id === 'level-0' ? 0.72 : 1.7 * (1 - 0.5 * nivelClaro);
plight.intensity = plight.intensity * 0.85 + (luzJugador * flicker) * 0.15;
const luzApagon = p.luz ? 1 - apagon * 0.5 : 1 - apagon * 0.92;
plight.intensity = plight.intensity * 0.85 + (luzJugador * flicker * luzApagon) * 0.15;
plight.position.set(px, 1.6, pz);
plight.distance = (world.visionActual() + 3) * (p.luz ? 2.4 : 1.6);

Expand Down Expand Up @@ -1746,6 +1756,7 @@
const sc = (50 - cordura) / 50;
targetFogBase = fogBase * (1 + sc * 0.9); // hasta +90% de niebla (claustrofobia)
}
if (!luzOn) targetFogBase *= 1 + apagon * 0.38;
// espectador (v30): desde 14 de altura la niebla normal funde el suelo
// a negro — casi fuera, que el guardián vea el plano entero
if (world.espectador) targetFogBase = Math.min(fogBase, 0.015);
Expand Down Expand Up @@ -1789,6 +1800,26 @@
}
actualizarLucesTecho(world, px, pz, fase0);

// Apagón sincronizado de Level 1: caen todas las luminarias del mapa,
// mientras el foco de la linterna sigue siendo útil.
if (world.level.id === 'level-1') {
const corriente = Math.max(0.025, 1 - apagon);
const objetivoAmb = ambBase * (0.06 + corriente * 0.94);
amb.intensity += (objetivoAmb - amb.intensity) * 0.28;
const objetivoCenital = dlightBase * corriente;
dlight.intensity += (objetivoCenital - dlight.intensity) * 0.35;
for (const l of ceilingLights) l.intensity *= corriente;
panelMats.forEach((m, i) => {
if (!m) return;
const falla = i === flkGrupo && !flkOn ? 0.18 : 1;
const f = corriente * falla;
m.color.setRGB(f, 0.965 * f, 0.863 * f);
});
const objetivoExposicion = exposureBase * (0.48 + corriente * 0.52);
renderer.toneMappingExposure +=
(objetivoExposicion - renderer.toneMappingExposure) * 0.24;
}

// ---------- modo espectador (v30): casa de muñecas ----------
// el techo y sus fluorescentes taparían TODO desde una cámara cenital:
// sus materiales se apagan mientras dure (se re-aplica cada frame porque
Expand Down Expand Up @@ -1977,7 +2008,7 @@
} else {
renderer.render(scene, camera);
}
drawOverlay(world, t);
drawOverlay(world, t, apagon);

if (window.DEBUG3D_ON) {
window.DEBUG3D = {
Expand All @@ -1999,7 +2030,7 @@
return [(v.x * 0.5 + 0.5) * W, (-v.y * 0.5 + 0.5) * H, v.z > 1];
}

function drawOverlay(world, t) {
function drawOverlay(world, t, apagon) {
octx.clearRect(0, 0, W, H);
if (!window.NOFX) Effects.draw(octx, 0, 0, t, 48, project);
// capa social del MMO: nombres flotantes y bocadillos de chat
Expand Down Expand Up @@ -2028,6 +2059,15 @@
octx.fillStyle = `rgba(235,110,30,${resp})`;
octx.fillRect(0, 0, W, H);
}
if (apagon > 0.001) {
const sombra = octx.createRadialGradient(
W / 2, H * 0.56, H * 0.08,
W / 2, H * 0.56, Math.max(W, H) * 0.72);
sombra.addColorStop(0, `rgba(0,2,8,${apagon * (world.player.luz ? 0.02 : 0.13)})`);
sombra.addColorStop(1, `rgba(0,1,6,${apagon * 0.42})`);
octx.fillStyle = sombra;
octx.fillRect(0, 0, W, H);
}
const fase0 = level0Phase(world);
if (fase0 > 0) {
octx.fillStyle = `rgba(95,115,125,${0.055 * fase0})`;
Expand Down
67 changes: 63 additions & 4 deletions game/js/engine/sfx.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,43 @@
} catch (e) {}
}

// Level 1: fallo de balastos, corte seco del zumbido y reencendido desigual.
// También se hunde el bus ambiental para que el silencio se note de verdad;
// los pasos, entidades y la linterna conservan su propio canal.
function level1Blackout(fase) {
try {
if (!ctx || muted) return;
const ahora = ctx.currentTime;
const destinoAmb = fase === 'oscuro' ? volAmb * 0.07
: fase === 'pre' ? volAmb * 0.42 : volAmb;
const factor = volAmb > 0 ? destinoAmb / volAmb : 1;
ambBus.gain.cancelScheduledValues(ahora);
ambBus.gain.setValueAtTime(Math.max(0.001, ambBus.gain.value), ahora);
ambBus.gain.exponentialRampToValueAtTime(
Math.max(0.001, destinoAmb),
ahora + (fase === 'vuelve' ? 1.45 : 0.22));
if (ambientAudioEl)
ambientAudioEl.volume = Math.min(1, 0.62 * vol * volAmb * factor);

if (fase === 'pre') {
ruido(0.18, 3600, 0.11, 'bandpass', 700);
tono(118, 0.42, 0.075, 'square', 82);
setTimeout(() => ctx && !muted && ruido(0.08, 2600, 0.09, 'bandpass', 500), 420);
setTimeout(() => ctx && !muted && ruido(0.06, 3300, 0.075, 'bandpass', 650), 820);
} else if (fase === 'oscuro') {
ruido(0.075, 2100, 0.16, 'bandpass', 180);
tono(92, 0.28, 0.12, 'square', 38);
setTimeout(() => ctx && !muted && tono(34, 0.8, 0.06, 'sine', 27), 90);
} else if (fase === 'vuelve') {
ruido(0.09, 2900, 0.12, 'bandpass', 700);
tono(76, 0.18, 0.08, 'square', 108);
setTimeout(() => ctx && !muted && ruido(0.055, 3900, 0.08, 'bandpass', 1200), 260);
setTimeout(() => ctx && !muted && tono(120, 0.32, 0.045, 'sine'), 620);
setTimeout(() => ctx && !muted && ruido(0.05, 3400, 0.055, 'bandpass', 1000), 980);
}
} catch (e) {}
}

// ---------- recetas de ambiente por nivel ----------
// cada receta devuelve nodos; el gain común hace fade in/out
const RECETAS = {
Expand Down Expand Up @@ -541,6 +578,14 @@
try {
stopAmbient();
if (muted) return;
const apagado = levelDef.id === 'level-1' && window.Game?.world?.apagon;
const factorApagon = apagado?.fase === 'oscuro' ? 0.07
: apagado?.fase === 'pre' ? 0.42
: apagado?.fase === 'vuelve' ? 0.7 : 1;
if (ctx && ambBus) {
ambBus.gain.cancelScheduledValues(ctx.currentTime);
ambBus.gain.setTargetAtTime(volAmb * factorApagon, ctx.currentTime, 0.08);
}
const gen = ++ambientGen;
// 1) archivo del nivel (del usuario o de la wiki) — SOLO rutas que
// existen según los manifiestos (v30.6: antes se probaban 3 extensiones
Expand All @@ -562,7 +607,7 @@
}
const el = new window.Audio(candidatos[i++]);
el.loop = true;
el.volume = Math.min(1, 0.62 * vol * volAmb);
el.volume = Math.min(1, 0.62 * vol * volAmb * factorApagon);
// un archivo fallido dispara TANTO el evento 'error' COMO el rechazo de
// play(): sin este candado la cadena se bifurcaba en dos (acumulación)
let siguiente = false;
Expand Down Expand Up @@ -601,9 +646,23 @@
} else if (canal === 'amb') {
volAmb = v;
try { localStorage.setItem('backrooms-volamb', String(volAmb)); } catch (e) {}
if (ambBus) ambBus.gain.value = volAmb;
if (ambBus) {
const a = window.Game?.world?.level?.id === 'level-1'
? window.Game.world.apagon : null;
const factor = a?.fase === 'oscuro' ? 0.07
: a?.fase === 'pre' ? 0.42
: a?.fase === 'vuelve' ? 0.7 : 1;
ambBus.gain.value = volAmb * factor;
}
}
if (ambientAudioEl) {
const a = window.Game?.world?.level?.id === 'level-1'
? window.Game.world.apagon : null;
const factor = a?.fase === 'oscuro' ? 0.07
: a?.fase === 'pre' ? 0.42
: a?.fase === 'vuelve' ? 0.7 : 1;
ambientAudioEl.volume = Math.min(1, 0.62 * vol * volAmb * factor);
}
if (ambientAudioEl) ambientAudioEl.volume = Math.min(1, 0.62 * vol * volAmb);
if (menuAudioEl) menuAudioEl.volume = Math.min(1, 0.62 * vol * volAmb);
}

Expand Down Expand Up @@ -693,7 +752,7 @@

window.Sfx = {
unlock, cargarOverrides, play, cue, cueDist, entityLoop, updateEntityLoops, ambient, stopAmbient, toggleMute, setVolume, idle,
level0Flicker, playMenu, stopMenu,
level0Flicker, level1Blackout, playMenu, stopMenu,
get muted() { return muted; },
get volumen() { return vol; },
get volumenFx() { return volFx; },
Expand Down
2 changes: 1 addition & 1 deletion game/js/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Arranque: input, bucle de animación y pantalla de título.
(function () {
// versión visible del juego (Ajustes); súbela con cada tanda de cambios
window.VERSION_JUEGO = 'v30.12';
window.VERSION_JUEGO = 'v30.13';
const world = Game.world;
world.data = window.GAME_DATA;

Expand Down
Loading
Loading