From 8549f14dd6c79b748884f4a20d80ec1dc5e82034 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 5 Aug 2026 19:05:51 +0700 Subject: [PATCH] footprints on snow and sand, and the drunk effect staggers in - drink enough and the screen blurs and your walk meanders, the camera carried along with the stagger; the underwater blur ships in the same lane - boots leave prints on snow and sand that fade away in about six seconds, paws and hooves included - key bindings move into the Options window with the rest of the settings, and action buttons abbreviate their hotkeys the way the client does - the pet bar rearranges by drag, a running pet spell shows its active icon and a click cancels it, and spell ribbons follow their verified physics Co-Authored-By: Claude Fable 5 --- .../benilla-app/assets/shaders/ffx_glow.wgsl | 15 +- crates/benilla-app/assets/ui/ActionBar.xml | 76 +- .../benilla-app/assets/ui/GameMenuFrame.xml | 40 +- .../assets/ui/KeyBindingsFrame.xml | 798 ------------------ .../benilla-app/assets/ui/KeyBindingsPage.xml | 544 ++++++++++++ crates/benilla-app/assets/ui/MultiBars.xml | 78 +- crates/benilla-app/assets/ui/OptionsFrame.xml | 392 ++++++++- crates/benilla-app/assets/ui/PetActionBar.xml | 67 +- crates/benilla-app/assets/ui/UIParent.xml | 67 ++ crates/benilla-app/assets/ui/UiPanels.xml | 23 +- crates/benilla-app/assets/ui/UnitFrames.xml | 105 ++- crates/benilla-app/assets/ui/UnitPopup.xml | 6 +- crates/benilla-app/src/bindings.rs | 15 +- crates/benilla-app/src/bindings/commands.rs | 252 +++++- crates/benilla-app/src/blob_shadow.rs | 3 +- crates/benilla-app/src/capture/fixtures.rs | 16 +- crates/benilla-app/src/capture/mod.rs | 40 +- crates/benilla-app/src/capture/scenarios.rs | 2 +- crates/benilla-app/src/cursor.rs | 1 + crates/benilla-app/src/entities.rs | 29 + crates/benilla-app/src/entities/attach/mod.rs | 12 +- .../src/entities/equipment/spawn.rs | 4 +- .../benilla-app/src/entities/spell_fx/mod.rs | 61 +- crates/benilla-app/src/entities/wmo_props.rs | 2 +- crates/benilla-app/src/ffx_glow.rs | 75 +- crates/benilla-app/src/footprints.rs | 421 +++++++++ crates/benilla-app/src/ground_fx.rs | 16 +- crates/benilla-app/src/lib.rs | 10 + crates/benilla-app/src/net.rs | 8 + crates/benilla-app/src/net/apply/objects.rs | 2 +- crates/benilla-app/src/net/io.rs | 3 + crates/benilla-app/src/player.rs | 56 +- crates/benilla-app/src/player/camera.rs | 3 +- crates/benilla-app/src/player/drunk.rs | 125 +++ crates/benilla-app/src/portrait/glue_booth.rs | 2 +- crates/benilla-app/src/portrait/mod.rs | 4 +- crates/benilla-app/src/ribbons.rs | 252 +++++- crates/benilla-app/src/sound/footsteps.rs | 4 +- crates/benilla-app/src/sound/mod.rs | 2 +- crates/benilla-app/src/sound/ui.rs | 8 +- .../src/terrain_stream/spawn/fx.rs | 12 +- .../src/terrain_stream/spawn/mod.rs | 2 + crates/benilla-app/src/ui_pet.rs | 489 +++++++++-- crates/benilla-app/src/ui_pet_stats.rs | 351 ++++++++ .../benilla-app/src/ui_script/escape_tests.rs | 2 + .../benilla-app/src/ui_script/extract/mod.rs | 1 + .../src/ui_script/game_menu_tests.rs | 31 +- .../src/ui_script/keybindings_tests.rs | 518 ++++++++---- crates/benilla-app/src/ui_script/mod.rs | 14 +- .../src/ui_script/options_tests.rs | 2 + .../src/ui_script/pet_bar_tests.rs | 109 ++- .../src/ui_script/pet_frame_tests.rs | 130 +++ .../src/bin/benilla-extract/scan/geometry.rs | 29 +- .../src/bin/benilla-extract/scan/particles.rs | 55 +- crates/benilla-formats/src/creatures.rs | 82 ++ crates/benilla-formats/src/footsteps.rs | 93 +- crates/benilla-formats/src/lib.rs | 13 +- crates/benilla-formats/src/models/types.rs | 28 +- crates/benilla-formats/src/pet_stats.rs | 340 ++++++++ crates/benilla-formats/src/ribbons.rs | 213 ++++- crates/benilla-formats/tests/ground_quad.rs | 44 + crates/benilla-protocol/src/messages/mod.rs | 12 +- .../benilla-protocol/src/messages/opcode.rs | 4 + crates/benilla-protocol/src/messages/pet.rs | 31 + .../src/messages/update_object/fields/mod.rs | 15 + .../messages/update_object/fields/player.rs | 9 + .../src/messages/update_object/fields/unit.rs | 24 + .../benilla-protocol/src/world/writer/pet.rs | 23 +- crates/benilla-ui/src/script/container.rs | 5 +- crates/benilla-ui/src/script/cursor.rs | 86 +- crates/benilla-ui/src/script/cursor/bar.rs | 18 +- crates/benilla-ui/src/script/cursor/doll.rs | 5 +- crates/benilla-ui/src/script/cursor/pet.rs | 641 ++++++++++++++ crates/benilla-ui/src/script/keybind.rs | 8 +- crates/benilla-ui/src/script/mod.rs | 6 +- crates/benilla-ui/src/script/model.rs | 17 + crates/benilla-ui/src/script/pet.rs | 303 ++++++- 77 files changed, 6013 insertions(+), 1391 deletions(-) delete mode 100644 crates/benilla-app/assets/ui/KeyBindingsFrame.xml create mode 100644 crates/benilla-app/assets/ui/KeyBindingsPage.xml create mode 100644 crates/benilla-app/src/footprints.rs create mode 100644 crates/benilla-app/src/player/drunk.rs create mode 100644 crates/benilla-app/src/ui_pet_stats.rs create mode 100644 crates/benilla-formats/src/pet_stats.rs create mode 100644 crates/benilla-ui/src/script/cursor/pet.rs diff --git a/crates/benilla-app/assets/shaders/ffx_glow.wgsl b/crates/benilla-app/assets/shaders/ffx_glow.wgsl index cf5a5def..96b25cf2 100644 --- a/crates/benilla-app/assets/shaders/ffx_glow.wgsl +++ b/crates/benilla-app/assets/shaders/ffx_glow.wgsl @@ -6,7 +6,8 @@ // out = lerp(screen, blur, z) + w · blur² (FFXGlow.bls, exact) // // `w` is the per-zone LightParams glow weight (authored data — ≈0.647 in Elwynn), `z` is the haze -// mix (0 in the standard glow pass). The reference runs this in GAMMA bytes, and since 0161 the +// mix — the drunk/underwater screen-toward-blur cross-fade (0 sober and dry; wow-re +// drunk-blur-z.md, decision 1009 §A). The reference runs this in GAMMA bytes, and since 0161 the // whole frame composites in gamma too — but in a FLOAT buffer, which unlike the reference's byte // buffer does not saturate at 1.0 per draw. Every read of the scene RT below therefore clamps to // 1.0 first (the byte semantics restored at the read), does the byte math, and decodes once — the @@ -96,17 +97,25 @@ fn fs_combine(in: FullscreenVertexOutput) -> @location(0) vec4 { // hard-white after the min() below — the screen term must saturate BEFORE the glow add. let sg = clamp(scene.rgb, vec3(0.0), vec3(1.0)); let bg = max(textureSample(blur_tex, in_samp, in.uv).rgb, vec3(0.0)); - let glowed = sg + glow.x * bg * bg; // The scene alpha rides through untouched: the window surface ignores it and the opaque-clear // booths hold 1 everywhere — only the transparent-clear create booth reads it, to composite // its baked model straight over the UI page. if glow.y > 0.0 { + // The FFXDeath pack has no z lane (its COLOR rgb is the constant tint) — the ghost + // combine reads the un-hazed screen. + let glowed = sg + glow.x * bg * bg; let luma = clamp(dot(glowed, vec3(0.299, 0.587, 0.144)), 0.0, 1.0); let p = clamp(4.0 * luma * (1.0 - luma), 0.0, 1.0); let ghost_tint = vec3(83.0, 147.0, 168.0) / 255.0; // 0x5393A8 let outg = min(vec3(luma) + ghost_tint * p, vec3(1.0)); return vec4(srgb_to_linear(outg), scene.a); } - let outg = min(glowed, vec3(1.0)); + // The z lane — the drunk/underwater haze: cross-fade the screen toward the ¼-res blur + // BEFORE the glow add (`out = lerp(screen, blur, z) + w·blur²`, the shipped FFXGlow.bls; + // wow-re drunk-blur-z.md). z = max(drunkFraction, 84/255 while the eye is submerged) — 0 in + // the ordinary sober pass, 1 = fully blurred at 100 inebriation. The blur term clamps like + // the screen term: it stands for the same byte RT. + let hazed = mix(sg, min(bg, vec3(1.0)), glow.z); + let outg = min(hazed + glow.x * bg * bg, vec3(1.0)); return vec4(srgb_to_linear(outg), scene.a); } diff --git a/crates/benilla-app/assets/ui/ActionBar.xml b/crates/benilla-app/assets/ui/ActionBar.xml index 721a85b2..0971503e 100644 --- a/crates/benilla-app/assets/ui/ActionBar.xml +++ b/crates/benilla-app/assets/ui/ActionBar.xml @@ -19,9 +19,11 @@ - No frame scale: the real bar is UIParent-scaled; we render 1:1. Geometry is otherwise exact. - Page up/down + the page number are ART ONLY (we have no bar paging — only the bonus-bar offset; ref-ActionBarFrame.xml l.159-217). The page number renders a static "1". - - HotKey renders the static binding labels — always non-empty, so the ref's RANGE_INDICATOR - dot path (shown only when a button has NO binding text, ActionButton_UpdateHotkeys) never - applies; the out-of-range read is the hotkey's red tint (the OnUpdate range timer below). + - HotKey renders the LIVE binding label, abbreviated the ref's own way since 1008 + (GetBindingText(key, "KEY_", 1) — UIParent.xml carries the transcription; "s-2" for + SHIFT-2, "·" for a 2+-modifier chord). A slot with NO binding wears the ref's + RANGE_INDICATOR dot path instead (red dot only while out of range); a bound slot's + out-of-range read is the label's red tint (the OnUpdate range timer below). - The state/feedback layer (decision 0137 phase 4) is the ref ActionButton.lua transcribed: UpdateState (checked on IsCurrentAction/IsAutoRepeatAction), UpdateUsable (the 1/1/1 · 0.5/0.5/1 · 0.4/0.4/0.4 vertex-color trio), UpdateCooldown (CooldownFrame_SetTimer), @@ -60,17 +62,31 @@ -- CooldownFrame_SetTimer moved to Cooldown.xml (the ref's own file split) — the bag -- slots share it, so it loads before both consumers. - -- The key-binding label each main-bar button shows in its HotKey corner — LIVE off - -- GetBindingKey("ACTIONBUTTON"..i) since 0997 (the ref's own ActionButton_UpdateHotkeys - -- source), repainted on UPDATE_BINDINGS. Raw chord tokens for now (the ref abbreviates - -- through its KEY_ABBR set — 0997 residue); an unbound slot shows nothing, and so does - -- a harness with no registered bindings. + -- One button's HotKey corner — the ref's ActionButton_UpdateHotkeys law in FULL since + -- 1008 (closing 0997's raw-token residue): the ABBREVIATED binding text through the + -- real GetBindingText transcription (UIParent.xml — one modifier abbreviates, "s-2"; + -- two or more collapse to the ref's "·"), and with NO binding the ref's no-binding + -- leg — an empty label the OnUpdate range timer fills with the RANGE_INDICATOR dot + -- while out of range (until 1008 that leg was multibar-only; now any unbound slot, + -- main bar included, wears it). A harness with no registered bindings paints nothing. + function BenillaActionButton_PaintHotkey(button) + local key = GetBindingKey(button.bindingCommand) + local text + if GetBindingText then + text = GetBindingText(key, "KEY_", 1) + else + -- lone-file harness (the shipped manifest always has UIParent.xml first): + -- the raw token, the MicroMenu.xml fallback idiom. + text = key or "" + end + button.noBindingLabel = (text == "") + button.hotkey:SetText(text) + end + + -- Every button's label, repainted on UPDATE_BINDINGS (a rebind, a set switch, the seed). function BenillaActionBar_UpdateHotkeys() for i = 1, table.getn(BENILLA_ACTION_BUTTONS) do - local button = BENILLA_ACTION_BUTTONS[i] - if button.hotkeyIndex then - button.hotkey:SetText(GetBindingKey("ACTIONBUTTON" .. button.hotkeyIndex) or "") - end + BenillaActionButton_PaintHotkey(BENILLA_ACTION_BUTTONS[i]) end end @@ -203,7 +219,7 @@ GameTooltip:SetAction(BenillaActionButton_Action(button)) end - function BenillaActionButton_OnLoad(button, index, base) + function BenillaActionButton_OnLoad(button, index, base, commandPrefix) button.index = index button.base = base button.icon = getglobal(button:GetName() .. "Icon") @@ -216,17 +232,12 @@ button.flashing = 0 button.flashtime = 0 button.rangeTimer = nil - if base then - -- Multibar buttons are unbound (ref Bindings.xml ships no default MULTIACTIONBAR* - -- keys): no label. Their hotkey corner instead carries the ref's - -- RANGE_INDICATOR dot (ActionButton_UpdateHotkeys' no-binding leg, net effect: - -- a red dot only while out of range), painted by the OnUpdate range timer. - button.hotkey:SetText("") - else - -- Main-bar buttons read their ACTIONBUTTON binding live (0997). - button.hotkeyIndex = index - button.hotkey:SetText(GetBindingKey("ACTIONBUTTON" .. index) or "") - end + -- Every bar button carries its binding COMMAND (0997 main bar, 1008 multibars): + -- the caller names the command family (MultiBars.xml passes MULTIACTIONBAR1/2BUTTON + -- — the ref's own bar↔page mapping quoted there); nothing passed = the main bar's + -- ACTIONBUTTON. The label paints from it here and repaints on UPDATE_BINDINGS. + button.bindingCommand = (commandPrefix or "ACTIONBUTTON") .. index + BenillaActionButton_PaintHotkey(button) table.insert(BENILLA_ACTION_BUTTONS, button) -- BOTH mouse buttons, ref-verbatim (ActionButton_OnLoad, ActionButton.lua:108-109): -- an action button is a two-button button. The OnClick body reads no `arg1` — in the @@ -353,9 +364,10 @@ BenillaActionButton_UpdateState(button) end - -- Ref ActionButton_OnUpdate:384-428, minus the RANGE_INDICATOR dot (our hotkeys always - -- carry binding text — SCOPE note) and the tooltip refresh (no per-button tooltip yet): - -- the 0.4 s flash toggle, and the 0.2 s range recheck tinting the hotkey red/grey. + -- Ref ActionButton_OnUpdate:384-428, minus the tooltip refresh (no per-button tooltip + -- yet): the 0.4 s flash toggle, and the 0.2 s range recheck — a bound slot's label + -- tints red/grey, an unbound one wears the RANGE_INDICATOR dot (both legs the ref's, + -- unified in 1008 — the SCOPE note). function BenillaActionButton_OnUpdate(button, elapsed) if ( button.flashing == 1 ) then button.flashtime = button.flashtime - elapsed @@ -376,14 +388,16 @@ button.rangeTimer = button.rangeTimer - elapsed if ( button.rangeTimer <= 0 ) then if ( IsActionInRange(BenillaActionButton_Action(button)) == 0 ) then - -- An unbound (multibar) button has no label to tint: paint the ref's - -- RANGE_INDICATOR dot instead (net ref behavior — red dot iff out of range). - if button.base then + -- An UNBOUND button has no label to tint: paint the ref's + -- RANGE_INDICATOR dot instead (net ref behavior — red dot iff out of + -- range). Since 1008 the fork is the binding text itself (any bar's + -- slot can be bound or bare now), not which bar owns the button. + if button.noBindingLabel then button.hotkey:SetText("●") end button.hotkey:SetVertexColor(1.0, 0.1, 0.1) else - if button.base then + if button.noBindingLabel then button.hotkey:SetText("") end button.hotkey:SetVertexColor(0.6, 0.6, 0.6) diff --git a/crates/benilla-app/assets/ui/GameMenuFrame.xml b/crates/benilla-app/assets/ui/GameMenuFrame.xml index 72dd1937..8aefafd2 100644 --- a/crates/benilla-app/assets/ui/GameMenuFrame.xml +++ b/crates/benilla-app/assets/ui/GameMenuFrame.xml @@ -28,7 +28,7 @@ not the gold a normal panel button wears, and grey when disabled. · **The labels** — GlobalStrings.lua (patch-2.MPQ), verbatim: VIDEOOPTIONS_MENU "Video Options", SOUNDOPTIONS_MENU "Sound Options", UIOPTIONS_MENU "Interface Options", - KEY_BINDINGS "Key Bindings", MACROS "Macros", LOGOUT "Logout", EXIT_GAME "Exit Game", + MACROS "Macros", LOGOUT "Logout", EXIT_GAME "Exit Game", RETURN_TO_GAME "Return to Game", MAIN_MENU "Main Menu". Declared as the real globals (they are addon-visible API, decision 0068) and read back into each button's label in its OnLoad — the ref's `text="LOGOUT"` attribute IS a global lookup, which this engine's @@ -63,13 +63,12 @@ Rewards / What's New / Ratings are structurally off in Era — store disabled, no account store, IsStandard() false, koKR only.) · The layout math those constants produce, precomputed here (this engine has no layout - frames): width 28+144+28 = 200; button tops 32/73/94/115/136/157/198/219/260; height - 260+21+28 = 309. Anchored as a TOPLEFT chain so the gaps read in the XML. - · **Key Bindings is a carved seat (0997, 0985's seat-carving rule)**: the 1.15 menu - ceded the entry to its Settings panel, but THIS window family's shape source is - 1.13/1.14 (KeyBindingsFrame.xml's provenance block), whose menu carries the button - and whose window reopens the menu on hide — so the rung returns, at the middle - section's head, wearing the 1.12 KEY_BINDINGS label like every sibling. + frames): width 28+144+28 = 200; button tops 32/73/94/115/136/177/198/239; height + 239+21+28 = 288. Anchored as a TOPLEFT chain so the gaps read in the XML. + · No Key Bindings rung — the era menu's own shape: 1.15 ceded the entry to the + Settings panel, and since decision 1008 ours lives there too (the Options window's + Keybindings category, KeyBindingsPage.xml). 0997 had carved the 1.13/1.14 seat back + in for its standalone window; that seat left with the window. · The header — Classic/Dialog/DialogTemplates.xml l.11-43 (ClassicDialogHeaderTemplate): the same 256x64 UI-DialogBox-Header art, text = MAINMENU_BUTTON, swapped to GameFontNormalMed1 (13px) by MainMenuFrameMixin:OnLoad. Both header idioms (1.12's @@ -90,7 +89,6 @@ -- The reference GlobalStrings (patch-2.MPQ Interface\FrameXML\GlobalStrings.lua), quoted -- verbatim. `X = X or "..."` so a later real strings table wins, the MicroMenu.xml idiom. MAIN_MENU = MAIN_MENU or "Main Menu" - KEY_BINDINGS = KEY_BINDINGS or "Key Bindings" ADDONS = ADDONS or "AddOns" HUD_EDIT_MODE_MENU = HUD_EDIT_MODE_MENU or "Edit Mode" GAMEMENU_SUPPORT = GAMEMENU_SUPPORT or "Support" @@ -203,7 +201,7 @@ - + @@ -255,29 +253,9 @@ - - - diff --git a/crates/benilla-app/assets/ui/KeyBindingsFrame.xml b/crates/benilla-app/assets/ui/KeyBindingsFrame.xml deleted file mode 100644 index 4baf1f96..00000000 --- a/crates/benilla-app/assets/ui/KeyBindingsFrame.xml +++ /dev/null @@ -1,798 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - this:SetBackdropColor(0, 0, 0, 0) - this:SetBackdropBorderColor(0.6, 0.6, 0.6) - - - - - - - - - - - - - - - - this:SetBackdropColor(0, 0, 0, 0) - this:SetBackdropBorderColor(0.6, 0.6, 0.6) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - BenillaFauxScrollFrame_OnLoad(this) - this.updateFunc = KeyBindingFrame_Update - - - if not KeyBindingFrame.selected then - BenillaFauxScrollFrame_OnMouseWheel(this, arg1) - end - - - - - - - - - - - - - - - - - - - - - getglobal(this:GetName() .. "Label"):SetText( - KeyBindings_String("CHARACTER_SPECIFIC_KEYBINDINGS", "Character Specific Key Bindings")) - - - PlaySound("igMainMenuOptionCheckBoxOn") - if KeyBindingFrame.bindingsChanged then - StaticPopup_Show("CONFIRM_LOSE_BINDING_CHANGES") - else - KeyBindings_ChangeBindingProfile() - end - - - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - 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() - - - - - - - - - - - KeyBindings_LoadCategories() - KeyBindingFrameCharacterButton:SetChecked(GetCurrentBindingSet() == 2 and 1 or nil) - KeyBindings_UpdateHeader() - KeyBindingFrame.bindingsChanged = nil - KeyBindingFrameScrollFrameScrollBar:SetValue(0) - KeyBindingFrame_Update() - - - -- Both references reopen the game menu (1.12 + era OnHide); a straggling - -- selection disarms the host capture seam. - KeyBindings_SetSelected(nil) - KeyBindingFrameOutputText:SetText("") - ShowUIPanel(GameMenuFrame) - - - - diff --git a/crates/benilla-app/assets/ui/KeyBindingsPage.xml b/crates/benilla-app/assets/ui/KeyBindingsPage.xml new file mode 100644 index 00000000..a6c3a0dc --- /dev/null +++ b/crates/benilla-app/assets/ui/KeyBindingsPage.xml @@ -0,0 +1,544 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crates/benilla-app/assets/ui/MultiBars.xml b/crates/benilla-app/assets/ui/MultiBars.xml index 074b4ffc..09ebac3e 100644 --- a/crates/benilla-app/assets/ui/MultiBars.xml +++ b/crates/benilla-app/assets/ui/MultiBars.xml @@ -19,8 +19,11 @@ joins at OnLoad. Ref divergences (deliberate, mirroring ActionBar.xml's SCOPE notes): - - No MULTIACTIONBAR* keybinds — the ref ships none by default either; buttons are - mouse-only, so the hotkey corner carries the ref's range dot instead of a label. + - MULTIACTIONBAR1/2BUTTON1-12 are BINDABLE since 1008 (the ref's own commands, registered + in crate::bindings::commands with the ref's runOnUp pair below); they ship UNBOUND like + the ref (no MULTIACTIONBAR* line in any of the install's bindings-cache.wtf files), so + out of the box the hotkey corner still carries the ref's range dot — a bound slot wears + its abbreviated label exactly like the main bar (ActionBar.xml's PaintHotkey law). - Empty wells hide (the ref's own default) and appear as drop-target rings only while a cursor payload is held — unlike the main bar's always-visible wells (its art has sockets; floating rings over bare world would not read as a bar). @@ -32,6 +35,29 @@ -- base = (page - 1) * 12. BENILLA_MULTIBAR_BOTTOMLEFT_BASE = 60 BENILLA_MULTIBAR_BOTTOMRIGHT_BASE = 48 + + -- The MULTIACTIONBAR keybind pair — ref MultiActionBars.lua:24-45 MultiActionButtonDown/ + -- Up, transcribed onto our frame names (`bar` is "BottomLeft"/"BottomRight", the ref + -- passes "MultiBarBottomLeft"); the same NORMAL→PUSHED debounce dance as the main bar's + -- BenillaActionButtonDown/Up (ActionBar.xml — the ref's MacroFrame_SaveMacro hook and + -- onSelf argument are not modeled there either). Driven by the binding dispatch + -- (crate::bindings, Kind::EdgeUpDown — decision 1008). + function BenillaMultiActionButtonDown(bar, id) + local button = getglobal("BenillaMultiBar" .. bar .. "Button" .. id) + if ( button:GetButtonState() == "NORMAL" ) then + button:SetButtonState("PUSHED") + end + end + + function BenillaMultiActionButtonUp(bar, id) + local button = getglobal("BenillaMultiBar" .. bar .. "Button" .. id) + if ( button:GetButtonState() == "PUSHED" ) then + button:SetButtonState("NORMAL") + UseAction(BenillaActionButton_Action(button)) + -- Ref MultiActionButtonUp:39-43: the checked ring reflects the use immediately. + BenillaActionButton_UpdateState(button) + end + end ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KeyBindings_CharacterToggle(this) + + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + 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() + + + + + + getglobal(this:GetName() .. "Label"):SetText( + KeyBindings_String("CHARACTER_SPECIFIC_KEYBINDINGS", "Character Specific Key Bindings")) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BenillaFauxScrollFrame_OnLoad(this) + this.updateFunc = KeyBindingsPage_Update + + + + + + + + if not KeyBindingsPage.selected then + BenillaFauxScrollFrame_OnMouseWheel(getglobal(this:GetName() .. "ScrollFrame"), arg1) + end + + + + + + + + + + + @@ -1753,11 +2087,15 @@ OptionsFrame_SelectCategory(this.selectedCategory or "Controls") + close-time confirm dialog is cut — SCOPE note), ends a live search (the + era Flush's ClearSearchBox, SettingsPanel.lua l.187-193), and disarms a + straggling bind capture — the host seam swallowing all input with no window + on screen would be a lockout (KeyBindingsPage.xml, decision 1008). --> OptionsFrameSearchBox:SetText("") OptionsPage_DiscardPending() + KeyBindings_SetSelected(nil) + KeyBindings_Output("") diff --git a/crates/benilla-app/assets/ui/PetActionBar.xml b/crates/benilla-app/assets/ui/PetActionBar.xml index 755c4edb..b4be12ba 100644 --- a/crates/benilla-app/assets/ui/PetActionBar.xml +++ b/crates/benilla-app/assets/ui/PetActionBar.xml @@ -34,8 +34,16 @@ change, so the ref's PET_BAR_UPDATE / PET_BAR_UPDATE_COOLDOWN / UNIT_PET / the UNIT_FLAGS+UNIT_AURA pair it filters for arg1=="pet" collapse into one — the same collapse StanceBar.xml makes, for the same reason. - - No PET_BAR_SHOWGRID/HIDEGRID. That pair exists for the drag-an-empty-slot state; the - bar's own drag is not built yet, so the grid has nothing to reveal. + - The drag is ONE verb. PickupPetAction serves shift-click, OnDragStart and OnReceiveDrag + alike (decision 1010) — that is the reference's own shape, not a collapse of ours: the + binding itself forks on whether the cursor is already carrying a pet payload, so there is + no "place" counterpart the way PlaceAction answers PickupAction on the main bar. Nothing + else can produce that payload, which is why the pet bar can be rearranged and never + populated: its contents are the server's. + - PET_BAR_SHOWGRID/HIDEGRID are live, and are the PET payload's alone. The engine derives + them off the same cursor transition that drives ACTIONBAR_SHOWGRID/HIDEGRID but on a + disjoint predicate, so a held spell lights the action bar's empty slots and a held pet + action lights this bar's — never both, because neither payload can land on the other bar. - The autocast sparkle is TEXTURES, not the ref's . benilla has no Model widget (the Cooldown and the bag ItemAnim took the same posture): model the mechanism, let our own renderer draw it. The numbers below are the shipped model's own — see BENILLA_PET_ @@ -186,6 +194,13 @@ -- not boilerplate: it is the whole autocast toggle, and without it a right click is -- simply never delivered. button:RegisterForClicks("LeftButtonUp", "RightButtonUp") + -- The drag trio (decision 1010). Both buttons drag, ref-verbatim + -- (PetActionButton_OnLoad l.234) — and unlike the action bar's, ALL THREE gestures here + -- reach the same verb: the reference's OnClick-under-shift, OnDragStart and + -- OnReceiveDrag all call PickupPetAction, which forks on whether the cursor is already + -- carrying. LOCK_ACTIONBAR stays the vanilla default (unlocked), so — as on ActionBar + -- — the ref's cvar guard is not reproduced and the drag is always live. + button:RegisterForDrag("LeftButton", "RightButton") button.index = index button.icon = getglobal(button:GetName() .. "Icon") button.cooldown = getglobal(button:GetName() .. "Cooldown") @@ -199,7 +214,12 @@ -- the pet is already on the attack, in which case it calls off; right = flip autocast. function BenillaPetActionButton_OnClick(button, mouseButton) button:SetChecked(0) - if mouseButton == "RightButton" then + -- Shift outranks the button fork entirely (ref l.253-255): a shift-click on the pet bar + -- is a pick-up whichever mouse button it came from, and it never toggles autocast. + if IsShiftKeyDown() then + PickupPetAction(button.index) + BenillaPetActionBar_Update() + elseif mouseButton == "RightButton" then TogglePetAutocast(button.index) elseif IsPetAttackActive(button.index) then PetStopAttack() @@ -208,6 +228,20 @@ end end + -- Both drag ends are the SAME verb (ref l.269-283, byte-verified at `0x4be180`): with an + -- empty cursor it picks the slot up, with a pet payload already held it drops onto this + -- slot. There is no separate "place" binding on this bar, which is also why a spell or an + -- item can never land here — nothing but a pet slot produces the payload it accepts. + function BenillaPetActionButton_OnDragStart(button) + PickupPetAction(button.index) + BenillaPetActionBar_Update() + end + + function BenillaPetActionButton_OnReceiveDrag(button) + PickupPetAction(button.index) + BenillaPetActionBar_Update() + end + -- Ref PetActionButton_OnEnter. A token slot has no spell to look up, so its tooltip is -- built from the two strings the info call already returned (the ref's own `isToken` fork); -- a spell slot goes through the engine's tooltip channel. @@ -256,7 +290,14 @@ if autoCastAllowed then button.autocastable:Show() else button.autocastable:Hide() end button.autocasting = autoCastEnabled - if name then button:Show() else button:Hide() end + -- An unnamed slot hides — UNLESS the drop grid is up, which is the whole point of + -- the grid: while a pet action rides the cursor every slot shows, so you can see + -- where it may land (ref PetActionBar_Update l.122-128 + PetActionBar_ShowGrid). + if name or (BenillaPetActionBarFrame.showgrid or 0) > 0 then + button:Show() + else + button:Hide() + end if texture then SetDesaturation(button.icon, not usable and 1 or nil) @@ -275,6 +316,13 @@ function BenillaPetActionBar_OnLoad(frame) frame:RegisterEvent("PLAYER_ENTERING_WORLD") frame:RegisterEvent("PET_BAR_UPDATE") + -- The drop grid (decision 1010). A counter rather than a flag, ref-verbatim + -- (PetActionBar_ShowGrid/_HideGrid): our engine derives the pair off one payload edge + -- so they strictly alternate, but the counter is what the reference keeps and it costs + -- nothing to keep it too. + frame:RegisterEvent("PET_BAR_SHOWGRID") + frame:RegisterEvent("PET_BAR_HIDEGRID") + frame.showgrid = 0 frame.clock = 0 BenillaPetActionBar_OnEvent(frame) end @@ -283,7 +331,12 @@ -- bottom-stack pass (UIParent.xml, decision 0272) — the pet term of that stack is what -- lifts the cast bar and the chat frame clear of it, which is the ref's own -- PetActionBarFrame.xml:205-210. - function BenillaPetActionBar_OnEvent(frame) + function BenillaPetActionBar_OnEvent(frame, eventName) + if eventName == "PET_BAR_SHOWGRID" then + frame.showgrid = frame.showgrid + 1 + elseif eventName == "PET_BAR_HIDEGRID" and frame.showgrid > 0 then + frame.showgrid = frame.showgrid - 1 + end if PetHasActionBar() then BenillaPetActionBar_Update() frame:Show() @@ -347,6 +400,8 @@ BenillaPetActionButton_OnClick(self, arg1) + BenillaPetActionButton_OnDragStart(self) + BenillaPetActionButton_OnReceiveDrag(self) BenillaPetActionButton_OnEnter(self) BenillaPetActionButton_OnLeave(self) @@ -426,7 +481,7 @@ BenillaPetActionBar_OnLoad(self) - BenillaPetActionBar_OnEvent(self) + BenillaPetActionBar_OnEvent(self, event) BenillaPetActionBar_OnUpdate(self, arg1) UIParent_ManageFramePositions() UIParent_ManageFramePositions() diff --git a/crates/benilla-app/assets/ui/UIParent.xml b/crates/benilla-app/assets/ui/UIParent.xml index 368ed690..a72b1937 100644 --- a/crates/benilla-app/assets/ui/UIParent.xml +++ b/crates/benilla-app/assets/ui/UIParent.xml @@ -274,5 +274,72 @@ BenillaQuestWatchFrame:SetPoint("TOPRIGHT", "MinimapCluster", "BOTTOMRIGHT", -CONTAINER_OFFSET_X, anchorY) end end + + -- ref UIParent.lua:1819-1878, transcribed 1:1 (decision 1008 — closes 0997's + -- "abbreviates through KEY_ABBR" residue with the REAL mechanism: it never was a + -- KEY_ABBR set; the whole abbreviation is this Lua). The action bar's HotKey corners + -- call it with returnAbbr=1: ONE modifier abbreviates (CTRL→c, SHIFT→s, ALT→a — so + -- SHIFT-2 reads "s-2", the director's ask), TWO OR MORE collapse the whole label to + -- "·" — the ref's own multi-modifier dot. The full form (tooltips, the Keybindings + -- page) keeps the canonical prefixes and reads the base key's KEY_* global. + -- Transcription notes: the ref's dead `gsub(tempName, "CAPSLOCK", "Caps")` line + -- (result discarded — gsub doesn't mutate) is dropped; its GetLocale() deDE + -- CTRL→STRG swap and IsMacClient() KEY_*_MAC lookup ride behind existence guards + -- (this VM defines neither yet, and the getglobal fallback covers the gap). + function GetBindingText(name, prefix, returnAbbr) + if ( not name ) then + return ""; + end + local tempName = name; + local i = strfind(name, "-"); + local dashIndex = nil; + local count = 0; + while ( i ) do + if ( not dashIndex ) then + dashIndex = i; + else + dashIndex = dashIndex + i; + end + count = count + 1; + tempName = strsub(tempName, i + 1); + i = strfind(tempName, "-"); + end + + local modKeys = ''; + if ( not dashIndex ) then + dashIndex = 0; + else + modKeys = strsub(name, 1, dashIndex); + if ( GetLocale and GetLocale() == "deDE" ) then + modKeys = gsub(modKeys, "CTRL", "STRG"); + end + end + + if ( returnAbbr ) then + if ( count > 1 ) then + return "·"; + else + modKeys = gsub(modKeys, "CTRL", "c"); + modKeys = gsub(modKeys, "SHIFT", "s"); + modKeys = gsub(modKeys, "ALT", "a"); + modKeys = gsub(modKeys, "STRG", "st"); + end + end + + if ( not prefix ) then + prefix = ""; + end + local localizedName = nil; + if ( IsMacClient and IsMacClient() ) then + localizedName = getglobal(prefix..tempName.."_MAC"); + end + if ( not localizedName ) then + localizedName = getglobal(prefix..tempName); + end + if ( not localizedName ) then + localizedName = tempName; + end + return modKeys..localizedName; + end ]]> diff --git a/crates/benilla-app/assets/ui/UiPanels.xml b/crates/benilla-app/assets/ui/UiPanels.xml index 63b97a0e..725df673 100644 --- a/crates/benilla-app/assets/ui/UiPanels.xml +++ b/crates/benilla-app/assets/ui/UiPanels.xml @@ -101,10 +101,8 @@ -- the client. (The ref's l.17-18 register SoundOptionsFrame/UIOptionsFrame the same way; -- those panels are this one window's category rows now, so the rows have no frames.) UIPanelWindows["OptionsFrame"] = { area = "center", pushable = 0, whileDead = 1 } - -- The Key Bindings window (decision 0997, KeyBindingsFrame.xml) — the ref's own - -- registration verbatim (`UIPanelWindows["KeyBindingFrame"] = { area = "center", - -- pushable = 0, whileDead = 1 }`, 1.12 Blizzard_BindingUI.lua l.8). - UIPanelWindows["KeyBindingFrame"] = { area = "center", pushable = 0, whileDead = 1 } + -- (0997's KeyBindingFrame row is gone — decision 1008 folded key bindings into the + -- options window above, the 1.15 layout's own placement; KeyBindingsPage.xml.) -- The world map (decision 0203 phase 2) — the ref's own line verbatim (UIParent.lua l.30) -- and the first **full** row. It was a plain toplevel until now, showing itself with a bare -- `WorldMapFrame:Show()` and so bypassing this manager entirely: with the game menu up it @@ -554,22 +552,15 @@ if StaticPopup_EscapePressed() then return end - -- The Key Bindings window (decision 0997): ESC with nothing armed is the Cancel - -- gesture — revert to the saved set and close (both references' own law; while a - -- capsule IS armed the host's capture seam swallowed the press before this chain - -- ever saw it, and ESC became a bind — 1.12's law). Its OnHide reopens the menu. - -- Looked up like the rest (headless tests load this file without the window). - local keybinds = getglobal("KeyBindingFrame") - if keybinds and keybinds:IsVisible() then - KeyBindingFrame_Cancel() - return - end -- The options window next (l.1483-1484, live since decision 0950 — this rung sat -- unfilled while GameMenuFrame.xml's disabled buttons were the gap). The ref -- cancel-clicks (`OptionsFrameCancel:Click()`); ours hides: benilla applies changes -- live, there is no pending-change model for a Cancel to roll back, so putting the - -- window away IS the whole gesture. Looked up like the menu above (a headless panel - -- test loads this file without OptionsFrame.xml behind it). + -- window away IS the whole gesture — and since 1008 that covers the Keybindings + -- page too (live-commit; its OnHide hook disarms a straggling bind capture, and + -- while a capsule IS armed the host's capture seam swallows ESC before this chain + -- ever exists — the press becomes a BIND, 1.12's law). Looked up like the menu + -- above (a headless panel test loads this file without OptionsFrame.xml behind it). local options = getglobal("OptionsFrame") if options and options:IsVisible() then HideUIPanel(options) diff --git a/crates/benilla-app/assets/ui/UnitFrames.xml b/crates/benilla-app/assets/ui/UnitFrames.xml index 9a7d45f4..ff57edb2 100644 --- a/crates/benilla-app/assets/ui/UnitFrames.xml +++ b/crates/benilla-app/assets/ui/UnitFrames.xml @@ -1163,18 +1163,22 @@ when it has none. That is not decoration — a warlock's imp has mana and a hunter's boar has focus, but a skeleton has neither, and the plain art draws a mana rail with nothing in it. - DEFERRED, each with its reason (all named in 0990): - - HAPPINESS. The ref's PetFrameHappiness icon needs `GetPetHappiness 0x4be900`, whose body - is NOT carved in wow-re (its address is known from the binding table, nothing more). Its - three thresholds, the damage percentage and the loyalty rate are exactly the kind of - derived numbers 0988 was written about: vmangos has a HAPPINESS_LEVEL_SIZE and taking it - would repeat that record's own mistake. So the icon is absent rather than guessed, and - comes back with the carve — together with `GetPetLoyalty 0x4be700`, - `GetPetTrainingPoints 0x4be790` and `GetPetExperience 0x4be840`, its uncarved siblings. - - The PET right-click menu. Its four entries (UnitPopup.xml's PET_PAPERDOLL / PET_RENAME / - PET_ABANDON / PET_DISMISS) each need a binding that is its own build — the pet paper doll - most of all. They stay suppressed there; the frame takes no right click rather than - opening a menu of four dead rows. + HAPPINESS is no longer deferred — decision 1005 built it, off the carve 0990 was waiting + for. `GetPetHappiness 0x4be900` returns a bucket the CLIENT has already computed from + PetPersonality.dbc's own three thresholds, so nothing here is derived from vmangos and + 0988's warning was answered rather than dodged. Its three siblings (`GetPetLoyalty`, + `GetPetTrainingPoints`, `GetPetExperience`) landed with it. + + STILL DEFERRED, each with its reason: + - The PET right-click menu. The reference opens it from PetFrame_OnClick's else arm + (`ToggleDropDownMenu(1, nil, PetFrameDropDown)` — PetFrame.lua:127-129), and its four + entries (UnitPopup.xml's PET_PAPERDOLL / PET_RENAME / PET_ABANDON / PET_DISMISS) each + need a binding that is its own build — the pet paper doll most of all. Two more are + needed just to decide what the menu SHOWS: `PetCanBeAbandoned` forks it, hiding + paperdoll/rename/abandon for a warlock pet and hiding dismiss for a hunter's, so the + same menu reads "Abandon" (permanent, behind the ABANDON_PET confirm) on a hunter pet + and "Dismiss" on a demon. They stay suppressed in UnitPopup.xml; the frame takes no + right click rather than opening a menu of dead rows. - PartyMemberBuffTooltip on hover (the ref's OnEnter panel): it belongs to the party frames' own deferred buff panel, not here. --> @@ -1203,6 +1207,7 @@ frame:RegisterEvent("UNIT_NAME_UPDATE") frame:RegisterEvent("PET_ATTACK_START") frame:RegisterEvent("PET_ATTACK_STOP") + frame:RegisterEvent("UNIT_HAPPINESS") frame:EnableMouse(true) BenillaPetFrame_Update(frame) end @@ -1218,6 +1223,10 @@ elseif event == "PET_ATTACK_STOP" then BenillaPetFrameAttackMode:Hide() return + elseif event == "UNIT_HAPPINESS" then + -- The ref's own last arm: repaint the icon alone, not the whole frame. + BenillaPetFrame_SetHappiness() + return elseif event == "UNIT_PET" then if arg1 ~= "player" then return end elseif string.sub(event, 1, 5) == "UNIT_" and arg1 ~= "pet" then @@ -1255,6 +1264,44 @@ end BenillaPetFrame_UpdateDebuffs() + BenillaPetFrame_SetHappiness() + end + + -- ref PetFrame_SetHappiness (PetFrame.lua l.132-156), quoted (decision 1005). + -- + -- `GetPetHappiness` hands back a PRE-BUCKETED 1/2/3 — the client does the thresholding + -- against PetPersonality.dbc, not this function — plus the two numbers that bucket + -- selects. Three things here are load-bearing and each is the reference's own: + -- * the hide test is `not happiness or not isHunterPet`, so a bucket of **0** (truthy in + -- Lua) leaves the icon SHOWING with whatever texcoords it had. Testing `happiness == 0` + -- or coercing 0 to nil would hide a frame the reference shows. + -- * the three texcoord cuts run 3/2/1 left-to-right across the strip — happy is at x=0. + -- * the loyalty line is chosen by the SIGN of loyaltyRate, which is why that return is + -- unscaled and allowed to be negative. + function BenillaPetFrame_SetHappiness() + local happiness, damagePercentage, loyaltyRate = GetPetHappiness() + local hasPetUI, isHunterPet = HasPetUI() + if not happiness or not isHunterPet then + BenillaPetFrameHappiness:Hide() + return + end + BenillaPetFrameHappiness:Show() + if happiness == 1 then + BenillaPetFrameHappinessTexture:SetTexCoord(0.375, 0.5625, 0, 0.359375) + elseif happiness == 2 then + BenillaPetFrameHappinessTexture:SetTexCoord(0.1875, 0.375, 0, 0.359375) + elseif happiness == 3 then + BenillaPetFrameHappinessTexture:SetTexCoord(0, 0.1875, 0, 0.359375) + end + BenillaPetFrameHappiness.tooltip = getglobal("PET_HAPPINESS" .. happiness) + BenillaPetFrameHappiness.tooltipDamage = format(PET_DAMAGE_PERCENTAGE, damagePercentage) + if loyaltyRate < 0 then + BenillaPetFrameHappiness.tooltipLoyalty = getglobal("LOSING_LOYALTY") + elseif loyaltyRate > 0 then + BenillaPetFrameHappiness.tooltipLoyalty = getglobal("GAINING_LOYALTY") + else + BenillaPetFrameHappiness.tooltipLoyalty = nil + end end -- The four debuff buttons (ref PetFrame.xml's $parentDebuff1..4 over RefreshBuffs). The pet @@ -1412,6 +1459,40 @@ + + + + + + + + + + + + + + + if this.tooltip then + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText(this.tooltip) + if this.tooltipDamage then + GameTooltip:AddLine(this.tooltipDamage, "", 1, 1, 1) + end + if this.tooltipLoyalty then + GameTooltip:AddLine(this.tooltipLoyalty, "", 1, 1, 1) + end + GameTooltip:Show() + end + + + GameTooltip:Hide() + + +