From 9f4595597d5157a349849f8a9f705fbebe1662cc Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 7 Aug 2026 16:03:16 +0700 Subject: [PATCH] fishing becomes whole: the bite, the line, the splash and the reel - the whole loop plays: the line arcs rod tip to bobber, the bite splashes with its own sound, and clicking the bobber reels in - the verdicts read like 1.12: yellow "your fish got away" lines, the reel-in sound, the fishing loot window - "You feel rested." prints on the transition, rest states come from the client's own Exhaustion table, and level 60 trades the XP strip for the max-level rail - the Skills pane lists what the real client lists: the hidden rows, the sort, gray numberless single-rank lines Co-Authored-By: Claude Fable 5 --- Cargo.lock | 1 + crates/benilla-app/Cargo.toml | 6 + crates/benilla-app/assets/ui/ActionBar.xml | 106 +++- crates/benilla-app/assets/ui/LootFrame.xml | 25 +- crates/benilla-app/assets/ui/SkillFrame.xml | 41 +- crates/benilla-app/assets/ui/UnitFrames.xml | 35 +- crates/benilla-app/src/capture/fixtures.rs | 1 + crates/benilla-app/src/creature_anim.rs | 4 +- .../benilla-app/src/creature_anim/events.rs | 2 +- crates/benilla-app/src/entities.rs | 15 +- crates/benilla-app/src/entities/display.rs | 18 + .../src/entities/equipment/spawn.rs | 12 + crates/benilla-app/src/fishing_line.rs | 156 ++++++ crates/benilla-app/src/go_anim.rs | 284 ++++++++-- crates/benilla-app/src/interact/inspect.rs | 57 +- crates/benilla-app/src/lib.rs | 3 + crates/benilla-app/src/minimap/blips/mod.rs | 4 +- crates/benilla-app/src/minimap/mod.rs | 9 +- crates/benilla-app/src/net/apply.rs | 8 + crates/benilla-app/src/net/apply/loot.rs | 56 +- crates/benilla-app/src/net/apply/objects.rs | 16 + crates/benilla-app/src/net/apply/quests.rs | 2 + crates/benilla-app/src/portrait/framing.rs | 512 +++++++++++++----- crates/benilla-app/src/portrait/glue_booth.rs | 14 +- crates/benilla-app/src/portrait/mod.rs | 155 ++++-- crates/benilla-app/src/portrait/test_bake.rs | 8 + crates/benilla-app/src/sound/gameobject.rs | 103 +++- crates/benilla-app/src/target/click.rs | 3 + crates/benilla-app/src/target/cursor_mode.rs | 347 +++++++++--- crates/benilla-app/src/target/hover.rs | 11 +- crates/benilla-app/src/target/mod.rs | 9 +- crates/benilla-app/src/ui_action/errors.rs | 17 + crates/benilla-app/src/ui_action/feed.rs | 23 +- crates/benilla-app/src/ui_char.rs | 106 ++-- crates/benilla-app/src/ui_loot.rs | 71 ++- crates/benilla-app/src/ui_quest.rs | 1 + .../src/ui_script/action_bar_tests.rs | 4 +- .../src/ui_script/character_tests.rs | 12 +- .../benilla-app/src/ui_script/escape_tests.rs | 1 + .../src/ui_script/exp_bar_tests.rs | 85 ++- .../src/ui_script/game_menu_tests.rs | 1 + .../benilla-app/src/ui_script/loot_tests.rs | 65 ++- crates/benilla-app/src/ui_script/mod.rs | 3 + .../src/ui_script/skills_frame_tests.rs | 306 +++++++++++ .../src/ui_script/unit_frame_tests.rs | 56 ++ crates/benilla-app/src/ui_tooltip/mod.rs | 14 +- crates/benilla-app/src/ui_unit.rs | 225 ++++++-- .../benilla-app/src/wmo_portal/audit/mod.rs | 23 +- .../benilla-app/src/wmo_portal/audit/pin.rs | 263 ++++++++- crates/benilla-assets/src/m2.rs | 20 + .../benilla-formats/examples/scan_events.rs | 59 ++ crates/benilla-formats/examples/srci_dump.rs | 64 +++ crates/benilla-formats/src/exhaustion.rs | 96 ++++ crates/benilla-formats/src/lib.rs | 30 +- crates/benilla-formats/src/models/anim.rs | 27 + crates/benilla-formats/src/skill_lines.rs | 214 +++++++- .../tests/m2_portrait_camera.rs | 79 +++ crates/benilla-protocol/src/events.rs | 12 + crates/benilla-protocol/src/events/decode.rs | 5 + .../src/messages/gameobject.rs | 40 +- .../benilla-protocol/src/messages/opcode.rs | 16 + .../benilla-protocol/src/messages/packet.rs | 19 + crates/benilla-protocol/src/messages/parse.rs | 7 + .../src/messages/update_object/fields/mod.rs | 4 + .../messages/update_object/fields/player.rs | 11 + crates/benilla-ui/src/loader/regions.rs | 21 +- crates/benilla-ui/src/loader/widgets.rs | 8 +- crates/benilla-ui/src/script/button.rs | 12 +- .../benilla-ui/src/script/editbox/methods.rs | 2 +- crates/benilla-ui/src/script/extract.rs | 30 +- crates/benilla-ui/src/script/loot.rs | 25 + crates/benilla-ui/src/script/model.rs | 29 +- crates/benilla-ui/src/script/region.rs | 44 +- crates/benilla-ui/src/script/skills.rs | 325 ++++++++--- crates/benilla-ui/src/script/slider.rs | 9 +- crates/benilla-ui/src/script/statusbar.rs | 16 +- crates/benilla-ui/src/script/tests/regions.rs | 76 +++ crates/benilla-ui/src/script/tooltip/mod.rs | 6 +- crates/benilla-ui/src/script/types.rs | 33 +- crates/benilla-ui/src/script/unit/bindings.rs | 61 ++- crates/benilla-ui/src/script/unit/mod.rs | 13 + 81 files changed, 4052 insertions(+), 660 deletions(-) create mode 100644 crates/benilla-app/src/fishing_line.rs create mode 100644 crates/benilla-app/src/ui_script/skills_frame_tests.rs create mode 100644 crates/benilla-formats/examples/scan_events.rs create mode 100644 crates/benilla-formats/examples/srci_dump.rs create mode 100644 crates/benilla-formats/src/exhaustion.rs diff --git a/Cargo.lock b/Cargo.lock index 0c72824c..5981928e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -592,6 +592,7 @@ dependencies = [ "benilla-formats", "benilla-protocol", "benilla-ui", + "benilla-wmo", "bevy", "bevy_egui", "bytemuck", diff --git a/crates/benilla-app/Cargo.toml b/crates/benilla-app/Cargo.toml index b43c8112..335e99c2 100644 --- a/crates/benilla-app/Cargo.toml +++ b/crates/benilla-app/Cargo.toml @@ -82,6 +82,12 @@ mint = "0.5" # source. Pinned to 0.8: the version `benilla-srp` already depends on, so this adds no crate. rand = "0.8" +[dev-dependencies] +# The WMO portal-cull audit's raw-face instruments (`WOW_PIN_COL`): the pin probe compares the +# filtered walking-collision set against the group file's FULL face list with per-face MOPY flags, +# which only the raw parser exposes — benilla-formats' gathers are exactly the filtered views. +benilla-wmo = { path = "../benilla-wmo" } + # Linux/BSD: the Wayland clipboard (decision 0702). We build with bevy's `wayland` feature, so on a # Wayland session winit gives us a native Wayland window and `arboard` — X11-only without its # non-default `wayland-data-control` feature — finds no X11 CLIPBOARD owner and every paste fails. diff --git a/crates/benilla-app/assets/ui/ActionBar.xml b/crates/benilla-app/assets/ui/ActionBar.xml index 5567011d..a09af17c 100644 --- a/crates/benilla-app/assets/ui/ActionBar.xml +++ b/crates/benilla-app/assets/ui/ActionBar.xml @@ -648,13 +648,54 @@ BenillaExhaustionTick.timer = nil end + -- ── The max-level rail (ref-ReputationFrame.lua, the max-level slice) ───────────────── + -- ref l.14: the constant lives in ReputationFrame.lua, which isn't built yet — it moves + -- there with the reputation system. + MAX_PLAYER_LEVEL = 60 + + function BenillaMaxLevelBar_OnLoad(bar) + bar:RegisterEvent("PLAYER_ENTERING_WORLD") + bar:RegisterEvent("PLAYER_LEVEL_UP") + end + + -- ref ReputationWatchBar_Update's else arm (ref-ReputationFrame.lua l.161-166 + + -- l.236-244), the no-watched-faction branch only — the reputation branches wait for the + -- reputation system: at MAX_PLAYER_LEVEL the XP strip gives way to the rail and the + -- tick goes with it. Login seam: the ref initializes off UPDATE_FACTION (its OnEvent's + -- else arm; the faction list lands at every login), an event benilla doesn't carry yet, + -- so the rail listens to PLAYER_ENTERING_WORLD instead — same moment, same values. + function BenillaMaxLevelBar_Update(newLevel) + if not newLevel then + newLevel = UnitLevel("player") + end + if newLevel == MAX_PLAYER_LEVEL then + BenillaExpBar:Hide() + BenillaMaxLevelBar:Show() + BenillaExhaustionTick:Hide() + else + BenillaExpBar:Show() + BenillaMaxLevelBar:Hide() + end + end + + -- The ref's event fan (ref-ReputationFrame.xml l.1002-1016): PLAYER_LEVEL_UP passes + -- arg1 (the new level — the level FIELD may not have landed yet), everything else + -- re-reads UnitLevel. + function BenillaMaxLevelBar_OnEvent() + if event == "PLAYER_LEVEL_UP" then + BenillaMaxLevelBar_Update(arg1) + else + BenillaMaxLevelBar_Update() + end + end + -- ── The exhaustion tick (ref-MainMenuBar.lua ExhaustionTick_*) ──────────────────────── -- The round marker riding the XP bar at the point where rested XP runs out, plus the - -- pale fill between the bar's value and that point. Two transcription cuts, named: - -- the ref's MainMenuBarMaxLevelBar / ReputationWatchBar guards reference max-level bar - -- art and the faction watch bar, neither built — each is its own slice, and until then - -- the guards' frames can't be shown, so the cuts are unreachable-code cuts, not behavior - -- cuts. (A level-60 body simply keeps its inert XP bar for now.) + -- pale fill between the bar's value and that point. One transcription cut left, named: + -- the ref's two ReputationWatchBar guards (the max-level+rep tick hide, l.51-54, and + -- the rep-bar-replaced-the-xp-bar hide, l.69-71) reference the faction watch bar, not + -- built — its frame can't be shown, so the cut is an unreachable-code cut, not a + -- behavior cut. The MainMenuBarMaxLevelBar guard is live since 1094 (the rail below). function BenillaExhaustionTick_OnLoad(tick) tick:RegisterEvent("PLAYER_ENTERING_WORLD") tick:RegisterEvent("PLAYER_XP_UPDATE") @@ -683,7 +724,8 @@ local exhaustionTickSet = max(((playerCurrXP + exhaustionThreshold) / playerMaxXP) * BenillaExpBar:GetWidth(), 0) tick:ClearAllPoints() - if exhaustionTickSet > BenillaExpBar:GetWidth() then + if exhaustionTickSet > BenillaExpBar:GetWidth() + or BenillaMaxLevelBar:IsShown() then tick:Hide() BenillaExhaustionLevelFillBar:Hide() else @@ -1049,6 +1091,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BenillaMaxLevelBar_OnLoad(self) + BenillaMaxLevelBar_OnEvent() + + + + OnShow resets it to the skull and swaps a fishing loot to FishingLoot-Icon + (decision 1086), exactly as the ref's OnShow does (l.133-140). --> diff --git a/crates/benilla-app/assets/ui/SkillFrame.xml b/crates/benilla-app/assets/ui/SkillFrame.xml index c60ca7ff..65d32dd7 100644 --- a/crates/benilla-app/assets/ui/SkillFrame.xml +++ b/crates/benilla-app/assets/ui/SkillFrame.xml @@ -111,19 +111,52 @@ function BenillaSkillFrame_PaintBar(bar, skillName, skillRank, skillModifier, skillMaxRank) local nameText = getglobal(bar:GetName() .. "SkillName") local rankText = getglobal(bar:GetName() .. "SkillRank") + -- The trough behind the fill (ref `$parentBackground`), recoloured per branch alongside + -- the fill itself — ref SkillFrame.lua:158 (normal) and :167 (proficiency), and the + -- detail pane's identical pair at :356/:376. The ref sets the normal-skill pair BEFORE + -- its `skillMaxRank` gate and lets the proficiency arm overwrite it; setting each arm's + -- own pair here is the same result with no dead write. The ref's THIRD writer of this + -- texture — the `skillCostType == 1/2/3` green/yellow/red block at :96-111, and the + -- `stepCost` "Learn " gray at :126 — is unreachable for us and would be + -- overwritten here even if it weren't: `stepCost`/`rankCost` are always nil and + -- `numTempPoints` always 0 (skills.rs's module doc — dead 1.12 data, deviation 1/5), so + -- every entry row takes the ref's own `else -- Normal skill` arm. + local bgTexture = getglobal(bar:GetName() .. "Background") nameText:SetText(skillName) nameText:SetTextColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b) rankText:SetTextColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b) - if skillMaxRank == 0 then - -- A 1.12 proficiency line the app reports as 0/0 (SkillEntry's own doc) — barless - -- plain text, deviation-free reading of the ref's own "skillMaxRank == 1" gray-bar - -- intent, adapted to our app's 0/0 shape (this file's header comment, no-fillbar note). + if skillMaxRank == 1 then + -- ref SkillFrame.lua:161-167 verbatim: "If max rank in a skill is 1 assume that + -- its a proficiency" — a FULL gray bar and NO rank text. Two kinds of line land + -- here, and the ref can't tell them apart because it doesn't need to: the armor + -- proficiencies, which the server really does report as 1/1, and the single-rank + -- lines (class skills, Dual Wield, racials, the per-mount riding lines) whose + -- `skillMaxRank` the engine already overrode to 1 off + -- `SkillRaceClassInfo.flags & 0x400` (skills.rs's `SkillEntry::mono`) — which is + -- why a hunter's `Beast Mastery 300/300` draws gray and numberless here, exactly + -- as it does in the real client. + bar:SetMinMaxValues(0, 1) + bar:SetValue(1) + bar:SetStatusBarColor(0.5, 0.5, 0.5) + rankText:SetText("") + bgTexture:SetVertexColor(1.0, 1.0, 1.0, 0.5) + elseif skillMaxRank == 0 then + -- Not reachable off a live server (a line the player HAS always carries a max ≥ 1) + -- and the ref leaves this case to fall through its own if/elseif untouched — + -- which, on a recycled row, would leave the PREVIOUS skill's bar and rank text + -- sitting there. We blank instead: a stale-state hole is a quirk, not behaviour. bar:SetMinMaxValues(0, 1) bar:SetValue(0) bar:SetStatusBarColor(0, 0, 0, 0) rankText:SetText("") + -- back to the template's own declared base, so a recycled row shows a neutral empty + -- trough rather than the previous skill's blue or white. The base lives in the + -- TEXEL (the template's ``), so restoring it means an IDENTITY + -- vertex colour — writing 0.2 here would multiply it to 0.04 (decision 1101). + bgTexture:SetVertexColor(1.0, 1.0, 1.0, 1.0) else bar:SetStatusBarColor(0.0, 0.0, 1.0, 0.5) + bgTexture:SetVertexColor(0.0, 0.0, 0.75, 0.5) bar:SetMinMaxValues(0, skillMaxRank) bar:SetValue(skillRank) -- ref SkillFrame.lua:178-186, transcribed byte-exact (this file's header comment, diff --git a/crates/benilla-app/assets/ui/UnitFrames.xml b/crates/benilla-app/assets/ui/UnitFrames.xml index cfaef29f..50f1b9e7 100644 --- a/crates/benilla-app/assets/ui/UnitFrames.xml +++ b/crates/benilla-app/assets/ui/UnitFrames.xml @@ -946,6 +946,28 @@ + + + + + + + + + + + + + + - - - - - - - - - - - -