Skip to content

Commit 4a37c4b

Browse files
authored
Merge pull request #77 from josealmon/agent/corrige-muerte-instantanea-sed
Añade apagones globales al Level 1 y ajusta la sed
2 parents 351731c + e6d051b commit 4a37c4b

13 files changed

Lines changed: 409 additions & 57 deletions

File tree

game/index.html

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<meta charset="utf-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<title>BACKROOMS MMO</title>
7-
<link rel="stylesheet" href="css/style.css?v=294">
8-
<link rel="stylesheet" href="css/title-interface.css?v=294">
7+
<link rel="stylesheet" href="css/style.css?v=295">
8+
<link rel="stylesheet" href="css/title-interface.css?v=295">
99
</head>
1010
<body>
1111
<div id="app">
@@ -391,44 +391,44 @@ <h3>Tu recorrido</h3>
391391

392392
<!-- ?v= rompe la caché del edge (Cloudflare cachea .js/.css por defecto aunque
393393
el origen no lo pida): SUBIR el número en cada versión, junto con VERSION_JUEGO -->
394-
<script src="js/data.js?v=294"></script>
395-
<script src="js/engine/rng.js?v=294"></script>
396-
<script src="js/mapgen/mapgen.js?v=294"></script>
397-
<script src="js/engine/tiles.js?v=294"></script>
398-
<script src="js/engine/sprites.js?v=294"></script>
399-
<script src="js/engine/effects.js?v=294"></script>
400-
<script src="js/audio-manifest.js?v=294"></script>
401-
<script src="js/assets-manifest.js?v=294"></script>
402-
<script src="js/engine/sfx.js?v=294"></script>
403-
<script src="js/engine/fov.js?v=294"></script>
404-
<script src="js/systems/entities.js?v=294"></script>
405-
<script src="js/systems/rules.js?v=294"></script>
406-
<script src="js/engine/render.js?v=294"></script>
407-
<script src="js/lib/three.min.js?v=294"></script>
408-
<script src="js/lib/shaders/CopyShader.js?v=294"></script>
409-
<script src="js/lib/shaders/LuminosityHighPassShader.js?v=294"></script>
410-
<script src="js/lib/shaders/GammaCorrectionShader.js?v=294"></script>
411-
<script src="js/lib/postprocessing/EffectComposer.js?v=294"></script>
412-
<script src="js/lib/postprocessing/RenderPass.js?v=294"></script>
413-
<script src="js/lib/postprocessing/ShaderPass.js?v=294"></script>
414-
<script src="js/lib/postprocessing/UnrealBloomPass.js?v=294"></script>
415-
<script src="js/engine/atmos3d.js?v=294"></script>
416-
<script src="js/engine/render3d.js?v=294"></script>
417-
<script src="js/systems/game.js?v=294"></script>
418-
<script src="js/ui/icons.js?v=294"></script>
419-
<script src="js/ui/controllers.js?v=294"></script>
420-
<script src="js/ui/ui.js?v=294"></script>
421-
<script src="js/ui/title-interface.js?v=294"></script>
422-
<script src="js/ui/changelog.js?v=294"></script>
423-
<script src="js/ui/minimap.js?v=294"></script>
424-
<script src="js/sim/fisica.js?v=294"></script>
394+
<script src="js/data.js?v=295"></script>
395+
<script src="js/engine/rng.js?v=295"></script>
396+
<script src="js/mapgen/mapgen.js?v=295"></script>
397+
<script src="js/engine/tiles.js?v=295"></script>
398+
<script src="js/engine/sprites.js?v=295"></script>
399+
<script src="js/engine/effects.js?v=295"></script>
400+
<script src="js/audio-manifest.js?v=295"></script>
401+
<script src="js/assets-manifest.js?v=295"></script>
402+
<script src="js/engine/sfx.js?v=295"></script>
403+
<script src="js/engine/fov.js?v=295"></script>
404+
<script src="js/systems/entities.js?v=295"></script>
405+
<script src="js/systems/rules.js?v=295"></script>
406+
<script src="js/engine/render.js?v=295"></script>
407+
<script src="js/lib/three.min.js?v=295"></script>
408+
<script src="js/lib/shaders/CopyShader.js?v=295"></script>
409+
<script src="js/lib/shaders/LuminosityHighPassShader.js?v=295"></script>
410+
<script src="js/lib/shaders/GammaCorrectionShader.js?v=295"></script>
411+
<script src="js/lib/postprocessing/EffectComposer.js?v=295"></script>
412+
<script src="js/lib/postprocessing/RenderPass.js?v=295"></script>
413+
<script src="js/lib/postprocessing/ShaderPass.js?v=295"></script>
414+
<script src="js/lib/postprocessing/UnrealBloomPass.js?v=295"></script>
415+
<script src="js/engine/atmos3d.js?v=295"></script>
416+
<script src="js/engine/render3d.js?v=295"></script>
417+
<script src="js/systems/game.js?v=295"></script>
418+
<script src="js/ui/icons.js?v=295"></script>
419+
<script src="js/ui/controllers.js?v=295"></script>
420+
<script src="js/ui/ui.js?v=295"></script>
421+
<script src="js/ui/title-interface.js?v=295"></script>
422+
<script src="js/ui/changelog.js?v=295"></script>
423+
<script src="js/ui/minimap.js?v=295"></script>
424+
<script src="js/sim/fisica.js?v=295"></script>
425425
<!-- reglas del mundo COMPARTIDAS con el servidor (duales): IA + Sala; el modo
426426
offline las ejecuta en la pestaña vía el servidor local de net/local.js -->
427-
<script src="js/sim/entidades.js?v=294"></script>
428-
<script src="js/sim/sala.js?v=294"></script>
429-
<script src="js/net/local.js?v=294"></script>
430-
<script src="js/net/otros.js?v=294"></script>
431-
<script src="js/net/cliente.js?v=294"></script>
432-
<script src="js/main.js?v=294"></script>
427+
<script src="js/sim/entidades.js?v=295"></script>
428+
<script src="js/sim/sala.js?v=295"></script>
429+
<script src="js/net/local.js?v=295"></script>
430+
<script src="js/net/otros.js?v=295"></script>
431+
<script src="js/net/cliente.js?v=295"></script>
432+
<script src="js/main.js?v=295"></script>
433433
</body>
434434
</html>

