diff --git a/.cargo/config.toml b/.cargo/config.toml index 0f5db367..11dba282 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,5 @@ # `cargo play` — the run loop. Same optimization as `--release`, plus incremental compilation (see # `[profile.play]` in the workspace Cargo.toml for why it is a separate profile and not a flip of -# `release`). Env vars still prefix it: `WOW_USER=one WOW_PASS=pone cargo play`. +# `release`). Env vars still prefix it: `WOW_USER= WOW_PASS= cargo play`. [alias] play = "run --profile play -p benilla" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3a23b396..368962f6 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,7 @@ +**The issue it fixes** (`Fixes #N`, if there is one) + **What this changes** **What 1.12.1 does, and how you know** (the client's behaviour, a DBC field, a FrameXML line, a capture) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..064e41af --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +# The gates of scripts/gates.sh that need neither a WoW install nor a display, on Linux, for a pull +# request from a fork: a red one is seen before anyone builds it. The data-gated tests, the engine +# boot checks and a live run happen on the maintainer's machine before it lands. +name: CI + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + gates: + # A branch of this repository runs the full chain before it lands; a fork's pull request runs here. + if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name != github.repository + runs-on: ubuntu-latest + timeout-minutes: 150 + env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + # No debuginfo: it is most of target/, and a runner's disk is small. + CARGO_PROFILE_DEV_DEBUG: 0 + # Set and empty: no install, as the gates' test-no-install runs. + WOW_DATA: "" + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Free disk space + run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL + - name: System libraries + run: sudo apt-get update -qq && sudo apt-get install -y -qq pkg-config libasound2-dev libudev-dev + - name: Toolchain + run: rustup toolchain install && rustc -V + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + - name: fmt + run: cargo fmt --all -- --check + - name: clippy + run: cargo clippy --workspace --all-targets -- -D warnings + - name: test + run: cargo test --workspace + - name: doc-links + 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/README.md b/README.md index 3c1cf784..9773abf6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

benilla

-

A from-scratch World of Warcraft 1.12.1 client in Rust and Bevy

+

A complete World of Warcraft 1.12.1 client, written from scratch in Rust and Bevy

Discord YouTube devlog @@ -8,73 +8,87 @@

