diff --git a/LIGHTING.md b/LIGHTING.md index 2122d3df..ff447c35 100644 --- a/LIGHTING.md +++ b/LIGHTING.md @@ -15,26 +15,93 @@ behind each constant. | Lane | What you see | Main switch | |---|---|---| | Sun shadows, characters | real silhouettes for units instead of the oval blob | `characterShadows` | -| Sun shadows, world | trees, buildings and alpha-tested foliage cast; baked terrain shadows step aside | `worldShadows` | +| Sun shadows, world | trees, buildings and alpha-tested foliage cast; baked terrain shadows step aside; the static caster cache also refreshes when streamed geometry arrives, so a standing camera picks up late trees (`world_shadow.rs::refresh_streamed_shadows`, independent of volumetric fog) | `worldShadows` | | Dynamic interiors | WMO rooms are lit per fragment by their own fixtures with a soft falloff, gated per room so light does not leak through walls | `interiorLight`, `interiorRoomGate` | | Synthesised lights | torches, braziers, lanterns, campfires and lampposts emit a light in the colour of their flame, with flicker | `fireLightGain`, `fireFlicker` | | Torch shadows | cube-map shadows from point lights onto buildings, models and terrain, with a contact-hardening penumbra and a shadow floor | `interiorShadows`, `exteriorShadows`, `torchShadowStrength`, `interiorShadowSoft` | | Daylight and doorways | calibrated fixtures at doors, windows and open boundaries carry daylight into a room, and doorways between rooms bleed light | `interiorDaylight`, env `WOW_DAYLIGHT`, `WOW_BLEED` | +| Terrain in torch shadows | the ground casts into an outdoor fire's cube map, so a hill or bank blocks the fire (settled exterior slots only; `torch_terrain.rs` gathers the resident MCNK chunks inside the cube's 48 yd) | `torchTerrainShadows` (default 0, High 1), env `WOW_TORCH_TERRAIN=0\|1` | +| City day floor | `interiorDaylight`'s room floor also reaches Stormwind/Ironforge rooms that the portal graph connects to the sky (an exterior-facing portal, or one hop from one; `district_sky_rooms` in `daylight.rs`); before this no city room could take it | `interiorDaylight` (default 0, suggested High 0.10) | | Spell and firework lights | fire, holy and fel effects light their surroundings for their lifetime; frost, nature, arcane and shadow do not | `spellLightGain`, env `WOW_SPELL_LIGHT=0` | | 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` | +| Sky dithering | a faint screen-space dither in the FFXGlow combine so smooth sky and fog gradients do not band (MONKEY p0; was env-only) | Advanced Graphics → Sky Dithering, cvar `skyDither` 0/1 (default 0, Graphics preset High = 1), env `WOW_DITHER=1` still forces it on; bridge in `benilla-app/src/monkey_gfx.rs` | +| HDR emission and bloom | lit WMO windows, additive models/particles and magma can exceed display white; a soft-capped fullscreen halo is added before the faithful FFX clamp | Advanced Graphics → Bloom (Off/Low/High), cvar `bloom`; Off is the unchanged reference image, suggested High preset value 2 | +| Sky quality | Enhanced: the five Light.dbc sky stops through a smooth monotone curve in linear light (no bands at the rings), a soft sun glow tinted by sun and fog colour (fades at night and under cloud), a procedural star field with twinkle and a faint Milky Way over the stock `Stars.m2`. High adds domain-warped cloud detail and sun-lit clouds (self-shadow, silver lining; technique from WarcraftXL, see `THIRD-PARTY.md`). Classic is the reference sky unchanged | Advanced Graphics → Sky Quality, cvar `skyQuality` 0 Classic / 1 Enhanced / 2 High, env `WOW_SKY_QUALITY` | +| Modern fog | MONKEY (fog): radial distance fog with a gradual exponential curve, a daylight sun lobe, and an end colour shared by the world, WDL hull, sky horizon and volumetric haze. Each zone keeps its authored fog end up to the reference view-distance limit; optional `LightFogBand.dbc` rows provide height, sun and end-fog controls. Interior WMO fog stays classic | Advanced Graphics → Modern Fog, cvar `fogModel` 0 Classic (default) / 1 Modern (High = 1), env `WOW_FOGMODEL=0\|1`; `fog_hook.wgsl`, `lighting/fog_model.rs`, `light/fog_band.rs` | +| Rain on surfaces | MONKEY (wet): rain gradually darkens and saturates exposed terrain and models, adds puddles and a sky/sun sheen, and produces rings on exterior Enhanced water. Wetness rises over roughly 90 seconds and dries over roughly four minutes; MONKEY (rainshelter): a camera-centred rain-occlusion map (128² cells of 0.75 yd, filled by vertical collision rays) keeps ground, walls and water under roofs, porches and bridges dry, with a ~1.5 yd drip margin and a soft edge; tree canopies without colliders do not shelter | Advanced Graphics → Wet Surfaces in Rain, cvar `rainSurfaces` 0/1 (default and High 1), env `WOW_RAIN_SURFACES`, `WOW_WETNESS`, `WOW_WET_T`; `weather/wetness.rs`, `weather/shelter.rs`, `wet_hook.wgsl` | +| Foliage wind | one weather-fed gust/veer field drives grass and classified tree/bush foliage; grass also parts around the player and nearby units | Advanced Graphics → Foliage Wind, cvar `foliageWind` 0 Off / 1 Grass / 2 Grass + Trees (High = 2), capture override `WOW_FOLIAGE_WIND`; field and benders in `benilla-world/src/wind/`. The wave phase is the CPU-integrated travel (speed integrated over time, wrapped seamlessly), and the waves' spatial term uses the fixed profile heading; the veer turns only the bend. Tree exile copies sway through material marker bit 14 (`FOLIAGE_WIND_MARKER`). Capture offset `WOW_CAPTURE_WIND_T` (s). Known: shadow casters (world-shadow proxies, `torch_depth.wgsl`) do not apply the sway, so leaves sample a static shadow; a future world-camera DepthPrepass must call the same offsets | +| Ambient occlusion | soft contact shadows under props, in wall-floor corners and around feet; depth-only SSAO at half resolution (depth-reconstructed normals, 6/12 spiral taps, 4x4 depth-aware blur, bilateral upsample) multiplied into the opaque scene after the opaque pass and before the water copy, so water, particles and UI are untouched; fades out 45-90 yd (Low) / 60-120 yd (High), spares sky and bright pixels; on alpha-tested foliage (a dense window of two-sided depth spikes) the blur widens its depth tolerance and the AO drops 40 %, which removes the leaf grain while planes, silhouettes and contact creases keep full AO (`benilla-app/src/ssao.rs`, `benilla-app/src/shaders/ssao.wgsl`) | Advanced Graphics → Ambient Occlusion (Off/Low/High; default Off, High preset 2), cvar `ambientOcclusion`, env `WOW_AO=0\|1\|2`, `WOW_AO_DEBUG=1..5` diagnostic views (factor, protection, distance, raw occlusion, foliage mask), `WOW_AO_GAIN`/`_RADIUS`/`_BIAS`/`_STRENGTH` tuning | +| Lamp fog | MONKEY (lampfog): the fog fullscreen pass selects the nearest resolved exterior and interior point-table sources and analytically integrates their softened inverse-square light over the camera-to-scene-depth ray. A near-field haze floor makes lamps visible inside the ordinary volumetric haze's 10 yd clear zone; it is zero by day, stronger in rain/fog, and deliberately unshadowed. The table colour already includes intensity, flame flicker and `fireLightGain`. Halo output uses destination headroom and stays below 1.0, so a later bloom pass cannot blow it out | Advanced Graphics → Lamp Fog, cvar `lampFog` 0 Off (default, unchanged) / 1 Low (16 lamps) / 2 High (32 lamps; Graphics preset High = 2), env `WOW_LAMPFOG=0\|1\|2`; `benilla-app/src/volumetric_fog.rs` | +| City window daylight | MONKEY (fix-daylight/integration): a district WMO's oversized window batch (e.g. the Cathedral of Light's stained-glass walls) is split into window-sized daylight apertures, so the nave takes daylight pane by pane. Applies to WMOs loaded after a change | Advanced Graphics → City Window Daylight, cvar `daylightWindowSplit` 0/1 (default 1, the merged behaviour); `benilla-world/src/lighting/daylight.rs` (`set_window_split`), bridge `benilla-app/src/dynamic_interior.rs` | +| 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 / +Players reach all of it from **Options -> Advanced Graphics** (a Graphics Preset over everything, a Render Distance slider, a Lighting Quality preset Off / Low / Medium / High / Ultra plus the individual rows; Classic / Off is the original client look). The dev build has a panel for all of it: **Ctrl+Shift+D → Lighting & shadows**, with Dim / Default / Bright presets. +## Graphics Preset + +MONKEY (presets). The top row of Advanced Graphics, cvar `graphicsQuality`: Classic / Low / Medium / +High / Ultra, or Custom when the rows match none of them. The table is `GRAPHICS_PRESETS` in +`benilla-app/src/cvars.rs` (one line per governed row); choosing a rung writes each row through +the ordinary cvar path, and the label is re-derived every frame, so editing any governed row +(or any Lighting Quality member) shows Custom. `lightingQuality` is itself a governed row, so +every row the lighting ladder owns (`LIGHTING_PRESETS`: both sun lanes, shadow resolution, the +room and torch lanes, moon shadows, `waterQuality`, `volumetricFog`, `bloom`, `sunShafts`, +`colorGrading`, `foliageWind`, the fire/spell/lava gains, `nightGain`, `interiorGain`) follows it. + +| row | Classic | Low | Medium | High | Ultra | +|---|---|---|---|---|---| +| `lightingQuality` | Off | Low | Medium | High | Ultra | +| `farclip` (Render Distance, yd) | 350 | 350 | 477 | 777 | 1497 | +| `skyQuality` | 0 | 1 | 1 | 2 | 2 | +| `skyDither` | 0 | 1 | 1 | 1 | 1 | +| `fogModel` | 0 | 1 | 1 | 1 | 1 | +| `rainSurfaces` | 0 | 1 | 1 | 1 | 1 | +| `torchTerrainShadows` | 0 | 0 | 0 | 1 | 1 | +| `daylightWindowSplit` | 0 | 1 | 1 | 1 | 1 | +| `ambientOcclusion` | 0 | 0 | 1 | 2 | 2 | +| `zoneSkyboxes` | 0 | 0 | 1 | 1 | 1 | +| `lampFog` | 0 | 0 | 1 | 2 | 2 | + +What the lighting rungs set, beyond Off (everything off, gains 1.0) and High (the registered +defaults): Low = character shadows only at a 1024 map, no torch or moon shadows, bloom Low; +Medium = both sun lanes, indoor torch shadows (6 resident / 2 moving), moon shadows, bloom Low, +no sun shafts or colour grading; Ultra = High with a 4096 sun map, 16 / 8 torch maps, High water +and High volumetric fog. No rung decides `shadowDistance` or `interiorShadowSoft`. + +**Default: High.** A player whose `config.toml` names no `graphicsQuality` is seeded to the High +column at startup (`Cvars::seed_graphics_preset`) on every row the file does not carry; the rows' +registered defaults stay their per-lane ones (`farclip` the reference's 350), and captures and +tests, which never seed, keep them. The Advanced page's Defaults button lands on High too. + +**Saved presets and new rows** (MONKEY (followups)). On load, `Cvars::reapply_saved_presets` +writes the saved `graphicsQuality` rung (and the saved `lightingQuality` rung, else the Graphics +rung's) over every governed row the file does NOT carry, so a row registered after the save gets +the preset's value, not its registered default. Rows the file carries and session-owned rows stay; +`Custom` writes nothing. + +**Render Distance** is `farclip`, the same cvar as the Graphics page's Terrain Distance (which +keeps the stock 177–777 slider). Its clamp (`view::FARCLIP_RANGE`) now reaches `FARCLIP_MAX` = +1497 yd, the 1.12 grid of 60 carried on. Terrain residency (`terrain_stream/window.rs`), the +model/WMO and particle walls (`view::within_farclip`) and the art-cache sweep radius (now 7 tiles, +`art_scope.rs`) all follow it; at 1497 the residency window is at most 7×7 ADT tiles against +777's 5×5, which is the memory bound. Past 777 only Modern fog (`fog_model::modern_fog_end`) +stretches the fog end with it; under Classic fog the zone's fog end still hides the extra reach. + The optional enhanced water (Water Quality, refraction, caustics, High reflections, lava glow) is its own module, documented in `WATER.md`. ## Where the code lives - **Shaders**: `crates/benilla-assets/src/shaders/shadow_hook.wgsl` (shared shadow sampling), + `fog_hook.wgsl` (MONKEY p0: the ONE distance-fog law — `fog_linear`, `fog_sample`, `apply_fog`; + terrain, wow_model, static_gx, liquid, enhanced_water, wow_effect and wdl all call it; classic is + bit-identical to the old per-shader copies, and `MonkeyFrame.fog_model` = 1 is the FOG lane's + extension point), `monkey_frame.wgsl` (the MonkeyFrame struct), + `crates/benilla-assets/src/shaders/emissive_hook.wgsl` (shared opt-in HDR multipliers), `crates/benilla-world/src/shaders/torch_depth.wgsl`, and the lighting lanes inside `static_gx.wgsl`, `wow_model.wgsl` and `terrain.wgsl`. The three receivers mirror each other; the comments say where. @@ -47,14 +114,51 @@ is its own module, documented in `WATER.md`. `character_shadow.rs`, `world_shadow.rs`, `blob_shadow.rs`, `entities/carried_light.rs`, `entities/spell_fx/lifecycle.rs`, `dynamic_interior.rs`, `debug_panel/lighting_controls.rs`, and the cvars in `cvars.rs` / `video.rs`. +- **Sky** (`crates/benilla-world/src`): `sky_fx.rs` (tier, clock, glow inputs), `shaders/sky_fx.wgsl` (gradient curve, glow, stars, cloud noise), the tier branches in `shaders/sky.wgsl` and `shaders/cloud.wgsl`, `clouds/layer.rs` (`update_cloud_fx`); the cvar bridge is `benilla-app/src/sky_quality.rs`. +- **Census**: `cargo test -p benilla-world --lib lighting::daylight::census -- --ignored --nocapture` + (`WOW_CENSUS_WMO`, `WOW_CENSUS_PLACE=map,tx,ty,uid`) prints, per interior room of a city WMO, + whether a daylight fixture, a bleed fixture or neither reaches it, and the sky-room count. +- **Wind**: `wind/mod.rs` resolves the WXL-derived shared field and nearest-unit benders; + `clutter.rs` authors blade weights/phases; `static_gx` classifies and marks alpha-tested leaf + batches. `wind_hook.wgsl` owns the displacement shared by clutter, retained trees and fade twins. - **Tools**: `benilla-extract wmolights [--verts ]`, `wmolamps`, `m2firescan` print the inputs the system works from (groups, batch classes, portals, claims, flame emitters). ## Invariants worth knowing before editing -- The shared light buffer stays 8528 bytes and the torch table 6416 bytes; both are mirrored in - three shaders and pinned by tests. +- The shared light buffer's per-frame blob is 8784 bytes and the torch table 6416 bytes; both are + mirrored in the shaders and pinned by tests. The blob is 21 header rows (336 B), the 256-slot + point-light table (8192 B), then the MONKEY **MonkeyFrame** block (16 rows, 256 B; nothing before + it moves). Every `WowLight` mirror (terrain, wow_model, static_gx, liquid, enhanced_water, + wow_effect, wdl) declares it as `monkey: monkey_frame::MonkeyFrame` after `points`: + + | row | x | y | z | w | + |---|---|---|---|---| + | `fog_a` | height_fog_density | height_fog_height | height_fog_falloff | curve_blend | + | `fog_b` | sun_fog r | g | b | sun_fog_strength | + | `fog_c` | end_fog r | g | b | end_fog_distance | + | `fog_d` | fog_model (0 classic; Modern = scene fog end in yd) | sun_fog_angle | sun direction octahedral x | y | + | `wind_a` | dir_x | dir_y | base_heading (rad) | gust | + | `wind_b` | travel (yd, wrapped at 4096) | sway_strength | grass_strength | tree_strength | + | `wet_a` | rain_rate | wetness | ripple_time_s | snow | + | `misc` | bender_count | time_of_day 0..1 | night 0..1 | 0 | + | `benders[8]` | world x | world y | world z | radius | + + MONKEY (rainshelter): the rain-shelter region follows the MonkeyFrame block (row 549: two + header rows, then 16384 packed `u32` cells, 65568 B; `weather/shelter.rs`), and the prop-probe + region moved up by that much. `terrain`, `wow_model` and `static_gx` mirror it as + `shelter_hdr`, `shelter_cfg`, `shelter`; `enhanced_water` reads it through its row view. + + Positions are Bevy world space (Y up, yards; WoW `(x, y, z)` = Bevy `(-y, z, -x)`), the wind + direction is a unit vector in world XZ. Lanes write the `MonkeyFrame` resource + (`benilla-world/src/lighting/monkey_frame.rs`); `global_light::pack_monkey_frame` packs it after + the point table and fills `time_of_day` / `night` itself. All zero = no visual change. - World lights use upstream's `WorldPointLight`, never Bevy's `PointLight`. +- Foliage wind is vertex-only. Tree and leaf shadow meshes stay static; the small mismatch is the + accepted first-stage cost/complexity tradeoff. +- MONKEY (lampfog): `ResolvedPointLights` is a CPU-only read view of the live, truncated point table; + it does not add a row to the shared GPU buffer. Lamp selection is capped at 100 yd and ranks both + exterior and interior rows together before the 16/32 nearest limit is applied. - WGSL only fails at pipeline creation, so a shader edit is verified by running a world scene, not by `cargo check`. - Exterior point lights are selected per draw unit (12 slots, ranked against the chunk's box); the @@ -71,4 +175,5 @@ Debug tracing: `WOW_TORCH_TRACE=1`, `WOW_POINTS_DUMP=1`, `WOW_SHADOW_TRACE=1`, a ## Licence -Same as upstream: MIT OR Apache-2.0. +Same as upstream: MIT OR Apache-2.0. Code and techniques taken from other projects (WarcraftXL, +by iThorgrim) are credited file by file in [`THIRD-PARTY.md`](THIRD-PARTY.md). diff --git a/README.md b/README.md index 9d73c9d0..63e92bc2 100644 --- a/README.md +++ b/README.md @@ -89,4 +89,5 @@ adapters and developer frames, not copies of it. World of Warcraft is a trademark of Blizzard Entertainment, Inc. Our own code is licensed under [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE), at your option. The two vendored components under `third_party/` — the kira audio engine, and a Lua 5.1 patched to the 1.12 client's dialect — -keep their own upstream licenses, alongside each. +keep their own upstream licenses, alongside each. Code and techniques ported from other projects +(WarcraftXL, by iThorgrim) are credited file by file in [`THIRD-PARTY.md`](THIRD-PARTY.md). diff --git a/THIRD-PARTY.md b/THIRD-PARTY.md new file mode 100644 index 00000000..629f08ec --- /dev/null +++ b/THIRD-PARTY.md @@ -0,0 +1,44 @@ +# Third-party credits + +Code and techniques this fork takes from other projects, with the files they landed in. Vendored +components under `third_party/` carry their own licences beside them and are listed in `README.md`. + +## WarcraftXL + +- **Project: [WarcraftXL](https://github.com/WarcraftXL)** — client extensions for the **3.3.5a** + (build 12340) client, written in C++ with D3D9 / HLSL. +- **Author: iThorgrim.** + +WarcraftXL's code is reused here on the condition that the author and the original project are +named. This file, and the header line in each file below, is that attribution; keep both wherever +the code is copied or shared. + +**Rule for contributors.** A file that ports code or a technique from WarcraftXL carries this line +in its header: + +``` +Ported from WarcraftXL (https://github.com/WarcraftXL) by iThorgrim — module , . +``` + +and gets a row in the table below. Blocks inside a file that port a specific routine also say so +where they stand. Do not remove either. + +| Benilla file | WarcraftXL module | Source files | What was taken | +|---|---|---|---| +| `crates/benilla-assets/src/shaders/enhanced_water.wgsl` (DEPTH LOOK block) | `wxl-experimental-water` | `shaders/Surface.ps.hlsl`, `render/Refraction.cpp` | Per-channel Beer-Lambert extinction over the view path through the water column, the body as a lerp from scattered colour to what is behind by the transmittance, normal-bent scene-copy refraction scaled by the water depth (ported) | +| `crates/benilla-assets/src/shaders/enhanced_water.wgsl` (caustics) | `wxl-experimental-water` | `shaders/Surface.ps.hlsl`, `render/Noise.cpp` | Two caustic layers at incommensurate scales and rates, multiplied (ported; our layers are built from animated cell edges, not value noise) | +| `crates/benilla-assets/src/shaders/enhanced_water.wgsl` (HIGH reflections) | `wxl-experimental-water` | `shaders/Surface.ps.hlsl` | Screen-space reflection of the scene copy along an almost-planar normal, masked by the screen edge and by rays turning back toward the eye (principle; ours is a bisected march against the scene depth, not a single probe) | +| `crates/benilla-assets/src/shaders/enhanced_water.wgsl` (beach surf, `on_bed`) | `wxl-experimental-water` | `sea/Shore.hpp`, `shaders/Shore.hlsli` | Shore surf limited by the terrain column, so objects standing in the water never foam (principle) | +| `crates/benilla-assets/src/shaders/enhanced_water.wgsl` (Gerstner crests, High) + `crates/benilla-world/src/liquid/waves.rs` | `wxl-experimental-water` | `shaders/Surface.ps.hlsl`, `sea/Spectrum.*`, `sea/Ocean.*` | Gerstner displacement and fold-driven crest whitecaps; the CPU swimmer sample inverts the same displacement (ported) | +| `crates/benilla-world/src/water_fx/bob.rs` | `wxl-experimental-water` | `world/Ride.cpp` | Visual-only wave riding for swimmers; the authoritative position never moves (principle, implemented independently) | +| `crates/benilla-app/src/post/grading.rs` + `crates/benilla-app/src/post/grading.wgsl` | `wxl-retail-grading` | `Grading.*`, `shaders/Grading.ps.hlsl` | 32³ LUT convention, strength control and strip lookup mathematics, with the strip uploaded as a native 3D texture (ported from the GPL-3.0-or-later source implementation, Copyright (C) 2026 WarcraftXL) | +| `crates/benilla-world/src/shaders/sky_fx.wgsl` | `wxl-retail-clouds` | `Clouds.cpp`, `Clouds.hpp` | Domain-warped billow fbm (quintic value noise, 0.6 cotton blend, 0.55 octave gain) for the cloud detail | +| `crates/benilla-world/src/shaders/cloud.wgsl` | `wxl-retail-clouds` | `Clouds.cpp` | The 3-tap march toward the sun (self-shadow, ambient floor) and the one-tap silver lining | +| `crates/benilla-world/src/clouds/layer.rs` (`update_cloud_fx`) | `wxl-retail-clouds` | `Clouds.cpp` | March direction = the sun projected on the sheet, strength folded by its flatness (noon clouds evenly lit) | +| `crates/benilla-world/src/wind/mod.rs` | `wxl-experimental-wind` | `field/Wind.hpp`, `field/Wind.cpp` | Stateless three-sine gust and veer field, default profile, and weather gain (ported) | +| `crates/benilla-assets/src/shaders/wind_hook.wgsl` | `wxl-experimental-wind` | `grass/GrassWind.hpp`, `grass/GrassWind.cpp` | Two-wave grass sway, gust response, lean, blade phase/variance, distance fade and radial parting (ported; extended to eight benders) | +| `crates/benilla-assets/src/shaders/wow_model.wgsl` (MONKEY wind hook) | `wxl-experimental-wind` | `grass/GrassWind.cpp` | Grass vertex displacement call seam (technique) | +| `crates/benilla-world/src/clutter.rs` (MONKEY wind attributes) | `wxl-experimental-wind` | `grass/GrassWind.cpp` | Per-blade bend weight and per-tuft phase inputs (technique; height replaces WXL's unverified texture-V weight) | + +Planned (not yet in the tree): breaker index from `sea/Shore.hpp`, `shaders/Wave.hlsli`. +The lane that ports one adds its row here. diff --git a/WATER.md b/WATER.md index 9ee06550..ce61136c 100644 --- a/WATER.md +++ b/WATER.md @@ -2,7 +2,7 @@ An optional enhanced water renderer for [benilla](https://github.com/samwhosung/benilla), the Rust + Bevy reimplementation of the 1.12.1 client. It needs no data changes: everything is derived from -what a 1.12 install already carries (MCLQ / MLIQ liquid grids and their authored depth bytes, the +what a 1.12 install already carries (MCLQ liquid depth and MLIQ opacity bytes, their liquid grids, the `Light.dbc` water and sky rows, the opaque scene depth). It is a **module on top of the reference water, not a replacement for it**. The reference path @@ -12,7 +12,7 @@ player picks it, and every part of it can be switched off. ## Credits - **Original project: [WarcraftXL](https://github.com/WarcraftXL)** — the `wxl-experimental-water` - module for the 1.12 client. + 3.3.5a client extension. - **Author: iThorgrim.** The WarcraftXL water module is the design reference for this one, and its author has given @@ -21,8 +21,11 @@ named. This section is that attribution; it must travel with the module (this fi `enhanced_water.wgsl` header) wherever the module is copied or shared. Rule for contributors: a file that ports code or a technique from WarcraftXL says so in its header -(`Portions derived from WarcraftXL wxl-experimental-water by iThorgrim, used with permission`) and -the technique is listed in the table below. Do not remove either. +(`Ported from WarcraftXL (https://github.com/WarcraftXL) by iThorgrim — module , .`; blocks inside a file keep their `Portions derived from WarcraftXL ...` line), the +technique is listed in the table below, and the file is listed in +[`THIRD-PARTY.md`](THIRD-PARTY.md), the one table of every WarcraftXL-derived file across the +fork. Do not remove any of them. | Technique | Status here | From WarcraftXL | |---|---|---| @@ -40,12 +43,16 @@ the technique is listed in the table below. Do not remove either. |---|---|---| | Quality tiers | `Classic` = the reference water, `Enhanced`, `High` (= Enhanced + scenery reflections) | Video options → Water Quality, cvar `waterQuality`, env `WOW_WATER=0\|1\|2` | | Procedural waves | multi-band analytic waves with exact normals; the long swell moves ocean vertices | tier | +| Open-sea whitecaps | High adds Gerstner crest gathering and fold-thresholded foam; calm canals remain clear | tier | +| Near mesh refinement | High builds a transient 4x liquid lattice within 64 yd, with an 80 yd release ring | tier | | Ocean / inland profiles | the sea and lakes/rivers have their own colour, energy and reflectivity | tier | +| WMO pools | exterior canals use a calm drifting profile with outdoor glints; interiors use their authored MOMT colour and room-fog reflection | tier | | Depth look | light fades per channel over the real path through the water; the bed is seen through it, bent by the waves; sunlit shallows carry a caustic web | tier | | Reflection and glints | Fresnel sky reflection from the zone's sky rows, sun path, moon path, point-light glints | tier | | Beach surf | a travelling wave front, a lace of foam that dissolves behind it, a swash sheet on the sand | tier | | Swimmers | wake foam, treading rings and a visual-only bob with the swell | tier | | Lava glow | magma surfaces emit warm point lights through the lighting module | Advanced Graphics → Lava Glow, `lavaLightGain` | +| Lava HDR | magma partially resists distance fog and feeds the screen-space halo | Advanced Graphics → Bloom, `bloom` (Off/Low/High) | Rivers deliberately have NO flow effect: a derived current was tried and removed at the owner's request. If it ever returns it should read the authored MCLQ flow records, not derive one. @@ -64,7 +71,9 @@ request. If it ever returns it should read the authored MCLQ flow records, not d `materials.rs` (`LiquidExt` fields 103-106, shader registration), `lib.rs` (`WaterQuality`). - **World** (`crates/benilla-world/src`): `liquid/scene_depth.rs` (copies the opaque depth AND colour after the main opaque pass), `liquid/waves.rs` (the CPU mirror of the vertex swell), `liquid/surface.rs` - (per-kind material parameters), `water_fx/bob.rs` (swimmer bob), `lighting/lava_light.rs`. + (per-kind material parameters and the opt-in `WOW_WATER_PROBE` WMO classification/depth log), + `liquid/lod.rs` (High-only 4x near-grid swap), + `water_fx/bob.rs` (swimmer bob), `lighting/lava_light.rs`. - **App** (`crates/benilla-app/src`): the setting in `cvars.rs` / `video.rs`, the options rows in `assets/ui/OptionsFrame.xml`, the capture scenes `water-*` and `lava-*` in `capture/scenarios.rs`. @@ -73,7 +82,11 @@ request. If it ever returns it should read the authored MCLQ flow records, not d - `Classic` must stay byte-identical: capture `water-noon` with `WOW_WATER=0` and compare with the baseline before and after any edit. - The CPU swell (`liquid/waves.rs`) and the shader's `WATER_WAVES` table are one contract. +- MLIQ's per-vertex byte is an opacity-ramp coordinate, not water depth; enhanced WMO water uses + reconstructed scene depth for its column measurement. - Every screen derivative in `enhanced_water()` is taken at the top of the function (WGSL uniformity); WGSL only fails at pipeline creation, so verify by running a `water-*` scene. - Never rotate a noise domain by a per-pixel angle at world coordinates (~1e4 yd): it shears. +- The temporary water wind is `WATER_WIND_DIR`; all wind-aligned shader terms derive from it until + the weather lane supplies shared wind. - Run one GPU capture at a time, and run `cargo test` in a shell WITHOUT `WOW_DATA` exported. diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml index 71e19e82..74dc3699 100644 --- a/crates/benilla-app/assets/ui/OptionsFrame.xml +++ b/crates/benilla-app/assets/ui/OptionsFrame.xml @@ -492,9 +492,22 @@ enUS = { page = "Advanced Graphics", lightingQuality = "Lighting Quality", + -- MONKEY (presets): the Graphics Preset and Render Distance rows. + graphicsQuality = "Graphics Preset", + renderDistance = "Render Distance", + pClassic = "Classic", + qUltra = "Ultra", -- MONKEY (volumetric fog): localised atmospheric quality row. volumetricFog = "Volumetric Fog", + -- MONKEY (post): HDR excess + halo quality. + bloom = "Bloom", + sunShafts = "Sun Shafts", + colorGrading = "Colour Grading", + -- MONKEY (sky): the sky tier row. + skyQuality = "Sky Quality", fogOff = "Off", + -- MONKEY (skybox): the zone skybox row. + zoneSkyboxes = "Zone Skyboxes", waterQuality = "Water Quality", waterClassic = "Classic", waterEnhanced = "Enhanced", @@ -513,27 +526,83 @@ interiorLight = "Dynamic Interior Lighting", interiorShadows = "Indoor Torch Shadows", exteriorShadows = "Outdoor Torch Shadows", + torchTerrainShadows = "Terrain Blocks Torchlight", torchSoft = "Torch Shadow Softness", fireLight = "Fire Light Brightness", fireFlicker = "Fire Flicker", spellLights = "Spell Lights", nightDarkness = "Night Darkness", interiorDarkness = "Interior Darkness", + -- MONKEY (p0 skyDither) + skyDither = "Sky Dithering", + -- MONKEY (fog) + fogModel = "Modern Fog", + -- MONKEY (wet) + rainSurfaces = "Wet Surfaces in Rain", + -- MONKEY (wind) + foliageWind = "Foliage Wind", + windGrass = "Grass", + windTrees = "Grass + Trees", + -- MONKEY (ao): localised contact-shadow row. + ambientOcclusion = "Ambient Occlusion", + -- MONKEY (lampfog) + lampFog = "Lamp Fog", + -- MONKEY (integration): the daylight lane's district window split. + daylightWindowSplit = "City Window Daylight", yards = "%d yd", tips = { + -- MONKEY (presets) + GRAPHICS_QUALITY = + "Sets every graphics setting at once: Lighting Quality and everything it sets, " + .. "Render Distance, Sky Quality, Sky Dithering, Modern Fog, Wet Surfaces in Rain, " + .. "Terrain Blocks Torchlight, the daylight through city windows, Ambient Occlusion, Lamp Fog " + .. "and Zone Skyboxes.\n\n" + .. "Classic is the original client: every enhancement off and the original 350 yd " + .. "view distance. Low and Medium add the enhancements that cost almost nothing; High " + .. "is the recommended look; Ultra raises every quality to its maximum and the view " + .. "distance to 1497 yd, and costs the most.\n\n" + .. "Shadow Distance and Torch Shadow Softness stay as you set them. Changing any " + .. "setting the preset sets shows Custom.", + RENDER_DISTANCE = + "How far the detailed world is drawn, in yards: the same setting as Terrain " + .. "Distance on the Graphics page, with a longer reach. Past 777 yd, the original " + .. "client's limit, the extra distance shows through Modern Fog; with the original " + .. "fog the zone's own fog hides it.\n\n" + .. "Farther costs more memory, loading and GPU time.", LIGHTING_QUALITY = "Sets character, world, indoor torch and outdoor torch shadows; dynamic " .. "interior lighting; moon shadows; volumetric fog; water quality; lava glow; fire and spell brightness; fire flicker; " - .. "and night and interior darkness. Low, Medium and High also set shadow " + .. "foliage wind; and night and interior darkness. Low, Medium and High also set shadow " .. "resolution; Medium and High also set resident and moving torch shadow counts.\n\n" .. "Off restores the original client's look: Classic water, no volumetric fog, lava glow or synthesised fire lights, " .. "night gain 1.0 and interior gain 1.0. All other settings, including " .. "Shadow Distance and Torch Shadow Softness, stay as you set them. " .. "Low, Medium and High use Enhanced water; mirror reflections stay opt-in. " + .. "Ultra also raises shadow resolution to 4096 and the torch shadow counts to 16 and 8, " + .. "and uses High water and High volumetric fog. " .. "Changing a preset member shows Custom when no preset matches.", -- MONKEY (volumetric fog): explain cost and the shadow-map dependency. + -- MONKEY (post): Off guarantees the pre-HDR image. + BLOOM = + "Makes fire, lava, spells and lit windows carry a soft halo. Low uses a smaller blur chain; High is smoother. Off leaves the original image unchanged.", + SUN_SHAFTS = + "Draws sunlight through gaps in trees and buildings. Scene depth blocks the rays; they fade at night, off-screen and indoors.", + COLOR_GRADING = + "Applies a subtle zone-specific colour palette to the 3D world and blends between its day and night looks. The interface is never graded.", 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 (sky): what each sky tier adds. + SKY_QUALITY = + "Classic is the original client's sky. Enhanced smooths the sky's colour " + .. "gradient, adds a soft glow around the sun and a starry night sky with the " + .. "Milky Way. High also gives the clouds finer detail and sunlit edges. " + .. "Costs very little.", + -- 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 " @@ -578,6 +647,11 @@ "The same for fires out in the open: campfires, braziers and lampposts " .. "cast real shadows after dark.\n\n" .. "It does nothing by day and does not require Dynamic Interior Lighting.", + TORCH_TERRAIN_SHADOWS = + "Lets the ground itself take part in outdoor torch shadows: a hill or a " + .. "bank between a fire and the slope behind it blocks the fire's light.\n\n" + .. "Adds the nearby terrain to each outdoor fire's shadow map when it is " + .. "rebuilt; the per-frame cost is small. Needs Outdoor Torch Shadows.", TORCH_SOFT = "How soft the edge of a torch shadow is where the object meets the " .. "ground. Low is hard and crisp; high spreads the edge the way a real " @@ -611,14 +685,68 @@ "Scales interior fixture light and room ambient, including the light from " .. "lamps and candles. Lower values make both dimmer.\n\n" .. "1.0 leaves these inputs unchanged. Free at any setting.", + -- MONKEY (p0 skyDither) + SKY_DITHER = + "Adds a faint noise to the final image so smooth sky and fog gradients do not " + .. "break into visible bands. Off is the original client's look. Practically free.", + -- MONKEY (fog) + FOG_MODEL = + "Softer distance fog in the style of the modern client: it thickens gradually " + .. "instead of in a straight ramp, glows toward the sun by day and shifts to the " + .. "sky's colour in the distance, so the land melts into the horizon. Each zone " + .. "keeps its own fog distance; above the original view-distance limit the fog " + .. "recedes with it.\n\nOff is the original client's fog. Practically free.", + -- MONKEY (wet) + RAIN_SURFACES = + "Rain slowly soaks the ground, stone and wood under the open sky: they darken, " + .. "puddles gather on flat ground and wet tops catch the light; rain drops ring " + .. "the water. It dries over a few minutes after the rain stops. Off is the " + .. "original client's look. Nearly free, and only active while things are wet.", + -- MONKEY (wind) + FOLIAGE_WIND = + "Grass bends in two travelling wind waves and parts around nearby characters. " + .. "Grass + Trees also gives classified tree and bush foliage a gentle sway.\n\n" + .. "Off makes no vertex displacement and restores the unchanged image. Tree shadows stay static.", + -- MONKEY (ao): what the contact shadows touch and what they spare. + AMBIENT_OCCLUSION = + "Soft contact shadows where surfaces meet: under props and furniture, in the corners " + .. "between walls and floors, and around characters' feet. Low uses fewer samples; " + .. "High is smoother and reaches a little further.\n\n" + .. "Fades out with distance and spares the sky, water, lit windows and flames. " + .. "Off leaves the image unchanged.", + -- MONKEY (integration): daylight window split. + DAYLIGHT_WINDOW_SPLIT = + "Large city buildings take daylight through each window on its own instead of " + .. "treating a whole wall of windows as one opening, so halls like the cathedral " + .. "catch the sun pane by pane.\n\nApplies to buildings loaded after the change. " + .. "Off is the earlier look.", + -- MONKEY (lampfog) + LAMP_FOG = + "Makes nearby lamps and fires glow through the air after dark. Low uses the " + .. "nearest 16 lights; High uses 32. Rain and fog strengthen the halos. " + .. "The effect is unshadowed, but stops at visible scene depth.\n\n" + .. "Off is unchanged, and daylight is unchanged at every setting.", }, }, ruRU = { page = "Расширенная графика", lightingQuality = "Качество освещения", + -- MONKEY (presets): the Graphics Preset and Render Distance rows. + graphicsQuality = "Набор графики", + renderDistance = "Дальность прорисовки", + pClassic = "Классика", + qUltra = "Ультра", + -- MONKEY (post): Russian HDR excess + halo quality row. + bloom = "Свечение", + sunShafts = "Солнечные лучи", + colorGrading = "Цветокоррекция", -- MONKEY (volumetric fog): Russian atmospheric quality row. volumetricFog = "Объёмный туман", + -- MONKEY (sky): the sky tier row. + skyQuality = "Качество неба", fogOff = "Выкл", + -- MONKEY (skybox): the zone skybox row. + zoneSkyboxes = "Небосводы зон", waterQuality = "Качество воды", waterClassic = "Классическое", waterEnhanced = "Улучшенное", @@ -637,14 +765,49 @@ interiorLight = "Динамическое освещение помещений", interiorShadows = "Тени факелов в помещениях", exteriorShadows = "Тени огней под открытым небом", + torchTerrainShadows = "Рельеф заслоняет огни", torchSoft = "Мягкость теней от огня", fireLight = "Яркость огня", fireFlicker = "Мерцание огня", spellLights = "Свет от заклинаний", nightDarkness = "Темнота ночи", interiorDarkness = "Темнота помещений", + -- MONKEY (p0 skyDither) + skyDither = "Дизеринг неба", + -- MONKEY (fog) + fogModel = "Современный туман", + -- MONKEY (wet) + rainSurfaces = "Мокрые поверхности в дождь", + -- MONKEY (wind) + foliageWind = "Ветер листвы", + windGrass = "Трава", + windTrees = "Трава + деревья", + -- MONKEY (ao): Russian contact-shadow row. + ambientOcclusion = "Затенение окружения", + -- MONKEY (lampfog) + lampFog = "Свечение фонарей в тумане", + -- MONKEY (integration): the daylight lane's district window split. + daylightWindowSplit = "Дневной свет через окна города", yards = "%d ярд.", tips = { + -- MONKEY (presets) + GRAPHICS_QUALITY = + "Задаёт все настройки графики сразу: качество освещения и всё, что оно задаёт, " + .. "дальность прорисовки, качество неба, дизеринг неба, современный туман, мокрые " + .. "поверхности в дождь, заслонение огней рельефом, дневной свет в окнах городов, " + .. "затенение окружения, свечение фонарей в тумане и небосводы зон.\n\n" + .. "«Классика» — оригинальный клиент: все улучшения выключены, дальность обзора " + .. "прежние 350 ярдов. «Низкое» и «Среднее» добавляют почти бесплатные улучшения; " + .. "«Высокое» — рекомендуемый вид; «Ультра» поднимает всё качество до максимума, а " + .. "дальность — до 1497 ярдов, и стоит дороже всего.\n\n" + .. "Дальность теней и мягкость теней от огня сохраняют ваши значения. Изменение " + .. "любой настройки набора показывает «Своё».", + RENDER_DISTANCE = + "Как далеко рисуется подробный мир, в ярдах: та же настройка, что «Дальность " + .. "обзора» на странице «Графика», но с большим пределом. Дальше 777 ярдов, предела " + .. "оригинального клиента, дополнительное расстояние видно с современным туманом; " + .. "с оригинальным туманом его скрывает туман зоны.\n\n" + .. "Чем дальше, тем больше памяти, загрузки и работы видеокарты.", LIGHTING_QUALITY = "Задаёт тени персонажей, мира и огней внутри и снаружи помещений; " .. "динамическое освещение помещений; лунные тени; объёмный туман; качество воды; свечение лавы; яркость огня и заклинаний; " @@ -655,11 +818,32 @@ .. "источников света, с коэффициентами яркости ночи и помещений 1.0. " .. "Все остальные настройки, включая дальность теней и мягкость теней от огня, " .. "сохраняют ваши значения. «Низкое», «Среднее» и «Высокое» используют улучшенную воду; " - .. "зеркальные отражения включаются отдельно. Изменение настройки набора показывает «Своё», " + .. "зеркальные отражения включаются отдельно. «Ультра» также поднимает разрешение теней " + .. "до 4096 и число теней от огней до 16 и 8, а вода и объёмный туман — «Высокое». " + .. "Изменение настройки набора показывает «Своё», " .. "если ни один набор больше не совпадает.", + -- MONKEY (post): localised bloom effect and Off contract. + BLOOM = + "Добавляет мягкое сияние огню, лаве, заклинаниям и освещённым окнам. Низкое качество использует короткое размытие, высокое — более плавное. Выключение сохраняет исходное изображение.", + SUN_SHAFTS = + "Показывает солнечные лучи сквозь деревья и здания. Рельеф и здания перекрывают свет; ночью и в помещениях эффект исчезает.", + COLOR_GRADING = + "Применяет мягкую цветовую палитру зоны и плавно меняет дневной вариант на ночной. Интерфейс не изменяется.", -- MONKEY (volumetric fog): localised effect and cost explanation. VOLUMETRIC_FOG = "Выкл отключает объёмный туман. Низкое добавляет дымку вблизи и лучи солнца или луны с меньшим числом шагов; Высокое использует больше шагов для более плавных лучей. Туман гуще на рассвете и в непогоду, а в помещениях слабее. Для лучей нужны тени; без теней остаётся только дымка.", + -- MONKEY (sky): what each sky tier adds. + SKY_QUALITY = + "«Классическое» — небо оригинального клиента. «Улучшенное» сглаживает " + .. "цветовой переход неба, добавляет мягкое сияние вокруг солнца и звёздное " + .. "ночное небо с Млечным Путём. «Высокое» также добавляет облакам мелкие " + .. "детали и освещённые солнцем края. Почти не влияет на производительность.", + -- MONKEY (skybox): what the zone skybox row changes. + ZONE_SKYBOXES = + "Рисует нарисованное небо, заданное освещением зоны, как в поздних дополнениях: " + .. "вулканическое небо Пылающих степей, чужое небо Выжженных земель " + .. "и звёздную ночь над горой Хиджал, с плавным появлением на границе зоны. " + .. "«Выкл.» оставляет небо оригинального клиента везде, кроме неба призрака и подземелий. Почти бесплатно.", WATER_QUALITY = "«Классическое» — вода оригинального клиента. «Улучшенное» добавляет плавные переходы у берегов, " .. "анимированную пену у берегов и вокруг объектов, подвижные блики на волнах, отражение неба " @@ -710,6 +894,12 @@ "То же для огней под открытым небом: костры, жаровни и фонари " .. "отбрасывают настоящие тени после наступления темноты.\n\n" .. "Днём не действует и не требует динамического освещения помещений.", + TORCH_TERRAIN_SHADOWS = + "Земля сама участвует в тенях от огней снаружи: холм или склон между " + .. "костром и местностью за ним заслоняет свет огня.\n\n" + .. "Добавляет ближний рельеф в карту теней каждого уличного огня при её " + .. "перестройке; затраты на кадр невелики. Требует теней огней под " + .. "открытым небом.", TORCH_SOFT = "Насколько мягок край тени от огня там, где предмет касается пола. " .. "Низкое значение — резкий край, высокое — размытый, как от " @@ -743,6 +933,48 @@ "Масштабирует свет светильников и фоновое освещение помещений, включая " .. "свет ламп и свечей. Меньшие значения ослабляют и то и другое.\n\n" .. "1.0 оставляет эти составляющие без изменений. Ничего не стоит при любом значении.", + -- MONKEY (p0 skyDither) + SKY_DITHER = + "Добавляет едва заметный шум в итоговое изображение, чтобы плавные переходы цвета " + .. "неба и тумана не распадались на полосы. Выключено — вид оригинального клиента. " + .. "Почти ничего не стоит.", + -- MONKEY (fog) + FOG_MODEL = + "Более мягкий туман в духе современного клиента: он сгущается постепенно, а не " + .. "по прямой, днём светлеет в сторону солнца, а вдали принимает цвет неба, " + .. "так что земля растворяется в горизонте. Дальность тумана в каждой зоне " + .. "прежняя; за пределом дальности обзора оригинала туман отступает вместе с ней." + .. "\n\nВыключено — туман оригинального клиента. Почти ничего не стоит.", + -- MONKEY (wet) + RAIN_SURFACES = + "Дождь постепенно намачивает землю, камень и дерево под открытым небом: они темнеют, " + .. "на ровной земле собираются лужи, мокрые поверхности блестят, а капли расходятся " + .. "кругами по воде. После дождя всё высыхает за несколько минут. Выключено — вид " + .. "оригинального клиента. Почти ничего не стоит и работает, только пока всё мокрое.", + -- MONKEY (wind) + FOLIAGE_WIND = + "Трава гнётся двумя бегущими волнами ветра и расступается вокруг персонажей. " + .. "Трава + деревья также добавляет мягкое качание листвы деревьев и кустов.\n\n" + .. "Выключено не смещает вершины и восстанавливает исходный вид. Тени деревьев остаются неподвижными.", + -- MONKEY (ao): Russian contact-shadow explanation. + AMBIENT_OCCLUSION = + "Мягкие контактные тени там, где поверхности соприкасаются: под предметами и мебелью, " + .. "в углах между стенами и полом, у ног персонажей. «Низкое» использует меньше выборок; " + .. "«Высокое» — более плавное и захватывает чуть больше.\n\n" + .. "Ослабевает с расстоянием и не затрагивает небо, воду, освещённые окна и пламя. " + .. "«Выкл» оставляет изображение без изменений.", + -- MONKEY (integration): daylight window split. + DAYLIGHT_WINDOW_SPLIT = + "Большие городские здания пропускают дневной свет через каждое окно отдельно, " + .. "а не через всю стену окон как один проём, поэтому залы вроде собора " + .. "освещаются солнцем окно за окном.\n\nДействует для зданий, загруженных после " + .. "изменения. Выключено — прежний вид.", + -- MONKEY (lampfog) + LAMP_FOG = + "После заката ближайшие фонари и огни светятся сквозь воздух. Низкое " + .. "качество использует 16 ближайших источников, высокое — 32. Дождь и туман усиливают ореолы. " + .. "Эффект не рассчитывает тени, но обрывается на видимой глубине.\n\n" + .. "Выключено — без изменений; днём изображение не меняется при любом значении.", }, }, } @@ -794,13 +1026,37 @@ -- the rig they share, and the Graphics page had become the place where a player found -- three of eighteen shadow settings and no hint that the other fifteen existed. AdvancedGraphics = { + -- MONKEY (presets): the ladder over every row below, then the view distance. + "RowGraphicsQuality", "RowRenderDistance", -- MONKEY (volumetric fog): include in refresh, defaults and search. "RowLightingQuality", "RowWaterQuality", "RowVolumetricFog", + -- MONKEY (sky): the sky tier row. + "RowSkyQuality", + -- MONKEY (post): the world-only screen effects. + "RowBloom", "RowSunShafts", "RowColorGrading", "RowCharacterShadows", "RowWorldShadows", "RowShadowDistance", "RowShadowResolution", "RowMoonShadows", - "RowInteriorLight", "RowInteriorShadows", "RowExteriorShadows", "RowTorchSoft", + "RowInteriorLight", "RowInteriorShadows", "RowExteriorShadows", + -- MONKEY (daylight: terrain torch casters) + "RowTorchTerrainShadows", "RowTorchSoft", "RowFireLight", "RowFireFlicker", "RowSpellLights", "RowLavaGlow", "RowNightDarkness", "RowInteriorDarkness", + -- MONKEY (p0 skyDither) + "RowSkyDither", + -- MONKEY (fog) + "RowFogModel", + -- MONKEY (wet) + "RowRainSurfaces", + -- MONKEY (wind): appended for conflict-free lane merges. + "RowFoliageWind", + -- MONKEY (ao): appended so the other rows keep their indices. + "RowAmbientOcclusion", + -- MONKEY (lampfog) + "RowLampFog", + -- MONKEY (integration): daylight window split. + "RowDaylightWindowSplit", + -- MONKEY (integration): the skybox lane's row (the lane left it out of this list). + "RowZoneSkyboxes", }, -- Player Guild Names is a child of Player Names, as in 1.12 (l.702-708). Nameplates = { @@ -1315,6 +1571,9 @@ local torch = (interior and GetCVar("interiorShadows") ~= "0") or GetCVar("exteriorShadows") ~= "0" OptionsRow_SetEnabled(getglobal(base .. "RowTorchSoft"), torch) + -- MONKEY (daylight: terrain torch casters): the ground only casts for outdoor fires. + OptionsRow_SetEnabled(getglobal(base .. "RowTorchTerrainShadows"), + GetCVar("exteriorShadows") ~= "0") return end if key == "Controls" then @@ -1408,7 +1667,20 @@ for _, rkey in ipairs(OPTIONS_PAGE_ROWS[key]) do local row = getglobal(base .. rkey) row.pending = nil - OptionsRow_Set(row, OptionsRow_Default(row)) + if not row.preset then + OptionsRow_Set(row, OptionsRow_Default(row)) + end + end + -- MONKEY (presets): a preset row goes last, so its default rung (the Graphics + -- Preset's High) is what the page lands on rather than its members' own defaults. + -- `SetCVar` queues only a moved value, so "Custom" (which the host applies as + -- nothing) goes first: the rung is then always a queued write, after the members. + for _, rkey in ipairs(OPTIONS_PAGE_ROWS[key]) do + local row = getglobal(base .. rkey) + if row.preset then + OptionsRow_Set(row, "Custom") + OptionsRow_Set(row, OptionsRow_Default(row)) + end end OptionsPage_Refresh(key) end @@ -2537,11 +2809,51 @@ - + + + + OptionsRow_OnLoad(self, "graphicsQuality", + BENILLA_ADVGFX.graphicsQuality, + "BENILLA_TOOLTIP_GRAPHICS_QUALITY") + self.preset = 1 + OptionsDropdownRow_Setup(self, { + { text = BENILLA_ADVGFX.pClassic, value = "Classic" }, + { text = BENILLA_ADVGFX.qLow, value = "Low" }, + { text = BENILLA_ADVGFX.qMedium, value = "Medium" }, + { text = BENILLA_ADVGFX.qHigh, value = "High" }, + { text = BENILLA_ADVGFX.qUltra, value = "Ultra" }, + { text = BENILLA_ADVGFX.qCustom, value = "Custom" }, + }) + + + + + + + + + + OptionsRow_OnLoad(self, "farclip", + BENILLA_ADVGFX.renderDistance, + "BENILLA_TOOLTIP_RENDER_DISTANCE") + OptionsSliderRow_Setup(self, 177, 1497, 60, "yd") + + + + + + + -- The preset ladder. This row writes ONE CVar and -- nothing else: the host's own table @@ -2566,6 +2878,7 @@ { text = BENILLA_ADVGFX.qLow, value = "Low" }, { text = BENILLA_ADVGFX.qMedium, value = "Medium" }, { text = BENILLA_ADVGFX.qHigh, value = "High" }, + { text = BENILLA_ADVGFX.qUltra, value = "Ultra" }, { text = BENILLA_ADVGFX.qCustom, value = "Custom" }, }) @@ -2602,11 +2915,66 @@ }) - + + + + OptionsRow_OnLoad(self, "skyQuality", BENILLA_ADVGFX.skyQuality, + "BENILLA_TOOLTIP_SKY_QUALITY") + OptionsDropdownRow_Setup(self, { + { text = BENILLA_ADVGFX.waterClassic, value = "0" }, + { text = BENILLA_ADVGFX.waterEnhanced, value = "1" }, + { text = BENILLA_ADVGFX.waterHigh, value = "2" }, + }) + + + + + + + + + + OptionsRow_OnLoad(self, "bloom", BENILLA_ADVGFX.bloom, + "BENILLA_TOOLTIP_BLOOM") + OptionsDropdownRow_Setup(self, { + { text = BENILLA_ADVGFX.fogOff, value = "0" }, + { text = BENILLA_ADVGFX.qLow, value = "1" }, + { text = BENILLA_ADVGFX.qHigh, value = "2" }, + }) + + + + + + + + + + OptionsRow_OnLoad(self, "sunShafts", BENILLA_ADVGFX.sunShafts, + "BENILLA_TOOLTIP_SUN_SHAFTS") + + + + + + + + + + OptionsRow_OnLoad(self, "colorGrading", BENILLA_ADVGFX.colorGrading, + "BENILLA_TOOLTIP_COLOR_GRADING") + + + + + + + + OptionsRow_OnLoad(self, "characterShadows", BENILLA_ADVGFX.characterShadows, @@ -2721,11 +3089,23 @@ "BENILLA_TOOLTIP_EXTERIOR_SHADOWS") - + + + + OptionsRow_OnLoad(self, "torchTerrainShadows", + BENILLA_ADVGFX.torchTerrainShadows, + "BENILLA_TOOLTIP_TORCH_TERRAIN_SHADOWS") + + + + + + + -- 0.5..3, the CVar's own clamp verbatim: it is a -- SCALE on the PCF tap radius at a contact, so the @@ -2827,6 +3207,115 @@ OptionsSliderRow_Setup(self, 0.2, 1.5, 0.05, "darkness") + + + + + + + + OptionsRow_OnLoad(self, "skyDither", + BENILLA_ADVGFX.skyDither, + "BENILLA_TOOLTIP_SKY_DITHER") + + + + + + + + + + OptionsRow_OnLoad(self, "fogModel", + BENILLA_ADVGFX.fogModel, + "BENILLA_TOOLTIP_FOG_MODEL") + + + + + + + + + + OptionsRow_OnLoad(self, "rainSurfaces", + BENILLA_ADVGFX.rainSurfaces, + "BENILLA_TOOLTIP_RAIN_SURFACES") + + + + + + + + + + OptionsRow_OnLoad(self, "foliageWind", + BENILLA_ADVGFX.foliageWind, + "BENILLA_TOOLTIP_FOLIAGE_WIND") + OptionsDropdownRow_Setup(self, { + { text = BENILLA_ADVGFX.qOff, value = "0" }, + { text = BENILLA_ADVGFX.windGrass, value = "1" }, + { text = BENILLA_ADVGFX.windTrees, value = "2" }, + }) + + + + + + + + + + OptionsRow_OnLoad(self, "ambientOcclusion", BENILLA_ADVGFX.ambientOcclusion, + "BENILLA_TOOLTIP_AMBIENT_OCCLUSION") + OptionsDropdownRow_Setup(self, { + { text = BENILLA_ADVGFX.fogOff, value = "0" }, + { text = BENILLA_ADVGFX.qLow, value = "1" }, + { text = BENILLA_ADVGFX.qHigh, value = "2" }, + }) + + + + + + + + + + OptionsRow_OnLoad(self, "lampFog", BENILLA_ADVGFX.lampFog, + "BENILLA_TOOLTIP_LAMP_FOG") + OptionsDropdownRow_Setup(self, { + { text = BENILLA_ADVGFX.fogOff, value = "0" }, + { text = BENILLA_ADVGFX.qLow, value = "1" }, + { text = BENILLA_ADVGFX.qHigh, value = "2" }, + }) + + + + + + + + + + OptionsRow_OnLoad(self, "daylightWindowSplit", + BENILLA_ADVGFX.daylightWindowSplit, + "BENILLA_TOOLTIP_DAYLIGHT_WINDOW_SPLIT") + + + + + + + + + + OptionsRow_OnLoad(self, "zoneSkyboxes", + BENILLA_ADVGFX.zoneSkyboxes, + "BENILLA_TOOLTIP_ZONE_SKYBOXES") + +