game/js/engine/render.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@
543543
const g = world.map.grid;
544544
const cam = world.camera;
545545
const dark = world.level.oscuridad;
546+
const apagon = world.apagonIntensidad ? world.apagonIntensidad(t) : 0;
546547

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

559560
const x0 = Math.max(0, Math.floor(cam.x / TILE) - 1);
560561
const y0 = Math.max(0, Math.floor(cam.y / TILE) - 1);
@@ -790,6 +791,17 @@
790791
ctx.fillRect(0, 0, W, H);
791792
}
792793

794+
if (apagon > 0.001) {
795+
const px = world.player.rx * TILE - cam.x + TILE / 2;
796+
const py = world.player.ry * TILE - cam.y + TILE / 2;
797+
const radio = TILE * (world.player.luz ? 4.5 : 1.8);
798+
const sombra = ctx.createRadialGradient(px, py, 8, px, py, radio);
799+
sombra.addColorStop(0, `rgba(0,2,7,${apagon * (world.player.luz ? 0.08 : 0.34)})`);
800+
sombra.addColorStop(1, `rgba(0,1,5,${apagon * 0.78})`);
801+
ctx.fillStyle = sombra;
802+
ctx.fillRect(0, 0, W, H);
803+
}
804+
793805
if (!window.NOFX) {
794806
const vin = ctx.createRadialGradient(W / 2, H / 2, H * 0.36, W / 2, H / 2, H * 0.78);
795807
vin.addColorStop(0, 'rgba(0,0,0,0)');

game/js/engine/render3d.js

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
let composer = null; // postprocesado (bloom + gamma); null => render directo
3131
let bloomPass = null; // pase de bloom para el pulso dinámico por cordura baja
3232
let fogBase = 0.08;
33+
let ambBase = 0.35;
34+
let dlightBase = 0.35;
35+
let exposureBase = 1.1;
3336
let nivelClaro = 0; // [0,1] cuánto se pasa de CLARA la paleta del nivel (v30.2)
3437
let glCanvas, overlay, octx, W, H;
3538
let levelKey = null;
@@ -1237,10 +1240,13 @@
12371240
const cSuelo = new THREE.Color(pal.suelo);
12381241
const lumSuelo = 0.2126 * cSuelo.r + 0.7152 * cSuelo.g + 0.0722 * cSuelo.b;
12391242
nivelClaro = Math.max(0, Math.min(1, (lumSuelo - 0.55) / 0.35));
1240-
amb.intensity = esLevel0 ? 0.22
1243+
ambBase = esLevel0 ? 0.22
12411244
: Math.max(0.12, 0.55 - world.level.oscuridad * 0.4) * (1 - 0.35 * nivelClaro);
1242-
dlight.intensity = esLevel0 ? 0.14 : 0.35 * (1 - 0.3 * nivelClaro);
1243-
renderer.toneMappingExposure = esLevel0 ? 0.96 : 1.15 - 0.25 * nivelClaro;
1245+
dlightBase = esLevel0 ? 0.14 : 0.35 * (1 - 0.3 * nivelClaro);
1246+
exposureBase = esLevel0 ? 0.96 : 1.15 - 0.25 * nivelClaro;
1247+
amb.intensity = ambBase;
1248+
dlight.intensity = dlightBase;
1249+
renderer.toneMappingExposure = exposureBase;
12441250
plight.color = new THREE.Color(pal.luz);
12451251
plight.distance = (world.visionActual() + 3) * 1.6;
12461252
plight.castShadow = !esLevel0;
@@ -1501,6 +1507,7 @@
15011507

15021508
const p = world.player;
15031509
const px = p.rx + 0.5, pz = p.ry + 0.5;
1510+
const apagon = world.apagonIntensidad ? world.apagonIntensidad(t) : 0;
15041511
if (!playerSprite) {
15051512
playerSprite = new THREE.Sprite(new THREE.SpriteMaterial({ transparent: true }));
15061513
playerSprite.scale.set(1, SPRITE_H, 1);
@@ -1683,6 +1690,7 @@
16831690
const locuraFondo = new THREE.Color(0x13010b);
16841691
baseFondo.lerp(locuraFondo, sc * 0.85);
16851692
}
1693+
if (apagon > 0) baseFondo.multiplyScalar(1 - apagon * 0.78);
16861694
scene.background.copy(baseFondo);
16871695
scene.fog.color.copy(baseFondo);
16881696
}
@@ -1699,6 +1707,7 @@
16991707
targetBloom = 0.55 + sc * 0.65 + pulso;
17001708
targetThreshold = 0.82 - sc * 0.3; // con locura baja hasta 0.52: brilla, no ciega
17011709
}
1710+
targetBloom *= 1 - apagon * 0.72;
17021711
bloomPass.strength = targetBloom;
17031712
bloomPass.threshold = targetThreshold;
17041713
}
@@ -1713,7 +1722,8 @@
17131722
// en paletas casi blancas la luz del jugador quema un disco alrededor
17141723
// (albedo claro × 1.7 satura el ACES): se normaliza con nivelClaro
17151724
const luzJugador = world.level.id === 'level-0' ? 0.72 : 1.7 * (1 - 0.5 * nivelClaro);
1716-
plight.intensity = plight.intensity * 0.85 + (luzJugador * flicker) * 0.15;
1725+
const luzApagon = p.luz ? 1 - apagon * 0.5 : 1 - apagon * 0.92;
1726+
plight.intensity = plight.intensity * 0.85 + (luzJugador * flicker * luzApagon) * 0.15;
17171727
plight.position.set(px, 1.6, pz);
17181728
plight.distance = (world.visionActual() + 3) * (p.luz ? 2.4 : 1.6);
17191729

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