-benilla speaks the original 1.12.1 protocol, so it connects to any server the real client could, -and reads its game data at runtime from your own 1.12.1 install. Every file format and the network -protocol are implemented from scratch, with no original client code, no third-party WoW crates, -and no bundled game assets. +benilla plays the whole game: character creation, questing and professions, dungeons and raids, +battlegrounds and honor, groups, guilds, trade, mail and the auction house, on the stock 1.12 +interface and with your addons. It connects to a 1.12.1 server over the original protocol and +reads the game's data from your own 1.12.1 install. Every file format, the network protocol and +the interface engine are written from scratch, with no original client code, no third-party WoW +crates and no bundled game assets. -## What works +## What's inside -- **Formats:** readers for the full asset stack (MPQ patch chain, BLP, DBC, ADT/WDT/WDL, M2, WMO), - wired into Bevy as an asset source. -- **World:** streamed terrain out to the horizon, portal-culled WMOs with interior lighting, - doodads and ground clutter, swimmable liquids, sky and weather, and the client's own day/night - lighting, fog and gamma passes. -- **Models:** GPU-skinned M2s with the full animation controller, a near feature-complete particle - system, ribbons, and animated gameobjects from doors to lifts. -- **Characters:** customization end to end, the armor texture composite, weapons with sheathing and - enchant glows, shapeshift forms, stealth and mounts. -- **Movement:** a WoW-feel controller, networked movement in both directions, the server-granted - modes from slow fall to roots, a follow camera with collision, boats, zeppelins and taxi flights. -- **Networking:** SRP6 auth through world-session crypto, the object mirror into the ECS, and live - wire coverage from movement and chat through spells, party, quests, mail, trade, vendors, bank, - loot, the auction house, PvP honor and battlegrounds. -- **UI:** a from-scratch FrameXML + Lua engine that runs the client's own stock interface off - your install's patch chain, from the login and character screens through the full HUD, the - classic windows (guild, macros and key bindings included), chat, nameplates, floating combat - text and tooltips; third-party addons load from a `benilla-config/AddOns/` folder beside the - executable (partial: AtlasLoot and Bagnon run). -- **Combat:** melee on the faithful swing law, ranged and Auto Shot, casting with GCD and - cooldowns, combo points, crowd control that really holds you, and the spell visual pipeline. -- **Audio:** music, ambience and SFX under the client's own selection and crossfade rules, with - interior and underwater transitions and zone reverb. +- **Formats:** readers for the whole asset stack (the MPQ patch chain, BLP, DBC, ADT, WDT, WDL, M2 + and WMO), wired into Bevy as an asset source. +- **World:** terrain streamed out to the horizon, portal-culled buildings with interior lighting, + doodads and ground clutter, swimmable water, sky and weather, and the client's own day/night + lighting, fog and gamma. +- **Models:** GPU-skinned M2s with the full animation controller, particles, ribbons and the spell + visuals, and characters end to end: customization, the armor composite, weapons with their + enchant glows, forms, stealth and mounts. +- **Movement:** networked movement in both directions, the server-granted modes from slow fall to + roots, a follow camera with collision, boats, zeppelins and taxi flights. +- **Networking:** SRP6 auth through world-session crypto and the object mirror into the ECS, + covering the game from movement and chat through combat, spells, groups and raids, quests, + trade, mail, the auction house and battlegrounds. +- **Interface:** a FrameXML and Lua engine that runs the stock 1.12 interface off your install's + patch chain, and third-party 1.12 addons: Questie, pfUI, Bagnon, Bartender2 and most others + run. By choice, the options window and the ESC menu follow the Classic Era client's rather than + 1.12's. +- **Audio:** music, ambience and effects under the client's own selection and crossfade rules, + with interior and underwater transitions and zone reverb. -## Where it's going +The format readers and the UI engine core are plain Rust with no Bevy in them, and the world +renderer runs with no game attached. [`docs/MAP.md`](docs/MAP.md) maps every crate and subsystem, +generated from the code. -benilla is done when a 1.12.1 player can do everything here that they could in the original -client, it looks and feels the same, and it runs from a download on Windows, Linux and macOS. -No dates; the order is what is likely, not a promise. +## Status -- The long tail of small features that separates a working client from a finished one. +Complete and fully playable. What is left: + +- The long tail of small features that separates a working client from a finished one, tracked + as [issues](https://github.com/samwhosung/benilla/issues). - Addons, options and performance, ongoing. -- Playable downloads for Windows, Linux and macOS. Linux first. + +benilla is a faithful 1.12.1 client and the foundation people build on. There are no prebuilt +downloads: a packaged build, a particular server's changes or anything 1.12.1 never had belongs +in a fork, and forks are welcome. GitHub lists +[every public fork](https://github.com/samwhosung/benilla/forks). Not planned: other expansions or client versions, Warden (anticheat). ## Running it -You need a **1.12.1 (build 5875) client install** for game data, a vanilla server to connect to, -stable Rust and a C compiler (the Lua is built from source; on macOS the Xcode command line -tools, on Linux the ALSA and udev development packages). Any 1.12.1 core works; -[vmangos](https://github.com/vmangos/core) is what development runs against, and cMaNGOS and -the rest speak the same protocol. +benilla builds and runs on macOS, Linux and Windows. You need: + +- **An English 1.12.1 client (build 5875)** for the game data. benilla only reads it. +- **A 1.12.1 server with Warden off.** [vmangos](https://github.com/vmangos/core) is what + development runs against, and it ships with Warden off; cMaNGOS and the other 1.12.1 cores speak + the same protocol. +- **Stable Rust and a C compiler**, because the client's Lua is built from source: on macOS the + Xcode command line tools, on Linux the ALSA and udev development packages and pkg-config, on + Windows the MSVC build tools that the Rust installer sets up. ```sh WOW_DATA=/path/to/WoW/Data cargo run --release -p benilla ``` -The server defaults to `localhost:3724`, the stock `realmd` auth port. Point `WOW_HOST` -at any IP or hostname, appending the auth port if yours is remapped -(`WOW_HOST=play.example.com:5000`). Credentials go in at the login screen, or set `WOW_USER` / -`WOW_PASS` to skip it. +`WOW_DATA` names the install's `Data` folder; a link to the install named `WoW` at the repo root +does the same. The server defaults to `localhost:3724`, the stock auth port. Point `WOW_HOST` at +another (`WOW_HOST=play.example.com`, or `play.example.com:5000` for a remapped port), or set it +from the Realmlist button on the login screen, which remembers it. Credentials go in at the login +screen, or set `WOW_USER` and `WOW_PASS` to skip it. + +Settings, screenshots and addons live in `benilla-config/` at the repo root: a 1.12 addon goes in +`benilla-config/AddOns/`. [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md) has the rest, from the +player build to the tests. Optional, locally built skybox and colour-grading data: [`Optional/sky-and-grading/`](Optional/sky-and-grading/README.md). ## Contributing -Issues and pull requests are open. [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md) says what gets -in and how a change is judged. Bugs, questions and ideas are welcome on the -[Discord](https://discord.gg/wJSJx467G4) too. +Issues and pull requests are open. [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md) says where to +start, what gets in, how a change is judged and what happens to a pull request once it is open. +Bugs, questions and ideas are welcome on the [Discord](https://discord.gg/wJSJx467G4) too. --- @@ -83,13 +97,13 @@ Early inspiration and file format guidance came from the [warcraft-rs](https://github.com/wowemulation-dev/warcraft-rs) in particular. benilla is an independent fan project, not affiliated with or endorsed by Blizzard Entertainment. -It ships **no Blizzard content** — no art, models, sounds, maps, MPQ contents or FrameXML; you -provide your own legally obtained 1.12.1 client. The stock interface runs off your own install's -FrameXML at runtime; the handful of files under `crates/benilla-app/assets/ui/` are our own -adapters and developer frames, not copies of it. +It ships **no Blizzard content**: no art, models, sounds, maps, MPQ contents or FrameXML. You +provide your own legally obtained 1.12.1 client, and the stock interface runs off its FrameXML at +runtime. The few files under `crates/benilla-app/assets/ui/` are our own, not copies of it: +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 — +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 (WarcraftXL, by iThorgrim) are credited file by file in [`THIRD-PARTY.md`](THIRD-PARTY.md). diff --git a/crates/benilla-app/Cargo.toml b/crates/benilla-app/Cargo.toml index e61c481a..c6b65cd4 100644 --- a/crates/benilla-app/Cargo.toml +++ b/crates/benilla-app/Cargo.toml @@ -9,40 +9,34 @@ description = "The Bevy app as a library: everything but the build-id stamp, whi workspace = true [features] -# `dev` compiles in the instruments: the debug panel, perf HUD, inspector, capture harness and -# probes. Nothing may depend on it; `scripts/gates.sh` builds `benilla` with no default features. +# `dev` compiles in the instruments; the player build (`scripts/gates.sh`) has no defaults. default = ["dev"] -# `benilla-assets`, `-formats` and `-world` come in without default features and their `dev` is -# forwarded here: it is on by default in each, so unification would put it back in a player build. +# 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"] tracy = ["bevy/trace_tracy"] -# `--features trace_chrome` writes every system's per-run span to `trace-.json` in the cwd. +# Writes every system's per-run span to `trace-.json` in the cwd. trace_chrome = ["bevy/trace_chrome"] [dependencies] anyhow.workspace = true -# The EditBox clipboard chords, read on the main thread as macOS NSPasteboard requires; Wayland -# goes through `smithay-clipboard`. The 3.x bevy_egui uses, so the graph has one copy. +# The EditBox clipboard, on the main thread as NSPasteboard requires; bevy_egui's 3.x. arboard = "3" benilla-ui = { path = "../benilla-ui" } bevy.workspace = true wgpu.workspace = true bevy_egui = { version = "0.39", optional = true } bytemuck = { version = "1", features = ["derive"] } -# `f16` decode for the `WOW_BOOTH_DUMP` dump of the `Rgba16Float` booth render targets. +# `f16` decode for the `WOW_BOOTH_DUMP` dump of the `Rgba16Float` booth targets. half = "2" # Compiles `assets/ui` into the binary, so the interface loads on a machine without the source. include_dir = "0.7" -# The 1.12 chat filters (`text_filter`): the DBCs' 2289 profanity and 28 spam patterns, with one -# `RegexSet` for the any-match pass over every chat line. +# The 1.12 chat filters (`text_filter`), with one `RegexSet` for the any-match pass per line. regex = "1" -# `getrusage` for the perf probes' process CPU time; `mach_absolute_time` and `thread_policy_set` -# for the audio output's meter clock and its render thread's realtime policy. +# `getrusage` for perf CPU time; the audio clock and render thread's realtime policy on macOS. libc = "0.2" -# PNG encoding only: the macOS hardware cursor's NSImage data and screenshots. +# PNG encoding only: the macOS cursor's NSImage data and screenshots. image = { version = "0.25", default-features = false, features = ["png"] } -# Thread-to-ECS channels such as the net bridge's: crossbeam's `Receiver` is `Sync`, so either end -# can be a Bevy resource without a mutex. +# Thread-to-ECS channels: crossbeam's `Receiver` is `Sync`, so it is a resource without a mutex. crossbeam-channel = "0.5" benilla-assets = { path = "../benilla-assets", default-features = false } benilla-formats = { path = "../benilla-formats", default-features = false } @@ -55,33 +49,29 @@ toml = "0.8" avian3d = "0.6" # Shapes and rasterizes `FontString` text standalone (`ui_text`), at bevy_text's own 0.16. cosmic-text = "0.16" -# `cosmic-text`'s font database, whose types `ui_text` names; must stay the version it depends on. +# `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"] } # Aborts a debug build on an allocation in the audio IO callback or render pass; off in release. assert_no_alloc = "1.1" -# For `MediaSource`: kira's decode thread reads through our source, which raises the thread's QoS -# so the world-entry burst cannot starve it. Must stay kira's version, or the trait won't match. +# `MediaSource`: our source raises kira's decode thread's QoS; must stay kira's version. 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" -# The gossip greeting's weighted draw, re-rolled per open as in the reference; `benilla-srp`'s 0.8. +# The gossip greeting's weighted draw, re-rolled per open as in the reference. rand = "0.8" -# Linux/BSD Wayland clipboard: smithay-clipboard drives the standard `wl_data_device` on our own -# `wl_display`, read through raw-window-handle (bevy's 0.6). arboard's Wayland feature speaks -# `wlr-data-control`, which GNOME does not implement. +# Wayland clipboard over the standard `wl_data_device`; arboard's speaks `wlr-data-control`, +# which GNOME does not implement. [target.'cfg(all(unix, not(any(target_os = "macos", target_os = "android"))))'.dependencies] raw-window-handle = "0.6" smithay-clipboard = "0.7" -# macOS: AppKit through objc2 drives a native NSCursor with the window's cursor rects disabled; -# winit's cursor rects revert to the arrow under our continuous render loop (`cursor.rs`). +# 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`): the HAL output unit through coreaudio-rs, -# the AudioObject property API through objc2-core-audio, at the versions and features cpal uses. +# 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"] } @@ -89,7 +79,6 @@ objc2 = "0.5" objc2-app-kit = { version = "0.2", features = [ "NSApplication", "NSResponder", - # NSApplicationActivationPolicy (bgwin's Accessory demotion) is declared in this header. "NSRunningApplication", "NSWindow", "NSView", @@ -99,12 +88,11 @@ objc2-app-kit = { version = "0.2", features = [ ] } objc2-foundation = { version = "0.2", features = ["NSArray", "NSData", "NSGeometry"] } -# Off macOS the device layer's last hop runs over cpal (`sound::output::cpal`). macOS goes direct: -# there cpal cannot report or rebuild on a lost default device and drops the IO-cycle timestamp. -# An unsupported target gets cpal's Null host, which reports no device. +# 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`: `GetProcessTimes`, `GetThreadTimes`, `GetSystemTimes`. +# Windows CPU clocks for `perf::clock`. [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Threading"] } diff --git a/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml b/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml index d6f168ae..26466581 100644 --- a/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml +++ b/crates/benilla-app/assets/ui/ContainerFrameAdapters.xml @@ -1,19 +1,13 @@ - + - + - + @@ -115,8 +97,7 @@ - + @@ -126,9 +107,7 @@ - + - + - + - -- Deviation: ERA_WINDOW_SCALE (OptionsFrame.xml, loaded earlier) stands in for the - -- UIParent scale the era draws this menu and its options window at. - -- A second OnShow on this frame would replace this one: the last SetScript wins. + -- 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 diff --git a/crates/benilla-app/assets/ui/KeyBindingsPage.xml b/crates/benilla-app/assets/ui/KeyBindingsPage.xml index 2c316954..a89fa332 100644 --- a/crates/benilla-app/assets/ui/KeyBindingsPage.xml +++ b/crates/benilla-app/assets/ui/KeyBindingsPage.xml @@ -1,24 +1,16 @@ - + - + @@ -91,10 +81,8 @@ return fallback end - -- ── Key display ── 1.12's GetBindingText in full form, as the reference shows it here and - -- in tooltips (the action bar's abbreviated form is the stock GetBindingText): modifier - -- prefixes stay uppercase tokens, the base key reads its KEY_* global, else itself. - -- "CTRL--" is CTRL plus the minus key. + -- ── Key display ── 1.12's GetBindingText in full form: modifier prefixes stay tokens, the + -- base key reads its KEY_* global. "CTRL--" is CTRL plus the minus key. function KeyBindings_KeyText(key) if not key then return NORMAL_FONT_COLOR_CODE .. KeyBindings_String("NOT_BOUND", "Not Bound") .. FONT_COLOR_CODE_CLOSE @@ -119,9 +107,7 @@ return prefix .. KeyBindings_String("KEY_" .. rest, rest) end - -- ── Page state ── `sections` is the era section model, `selected`/`keyID` the 1.12 - -- selection, and `searchPainted` maps OptionsFrame.xml's live search rows (row name to - -- binding index). + -- ── Page state ── `searchPainted` maps OptionsFrame.xml's search rows to binding indices. KeyBindingsPage = { sections = {}, items = {} } KEYBINDINGS_ROW_BASE = "BenillaOptionsFrameContainerBodyKeybindingsRow" @@ -129,9 +115,8 @@ KEYBINDINGS_ROW_HEIGHT = 25 KEYBINDINGS_SEARCH_ROWS = 8 - -- ── Section model ── the era's CreateKeybindingInitializers: one section per category - -- token in first-appearance order, which is 1.12's Bindings.xml order. Sections start - -- collapsed (the era's Classic overrides pass no `expanded`); a rebuild keeps each state. + -- ── Section model ── one section per category in Bindings.xml order, collapsed at first + -- as in the era; a rebuild keeps each state. function KeyBindings_BuildSections() local page = KeyBindingsPage local was = {} @@ -153,8 +138,7 @@ KeyBindings_Flatten() end - -- The visible list the faux scroll runs over (the era's EvaluateVisibility): each header, - -- then its rows when expanded. + -- The visible list the faux scroll runs over. function KeyBindings_Flatten() local page = KeyBindingsPage page.items = {} @@ -168,7 +152,7 @@ end end - -- A header click (the era's OnExpandedChanged), voiced with the checkbox sound. + -- A header click, voiced with the checkbox sound. function KeyBindings_ToggleSection(n) local sec = KeyBindingsPage.sections[n] if not sec then return end @@ -178,7 +162,7 @@ KeyBindingsPage_Update() end - -- Expands or collapses a section silently; the capture fixture opens sections with it. + -- Silent, for the capture fixture. function KeyBindings_ExpandSection(n, on) local sec = KeyBindingsPage.sections[n] if not sec then return end @@ -186,8 +170,7 @@ KeyBindings_Flatten() end - -- ── Painters ── one row in one mode, shared by the list and the search rows, whose - -- capsules capture in place as the era's live results do. + -- ── Painters ── shared by the list and the search rows, which capture in place. function KeyBindings_PaintBindRow(name, index) local command, _, key1, key2 = GetBinding(index) local b1 = getglobal(name .. "Key1Button") @@ -234,7 +217,7 @@ getglobal(name .. "Key2Button"):Hide() end - -- ── The list repaint (1.12 KeyBindingFrame_Update over the era section list). ── + -- ── The list repaint (1.12 KeyBindingFrame_Update) ── function KeyBindingsPage_Update() local page = KeyBindingsPage local scroll = getglobal("BenillaOptionsFrameContainerBodyKeybindingsScrollFrame") @@ -255,14 +238,12 @@ end FauxScrollFrame_Update(scroll, table.getn(page.items), KEYBINDINGS_DISPLAYED, KEYBINDINGS_ROW_HEIGHT) - -- The trough is ours and the stock kit never shows one, so it follows the bar - -- `FauxScrollFrame_Update` has just shown or hidden. + -- Our trough follows the bar `FauxScrollFrame_Update` has just shown or hidden. BenillaScrollTrough_Follow(scroll:GetName()) KeyBindings_UpdateUnbindKey() end - -- The page entry (BenillaOptionsFrame_SelectCategory): rebuilds the sections, since a - -- rebind or a set switch may have changed the table, then repaints from the top. + -- The page entry: a rebind or a set switch may have changed the table. function KeyBindingsPage_Refresh() KeyBindings_BuildSections() local box = getglobal("BenillaOptionsFrameContainerBodyKeybindingsCharacterRowCheck") @@ -279,9 +260,7 @@ getglobal("BenillaOptionsFrameContainerBodyKeybindingsOutput"):SetText(text) end - -- ── Selection ── selecting a capsule arms the host's capture (BenillaBindCapture), which - -- swallows raw input and hands the canonical chord to KeyBindings_OnHostKey; deselecting - -- disarms. + -- ── Selection ── arms the host's capture, which hands its chord to KeyBindings_OnHostKey. function KeyBindings_SetSelected(command, keyID) KeyBindingsPage.selected = command KeyBindingsPage.keyID = keyID @@ -297,8 +276,7 @@ end end - -- Repaints whichever view shows binding rows, the list or every search row, since a bind - -- from one search row can unbind a victim shown on another. + -- Every search row: a bind from one can unbind a victim shown on another. function KeyBindings_RepaintVisible() if KeyBindingsPage.searchPainted then for name, index in pairs(KeyBindingsPage.searchPainted) do @@ -311,9 +289,7 @@ end function KeyBindingButton_OnClick(button) - -- As 1.12: while armed, only a left or right click reselects or deselects. A middle, - -- 4 or 5 press is a bind that the armed capture takes after this click, so it must - -- leave the selection alone. + -- As 1.12: while armed, a middle, 4 or 5 press is a bind the capture takes after this. local page = KeyBindingsPage if page.selected then if button ~= "LeftButton" and button ~= "RightButton" then @@ -337,14 +313,11 @@ KeyBindings_RepaintVisible() end - -- The live commit: saves the set a bind, unbind or reset has just changed. function KeyBindings_Commit() SaveBindings(GetCurrentBindingSet()) end - -- The host's capture callback, `chord` canonical (ALT-CTRL-SHIFT-TOKEN): 1.12's OnKeyDown - -- armed branch, then the live commit. A stolen chord names its victim in red only when it - -- was the victim's last key; the command's keys rebind in slot order. + -- `chord` is ALT-CTRL-SHIFT-TOKEN; 1.12's OnKeyDown armed branch, then the live commit. function KeyBindings_OnHostKey(chord) local page = KeyBindingsPage if not page.selected then @@ -379,9 +352,7 @@ KeyBindings_RepaintVisible() end - -- 1.12's KeyBindingFrame_SetBinding: a refused bind restores the slot's old key. SetBinding - -- refuses only a string that is not a key, so this fires for a chord the capture could not - -- name, never for the wheel; the message stays 1.12's wheel text. + -- 1.12's KeyBindingFrame_SetBinding, message and all; SetBinding refuses only a non-key. function KeyBindings_SetOrRestore(key, command, oldKey) if not SetBinding(key, command) then if oldKey then SetBinding(oldKey, command) end @@ -391,8 +362,7 @@ end end - -- The Unbind Key button (OptionsFrame.xml): 1.12's body, which empties the selected slot - -- and slides the other into slot order, then the live commit. + -- 1.12's Unbind Key body, then the live commit. function KeyBindings_Unbind() local page = KeyBindingsPage local key1, key2 = GetBindingKey(page.selected) @@ -409,12 +379,9 @@ KeyBindings_RepaintVisible() end - -- ── Character-specific set ── 1.12's binding sets under the era's checkbox. Checking - -- saves set 2 at once, a copy of the current binds (LoadBindings(2) falls back to the - -- account set), and that saved set is what the checkbox reads. Unchecking deletes it, so - -- it confirms (the era confirms only on uncheck) and the box stays checked meanwhile. - -- The accept loads set 1 before saving it; saving first would write the character binds - -- into the account set. + -- ── Character-specific set ── checking saves set 2 at once (LoadBindings(2) falls back + -- to the account set); unchecking confirms first, as the era does. The accept loads set 1 + -- before saving it, or the character binds would be written into the account set. function KeyBindings_CharacterToggle(box) PlaySound("igMainMenuOptionCheckBoxOn") if box:GetChecked() then @@ -442,8 +409,7 @@ KeyBindingsPage_Refresh() end, } - -- Reset To Default, the page's Defaults button: it confirms, as the era's does, with our - -- own string since 1.12 has none, and resets the current set with a live commit. + -- Reset To Default confirms, as the era's does; our string, since 1.12 has none. StaticPopupDialogs["CONFIRM_RESET_KEYBINDINGS"] = { text = "Are you sure you want to reset all keybindings to their defaults?", button1 = KeyBindings_String("OKAY", "Okay"), @@ -458,11 +424,8 @@ end, } - -- ── Search ── a binding's tag is its display name uppercased (the era's - -- AddSearchTags(GetBindingName(action))); matches list under the Keybindings group head, - -- which opens this page (the era's `redirectCategory`, Blizzard_SettingsPanel.lua l.578). - -- OptionsFrame.xml paints them as live rows. Deviation: a static pool of - -- KEYBINDINGS_SEARCH_ROWS rows, where the era's results scroll. + -- ── Search ── a binding's tag is its display name uppercased, as the era's. + -- A static pool of KEYBINDINGS_SEARCH_ROWS rows, where the era's results scroll. function KeyBindings_SearchMatches(words) local out = {} for i = 1, GetNumBindings() do diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml index dc95de76..d02cfa4d 100644 --- a/crates/benilla-app/assets/ui/OptionsFrame.xml +++ b/crates/benilla-app/assets/ui/OptionsFrame.xml @@ -1,18 +1,13 @@ - + @@ -31,17 +26,14 @@ - + - + - + @@ -1816,9 +1722,7 @@ - + - + @@ -1869,8 +1771,8 @@ - + @@ -1897,11 +1799,8 @@ - + @@ -1924,8 +1823,7 @@ - + @@ -1979,9 +1877,7 @@ - + @@ -2021,13 +1917,12 @@ - + - + @@ -2035,9 +1930,8 @@ - + @@ -2047,15 +1941,13 @@ - - + @@ -2064,10 +1956,8 @@ - + @@ -2102,10 +1992,8 @@ - + - + - + @@ -2235,7 +2123,7 @@ - + @@ -2243,8 +2131,7 @@ - + @@ -2290,9 +2177,7 @@ - + @@ -2307,28 +2192,23 @@ BenillaScroll_SyncBar(this) - + - + - + - + @@ -2375,9 +2255,9 @@ OptionsRow_OnLoad(self, "BlockTrades", "Block Trades", "OPTION_TOOLTIP_BLOCK_TRADES") - + @@ -2416,10 +2296,8 @@ OptionsSliderRow_Setup(self, 0.5, 1.5, 0.05) - + @@ -2435,7 +2313,6 @@ }) - @@ -2472,9 +2349,9 @@ OptionsSliderRow_Setup(self, 90, 270, 10, "raw") - + @@ -2495,8 +2372,7 @@ "OPTION_TOOLTIP_SMART_PIVOT") - + @@ -2522,9 +2398,7 @@ - + @@ -2558,7 +2432,6 @@ OptionsRow_OnLoad(self, "SoundVolume", "Sound Volume", "OPTION_TOOLTIP_SOUND_VOLUME", 1) - -- Era parentInitializer (SettingsPanel.lua l.665). self.parentRow = "RowMaster" @@ -2606,7 +2479,7 @@ self.soundQuirk = 1 - + @@ -2618,9 +2491,8 @@ self.soundQuirk = 1 - + @@ -2635,9 +2507,9 @@ - + @@ -2655,8 +2527,7 @@ OptionsSliderRow_Setup(self, OptionsFrameSliders[1].minValue, OptionsFrameSliders[1].maxValue, OptionsFrameSliders[1].valueStep) - -- Era CommitFlag.Apply (Graphics.lua l.684): a - -- live write would rescale the slider mid-drag. + -- A live write would rescale the slider mid-drag. self.deferred = 1 @@ -2688,16 +2559,16 @@ { "Low", "Medium", "High" }) - + - -- OptionsFrame_SetDefaults' default (l.441-445); + -- OptionsFrame_SetDefaults' default (l.444-445); -- `or 1` covers a VM with no CVars. local gd = tonumber(GetCVarDefault("gamma")) or 1 OptionsNumFuncRow_OnLoad(self, GetGamma, SetGamma, @@ -2708,9 +2579,8 @@ OptionsFrameSliders[6].valueStep, "travel") - + @@ -2719,9 +2589,8 @@ OptionsRow_OnLoad(self, "weatherDensity", "Weather Intensity", "OPTION_TOOLTIP_WEATHER_DETAIL") - -- Our stop names for the reference's rates: - -- `0x67b870` maps 0 to 3 onto the spawn-rate gains - -- {0.1, 0.33, 0.66, 1.0} in `[0x8680ec]`. + -- Our names for `0x67b870`'s spawn-rate gains + -- `{0.1, 0.33, 0.66, 1.0}` (`[0x8680ec]`). OptionsSliderRow_Setup(self, OptionsFrameSliders[9].minValue, OptionsFrameSliders[9].maxValue, OptionsFrameSliders[9].valueStep, nil, @@ -2735,10 +2604,8 @@ -- Deviation: modern Classic's two-entry Display - -- Mode dropdown (Graphics.lua in - -- Blizzard_SettingsDefinitions_Shared), as there - -- is no exclusive fullscreen; gxWindow "0" is the - -- borderless window. Applies on the click. + -- Mode, as there is no exclusive fullscreen; + -- gxWindow "0" is the borderless window. OptionsRow_OnLoad(self, "gxWindow", "Display Mode", "BENILLA_TOOLTIP_DISPLAY_MODE") OptionsDropdownRow_Setup(self, { @@ -2753,9 +2620,8 @@ - -- 1.12's Video Options checkbox 5. Not deferred - -- despite its gxRestart = 1: the present mode - -- changes live. + -- Not deferred despite gxRestart: vsync changes + -- live. OptionsRow_OnLoad(self, "gxVSync", "Vertical Sync", "OPTION_TOOLTIP_VERTICAL_SYNC") @@ -2773,11 +2639,10 @@ - -- Deviation: benilla's own row (the reference draws - -- to the backbuffer), and no Multisampling row, - -- which misses alpha-tested cutouts. Deferred: a - -- live write rebuilds the world render target on - -- every drag tick. + -- Deviation: our own row (the reference draws to + -- the backbuffer), and no Multisampling, which + -- misses alpha-tested cutouts. Deferred: a write + -- rebuilds the render target. OptionsRow_OnLoad(self, "renderScale", "Render Scale", "BENILLA_TOOLTIP_RENDER_SCALE") OptionsSliderRow_Setup(self, 0.5, 2.0, 0.05) @@ -2785,9 +2650,8 @@ - + for i, name in BENILLA_GRAPHICS_SLIDER_ROWS do @@ -3357,8 +3221,7 @@ AdvGfx_OnUpdate(this, arg1) - + @@ -3550,8 +3413,8 @@ + from SHOW_COMBAT_TEXT; at "0" neither `0x607140` nor + `0x6128b0` floats anything. --> @@ -3562,8 +3425,8 @@ "OPTION_TOOLTIP_SHOW_DAMAGE") - + @@ -3590,11 +3453,9 @@ - + @@ -3661,8 +3522,7 @@ "0", "Show Right ActionBar", "OPTION_TOOLTIP_SHOW_MULTIBAR3") - + @@ -3691,8 +3551,7 @@ OptionsGlobalRow_OnLoad(self, "ALWAYS_SHOW_MULTIBARS", "Always Show ActionBars", "OPTION_TOOLTIP_ALWAYS_SHOW_MULTIBARS") - -- The click arm (UIOptionsFrame.xml:683); the load - -- arm is the stock VARIABLES_LOADED one. + -- UIOptionsFrame.xml:680-683's click arm. self.applyFunc = "MultiActionBar_UpdateGridVisibility" @@ -3708,17 +3567,15 @@ - + - + @@ -3738,9 +3595,8 @@ self.cvarEvent = "STATUS_BAR_TEXT" - + @@ -3752,8 +3608,8 @@ self.applyFunc = "TargetofTarget_Update" - + @@ -3772,8 +3628,8 @@ self.applyFunc = "TargetofTarget_Update" - + @@ -3826,8 +3682,7 @@ "Automatic Quest Tracking", "OPTION_TOOLTIP_AUTO_QUEST_WATCH") - + @@ -3836,23 +3691,22 @@ OptionsRow_OnLoad(self, "profanityFilter", "Profanity Filter", "OPTION_TOOLTIP_PROFANITY_FILTER") - + - + OptionsFuncRow_OnLoad(self, TutorialsEnabled, BenillaOptionsFrame_SetTutorialsEnabled, "1", "Show Tutorials", "OPTION_TOOLTIP_SHOW_TUTORIALS") - + @@ -3874,9 +3728,8 @@ - + @@ -3889,7 +3742,7 @@ - + OptionsRow_OnLoad(self, "guildMemberNotify", "Guild Member Alert", "OPTION_TOOLTIP_GUILDMEMBER_ALERT") @@ -3941,9 +3794,9 @@ "Show Party Chat Bubbles", "OPTION_TOOLTIP_PARTY_CHAT_BUBBLES") - @@ -3958,10 +3811,9 @@ - + @@ -3992,8 +3844,7 @@ OptionsRow_OnLoad(self, "UnitNamePlayer", "Player Names", "OPTION_TOOLTIP_SHOW_PLAYER_NAMES") - + @@ -4023,9 +3874,8 @@ - + @@ -4039,8 +3889,7 @@ - + @@ -4197,12 +4046,11 @@ - + - + @@ -4210,13 +4058,11 @@ ScrollFrame_OnLoad() - + FauxScrollFrame_OnVerticalScroll(KEYBINDINGS_ROW_HEIGHT, KeyBindingsPage_Update) - + if not KeyBindingsPage.selected then ScrollFrameTemplate_OnMouseWheel(arg1) @@ -4225,8 +4071,7 @@ - + if not KeyBindingsPage.selected then @@ -4235,8 +4080,7 @@ - + - + @@ -4311,9 +4154,8 @@ - +