From bd1c6de9f81d0167f8e270e8de71a64e4c5b02a3 Mon Sep 17 00:00:00 2001 From: pkuzic Date: Fri, 25 Sep 2026 03:23:35 +0100 Subject: [PATCH] MONKEY (skybox): zoneSkyboxes cvar, Advanced Graphics row, skybox- capture scenarios Co-Authored-By: Claude Opus 5.5 --- LIGHTING.md | 1 + crates/benilla-app/assets/ui/OptionsFrame.xml | 30 +++++++- crates/benilla-app/src/capture/scenarios.rs | 77 +++++++++++++++++++ crates/benilla-app/src/cvars.rs | 6 ++ crates/benilla-app/src/debug_panel/world.rs | 10 ++- crates/benilla-app/src/game_plugins.rs | 2 + crates/benilla-app/src/lib.rs | 2 + crates/benilla-app/src/zone_skybox.rs | 45 +++++++++++ 8 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 crates/benilla-app/src/zone_skybox.rs diff --git a/LIGHTING.md b/LIGHTING.md index 2122d3df..9a5644ba 100644 --- a/LIGHTING.md +++ b/LIGHTING.md @@ -24,6 +24,7 @@ behind each constant. | Moon shadows | at night the same shadow rig re-aims at the moon and casts a faint shadow; dims only the night sky term, never point lights | `moonShadowStrength` | | Ground-effect spells | Flamestrike, Rain of Fire, Consecration, Flare and fire traps light the ground for their duration; frost and nature areas stay dark | `spellLightGain` | | Volumetric fog | near-field haze that converges on the zone fog colour (clear within 10 yd, full by 150 yd; mistier at dawn and in bad weather, faint indoors) and sun/moon light shafts through gaps, sampled from the shadow map; own fullscreen pass after the main pass (`benilla-app/src/volumetric_fog.rs`) | Advanced Graphics → Volumetric Fog (Off/Low/High), cvar `volumetricFog`, env `WOW_VOLFOG=0\|1\|2` | +| Zone skyboxes | the painted sky a zone's `LightParams` names (clear-weather slot, storm slot lerped by the storm blend) for the living, fading in by the Light sphere falloff with the modern client's crossfade (ghost > WMO > zone); extended `LightSkybox.dbc` flags `0x1` day cycle, `0x2` keep sun/moon/stars/clouds, `0x4` fog-colour horizon cone, plus a celestial second model; every skybox now plays its bones, texture transforms, colour and alpha tracks (`benilla-world/src/skybox.rs`, `skybox_anim.rs`, `benilla-formats/src/light/skybox.rs`) | Advanced Graphics → Zone Skyboxes, cvar `zoneSkyboxes` (0 Off default, 1 On; High = 1), env `WOW_ZONE_SKYBOXES=0\|1` | | Night and interior level | global dimming of the night sky term and of interior ambient | `nightGain`, `interiorGain`, `interiorBakeFloor` | Players reach all of it from **Options -> Advanced Graphics** (a Lighting Quality preset Off / Low / Medium / High plus the individual rows; Off is the original client look). The dev build has a panel for all of it: **Ctrl+Shift+D → Lighting & shadows**, with Dim / Default / diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml index 71e19e82..59bd286d 100644 --- a/crates/benilla-app/assets/ui/OptionsFrame.xml +++ b/crates/benilla-app/assets/ui/OptionsFrame.xml @@ -495,6 +495,8 @@ -- MONKEY (volumetric fog): localised atmospheric quality row. volumetricFog = "Volumetric Fog", fogOff = "Off", + -- MONKEY (skybox): the zone skybox row. + zoneSkyboxes = "Zone Skyboxes", waterQuality = "Water Quality", waterClassic = "Classic", waterEnhanced = "Enhanced", @@ -534,6 +536,12 @@ -- MONKEY (volumetric fog): explain cost and the shadow-map dependency. VOLUMETRIC_FOG = "Off disables volumetric fog. Low adds nearby haze and sun or moon shafts with fewer steps; High uses more steps for smoother shafts. Mist is heavier at dawn and in bad weather, and subtle indoors. Shafts require shadows; with shadows off only haze remains.", + -- MONKEY (skybox): what the zone skybox row changes. + ZONE_SKYBOXES = + "Draws the painted sky a zone's lighting names, as later expansions do: " + .. "the volcanic sky of the Burning Steppes, the alien sky of the Blasted Lands " + .. "and the starry night over Mount Hyjal, fading in at the zone's edge. " + .. "Off keeps the original client's sky everywhere but ghost and dungeon skies. Nearly free.", WATER_QUALITY = "Classic is the original client's water. Enhanced adds soft shorelines, " .. "animated foam around shores and objects, moving wave highlights, sky reflection " @@ -619,6 +627,8 @@ -- MONKEY (volumetric fog): Russian atmospheric quality row. volumetricFog = "Объёмный туман", fogOff = "Выкл", + -- MONKEY (skybox): the zone skybox row. + zoneSkyboxes = "Небосводы зон", waterQuality = "Качество воды", waterClassic = "Классическое", waterEnhanced = "Улучшенное", @@ -660,6 +670,12 @@ -- MONKEY (volumetric fog): localised effect and cost explanation. VOLUMETRIC_FOG = "Выкл отключает объёмный туман. Низкое добавляет дымку вблизи и лучи солнца или луны с меньшим числом шагов; Высокое использует больше шагов для более плавных лучей. Туман гуще на рассвете и в непогоду, а в помещениях слабее. Для лучей нужны тени; без теней остаётся только дымка.", + -- MONKEY (skybox): what the zone skybox row changes. + ZONE_SKYBOXES = + "Рисует нарисованное небо, заданное освещением зоны, как в поздних дополнениях: " + .. "вулканическое небо Пылающих степей, чужое небо Выжженных земель " + .. "и звёздную ночь над горой Хиджал, с плавным появлением на границе зоны. " + .. "«Выкл.» оставляет небо оригинального клиента везде, кроме неба призрака и подземелий. Почти бесплатно.", WATER_QUALITY = "«Классическое» — вода оригинального клиента. «Улучшенное» добавляет плавные переходы у берегов, " .. "анимированную пену у берегов и вокруг объектов, подвижные блики на волнах, отражение неба " @@ -2602,11 +2618,23 @@ }) - + + + + OptionsRow_OnLoad(self, "zoneSkyboxes", + BENILLA_ADVGFX.zoneSkyboxes, + "BENILLA_TOOLTIP_ZONE_SKYBOXES") + + + + + + + OptionsRow_OnLoad(self, "characterShadows", BENILLA_ADVGFX.characterShadows, diff --git a/crates/benilla-app/src/capture/scenarios.rs b/crates/benilla-app/src/capture/scenarios.rs index d970a9fd..07631c96 100644 --- a/crates/benilla-app/src/capture/scenarios.rs +++ b/crates/benilla-app/src/capture/scenarios.rs @@ -1378,6 +1378,72 @@ pub(super) const ON_DEMAND: &[Scenario] = &[ look: LAVA_RIVER_LOOK, minute: 0, ui: None, + }, // MONKEY (skybox): zone skyboxes (run with `WOW_ZONE_SKYBOXES=1`) and the two stock skybox + // lanes. Karazahn40 (Turtle map 814) is the one stock sphere naming a clear-slot skybox + // (Light 553, HellfireSkyBox); the zone shots need the sky patch. + Scenario { + name: "skybox-karazhan-noon", + map: Some(814), + eye: SKYBOX_KZ_EYE, + look: SKYBOX_KZ_LOOK, + minute: 720, + ui: None, + }, + Scenario { + name: "skybox-karazhan-night", + map: Some(814), + eye: SKYBOX_KZ_EYE, + look: SKYBOX_KZ_LOOK, + minute: 0, + ui: None, + }, + Scenario { + name: "skybox-steppes-noon", + map: Some(MAP_AZEROTH), + eye: SKYBOX_STEPPES_EYE, + look: SKYBOX_STEPPES_LOOK, + minute: 720, + ui: None, + }, + Scenario { + name: "skybox-steppes-night", + map: Some(MAP_AZEROTH), + eye: SKYBOX_STEPPES_EYE, + look: SKYBOX_STEPPES_LOOK, + minute: 0, + ui: None, + }, + Scenario { + name: "skybox-blasted-noon", + map: Some(MAP_AZEROTH), + eye: SKYBOX_BLASTED_EYE, + look: SKYBOX_BLASTED_LOOK, + minute: 720, + ui: None, + }, + Scenario { + name: "skybox-blasted-night", + map: Some(MAP_AZEROTH), + eye: SKYBOX_BLASTED_EYE, + look: SKYBOX_BLASTED_LOOK, + minute: 0, + ui: None, + }, + Scenario { + name: "skybox-hyjal-noon", + map: Some(MAP_KALIMDOR), + eye: SKYBOX_HYJAL_EYE, + look: SKYBOX_HYJAL_LOOK, + minute: 720, + ui: None, + }, + Scenario { + name: "skybox-hyjal-night", + map: Some(MAP_KALIMDOR), + eye: SKYBOX_HYJAL_EYE, + look: SKYBOX_HYJAL_LOOK, + minute: 0, + ui: None, }, ]; @@ -1482,6 +1548,17 @@ pub(crate) fn ui_opted_in() -> bool { std::env::var("WOW_CAPTURE_UI").as_deref() == Ok("1") || scenario_declares_ui() } +// MONKEY (skybox): the skybox shots, eye just above the ground at a sphere's centre, pitched up so +// the upper frame is sky. +const SKYBOX_KZ_EYE: [f32; 3] = [-6474.0, -2912.0, 40.0]; +const SKYBOX_KZ_LOOK: [f32; 3] = [-6300.0, -2800.0, 95.0]; +const SKYBOX_STEPPES_EYE: [f32; 3] = [-7979.0, -2571.0, 260.0]; +const SKYBOX_STEPPES_LOOK: [f32; 3] = [-7800.0, -2450.0, 320.0]; +const SKYBOX_BLASTED_EYE: [f32; 3] = [-11300.0, -3073.0, 30.0]; +const SKYBOX_BLASTED_LOOK: [f32; 3] = [-11120.0, -2960.0, 90.0]; +const SKYBOX_HYJAL_EYE: [f32; 3] = [4637.0, -4461.0, 1060.0]; +const SKYBOX_HYJAL_LOOK: [f32; 3] = [4800.0, -4350.0, 1120.0]; + #[cfg(test)] mod ui_opt_in_tests { use super::*; diff --git a/crates/benilla-app/src/cvars.rs b/crates/benilla-app/src/cvars.rs index 31a7eced..90e2fafb 100644 --- a/crates/benilla-app/src/cvars.rs +++ b/crates/benilla-app/src/cvars.rs @@ -921,6 +921,12 @@ pub(crate) const REGISTERED: &[Registered] = &[ ), // MONKEY (volumetric fog): saved live tier; capture override stays session-only. ours("volumetricFog", "1", "benilla's own: near-field volumetric fog, 0 Off / 1 Low / 2 High"), + // MONKEY (skybox): the living player's zone skybox from `LightParams`; High sets 1. + ours( + "zoneSkyboxes", + "0", + "benilla's own: draw the zone skybox LightParams names for the living, 0 Off / 1 On; the reference draws a DBC skybox only for the ghost", + ), ours( "waterQuality", "1", diff --git a/crates/benilla-app/src/debug_panel/world.rs b/crates/benilla-app/src/debug_panel/world.rs index b7d26a01..c673c2d5 100644 --- a/crates/benilla-app/src/debug_panel/world.rs +++ b/crates/benilla-app/src/debug_panel/world.rs @@ -105,12 +105,14 @@ pub(super) fn world_section(ui: &mut egui::Ui, world: &mut WorldReadout) { // 4-second crossfade (== the interior-fog blend, one number): `w 0.00` beside a name means // the flood published a skybox the crossfade hasn't engaged — faithful standing outside // the gate, not a resolve bug. - match world.skybox.0.as_deref() { - Some(path) => { + // MONKEY (skybox): the heaviest layer of the weighted list, at its own weight. + match world.skybox.primary() { + Some(layer) => { + let path = layer.path.as_str(); let leaf = path.rsplit('\\').next().unwrap_or(path); line.push_str(&format!( - " · skybox {leaf} w {:.2}", - world.skybox_weight.0 + " · skybox {leaf} w {:.2} (stand-down {:.2})", + layer.weight, world.skybox_weight.0 )); } None => line.push_str(" · sky gradient"), diff --git a/crates/benilla-app/src/game_plugins.rs b/crates/benilla-app/src/game_plugins.rs index adda66ec..db88690d 100644 --- a/crates/benilla-app/src/game_plugins.rs +++ b/crates/benilla-app/src/game_plugins.rs @@ -245,6 +245,8 @@ impl PluginGroup for GamePlugins { .add(crate::dynamic_interior::DynamicInteriorPlugin) // MONKEY (volumetric fog): bridge the live setting to Bevy. .add(crate::volumetric_fog::VolumetricFogPlugin) + // MONKEY (skybox): bridge the zone-skybox setting to the world lane. + .add(crate::zone_skybox::ZoneSkyboxPlugin) // The realmlist (decision 1667) — the logon address the login screen edits. Same reason as // VideoPlugin above: it is a CVar knob, so its resource has to exist before `load_config`. .add(crate::realmlist::RealmlistPlugin) diff --git a/crates/benilla-app/src/lib.rs b/crates/benilla-app/src/lib.rs index a7f9ac23..862b5fc1 100644 --- a/crates/benilla-app/src/lib.rs +++ b/crates/benilla-app/src/lib.rs @@ -74,6 +74,8 @@ mod doodad_events; /// MONKEY (dynamic interiors): the cvar → benilla-world bridge for the fixture-lit interior lane. // MONKEY (volumetric fog): optional near-field atmosphere plugin. mod volumetric_fog; +// MONKEY (skybox): the zoneSkyboxes cvar bridge. +mod zone_skybox; mod dynamic_interior; mod entities; mod fishing_line; diff --git a/crates/benilla-app/src/zone_skybox.rs b/crates/benilla-app/src/zone_skybox.rs new file mode 100644 index 00000000..ce4342cc --- /dev/null +++ b/crates/benilla-app/src/zone_skybox.rs @@ -0,0 +1,45 @@ +//! MONKEY (skybox): the `zoneSkyboxes` cvar's bridge to [`benilla_world::skybox::ZoneSkyboxes`]. +//! `WOW_ZONE_SKYBOXES=0|1` overrides it for the session, which is how a hermetic capture (no config +//! file, registry defaults) turns the lane on. + +use bevy::prelude::*; + +/// The session override, read once. +#[derive(Resource, Clone, Copy)] +struct ZoneSkyboxOverride(Option); + +pub(crate) struct ZoneSkyboxPlugin; + +impl Plugin for ZoneSkyboxPlugin { + fn build(&self, app: &mut App) { + let forced = std::env::var("WOW_ZONE_SKYBOXES") + .ok() + .and_then(|v| v.trim().parse::().ok()) + .map(|v| v != 0); + app.insert_resource(ZoneSkyboxOverride(forced)) + .add_observer(on_cvar) + .add_systems(Startup, apply_override); + } +} + +fn apply_override( + forced: Res, + zone: Option>, +) { + if let (Some(on), Some(mut zone)) = (forced.0, zone) { + zone.set_if_neq(benilla_world::skybox::ZoneSkyboxes(on)); + } +} + +fn on_cvar( + ev: On, + forced: Res, + zone: Option>, +) { + if !ev.is("zoneSkyboxes") || forced.0.is_some() { + return; + } + if let Some(mut zone) = zone { + zone.set_if_neq(benilla_world::skybox::ZoneSkyboxes(ev.flag())); + } +}