1803+
// Apagón sincronizado de Level 1: caen todas las luminarias del mapa,
1804+
// mientras el foco de la linterna sigue siendo útil.
1805+
if (world.level.id === 'level-1') {
1806+
const corriente = Math.max(0.025, 1 - apagon);
1807+
const objetivoAmb = ambBase * (0.06 + corriente * 0.94);
1808+
amb.intensity += (objetivoAmb - amb.intensity) * 0.28;
1809+
const objetivoCenital = dlightBase * corriente;
1810+
dlight.intensity += (objetivoCenital - dlight.intensity) * 0.35;
1811+
for (const l of ceilingLights) l.intensity *= corriente;
1812+
panelMats.forEach((m, i) => {
1813+
if (!m) return;
1814+
const falla = i === flkGrupo && !flkOn ? 0.18 : 1;
1815+
const f = corriente * falla;
1816+
m.color.setRGB(f, 0.965 * f, 0.863 * f);
1817+
});
1818+
const objetivoExposicion = exposureBase * (0.48 + corriente * 0.52);
1819+
renderer.toneMappingExposure +=
1820+
(objetivoExposicion - renderer.toneMappingExposure) * 0.24;
1821+
}
1822+
17921823
// ---------- modo espectador (v30): casa de muñecas ----------
17931824
// el techo y sus fluorescentes taparían TODO desde una cámara cenital:
17941825
// sus materiales se apagan mientras dure (se re-aplica cada frame porque
@@ -1977,7 +2008,7 @@
19772008
} else {
19782009
renderer.render(scene, camera);
19792010
}
1980-
drawOverlay(world, t);
2011+
drawOverlay(world, t, apagon);
19812012

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

