diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 317022b9..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -ko_fi: marcusaga diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8339effb..064e41af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,5 +48,7 @@ jobs: run: scripts/doc-links.py - name: pass-span-lint run: scripts/pass-span-lint.py + - name: player-build + run: cargo build -p benilla --no-default-features - name: player-tests run: cargo test -p benilla-formats -p benilla-app --no-default-features --lib diff --git a/.gitignore b/.gitignore index 36ae1bd5..8433b001 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ __pycache__/ # A zipped snapshot of this checkout, taken by hand to move the tree somewhere. Binary, and # redundant with the history it would land in. /benilla-octo.zip + +# run.sh's per-run client logs (logs/latest.log, logs/errors.log point at the newest). +logs/ diff --git a/Cargo.lock b/Cargo.lock index f5c9af40..263685c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -540,13 +540,13 @@ dependencies = [ "regex", "rtrb", "serde", + "serde_json", "sha1", "smithay-clipboard", "symphonia", "toml", "wgpu", "windows-sys 0.59.0", - "winit", ] [[package]] @@ -626,6 +626,7 @@ dependencies = [ "flate2", "num-bigint", "rand 0.8.6", + "serde", "sha1", "strum", ] @@ -696,7 +697,6 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd310426290cec560221f9750c2f4484be4a8eeea7de3483c423329b465c40e" dependencies = [ - "bevy_dylib", "bevy_internal", "tracing", ] @@ -1016,15 +1016,6 @@ dependencies = [ "sysinfo", ] -[[package]] -name = "bevy_dylib" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f142f72bbbc1a61338bf6da5dc7cf6dcf223683b9d53bcd635ebff4bfeb5308f" -dependencies = [ - "bevy_internal", -] - [[package]] name = "bevy_ecs" version = "0.18.1" @@ -3059,6 +3050,15 @@ dependencies = [ "syn", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "epaint" version = "0.33.3" @@ -4122,7 +4122,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kira" -version = "0.12.4" +version = "0.12.1" dependencies = [ "atomic-arena", "glam 0.33.2", @@ -4381,8 +4381,6 @@ dependencies = [ [[package]] name = "mlua-sys" version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1c3a7fc7580227ece249fd90aa2fa3b39eb2b49d3aec5e103b3e85f2c3dfc8" dependencies = [ "cc", "cfg-if", @@ -5666,12 +5664,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "regex-lite" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" - [[package]] name = "regex-syntax" version = "0.8.10" @@ -6189,9 +6181,9 @@ dependencies = [ [[package]] name = "symphonia" -version = "0.6.1" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7edef6a96b696d4e0cab5ee9ebb7ca155ed95f30a6b45bbb8b97d2727f02424" +checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" dependencies = [ "lazy_static", "symphonia-bundle-mp3", @@ -6204,20 +6196,21 @@ dependencies = [ [[package]] name = "symphonia-bundle-mp3" -version = "0.6.1" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ea5ffc8716bff677dfb3b01b420c7b758de901a72b8c330bf2040ab74b4add" +checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" dependencies = [ "lazy_static", "log", "symphonia-core", + "symphonia-metadata", ] [[package]] name = "symphonia-codec-adpcm" -version = "0.6.1" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445932ecb0c59362fde9c082dd63a75380650c5077fdb8b80b8cac850442a74c" +checksum = "2dddc50e2bbea4cfe027441eece77c46b9f319748605ab8f3443350129ddd07f" dependencies = [ "log", "symphonia-core", @@ -6225,9 +6218,9 @@ dependencies = [ [[package]] name = "symphonia-codec-pcm" -version = "0.6.1" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04ba75686acbe43542fdd374571195f0530c0b7785ca25cc6840e9c6c4b6eea" +checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95" dependencies = [ "log", "symphonia-core", @@ -6235,23 +6228,22 @@ dependencies = [ [[package]] name = "symphonia-core" -version = "0.6.1" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c412864d599d4750d0c3d684d7e093ec05e5309681ef5252cc1096a437f6e0" +checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af" dependencies = [ - "bitflags 2.11.1", + "arrayvec", + "bitflags 1.3.2", "bytemuck", "lazy_static", "log", - "num-complex", - "smallvec", ] [[package]] name = "symphonia-format-riff" -version = "0.6.1" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff70929083a8c1a5f6cd7c904b6071c7914ad04739b510c2f7239dfc9b7dabe" +checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f" dependencies = [ "extended", "log", @@ -6261,14 +6253,13 @@ dependencies = [ [[package]] name = "symphonia-metadata" -version = "0.6.1" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83713a97705d77bdef7cdbc0768fd6e5a54e4cd7e48d60a806ae85639e2c87c6" +checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16" dependencies = [ + "encoding_rs", "lazy_static", "log", - "regex-lite", - "smallvec", "symphonia-core", ] diff --git a/Cargo.toml b/Cargo.toml index 8ef32dbf..ed50fae9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,14 +21,14 @@ anyhow = "1.0.102" # and `vorbis`, and the client mixes through kira. Add a feature by name, never `2d`/`3d`/`ui`; a # plugin that takes bevy with its default features brings the audio stack back too. bevy = { version = "0.18.1", default-features = false, features = [ - "default_app", - "default_platform", - "2d_bevy_render", - "3d_bevy_render", - "ui_api", - "ui_bevy_render", - "scene", - "picking", + "default_app", + "default_platform", + "2d_bevy_render", + "3d_bevy_render", + "ui_api", + "ui_bevy_render", + "scene", + "picking", ] } # For the `WOW_GPU_MS` meter's query set, which bevy 0.18 does not wrap; must match bevy's wgpu. wgpu = "27" @@ -39,9 +39,15 @@ sha1 = "0.10.6" # the hunks and the command that checks them: `third_party/lua-src/BENILLA.md`. [patch.crates-io] lua-src = { path = "third_party/lua-src" } -# kira 0.12.4, patched to compute spatial gains and the volume tween per output chunk and +# kira 0.12.1, patched to compute spatial gains and the volume tween per output chunk and # interpolate them, not per sample frame (`third_party/kira/BENILLA.md`). kira = { path = "third_party/kira" } +# mlua-sys 0.10.0, patched so its Lua 5.1 traceback names a frame from `lua_getinfo`, as 5.1's own +# `debug.traceback` does, instead of searching the whole global table per stack level. mlua builds +# that traceback for every `Err` a Rust binding returns, which on this interface's 27,114 globals +# cost ~7 ms a raise — invisibly, for the raises an addon wraps in `pcall`: five a frame measured +# 44.7 ms of VM tick with an empty error log (`third_party/mlua-sys/BENILLA.md`). +mlua-sys = { path = "third_party/mlua-sys" } # Dependencies at opt-level 3 in dev: at opt-level 0, avian/parry collision drops a run to <1 fps. # Line tables only, and never packed into the binary, for the reason under `[profile.dev]` below. @@ -49,13 +55,9 @@ kira = { path = "third_party/kira" } opt-level = 3 debug = "line-tables-only" split-debuginfo = "unpacked" -# One codegen unit each, since `lto = "off"` below drops the ThinLTO that inlines across a crate's -# units: at 16 units the dependencies cost +1.2 cpu_ms at the leg pin, at one nothing, and a cold -# build is no slower. -codegen-units = 1 # Our crates at opt-level 1: Bevy's generic hot paths monomorphize into them, and at opt-level 0 -# Stormwind drops from a pinned 60 fps to ~45. +# Stormwind drops from a pinned 60 fps to ~45; the warm incremental rebuild cost is ~0. [profile.dev] opt-level = 1 # Line tables only, not `debug = 2`. DWARF is ~97% of what rustc writes for this tree: the largest @@ -70,10 +72,6 @@ opt-level = 1 # with sccache and no disk pressure. Re-measure on macOS before assuming it holds there. debug = "line-tables-only" split-debuginfo = "unpacked" -# No ThinLTO: above opt-level 0 rustc runs it over a crate's own codegen units by default, and an -# incremental rebuild redoes it whole: 19 s of a one-line `benilla-app` edit's 25 s, and half its -# 5.3 GB. Off, our crates cost +0.7 cpu_ms (+6 %) at the leg pin. -lto = "off" # The shipping build (`--profile ship`): fat LTO measures -1.0 cpu_ms (-5 %) against `release`, # which stays the measured profile because fat LTO's link time would tax every iteration. @@ -92,8 +90,3 @@ incremental = true # Not for speed: under `incremental`, thin-local LTO leaves promoted `anon.*.llvm.*` symbols # unresolved and the link fails intermittently (rustc #86049). lto = "off" - -# One codegen unit each, as in dev: at 16 the dependencies cost +1.2 cpu_ms against `release` at -# the leg pin, at one the profile is +0.5, and a cold build is no slower. -[profile.play.package."*"] -codegen-units = 1 diff --git a/LIGHTING.md b/LIGHTING.md index 0fa9df0d..53e4504e 100644 --- a/LIGHTING.md +++ b/LIGHTING.md @@ -15,7 +15,7 @@ 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, and so does the MCSH 0.5 sun dim on models standing on them (`wow_model.wgsl` / `static_gx.wgsl`, terrain's gate and dusk weight); 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` | +| 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` | @@ -61,19 +61,19 @@ every row the lighting ladder owns (`LIGHTING_PRESETS`: both sun lanes, shadow r 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 | +| 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; @@ -151,17 +151,17 @@ is its own module, documented in `WATER.md`. 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 | + | 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 @@ -172,7 +172,6 @@ is its own module, documented in `WATER.md`. 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. diff --git a/README.md b/README.md index 31decb8d..84e6a8fa 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,8 @@ runtime. The few files under `crates/benilla-app/assets/ui/` are our own, not co adapters over stock files, and the settings windows and script error log benilla draws itself. 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. Code and techniques ported from other projects +[MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE), at your option. The three vendored components +under `third_party/`, the kira audio engine, a Lua 5.1 patched to the 1.12 client's dialect and the +mlua FFI layer with its traceback's global-table search removed, 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 index ac25cf6d..7011afc4 100644 --- a/THIRD-PARTY.md +++ b/THIRD-PARTY.md @@ -23,23 +23,23 @@ 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.cpp`, `shaders/Grading.ps.hlsl` | 32³ LUT convention, strength control and strip lookup mathematics, with the strip uploaded as a native 3D texture (ported; used with the author's permission, attribution required) | -| `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/sky_fx.rs` | `wxl-retail-clouds` | `Clouds.cpp`, `Clouds.hpp` | The High cloud-detail tier and the sun-march inputs it feeds the sky and cloud materials (ported) | -| `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) | +| 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.cpp`, `shaders/Grading.ps.hlsl` | 32³ LUT convention, strength control and strip lookup mathematics, with the strip uploaded as a native 3D texture (ported; used with the author's permission, attribution required) | +| `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/sky_fx.rs` | `wxl-retail-clouds` | `Clouds.cpp`, `Clouds.hpp` | The High cloud-detail tier and the sun-march inputs it feeds the sky and cloud materials (ported) | +| `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 ae0f54a9..fdf94e02 100644 --- a/WATER.md +++ b/WATER.md @@ -33,7 +33,7 @@ fork. Do not remove any of them. | Shore surf limited by the TERRAIN column, so objects in the water never foam | shipped (authored depth gate, `on_bed`) | principle from `sea/Shore.hpp` | | Per-channel extinction + scene-copy refraction | shipped (`enhanced_water.wgsl` DEPTH LOOK; bent samples landing on anything in front of the water are refused via the scene depth) | ported from `shaders/Surface.ps.hlsl`, `render/Refraction.cpp` | | Caustics, two layers at incommensurate scales/rates | shipped (floor web; built from animated cell edges, not value noise) | ported from `shaders/Surface.ps.hlsl`, `render/Noise.cpp` | -| Screen-space reflection of the scenery | the planar mirror's fallback for water at another height than the mirror plane (a bisected march against the scene depth, not WXL's single probe; a bisected hit still deep behind the depth is a ray passing behind an occluder and marches on) | principle from `shaders/Surface.ps.hlsl` | +| Screen-space reflection of the scenery | shipped on `High` (a bisected march against the scene depth, not WXL's single probe) | principle from `shaders/Surface.ps.hlsl` | | Gerstner trains + breaker index, crest-fold foam | planned | `sea/Spectrum.*`, `sea/Shore.hpp`, `shaders/Wave.hlsli` | | Specular lobe widened by the normal's screen derivative, distance glints | shipped / planned | `shaders/Surface.ps.hlsl` | @@ -42,7 +42,6 @@ fork. Do not remove any of them. | Lane | What you see | Main switch | |---|---|---| | Quality tiers | `Classic` = the reference water, `Enhanced`, `High` (= Enhanced + scenery reflections) | Video options → Water Quality, cvar `waterQuality`, env `WOW_WATER=0\|1\|2` | -| Planar reflections | High only: the scene drawn a second time from the eye mirrored in the water plane the view looks at (half resolution, clipped at the plane by an oblique near plane, no water, no sun shadow cascades), so docks reflect their undersides and anything above the frame reflects; water at another height falls back to the screen-space march. About +1 ms CPU per frame, GPU about even (the march is skipped where the mirror covers). It REPLACES screen-space reflections on High; there is no player option | tier; dev env `WOW_WATER_REFLECT=0\|1` (0 = screen-space only, for A/B) | | 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. The fold mask adds the next two wave bands, a slow domain-warped gust field moves the break threshold and calms patches, and ~6 yd segments break each crest into short runs, so the caps do not form a lattice (`whitecap_fold`, `enhanced_water.wgsl`) | tier | | Near mesh refinement | High builds a transient 4x liquid lattice within 64 yd, with an 80 yd release ring | tier | @@ -63,17 +62,15 @@ request. If it ever returns it should read the authored MCLQ flow records, not d - **Shader**: `crates/benilla-assets/src/shaders/enhanced_water.wgsl` - the whole module on the GPU side, an importable library (`benilla::enhanced_water`) with its own bindings: 103 scene depth, 104 `WaterParams` (`benilla_assets::WaterUniform`), 105 the shared light buffer (a second - read-only view of upstream's binding 90), 106 the scene colour (for refraction and reflection), 107 the planar mirror (`liquid/mirror.rs`). Upstream's `liquid.wgsl` carries three hooks only: the + read-only view of upstream's binding 90), 106 the scene colour (for refraction and reflection). Upstream's `liquid.wgsl` carries three hooks only: the `#import`, `water_swell` in its vertex stage, and the `water_active()` branch at the top of its fragment stage, which returns the module's result as-is (the module fogs its own surface terms only: the scene it refracts and reflects is already fogged). Keep it that way: new water code goes in the module, not in `liquid.wgsl`. - **Assets** (`crates/benilla-assets/src`): `water_depth.rs` (`WaterUniform`, the scene-depth image), - `materials.rs` (`LiquidExt` fields 103-107, shader registration), `lib.rs` (`WaterQuality`, - `WaterReflections`). + `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/mirror.rs` (the planar mirror camera, its plane vote and binding 107), - `liquid/waves.rs` (the CPU mirror of the vertex swell), `liquid/surface.rs` + the main opaque pass), `liquid/waves.rs` (the CPU mirror of the vertex swell), `liquid/surface.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`. @@ -93,8 +90,3 @@ request. If it ever returns it should read the authored MCLQ flow records, not d - 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. -- The planar mirror is read at the water pixel's own screen position, flipped vertically: the mirror - camera is the reflected eye rolled half a turn (a proper rotation, so culling and winding stay as they - are). It must keep the world camera's view key (HDR, MSAA, perspective, shadow filter) or every - pipeline compiles a second time; it never draws water (`drop_water_from_mirror`). -- `WOW_WATER_MIRROR_LOG=1` prints the mirror's verdict once a second (plane, votes, active). diff --git a/crates/benilla-app/Cargo.toml b/crates/benilla-app/Cargo.toml index ce77d084..cfb2c44e 100644 --- a/crates/benilla-app/Cargo.toml +++ b/crates/benilla-app/Cargo.toml @@ -12,18 +12,10 @@ workspace = true # `dev` compiles in the instruments; the player build (`scripts/gates.sh`) has no defaults. default = ["dev"] # The asset, format and world crates default `dev` on, so they come in without it, forwarded here. -dev = [ - "benilla-assets/dev", - "benilla-formats/dev", - "benilla-world/dev", - "dep:bevy_egui", -] +dev = ["benilla-assets/dev", "benilla-formats/dev", "benilla-world/dev", "dep:bevy_egui"] tracy = ["bevy/trace_tracy"] # Writes every system's per-run span to `trace-.json` in the cwd. trace_chrome = ["bevy/trace_chrome"] -# Links Bevy as a shared library so an incremental rebuild relinks faster. The binary then starts -# only through `cargo run`, which puts the toolchain's libstd on the loader path. -bevy_dynamic_linking = ["bevy/dynamic_linking"] [dependencies] anyhow.workspace = true @@ -40,8 +32,7 @@ half = "2" include_dir = "0.7" # The 1.12 chat filters (`text_filter`), with one `RegexSet` for the any-match pass per line. regex = "1" -# `getrusage` for perf CPU time; `localtime_r` for the log stamp; the audio clock and render -# thread's realtime policy on macOS. +# `getrusage` for perf CPU time; the audio clock and render thread's realtime policy on macOS. libc = "0.2" # PNG encoding only: the macOS cursor's NSImage data and screenshots. image = { version = "0.25", default-features = false, features = ["png"] } @@ -53,27 +44,21 @@ benilla-protocol = { path = "../benilla-protocol" } benilla-world = { path = "../benilla-world", default-features = false } # The CVar diff persisted in `benilla-config/config.toml`. serde = { version = "1", features = ["derive"] } +# The item cache's on-disk form (`item_persist.rs`); already in the tree through bevy. +serde_json = "1" toml = "0.8" # Collision for our kinematic character controller; 0.6 targets Bevy 0.18, 0.7 moves to 0.19. avian3d = "0.6" # Shapes and rasterizes `FontString` text standalone (`ui_text`), at bevy_text's own 0.16. cosmic-text = "0.16" -# The layout's unshifted character per key (`KeyEventExtModifierSupplement`), which bevy's -# `KeyboardInput` drops; bevy_winit's own 0.30, whose features it sets. -winit = { version = "0.30", default-features = false } # `cosmic-text`'s font database, named by `ui_text`; must stay the version it depends on. fontdb = "0.23" # The mixer. Its `cpal` backend stays off on every target: `sound::output` is our own backend. -kira = { version = "0.12", default-features = false, features = [ - "mp3", - "wav", - "pcm", - "adpcm", -] } +kira = { version = "0.12", default-features = false, features = ["mp3", "wav", "pcm", "adpcm"] } # Aborts a debug build on an allocation in the audio IO callback or render pass; off in release. assert_no_alloc = "1.1" # `MediaSource`: our source raises kira's decode thread's QoS; must stay kira's version. -symphonia = { version = "0.6", default-features = false } +symphonia = { version = "0.5", default-features = false } # The audio threads' lock-free SPSC rings (output, mix tap); kira's version, so the graph has one. rtrb = "0.3" mint = "0.5" @@ -89,49 +74,30 @@ smithay-clipboard = "0.7" # A native NSCursor: winit's cursor rects revert to the arrow under a continuous render loop. [target.'cfg(target_os = "macos")'.dependencies] # The audio device layer (`sound::output::coreaudio`), at the versions and features cpal uses. -coreaudio-rs = { version = "0.13", default-features = false, features = [ - "audio_unit", - "core_audio", -] } -objc2-core-audio = { version = "0.3", default-features = false, features = [ - "std", - "AudioHardware", -] } -objc2-core-audio-types = { version = "0.3", default-features = false, features = [ - "std", - "CoreAudioBaseTypes", -] } +coreaudio-rs = { version = "0.13", default-features = false, features = ["audio_unit", "core_audio"] } +objc2-core-audio = { version = "0.3", default-features = false, features = ["std", "AudioHardware"] } +objc2-core-audio-types = { version = "0.3", default-features = false, features = ["std", "CoreAudioBaseTypes"] } objc2 = "0.5" objc2-app-kit = { version = "0.2", features = [ - "NSApplication", - "NSResponder", - "NSRunningApplication", - "NSWindow", - "NSView", - "NSImage", - "NSCursor", - "NSEvent", -] } -objc2-foundation = { version = "0.2", features = [ - "NSArray", - "NSData", - "NSGeometry", + "NSApplication", + "NSResponder", + "NSRunningApplication", + "NSWindow", + "NSView", + "NSImage", + "NSCursor", + "NSEvent", ] } +objc2-foundation = { version = "0.2", features = ["NSArray", "NSData", "NSGeometry"] } # The audio device layer off macOS (`sound::output::cpal`); on macOS cpal cannot rebuild on a # lost default device and drops the IO-cycle timestamp. [target.'cfg(not(target_os = "macos"))'.dependencies] cpal = "0.17" -# Windows CPU clocks for `perf::clock`; `GetLocalTime` for the log stamp (`ui_chat::logging`); the -# layout's virtual keys for `bindings::layout`. +# Windows CPU clocks for `perf::clock`. [target.'cfg(windows)'.dependencies] -windows-sys = { version = "0.59", features = [ - "Win32_Foundation", - "Win32_System_SystemInformation", - "Win32_System_Threading", - "Win32_UI_Input_KeyboardAndMouse", -] } +windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Threading"] } # The login cycle's fake realmd runs the server half of SRP6 (`net::io::cycle_tests`). [dev-dependencies] diff --git a/crates/benilla-app/assets/ui/AIOHandlers.lua b/crates/benilla-app/assets/ui/AIOHandlers.lua new file mode 100644 index 00000000..be47c98c --- /dev/null +++ b/crates/benilla-app/assets/ui/AIOHandlers.lua @@ -0,0 +1,27 @@ +-- benilla's own: tolerate the server's AIO blocks this client has no feature for. +-- +-- AIO dispatches a server block by name through `AIO_BLOCKHANDLES`, and an unregistered name is +-- an `error()` (`AIO.lua:547-549`). That is the right shape — except **pfUI replaces the global +-- `error` with one that prints to chat and returns** (`pfUI.lua:453-460`, which it does to feed +-- `seterrorhandler`). A non-raising `error` turns AIO's clean abort into a fall-through: the +-- next line calls the handler it just failed to find, so the player sees two errors, the real +-- one being `attempt to call local 'handledata' (a nil value)` at `AIO.lua:558`. +-- +-- That is pfUI's bug and it would bite the reference client the same way; what changed here is +-- only that benilla now loads AIO at all (for the calendar). So the blocks this client does not +-- implement are registered as no-ops, which is what "no handler" was always supposed to mean. +-- +-- `OctoRadio`: the realm's radio is driven entirely from the client — a station click sends +-- `.radio ` as a SAY command and the server answers with `SMSG_PLAY_MUSIC` +-- (`OctoRadioPlaceholder.lua`'s own header says so, and that file contains no AIO call at all). +-- Whatever this block carries is therefore supplementary to a feature that already works, so +-- dropping it costs nothing visible. Logged once so a block that turns out to matter is still +-- findable. +if AIO and AIO.RegisterEvent and not AIO_SERVER then + local ignored = { "OctoRadio" } + for _, name in ipairs(ignored) do + AIO.RegisterEvent(name, function() + -- Nothing: the block is for a feature this client does not implement. + end) + end +end diff --git a/crates/benilla-app/assets/ui/AccountLoginRealmlist.xml b/crates/benilla-app/assets/ui/AccountLoginRealmlist.xml deleted file mode 100644 index fb7bcac5..00000000 --- a/crates/benilla-app/assets/ui/AccountLoginRealmlist.xml +++ /dev/null @@ -1,127 +0,0 @@ - - - - - diff --git a/crates/benilla-app/assets/ui/CharacterFrameAdapters.xml b/crates/benilla-app/assets/ui/CharacterFrameAdapters.xml index 414d4ea9..7c4e2419 100644 --- a/crates/benilla-app/assets/ui/CharacterFrameAdapters.xml +++ b/crates/benilla-app/assets/ui/CharacterFrameAdapters.xml @@ -13,40 +13,4 @@ --> - - - diff --git a/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml b/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml index dbf4261d..26466581 100644 --- a/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml +++ b/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml @@ -1,18 +1,54 @@ diff --git a/crates/benilla-app/assets/ui/FrameXMLFixes.xml b/crates/benilla-app/assets/ui/FrameXMLFixes.xml deleted file mode 100644 index 461e6f9a..00000000 --- a/crates/benilla-app/assets/ui/FrameXMLFixes.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - diff --git a/crates/benilla-app/assets/ui/GameMenuAdapters.xml b/crates/benilla-app/assets/ui/GameMenuAdapters.xml deleted file mode 100644 index 43cf5fea..00000000 --- a/crates/benilla-app/assets/ui/GameMenuAdapters.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - diff --git a/crates/benilla-app/assets/ui/GameMenuFrame.xml b/crates/benilla-app/assets/ui/GameMenuFrame.xml new file mode 100644 index 00000000..4fac6ef2 --- /dev/null +++ b/crates/benilla-app/assets/ui/GameMenuFrame.xml @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- ERA_WINDOW_SCALE (OptionsFrame.xml) stands in for the era's UIParent scale. A + -- second OnShow on this frame would replace this one. + this:SetScale(ERA_WINDOW_SCALE or 1) + if UpdateMicroButtons then UpdateMicroButtons() end + if Disable_BagButtons then Disable_BagButtons() end + + + if UpdateMicroButtons then UpdateMicroButtons() end + if Enable_BagButtons then Enable_BagButtons() end + + + + diff --git a/crates/benilla-app/assets/ui/KeyBindingsPage.xml b/crates/benilla-app/assets/ui/KeyBindingsPage.xml index 03d4de7e..a89fa332 100644 --- a/crates/benilla-app/assets/ui/KeyBindingsPage.xml +++ b/crates/benilla-app/assets/ui/KeyBindingsPage.xml @@ -4,16 +4,14 @@ OptionsFrame.xml, which loads after. Only commands the engine dispatches appear. Deviation: the layout is the era Settings panel's Keybindings page (collapsible category sections), because 1.12's settings screens are much worse to use. Capture follows 1.12's - Blizzard_BindingUI: while a capsule is selected the window is keyboard-enabled, so the - next key binds through its OnKeyDown, and a mouse 3/4/5 click or a wheel notch binds; - left and right clicks only select, and ESC while armed binds Escape (its OnKeyDown has no - ESCAPE filter). + Blizzard_BindingUI: the next key or mouse 3/4/5 press binds, left and right clicks only + select, and ESC while armed binds Escape (its OnKeyDown has no ESCAPE filter). Deviation: every bind, unbind and reset saves at once, as the era panel commits, where 1.12 had Okay and Cancel; only Reset To Default and leaving the character set confirm. --> - - - + @@ -63,13 +61,13 @@ - BenillaKeyBindings_ToggleSection(this.sectionIndex) + KeyBindings_ToggleSection(this.sectionIndex) - - @@ -77,7 +75,7 @@ - - - + - + @@ -1890,7 +1916,7 @@ - - + @@ -1954,21 +1980,21 @@ - BenillaOptionsCheckbox_OnClick(this) - BenillaOptionsRow_Hover(this:GetParent(), 1) - BenillaOptionsRow_Hover(this:GetParent(), nil) + OptionsCheckbox_OnClick(this) + OptionsRow_Hover(this:GetParent(), 1) + OptionsRow_Hover(this:GetParent(), nil) - BenillaOptionsRow_Hover(this, 1) - BenillaOptionsRow_Hover(this, nil) + OptionsRow_Hover(this, 1) + OptionsRow_Hover(this, nil) - + @@ -2030,22 +2056,22 @@ - BenillaOptionsSlider_OnValueChanged(this, arg1) - BenillaOptionsRow_Hover(this:GetParent():GetParent(), 1) - BenillaOptionsRow_Hover(this:GetParent():GetParent(), nil) + OptionsSlider_OnValueChanged(this, arg1) + OptionsRow_Hover(this:GetParent():GetParent(), 1) + OptionsRow_Hover(this:GetParent():GetParent(), nil) - BenillaOptionsRow_Hover(this, 1) - BenillaOptionsRow_Hover(this, nil) + OptionsRow_Hover(this, 1) + OptionsRow_Hover(this, nil) - + @@ -2079,8 +2105,8 @@ - BenillaOptionsRow_Hover(this, 1) - BenillaOptionsRow_Hover(this, nil) + OptionsRow_Hover(this, 1) + OptionsRow_Hover(this, nil) @@ -2114,7 +2140,7 @@ - + @@ -2184,6 +2210,9 @@ + + OptionsTab_FitWidth(self) + @@ -2224,64 +2253,64 @@ - + - getglobal(this:GetName() .. "Label"):SetText("Gameplay") + getglobal(self:GetName() .. "Label"):SetText("Gameplay") - - - - - - - - + - getglobal(this:GetName() .. "Label"):SetText("System") + getglobal(self:GetName() .. "Label"):SetText("System") - - - - @@ -2352,19 +2381,9 @@ - - this:SetScrollChild(BenillaOptionsFrameContainerBody) - this.contentRange = BenillaOptionsScroll_Range - - BenillaOptionsScroll_Fit() - - - if BenillaKeyBindingsPage.selected then - BenillaKeyBindings_OnMouseWheel(arg1) - else - BenillaScroll_OnMouseWheel(this, arg1) - end - + self:SetScrollChild(BenillaOptionsFrameContainerBody) + OptionsScroll_Fit() + BenillaScroll_OnMouseWheel(this, arg1) BenillaScroll_SyncBar(this) BenillaScroll_SyncBar(this) @@ -2372,7 +2391,7 @@ + OptionsScroll_Fit sizes it. --> @@ -2392,194 +2411,185 @@ - + - BenillaOptionsRow_OnLoad(this, "deselectOnClick", "Sticky Targeting", + OptionsRow_OnLoad(self, "deselectOnClick", "Sticky Targeting", "OPTION_TOOLTIP_GAMEFIELD_DESELECT") - this.invert = 1 + self.invert = 1 - + - BenillaOptionsRow_OnLoad(this, "assistAttack", "Attack on assist", + OptionsRow_OnLoad(self, "assistAttack", "Attack on assist", "OPTION_TOOLTIP_ASSIST_ATTACK") - + - BenillaOptionsRow_OnLoad(this, "autoClearAFK", "Auto Clear AFK", + OptionsRow_OnLoad(self, "autoClearAFK", "Auto Clear AFK", "OPTION_TOOLTIP_CLEAR_AFK") - + - BenillaOptionsRow_OnLoad(this, "BlockTrades", "Block Trades", + OptionsRow_OnLoad(self, "BlockTrades", "Block Trades", "OPTION_TOOLTIP_BLOCK_TRADES") - + - BenillaOptionsRow_OnLoad(this, "autoSelfCast", "Auto Self Cast", + OptionsRow_OnLoad(self, "autoSelfCast", "Auto Self Cast", "OPTION_TOOLTIP_AUTO_SELF_CAST") - + - BenillaOptionsRow_OnLoad(this, "autoLootDefault", "Auto Loot") + OptionsRow_OnLoad(self, "autoLootDefault", "Auto Loot") - + - BenillaOptionsRow_OnLoad(this, "mouseInvertPitch", "Invert Mouse", + OptionsRow_OnLoad(self, "mouseInvertPitch", "Invert Mouse", "OPTION_TOOLTIP_INVERT_MOUSE") - + - BenillaOptionsRow_OnLoad(this, "mousespeed", "Mouse Sensitivity", + OptionsRow_OnLoad(self, "mousespeed", "Mouse Sensitivity", "OPTION_TOOLTIP_MOUSE_SENSITIVITY") - BenillaOptionsSliderRow_Setup(this, 0.5, 1.5, 0.05) + OptionsSliderRow_Setup(self, 0.5, 1.5, 0.05) - - + + - BenillaOptionsRow_OnLoad(this, "autointeract", "Click-to-Move", - "OPTION_TOOLTIP_CLICK_TO_MOVE") - - - - - - - - BenillaOptionsRow_OnLoad(this, "cameraSmoothStyle", + OptionsRow_OnLoad(self, "cameraSmoothStyle", "Camera Following Style", "OPTION_TOOLTIP_CAMERA1") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = "Smart", value = "1", tip = "OPTION_TOOLTIP_CAMERA1" }, { text = "Always", value = "2", tip = "OPTION_TOOLTIP_CAMERA2" }, { text = "Never", value = "0", alt = "3", tip = "OPTION_TOOLTIP_CAMERA3" }, }) - + - BenillaOptionsRow_OnLoad(this, "cameraTerrainTilt", "Follow Terrain", + OptionsRow_OnLoad(self, "cameraTerrainTilt", "Follow Terrain", "OPTION_TOOLTIP_FOLLOW_TERRAIN") - + - BenillaOptionsRow_OnLoad(this, "cameraBobbing", "Head Bob", + OptionsRow_OnLoad(self, "cameraBobbing", "Head Bob", "OPTION_TOOLTIP_HEAD_BOB") - + - BenillaOptionsRow_OnLoad(this, "cameraYawSmoothSpeed", "Auto-Follow Speed", + OptionsRow_OnLoad(self, "cameraYawSmoothSpeed", "Auto-Follow Speed", "OPTION_TOOLTIP_AUTO_FOLLOW_SPEED") - BenillaOptionsSliderRow_Setup(this, 90, 270, 10, "raw") + OptionsSliderRow_Setup(self, 90, 270, 10, "raw") - + - BenillaOptionsRow_OnLoad(this, "cameraWaterCollision", "Water Collision", + OptionsRow_OnLoad(self, "cameraWaterCollision", "Water Collision", "OPTION_TOOLTIP_WATER_COLLISION") - + - BenillaOptionsRow_OnLoad(this, "cameraPivot", "Smart Pivot", + OptionsRow_OnLoad(self, "cameraPivot", "Smart Pivot", "OPTION_TOOLTIP_SMART_PIVOT") - + - BenillaOptionsRow_OnLoad(this, "cameraYawMoveSpeed", "Mouse Look Speed", + OptionsRow_OnLoad(self, "cameraYawMoveSpeed", "Mouse Look Speed", "OPTION_TOOLTIP_MOUSE_LOOK_SPEED") - BenillaOptionsSliderRow_Setup(this, 90, 270, 10, "raw") - this.partner = { cvar = "cameraPitchMoveSpeed", scale = 0.5 } + OptionsSliderRow_Setup(self, 90, 270, 10, "raw") + self.partner = { cvar = "cameraPitchMoveSpeed", scale = 0.5 } - + - BenillaOptionsRow_OnLoad(this, "cameraDistanceMaxFactor", + OptionsRow_OnLoad(self, "cameraDistanceMaxFactor", "Max Camera Distance", "OPTION_TOOLTIP_MAX_FOLLOW_DIST") - BenillaOptionsSliderRow_Setup(this, 1.0, 2.0, 0.1, "yards") + OptionsSliderRow_Setup(self, 1.0, 2.0, 0.1, "yards") @@ -2591,104 +2601,104 @@ - + - BenillaOptionsRow_OnLoad(this, "MasterSoundEffects", "Enable All Sound", + OptionsRow_OnLoad(self, "MasterSoundEffects", "Enable All Sound", "OPTION_TOOLTIP_ENABLE_ALL_SOUND") - this.soundQuirk = 1 + self.soundQuirk = 1 - + - BenillaOptionsRow_OnLoad(this, "MasterVolume", "Master Volume", + OptionsRow_OnLoad(self, "MasterVolume", "Master Volume", "OPTION_TOOLTIP_MASTER_VOLUME") - + - BenillaOptionsRow_OnLoad(this, "SoundVolume", "Sound Volume", + OptionsRow_OnLoad(self, "SoundVolume", "Sound Volume", "OPTION_TOOLTIP_SOUND_VOLUME", 1) - this.parentRow = "RowMaster" + self.parentRow = "RowMaster" - + - BenillaOptionsRow_OnLoad(this, "MusicVolume", "Music Volume", + OptionsRow_OnLoad(self, "MusicVolume", "Music Volume", "OPTION_TOOLTIP_MUSIC_VOLUME", 1) - this.parentRow = "RowMaster" + self.parentRow = "RowMaster" - + - BenillaOptionsRow_OnLoad(this, "AmbienceVolume", "Ambience Volume", + OptionsRow_OnLoad(self, "AmbienceVolume", "Ambience Volume", "OPTION_TOOLTIP_AMBIENCE_VOLUME", 1) - this.parentRow = "RowMaster" + self.parentRow = "RowMaster" - + - BenillaOptionsRow_OnLoad(this, "EnableMusic", "Enable Music", + OptionsRow_OnLoad(self, "EnableMusic", "Enable Music", "OPTION_TOOLTIP_ENABLE_MUSIC") - this.soundQuirk = 1 + self.soundQuirk = 1 - + - BenillaOptionsRow_OnLoad(this, "EnableAmbience", "Enable Ambience", + OptionsRow_OnLoad(self, "EnableAmbience", "Enable Ambience", "OPTION_TOOLTIP_ENABLE_AMBIENCE") - this.soundQuirk = 1 + self.soundQuirk = 1 - + - BenillaOptionsRow_OnLoad(this, "EnableErrorSpeech", "Enable Error Speech", + OptionsRow_OnLoad(self, "EnableErrorSpeech", "Enable Error Speech", "OPTION_TOOLTIP_ENABLE_ERROR_SPEECH") - this.soundQuirk = 1 + self.soundQuirk = 1 - + - BenillaOptionsRow_OnLoad(this, "Sound_EnableSoundWhenGameIsInBG", + OptionsRow_OnLoad(self, "Sound_EnableSoundWhenGameIsInBG", "Enable Sound in Background", "BENILLA_TOOLTIP_BACKGROUND_SOUND") - this.soundQuirk = 1 + self.soundQuirk = 1 @@ -2711,106 +2721,88 @@ - + - BenillaOptionsRow_OnLoad(this, "uiScale", "UI Scale", + OptionsRow_OnLoad(self, "uiScale", "UI Scale", "OPTION_TOOLTIP_UI_SCALE") - BenillaOptionsSliderRow_Setup(this, OptionsFrameSliders[1].minValue, + OptionsSliderRow_Setup(self, OptionsFrameSliders[1].minValue, OptionsFrameSliders[1].maxValue, OptionsFrameSliders[1].valueStep) -- A live write would rescale the slider mid-drag. - this.deferred = 1 + self.deferred = 1 - + - BenillaOptionsRow_OnLoad(this, "farclip", "Terrain Distance", + OptionsRow_OnLoad(self, "farclip", "Terrain Distance", "OPTION_TOOLTIP_FARCLIP") - BenillaOptionsSliderRow_Setup(this, OptionsFrameSliders[2].minValue, + OptionsSliderRow_Setup(self, OptionsFrameSliders[2].minValue, OptionsFrameSliders[2].maxValue, OptionsFrameSliders[2].valueStep, "raw") - + - BenillaOptionsRow_OnLoad(this, "WorldDetail", "Environment Detail", + OptionsRow_OnLoad(self, "WorldDetail", "Environment Detail", "OPTION_TOOLTIP_ENVIRONMENT_DETAIL") -- Stop names from the era quality ladder. - BenillaOptionsSliderRow_Setup(this, OptionsFrameSliders[3].minValue, + OptionsSliderRow_Setup(self, OptionsFrameSliders[3].minValue, OptionsFrameSliders[3].maxValue, OptionsFrameSliders[3].valueStep, nil, { "Low", "Medium", "High" }) - - - - - - - - BenillaOptionsRow_OnLoad(this, "spellEffectLevel", SPELL_DETAIL, - "OPTION_TOOLTIP_SPELL_DETAIL") - -- Our names for the handler's emission scalars - -- 0.33, 0.66 and 1.0 (`0x689510`). - BenillaOptionsSliderRow_Setup(this, OptionsFrameSliders[8].minValue, - OptionsFrameSliders[8].maxValue, - OptionsFrameSliders[8].valueStep, nil, - { "Low", "Medium", "High" }) - - - + - - + + -- OptionsFrame_SetDefaults' default (l.444-445); -- `or 1` covers a VM with no CVars. local gd = tonumber(GetCVarDefault("gamma")) or 1 - BenillaOptionsNumFuncRow_OnLoad(this, GetGamma, SetGamma, + OptionsNumFuncRow_OnLoad(self, GetGamma, SetGamma, tostring(1 - gd), "Brightness", "BENILLA_TOOLTIP_BRIGHTNESS") - BenillaOptionsSliderRow_Setup(this, OptionsFrameSliders[6].minValue, + OptionsSliderRow_Setup(self, OptionsFrameSliders[6].minValue, OptionsFrameSliders[6].maxValue, OptionsFrameSliders[6].valueStep, "travel") - + - BenillaOptionsRow_OnLoad(this, "weatherDensity", "Weather Intensity", + OptionsRow_OnLoad(self, "weatherDensity", "Weather Intensity", "OPTION_TOOLTIP_WEATHER_DETAIL") -- Our names for `0x67b870`'s spawn-rate gains -- `{0.1, 0.33, 0.66, 1.0}` (`[0x8680ec]`). - BenillaOptionsSliderRow_Setup(this, OptionsFrameSliders[9].minValue, + OptionsSliderRow_Setup(self, OptionsFrameSliders[9].minValue, OptionsFrameSliders[9].maxValue, OptionsFrameSliders[9].valueStep, nil, { "Minimal", "Low", "Medium", "Full" }) - + @@ -2819,23 +2811,61 @@ -- Deviation: modern Classic's two-entry Display -- Mode, as there is no exclusive fullscreen; -- gxWindow "0" is the borderless window. - BenillaOptionsRow_OnLoad(this, "gxWindow", "Display Mode", + -- Latched, like the other two video rows. + self.deferred = 1 + OptionsRow_OnLoad(self, "gxWindow", "Display Mode", "BENILLA_TOOLTIP_DISPLAY_MODE") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = "Windowed (Fullscreen)", value = "0" }, { text = "Windowed", value = "1" }, }) - + - -- Not deferred despite gxRestart: vsync changes - -- live. - BenillaOptionsRow_OnLoad(this, "gxVSync", "Vertical Sync", + -- The window size. Backed by the index verbs, not + -- by `gxResolution` directly: `SetScreenResolution` + -- is 1-based into `GetScreenResolutions`, and + -- `GetCurrentResolution` reports the *live* window + -- rather than the CVar, which in borderless + -- fullscreen are not the same thing. + -- + -- Built from the real list, so the entries are + -- whatever this monitor actually offers. 1.12's + -- own video panel had this row; benilla shipped + -- the display-mode and vsync rows without it, so + -- there was no way to resize from inside the game. + -- `rebuild`, not a list baked here: at OnLoad + -- the engine has pushed no modes yet, so a list + -- built now is empty for the whole session. + self.rebuild = OptionsResolutionRow_Rebuild + self.deferred = 1 + OptionsNumFuncRow_OnLoad(self, + GetCurrentResolution, SetScreenResolution, + "1", "Resolution", + "BENILLA_TOOLTIP_RESOLUTION") + OptionsResolutionRow_Rebuild(self) + OptionsDropdownRow_Setup(self, self.entries) + + + + + + + + + -- Deferred: `gxVSync` is registered **latched** + -- (`cvars.rs`), so a write only stages until + -- `RestartGx`, which is what Apply calls. An + -- earlier comment here said "vsync changes live" + -- — it cannot, and that mismatch is why the + -- setting came back on after a relaunch. + self.deferred = 1 + OptionsRow_OnLoad(self, "gxVSync", "Vertical Sync", "OPTION_TOOLTIP_VERTICAL_SYNC") @@ -2846,7 +2876,7 @@ labels, same descriptions, same deferred commit on the slider — because they are three rows of one rig whose other fifteen a player could not reach at all. --> - + @@ -2856,10 +2886,10 @@ -- the backbuffer), and no Multisampling, which -- misses alpha-tested cutouts. Deferred: a write -- rebuilds the render target. - BenillaOptionsRow_OnLoad(this, "renderScale", "Render Scale", + OptionsRow_OnLoad(self, "renderScale", "Render Scale", "BENILLA_TOOLTIP_RENDER_SCALE") - BenillaOptionsSliderRow_Setup(this, 0.5, 2.0, 0.05) - this.deferred = 1 + OptionsSliderRow_Setup(self, 0.5, 2.0, 0.05) + self.deferred = 1 @@ -2871,7 +2901,7 @@ local info = OptionsFrameSliders[i] local row = getglobal(this:GetName() .. name) if ( info and row ) then - BenillaOptionsSliderRow_Setup(row, info.minValue, info.maxValue, + OptionsSliderRow_Setup(row, info.minValue, info.maxValue, info.valueStep, row.fmt, row.stops) end end @@ -2907,17 +2937,17 @@ Quality below it, ONE CVar: the host's `cvars::GRAPHICS_PRESETS` writes the rows and re-derives this one every frame. --> - + - BenillaOptionsRow_OnLoad(this, "graphicsQuality", + OptionsRow_OnLoad(self, "graphicsQuality", BENILLA_ADVGFX.graphicsQuality, "BENILLA_TOOLTIP_GRAPHICS_QUALITY") - this.preset = 1 - BenillaOptionsDropdownRow_Setup(this, { + self.preset = 1 + OptionsDropdownRow_Setup(self, { { text = BENILLA_ADVGFX.pClassic, value = "Classic" }, { text = BENILLA_ADVGFX.qLow, value = "Low" }, { text = BENILLA_ADVGFX.qMedium, value = "Medium" }, @@ -2931,19 +2961,19 @@ Distance, on the extended range (`view::FARCLIP_RANGE`, 177..1497 on the 1.12 grid of 60). Live like its Graphics twin; both read the one CVar. --> - + - BenillaOptionsRow_OnLoad(this, "farclip", + OptionsRow_OnLoad(self, "farclip", BENILLA_ADVGFX.renderDistance, "BENILLA_TOOLTIP_RENDER_DISTANCE") - BenillaOptionsSliderRow_Setup(this, 177, 1497, 60, "yd") + OptionsSliderRow_Setup(self, 177, 1497, 60, "yd") - + @@ -2964,10 +2994,10 @@ -- would be a copy the other three cannot see. -- The page's OnUpdate poll is what brings the -- member rows' new values back onto the screen. - BenillaOptionsRow_OnLoad(this, "lightingQuality", + OptionsRow_OnLoad(self, "lightingQuality", BENILLA_ADVGFX.lightingQuality, "BENILLA_TOOLTIP_LIGHTING_QUALITY") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = BENILLA_ADVGFX.qOff, value = "Off" }, { text = BENILLA_ADVGFX.qLow, value = "Low" }, { text = BENILLA_ADVGFX.qMedium, value = "Medium" }, @@ -2977,16 +3007,16 @@ }) - + - BenillaOptionsRow_OnLoad(this, "waterQuality", + OptionsRow_OnLoad(self, "waterQuality", BENILLA_ADVGFX.waterQuality, "BENILLA_TOOLTIP_WATER_QUALITY") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = BENILLA_ADVGFX.waterClassic, value = "0" }, { text = BENILLA_ADVGFX.waterEnhanced, value = "1" }, { text = BENILLA_ADVGFX.waterHigh, value = "2" }, @@ -2994,15 +3024,15 @@ - + - BenillaOptionsRow_OnLoad(this, "volumetricFog", BENILLA_ADVGFX.volumetricFog, + OptionsRow_OnLoad(self, "volumetricFog", BENILLA_ADVGFX.volumetricFog, "BENILLA_TOOLTIP_VOLUMETRIC_FOG") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = BENILLA_ADVGFX.fogOff, value = "0" }, { text = BENILLA_ADVGFX.qLow, value = "1" }, { text = BENILLA_ADVGFX.qHigh, value = "2" }, @@ -3010,15 +3040,15 @@ - + - BenillaOptionsRow_OnLoad(this, "skyQuality", BENILLA_ADVGFX.skyQuality, + OptionsRow_OnLoad(self, "skyQuality", BENILLA_ADVGFX.skyQuality, "BENILLA_TOOLTIP_SKY_QUALITY") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = BENILLA_ADVGFX.waterClassic, value = "0" }, { text = BENILLA_ADVGFX.waterEnhanced, value = "1" }, { text = BENILLA_ADVGFX.waterHigh, value = "2" }, @@ -3026,15 +3056,15 @@ - + - BenillaOptionsRow_OnLoad(this, "bloom", BENILLA_ADVGFX.bloom, + OptionsRow_OnLoad(self, "bloom", BENILLA_ADVGFX.bloom, "BENILLA_TOOLTIP_BLOOM") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = BENILLA_ADVGFX.fogOff, value = "0" }, { text = BENILLA_ADVGFX.qLow, value = "1" }, { text = BENILLA_ADVGFX.qHigh, value = "2" }, @@ -3042,51 +3072,51 @@ - + - BenillaOptionsRow_OnLoad(this, "sunShafts", BENILLA_ADVGFX.sunShafts, + OptionsRow_OnLoad(self, "sunShafts", BENILLA_ADVGFX.sunShafts, "BENILLA_TOOLTIP_SUN_SHAFTS") - + - BenillaOptionsRow_OnLoad(this, "colorGrading", BENILLA_ADVGFX.colorGrading, + OptionsRow_OnLoad(self, "colorGrading", BENILLA_ADVGFX.colorGrading, "BENILLA_TOOLTIP_COLOR_GRADING") - + - BenillaOptionsRow_OnLoad(this, "characterShadows", + OptionsRow_OnLoad(self, "characterShadows", BENILLA_ADVGFX.characterShadows, "BENILLA_TOOLTIP_CHARACTER_SHADOWS") - + - BenillaOptionsRow_OnLoad(this, "worldShadows", + OptionsRow_OnLoad(self, "worldShadows", BENILLA_ADVGFX.worldShadows, "BENILLA_TOOLTIP_WORLD_SHADOWS") - + @@ -3097,7 +3127,7 @@ -- own clamp (SHADOW_DISTANCE_RANGE). DEFERRED to -- Apply so a drag doesn't re-fit the cascade + recollect -- casters on every tick (one rebuild on commit instead). - BenillaOptionsRow_OnLoad(this, "shadowDistance", + OptionsRow_OnLoad(self, "shadowDistance", BENILLA_ADVGFX.shadowDistance, "BENILLA_TOOLTIP_SHADOW_DISTANCE") -- `"yd"`, not `"yards"`: the value is already a @@ -3106,11 +3136,11 @@ -- This row rode the wrong one from the day it was -- seated and printed "1200 yd" for an 80 yd -- cascade. - BenillaOptionsSliderRow_Setup(this, 40, 200, 10, "yd") - this.deferred = 1 + OptionsSliderRow_Setup(self, 40, 200, 10, "yd") + self.deferred = 1 - + @@ -3123,17 +3153,17 @@ -- rounds UP into a bigger and slower map than the -- one that was asked for. The entry texts are the -- numbers themselves — nothing to translate. - BenillaOptionsRow_OnLoad(this, "shadowMapSize", + OptionsRow_OnLoad(self, "shadowMapSize", BENILLA_ADVGFX.shadowResolution, "BENILLA_TOOLTIP_SHADOW_RESOLUTION") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = "1024", value = "1024" }, { text = "2048", value = "2048" }, { text = "4096", value = "4096" }, }) - + @@ -3144,58 +3174,58 @@ -- on it, so the left end of this groove is the -- night the client rendered before the moon lane -- existed. - BenillaOptionsRow_OnLoad(this, "moonShadowStrength", + OptionsRow_OnLoad(self, "moonShadowStrength", BENILLA_ADVGFX.moonShadows, "BENILLA_TOOLTIP_MOON_SHADOWS") - BenillaOptionsSliderRow_Setup(this, 0, 1, 0.05) + OptionsSliderRow_Setup(self, 0, 1, 0.05) - + - BenillaOptionsRow_OnLoad(this, "interiorLight", + OptionsRow_OnLoad(self, "interiorLight", BENILLA_ADVGFX.interiorLight, "BENILLA_TOOLTIP_INTERIOR_LIGHT") - + - BenillaOptionsRow_OnLoad(this, "interiorShadows", + OptionsRow_OnLoad(self, "interiorShadows", BENILLA_ADVGFX.interiorShadows, "BENILLA_TOOLTIP_INTERIOR_SHADOWS") - + - BenillaOptionsRow_OnLoad(this, "exteriorShadows", + OptionsRow_OnLoad(self, "exteriorShadows", BENILLA_ADVGFX.exteriorShadows, "BENILLA_TOOLTIP_EXTERIOR_SHADOWS") - + - BenillaOptionsRow_OnLoad(this, "torchTerrainShadows", + OptionsRow_OnLoad(self, "torchTerrainShadows", BENILLA_ADVGFX.torchTerrainShadows, "BENILLA_TOOLTIP_TORCH_TERRAIN_SHADOWS") - + @@ -3205,26 +3235,26 @@ -- SCALE on the PCF tap radius at a contact, so the -- readout is a percentage of the shipped radius -- and 100% is what the lane was tuned at. - BenillaOptionsRow_OnLoad(this, "interiorShadowSoft", + OptionsRow_OnLoad(self, "interiorShadowSoft", BENILLA_ADVGFX.torchSoft, "BENILLA_TOOLTIP_TORCH_SOFT") - BenillaOptionsSliderRow_Setup(this, 0.5, 3, 0.1) + OptionsSliderRow_Setup(self, 0.5, 3, 0.1) - + -- Full 0..4 registry range, in five-percent steps. - BenillaOptionsRow_OnLoad(this, "fireLightGain", + OptionsRow_OnLoad(self, "fireLightGain", BENILLA_ADVGFX.fireLight, "BENILLA_TOOLTIP_FIRE_LIGHT") - BenillaOptionsSliderRow_Setup(this, 0, 4, 0.05) + OptionsSliderRow_Setup(self, 0, 4, 0.05) - + @@ -3233,38 +3263,38 @@ -- 0..2, the CVar's own clamp verbatim: the -- amplitudes are authored at 100%, and 200% is the -- deliberate over-drive for judging the shape. - BenillaOptionsRow_OnLoad(this, "fireFlicker", + OptionsRow_OnLoad(self, "fireFlicker", BENILLA_ADVGFX.fireFlicker, "BENILLA_TOOLTIP_FIRE_FLICKER") - BenillaOptionsSliderRow_Setup(this, 0, 2, 0.05) + OptionsSliderRow_Setup(self, 0, 2, 0.05) - + -- Full 0..4 registry range, in five-percent steps. - BenillaOptionsRow_OnLoad(this, "spellLightGain", + OptionsRow_OnLoad(self, "spellLightGain", BENILLA_ADVGFX.spellLights, "BENILLA_TOOLTIP_SPELL_LIGHTS") - BenillaOptionsSliderRow_Setup(this, 0, 4, 0.05) + OptionsSliderRow_Setup(self, 0, 4, 0.05) - + - BenillaOptionsRow_OnLoad(this, "lavaLightGain", + OptionsRow_OnLoad(self, "lavaLightGain", BENILLA_ADVGFX.lavaGlow, "BENILLA_TOOLTIP_LAVA_GLOW") - BenillaOptionsSliderRow_Setup(this, 0, 4, 0.05) + OptionsSliderRow_Setup(self, 0, 4, 0.05) - + @@ -3281,13 +3311,13 @@ -- the row is named for the darkness: left is -- darker, and this is what makes the number say so -- too. The store keeps the gain, untouched. - BenillaOptionsRow_OnLoad(this, "nightGain", + OptionsRow_OnLoad(self, "nightGain", BENILLA_ADVGFX.nightDarkness, "BENILLA_TOOLTIP_NIGHT_DARKNESS") - BenillaOptionsSliderRow_Setup(this, 0.2, 1.5, 0.05, "darkness") + OptionsSliderRow_Setup(self, 0.2, 1.5, 0.05, "darkness") - + @@ -3295,59 +3325,59 @@ -- The room lane's own dim dial, same clamp and same -- backwards readout as the night one above it. - BenillaOptionsRow_OnLoad(this, "interiorGain", + OptionsRow_OnLoad(self, "interiorGain", BENILLA_ADVGFX.interiorDarkness, "BENILLA_TOOLTIP_INTERIOR_DARKNESS") - BenillaOptionsSliderRow_Setup(this, 0.2, 1.5, 0.05, "darkness") + OptionsSliderRow_Setup(self, 0.2, 1.5, 0.05, "darkness") - + - BenillaOptionsRow_OnLoad(this, "skyDither", + OptionsRow_OnLoad(self, "skyDither", BENILLA_ADVGFX.skyDither, "BENILLA_TOOLTIP_SKY_DITHER") - + - BenillaOptionsRow_OnLoad(this, "fogModel", + OptionsRow_OnLoad(self, "fogModel", BENILLA_ADVGFX.fogModel, "BENILLA_TOOLTIP_FOG_MODEL") - + - BenillaOptionsRow_OnLoad(this, "rainSurfaces", + OptionsRow_OnLoad(self, "rainSurfaces", BENILLA_ADVGFX.rainSurfaces, "BENILLA_TOOLTIP_RAIN_SURFACES") - + - BenillaOptionsRow_OnLoad(this, "foliageWind", + OptionsRow_OnLoad(self, "foliageWind", BENILLA_ADVGFX.foliageWind, "BENILLA_TOOLTIP_FOLIAGE_WIND") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = BENILLA_ADVGFX.qOff, value = "0" }, { text = BENILLA_ADVGFX.windGrass, value = "1" }, { text = BENILLA_ADVGFX.windTrees, value = "2" }, @@ -3355,28 +3385,28 @@ - + - BenillaOptionsRow_OnLoad(this, "foliageWindStrength", + OptionsRow_OnLoad(self, "foliageWindStrength", BENILLA_ADVGFX.foliageWindStrength, "BENILLA_TOOLTIP_FOLIAGE_WIND_STRENGTH") - BenillaOptionsSliderRow_Setup(this, 0.25, 3, 0.25) + OptionsSliderRow_Setup(self, 0.25, 3, 0.25) - + - BenillaOptionsRow_OnLoad(this, "ambientOcclusion", BENILLA_ADVGFX.ambientOcclusion, + OptionsRow_OnLoad(self, "ambientOcclusion", BENILLA_ADVGFX.ambientOcclusion, "BENILLA_TOOLTIP_AMBIENT_OCCLUSION") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = BENILLA_ADVGFX.fogOff, value = "0" }, { text = BENILLA_ADVGFX.qLow, value = "1" }, { text = BENILLA_ADVGFX.qHigh, value = "2" }, @@ -3384,15 +3414,15 @@ - + - BenillaOptionsRow_OnLoad(this, "lampFog", BENILLA_ADVGFX.lampFog, + OptionsRow_OnLoad(self, "lampFog", BENILLA_ADVGFX.lampFog, "BENILLA_TOOLTIP_LAMP_FOG") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = BENILLA_ADVGFX.fogOff, value = "0" }, { text = BENILLA_ADVGFX.qLow, value = "1" }, { text = BENILLA_ADVGFX.qHigh, value = "2" }, @@ -3400,39 +3430,39 @@ - + - BenillaOptionsRow_OnLoad(this, "daylightWindowSplit", + OptionsRow_OnLoad(self, "daylightWindowSplit", BENILLA_ADVGFX.daylightWindowSplit, "BENILLA_TOOLTIP_DAYLIGHT_WINDOW_SPLIT") - + - BenillaOptionsRow_OnLoad(this, "zoneSkyboxes", + OptionsRow_OnLoad(self, "zoneSkyboxes", BENILLA_ADVGFX.zoneSkyboxes, "BENILLA_TOOLTIP_ZONE_SKYBOXES") - + - BenillaOptionsRow_OnLoad(this, "volumetricLight", BENILLA_ADVGFX.volumetricLight, + OptionsRow_OnLoad(self, "volumetricLight", BENILLA_ADVGFX.volumetricLight, "BENILLA_TOOLTIP_VOLUMETRIC_LIGHT") - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = BENILLA_ADVGFX.fogOff, value = "0" }, { text = BENILLA_ADVGFX.qMedium, value = "1" }, { text = BENILLA_ADVGFX.qHigh, value = "2" }, @@ -3440,28 +3470,28 @@ - + - BenillaOptionsRow_OnLoad(this, "volumetricLightStrength", + OptionsRow_OnLoad(self, "volumetricLightStrength", BENILLA_ADVGFX.volumetricLightStrength, "BENILLA_TOOLTIP_VOLUMETRIC_LIGHT_STRENGTH") - BenillaOptionsSliderRow_Setup(this, 0, 2, 0.25) + OptionsSliderRow_Setup(self, 0, 2, 0.25) - + - BenillaOptionsRow_OnLoad(this, "moonLight", BENILLA_ADVGFX.moonLight, + OptionsRow_OnLoad(self, "moonLight", BENILLA_ADVGFX.moonLight, "BENILLA_TOOLTIP_MOONLIGHT") - BenillaOptionsSliderRow_Setup(this, 0, 2, 0.25) + OptionsSliderRow_Setup(self, 0, 2, 0.25) @@ -3482,227 +3512,227 @@ - + - BenillaOptionsGlobalRow_OnLoad(this, "SHOW_COMBAT_TEXT", + OptionsGlobalRow_OnLoad(self, "SHOW_COMBAT_TEXT", "Enable Floating Combat Text", "OPTION_TOOLTIP_SHOW_COMBAT_TEXT") - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_FLOAT_MODE", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_FLOAT_MODE", "Combat Text Float Mode", "OPTION_TOOLTIP_COMBAT_TEXT_MODE", 1) - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = "Scroll Up", value = "1" }, { text = "Scroll Down", value = "2" }, { text = "Arc", value = "3" }, }) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_LOW_HEALTH_MANA", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_LOW_HEALTH_MANA", "Show Low Mana and Health", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_LOW_HEALTH_MANA", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_AURAS", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_AURAS", "Show Auras", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_AURAS", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_AURA_FADE", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_AURA_FADE", "Show Fading Auras", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_AURA_FADE", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_COMBAT_STATE", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_COMBAT_STATE", "Show Combat State", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_COMBAT_STATE", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_DODGE_PARRY_MISS", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_DODGE_PARRY_MISS", "Show Dodge/Parries/Misses", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_DODGE_PARRY_MISS", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_RESISTANCES", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_RESISTANCES", "Show Damage Reduction", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_RESISTANCES", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_REPUTATION", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_REPUTATION", "Show Reputation", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_REPUTATION", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_REACTIVES", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_REACTIVES", "Show Reactive Spells and Abilities", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_REACTIVES", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_FRIENDLY_NAMES", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_FRIENDLY_NAMES", "Show Friendly Healer Names", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_FRIENDLY_NAMES", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_COMBO_POINTS", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_COMBO_POINTS", "Show Combo Points", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_COMBO_POINTS", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_MANA", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_MANA", "Show Energy Gains", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_MANA", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsGlobalRow_OnLoad(this, "COMBAT_TEXT_SHOW_HONOR_GAINED", + OptionsGlobalRow_OnLoad(self, "COMBAT_TEXT_SHOW_HONOR_GAINED", "Show Honor Gained", "OPTION_TOOLTIP_COMBAT_TEXT_SHOW_HONOR_GAINED", 1) - this.parentRow = "RowCombatText" - this.applyFunc = "BenillaCombatText_UpdateOrLoad" + self.parentRow = "RowCombatText" + self.applyFunc = "CombatText_UpdateOrLoad" - + - BenillaOptionsRow_OnLoad(this, "CombatDamage", "Show Target Damage", + OptionsRow_OnLoad(self, "CombatDamage", "Show Target Damage", "OPTION_TOOLTIP_SHOW_DAMAGE") - + - BenillaOptionsRow_OnLoad(this, "CombatLogPeriodicSpells", "Periodic Damage", + OptionsRow_OnLoad(self, "CombatLogPeriodicSpells", "Periodic Damage", "OPTION_TOOLTIP_LOG_PERIODIC_EFFECTS", 1) - this.parentRow = "RowShowDamage" + self.parentRow = "RowShowDamage" - + - BenillaOptionsRow_OnLoad(this, "PetMeleeDamage", "Pet Damage", + OptionsRow_OnLoad(self, "PetMeleeDamage", "Pet Damage", "OPTION_TOOLTIP_SHOW_PET_MELEE_DAMAGE", 1) - this.parentRow = "RowShowDamage" - this.partner = { cvar = "PetSpellDamage" } + self.parentRow = "RowShowDamage" + self.partner = { cvar = "PetSpellDamage" } @@ -3716,13 +3746,13 @@ - + - BenillaOptionsFuncRow_OnLoad(this, + OptionsFuncRow_OnLoad(self, function() return SHOW_MULTI_ACTIONBAR_1 end, function(value) local on @@ -3736,13 +3766,13 @@ "0", "Show Bottom Left ActionBar", "OPTION_TOOLTIP_SHOW_MULTIBAR1") - + - BenillaOptionsFuncRow_OnLoad(this, + OptionsFuncRow_OnLoad(self, function() return SHOW_MULTI_ACTIONBAR_2 end, function(value) local on @@ -3756,13 +3786,13 @@ "0", "Show Bottom Right ActionBar", "OPTION_TOOLTIP_SHOW_MULTIBAR2") - + - BenillaOptionsFuncRow_OnLoad(this, + OptionsFuncRow_OnLoad(self, function() return SHOW_MULTI_ACTIONBAR_3 end, function(value) local on @@ -3777,13 +3807,13 @@ - + - BenillaOptionsFuncRow_OnLoad(this, + OptionsFuncRow_OnLoad(self, function() return SHOW_MULTI_ACTIONBAR_4 end, function(value) local on @@ -3797,25 +3827,25 @@ "0", "Show Right ActionBar 2", "OPTION_TOOLTIP_SHOW_MULTIBAR4", 1) - + - BenillaOptionsGlobalRow_OnLoad(this, "ALWAYS_SHOW_MULTIBARS", + OptionsGlobalRow_OnLoad(self, "ALWAYS_SHOW_MULTIBARS", "Always Show ActionBars", "OPTION_TOOLTIP_ALWAYS_SHOW_MULTIBARS") -- UIOptionsFrame.xml:680-683's click arm. - this.applyFunc = "MultiActionBar_UpdateGridVisibility" + self.applyFunc = "MultiActionBar_UpdateGridVisibility" - + - BenillaOptionsGlobalRow_OnLoad(this, "LOCK_ACTIONBAR", + OptionsGlobalRow_OnLoad(self, "LOCK_ACTIONBAR", "Lock ActionBars", "OPTION_TOOLTIP_LOCK_ACTIONBAR") @@ -3830,153 +3860,153 @@ - + - BenillaOptionsRow_OnLoad(this, "UberTooltips", "Enhanced Tooltips", + OptionsRow_OnLoad(self, "UberTooltips", "Enhanced Tooltips", "OPTION_TOOLTIP_USE_UBERTOOLTIPS") - + - BenillaOptionsRow_OnLoad(this, "statusBarText", + OptionsRow_OnLoad(self, "statusBarText", "Status Bar Text", "OPTION_TOOLTIP_STATUS_BAR") - this.cvarEvent = "STATUS_BAR_TEXT" + self.cvarEvent = "STATUS_BAR_TEXT" - + - BenillaOptionsGlobalRow_OnLoad(this, "SHOW_TARGET_OF_TARGET", + OptionsGlobalRow_OnLoad(self, "SHOW_TARGET_OF_TARGET", "Show Target of Target", "OPTION_TOOLTIP_SHOW_TARGET_OF_TARGET") - this.applyFunc = "TargetofTarget_Update" + self.applyFunc = "TargetofTarget_Update" - + - BenillaOptionsGlobalRow_OnLoad(this, "SHOW_TARGET_OF_TARGET_STATE", + OptionsGlobalRow_OnLoad(self, "SHOW_TARGET_OF_TARGET_STATE", "Show When", "OPTION_TOOLTIP_TARGETOFTARGET5", 1) - BenillaOptionsDropdownRow_Setup(this, { + OptionsDropdownRow_Setup(self, { { text = "Raid", value = "1", tip = "OPTION_TOOLTIP_TARGETOFTARGET1" }, { text = "Party", value = "2", tip = "OPTION_TOOLTIP_TARGETOFTARGET2" }, { text = "Solo", value = "3", tip = "OPTION_TOOLTIP_TARGETOFTARGET3" }, { text = "Party or Raid", value = "4", tip = "OPTION_TOOLTIP_TARGETOFTARGET4" }, { text = "Always", value = "5", tip = "OPTION_TOOLTIP_TARGETOFTARGET5" }, }) - this.applyFunc = "TargetofTarget_Update" + self.applyFunc = "TargetofTarget_Update" - + - BenillaOptionsFuncRow_OnLoad(this, ShowingCloak, ShowCloak, "1", + OptionsFuncRow_OnLoad(self, ShowingCloak, ShowCloak, "1", "Show Cloak", "OPTION_TOOLTIP_SHOW_CLOAK") - + - BenillaOptionsFuncRow_OnLoad(this, ShowingHelm, ShowHelm, "1", + OptionsFuncRow_OnLoad(self, ShowingHelm, ShowHelm, "1", "Show Helm", "OPTION_TOOLTIP_SHOW_HELM") - + - BenillaOptionsGlobalRow_OnLoad(this, "QUEST_FADING_DISABLE", + OptionsGlobalRow_OnLoad(self, "QUEST_FADING_DISABLE", "Instant Quest Text", "OPTION_TOOLTIP_SHOW_QUEST_FADING") - + - BenillaOptionsGlobalRow_OnLoad(this, "SHOW_BUFF_DURATIONS", + OptionsGlobalRow_OnLoad(self, "SHOW_BUFF_DURATIONS", "Buff Durations", "OPTION_TOOLTIP_SHOW_BUFF_DURATION") - this.applyFunc = "BuffButtons_UpdatePositions" + self.applyFunc = "BuffButtons_UpdatePositions" - + - BenillaOptionsGlobalRow_OnLoad(this, "AUTO_QUEST_WATCH", + OptionsGlobalRow_OnLoad(self, "AUTO_QUEST_WATCH", "Automatic Quest Tracking", "OPTION_TOOLTIP_AUTO_QUEST_WATCH") - + - BenillaOptionsRow_OnLoad(this, "profanityFilter", "Profanity Filter", + OptionsRow_OnLoad(self, "profanityFilter", "Profanity Filter", "OPTION_TOOLTIP_PROFANITY_FILTER") - + - BenillaOptionsFuncRow_OnLoad(this, TutorialsEnabled, + OptionsFuncRow_OnLoad(self, TutorialsEnabled, BenillaOptionsFrame_SetTutorialsEnabled, "1", "Show Tutorials", "OPTION_TOOLTIP_SHOW_TUTORIALS") - + - BenillaOptionsRow_OnLoad(this, "showGameTips", + OptionsRow_OnLoad(self, "showGameTips", "Show Loading Screen Tips", "OPTION_TOOLTIP_SHOW_TIPOFTHEDAY") - + - BenillaOptionsGlobalRow_OnLoad(this, "SHOW_NEWBIE_TIPS", + OptionsGlobalRow_OnLoad(self, "SHOW_NEWBIE_TIPS", "Show Detailed Tooltips", "OPTION_TOOLTIP_SHOW_NEWBIE_TIPS") @@ -3990,19 +4020,19 @@ - + - BenillaOptionsRow_OnLoad(this, "guildMemberNotify", + OptionsRow_OnLoad(self, "guildMemberNotify", "Guild Member Alert", "OPTION_TOOLTIP_GUILDMEMBER_ALERT") - + @@ -4010,41 +4040,41 @@ - BenillaOptionsGlobalRow_OnLoad(this, "REMOVE_CHAT_DELAY", + OptionsGlobalRow_OnLoad(self, "REMOVE_CHAT_DELAY", "Remove Chat Hover Delay", "OPTION_TOOLTIP_REMOVE_CHAT_DELAY") - this.applyFunc = "BenillaChatFrame_ApplyMouseOverDelay" + self.applyFunc = "ChatFrame_ApplyMouseOverDelay" - + - BenillaOptionsRow_OnLoad(this, "ChatBubbles", + OptionsRow_OnLoad(self, "ChatBubbles", "Show Chat Bubbles", "OPTION_TOOLTIP_CHAT_BUBBLES") - + - BenillaOptionsRow_OnLoad(this, "showLootSpam", + OptionsRow_OnLoad(self, "showLootSpam", "Detailed Loot Information", "OPTION_TOOLTIP_SHOW_LOOT_SPAM") - + - BenillaOptionsRow_OnLoad(this, "ChatBubblesParty", + OptionsRow_OnLoad(self, "ChatBubblesParty", "Show Party Chat Bubbles", "OPTION_TOOLTIP_PARTY_CHAT_BUBBLES") @@ -4052,91 +4082,78 @@ and writes it (l.337). The filter drops a SpamMessages.dbc match in every chat type but CHAT_MSG_FILTERED (`0x49aad2`). --> - + - BenillaOptionsRow_OnLoad(this, "spamFilter", "Disable Spam Filter", + OptionsRow_OnLoad(self, "spamFilter", "Disable Spam Filter", "OPTION_TOOLTIP_DISABLE_SPAM_FILTER") - this.invert = 1 + self.invert = 1 - + - + - BenillaOptionsFuncRow_OnLoad(this, BenillaOptionsFrame_GetEnemyPlates, - BenillaOptionsFrame_SetEnemyPlates, "0", "Enemy Name Plates") + OptionsRow_OnLoad(self, "nameplateShowEnemies", + "Enemy Name Plates") - + - BenillaOptionsFuncRow_OnLoad(this, BenillaOptionsFrame_GetFriendlyPlates, - BenillaOptionsFrame_SetFriendlyPlates, "0", "Friendly Name Plates") + OptionsRow_OnLoad(self, "nameplateShowFriends", + "Friendly Name Plates") - + - BenillaOptionsRow_OnLoad(this, "UnitNamePlayer", "Player Names", + OptionsRow_OnLoad(self, "UnitNamePlayer", "Player Names", "OPTION_TOOLTIP_SHOW_PLAYER_NAMES") - + - BenillaOptionsRow_OnLoad(this, "UnitNamePlayerGuild", "Player Guild Names", + OptionsRow_OnLoad(self, "UnitNamePlayerGuild", "Player Guild Names", "OPTION_TOOLTIP_SHOW_GUILD_NAMES", 1) - this.parentRow = "RowPlayerNames" + self.parentRow = "RowPlayerNames" - - + - - BenillaOptionsRow_OnLoad(this, "UnitNamePlayerPVPTitle", "Player Titles", - "OPTION_TOOLTIP_SHOW_PLAYER_TITLES", 1) - this.parentRow = "RowPlayerNames" - - - - - - - - BenillaOptionsRow_OnLoad(this, "UnitNameNPC", "NPC Names", + OptionsRow_OnLoad(self, "UnitNameNPC", "NPC Names", "OPTION_TOOLTIP_SHOW_NPC_NAMES") - + - BenillaOptionsRow_OnLoad(this, "UnitNameOwn", "Show Own Name", + OptionsRow_OnLoad(self, "UnitNameOwn", "Show Own Name", "OPTION_TOOLTIP_SHOW_OWN_NAME") @@ -4182,10 +4199,10 @@ - BenillaKeyBindings_CharacterToggle(this) + KeyBindings_CharacterToggle(this) GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:SetText(BenillaKeyBindings_String("CHARACTER_SPECIFIC_KEYBINDING_TOOLTIP", + GameTooltip:SetText(KeyBindings_String("CHARACTER_SPECIFIC_KEYBINDING_TOOLTIP", "Click this to toggle between general key bindings and key bindings specific to this character."), nil, nil, nil, nil, 1) GameTooltip:Hide() @@ -4195,119 +4212,119 @@ getglobal(this:GetName() .. "Label"):SetText( - BenillaKeyBindings_String("CHARACTER_SPECIFIC_KEYBINDINGS", "Character Specific Key Bindings")) + KeyBindings_String("CHARACTER_SPECIFIC_KEYBINDINGS", "Character Specific Key Bindings")) - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -4325,106 +4342,100 @@ ScrollFrame_OnLoad() - - FauxScrollFrame_OnVerticalScroll(BENILLA_KEYBINDINGS_ROW_HEIGHT, BenillaKeyBindingsPage_Update) + FauxScrollFrame_OnVerticalScroll(KEYBINDINGS_ROW_HEIGHT, KeyBindingsPage_Update) - if BenillaKeyBindingsPage.selected then - BenillaKeyBindings_OnMouseWheel(arg1) - else + if not KeyBindingsPage.selected then ScrollFrameTemplate_OnMouseWheel(arg1) end - + - if BenillaKeyBindingsPage.selected then - BenillaKeyBindings_OnMouseWheel(arg1) - else + if not KeyBindingsPage.selected then BenillaFauxScrollFrame_OnMouseWheel(getglobal(this:GetName() .. "ScrollFrame"), arg1) end - BenillaKeyBindings_OnKeyDown(arg1) - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - @@ -4543,27 +4554,16 @@ BenillaOptionsFrame_UpdateScale() - BenillaOptionsTab_FitWidth(BenillaOptionsFrameGameTab) - BenillaOptionsTab_FitWidth(BenillaOptionsFrameAddOnsTab) BenillaOptionsFrame_SelectCategory(this.selectedCategory or "Controls") if UpdateMicroButtons then UpdateMicroButtons() end - - - this:EnableKeyboard(false) - this:EnableMouseWheel(false) - - BenillaKeyBindings_OnKeyDown() - BenillaKeyBindings_OnKeyDown(arg1) - BenillaKeyBindings_OnMouseWheel(arg1) - + BenillaOptionsFrameSearchBox:SetText("") - BenillaOptionsPage_DiscardPending() - BenillaKeyBindings_SetSelected(nil) - BenillaKeyBindings_Output("") + OptionsPage_DiscardPending() + KeyBindings_SetSelected(nil) + KeyBindings_Output("") if UpdateMicroButtons then UpdateMicroButtons() end diff --git a/crates/benilla-app/assets/ui/ScriptLogFrame.xml b/crates/benilla-app/assets/ui/ScriptLogFrame.xml index 13fd9dcc..375c1dcc 100644 --- a/crates/benilla-app/assets/ui/ScriptLogFrame.xml +++ b/crates/benilla-app/assets/ui/ScriptLogFrame.xml @@ -1,20 +1,16 @@ diff --git a/crates/benilla-app/assets/ui/ScrollTemplates.xml b/crates/benilla-app/assets/ui/ScrollTemplates.xml index d8fdd234..0a7cfd07 100644 --- a/crates/benilla-app/assets/ui/ScrollTemplates.xml +++ b/crates/benilla-app/assets/ui/ScrollTemplates.xml @@ -96,18 +96,11 @@ BenillaScrollBar_UpdateArrows(bar) end - -- The range the bar follows: the engine's, the reference's union of the child's whole - -- subtree, unless the frame measures its own content (`sf.contentRange`). - function BenillaScroll_Range(sf) - if sf.contentRange then return sf.contentRange(sf) end - return sf:GetVerticalScrollRange() - end - -- Hides the bar when the content fits (reference: ScrollFrame_OnScrollRangeChanged). function BenillaScroll_SyncBar(sf) local bar = getglobal(sf:GetName() .. "Bar") if not bar then return end - local range = BenillaScroll_Range(sf) + local range = sf:GetVerticalScrollRange() bar:SetValueStep(BENILLA_SCROLL_STEP) bar:SetMinMaxValues(0, range) bar:SetValue(sf:GetVerticalScroll()) @@ -118,7 +111,7 @@ -- Clamped here: SetVerticalScroll stores any value, as the reference's does. function BenillaScroll_Step(sf, delta) local target = sf:GetVerticalScroll() + delta - local range = BenillaScroll_Range(sf) + local range = sf:GetVerticalScrollRange() if target > range then target = range end if target < 0 then target = 0 end sf:SetVerticalScroll(target) @@ -190,7 +183,7 @@ - BenillaFauxScrollBar_OnValueChanged(this, arg1) + BenillaFauxScrollBar_OnValueChanged(self, arg1) diff --git a/crates/benilla-app/assets/ui/benilla.toc b/crates/benilla-app/assets/ui/benilla.toc new file mode 100644 index 00000000..d08351aa --- /dev/null +++ b/crates/benilla-app/assets/ui/benilla.toc @@ -0,0 +1,232 @@ +# The built-in interface, in load order: a bare filename is ours, from `assets/ui`; a path is +# the reference's own file, off the player's patch chain. Entry 0, Fonts.xml, loads alone into +# every boot VM, from Startup on; the rest loads on entering the world, then third-party addons. +# The font objects thus exist before the login screen; the reference loads Fonts.xml with the +# rest of FrameXML. +# `inherits=`, `parent=` and a named `relativeTo=` resolve only against names declared above; a +# missing one only warns (the template is skipped, the parent falls back to the enclosing frame). +# A later Lua global or template replaces an earlier one; a named frame publishes its global +# only while the name is free. +# A stock `.xml` that does not `"#, - ) - .unwrap(); - let mut script = UiScript::new().unwrap(); - script.set_screen_size(1024.0, 768.0); - script - .run("Heard = {} seterrorhandler(function(m) table.insert(Heard, m) end)") - .unwrap(); - let failures = addon.load(&script, &mut Status::default()); - assert_eq!(failures.len(), 4, "{failures:#?}"); - script.dispatch_script_errors_to_handler(); - let heard: Vec = script.eval("return Heard").unwrap(); - assert_eq!(heard.len(), 4, "{heard:#?}"); - for m in &heard { - assert!( - !m.contains("runtime error") - && !m.contains("syntax error") - && !m.contains("stack traceback") - && !m.contains("OnLoad: ") - && !m.contains("Bare/") - && !m.contains('\n'), - "{m:?}" - ); - } - assert!( - heard[0].ends_with("top.lua:2: attempt to index local `t' (a nil value)"), - "{:?}", - heard[0] - ); - assert!( - heard[0].starts_with("Interface\\AddOns\\Bare\\top.lua"), - "{:?}", - heard[0] - ); - assert!( - heard[1].contains("syntax.lua:1:") && heard[1].contains("unexpected symbol"), - "{:?}", - heard[1] - ); - assert!( - heard[2].ends_with("attempt to call method `NoSuchMethod' (a nil value)"), - "{:?}", - heard[2] - ); - assert!( - heard[3].ends_with("attempt to index local `u' (a nil value)"), - "{:?}", - heard[3] - ); - let _ = std::fs::remove_dir_all(&tmp); - } } diff --git a/crates/benilla-app/src/ui_script/bag_tests.rs b/crates/benilla-app/src/ui_script/bag_tests.rs index 2e2b7505..db83740a 100644 --- a/crates/benilla-app/src/ui_script/bag_tests.rs +++ b/crates/benilla-app/src/ui_script/bag_tests.rs @@ -101,16 +101,12 @@ fn b_opens_the_backpack_alone_and_closes_every_bag() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); s.set_container( @@ -175,16 +171,12 @@ fn shift_b_toggles_every_bag_at_once() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); s.set_container( 0, @@ -257,16 +249,12 @@ fn bag_bar_buttons_light_while_their_bag_is_open() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); s.set_container( 0, @@ -369,22 +357,19 @@ fn bag_tooltip_hangs_left_when_the_slot_sits_in_the_right_half() { s.set_screen_size(1024.0, 768.0); // The stock tooltip sizes from its lines, so reading its rect needs a text measurer. s.set_text_measurer(Box::new(super::FixedWidthFont(6.0))); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); let mut slots = std::collections::HashMap::new(); slots.insert( 1, ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -393,7 +378,6 @@ fn bag_tooltip_hangs_left_when_the_slot_sits_in_the_right_half() { texture: Some("Interface\\Icons\\INV_ThrowingKnife_02".into()), count: 200, quality: Some(1), - inventory_type: 0, item_id: 2947, link: Some("|cffffffff|Hitem:2947|h[Small Throwing Knife]|h|r".into()), locked: false, @@ -447,22 +431,19 @@ fn hovered_bag_tooltip_fills_itself_when_the_stats_land() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); let mut slots = std::collections::HashMap::new(); slots.insert( 1, ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -471,7 +452,6 @@ fn hovered_bag_tooltip_fills_itself_when_the_stats_land() { texture: Some("Interface\\Icons\\INV_Sword_04".into()), count: 1, quality: Some(1), - inventory_type: 0, item_id: 25, link: Some("|cffffffff|Hitem:25|h[Worn Shortsword]|h|r".into()), locked: false, @@ -555,22 +535,19 @@ fn vendor_bag_hover_shows_sell_price_and_arms_the_pouch_cursor() { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); let mut slots = std::collections::HashMap::new(); slots.insert( 1, ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -579,7 +556,6 @@ fn vendor_bag_hover_shows_sell_price_and_arms_the_pouch_cursor() { texture: Some("Interface\\Icons\\INV_Misc_Pelt_Wolf_01".into()), count: 4, quality: Some(1), - inventory_type: 0, item_id: 2318, link: Some("|cffffffff|Hitem:2318|h[Light Leather]|h|r".into()), locked: false, @@ -594,6 +570,7 @@ fn vendor_bag_hover_shows_sell_price_and_arms_the_pouch_cursor() { slots.insert( 2, ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -602,7 +579,6 @@ fn vendor_bag_hover_shows_sell_price_and_arms_the_pouch_cursor() { texture: Some("Interface\\Icons\\INV_Misc_Key_03".into()), count: 1, quality: Some(1), - inventory_type: 0, item_id: 9999, link: Some("|cffffffff|Hitem:9999|h[Shadowforge Key]|h|r".into()), locked: false, @@ -695,16 +671,12 @@ fn readable_letter_hover_shows_the_inspect_magnifier() { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); let mut slots = std::collections::HashMap::new(); @@ -766,22 +738,19 @@ fn drag_across_two_slots_queues_the_same_move_a_click_pickup_would() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); let mut slots = std::collections::HashMap::new(); slots.insert( 1, ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -790,7 +759,6 @@ fn drag_across_two_slots_queues_the_same_move_a_click_pickup_would() { texture: Some("Interface\\Icons\\INV_Misc_Food_16".into()), count: 5, quality: Some(3), - inventory_type: 0, item_id: 117, link: Some("|cffffffff|Hitem:117|h[Tough Jerky]|h|r".into()), locked: false, @@ -853,22 +821,19 @@ fn a_second_bag_window_feeds_and_paints_via_the_bag_bar() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); let mut slots = std::collections::HashMap::new(); slots.insert( 1, ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -877,7 +842,6 @@ fn a_second_bag_window_feeds_and_paints_via_the_bag_bar() { texture: Some("Interface\\Icons\\INV_Misc_Gem_01".into()), count: 1, quality: Some(2), - inventory_type: 0, item_id: 200, link: Some("|cffffffff|Hitem:200|h[Shiny Gem]|h|r".into()), locked: false, @@ -929,16 +893,12 @@ fn equipped_bag_window_snug_fits_its_row_count() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); // (bag, slots, height): 6 is 72+32+10, 8 is 94+32+10, 10 is 72+73+10, 20 is 94+155+10, and the @@ -988,24 +948,21 @@ fn equipped_bag_window_snug_fits_its_row_count() { } /// Open a backpack holding a five-stack in slot 1; returns that slot button's centre. -pub(super) fn open_backpack_with_a_five_stack(s: &mut UiScript) -> (f32, f32) { - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\StackSplitFrame.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { +fn open_backpack_with_a_five_stack(s: &mut UiScript) -> (f32, f32) { + for file in BAG_UI { load_xml(s, file); } + load_xml(s, "ScrollTemplates.xml"); // our scroll kit + load_xml(s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(s, "Interface\\FrameXML\\MerchantFrame.xml"); + load_xml(s, "Interface\\FrameXML\\StackSplitFrame.xml"); s.set_money(0); let mut slots = std::collections::HashMap::new(); slots.insert( 1, ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -1014,7 +971,6 @@ pub(super) fn open_backpack_with_a_five_stack(s: &mut UiScript) -> (f32, f32) { texture: Some("Interface\\Icons\\INV_Misc_Food_16".into()), count: 5, quality: Some(1), - inventory_type: 0, item_id: 117, link: Some("|cffffffff|Hitem:117|h[Tough Jerky]|h|r".into()), locked: false, @@ -1311,16 +1267,12 @@ fn bag_slot_cooldown_sweeps_through_the_xml() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); s.tick(100.0); // a nonzero clock epoch @@ -1369,18 +1321,14 @@ fn the_bar_bag_buttons_name_themselves_on_hover() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); // `GetBindingKey` reads the registered command set, seeded as the app seeds it. - crate::ui_script::load_stock_bindings(&mut s); + s.register_bindings(&crate::bindings::registry_commands()); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.resolve(); hover(&mut s, "MainMenuBarBackpackButton"); @@ -1417,6 +1365,7 @@ fn the_bar_bag_buttons_name_themselves_on_hover() { already_bound: false, bar_placeable: true, durability: None, + repair_cost: 0, flags: 0, item_id: 4496, icon: Some("Interface\\Icons\\INV_Misc_Bag_08".into()), @@ -1476,8 +1425,12 @@ fn keyring(size: u32, occupied: bool) -> ContainerState { } } -/// Seat a player at `level`: stock `GetKeyRingSize` (`ContainerFrame.lua:773-786`: 4, then 8 at -/// 40, 12 at 50, 16 above 60) reads `UnitLevel("player")`, so a fixture left at 0 gets 4 slots. +/// Seat a player at `level`. +/// +/// Note for anyone reading this expecting it to size the keyring: it does not, and an earlier +/// comment here claiming stock `GetKeyRingSize` reads `UnitLevel` was simply wrong — `UnitLevel` +/// appears nowhere in `ContainerFrame.lua`. That function *scans* the keyring for its highest +/// filled slot and rounds up to a multiple of four (`:816-844`). fn seat_player_at_level(s: &mut UiScript, level: u32) { let mut player = benilla_ui::script::UnitState { level, @@ -1487,6 +1440,74 @@ fn seat_player_at_level(s: &mut UiScript, level: u32) { s.set_unit("player", Some(player)); } +/// The keyring's two halves, which pfUI's show/hide button plays off against each other. +/// +/// `GetContainerNumSlots(KEYRING_CONTAINER)` answers **0** — nothing in stock FrameXML ever asks +/// it, because the keyring is sized by `GetKeyRingSize`'s scan everywhere, which is only worth +/// writing if the container API does not answer for it. Meanwhile every addressable slot still +/// answers `GetContainerItemInfo`, which is what that scan reads. +/// +/// pfUI's bag module is exactly those two: `GetContainerNumSlots(-2)` slots normally, and +/// `GetKeyRingSize()` when showing (`modules/bags.lua:202-203`). Reporting a real count for the +/// first made both arms agree and the button do nothing. +#[test] +fn the_keyring_reports_no_container_slots_but_still_answers_per_slot() { + use benilla_ui::script::{ContainerSlot, ContainerState}; + use std::collections::HashMap; + + let _data = benilla_formats::wow_data_or_skip!(); + let mut s = UiScript::new().unwrap(); + // `GetKeyRingSize` is FrameXML, not an engine global, so it has to be loaded to be scanned — + // but only its own file: the full `BAG_UI` set drags in `FloatingChatFrame`, whose + // prerequisites this harness does not seat (`docs/ADDON_COMPAT.md`'s harness row). + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\ContainerFrame.lua"); + // The full UI supplies this (it reads -2 in a loaded client); this fixture loads one file. + s.run("KEYRING_CONTAINER = -2").unwrap(); + s.set_container( + -2, + Some(ContainerState { + name: Some("Keyring".into()), + // As the feed writes it. + num_slots: 0, + slots: HashMap::from([( + 1, + ContainerSlot { + item_id: 5, + count: 1, + texture: Some(r"Interface\Icons\INV_Misc_Key_03".into()), + ..Default::default() + }, + )]), + }), + ); + + assert_eq!( + s.eval::("return GetContainerNumSlots(-2)").unwrap(), + 0, + "the hide arm of pfUI's toggle is this being zero" + ); + assert!( + s.eval::("return GetContainerItemInfo(-2, 1) ~= nil") + .unwrap(), + "and the slot still answers, which is what GetKeyRingSize scans" + ); + + // pfUI's own two lines, transcribed (`modules/bags.lua:202-203`), both ways round. + let bagsize = |s: &mut UiScript, showing: bool| { + s.eval::(&format!( + "local showKeyring = {} local bagsize = GetContainerNumSlots(-2) if showKeyring == true then bagsize = GetKeyRingSize() end return bagsize", + if showing { "true" } else { "nil" } + )) + .unwrap() + }; + assert_eq!(bagsize(&mut s, false), 0, "hidden draws no keyring slots"); + assert!( + bagsize(&mut s, true) > 0, + "shown draws the scanned size, so the button changes something" + ); +} + /// How many drawn quads carry a texture path containing `needle`. fn drawn_with(s: &mut UiScript, needle: &str) -> usize { s.resolve(); @@ -1499,17 +1520,13 @@ fn drawn_with(s: &mut UiScript, needle: &str) -> usize { /// The bar and bag files the keyring spans, plus `UIErrorsFrame.xml`: `PutKeyInKeyRing` reports a /// full ring through `UIErrorsFrame`. fn keyring_surface(s: &UiScript) { - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\UIErrorsFrame.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(s, file); } + load_xml(s, "ScrollTemplates.xml"); // our scroll kit + load_xml(s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(s, "Interface\\FrameXML\\MerchantFrame.xml"); + load_xml(s, "Interface\\FrameXML\\UIErrorsFrame.xml"); } /// The first key shows the keyring button, swaps the bar's two right-hand strips to the keyring @@ -1637,11 +1654,8 @@ fn the_keyring_button_opens_a_keyring_window() { .unwrap(); assert_eq!(shown, 8, "only the level-unlocked keyring slots are drawn"); assert_eq!( - s.eval::( - "local _, _, id = string.find(GetContainerItemLink(KEYRING_CONTAINER, 1), 'item:(%d+)') \ - return tonumber(id)" - ) - .unwrap(), + s.eval::("return BenillaGetContainerItemID(KEYRING_CONTAINER, 1)") + .unwrap(), 7146 ); @@ -1654,52 +1668,6 @@ fn the_keyring_button_opens_a_keyring_window() { assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); } -/// A hovered key's tooltip comes through the stock keyring arm of `ContainerFrameItemButton_OnEnter` -/// (`ContainerFrame.lua:616-619`): `SetInventoryItem("player", KeyRingButtonIDToInvSlotID(id))`, -/// the key's inventory slot 82 and up, answered from the keyring container. -#[test] -fn a_hovered_key_shows_its_tooltip_through_its_inventory_slot() { - let _data = benilla_formats::wow_data_or_skip!(); - let mut s = UiScript::new().unwrap(); - s.set_screen_size(1024.0, 768.0); - s.set_text_measurer(Box::new(super::FixedWidthFont(6.0))); - keyring_surface(&s); - s.set_money(0); - s.set_has_key(true); - seat_player_at_level(&mut s, 44); - s.set_container(-2, Some(keyring(8, true))); - s.fire_event("BAG_UPDATE", vec![benilla_ui::script::ScriptValue::Int(-2)]); - s.run("KeyRingButton:Click()").unwrap(); - assert!(open(&s, -2), "fixture: the keyring is open"); - // Which slot the tooltip is asked for: the frame's own field shadows the method table. - s.run( - r#"local method = GameTooltip.SetInventoryItem - GameTooltip.SetInventoryItem = function(self, unit, slot) - ASKED = unit .. ":" .. slot - return method(self, unit, slot) - end"#, - ) - .unwrap(); - s.resolve(); - - let key = bag_slot_button(&s, -2, 1); - hover(&mut s, &key); - assert!(s.errors().is_empty(), "hover errors: {:?}", s.errors()); - assert_eq!( - s.eval::>("return ASKED").unwrap().as_deref(), - Some("player:82"), - "the stock arm asks for key 1's inventory slot" - ); - assert!(s.eval::("return GameTooltip:IsVisible()").unwrap()); - assert_eq!( - s.eval::("return GameTooltipTextLeft1:GetText()") - .unwrap(), - "The Scarlet Key" - ); - unhover(&mut s); - assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); -} - /// Dropping a held key on the button files it in the first free keyring slot (stock /// `PutKeyInKeyRing`); a full keyring refuses the drop. #[test] @@ -1792,16 +1760,12 @@ fn an_item_push_drops_its_icon_into_the_bag_that_took_it() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1600.0, 900.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); // `ForcedBackpackItem.m2` (`benilla-extract m2seq`, `m2batch`): one sequence (id 0, 1000 ms, // clamp) and a 0.02707 × 0.07962 box, which is 42.41 × 124.72 at 16:9, where a layout unit is // 768·√(a²+1) = 1566.4 FrameXML units. @@ -1953,16 +1917,12 @@ fn an_addon_that_hooks_toggle_backpack_receives_the_click() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); // Bagnon's idiom: capture the original, replace the global. @@ -2002,16 +1962,12 @@ fn the_bag_slots_carry_the_references_names_and_icon_names() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); for i in 0..4 { assert!( @@ -2056,16 +2012,12 @@ fn the_backpack_buttons_ring_follows_its_own_window_through_real_clicks() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kit - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); // Stock `ToggleBag` opens nothing for a size-0 container, so the backpack is fed. s.set_container( diff --git a/crates/benilla-app/src/ui_script/bagnon_render_tests.rs b/crates/benilla-app/src/ui_script/bagnon_render_tests.rs index 19cc1ba8..76cd5553 100644 --- a/crates/benilla-app/src/ui_script/bagnon_render_tests.rs +++ b/crates/benilla-app/src/ui_script/bagnon_render_tests.rs @@ -129,7 +129,7 @@ fn seat(root: &Path, seat: Seat) -> UiScript { let mut registry: Vec = INSTALLED .iter() - .map(|n| super::addons::info_from_toc(n, &read_toc(root, n), false)) + .map(|n| super::addons::info_from_toc(n, &read_toc(root, n))) .collect(); s.register_addons(registry.clone(), Some(root.to_path_buf()), None, None); s.set_realm_name("Harness"); @@ -576,7 +576,7 @@ fn dragging_a_bagnon_slot_picks_the_item_up() { let (dx, dy) = centre_of(&mut s, &empty); s.mouse_move(sx, sy); s.mouse_button(sx, sy, "LeftButton", true); - s.mouse_move(sx + 12.0, sy + 12.0); // past the drag-start distance ⇒ OnDragStart + s.mouse_move(sx + 12.0, sy + 12.0); // past DRAG_START_THRESHOLD ⇒ OnDragStart s.mouse_move(dx, dy); s.mouse_button(dx, dy, "LeftButton", false); diff --git a/crates/benilla-app/src/ui_script/basic_controls_tests.rs b/crates/benilla-app/src/ui_script/basic_controls_tests.rs index e8773951..5f5a4f69 100644 --- a/crates/benilla-app/src/ui_script/basic_controls_tests.rs +++ b/crates/benilla-app/src/ui_script/basic_controls_tests.rs @@ -90,9 +90,9 @@ fn error_message_is_the_installed_handler_and_the_host_channel_stays_sighted() { "reported" ); // An engine-caught error still lands in `errors()`; the dispatch adds the dialog on top. - s.run("BrokenProbe = CreateFrame('Frame') BrokenProbe:RegisterEvent('PLAYER_ALIVE') BrokenProbe:SetScript('OnEvent', function() error('fault') end)") + s.run("BrokenProbe = CreateFrame('Frame') BrokenProbe:RegisterEvent('ERROR_PROBE') BrokenProbe:SetScript('OnEvent', function() error('fault') end)") .unwrap(); - s.fire_event("PLAYER_ALIVE", vec![]); + s.fire_event("ERROR_PROBE", vec![]); assert!( s.errors().iter().any(|e| e.contains("fault")), "engine-caught errors stay on the host channel: {:?}", diff --git a/crates/benilla-app/src/ui_script/battlefield_minimap_tests.rs b/crates/benilla-app/src/ui_script/battlefield_minimap_tests.rs index a6df8844..cd03be6c 100644 --- a/crates/benilla-app/src/ui_script/battlefield_minimap_tests.rs +++ b/crates/benilla-app/src/ui_script/battlefield_minimap_tests.rs @@ -4,16 +4,20 @@ use benilla_ui::script::{ BattlefieldFlagView, BattlefieldPositionView, QuadContent, UiScript, UnitState, - WorldMapContinentView, WorldMapLandmarkSource, WorldMapOverlayView, WorldMapZoneView, + WorldMapContinentView, WorldMapLandmarkView, WorldMapOverlayView, WorldMapZoneView, WorldStateUiView, ARROW_MODEL, }; +/// The manifest's one warning. Deviation: `gxRefresh`, read at `OptionsFrame.lua:300`, is not +/// registered, because nothing here can set a refresh rate. +const MANIFEST_WARNINGS: [&str; 1] = ["unknown CVar 'gxRefresh' (not host-registered) — ignored"]; + fn quiet(s: &UiScript) { assert!(s.errors().is_empty(), "script errors: {:#?}", s.errors()); - assert!( - s.warnings().is_empty(), - "host warnings: {:#?}", - s.warnings() + assert_eq!( + s.warnings(), + MANIFEST_WARNINGS, + "host warnings beyond the manifest's own" ); } @@ -60,6 +64,8 @@ fn arrow_facts(s: &mut UiScript) { fn overlay(texture: &str, w: u32, h: u32, ox: u32, oy: u32, bit: u32) -> WorldMapOverlayView { WorldMapOverlayView { texture: format!("Interface\\WorldMap\\WarsongGulch\\{texture}"), + texture_name: texture.to_owned(), + area_id: 0, width: w, height: h, offset_x: ox, @@ -411,17 +417,14 @@ fn the_poi_pool_grows_from_the_landmarks_and_parks_its_tail() { open(&mut s); assert_eq!(s.eval::("return NUM_BATTLEFIELDMAP_POIS").unwrap(), 0); - // A world position here is already map UV, so a landmark source sits where it says. - s.set_world_loc_projector(Box::new(|_, _, x, y| Some((x, y)))); - let landmark = |name: &str, icon: u32, uv: (f32, f32)| WorldMapLandmarkSource { + let landmark = |name: &str, icon: u32, uv: (f32, f32)| WorldMapLandmarkView { name: name.into(), description: String::new(), - map: 0, - pos: uv, - icons: [Some(icon); 3], + texture_index: icon, + uv, }; // Icon 6 is `ICON_POI_REDFLAG`, icon 9 the second row's first cell of the 8×8 `POIIcons` atlas. - s.set_world_map_landmark_sources(vec![ + s.set_world_map_landmarks(vec![ landmark("Silverwing Flag", 6, (0.25, 0.5)), landmark("Warsong Flag", 9, (0.75, 0.25)), ]); @@ -469,7 +472,7 @@ fn the_poi_pool_grows_from_the_landmarks_and_parks_its_tail() { "DEFAULT_POI_ICON_SIZE × GetBattlefieldMapIconScale()" ); - s.set_world_map_landmark_sources(vec![landmark("Warsong Flag", 9, (0.75, 0.25))]); + s.set_world_map_landmarks(vec![landmark("Warsong Flag", 9, (0.75, 0.25))]); s.tick(0.0); assert_eq!( s.eval::("return NUM_BATTLEFIELDMAP_POIS").unwrap(), diff --git a/crates/benilla-app/src/ui_script/battlefield_tests.rs b/crates/benilla-app/src/ui_script/battlefield_tests.rs index 6b97d0f1..9b7b238a 100644 --- a/crates/benilla-app/src/ui_script/battlefield_tests.rs +++ b/crates/benilla-app/src/ui_script/battlefield_tests.rs @@ -23,11 +23,11 @@ fn session() -> UiScript { // `FloatingChatFrame.lua:9`'s value, read by the icon's fade-in (`BattlefieldFrame.lua:142`). s.run("CHAT_FRAME_FADE_TIME = 0.15").unwrap(); for f in [ - "Interface\\FrameXML\\GlobalStrings.lua", "Interface\\FrameXML\\Fonts.xml", + "Interface\\FrameXML\\GlobalStrings.lua", "Interface\\FrameXML\\Localization.xml", - "Interface\\FrameXML\\BasicControls.xml", "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\BasicControls.xml", r"Interface\FrameXML\UIParent.xml", r"Interface\FrameXML\MoneyFrame.lua", r"Interface\FrameXML\MoneyFrame.xml", @@ -77,7 +77,7 @@ fn the_queue_icon_follows_the_slots_across_update_battlefield_status() { let mut s = session(); assert!(!visible(&s, "MiniMapBattlefieldFrame"), "no queue at load"); - s.set_battlefield_queue([slot(529, "Arathi Basin", 1, 0), empty(), empty()], 0); + s.set_battlefield_queue(vec![slot(529, "Arathi Basin", 1, 0), empty(), empty()], 0); s.fire_event("UPDATE_BATTLEFIELD_STATUS", vec![]); assert!( visible(&s, "MiniMapBattlefieldFrame"), @@ -103,7 +103,7 @@ fn the_queue_icon_follows_the_slots_across_update_battlefield_status() { assert!(!visible(&s, "StaticPopup1"), "queued is not yet a question"); // Status 2, ready to enter: the confirm dialog and the port countdown. - s.set_battlefield_queue([slot(529, "Arathi Basin", 2, 3), empty(), empty()], 0); + s.set_battlefield_queue(vec![slot(529, "Arathi Basin", 2, 3), empty(), empty()], 0); s.fire_event("UPDATE_BATTLEFIELD_STATUS", vec![]); assert!( visible(&s, "StaticPopup1"), @@ -127,7 +127,7 @@ fn the_queue_icon_follows_the_slots_across_update_battlefield_status() { s.run("StaticPopup1Button1:Click()").unwrap(); assert_eq!(s.take_battlefield_port_requests(), vec![(1, true)]); - s.set_battlefield_queue([empty(), empty(), empty()], 0); + s.set_battlefield_queue(vec![empty(), empty(), empty()], 0); s.fire_event("UPDATE_BATTLEFIELD_STATUS", vec![]); assert!( !visible(&s, "MiniMapBattlefieldFrame"), @@ -154,7 +154,7 @@ fn the_list_window_opens_on_battlefields_show_and_joins_the_selection() { }), group_queue: true, }); - s.set_battlefield_queue([empty(), empty(), empty()], 0); + s.set_battlefield_queue(vec![empty(), empty(), empty()], 0); assert!(!visible(&s, "BattlefieldFrame")); s.fire_event("BATTLEFIELDS_SHOW", vec![]); assert!(visible(&s, "BattlefieldFrame"), "the event shows the panel"); @@ -228,7 +228,7 @@ fn the_list_rows_carry_the_queue_status() { }), ..Default::default() }); - s.set_battlefield_queue([slot(529, "Arathi Basin", 1, 4), empty(), empty()], 0); + s.set_battlefield_queue(vec![slot(529, "Arathi Basin", 1, 4), empty(), empty()], 0); s.fire_event("BATTLEFIELDS_SHOW", vec![]); assert_eq!( s.eval::("return BattlefieldZone2Status:GetText()") @@ -243,14 +243,3 @@ fn the_list_rows_carry_the_queue_status() { (`FontString:GetText 0x79d690` substitutes nil)" ); } - -#[test] -fn a_party_leader_change_before_any_queue_push_updates_the_list_cleanly() { - let _data = benilla_formats::wow_data_or_skip!(); - let mut s = session(); - s.take_errors(); - // A party login fires PARTY_LEADER_CHANGED, which runs `BattlefieldFrame_Update`; it reads - // `GetBattlefieldStatus(1..3)` and concatenates the name of any status but "none" (line 242). - s.fire_event("PARTY_LEADER_CHANGED", vec![]); - assert_eq!(s.take_errors(), Vec::::new()); -} diff --git a/crates/benilla-app/src/ui_script/binder_tests.rs b/crates/benilla-app/src/ui_script/binder_tests.rs index 8b9db5e2..b85690dd 100644 --- a/crates/benilla-app/src/ui_script/binder_tests.rs +++ b/crates/benilla-app/src/ui_script/binder_tests.rs @@ -9,15 +9,15 @@ use super::test_ui::load_ui as load_xml; fn setup() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); s.set_binder_pending(true); s diff --git a/crates/benilla-app/src/ui_script/buff_tests.rs b/crates/benilla-app/src/ui_script/buff_tests.rs index 1dd38505..1af626a3 100644 --- a/crates/benilla-app/src/ui_script/buff_tests.rs +++ b/crates/benilla-app/src/ui_script/buff_tests.rs @@ -11,27 +11,29 @@ fn harness() -> UiScript { // The one-letter duration strings, which `SecondsToTimeAbbrev` formats unguarded. load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - // `SecondsToTimeAbbrev` comes with `UIParent.xml` (`UIParent.lua:1034`). - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); + // `BuffButton_Update` asks `GameTooltip:IsOwned(this)` unguarded (`BuffFrame.lua:104`). load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); - // `BuffButton_Update` asks `GameTooltip:IsOwned(this)` unguarded (`BuffFrame.lua:104`). load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); + // `SecondsToTimeAbbrev` comes with `UIParent.xml` (`UIParent.lua:1034`). + load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); + load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); + load_xml(&s, "Interface\\FrameXML\\Cooldown.xml"); + load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml"); load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua"); load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml"); load_xml(&s, "Interface\\FrameXML\\MainMenuBar.xml"); - load_xml(&s, "Interface\\FrameXML\\Cooldown.xml"); - load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml"); load_xml(&s, "Interface\\FrameXML\\ActionBarFrame.xml"); + load_xml(&s, "Interface\\FrameXML\\BonusActionBarFrame.xml"); // Timers on for these tests; the 1.12 default is "0" (`UIOptionsFrame.lua:104`). s.run("SHOW_BUFF_DURATIONS = \"1\"").unwrap(); load_xml(&s, "Interface\\FrameXML\\BuffFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\BonusActionBarFrame.xml"); // As the app does: `BuffFrame_OnLoad` never calls `BuffButtons_UpdatePositions`, which 1.12 // runs on `VARIABLES_LOADED` (`UIOptionsFrame.lua:206`). super::manifest::apply_buff_durations(&s).unwrap(); @@ -58,6 +60,7 @@ fn aura( duration: expiration_time, expiration_time, helpful, + slot: if helpful { 0 } else { 32 }, cancelable, // The feed takes this from the spell (`ui_aura::until_cancelled`); here, from the expiry. until_cancelled: expiration_time == 0.0, @@ -67,7 +70,7 @@ fn aura( /// Push the list and fire `PLAYER_AURAS_CHANGED`, as the app's feed does (`BuffFrame.lua:113`). fn push(s: &mut UiScript, auras: Vec) { - s.set_player_auras(auras); + s.set_auras("player", Some(auras)); s.fire_event("PLAYER_AURAS_CHANGED", vec![]); } @@ -349,7 +352,7 @@ fn a_refreshed_duration_reaches_the_bar_with_no_aura_event() { assert_eq!(text(&s, "BuffButton0Duration"), "19 s"); // The refresh: a new expiry and no event. - s.set_player_auras(vec![mark(300.0)]); + s.set_auras("player", Some(vec![mark(300.0)])); frame(&mut s, 0.1); assert_eq!( text(&s, "BuffButton0Duration"), @@ -365,14 +368,14 @@ fn a_refreshed_duration_reaches_the_bar_with_no_aura_event() { // timed aura with no stamp yet shows "0 s" and pulses until the stamp joins. let mut pending = mark(0.0); pending.until_cancelled = false; // a timed spell, stamp not yet arrived - s.set_player_auras(vec![pending]); + s.set_auras("player", Some(vec![pending])); frame(&mut s, 0.1); assert_eq!( text(&s, "BuffButton0Duration"), "0 s", "the reference draws the floor of a timed aura it has no stamp for, not a blank" ); - s.set_player_auras(vec![mark(60.0)]); // the stamp joins, still no event + s.set_auras("player", Some(vec![mark(60.0)])); // the stamp joins, still no event frame(&mut s, 0.1); assert_eq!( text(&s, "BuffButton0Duration"), @@ -883,14 +886,17 @@ fn the_duration_line_reads_the_real_global_strings() { // `secs_left` seconds remaining at GetTime = 10, read back as the tooltip's last line. let mut line = |secs_left: f64| -> String { - s.set_player_auras(vec![AuraState { - spell_id: 1459, - name: Some("Arcane Intellect".into()), - duration: 86_400.0, - expiration_time: 10.0 + secs_left, - helpful: true, - ..Default::default() - }]); + s.set_auras( + "player", + Some(vec![AuraState { + spell_id: 1459, + name: Some("Arcane Intellect".into()), + duration: 86_400.0, + expiration_time: 10.0 + secs_left, + helpful: true, + ..Default::default() + }]), + ); s.run( r#" BENILLA_TIP:SetOwner(BENILLA_ANCHOR, "ANCHOR_RIGHT") diff --git a/crates/benilla-app/src/ui_script/cast_tests.rs b/crates/benilla-app/src/ui_script/cast_tests.rs index 60493c84..0854e8df 100644 --- a/crates/benilla-app/src/ui_script/cast_tests.rs +++ b/crates/benilla-app/src/ui_script/cast_tests.rs @@ -9,9 +9,9 @@ use super::test_ui::load_ui as load_xml; fn harness() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); + load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); // The stock file sets `CastingBarText` to the `FAILED` global (`CastingBarFrame.lua:61`). load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua"); - load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); load_xml(&s, "Interface\\FrameXML\\CastingBarFrame.xml"); s } @@ -424,22 +424,22 @@ fn managed_positions_track_the_bottom_bar_stack() { benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\UIParent.xml"); + load_xml(&s, "Interface\\FrameXML\\CastingBarFrame.xml"); + load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit the chat menus build from + load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua"); + load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); + load_xml(&s, "Interface\\FrameXML\\ChatFrame.xml"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); // TOOLTIP_DEFAULT_COLOR for the dropdowns - load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit the chat menus build from load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua"); load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); - load_xml(&s, "Interface\\FrameXML\\ChatFrame.xml"); load_xml(&s, "Interface\\FrameXML\\FloatingChatFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\CastingBarFrame.xml"); // The load-time pass with no bars shown: the bare bases. s.run("UIParent_ManageFramePositions()").unwrap(); diff --git a/crates/benilla-app/src/ui_script/character_tests.rs b/crates/benilla-app/src/ui_script/character_tests.rs index 4d2c3b0e..c66b96a6 100644 --- a/crates/benilla-app/src/ui_script/character_tests.rs +++ b/crates/benilla-app/src/ui_script/character_tests.rs @@ -56,6 +56,7 @@ fn inventory_with_head_item() -> InventorySlots { already_bound: false, bar_placeable: true, durability: None, + repair_cost: 0, flags: 0, item_id: 1234, icon: Some("Interface\\Icons\\INV_Helmet_01".into()), @@ -78,6 +79,7 @@ fn backpack_with_fitting_helm() -> benilla_ui::script::ContainerState { slots.insert( 1, benilla_ui::script::ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -86,7 +88,6 @@ fn backpack_with_fitting_helm() -> benilla_ui::script::ContainerState { texture: Some("Interface\\Icons\\INV_Helmet_02".into()), count: 1, quality: Some(3), - inventory_type: 0, item_id: 2000, link: Some("|cff0070dd|Hitem:2000:0:0:0|h[Another Helm]|h|r".into()), locked: false, @@ -443,11 +444,10 @@ fn clicking_an_occupied_doll_slot_picks_it_up_and_locks_it() { assert!(s.errors().is_empty(), "click errors: {:?}", s.errors()); assert!(s.eval::("return CursorHasItem()").unwrap()); - assert!( - matches!(s.cursor_payload(), Some(benilla_ui::script::CursorPayload::Item(c)) if c.item_id == 1234), - "the cursor holds item 1234: {:?}", - s.cursor_payload() - ); + let (kind, id) = s + .eval::<(String, i64)>("local k, id = GetCursorInfo() return k, id") + .unwrap(); + assert_eq!((kind.as_str(), id), ("item", 1234)); assert!( s.eval::("return IsInventoryItemLocked(1)").unwrap(), "the picked slot locks" @@ -510,20 +510,8 @@ fn cursor_update_highlights_fitting_doll_slots_while_holding_a_bag_item() { assert!(s.errors().is_empty(), "{:?}", s.errors()); // The predicate the arm branches on, pinned beside the effect. - // `CursorCanGoInSlot` (`0x4c8ff0`) pushes the number 1 (`0x4c9063`) or nil (`0x4c909e`) and - // returns one value (`arg.n` counts the returns). - let ask = - "local function c(...) return arg.n .. ':' .. type(arg[1]) .. ':' .. tostring(arg[1]) end"; - assert_eq!( - s.eval::(&format!("{ask} return c(CursorCanGoInSlot(1))")) - .unwrap(), - "1:number:1" - ); - assert_eq!( - s.eval::(&format!("{ask} return c(CursorCanGoInSlot(2))")) - .unwrap(), - "1:nil:nil" - ); + assert!(s.eval::("return CursorCanGoInSlot(1)").unwrap()); + assert!(!s.eval::("return CursorCanGoInSlot(2)").unwrap()); assert!( ring_lit(&mut s, "CharacterHeadSlot"), @@ -1096,20 +1084,20 @@ fn an_addons_tab_click_selects_through_the_generic_entry_point() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); // `PanelTemplates_SelectTab` asks `GameTooltip:IsOwned(tab)` (`UIPanelTemplates.lua:130`). + load_xml(&s, "ScrollTemplates.xml"); // our scroll kits load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); load_xml(&s, r"Interface\FrameXML\CharacterFrameTemplates.xml"); // the window tab + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); - load_xml(&s, "ScrollTemplates.xml"); // our scroll kits // Tabs named `Tab1..N`, as `PanelTemplates_UpdateTabs` looks them up, each with its id. s.run( diff --git a/crates/benilla-app/src/ui_script/chat_options_tests.rs b/crates/benilla-app/src/ui_script/chat_options_tests.rs index 4b87d9a6..2c2ec228 100644 --- a/crates/benilla-app/src/ui_script/chat_options_tests.rs +++ b/crates/benilla-app/src/ui_script/chat_options_tests.rs @@ -22,23 +22,27 @@ fn bake_strings(s: &UiScript) { fn chat_with_menu() -> UiScript { let mut s = UiScript::new().unwrap(); for file in [ - "Interface\\FrameXML\\GlobalStrings.lua", "Interface\\FrameXML\\Fonts.xml", - "Interface\\FrameXML\\BasicControls.xml", - "Interface\\FrameXML\\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", r"Interface\FrameXML\MoneyFrame.lua", r"Interface\FrameXML\MoneyFrame.xml", + r"Interface\FrameXML\UIParent.xml", "Interface\\FrameXML\\GameTooltip.xml", - "Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from "Interface\\FrameXML\\UIDropDownMenu.xml", + "ScrollTemplates.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\GlobalStrings.lua", "Interface\\FrameXML\\StaticPopup.xml", - "Interface\\FrameXML\\ChatFrame.xml", - "Interface\\FrameXML\\FloatingChatFrame.xml", "Interface\\FrameXML\\ColorPickerFrame.xml", - "ScrollTemplates.xml", + "Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\ChatFrame.xml", + "Interface\\FrameXML\\UIPanelTemplates.lua", + "Interface\\FrameXML\\UIPanelTemplates.xml", + "Interface\\FrameXML\\FloatingChatFrame.xml", ] { load_xml(&s, file); } diff --git a/crates/benilla-app/src/ui_script/chat_resize_tests.rs b/crates/benilla-app/src/ui_script/chat_resize_tests.rs index 166d7948..8fb70081 100644 --- a/crates/benilla-app/src/ui_script/chat_resize_tests.rs +++ b/crates/benilla-app/src/ui_script/chat_resize_tests.rs @@ -7,29 +7,33 @@ use super::test_ui::load_ui as load_xml; fn chat_ui() -> UiScript { let mut s = UiScript::new().unwrap(); for file in [ - "Interface\\FrameXML\\GlobalStrings.lua", "Interface\\FrameXML\\Fonts.xml", - "Interface\\FrameXML\\BasicControls.xml", - "Interface\\FrameXML\\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", r"Interface\FrameXML\MoneyFrame.lua", r"Interface\FrameXML\MoneyFrame.xml", + r"Interface\FrameXML\UIParent.xml", "Interface\\FrameXML\\GameTooltip.xml", - "Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from "Interface\\FrameXML\\UIDropDownMenu.xml", + "ScrollTemplates.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", "Interface\\FrameXML\\StaticPopup.xml", - "Interface\\FrameXML\\TextStatusBar.lua", - "Interface\\FrameXML\\TextStatusBar.xml", + "Interface\\FrameXML\\ColorPickerFrame.xml", + "Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", // A tab-drag stop's `FCF_ValidateChatFramePosition` reads `MainMenuBar:GetHeight()`. - "Interface\\FrameXML\\MainMenuBar.xml", "Interface\\FrameXML\\Cooldown.xml", "Interface\\FrameXML\\ActionButtonTemplate.xml", + "Interface\\FrameXML\\TextStatusBar.lua", + "Interface\\FrameXML\\TextStatusBar.xml", + "Interface\\FrameXML\\MainMenuBar.xml", "Interface\\FrameXML\\ChatFrame.xml", + "Interface\\FrameXML\\UIPanelTemplates.lua", + "Interface\\FrameXML\\UIPanelTemplates.xml", "Interface\\FrameXML\\FloatingChatFrame.xml", - "Interface\\FrameXML\\ColorPickerFrame.xml", - "ScrollTemplates.xml", ] { load_xml(&s, file); } @@ -79,11 +83,7 @@ fn left_click(s: &mut UiScript, frame: &str) { /// The tabs ship hidden until a stationary hover reveals them (`FCF_OnUpdate`), and a window /// moves by its tab (`OnDragStart` calls `StartMoving`), so every drag starts here. fn reveal(s: &mut UiScript) { - reveal_window(s, "ChatFrame1"); -} - -fn reveal_window(s: &mut UiScript, window: &str) { - let (cx, cy) = centre(s, window); + let (cx, cy) = centre(s, "ChatFrame1"); s.mouse_move(cx, cy); for _ in 0..45 { s.tick(0.016); @@ -91,14 +91,11 @@ fn reveal_window(s: &mut UiScript, window: &str) { } } -/// A move past the drag-start distance (0.01 of the 1600×900 diagonal, 15.7 root units). -const DRAG_STARTS_AT: f32 = 20.0; - fn drag_tab(s: &mut UiScript, dx: f32, dy: f32) { reveal(s); let (tx, ty) = centre(s, "ChatFrame1Tab"); s.mouse_button(tx, ty, "LeftButton", true); - s.mouse_move(tx + DRAG_STARTS_AT, ty); // past the drag-start distance: OnDragStart -> StartMoving + s.mouse_move(tx + 5.0, ty); // past the 4px threshold: OnDragStart -> StartMoving s.mouse_move(tx + dx, ty + dy); s.mouse_button(tx + dx, ty + dy, "LeftButton", false); s.resolve(); @@ -287,18 +284,18 @@ fn the_tab_drag_moves_an_unlocked_window_and_a_locked_one_stays_put() { reveal(&mut s); let (tx, ty) = centre(&mut s, "ChatFrame1Tab"); s.mouse_button(tx, ty, "LeftButton", true); - s.mouse_move(tx + DRAG_STARTS_AT, ty); + s.mouse_move(tx + 5.0, ty); s.mouse_move(tx + 80.0, ty); assert_eq!( left(&mut s), - start + 80.0 - DRAG_STARTS_AT, + start + 75.0, "the window followed the cursor from where the drag started" ); s.mouse_button(tx + 80.0, ty, "LeftButton", false); s.mouse_move(tx + 400.0, ty); assert_eq!( left(&mut s), - start + 80.0 - DRAG_STARTS_AT, + start + 75.0, "OnDragStop ended it — a move that outlived the button would keep going" ); assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); @@ -321,8 +318,8 @@ fn a_moved_window_is_user_placed_and_the_managed_pass_skips_it() { let moved = left(&mut s); assert_eq!( moved, - seated + 120.0 - DRAG_STARTS_AT, - "120 less the move spent on the drag-start distance" + seated + 115.0, + "120 less the 5px spent on the threshold" ); assert!( s.eval::("return ChatFrame1:IsUserPlaced()").unwrap(), @@ -361,9 +358,7 @@ fn the_geometry_round_trips_through_the_save_file() { assert_eq!(saved.len(), 1, "one window was placed: {saved:?}"); assert_eq!(saved[0].name, "ChatFrame1"); let text = crate::ui_layout::render(&saved); - let crate::ui_layout::Layout::Rect(read_back) = crate::ui_layout::parse(&text) else { - panic!("written in the rect form:\n{text}"); - }; + let read_back = crate::ui_layout::parse(&text); assert_eq!( read_back, saved, "the file expresses what the seam produced" @@ -526,158 +521,3 @@ fn the_docked_combat_log_cannot_be_moved_or_resized_on_its_own() { assert_eq!(one, two, "the dock moved as one"); assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); } - -/// Drag `window`'s tab by `(dx, dy)`: press, past the drag threshold, move, release. -fn drag_named_tab(s: &mut UiScript, window: &str, dx: f32, dy: f32) { - reveal_window(s, window); - let tab = format!("{window}Tab"); - let tab = tab.as_str(); - let (tx, ty) = centre(s, tab); - assert_eq!( - s.hit_test_name(tx, ty).as_deref(), - Some(tab), - "the tab is hit" - ); - // As a player's mouse runs it: the hover is on the tab before the press, and a frame resolves - // the moved layout before the release tests the cursor against the tab. - s.mouse_move(tx, ty); - s.mouse_button(tx, ty, "LeftButton", true); - s.mouse_move(tx + DRAG_STARTS_AT, ty); - s.mouse_move(tx + dx, ty + dy); - s.resolve(); - s.mouse_button(tx + dx, ty + dy, "LeftButton", false); - s.resolve(); -} - -/// `FCF_Tab_OnClick` reads `GetButtonState()` inside `OnClick`: NORMAL on the press (`0x779285` -/// before the push `0x7792b1`) so an undocked tab picks its window up, PUSHED on the release -/// (`0x7793a9` before `0x7793de`) so it drops it. A tab pulled out of the dock drags again, and a -/// plain click picks the window up and puts it down, never leaving it on the cursor. -#[test] -fn an_undocked_tab_drags_again_and_a_plain_click_drops_the_window() { - let _data = benilla_formats::wow_data_or_skip!(); - let mut s = chat_ui(); - s.run("FCF_SelectDockFrame(ChatFrame2)").unwrap(); - s.resolve(); - drag_named_tab(&mut s, "ChatFrame2", 0.0, -200.0); - assert!( - s.eval::("return not ChatFrame2.isDocked").unwrap(), - "the first drag pulled the window out of the dock" - ); - assert!(s.eval::("return MOVING_CHATFRAME == nil").unwrap()); - - let first = centre(&mut s, "ChatFrame2"); - drag_named_tab(&mut s, "ChatFrame2", 0.0, 120.0); - let second = centre(&mut s, "ChatFrame2"); - assert_eq!( - second.1, - first.1 + 120.0, - "the second drag of the undocked tab moved the window" - ); - - reveal_window(&mut s, "ChatFrame2"); - let (tx, ty) = centre(&mut s, "ChatFrame2Tab"); - s.mouse_move(tx, ty); - s.mouse_button(tx, ty, "LeftButton", true); - assert!( - s.eval::("return MOVING_CHATFRAME == ChatFrame2") - .unwrap(), - "the press picked the window up" - ); - s.mouse_button(tx, ty, "LeftButton", false); - assert!( - s.eval::("return MOVING_CHATFRAME == nil").unwrap(), - "the release put it down" - ); - let before = centre(&mut s, "ChatFrame2"); - s.mouse_move(tx + 90.0, ty + 40.0); - assert_eq!( - centre(&mut s, "ChatFrame2"), - before, - "the window does not follow a cursor with no button held" - ); - assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); -} - -/// `(left, right, bottom, top)` of `frame`, after a resolve. -fn edges(s: &mut UiScript, frame: &str) -> (f32, f32, f32, f32) { - s.resolve(); - let (l, r, b, t): (f64, f64, f64, f64) = s - .eval(&format!( - "return {frame}:GetLeft(), {frame}:GetRight(), {frame}:GetBottom(), {frame}:GetTop()" - )) - .unwrap(); - (l as f32, r as f32, b as f32, t as f32) -} - -/// Unlocking a docked window re-anchors it at CENTER (`FloatingChatFrame.lua:785-792`); a grip -/// then moves only the grabbed edge, since the resize starts by pinning the window to one TOPLEFT -/// anchor at its rect (`0x7652b0` → `0x768430`). -#[test] -fn an_unlocked_centred_window_resizes_from_the_grabbed_edge_only() { - let _data = benilla_formats::wow_data_or_skip!(); - let mut s = chat_ui(); - reveal(&mut s); - right_click(&mut s, "ChatFrame2Tab"); - assert!(s - .eval::("return DropDownList1Button1:GetText() == UNLOCK_WINDOW") - .unwrap()); - left_click(&mut s, "DropDownList1Button1"); - assert_eq!( - s.eval::<(String, i64)>("return (ChatFrame2:GetPoint(1)), ChatFrame2:GetNumPoints()") - .unwrap(), - ("CENTER".to_string(), 1), - "FCF_ToggleLock undocked it and centred it" - ); - - for (grip, dx, dy) in [("Right", 40.0, 0.0), ("Top", 0.0, 30.0)] { - reveal_window(&mut s, "ChatFrame2"); - let before = edges(&mut s, "ChatFrame2"); - let name = format!("ChatFrame2Resize{grip}"); - let (x, y) = centre(&mut s, &name); - assert_eq!(s.hit_test_name(x, y).as_deref(), Some(name.as_str())); - s.mouse_button(x, y, "LeftButton", true); - s.mouse_move(x + dx, y + dy); - s.mouse_button(x + dx, y + dy, "LeftButton", false); - let after = edges(&mut s, "ChatFrame2"); - assert_eq!( - after, - (before.0, before.1 + dx, before.2, before.3 + dy), - "the {grip} grip moved its own edge and no other" - ); - } - assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); -} - -/// A tab dropped off the bottom of the screen: `FCF_ValidateChatFramePosition`'s -/// `SetPoint("TOPLEFT", …)` (`FloatingChatFrame.lua:1304-1334`) replaces the drag's one TOPLEFT -/// anchor, so the window comes back whole at its own size. -#[test] -fn a_tab_dropped_off_screen_puts_the_window_back_whole() { - let _data = benilla_formats::wow_data_or_skip!(); - let mut s = chat_ui(); - s.run("FCF_SetLocked(ChatFrame1, nil)").unwrap(); - let (w, h) = (width(&mut s), height(&mut s)); - drag_tab(&mut s, 0.0, -600.0); - let (n, point, rel, rp) = s - .eval::<(i64, String, String, String)>( - "local p, rel, rp = ChatFrame1:GetPoint(1) \ - return ChatFrame1:GetNumPoints(), p, rel:GetName(), rp", - ) - .unwrap(); - assert_eq!( - (n, point.as_str(), rel.as_str(), rp.as_str()), - (1, "TOPLEFT", "UIParent", "TOPLEFT"), - "the validate's anchor is the only one" - ); - assert_eq!((width(&mut s), height(&mut s)), (w, h), "at its own size"); - let want_top: f64 = s - .eval("return MainMenuBar:GetHeight() + ChatFrame1Tab:GetHeight()") - .unwrap(); - let top: f64 = s.eval("return ChatFrame1:GetTop()").unwrap(); - assert!( - (top - want_top).abs() < 1e-3, - "back on screen: {top} vs {want_top}" - ); - assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); -} diff --git a/crates/benilla-app/src/ui_script/chat_tests.rs b/crates/benilla-app/src/ui_script/chat_tests.rs index 86d5770c..b16224f3 100644 --- a/crates/benilla-app/src/ui_script/chat_tests.rs +++ b/crates/benilla-app/src/ui_script/chat_tests.rs @@ -6,30 +6,30 @@ use benilla_ui::script::{ExtractedQuad, QuadContent, UiScript}; use super::test_ui::load_ui as load_xml; /// The chat stack, fonts first so `inherits="ChatFontNormal"` resolves. -pub(crate) fn chat_frame() -> UiScript { +fn chat_frame() -> UiScript { let mut s = UiScript::new().unwrap(); - load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - // UIParent's OnUpdate runs `FCF_OnUpdate`, the dock's driver, as in the reference. - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); // `FCF_Tab_OnClick` uses the dropdown kit, whose backdrop reads GameTooltip.lua's colour. load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); - load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit the chat menus build from load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); - load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua"); - load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit the chat menus build from + load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua"); + load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); + // UIParent's OnUpdate runs `FCF_OnUpdate`, the dock's driver, as in the reference. + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); - load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua"); - load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml"); // `FCF_ValidateChatFramePosition` reads `MainMenuBar:GetHeight()`, so the bar loads first. - load_xml(&s, "Interface\\FrameXML\\MainMenuBar.xml"); load_xml(&s, "Interface\\FrameXML\\Cooldown.xml"); load_xml(&s, "Interface\\FrameXML\\ActionButtonTemplate.xml"); + load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua"); + load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml"); + load_xml(&s, "Interface\\FrameXML\\MainMenuBar.xml"); load_xml(&s, "Interface\\FrameXML\\ChatFrame.xml"); + load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua"); + load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml"); load_xml(&s, "Interface\\FrameXML\\FloatingChatFrame.xml"); super::fire_chat_login(&mut s); s.set_screen_size(1600.0, 900.0); @@ -166,37 +166,6 @@ fn input_editbox_enter_drains_the_typed_line() { assert!(s.take_chat_sends().is_empty(), "drained"); } -/// The chat menu's Language list sets `editBox.language` (`ChatFrame.lua:2403-2405`) and -/// `ChatEdit_SendText` passes it on every send (`:1950-1954`), so `/s` and `/y` speak the pick. -#[test] -fn the_edit_box_speaks_the_language_picked_in_the_chat_menu() { - benilla_formats::wow_data_or_skip!(); - let mut s = chat_frame(); - s.set_language_table(vec![(2, "Darnassian".into()), (7, "Common".into())]); - s.set_known_languages(vec!["Darnassian".into(), "Common".into()]); - for line in ["/s before", "/s ishnu", "/y alah"] { - if line == "/s ishnu" { - s.run("ChatFrameEditBox.language = GetLanguageByIndex(1)") - .unwrap(); - } - assert!(s.focus_editbox("ChatFrameEditBox")); - s.char_input(line); - assert!(s.key_input("ENTER")); - } - let sends = s.take_chat_sends(); - assert_eq!( - sends - .iter() - .map(|c| (c.text.as_str(), c.chat_type.as_str(), c.language)) - .collect::>(), - vec![ - ("before", "SAY", None), - ("ishnu", "SAY", Some(2)), - ("alah", "YELL", Some(2)), - ] - ); -} - #[test] fn input_escape_closes_without_submitting() { benilla_formats::wow_data_or_skip!(); @@ -244,11 +213,7 @@ fn chat_box_arrows_edit_and_history_recalls() { ); assert!(s.focus_editbox("ChatFrameEditBox")); s.char_input("dra"); - s.editbox_action(EditAction::Move { - unit: EditUnit::Row, - back: true, - extend: false, - }); + s.editbox_action(EditAction::HistoryPrev); // `ChatEdit_AddHistory` filed "/y hi" (`ChatFrame.lua:1916-1937`); the recall's `OnTextSet` // parse (l.2077-2079) turns it back into YELL with "hi". assert!( @@ -262,11 +227,7 @@ fn chat_box_arrows_edit_and_history_recalls() { s.eval::("return ChatFrameEditBox:GetText()") .unwrap() ); - s.editbox_action(EditAction::Move { - unit: EditUnit::Row, - back: false, - extend: false, - }); + s.editbox_action(EditAction::HistoryNext); assert_eq!( s.eval::("return ChatFrameEditBox:GetText()") .unwrap(), @@ -284,15 +245,14 @@ fn chat_click_dismisses_a_stuck_spell_but_not_an_item() { use benilla_ui::script::{ ContainerSlot, ContainerState, SpellBookState, SpellSlotView, SpellTabView, }; - let mut s = super::spellbook_tests::spellbook_ui_with( - 1024.0, - 768.0, - &[ - "Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from - "Interface\\FrameXML\\ChatFrame.xml", - "Interface\\FrameXML\\FloatingChatFrame.xml", - ], - ); + let mut s = super::spellbook_tests::spellbook_ui(1024.0, 768.0); + for f in [ + "Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from + "Interface\\FrameXML\\ChatFrame.xml", + "Interface\\FrameXML\\FloatingChatFrame.xml", + ] { + load_xml(&s, f); + } s.fire_event("PLAYER_ENTERING_WORLD", vec![]); s.set_spellbook(SpellBookState { tabs: vec![SpellTabView { @@ -315,7 +275,7 @@ fn chat_click_dismisses_a_stuck_spell_but_not_an_item() { s.run("ToggleSpellBook(BOOKTYPE_SPELL)").unwrap(); s.resolve(); - // Drag Fireball off its book button (press → past the drag-start distance → the payload is up). + // Drag Fireball off its book button (press → past the 4px threshold → the payload is up). let (l, r, t, b) = ( s.eval::("return SpellButton1:GetLeft()").unwrap(), s.eval::("return SpellButton1:GetRight()").unwrap(), @@ -352,6 +312,7 @@ fn chat_click_dismisses_a_stuck_spell_but_not_an_item() { slots.insert( 1, ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -360,7 +321,6 @@ fn chat_click_dismisses_a_stuck_spell_but_not_an_item() { texture: Some("Interface\\Icons\\INV_Misc_Food_16".into()), count: 5, quality: Some(1), - inventory_type: 0, item_id: 117, link: None, locked: false, @@ -926,20 +886,25 @@ fn the_chat_menu_builds_its_rows_on_the_references_kit() { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); for f in [ - "Interface\\FrameXML\\GlobalStrings.lua", "Interface\\FrameXML\\Fonts.xml", - "Interface\\FrameXML\\BasicControls.xml", - "Interface\\FrameXML\\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", r"Interface\FrameXML\MoneyFrame.lua", r"Interface\FrameXML\MoneyFrame.xml", - "Interface\\FrameXML\\GameTooltip.xml", - r"Interface\FrameXML\UIMenu.xml", - "Interface\\FrameXML\\UIDropDownMenu.xml", + r"Interface\FrameXML\UIParent.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", "Interface\\FrameXML\\StaticPopup.xml", + "Interface\\FrameXML\\GameTooltip.xml", + r"Interface\FrameXML\UIMenu.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\UIMenu.xml", "Interface\\FrameXML\\ChatFrame.xml", + "Interface\\FrameXML\\UIDropDownMenu.xml", + "Interface\\FrameXML\\UIPanelTemplates.lua", + "Interface\\FrameXML\\UIPanelTemplates.xml", "Interface\\FrameXML\\FloatingChatFrame.xml", ] { load_xml(&s, f); @@ -1116,7 +1081,7 @@ fn a_glass_windows_plate_fades_in_on_every_hover_under_the_full_manifest() { /// Leaving and re-entering within `CHAT_FRAME_FADE_TIME` traps the stock Lua (the tab fade's /// finished callback nils `oldAlpha` under a live hover): the bare chat stack must trap, and the -/// shipped interface, with the layer's `FrameXMLFixes.xml`, must not. +/// shipped manifest, with `install_chat_plate_guard`, must not. #[test] fn a_quick_exit_and_reentry_keeps_the_plates_hover_fade() { benilla_formats::wow_data_or_skip!(); @@ -1183,7 +1148,7 @@ fn a_quick_exit_and_reentry_keeps_the_plates_hover_fade() { "the trapped state: tab up, oldAlpha nil, hover stuck" ); - // The shipped interface, with the guard the layer installs. + // The shipped interface, with the guard the manifest load installs. let mut shipped = UiScript::new().unwrap(); shipped.set_screen_size(1600.0, 900.0); super::test_ui::load_ui(&shipped, "Interface\\FrameXML\\GlobalStrings.lua"); @@ -1260,85 +1225,3 @@ fn the_stock_afk_and_dnd_commands_reach_the_wire() { "a real ChatTypeInfo key that is nonetheless not sendable stays None" ); } - -/// A session's dock on the whole shipped UI: the login's events, the Combat Log selected and -/// deselected, and the dock moved by the bars `UIParent_ManageFramePositions` places, with -/// `FCF_OnUpdate` running every frame. The docked tabs sit on one row on ChatFrame1's background -/// throughout. The hidden Combat Log's layout waits while it is hidden, and the dock's own reads -/// (`MouseIsOver`'s getters in `FCF_OnUpdate`) resolve it each frame (`0x7a1980`), so its tab -/// follows the dock. -#[test] -fn the_docked_tabs_follow_the_dock_through_a_session() { - benilla_formats::wow_data_or_skip!(); - let mut s = UiScript::new().unwrap(); - s.set_screen_size(768.0 * 16.0 / 9.0, 768.0); - s.set_texture_size_probe(Box::new(|_| Some((32, 32)))); - s.set_text_measurer(Box::new(super::FixedWidthFont(6.0))); - let failures = super::load_default_ui(&s); - assert!(failures.is_empty(), "{failures:?}"); - super::lifecycle::finish_ui_load_with(&mut s, |_| {}, super::fire_chat_login); - s.fire_world_enter(); - let frames = |s: &mut UiScript, n: usize| { - for _ in 0..n { - s.tick(0.016); - s.resolve(); - } - }; - let check = |s: &mut UiScript, step: &str| { - // Hover the dock, so the tabs show (`FCF_OnUpdate`). - let (x, y): (f32, f32) = s - .eval( - "return (ChatFrame1:GetLeft() + ChatFrame1:GetRight()) / 2, \ - (ChatFrame1:GetBottom() + ChatFrame1:GetTop()) / 2", - ) - .unwrap(); - s.mouse_move(x, y); - frames(s, 45); - let row: f32 = s.eval("return ChatFrame1Background:GetTop()").unwrap(); - let quads = s.extract(); - for tab in ["ChatFrame1Tab", "ChatFrame2Tab"] { - let q = quads - .iter() - .find(|q| { - matches!(q.content, QuadContent::Frame) - && s.target_owner_name(q.target).as_deref() == Some(tab) - }) - .unwrap_or_else(|| panic!("{step}: {tab} draws")); - let bottom = q.rect.expect("a placed tab").bottom; - assert!( - (bottom - row).abs() < 0.01, - "{step}: {tab} sits at {bottom}, the dock's row is {row}" - ); - } - }; - let click = |s: &mut UiScript, tab: &str| { - s.run(&format!( - "this = {tab} FCF_Tab_OnClick('LeftButton') this = nil" - )) - .unwrap(); - frames(s, 5); - }; - frames(&mut s, 30); - check(&mut s, "login"); - click(&mut s, "ChatFrame2Tab"); - check(&mut s, "Combat Log selected"); - click(&mut s, "ChatFrame1Tab"); - check(&mut s, "General selected"); - s.run("SHOW_MULTI_ACTIONBAR_1 = 1 MultiActionBar_Update() UIParent_ManageFramePositions()") - .unwrap(); - frames(&mut s, 5); - check(&mut s, "bottom-left bar"); - s.run("PetActionBarFrame:Show() UIParent_ManageFramePositions()") - .unwrap(); - frames(&mut s, 5); - check(&mut s, "pet bar"); - click(&mut s, "ChatFrame2Tab"); - check(&mut s, "Combat Log selected over the pet bar"); - s.run("PetActionBarFrame:Hide() UIParent_ManageFramePositions()") - .unwrap(); - frames(&mut s, 5); - check(&mut s, "pet bar gone"); - click(&mut s, "ChatFrame1Tab"); - check(&mut s, "General selected again"); - assert!(s.errors().is_empty(), "{:?}", s.errors()); -} diff --git a/crates/benilla-app/src/ui_script/cinematic_tests.rs b/crates/benilla-app/src/ui_script/cinematic_tests.rs index 72a752f2..c5f03d0e 100644 --- a/crates/benilla-app/src/ui_script/cinematic_tests.rs +++ b/crates/benilla-app/src/ui_script/cinematic_tests.rs @@ -10,17 +10,15 @@ use super::test_ui::load_ui as load_xml; fn ui_with_the_cinematic_frame() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); - // `TakeScreenshot` (`WorldFrame.lua:46`), which the SCREENSHOT binding's body calls. - load_xml(&s, r"Interface\FrameXML\WorldFrame.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); // StaticPopup's money row load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); load_xml(&s, "Interface\\FrameXML\\CinematicFrame.xml"); s.resolve(); @@ -97,8 +95,8 @@ fn the_host_has_a_reference_name_for_the_keys_it_now_delivers() { (KeyCode::F1, "F1"), ] { assert_eq!( - key_token(code, &Default::default()).map(|n| n.to_string()), - Some(name.to_string()), + key_token(code), + Some(name), "{code:?} must reach a keyboard frame under the reference's own name" ); } @@ -109,7 +107,9 @@ fn the_screenshot_key_is_handed_back_to_its_binding() { benilla_formats::wow_data_or_skip!(); let mut s = ui_with_the_cinematic_frame(); // `GetBindingKey("SCREENSHOT")` must answer, or the handler's screenshot arm is skipped. - crate::ui_script::load_stock_bindings(&mut s); + s.register_bindings(&crate::bindings::registry_commands()); + s.seed_binding_set(1, None); + s.load_binding_set(1); let key = s .keybind_snapshot() .into_iter() @@ -118,22 +118,23 @@ fn the_screenshot_key_is_handed_back_to_its_binding() { .expect("SCREENSHOT ships a default chord"); start_cinematic(&mut s); - let _ = s.take_screenshot_asks(); + let _ = s.take_keybind_requests(); assert!( s.frame_key_input(&key), "the frame consumes {key} like any other key" ); assert_eq!( - s.take_screenshot_asks(), - 1, - "the screenshot key must reach its binding's body through RunBinding" + s.take_keybind_requests(), + vec![benilla_ui::script::keybind::KeybindRequest::Run( + "SCREENSHOT".into() + )], + "the screenshot key must reach its binding through RunBinding" ); - // ESCAPE still skips, and asks for no screenshot. + // ESCAPE still skips, and queues nothing on the binding channel. assert!(s.frame_key_input("ESCAPE")); - assert_eq!(s.take_screenshot_asks(), 0); - assert!(s.errors().is_empty(), "{:?}", s.errors()); + assert!(s.take_keybind_requests().is_empty()); } /// `ShowUIPanel`'s `area = "full"` row (`UIParent.lua:31`) routes to `SetFullScreenFrame`, which diff --git a/crates/benilla-app/src/ui_script/color_picker_tests.rs b/crates/benilla-app/src/ui_script/color_picker_tests.rs index ae550e5e..99198aa0 100644 --- a/crates/benilla-app/src/ui_script/color_picker_tests.rs +++ b/crates/benilla-app/src/ui_script/color_picker_tests.rs @@ -5,24 +5,24 @@ use benilla_ui::script::{QuadContent, UiScript}; use super::test_ui::load_ui as load_xml; -/// The files the picker needs, in the production order, ending with the window. +/// The files the picker needs, in manifest order, ending with the window. fn picker() -> UiScript { let mut s = UiScript::new().unwrap(); for file in [ - "Interface\\FrameXML\\GlobalStrings.lua", "Interface\\FrameXML\\Fonts.xml", - "Interface\\FrameXML\\BasicControls.xml", - "Interface\\FrameXML\\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", r"Interface\FrameXML\MoneyFrame.lua", r"Interface\FrameXML\MoneyFrame.xml", + r"Interface\FrameXML\UIParent.xml", "Interface\\FrameXML\\GameTooltip.xml", "Interface\\FrameXML\\UIDropDownMenu.xml", + "ScrollTemplates.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", "Interface\\FrameXML\\StaticPopup.xml", "Interface\\FrameXML\\ColorPickerFrame.xml", - "ScrollTemplates.xml", ] { load_xml(&s, file); } diff --git a/crates/benilla-app/src/ui_script/combat_text_tests.rs b/crates/benilla-app/src/ui_script/combat_text_tests.rs index 170346da..c74d8a63 100644 --- a/crates/benilla-app/src/ui_script/combat_text_tests.rs +++ b/crates/benilla-app/src/ui_script/combat_text_tests.rs @@ -10,8 +10,8 @@ fn load_combat_text() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua"); // ENTERING_COMBAT & co. - load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); // TEXT() + load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); load_xml(&s, r"Interface\FrameXML\UIParent.xml"); super::test_ui::seat_chain_addon(&mut s, "Blizzard_CombatText"); // The options window's defaults block (`UIOptionsFrame.lua:125`), seated by hand, master on. diff --git a/crates/benilla-app/src/ui_script/combo_frame_tests.rs b/crates/benilla-app/src/ui_script/combo_frame_tests.rs index 670db95d..20332f3d 100644 --- a/crates/benilla-app/src/ui_script/combo_frame_tests.rs +++ b/crates/benilla-app/src/ui_script/combo_frame_tests.rs @@ -13,24 +13,26 @@ use super::test_ui::load_ui as load_xml; fn load_combo_frame() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); + load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); + load_xml(&s, "Interface\\FrameXML\\UnitPopup.xml"); + load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); + load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); load_xml(&s, "Interface\\FrameXML\\TextStatusBar.lua"); load_xml(&s, "Interface\\FrameXML\\TextStatusBar.xml"); load_xml(&s, "Interface\\FrameXML\\BuffFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\CombatFeedback.xml"); - load_xml(&s, "Interface\\FrameXML\\UnitPopup.xml"); load_xml(&s, "Interface\\FrameXML\\UnitFrame.xml"); + load_xml(&s, "Interface\\FrameXML\\CombatFeedback.xml"); load_xml(&s, "Interface\\FrameXML\\PlayerFrame.xml"); load_xml(&s, "Interface\\FrameXML\\PartyFrame.xml"); load_xml(&s, "Interface\\FrameXML\\TargetFrame.xml"); diff --git a/crates/benilla-app/src/ui_script/content.rs b/crates/benilla-app/src/ui_script/content.rs index 521e1986..c106c16b 100644 --- a/crates/benilla-app/src/ui_script/content.rs +++ b/crates/benilla-app/src/ui_script/content.rs @@ -2,7 +2,7 @@ //! missing; `load_ui_files` and its `` provider read them here, never through `std::fs`. //! A dev build reads the source tree first, so an edit needs no recompile. -/// The shipped UI tree: `layer.toc` and every file of ours it names. +/// The shipped UI tree: `benilla.toc` and every file it names. static UI: include_dir::Dir<'_> = include_dir::include_dir!("$CARGO_MANIFEST_DIR/assets/ui"); /// The text of one shipped UI file, by path relative to `assets/ui`; the basename fallback for a @@ -20,10 +20,8 @@ fn read_source_tree(req: &str) -> Option { std::fs::read_to_string(dir.join(req)).ok() } -/// FNV-1a (not cryptographic) over the core's load list, then the layer's manifest and each file -/// it names when this run loads it, in load order, as eight hex digits: a stamp that tells two runs -/// whether they loaded the same interface. The core counts by its rows' names alone, in the toc's -/// order: their bytes are the player's install. +/// FNV-1a (not cryptographic) over the manifest and each file it names, in load order, as eight +/// hex digits: a stamp that tells two runs whether they loaded the same interface. pub(crate) fn digest() -> String { let mut h: u64 = 0xcbf2_9ce4_8422_2325; let mut eat = |bytes: &[u8]| { @@ -32,24 +30,15 @@ pub(crate) fn digest() -> String { h = h.wrapping_mul(0x1000_0000_01b3); } }; - eat(super::reference_ui::TOC.as_bytes()); - for name in super::reference_ui::core() - .map(|core| core.toc.files) - .unwrap_or_default() - { - eat(name.as_bytes()); + eat(super::manifest::MANIFEST.as_bytes()); + if let Some(toc) = read(super::manifest::MANIFEST) { + eat(toc.as_bytes()); } - if super::manifest::layer_enabled() { - let manifest = super::manifest::LAYER_MANIFEST; - eat(manifest.as_bytes()); - if let Some(toc) = read(manifest) { - eat(toc.as_bytes()); - } - for name in super::addons::Addon::layer().toc.files { - eat(name.as_bytes()); - if let Some(text) = read(&name) { - eat(text.as_bytes()); - } + for name in super::addons::Addon::builtin().toc.files { + // Only a chain entry's name counts: its bytes are the player's install. + eat(name.as_bytes()); + if let Some(text) = read(&name) { + eat(text.as_bytes()); } } format!("{:08x}", h as u32) @@ -68,7 +57,7 @@ mod tests { for name in crate::ui_script::manifest::shipped_manifest_files() { assert!( super::read(&name).is_some(), - "a manifest names {name}, which is not in assets/ui" + "benilla.toc names {name}, which is not in assets/ui" ); } } diff --git a/crates/benilla-app/src/ui_script/death_tests.rs b/crates/benilla-app/src/ui_script/death_tests.rs index 0cc0ee71..9847b163 100644 --- a/crates/benilla-app/src/ui_script/death_tests.rs +++ b/crates/benilla-app/src/ui_script/death_tests.rs @@ -8,15 +8,15 @@ use super::test_ui::load_ui as load_xml; fn setup() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); s } diff --git a/crates/benilla-app/src/ui_script/delete_item_tests.rs b/crates/benilla-app/src/ui_script/delete_item_tests.rs index 98849e45..abb92336 100644 --- a/crates/benilla-app/src/ui_script/delete_item_tests.rs +++ b/crates/benilla-app/src/ui_script/delete_item_tests.rs @@ -5,7 +5,7 @@ use benilla_ui::script::{ContainerSlot, ContainerState, UiScript}; use super::test_ui::{ - bag_open, bag_slot_button, load_ui as load_xml, world_click, BAG_UI, WORLD_FRAME_UI, + bag_open, bag_slot_button, load_ui as load_xml, load_world_frame, world_click, BAG_UI, }; /// A backpack holding a 5-stack of `item_id` at `quality`, the parameter the popup forks on. @@ -14,6 +14,7 @@ fn one_item_backpack(item_id: u32, name: &str, quality: u32) -> ContainerState { slots.insert( 1, ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -22,7 +23,6 @@ fn one_item_backpack(item_id: u32, name: &str, quality: u32) -> ContainerState { texture: Some("Interface\\Icons\\INV_Misc_Food_16".into()), count: 5, quality: Some(quality), - inventory_type: 0, item_id, link: Some(format!("|cffffffff|Hitem:{item_id}|h[{name}]|h|r")), locked: false, @@ -45,31 +45,25 @@ fn one_item_backpack(item_id: u32, name: &str, quality: u32) -> ContainerState { fn setup() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); + load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); + load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); + load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); + load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); + load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + // The stock `DELETE_GOOD_ITEM`'s `OnHide` hands focus back to `ChatFrameEditBox`. + load_xml(&s, r"Interface\FrameXML\UIMenu.xml"); + load_xml(&s, r"Interface\FrameXML\ChatFrame.xml"); + load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); // TOOLTIP_DEFAULT_COLOR, for dropdowns + load_xml(&s, r"Interface\FrameXML\UIDropDownMenu.xml"); + load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); + load_xml(&s, r"Interface\FrameXML\FloatingChatFrame.xml"); // declares ChatFrameEditBox + // The drop is a click on the full-screen world frame, so it must be loaded. - for file in super::test_ui::production_order(&[ - &[ - r"Interface\FrameXML\GlobalStrings.lua", - "Interface\\FrameXML\\Fonts.xml", - r"Interface\FrameXML\BasicControls.xml", - r"Interface\FrameXML\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", - r"Interface\FrameXML\MoneyFrame.lua", - r"Interface\FrameXML\MoneyFrame.xml", - "Interface\\FrameXML\\GameTooltip.xml", // TOOLTIP_DEFAULT_COLOR, for dropdowns - r"Interface\FrameXML\UIMenu.xml", - r"Interface\FrameXML\UIDropDownMenu.xml", - r"Interface\FrameXML\UIPanelTemplates.lua", - r"Interface\FrameXML\UIPanelTemplates.xml", - r"Interface\FrameXML\StaticPopup.xml", - // The stock `DELETE_GOOD_ITEM`'s `OnHide` hands focus back to `ChatFrameEditBox`, - // which FloatingChatFrame.xml declares. - r"Interface\FrameXML\ChatFrame.xml", - r"Interface\FrameXML\FloatingChatFrame.xml", - ], - WORLD_FRAME_UI, - ]) { - load_xml(&s, file); - } + load_world_frame(&s); s.set_money(0); s } @@ -78,18 +72,14 @@ fn setup() -> UiScript { fn bag_setup() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - // `ContainerFrame_Update` reads `MerchantFrame:IsShown()` on any slot the tooltip owns. - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kits - ], - WORLD_FRAME_UI, // the drop target - ]) { + for file in BAG_UI { load_xml(&s, file); } + // `ContainerFrame_Update` reads `MerchantFrame:IsShown()` on any slot the tooltip owns. + load_xml(&s, "ScrollTemplates.xml"); // our scroll kits + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); + load_world_frame(&s); // the drop target // `BAG_UI` leaves out `CharacterFrame.xml`, so `PaperDollFrame` has no hidden parent and would // cover the world; the real client never shows it here. diff --git a/crates/benilla-app/src/ui_script/dressup_tests.rs b/crates/benilla-app/src/ui_script/dressup_tests.rs index e391591d..d715b811 100644 --- a/crates/benilla-app/src/ui_script/dressup_tests.rs +++ b/crates/benilla-app/src/ui_script/dressup_tests.rs @@ -12,47 +12,49 @@ use super::test_ui::{bag_slot_button, click, load_ui as load_xml, BAG_UI, CHARAC /// A real 1.12 item link: Tough Jerky (117), quality white. const JERKY_LINK: &str = "|cffffffff|Hitem:117|h[Tough Jerky]|h|r"; -/// The room's own files, in the production order; both loaders end with these. +/// The room's own files, in manifest order; both loaders end with these. const ROOM_UI: &[&str] = &[ + "Interface\\FrameXML\\UIDropDownMenu.xml", "Interface\\FrameXML\\GlobalStrings.lua", "Interface\\FrameXML\\BasicControls.xml", // `TEXT`, which UnitPopup.lua reads at file scope - "Interface\\FrameXML\\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", + "Interface\\FrameXML\\UnitPopup.xml", + "Interface\\FrameXML\\ItemRef.xml", + "ScrollTemplates.xml", // our scroll kits + "Interface\\FrameXML\\CharacterFrameTemplates.xml", + "Interface\\FrameXML\\MerchantFrame.xml", + "Interface\\FrameXML\\StackSplitFrame.xml", "Interface\\FrameXML\\UIMenu.xml", // the kit ChatMenu/EmoteMenu/VoiceMacroMenu build from - "Interface\\FrameXML\\UIDropDownMenu.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\ChatFrame.xml", "Interface\\FrameXML\\UIPanelTemplates.lua", "Interface\\FrameXML\\UIPanelTemplates.xml", - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\StackSplitFrame.xml", - "Interface\\FrameXML\\UnitPopup.xml", - "Interface\\FrameXML\\ChatFrame.xml", - "Interface\\FrameXML\\FloatingChatFrame.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "Interface\\FrameXML\\ItemRef.xml", // After the panel kit: the room's Close and Reset buttons inherit its templates at load. "Interface\\FrameXML\\DressUpFrame.xml", - "ScrollTemplates.xml", // our scroll kits + r"Interface\FrameXML\UIParent.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\FloatingChatFrame.xml", ]; /// The room with no bag window, for the click sites that never touch a container. fn load_room(s: &UiScript) { - for file in crate::ui_script::test_ui::production_order(&[ - &[ - "Interface\\FrameXML\\GlobalStrings.lua", - "Interface\\FrameXML\\Fonts.xml", - "Interface\\FrameXML\\BasicControls.xml", - "Interface\\FrameXML\\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", - r"Interface\FrameXML\MoneyFrame.lua", - r"Interface\FrameXML\MoneyFrame.xml", - "Interface\\FrameXML\\GameTooltip.xml", - r"Interface\FrameXML\UIPanelTemplates.lua", - r"Interface\FrameXML\UIPanelTemplates.xml", - "Interface\\FrameXML\\StaticPopup.xml", - "Interface\\FrameXML\\Cooldown.xml", - ], - ROOM_UI, - ]) { + for file in [ + "Interface\\FrameXML\\Fonts.xml", + r"Interface\FrameXML\MoneyFrame.lua", + r"Interface\FrameXML\MoneyFrame.xml", + r"Interface\FrameXML\UIParent.xml", + r"Interface\FrameXML\UIPanelTemplates.lua", + r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\StaticPopup.xml", + "Interface\\FrameXML\\GameTooltip.xml", + "Interface\\FrameXML\\Cooldown.xml", + ] { + load_xml(s, file); + } + for file in ROOM_UI { load_xml(s, file); } } @@ -60,11 +62,11 @@ fn load_room(s: &UiScript) { /// The room over the stock bag windows; `UIParent.xml` leads because every window parents to it /// and `BAG_UI` has no line for it. fn load_room_with_bags(s: &UiScript) { - for file in crate::ui_script::test_ui::production_order(&[ - &[r"Interface\FrameXML\UIParent.xml"], - BAG_UI, - ROOM_UI, - ]) { + load_xml(s, r"Interface\FrameXML\UIParent.xml"); + for file in BAG_UI { + load_xml(s, file); + } + for file in ROOM_UI { load_xml(s, file); } } @@ -75,23 +77,23 @@ fn load_room_with_bags(s: &UiScript) { fn shown_paper_doll() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - CHARACTER_UI, - &[ - "Interface\\FrameXML\\GlobalStrings.lua", - "Interface\\FrameXML\\BasicControls.xml", - "Interface\\FrameXML\\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", - "Interface\\FrameXML\\UIMenu.xml", - "Interface\\FrameXML\\UIDropDownMenu.xml", - "Interface\\FrameXML\\UIPanelTemplates.lua", - "Interface\\FrameXML\\UIPanelTemplates.xml", - "Interface\\FrameXML\\ChatFrame.xml", - "Interface\\FrameXML\\FloatingChatFrame.xml", - // After the panel kit its buttons inherit from. - "Interface\\FrameXML\\DressUpFrame.xml", - ], - ]) { + for file in CHARACTER_UI { + super::test_ui::load_ui_strict(&s, file); + } + for file in [ + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\UIMenu.xml", + "Interface\\FrameXML\\ChatFrame.xml", + "Interface\\FrameXML\\UIDropDownMenu.xml", + "Interface\\FrameXML\\UIPanelTemplates.lua", + "Interface\\FrameXML\\UIPanelTemplates.xml", + // After the panel kit its buttons inherit from. + "Interface\\FrameXML\\DressUpFrame.xml", + r"Interface\FrameXML\UIParent.xml", + "Interface\\FrameXML\\LocaleProperties.lua", + "Interface\\FrameXML\\FloatingChatFrame.xml", + ] { super::test_ui::load_ui_strict(&s, file); } s.set_unit( @@ -161,7 +163,7 @@ fn ctrl_click_on_a_bag_item_opens_the_room_wearing_it() { "ctrl-click opened the dressing room" ); assert_eq!( - s.take_dressup_intents(), + s.take_dressup_intent_kinds(), vec![DressUpIntent::Dress, DressUpIntent::TryOn(117)], "re-dress first, then try the clicked item on" ); @@ -229,7 +231,7 @@ fn ctrl_clicking_a_chat_link_previews_it() { s.set_modifiers(false, false, false); assert_eq!( - s.take_dressup_intents(), + s.take_dressup_intent_kinds(), vec![DressUpIntent::Dress, DressUpIntent::TryOn(117)], "a chat link's id reached TryOn through DressUpItemLink's own gsub" ); @@ -264,14 +266,14 @@ fn the_paper_doll_slots_preview_and_post_what_you_wear() { "|cff1eff00|Hitem:1234:0:0:0|h[Test Helm]|h|r" ); s.run(r#"ToggleCharacter("PaperDollFrame")"#).unwrap(); - let _ = s.take_dressup_intents(); + let _ = s.take_dressup_intent_kinds(); s.resolve(); s.set_modifiers(false, true, false); click(&mut s, "CharacterHeadSlot", "LeftButton"); s.set_modifiers(false, false, false); assert_eq!( - s.take_dressup_intents(), + s.take_dressup_intent_kinds(), vec![DressUpIntent::Dress, DressUpIntent::TryOn(1234)] ); @@ -307,7 +309,7 @@ fn shift_clicking_an_unresolved_slot_posts_nothing_and_never_raises() { }); s.set_inventory_slots(slots); s.run(r#"ToggleCharacter("PaperDollFrame")"#).unwrap(); - let _ = s.take_dressup_intents(); + let _ = s.take_dressup_intent_kinds(); s.resolve(); assert!(s.focus_editbox("ChatFrameEditBox")); @@ -327,7 +329,7 @@ fn shift_clicking_an_unresolved_slot_posts_nothing_and_never_raises() { click(&mut s, "CharacterHeadSlot", "LeftButton"); s.set_modifiers(false, false, false); assert!( - s.take_dressup_intents().is_empty(), + s.take_dressup_intent_kinds().is_empty(), "no try-on for an item we cannot even name yet" ); assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); @@ -343,7 +345,7 @@ fn reset_re_dresses_close_empties_and_the_arrows_spin_the_pane() { load_room(&s); s.run("ShowUIPanel(DressUpFrame)").unwrap(); let _ = s.take_sounds(); - let _ = s.take_dressup_intents(); + let _ = s.take_dressup_intent_kinds(); s.resolve(); // `Model_OnLoad` seeds the default facing. @@ -353,7 +355,7 @@ fn reset_re_dresses_close_empties_and_the_arrows_spin_the_pane() { ); s.run("DressUpFrameResetButton:Click()").unwrap(); - assert_eq!(s.take_dressup_intents(), vec![DressUpIntent::Dress]); + assert_eq!(s.take_dressup_intent_kinds(), vec![DressUpIntent::Dress]); assert_eq!( s.take_sounds(), vec![SoundRequest::KitName("gsTitleOptionOK".into())], @@ -381,6 +383,6 @@ fn reset_re_dresses_close_empties_and_the_arrows_spin_the_pane() { s.run("HideUIPanel(DressUpFrame)").unwrap(); // The app empties the booth off the frame's visibility; the stock `OnHide` queues nothing. assert!(!s.frame_visible("DressUpFrame"), "the room is hidden"); - assert!(s.take_dressup_intents().is_empty()); + assert!(s.take_dressup_intent_kinds().is_empty()); assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); } diff --git a/crates/benilla-app/src/ui_script/duel_tests.rs b/crates/benilla-app/src/ui_script/duel_tests.rs index 0fbcf455..6326d799 100644 --- a/crates/benilla-app/src/ui_script/duel_tests.rs +++ b/crates/benilla-app/src/ui_script/duel_tests.rs @@ -7,15 +7,15 @@ use super::test_ui::load_ui as load_xml; fn setup() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); s } diff --git a/crates/benilla-app/src/ui_script/durability_tests.rs b/crates/benilla-app/src/ui_script/durability_tests.rs index 3d9b166d..ddc1e950 100644 --- a/crates/benilla-app/src/ui_script/durability_tests.rs +++ b/crates/benilla-app/src/ui_script/durability_tests.rs @@ -9,18 +9,18 @@ fn harness() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); for f in [ - "Interface\\FrameXML\\GlobalStrings.lua", "Interface\\FrameXML\\Fonts.xml", - "Interface\\FrameXML\\BasicControls.xml", - "Interface\\FrameXML\\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", r"Interface\FrameXML\MoneyFrame.lua", r"Interface\FrameXML\MoneyFrame.xml", - "Interface\\FrameXML\\GameTooltip.xml", - "Interface\\FrameXML\\UIDropDownMenu.xml", + r"Interface\FrameXML\UIParent.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", "Interface\\FrameXML\\StaticPopup.xml", + "Interface\\FrameXML\\GameTooltip.xml", + "Interface\\FrameXML\\UIDropDownMenu.xml", "Interface\\FrameXML\\BattlefieldFrame.xml", "Interface\\FrameXML\\Minimap.xml", "Interface\\FrameXML\\DurabilityFrame.xml", @@ -28,9 +28,9 @@ fn harness() -> UiScript { load_xml(&s, f); } // Deviation: the seat refreshes on every alert recompute, because the stock one goes stale - // when a side glyph appears while shown and hangs the shield off the screen edge. The layer - // installs this after every stock file. - load_xml(&s, "FrameXMLFixes.xml"); + // when a side glyph appears while shown and hangs the shield off the screen edge. The app + // installs this after every load. + super::manifest::install_durability_reseat(&s).unwrap(); s } @@ -350,30 +350,31 @@ fn the_quest_tracker_stacks_below_the_durability_guy() { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); for f in [ - "Interface\\FrameXML\\GlobalStrings.lua", "Interface\\FrameXML\\Fonts.xml", - "Interface\\FrameXML\\BasicControls.xml", - "Interface\\FrameXML\\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", r"Interface\FrameXML\MoneyFrame.lua", r"Interface\FrameXML\MoneyFrame.xml", - "Interface\\FrameXML\\GameTooltip.xml", - "Interface\\FrameXML\\UIDropDownMenu.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + r"Interface\FrameXML\UIParent.xml", r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", + "Interface\\FrameXML\\BasicControls.xml", + "Interface\\FrameXML\\LocaleProperties.lua", "Interface\\FrameXML\\StaticPopup.xml", + "Interface\\FrameXML\\GameTooltip.xml", + "Interface\\FrameXML\\UIDropDownMenu.xml", "Interface\\FrameXML\\BattlefieldFrame.xml", "Interface\\FrameXML\\Minimap.xml", - r"Interface\FrameXML\MainMenuBarMicroButtons.xml", + "ScrollTemplates.xml", + "Interface\\FrameXML\\DurabilityFrame.xml", + "Interface\\FrameXML\\BasicControls.xml", "Interface\\FrameXML\\ItemButtonTemplate.xml", "Interface\\FrameXML\\QuestFrame.xml", + r"Interface\FrameXML\MainMenuBarMicroButtons.xml", "Interface\\FrameXML\\QuestLogFrame.xml", - "Interface\\FrameXML\\DurabilityFrame.xml", - "ScrollTemplates.xml", ] { load_xml(&s, f); } - load_xml(&s, "FrameXMLFixes.xml"); + super::manifest::install_durability_reseat(&s).unwrap(); let mut inv: InventorySlots = Default::default(); inv[18] = slot(2504, Some((0, 20))); diff --git a/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs b/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs index 9bb0c010..338a93d3 100644 --- a/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs +++ b/crates/benilla-app/src/ui_script/enchant_confirm_tests.rs @@ -7,15 +7,15 @@ use super::test_ui::load_ui as load_xml; fn setup() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); s } diff --git a/crates/benilla-app/src/ui_script/errors_tests.rs b/crates/benilla-app/src/ui_script/errors_tests.rs index d946a56c..e8cf5179 100644 --- a/crates/benilla-app/src/ui_script/errors_tests.rs +++ b/crates/benilla-app/src/ui_script/errors_tests.rs @@ -91,28 +91,28 @@ fn an_error_toast_draws_over_an_open_panel_window() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); - // A missing template only warns at load, so an under-loaded list fails at its first update. - load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); + load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); - load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); - load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); - load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml"); + load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml"); load_xml(&s, "Interface\\FrameXML\\Cooldown.xml"); + load_xml(&s, "ScrollTemplates.xml"); // ours: the scroll kits + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); + // A missing template only warns at load, so an under-loaded list fails at its first update. + load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); load_xml(&s, "Interface\\FrameXML\\ItemButtonTemplate.xml"); load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml"); + load_xml(&s, r"Interface\FrameXML\MainMenuBarMicroButtons.xml"); load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); - load_xml(&s, "ScrollTemplates.xml"); // ours: the scroll kits // A left-slot panel opens, then the toast: stratum, not order, must decide. s.eval::<()>("ShowUIPanel(QuestLogFrame)").unwrap(); diff --git a/crates/benilla-app/src/ui_script/escape_tests.rs b/crates/benilla-app/src/ui_script/escape_tests.rs index 978b9601..031ef666 100644 --- a/crates/benilla-app/src/ui_script/escape_tests.rs +++ b/crates/benilla-app/src/ui_script/escape_tests.rs @@ -12,6 +12,7 @@ fn one_item_backpack() -> ContainerState { slots.insert( 1, ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -20,7 +21,6 @@ fn one_item_backpack() -> ContainerState { texture: Some("Interface\\Icons\\INV_Misc_Food_16".into()), count: 1, quality: Some(1), - inventory_type: 0, item_id: 117, link: Some("|cffffffff|Hitem:117|h[Tough Jerky]|h|r".into()), locked: false, @@ -46,27 +46,27 @@ fn escape_closes_bag_and_panel_releases_loot_and_keeps_the_held_item() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - // `LOOT_UI` whole: a missing `ItemButtonTemplate` is only a load warning, so it fails silently. - for file in super::test_ui::production_order(&[ - BAG_UI, - super::test_ui::LOOT_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - // Stock `ContainerFrame.lua` reads `MerchantFrame:IsShown()`. - "Interface\\FrameXML\\MerchantFrame.xml", - "Interface\\FrameXML\\LootFrame.xml", - "ScrollTemplates.xml", // our scroll kits - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + // `LOOT_UI` whole: a missing `ItemButtonTemplate` is only a load warning, so it fails silently. + for file in super::test_ui::LOOT_UI { + if BAG_UI.contains(file) { + continue; // a file loads once; `BAG_UI` carried it + } + load_xml(&s, file); + } + load_xml(&s, "Interface\\FrameXML\\LootFrame.xml"); + load_xml(&s, "ScrollTemplates.xml"); // our scroll kits + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + // Stock `ContainerFrame.lua` reads `MerchantFrame:IsShown()`. + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); s.set_container(0, Some(one_item_backpack())); // Open the bag and the loot window; drain their open sounds. s.run("MainMenuBarBackpackButton:Click()").unwrap(); s.set_loot(Some(LootState { - source_unit: false, fishing: false, master_candidates: Vec::new(), rows: vec![Some(LootRow { @@ -129,26 +129,22 @@ fn escape_is_consumed_by_a_focused_editbox_and_leaves_windows_open() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\GlobalStrings.lua", - "Interface\\FrameXML\\BasicControls.xml", - r"Interface\FrameXML\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", - "Interface\\FrameXML\\UIMenu.xml", // the kit the chat menus build from - "Interface\\FrameXML\\UIDropDownMenu.xml", - "Interface\\FrameXML\\UIPanelTemplates.lua", - "Interface\\FrameXML\\UIPanelTemplates.xml", - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\ChatFrame.xml", - "Interface\\FrameXML\\FloatingChatFrame.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kits - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kits + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); + load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); // the kit the chat menus build from + load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua"); + load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); + load_xml(&s, "Interface\\FrameXML\\ChatFrame.xml"); + load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); + load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua"); + load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, "Interface\\FrameXML\\FloatingChatFrame.xml"); s.set_money(0); s.set_container(0, Some(one_item_backpack())); @@ -174,23 +170,22 @@ fn escape_closes_the_options_window_before_opening_the_menu() { benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); - load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); - load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); - load_xml(&s, r"Interface\FrameXML\GameMenuFrame.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); + load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); + load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); load_xml(&s, "ScrollTemplates.xml"); // the Keybindings page's faux-scroll kit load_xml(&s, "KeyBindingsPage.xml"); load_xml(&s, "OptionsFrame.xml"); - load_xml(&s, "GameMenuAdapters.xml"); + load_xml(&s, "GameMenuFrame.xml"); s.run("ShowUIPanel(BenillaOptionsFrame)").unwrap(); assert!(s @@ -227,32 +222,29 @@ fn escape_closes_an_open_stack_split_frame() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\GlobalStrings.lua", - "Interface\\FrameXML\\BasicControls.xml", - r"Interface\FrameXML\LocaleProperties.lua", - r"Interface\FrameXML\UIParent.xml", - "Interface\\FrameXML\\UIMenu.xml", - "Interface\\FrameXML\\UIPanelTemplates.lua", - "Interface\\FrameXML\\UIPanelTemplates.xml", - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\StackSplitFrame.xml", - "Interface\\FrameXML\\ChatFrame.xml", // ChatFrameEditBox, for the shift fork - "Interface\\FrameXML\\FloatingChatFrame.xml", - "Interface\\FrameXML\\MerchantFrame.xml", // read by the stock bag-slot click - "ScrollTemplates.xml", // our scroll kits - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kits + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); // read by the stock bag-slot click + load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua"); + load_xml(&s, "Interface\\FrameXML\\BasicControls.xml"); + load_xml(&s, "Interface\\FrameXML\\UIMenu.xml"); + load_xml(&s, "Interface\\FrameXML\\ChatFrame.xml"); // ChatFrameEditBox, for the shift fork + load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua"); + load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); + load_xml(&s, "Interface\\FrameXML\\FloatingChatFrame.xml"); + load_xml(&s, "Interface\\FrameXML\\StackSplitFrame.xml"); s.set_money(0); let mut slots = std::collections::HashMap::new(); slots.insert( 1, ContainerSlot { + charges: 0, duration_ms: None, petition: None, already_bound: false, @@ -261,7 +253,6 @@ fn escape_closes_an_open_stack_split_frame() { texture: Some("Interface\\Icons\\INV_Misc_Food_16".into()), count: 5, quality: Some(1), - inventory_type: 0, item_id: 117, link: Some("|cffffffff|Hitem:117|h[Tough Jerky]|h|r".into()), locked: false, @@ -310,19 +301,14 @@ fn escape_ladder_cast_then_windows_then_target_one_eater_per_press() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - // The dropdown backdrop reads `TOOLTIP_DEFAULT_COLOR`, which `BAG_UI`'s tooltip file - // sets. - "Interface\\FrameXML\\UIDropDownMenu.xml", - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kits - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + // The dropdown backdrop reads `TOOLTIP_DEFAULT_COLOR`, which `BAG_UI`'s tooltip file sets. + load_xml(&s, "Interface\\FrameXML\\UIDropDownMenu.xml"); + load_xml(&s, "ScrollTemplates.xml"); // our scroll kits + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); s.set_container(0, Some(one_item_backpack())); s.run("MainMenuBarBackpackButton:Click()").unwrap(); @@ -395,16 +381,12 @@ fn escape_ladder_cast_then_windows_then_target_one_eater_per_press() { ); // Press 4, no target: `ClearTarget` answers nil; the menu it falls through to is not loaded. - // Its deselect queues either way and finds nothing selected when it applies. s.set_unit("target", None); s.run("ToggleGameMenu()").unwrap(); - assert!(s.take_target_clear(), "the bare press reaches ClearTarget"); - assert_eq!( - s.eval::>("return ClearTarget()").unwrap(), - None, - "ClearTarget answers nil with no target" + assert!( + !s.take_target_clear(), + "ClearTarget answers nil with no target — nothing queued" ); - let _ = s.take_target_clear(); assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); } @@ -415,16 +397,12 @@ fn escape_ladder_targeting_rung_after_cast_before_windows() { let _data = benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - for file in crate::ui_script::test_ui::production_order(&[ - BAG_UI, - &[ - "Interface\\FrameXML\\CharacterFrameTemplates.xml", - "Interface\\FrameXML\\MerchantFrame.xml", - "ScrollTemplates.xml", // our scroll kits - ], - ]) { + for file in BAG_UI { load_xml(&s, file); } + load_xml(&s, "ScrollTemplates.xml"); // our scroll kits + load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml"); + load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); s.set_money(0); s.set_container(0, Some(one_item_backpack())); s.run("MainMenuBarBackpackButton:Click()").unwrap(); @@ -479,18 +457,17 @@ fn an_addon_frame_registered_in_uispecialframes_closes_on_escape() { benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); - load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); - load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); - load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); - load_xml(&s, r"Interface\FrameXML\GameMenuFrame.xml"); + load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); + load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); + load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); - load_xml(&s, "GameMenuAdapters.xml"); + load_xml(&s, "GameMenuFrame.xml"); s.run( r#" diff --git a/crates/benilla-app/src/ui_script/event_shape_gate.rs b/crates/benilla-app/src/ui_script/event_shape_gate.rs index 3ba9b0d2..63579a9b 100644 --- a/crates/benilla-app/src/ui_script/event_shape_gate.rs +++ b/crates/benilla-app/src/ui_script/event_shape_gate.rs @@ -148,7 +148,6 @@ fn fire_sites() -> Vec<(String, Vec, String)> { for call in [ concat!("fire_event", "("), concat!("fire_event_into", "("), - concat!("model.raise", "("), concat!("pending_events.push", "(("), ] { let mut from = 0; @@ -254,38 +253,6 @@ fn every_event_we_fire_carries_the_reference_s_arguments() { ); } -/// The in-game event table (`0x703d90`, name array `0xbe1198`) holds the names of -/// `reference/1.12-event-catalog.tsv` and the glue table the 26 of `GLUE_EVENTS`; `RegisterEvent` -/// drops any other name (`0x702140`), so an event fired under a name off both reaches no frame -/// that registered it by name. -#[test] -fn every_event_we_fire_is_in_the_reference_table() { - let tsv = concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../reference/1.12-event-catalog.tsv" - ); - let text = std::fs::read_to_string(tsv).expect("reference/1.12-event-catalog.tsv"); - let names: std::collections::BTreeSet<&str> = text - .lines() - .filter(|l| !l.starts_with('#')) - .filter_map(|l| l.split_once('\t').map(|(_, n)| n)) - .chain(benilla_ui::script::GLUE_EVENTS) - .collect(); - assert!(names.len() > 390, "the catalog stopped being read"); - let mut off: Vec = fire_sites() - .into_iter() - .filter(|(name, _, _)| !names.contains(name.as_str())) - .map(|(name, _, at)| format!("{name} from {at}")) - .collect(); - off.sort(); - off.dedup(); - assert!( - off.is_empty(), - "fired under a name the reference's event table lacks:\n {}", - off.join("\n ") - ); -} - /// `0x51bbb0` registers one watch per named unit-window field, so each of the 25 named event ids /// below `0xb6` comes from that bridge with the unit token, and each needs a producer here. #[test] diff --git a/crates/benilla-app/src/ui_script/exp_bar_tests.rs b/crates/benilla-app/src/ui_script/exp_bar_tests.rs index f5db8e86..0a804d56 100644 --- a/crates/benilla-app/src/ui_script/exp_bar_tests.rs +++ b/crates/benilla-app/src/ui_script/exp_bar_tests.rs @@ -6,25 +6,25 @@ fn exp_bar_harness() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); for file in [ - "Interface\\FrameXML\\GlobalStrings.lua", "Interface\\FrameXML\\Fonts.xml", r"Interface\FrameXML\UIParent.xml", r"Interface\FrameXML\MoneyFrame.lua", r"Interface\FrameXML\MoneyFrame.xml", "Interface\\FrameXML\\GameTooltip.xml", + "Interface\\FrameXML\\TextStatusBar.lua", + "Interface\\FrameXML\\TextStatusBar.xml", + "Interface\\FrameXML\\Cooldown.xml", + "Interface\\FrameXML\\ActionButtonTemplate.xml", + "Interface\\FrameXML\\GlobalStrings.lua", + "Interface\\FrameXML\\MainMenuBar.xml", + "Interface\\FrameXML\\ActionBarFrame.xml", + "Interface\\FrameXML\\BonusActionBarFrame.xml", // `ExhaustionTick_Update` reads `ReputationWatchBar`, which `ReputationFrame.xml` // declares; the two template files before it are what its check boxes inherit. r"Interface\FrameXML\UIPanelTemplates.lua", r"Interface\FrameXML\UIPanelTemplates.xml", r"Interface\FrameXML\OptionsFrameTemplates.xml", - "Interface\\FrameXML\\TextStatusBar.lua", - "Interface\\FrameXML\\TextStatusBar.xml", - "Interface\\FrameXML\\MainMenuBar.xml", - "Interface\\FrameXML\\Cooldown.xml", - "Interface\\FrameXML\\ActionButtonTemplate.xml", - "Interface\\FrameXML\\ActionBarFrame.xml", r"Interface\FrameXML\ReputationFrame.xml", - "Interface\\FrameXML\\BonusActionBarFrame.xml", ] { super::test_ui::load_ui(&s, file); } diff --git a/crates/benilla-app/src/ui_script/extract/mod.rs b/crates/benilla-app/src/ui_script/extract/mod.rs index 2397217d..d694e810 100644 --- a/crates/benilla-app/src/ui_script/extract/mod.rs +++ b/crates/benilla-app/src/ui_script/extract/mod.rs @@ -97,6 +97,7 @@ fn content_kind(c: &QuadContent) -> &'static str { QuadContent::Texture { .. } => "texture", QuadContent::ColorWheel => "colorwheel", QuadContent::ColorValue { .. } => "colorvalue", + QuadContent::Backdrop { .. } => "backdrop", QuadContent::Text { .. } => "text", } } @@ -114,9 +115,8 @@ pub(crate) fn tiling_axes(uv: &UvRect) -> (bool, bool) { } /// The [`UiQuad::uv_clamp`] window of an atlas crop, inset half a texel so a magnified cell does -/// not filter in its neighbour (in `ChatBubble-Backdrop`, a white alpha-0 column: a pale line -/// through the speech bubble). A tiling axis is left alone, or each period would land short and -/// walk the art; a whole-texture axis needs no window. +/// not filter in its neighbour. A tiling axis is left alone, as +/// [`benilla_ui::script::inset_atlas_bleed`] does; a whole-texture axis needs no window. fn uv_clamp_window(uv: &UvRect, size: (u32, u32)) -> Option<[f32; 4]> { let mut out = [1.0, 1.0, 0.0, 0.0]; // both axes off: `min > max` let mut any = false; @@ -280,10 +280,14 @@ fn span_bounds(spans: &[u32], i: usize) -> (usize, usize) { fn splice_simple(eq: &benilla_ui::script::ExtractedQuad) -> bool { match &eq.content { QuadContent::Frame - // At most one quad and one idempotent tile request. - | QuadContent::ModelPane { .. } + | QuadContent::Backdrop { .. } | QuadContent::ColorWheel | QuadContent::ColorValue { .. } => true, + // A file pane writes at most one quad and one idempotent tile request. A **unit** pane + // claims a pooled booth slot, and the claim sweep runs only on the full path — exactly + // why a `portrait_unit` texture below is unspliceable too: an entry whose side channel + // decides which booth renders has to be seen by the pass that rebuilds the whole map. + QuadContent::ModelPane { unit, .. } => unit.is_none(), // Region text only: a message line's link spans replace the whole set, and a line losing // its link would leave a stale rect while the tripwire's scratch stays empty. QuadContent::Text { .. } => matches!(eq.target, benilla_ui::order::ZTarget::Region(_)), @@ -424,30 +428,29 @@ pub(super) fn tick_script( mut last_seam: Local, // The `scale_factor` measures were last answered under; 0 until the first frame. mut last_dpi: Local, - // The dial, `UIParent`'s scale in the VM, and the automatic scale's last display. - (ui_scale, mut auto_cache): ( - Res, - Option>, - ), + ui_scale: Res, mut ui_cost: ResMut, mut pass: ResMut, ) { pass.live = false; let Some(mut script) = script else { // No VM, no UI sampling a booth pane: a stranded pane would keep the paper-doll camera - // rendering behind a dead UI. + // rendering behind a dead UI, and a stranded claim would hold a pooled booth for a + // `FrameHandle` the next VM will reuse for something else. booths.panes.0.clear(); + booths.unit_panes.release_all(); return; }; let Ok(window) = window.single() else { booths.panes.0.clear(); + booths.unit_panes.release_all(); return; }; let (w, h) = (window.width(), window.height()); // The client's UI space is 768 units tall at every aspect (`f(screenH) = 768`, `0x41ad10`; the - // caret's `H_px/192`, `0x77b8c0`), the dial being `UIParent`'s scale inside it. The VM lives in - // it: quads scale ×s out, and the mouse and the measures ÷s in. - let s = super::seam_scale(h); + // caret's `H_px/192`, `0x77b8c0`), `768/uiScale` with the dial. The VM lives in it: quads + // scale ×s out, and the mouse and the measures ÷s in. + let s = super::seam_scale(h, ui_scale.0); // A moved seam scale or DPI stales every text metric the engine caches: integer-stepped // advances do not rescale, and the size snap moves a string's width by several percent, // enough for the ellipsis to eat fitting text. A monitor hop moves the DPI alone. @@ -489,28 +492,9 @@ pub(super) fn tick_script( { let _span = bevy::log::info_span!("ui_script: tick").entered(); let resized = script.set_screen_size(w / s, if h > 0.0 { h / s } else { 768.0 }); - // The UI scale is `UIParent`'s, which only the in-game state has; the glue lays out on - // the root alone (`0x46dd30`). - if script.vm_kind() == benilla_ui::script::VmKind::InGame { - // A display change re-applies the UI scale (`0x492e90`), whose narrow-aspect cap - // follows the window; a moved dial applies once, as its CVar callback does - // (`0x490770`). Deviation: the reference then re-reads the aspect (`0x492f13`), and on - // a change of 0.001 or more calls `0x491380`, which sets the reload trigger - // (`[0xb4b3f4]`) when the script-context check `0x494a50(0xa)` allows it; here a - // resize re-lays out the live UI, the chat bubbles' window-derived `BubbleGeometry` - // with it, because the window resizes freely and a reload on each resize would tear - // down every addon's state mid-play. - if resized { - let display = (w.round() as u32, h.round() as u32); - let mut none = None; - let cache = auto_cache.as_deref_mut().map_or(&mut none, |c| &mut c.0); - script.apply_ui_scale(display, cache); - } - let rescaled = script.set_ui_scale(ui_scale.0); - // Either re-runs what the interface computed from the old `GetScreenHeight()`. - if resized || rescaled { - super::manifest::on_screen_resized(&script); - } + // A resize re-runs what the interface computed from the old screen size. + if resized { + super::manifest::on_screen_resized(&script); } script.tick(time.delta_secs()); } @@ -523,8 +507,7 @@ pub(super) fn tick_script( let us_tick = lap(); // ── The FontString measure round-trip, before the resolve ──────────────────────────────── // A request reads only region data, never a resolved rect, and `resolve` writes no region's - // size, so measuring first lets the resolve below see the answers. The tick drained the layout - // after its walk; this resolve takes the measures and what the tick's tail moved. + // size, so measuring first lets the frame's one resolve see the answers. let mut measured_any = false; if let Some(atlas) = font_atlas.as_deref_mut() { measured_any = measure_fontstrings(&mut script, atlas, s, &mut ui_cost, ui_cost_wanted.0); @@ -560,9 +543,6 @@ pub(super) fn tick_script( path: r.font.as_deref(), height: crate::ui_text::drawn_px(r.height, None, s * r.scale), outline: r.outline, // the outline biases the stepped width - spacing: 0.0, // the gap moves rows, never counts them - // The drawn shadow seeds each row, as the draw's does. - shadow_x: r.shadow_x * s * r.scale, alpha_gradient: None, }, ); @@ -583,13 +563,6 @@ pub(super) fn tick_script( for w in script.take_warnings() { warn!("ui_script: {w}"); } - // A runtime `LoadAddOn`'s record, appended to `Logs\FrameXML.log`; the glue has none, but - // whatever its VM drains is the glue log's. - let writes = script.take_load_log_writes(); - match script.vm_kind() { - benilla_ui::script::VmKind::InGame => super::load_log::write(writes), - benilla_ui::script::VmKind::Glue => super::load_log::write_glue(writes), - } // ── The handover ──────────────────────────────────────────────────────────────────────── *pass = super::UiPassState { @@ -694,8 +667,23 @@ pub(super) fn paint_script( // The focused editbox's per-byte advances, measured as it draws, for click and scroll. if let Some(atlas) = font_atlas.as_deref_mut() { if let Some(req) = script.editbox_advances_request() { - let (cum, rows, cell_h) = crate::ui_text::editbox_advances(&mut atlas.lock(), s, &req); - script.set_editbox_advances(req.id, req.key, cum, rows, cell_h); + let spec = crate::ui_text::FontSpec { + path: req.font.as_deref(), + // At the drawn size, divided by the seam alone: screen UI units, like the mouse. + height: crate::ui_text::drawn_px(req.height, None, s * req.scale), + outline: req.outline, + alpha_gradient: None, // alpha never changes metrics + }; + let cum: Vec = crate::ui_text::line_advances(&mut atlas.lock(), &req.text, spec) + .iter() + .map(|a| a / s) + .collect(); + // A multiline box also gets the draw's row starts and pitch, in UI units. + let (rows, cell_h) = match req.wrap_width { + Some(w) => crate::ui_text::line_rows(&mut atlas.lock(), &req.text, w * s, spec), + None => (vec![0], 0.0), + }; + script.set_editbox_advances(req.id, req.key, cum, rows, cell_h / s); } } // The focused editbox's text UI: its Text quad, scroll window, caret and selection spans, @@ -708,7 +696,13 @@ pub(super) fn paint_script( // ── The extract gate ────────────────────────────────────────────────────── // Equal inputs skip the conversion; the quads, minimap slot and link spans stay as they are. let dims = (w.to_bits(), h.to_bits(), s.to_bits(), dpi.to_bits()); + // A pooled unit-pane booth came free while a pane was waiting for one: take the full path + // once so the claim can move ([`crate::portrait::UnitPaneBooths::take_handover`]). The claim + // sweep runs only there, and a refused pane would otherwise sit empty until something else + // in the UI happened to change. + let pool_handover = booths.unit_panes.take_handover(); let settled = capture.is_none() + && !pool_handover && prev.dims == Some(dims) && prev.generation == generation && text_ui == prev.text_ui @@ -773,6 +767,9 @@ pub(super) fn paint_script( if capture.is_some() { no_splice!("capture mode"); } + if pool_handover { + no_splice!("a pooled unit-pane booth came free"); + } if ui_diff_enabled() { no_splice!("WOW_UI_DIFF pins the full path"); } @@ -976,8 +973,12 @@ pub(super) fn paint_script( prev.portraits = booths.images.0.clone(); prev.extracted = extracted.clone(); // Cleared only on the full-conversion path: a settled frame's map is still true, and - // clearing it would put the body panes' cameras to sleep. + // clearing it would put the body panes' cameras to sleep. The dynamic unit-pane claims are + // swept on the same edge and for the same reason — this is the one path that sees every + // drawn pane, so it is the only one that can tell a pane that stopped drawing from one whose + // entry simply did not change ([`crate::portrait::UnitPaneBooths::sweep_begin`]). booths.panes.0.clear(); + booths.unit_panes.sweep_begin(); let mut spans: Vec = Vec::with_capacity(prev.extracted.len()); for eq in extracted { convert_entry( @@ -999,6 +1000,8 @@ pub(super) fn paint_script( spans.push(out.len() as u32); } prev.spans = spans; + // Every drawn pane has now re-claimed its booth; release the slots nobody asked for. + booths.unit_panes.sweep_end(); // Capture mode only: the held payload's icon at the mouse, for the hardware cursor a capture // cannot show; every payload kind, as `crate::cursor`'s `payload_icon`. if capture.is_some() { @@ -1130,23 +1133,32 @@ fn convert_entry( // A frame draws nothing itself; its regions carry the visuals. QuadContent::Frame => {} // The `` content hole, parked for the minimap renderer to fill at this `z`. - QuadContent::Minimap { zoom, inside_zoom } => { + QuadContent::Minimap { + zoom, + inside_zoom, + arrow_scale, + } => { *minimap_slot = Some(crate::minimap::MinimapSlot { rect, z: eq.z, zoom, inside_zoom, alpha: eq.alpha, + arrow_scale, }); } // A ``/`` pane draws itself, as the stock files declare a bare pane - // with no Texture: a glue `ModelFFX` is the glue booth's scene, a file pane is a model - // tile, a unit pane samples its window's body bake square - // ([`crate::portrait::model_pane_booth`]), and an unclaimed pane draws nothing. + // with no Texture: a file pane is a model tile, and a unit pane samples a booth's bake + // square — its window's, by the pane's global name + // ([`crate::portrait::model_pane_booth`]), or a pooled dynamic one for an addon's own + // pane ([`crate::portrait::UnitPaneBooths`]). A pane with neither a file nor a unit, and + // one past the pool's cap, draw nothing. QuadContent::ModelPane { handle, name, model, + skins, + unit, facing, model_scale, position, @@ -1155,41 +1167,8 @@ fn convert_entry( camera, light, fog, - ffx, } => { use crate::portrait::PortraitSource; - // A `ModelFFX`'s scene draws once, at its own bucket's ARTWORK callback (`0x46fa90` - // overrides the paint registration `0x76d160`), so every UI region paints over it. - // The scene is the glue booth's, which renders the window's view of the stage the - // glue VM feeds it (`crate::ui_script::glue_scene`); the pane shows its part of it. - if ffx { - let Some(PortraitSource::Live(image)) = - booths.images.0.get(crate::portrait::GLUE_SLOT) - else { - return; - }; - if w <= 0.0 || h <= 0.0 { - return; - } - out.push(UiQuad { - rect, - z_key: eq.z, - texture: Some(image.clone()), - uv: UvRect::from_tex_coords([ - rect.min.x / w, - rect.max.x / w, - rect.min.y / h, - rect.max.y / h, - ]), - // The scene draws at the widget's own alpha (`0x76d120`). - color: [1.0, 1.0, 1.0, own_alpha], - // The booth's target is premultiplied. - premultiplied: true, - clip, - ..default() - }); - return; - } // A file pane publishes an idempotent tile request; `ui_models::compose_tiles` draws // its quad each frame, so a cell packed later needs no re-conversion. if let Some(path) = model.as_deref() { @@ -1206,6 +1185,7 @@ fn convert_entry( handle, crate::ui_models::TileRequest { path: path.to_string(), + skins: skins.clone(), size_px, // 1 model unit is `1280 · modelScale · layoutScale` FrameXML units // (`0x76d1a0`). @@ -1250,7 +1230,7 @@ fn convert_entry( } return; } - let Some(slot) = name.as_deref().and_then(crate::portrait::model_pane_booth) else { + let Some(slot) = booths.model_pane_slot(name.as_deref(), handle, unit, facing) else { return; }; // The aspect is published before the readiness check: the bake waits on the publish. @@ -1330,7 +1310,7 @@ fn convert_entry( }, color: [1.0, 1.0, 1.0, eq.alpha], // `SetPortraitTexture` cuts the inscribed circle, as the reference stamps into - // its 64² bake's alpha. + // its 64² bake's alpha; `BenillaSetBoothTexture` samples square. circular, premultiplied, clip, @@ -1456,6 +1436,44 @@ fn convert_entry( ..default() }); } + QuadContent::Backdrop { + path, + color, + uvs, + tile, + } => { + // A backdrop piece, the bg or one of the 8 border pieces, sampled `Repeat` when `tile`; + // its UVs are per-corner, as the TOP and BOTTOM edges are rotated. + let handle = assets.as_mut().and_then(|a| { + if tile { + a.sprite_texture_tiled(&path, images) + } else { + a.sprite_texture(&path, images) + } + }); + // A missing BLP draws nothing. + let Some(handle) = handle else { return }; + // The 8 border pieces share one atlas, so a `tile` piece is pulled half a texel in on + // its bounded axes; a stretched bg is a whole texture. + let uvs = match images.get(&handle) { + Some(img) if tile => { + let sz = img.texture_descriptor.size; + benilla_ui::script::inset_atlas_bleed(uvs, sz.width as f32, sz.height as f32) + } + _ => uvs, + }; + let mut color = color; + color[3] *= eq.alpha; + out.push(UiQuad { + rect, + z_key: eq.z, + texture: Some(handle), + uv: UvRect::from_corners(uvs), + color, + clip, + ..default() + }); + } QuadContent::Text { text, color, @@ -1466,7 +1484,6 @@ fn convert_entry( text_height, shadow, outline, - spacing, alpha_gradient, world_seat, } => { @@ -1486,7 +1503,6 @@ fn convert_entry( text_height, shadow, outline, - spacing, alpha_gradient, world_seat, }, @@ -1701,6 +1717,7 @@ mod clip_plumb_tests { app.init_resource::>(); app.init_resource::(); app.init_resource::(); + app.init_resource::(); app.init_resource::(); app.init_resource::(); app.init_resource::(); @@ -1742,10 +1759,9 @@ mod clip_plumb_tests { ); } - /// The dial is `UIParent`'s scale, so a ScrollFrame with no parent keeps its px rect and clip - /// at dial 0.5: the root stays 768 units tall. + /// At dial 0.5 the VM sees a 2048×1536 screen, and every window-px rect halves. #[test] - fn the_uiscale_dial_leaves_a_parentless_frame_alone() { + fn the_uiscale_dial_scales_the_extracted_rects() { let mut app = app_with_scrolled_marker(); app.insert_resource(crate::ui_script::UiScaleCvar(0.5)); app.update(); @@ -1756,10 +1772,12 @@ mod clip_plumb_tests { .find(|q| q.color[0] == 1.0 && q.color[1] == 0.0 && q.color[2] == 0.0) .expect("the colored marker quad extracted"); + // WoW space: top 1536 − 100 = 1436, bottom 1236, right 300; ×0.5 is y-up px 618..718, + // and y-down through the 768 window the top is 50 and the bottom 150. assert_eq!( marker.clip, - Some(Rect::new(0.0, 100.0, 300.0, 300.0)), - "dial 0.5 moves nothing outside UIParent" + Some(Rect::new(0.0, 50.0, 150.0, 150.0)), + "dial 0.5 halves the px rect and re-hangs the frame from the taller virtual top" ); } @@ -1786,6 +1804,7 @@ mod clip_plumb_tests { app.init_resource::>(); app.init_resource::(); app.init_resource::(); + app.init_resource::(); app.init_resource::(); app.init_resource::(); app.init_resource::(); @@ -1973,11 +1992,6 @@ mod extract_gate_tests { fn app_from_script(lua: &str) -> App { let script = UiScript::new().unwrap(); script.run(lua).unwrap(); - app_over(script) - } - - /// [`app_from_script`] over a VM of the caller's. - fn app_over(script: UiScript) -> App { let mut app = App::new(); app.insert_non_send_resource(script); app.init_resource::(); @@ -1985,6 +1999,7 @@ mod extract_gate_tests { app.init_resource::>(); app.init_resource::(); app.init_resource::(); + app.init_resource::(); app.init_resource::(); app.init_resource::(); app.init_resource::(); @@ -2054,151 +2069,208 @@ mod extract_gate_tests { ); } - /// A glue `ModelFFX` draws the glue booth's image over its own rect, sampling the window's - /// part of the booth's view, at its bucket's callback rank, and asks the UI model tiles for - /// nothing; a plain `Model` beside it is still a tile. + /// `PaperDollFrame.xml:204` declares a bare ``, and a + /// window feeds it a subject and a yaw; an addon's own pane has no window, so `SetUnit` is the + /// whole request — and it used to draw nothing at all, which is the empty 3D portrait box in + /// pfUI. The rule now, in one test because the three halves only mean anything together: + /// + /// 1. a stock name still takes its window's booth ([`crate::portrait::model_pane_booth`]), + /// 2. any other pane that names a unit takes a pooled dynamic booth + /// ([`crate::portrait::UnitPaneBooths`]), one slot each, in the order they draw, + /// 3. and the first pane past the pool's cap draws nothing — the same answer + /// `UI_MODEL_CAM_LAYERS` already gives a ninth perspective file pane, and deliberately not + /// a round 2D bust stand-in, which would look plausible and be a different thing. #[test] - fn a_model_ffx_draws_the_glue_booth_and_asks_for_no_tile() { - let script = UiScript::new_glue().unwrap(); - script - .run( - r#" - local holder = CreateFrame("Frame", "Holder") - holder:SetPoint("TOPLEFT", 0, 0); holder:SetWidth(1024); holder:SetHeight(768) - holder:SetAlpha(0.5) - local scene = CreateFrame("ModelFFX", "Scene", holder) - scene:SetPoint("TOPLEFT", 100, -50) - scene:SetWidth(400); scene:SetHeight(300) - scene:SetModel("Interface\\Glues\\Models\\UI_Orc\\UI_Orc.mdx") - scene:SetAlpha(0.8) - local art = scene:CreateTexture(nil, "ARTWORK") - art:SetAllPoints(scene); art:SetTexture(1, 0, 0) - local over = scene:CreateTexture(nil, "OVERLAY") - over:SetAllPoints(scene); over:SetTexture(0, 1, 0) - local child = CreateFrame("Frame", "Child", scene) - child:SetAllPoints(scene) - local under = child:CreateTexture(nil, "BACKGROUND") - under:SetAllPoints(child); under:SetTexture(0, 0, 1) - local plain = CreateFrame("Model", "Plain") - plain:SetPoint("TOPLEFT", 600, -50) - plain:SetWidth(64); plain:SetHeight(64) - plain:SetModel("Plain.mdx") - "#, - ) - .unwrap(); - let mut app = app_over(script); - let image = app - .world_mut() - .resource_mut::>() - .add(Image::default()); - app.world_mut().resource_mut::().0.insert( - crate::portrait::GLUE_SLOT.to_string(), - crate::portrait::PortraitSource::Live(image.clone()), - ); - app.update(); - - let paths: Vec = app - .world() - .resource::() - .requests - .values() - .map(|r| r.path.clone()) - .collect(); - assert_eq!(paths, ["Plain.mdx"], "the ModelFFX is no tile"); - - let quads = &app.world().resource::().quads; - let scene: Vec<_> = quads - .iter() - .filter(|q| q.texture.as_ref() == Some(&image)) - .collect(); - assert_eq!(scene.len(), 1, "the pane draws the glue booth's image once"); - let q = scene[0]; - assert!(q.premultiplied); - // The widget's own alpha (`0x76d120`); `SetAlpha` writes the raw value down the subtree - // with no product, so the holder's earlier 0.5 is gone. - assert!((q.color[3] - 0.8).abs() < 1e-3, "{:?}", q.color); - // The ARTWORK callback of the pane's own bucket: after its ARTWORK regions, before its - // OVERLAY ones and every child frame's (`0x46fa90`, layer 2). - let z_of = |rgb: [f32; 3]| { - quads - .iter() - .find(|q| q.texture.is_none() && q.color[..3] == rgb) - .map(|q| q.z_key) - .expect("the region drew") - }; - let (art, over, child) = ( - z_of([1.0, 0.0, 0.0]), - z_of([0.0, 1.0, 0.0]), - z_of([0.0, 0.0, 1.0]), - ); - assert!( - art < q.z_key && q.z_key < over && over < child, - "{art} {} {over} {child}", - q.z_key - ); - assert!(q.rect.width() > 0.0 && q.rect.height() > 0.0); - let (w, h) = (1024.0, 768.0); - assert!( - q.uv == super::UvRect::from_tex_coords([ - q.rect.min.x / w, - q.rect.max.x / w, - q.rect.min.y / h, - q.rect.max.y / h, - ]), - "the pane samples its own part of the window's view" - ); - } - - /// `PaperDollFrame.xml:204` declares a bare ``; a pane - /// no window claims ([`crate::portrait::model_pane_booth`]) must not borrow a bake. - #[test] - fn a_named_model_pane_samples_its_booth_and_an_unclaimed_one_draws_nothing() { - let mut app = app_from_script( + fn a_named_pane_keeps_its_window_booth_while_addon_panes_fill_the_pool_and_the_first_past_the_cap_draws_nothing( + ) { + use crate::portrait::UNIT_PANE_SLOTS; + // One more pane than the pool has slots, so the cap is exercised, not just approached. + let panes = UNIT_PANE_SLOTS.len() + 1; + let mut app = app_from_script(&format!( r#" local doll = CreateFrame("PlayerModel", "CharacterModelFrame") doll:SetPoint("TOPLEFT", 0, 0) doll:SetWidth(233); doll:SetHeight(224) doll:SetUnit("player") - local stray = CreateFrame("PlayerModel", "SomeAddonsModelPane") - stray:SetPoint("TOPLEFT", 300, 0) - stray:SetWidth(233); stray:SetHeight(224) - stray:SetUnit("player") - "#, + for i = 1, {panes} do + local p = CreateFrame("PlayerModel", "pfPortraitModelraid" .. i) + p:SetPoint("TOPLEFT", 300, -40 * i) + p:SetWidth(64); p:SetHeight(32) + p:SetUnit("player") + p:SetCamera(0) + end + "# + )); + // The pane's token has to name something: the booth is handed a GUID, so a token that + // resolves to nothing claims no slot. + app.world_mut() + .non_send_resource_mut::() + .set_unit( + "player", + Some(benilla_ui::script::UnitState { + guid: 0xdead_beef, + exists: true, + ..default() + }), + ); + // One distinguishable bake per booth: the paper doll's and the pool's eight. + let bake_for = |app: &mut App, slot: &str| { + let bake = app + .world_mut() + .resource_mut::>() + .add(Image::default()); + app.world_mut().resource_mut::().0.insert( + slot.to_string(), + crate::portrait::PortraitSource::Live(bake.clone()), + ); + bake + }; + let doll_bake = bake_for(&mut app, "paperdoll"); + let pool_bakes: Vec> = UNIT_PANE_SLOTS + .iter() + .map(|slot| bake_for(&mut app, slot)) + .collect(); + app.update(); + + let drawn = |app: &App, bake: &Handle| { + app.world() + .resource::() + .quads + .iter() + .filter(|q| q.texture.as_ref() == Some(bake)) + .count() + }; + assert_eq!( + drawn(&app, &doll_bake), + 1, + "exactly the named pane draws the paper doll's bake" ); + for (i, bake) in pool_bakes.iter().enumerate() { + assert_eq!( + drawn(&app, bake), + 1, + "pool slot {i} ({}) must be drawn by exactly one pane", + UNIT_PANE_SLOTS[i] + ); + } + // The cap: nine panes asked, eight booths exist, so one pane drew no bake at all. + let pane_quads: usize = std::iter::once(&doll_bake) + .chain(&pool_bakes) + .map(|b| drawn(&app, b)) + .sum(); + assert_eq!( + pane_quads, + 1 + UNIT_PANE_SLOTS.len(), + "the pane past the cap must draw nothing, not borrow a booth" + ); + let booths = app.world().resource::(); + let aspect = booths + .0 + .get("paperdoll") + .copied() + .expect("the named pane publishes its aspect"); + assert!( + (aspect - 233.0 / 224.0).abs() < 0.01, + "the pane's own rect is the aspect, got {aspect}" + ); + for slot in UNIT_PANE_SLOTS { + let a = booths + .0 + .get(slot) + .copied() + .unwrap_or_else(|| panic!("pool slot {slot} publishes its aspect")); + assert!( + (a - 64.0 / 32.0).abs() < 0.01, + "a pooled booth frames at its own pane's aspect, got {a} for {slot}" + ); + } + let bake_quad = app + .world() + .resource::() + .quads + .iter() + .find(|q| q.texture.as_ref() == Some(&pool_bakes[0])) + .expect("slot 0 drew"); + assert!( + bake_quad.premultiplied, + "a render-target bake carries premultiplied colour" + ); + } + + /// The cap is only acceptable if a booth comes back: without a release the first eight panes + /// to draw would own the pool for the session, and pfUI's ninth unit frame would be + /// permanently empty. A pane hiding frees its slot, and the pane that was refused takes it — + /// which needs the extract to run one more full conversion, since the claim sweep runs + /// nowhere else (`UnitPaneBooths::take_handover`); with no UI change to force it, a settled + /// frame would leave the refused pane blank forever. + #[test] + fn a_hidden_unit_pane_frees_its_pooled_booth_for_the_pane_the_cap_refused() { + use crate::portrait::UNIT_PANE_SLOTS; + let filling = UNIT_PANE_SLOTS.len(); + let mut app = app_from_script(&format!( + r#" + panes = {{}} + for i = 1, {filling} do + local p = CreateFrame("PlayerModel", "pfPortraitModelraid" .. i) + p:SetPoint("TOPLEFT", 10 * i, 0) + p:SetWidth(64); p:SetHeight(32) + p:SetUnit("player") + panes[i] = p + end + -- The pane past the cap, at an x no filling pane uses. + refused = CreateFrame("PlayerModel", "pfPortraitModelrefused") + refused:SetPoint("TOPLEFT", 500, 0) + refused:SetWidth(64); refused:SetHeight(32) + refused:SetUnit("player") + "# + )); + app.world_mut() + .non_send_resource_mut::() + .set_unit( + "player", + Some(benilla_ui::script::UnitState { + guid: 0xdead_beef, + exists: true, + ..default() + }), + ); + // One shared bake under every slot, so "drew a booth" is a texture match and which slot + // the claim landed in is not part of the property. let bake = app .world_mut() .resource_mut::>() .add(Image::default()); - app.world_mut().resource_mut::().0.insert( - "paperdoll".to_string(), - crate::portrait::PortraitSource::Live(bake.clone()), - ); + for slot in UNIT_PANE_SLOTS { + app.world_mut().resource_mut::().0.insert( + slot.to_string(), + crate::portrait::PortraitSource::Live(bake.clone()), + ); + } + let refused_drew = |app: &App| { + app.world() + .resource::() + .quads + .iter() + .any(|q| q.texture.as_ref() == Some(&bake) && q.rect.min.x == 500.0) + }; app.update(); + assert!( + !refused_drew(&app), + "the pool is full, so the ninth pane draws nothing yet" + ); - let quads = &app.world().resource::().quads; - let panes: Vec<_> = quads - .iter() - .filter(|q| q.texture.as_ref() == Some(&bake)) - .collect(); - assert_eq!( - panes.len(), - 1, - "exactly the claimed pane draws the paper doll's bake" - ); + // One filling pane leaves. Its slot frees at the end of that conversion, which is after + // the refused pane already asked, so the handover costs one more conversion — and the + // only thing that changed in the UI is the hide, so nothing else will force one. + app.world_mut() + .non_send_resource_mut::() + .run("panes[1]:Hide()") + .unwrap(); + app.update(); + app.update(); assert!( - panes[0].premultiplied, - "a render-target bake carries premultiplied colour" - ); - let aspect = app - .world() - .resource::() - .0 - .get("paperdoll") - .copied() - .expect("the pane publishes its aspect"); - assert!( - (aspect - 233.0 / 224.0).abs() < 0.01, - "the pane's own rect is the aspect, got {aspect}" + refused_drew(&app), + "the freed booth must reach the pane the cap refused" ); } @@ -2209,15 +2281,15 @@ mod extract_gate_tests { let mut app = app_with_marker(); app.world_mut() .non_send_resource_mut::() - .run("SetPortraitTexture(marker, 'target')") + .run("BenillaSetBoothTexture(marker, 'paperdoll')") .unwrap(); - // The booth publishes a live bake for that unit; without an entry the region draws nothing. + // The booth publishes a live bake for that slot; without an entry the region draws nothing. let bake = app .world_mut() .resource_mut::>() .add(Image::default()); app.world_mut().resource_mut::().0.insert( - "target".to_string(), + "paperdoll".to_string(), crate::portrait::PortraitSource::Live(bake.clone()), ); app.update(); diff --git a/crates/benilla-app/src/ui_script/extract/text.rs b/crates/benilla-app/src/ui_script/extract/text.rs index bfa4c2a6..566efe6a 100644 --- a/crates/benilla-app/src/ui_script/extract/text.rs +++ b/crates/benilla-app/src/ui_script/extract/text.rs @@ -25,8 +25,6 @@ pub(super) struct TextStyle { pub text_height: Option, pub shadow: Option, pub outline: Outline, - /// The gap between lines, frame-local, scaled like the font height. - pub spacing: f32, pub alpha_gradient: Option<(f32, f32)>, /// The V-plate's name and level, seated on the plate's device-snapped overlay, not the UI grid. pub world_seat: bool, @@ -42,11 +40,10 @@ pub(super) struct TextHost<'a> { /// The focused editbox's text UI, unfiltered: [`emit`] matches it to this quad by `target`. pub ebox: Option<&'a EditBoxTextUi>, pub screen_h: f32, - /// The seam scale `windowH/768`, for the root-unit inputs; rects arrive in px. + /// The seam scale `windowH/768 × uiScale`, for the unit-space inputs; rects arrive in px. pub scale: f32, /// The frame's effective scale, already in the rect: glyph size and shadow offset ride it, as - /// the 1.12 client's text rides `SetScale` and the UI scale; the editbox x-offsets arrive in - /// root units. + /// the 1.12 client's text rides `SetScale`; the editbox x-offsets arrive in screen UI units. pub font_scale: f32, /// Captures pin the caret on for deterministic pixels; live, the engine's blink decides. pub caret_pinned: bool, @@ -78,11 +75,6 @@ pub(super) fn emit( host.scale * host.font_scale, ), outline: style.outline, - spacing: style.spacing * host.scale * host.font_scale, - // A set shadow seeds each row's pen at its drawn offset (`0x5cddb1`, `0x7726a4`). - shadow_x: style - .shadow - .map_or(0.0, |sh| sh.offset[0] * host.scale * host.font_scale), alpha_gradient: style.alpha_gradient, }; // The focused editbox draws windowed (`0x77da80`): the scroll window's substring from the @@ -395,7 +387,7 @@ mod tests { assert_eq!(band_clip_slack(Some(2.0)), 3.0); } - /// `1.0546875` is a 1600×900 window's seam times the 0.9 UI scale. + /// `1.0546875` is the seam scale of a 1600×900 window at uiScale 0.9. #[test] fn a_shadow_offset_is_a_whole_pixel_and_never_vanishes() { assert_eq!(shadow_offset_px(1.0), 1.0); @@ -470,6 +462,7 @@ mod caret_tests { app.init_resource::>(); app.init_resource::(); app.init_resource::(); + app.init_resource::(); app.init_resource::(); app.init_resource::(); app.init_resource::(); diff --git a/crates/benilla-app/src/ui_script/fade_tests.rs b/crates/benilla-app/src/ui_script/fade_tests.rs index 1a1f495d..7c056f68 100644 --- a/crates/benilla-app/src/ui_script/fade_tests.rs +++ b/crates/benilla-app/src/ui_script/fade_tests.rs @@ -10,14 +10,14 @@ fn a_started_fade_ramps_off_uiparents_tick_and_completes() { benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); + load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); + load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); + load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); + load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); - load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); - load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); - load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); - load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); s.resolve(); s.run(r#"CreateFrame("Frame", "BenillaFadeProbe")"#) @@ -62,14 +62,14 @@ fn a_flash_alternates_then_stops() { benilla_formats::wow_data_or_skip!(); let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); + load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); + load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); + load_xml(&s, r"Interface\FrameXML\UIParent.xml"); + load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); + load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); load_xml(&s, r"Interface\FrameXML\GlobalStrings.lua"); load_xml(&s, r"Interface\FrameXML\BasicControls.xml"); load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua"); - load_xml(&s, r"Interface\FrameXML\UIParent.xml"); - load_xml(&s, r"Interface\FrameXML\MoneyFrame.lua"); - load_xml(&s, r"Interface\FrameXML\MoneyFrame.xml"); - load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); - load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); load_xml(&s, r"Interface\FrameXML\StaticPopup.xml"); s.resolve(); diff --git a/crates/benilla-app/src/ui_script/faux_scroll_tests.rs b/crates/benilla-app/src/ui_script/faux_scroll_tests.rs index 042171a0..9b72cbd0 100644 --- a/crates/benilla-app/src/ui_script/faux_scroll_tests.rs +++ b/crates/benilla-app/src/ui_script/faux_scroll_tests.rs @@ -22,9 +22,9 @@ fn harness() -> UiScript { let mut s = UiScript::new().unwrap(); s.set_screen_size(1024.0, 768.0); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); + load_xml(&s, "ScrollTemplates.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); - load_xml(&s, "ScrollTemplates.xml"); load_inline( &s, r#" @@ -337,7 +337,6 @@ fn the_reference_on_vertical_scroll_path_runs_once_a_scroll_child_exists() { .unwrap(); s.resolve(); s.run("AddonScroll:UpdateScrollChildRect()").unwrap(); - s.tick(0.016); assert_eq!( s.eval::("return AddonScroll:GetVerticalScrollRange()") .unwrap(), @@ -368,9 +367,9 @@ fn scrolling_edit_helpers_answer_bare_calls_from_a_handler() { benilla_formats::wow_data_or_skip!(); let s = UiScript::new().unwrap(); load_xml(&s, "Interface\\FrameXML\\Fonts.xml"); + load_xml(&s, "ScrollTemplates.xml"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.lua"); load_xml(&s, r"Interface\FrameXML\UIPanelTemplates.xml"); - load_xml(&s, "ScrollTemplates.xml"); load_inline( &s, r#" diff --git a/crates/benilla-app/src/ui_script/flash_trace.rs b/crates/benilla-app/src/ui_script/flash_trace.rs new file mode 100644 index 00000000..b46f51c6 --- /dev/null +++ b/crates/benilla-app/src/ui_script/flash_trace.rs @@ -0,0 +1,141 @@ +//! `WOW_FLASH_CHURN=1`: name what is flashing. +//! +//! A window that "stutters" or "flashes" is being shown and hidden faster than the eye resolves, +//! and nothing in the log says which one — a `Show()`/`Hide()` pair is an ordinary thing to do, so +//! neither is worth a line on its own. What is *not* ordinary is doing it many times a second, and +//! that is a shape only a per-frame sweep can see. +//! +//! This walks every named frame **and region** each frame, counts the visibility edges, and once a +//! second prints the ones that toggled more than [`LOUD`] times. The name is the answer: a run +//! that prints `QuestFrame 47` says the quest window is being driven, not that the renderer is +//! slow. +//! +//! Regions matter as much as frames and were missed at first: a texture shown and hidden every +//! frame flashes exactly as visibly as a window, and Tmog's "Loading" overlay is one. Reading +//! frames alone reported "nothing is toggling" through a session that was visibly flashing, which +//! is the worst answer an instrument can give. +//! +//! Off unless the variable is set; with it set the sweep is one pass over the frame arena and a +//! hash lookup per named widget, keyed on handles ([`VisKey`]). **It is still O(named widgets) +//! per frame** — 2.7 ms on this interface — so it is for a session spent hunting a flashing +//! window, not for one measuring anything else. +//! +//! **The keys are handles, not names, and that is not a detail.** The first cut kept a +//! `HashMap` and cloned a name per named widget per frame. On this interface — +//! 17 064 frames and 40 012 regions — that is ~57 000 allocations and ~114 000 string hashes +//! every frame, which measured **6 ms a frame, 420 ms of every second**, enough to rank second +//! among all systems in a `WOW_SYSTEM_TIMING` report. An instrument armed in every diagnostic run +//! was therefore producing a large share of the stutter it was pointed at, and corrupting every +//! measurement taken beside it. Handles are `Copy` and hash without allocating; names are +//! resolved by [`UiScript::vis_key_name`] for the dozen rows that actually print. + +use std::collections::HashMap; + +use bevy::prelude::*; + +use benilla_ui::script::{UiScript, VisKey}; + +/// Edges in a one-second window past which a frame is worth naming. A window opening and closing +/// legitimately does so once or twice; ten is a frame being driven by something. +const LOUD: u32 = 10; + +/// Per-frame visibility and the edge tally since the last report. +#[derive(Default)] +pub(crate) struct FlashTrace { + last: HashMap, + edges: HashMap, + scratch: Vec<(VisKey, bool)>, + window_start: Option, + armed: Option, +} + +impl FlashTrace { + /// Whether the tracer is on, read once. + fn on(&mut self) -> bool { + *self.armed.get_or_insert_with(|| { + // Its own switch, not `WOW_FLASH_TRACE`. That variable also arms the frame-cost line + // ([`crate::perf`]), which is the meter every session wants and costs nothing — while + // this sweep is O(named widgets) and measured **2.7 ms a frame, 447 ms of every + // second** on a 17 000-frame interface even after its allocations were removed. Two + // instruments on one switch meant every run paid for the expensive one to read the + // cheap one, and several of this project's measurements were taken that way. + let on = std::env::var_os("WOW_FLASH_CHURN").is_some(); + // Say so once. Silence from a tracer is only evidence if you know it ran: two + // sessions were read as "nothing is toggling" when the honest reading was "no idea". + if on { + info!( + "flash: churn tracer armed (WOW_FLASH_CHURN) — frames and regions toggling \ + >{LOUD}x a second will be named; costs ~2.7 ms a frame on a loaded interface" + ); + } + on + }) + } +} + +/// Count every named frame's visibility edges; report the loud ones once a second. +pub(crate) fn trace_visibility_churn( + script: Option>, + time: Res