2002-
function drawOverlay(world, t) {
2033+
function drawOverlay(world, t, apagon) {
20032034
octx.clearRect(0, 0, W, H);
20042035
if (!window.NOFX) Effects.draw(octx, 0, 0, t, 48, project);
20052036
// capa social del MMO: nombres flotantes y bocadillos de chat
@@ -2028,6 +2059,15 @@
20282059
octx.fillStyle = `rgba(235,110,30,${resp})`;
20292060
octx.fillRect(0, 0, W, H);
20302061
}
2062+
if (apagon > 0.001) {
2063+
const sombra = octx.createRadialGradient(
2064+
W / 2, H * 0.56, H * 0.08,
2065+
W / 2, H * 0.56, Math.max(W, H) * 0.72);
2066+
sombra.addColorStop(0, `rgba(0,2,8,${apagon * (world.player.luz ? 0.02 : 0.13)})`);
2067+
sombra.addColorStop(1, `rgba(0,1,6,${apagon * 0.42})`);
2068+
octx.fillStyle = sombra;
2069+
octx.fillRect(0, 0, W, H);
2070+
}
20312071
const fase0 = level0Phase(world);
20322072
if (fase0 > 0) {
20332073
octx.fillStyle = `rgba(95,115,125,${0.055 * fase0})`;

game/js/engine/sfx.js

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,43 @@
273273
} catch (e) {}
274274
}
275275

276+
// Level 1: fallo de balastos, corte seco del zumbido y reencendido desigual.
277+
// También se hunde el bus ambiental para que el silencio se note de verdad;
278+
// los pasos, entidades y la linterna conservan su propio canal.
279+
function level1Blackout(fase) {
280+
try {
281+
if (!ctx || muted) return;
282+
const ahora = ctx.currentTime;
283+
const destinoAmb = fase === 'oscuro' ? volAmb * 0.07
284+
: fase === 'pre' ? volAmb * 0.42 : volAmb;
285+
const factor = volAmb > 0 ? destinoAmb / volAmb : 1;
286+
ambBus.gain.cancelScheduledValues(ahora);
287+
ambBus.gain.setValueAtTime(Math.max(0.001, ambBus.gain.value), ahora);
288+
ambBus.gain.exponentialRampToValueAtTime(
289+
Math.max(0.001, destinoAmb),
290+
ahora + (fase === 'vuelve' ? 1.45 : 0.22));
291+
if (ambientAudioEl)
292+
ambientAudioEl.volume = Math.min(1, 0.62 * vol * volAmb * factor);
293+
294+
if (fase === 'pre') {
295+
ruido(0.18, 3600, 0.11, 'bandpass', 700);
296+
tono(118, 0.42, 0.075, 'square', 82);
297+
setTimeout(() => ctx && !muted && ruido(0.08, 2600, 0.09, 'bandpass', 500), 420);
298+
setTimeout(() => ctx && !muted && ruido(0.06, 3300, 0.075, 'bandpass', 650), 820);
299+
} else if (fase === 'oscuro') {
300+
ruido(0.075, 2100, 0.16, 'bandpass', 180);
301+
tono(92, 0.28, 0.12, 'square', 38);
302+
setTimeout(() => ctx && !muted && tono(34, 0.8, 0.06, 'sine', 27), 90);
303+
} else if (fase === 'vuelve') {
304+
ruido(0.09, 2900, 0.12, 'bandpass', 700);
305+
tono(76, 0.18, 0.08, 'square', 108);
306+
setTimeout(() => ctx && !muted && ruido(0.055, 3900, 0.08, 'bandpass', 1200), 260);
307+
setTimeout(() => ctx && !muted && tono(120, 0.32, 0.045, 'sine'), 620);
308+
setTimeout(() => ctx && !muted && ruido(0.05, 3400, 0.055, 'bandpass', 1000), 980);
309+
}
310+
} catch (e) {}
311+
}
312+
276313
// ---------- recetas de ambiente por nivel ----------
277314
// cada receta devuelve nodos; el gain común hace fade in/out
278315
const RECETAS = {
@@ -541,6 +578,14 @@
541578
try {
542579
stopAmbient();
543580
if (muted) return;
581+
const apagado = levelDef.id === 'level-1' && window.Game?.world?.apagon;
582+
const factorApagon = apagado?.fase === 'oscuro' ? 0.07
583+
: apagado?.fase === 'pre' ? 0.42
584+
: apagado?.fase === 'vuelve' ? 0.7 : 1;
585+
if (ctx && ambBus) {
586+
ambBus.gain.cancelScheduledValues(ctx.currentTime);
587+
ambBus.gain.setTargetAtTime(volAmb * factorApagon, ctx.currentTime, 0.08);
588+
}
544589
const gen = ++ambientGen;
545590
// 1) archivo del nivel (del usuario o de la wiki) — SOLO rutas que
546591
// existen según los manifiestos (v30.6: antes se probaban 3 extensiones
@@ -562,7 +607,7 @@
562607
}
563608
const el = new window.Audio(candidatos[i++]);
564609
el.loop = true;
565-
el.volume = Math.min(1, 0.62 * vol * volAmb);
610+
el.volume = Math.min(1, 0.62 * vol * volAmb * factorApagon);
566611
// un archivo fallido dispara TANTO el evento 'error' COMO el rechazo de
567612
// play(): sin este candado la cadena se bifurcaba en dos (acumulación)
568613
let siguiente = false;
@@ -601,9 +646,23 @@
601646
} else if (canal === 'amb') {
602647
volAmb = v;
603648
try { localStorage.setItem('backrooms-volamb', String(volAmb)); } catch (e) {}
604-
if (ambBus) ambBus.gain.value = volAmb;
649+
if (ambBus) {
650+
const a = window.Game?.world?.level?.id === 'level-1'
651+
? window.Game.world.apagon : null;
652+
const factor = a?.fase === 'oscuro' ? 0.07
653+
: a?.fase === 'pre' ? 0.42
654+
: a?.fase === 'vuelve' ? 0.7 : 1;
655+
ambBus.gain.value = volAmb * factor;
656+
}
657+
}
658+
if (ambientAudioEl) {
659+
const a = window.Game?.world?.level?.id === 'level-1'
660+
? window.Game.world.apagon : null;
661+
const factor = a?.fase === 'oscuro' ? 0.07
662+
: a?.fase === 'pre' ? 0.42
663+
: a?.fase === 'vuelve' ? 0.7 : 1;
664+
ambientAudioEl.volume = Math.min(1, 0.62 * vol * volAmb * factor);
605665
}
606-
if (ambientAudioEl) ambientAudioEl.volume = Math.min(1, 0.62 * vol * volAmb);
607666
if (menuAudioEl) menuAudioEl.volume = Math.min(1, 0.62 * vol * volAmb);
608667
}
609668

@@ -693,7 +752,7 @@
693752

694753
window.Sfx = {
695754
unlock, cargarOverrides, play, cue, cueDist, entityLoop, updateEntityLoops, ambient, stopAmbient, toggleMute, setVolume, idle,
696-
level0Flicker, playMenu, stopMenu,
755+
level0Flicker, level1Blackout, playMenu, stopMenu,
697756
get muted() { return muted; },
698757
get volumen() { return vol; },
699758
get volumenFx() { return volFx; },

game/js/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Arranque: input, bucle de animación y pantalla de título.
22
(function () {
33
// versión visible del juego (Ajustes); súbela con cada tanda de cambios
4-
window.VERSION_JUEGO = 'v30.12';
4+
window.VERSION_JUEGO = 'v30.13';
55
const world = Game.world;
66
world.data = window.GAME_DATA;
77

0 commit comments

Comments
 (0)