From d3a4a0df769f1fc3f0e2190223eb33b4b607a224 Mon Sep 17 00:00:00 2001 From: Sam Date: Thu, 17 Sep 2026 16:41:38 +0700 Subject: [PATCH] the channel bar says Channeling, and eight caches become one - a channelled spell's bar reads "Channeling" rather than the spell's name, and stays orange: 1.12 tells a channel from a cast by its label and its drain, not by a green bar (2284) - a shot's muzzle blast fires only when a projectile is queued, the reference's own gate, so a target-less shot shows none (2289) - the eight hand-copied query caches, names, items, objects, page texts, quests, guilds, petitions and mail, are one generic cache, and a read that asks no longer takes the whole store exclusively (2288) - the schedule's ambiguity ratchet counts by class, and the cast-fail argument table is closed: MIN_SKILL is the prospecting leg and cannot fire on 1.12 (2287, 2292) Co-Authored-By: Claude Fable 5.1 --- crates/benilla-app/src/capture/live_shot.rs | 2 +- .../benilla-app/src/capture/probe_charter.rs | 2 +- crates/benilla-app/src/capture/probe_clam.rs | 6 +- crates/benilla-app/src/capture/probe_mail.rs | 2 +- .../src/capture/probe_vendor_swap.rs | 4 +- .../src/capture/probes/ground_census.rs | 2 +- .../src/creature_anim/spell_visual.rs | 4 +- crates/benilla-app/src/death.rs | 29 +- crates/benilla-app/src/debug_panel/inspect.rs | 2 +- crates/benilla-app/src/debug_panel/journal.rs | 2 +- crates/benilla-app/src/debug_panel/world.rs | 2 +- crates/benilla-app/src/entities.rs | 2 +- .../src/entities/equipment/resolve.rs | 4 +- crates/benilla-app/src/entities/missile.rs | 52 ++- crates/benilla-app/src/game_plugins.rs | 377 +++++++++++------- crates/benilla-app/src/go_templates.rs | 66 +-- crates/benilla-app/src/items.rs | 80 ++-- crates/benilla-app/src/lib.rs | 1 + crates/benilla-app/src/minimap/blips/mod.rs | 2 +- crates/benilla-app/src/nameplates.rs | 2 +- crates/benilla-app/src/names.rs | 109 +++-- crates/benilla-app/src/net.rs | 29 +- crates/benilla-app/src/net/apply.rs | 14 +- .../benilla-app/src/net/apply/combat_log.rs | 4 +- crates/benilla-app/src/net/apply/group.rs | 12 +- crates/benilla-app/src/net/apply/mail.rs | 3 +- crates/benilla-app/src/net/apply/objects.rs | 4 +- crates/benilla-app/src/npc_text.rs | 4 +- crates/benilla-app/src/query_cache.rs | 242 +++++++++++ crates/benilla-app/src/ranged_flex.rs | 71 +++- crates/benilla-app/src/sound/mod.rs | 5 +- crates/benilla-app/src/sound/ui.rs | 2 +- crates/benilla-app/src/target/lock.rs | 8 +- crates/benilla-app/src/ui_action/cast_fail.rs | 28 +- crates/benilla-app/src/ui_action/cast_send.rs | 2 +- crates/benilla-app/src/ui_action/drain.rs | 2 +- crates/benilla-app/src/ui_action/feed.rs | 42 +- crates/benilla-app/src/ui_action/state.rs | 6 +- crates/benilla-app/src/ui_action/usable.rs | 20 +- .../benilla-app/src/ui_action/weapon_icon.rs | 16 +- crates/benilla-app/src/ui_auction/mod.rs | 28 +- crates/benilla-app/src/ui_battlefield.rs | 2 +- .../src/ui_battlefield_positions.rs | 2 +- .../benilla-app/src/ui_battlefield_score.rs | 6 +- crates/benilla-app/src/ui_bind_confirm.rs | 4 +- crates/benilla-app/src/ui_cast.rs | 291 ++++++++++++-- crates/benilla-app/src/ui_char.rs | 35 +- crates/benilla-app/src/ui_chat/combat.rs | 6 +- crates/benilla-app/src/ui_chat/feed.rs | 10 +- crates/benilla-app/src/ui_chat/input/mod.rs | 12 +- crates/benilla-app/src/ui_craft.rs | 2 +- crates/benilla-app/src/ui_dialog_verbs.rs | 2 +- crates/benilla-app/src/ui_dressup.rs | 70 ++-- crates/benilla-app/src/ui_duel.rs | 2 +- crates/benilla-app/src/ui_gossip.rs | 4 +- crates/benilla-app/src/ui_guild/mod.rs | 68 ++-- crates/benilla-app/src/ui_inspect.rs | 6 +- crates/benilla-app/src/ui_item_text.rs | 89 ++--- crates/benilla-app/src/ui_items/drain.rs | 20 +- crates/benilla-app/src/ui_items/feed.rs | 36 +- crates/benilla-app/src/ui_items/mod.rs | 16 +- crates/benilla-app/src/ui_loot.rs | 34 +- crates/benilla-app/src/ui_loot_roll.rs | 20 +- crates/benilla-app/src/ui_mail/mod.rs | 92 ++--- crates/benilla-app/src/ui_merchant.rs | 26 +- crates/benilla-app/src/ui_party/feed.rs | 2 +- crates/benilla-app/src/ui_pet_stats.rs | 34 +- crates/benilla-app/src/ui_petition/feed.rs | 6 +- crates/benilla-app/src/ui_petition/mod.rs | 57 +-- crates/benilla-app/src/ui_quest.rs | 28 +- crates/benilla-app/src/ui_quest_log.rs | 111 +++--- crates/benilla-app/src/ui_quest_share.rs | 2 +- .../benilla-app/src/ui_script/cast_tests.rs | 19 +- crates/benilla-app/src/ui_shapeshift.rs | 4 +- crates/benilla-app/src/ui_social/feed.rs | 14 +- crates/benilla-app/src/ui_spellbook.rs | 6 +- crates/benilla-app/src/ui_stable.rs | 8 +- crates/benilla-app/src/ui_summon.rs | 2 +- crates/benilla-app/src/ui_tabard.rs | 4 +- crates/benilla-app/src/ui_taxi/mod.rs | 2 +- crates/benilla-app/src/ui_tooltip/mod.rs | 4 +- crates/benilla-app/src/ui_trade.rs | 22 +- crates/benilla-app/src/ui_tradeskill.rs | 20 +- crates/benilla-app/src/ui_trainer/law.rs | 2 +- crates/benilla-app/src/ui_trainer/mod.rs | 10 +- crates/benilla-app/src/ui_trainer/tests.rs | 28 +- crates/benilla-app/src/ui_unit.rs | 2 +- crates/benilla-app/src/vplates.rs | 2 +- crates/benilla-formats/src/itemsubclass.rs | 22 + crates/benilla-formats/src/lib.rs | 3 +- .../src/spells/catalog_tests.rs | 113 ++++++ crates/benilla-formats/src/spells/display.rs | 31 ++ crates/benilla-formats/src/spells/mod.rs | 42 +- 93 files changed, 1809 insertions(+), 940 deletions(-) create mode 100644 crates/benilla-app/src/query_cache.rs diff --git a/crates/benilla-app/src/capture/live_shot.rs b/crates/benilla-app/src/capture/live_shot.rs index ea7a32a8..2f83bf35 100644 --- a/crates/benilla-app/src/capture/live_shot.rs +++ b/crates/benilla-app/src/capture/live_shot.rs @@ -134,7 +134,7 @@ fn fire_live_shot( self_q: Query<&ObjectStore, With>, subjects: Query<(&Guid, &Transform), (With, Without)>, camera: Query<(&Camera, &Transform), With>, - mut names: ResMut, + names: Res, net_commands: Res, mut refused: Local, mut next_require_warn: Local, diff --git a/crates/benilla-app/src/capture/probe_charter.rs b/crates/benilla-app/src/capture/probe_charter.rs index fbf6db4a..5fce6ffa 100644 --- a/crates/benilla-app/src/capture/probe_charter.rs +++ b/crates/benilla-app/src/capture/probe_charter.rs @@ -458,7 +458,7 @@ fn charter_probe( mut probe: ResMut, gossip: Res, registrar: Res, - mut items: ResMut, + items: Res, script: Option>, self_q: Query<&ObjectStore, With>, player: Res, diff --git a/crates/benilla-app/src/capture/probe_clam.rs b/crates/benilla-app/src/capture/probe_clam.rs index 788c0345..fc31c1ff 100644 --- a/crates/benilla-app/src/capture/probe_clam.rs +++ b/crates/benilla-app/src/capture/probe_clam.rs @@ -184,7 +184,7 @@ fn window_open(script: &UiScript, loot: &LootState) -> bool { fn find_in_backpack( store: &ObjectStore, entry: u32, - items: &mut Items, + items: &Items, net: &NetCommands, ) -> Option<(u64, u32)> { let (i, guid) = (0..16u8) @@ -209,7 +209,7 @@ fn clam_probe( mut probe: ResMut, script: Option>, self_store: Query<&ObjectStore, With>, - mut items: ResMut, + items: Res, mut cfg: ResMut, loot: Res, latch: Res, @@ -240,7 +240,7 @@ fn clam_probe( probe.phase = Phase::Stocking { sent_at: now }; } Phase::Stocking { sent_at } => { - if let Some((guid, slot)) = find_in_backpack(store, entry, &mut items, &net) { + if let Some((guid, slot)) = find_in_backpack(store, entry, &items, &net) { info!( "PROBE_CLAM: item {entry} is guid {guid:#x} in backpack slot {slot} — \ sampling {CONTROL_FRAMES} control frames with it UNCLICKED" diff --git a/crates/benilla-app/src/capture/probe_mail.rs b/crates/benilla-app/src/capture/probe_mail.rs index 1e816c87..85c747fd 100644 --- a/crates/benilla-app/src/capture/probe_mail.rs +++ b/crates/benilla-app/src/capture/probe_mail.rs @@ -377,7 +377,7 @@ fn mail_probe( }; let landed = entry_of(&mail, letter_id).and_then(|e| { (e.item_text_id != 0) - .then(|| mail.bodies.get(&e.item_text_id).cloned()) + .then(|| mail.bodies.get(e.item_text_id).cloned()) .flatten() .map(|body| (body, e.checked & CHECKED_READ != 0)) }); diff --git a/crates/benilla-app/src/capture/probe_vendor_swap.rs b/crates/benilla-app/src/capture/probe_vendor_swap.rs index 2291f89f..6093fd07 100644 --- a/crates/benilla-app/src/capture/probe_vendor_swap.rs +++ b/crates/benilla-app/src/capture/probe_vendor_swap.rs @@ -168,7 +168,7 @@ fn vendor_swap_probe( mut probe: ResMut, merchant: Res, script: Option>, - mut names: ResMut, + names: Res, panes: Res, images: Res, self_player: Query<(), With>, @@ -219,7 +219,7 @@ fn vendor_swap_probe( let (brog, dobbins) = (find(BROG_ENTRY), find(DOBBINS_ENTRY)); // Both names must be cached before the legs read them back out of the VM — the // ask-once resolve is what the unit feed itself would send on the open frame. - let mut named = |g: Option| g.is_some_and(|g| names.resolve(g, &net).is_some()); + let named = |g: Option| g.is_some_and(|g| names.resolve(g, &net).is_some()); let (brog_named, dobbins_named) = (named(brog), named(dobbins)); if brog_named && dobbins_named { probe.brog = brog; diff --git a/crates/benilla-app/src/capture/probes/ground_census.rs b/crates/benilla-app/src/capture/probes/ground_census.rs index 22d6ebe2..6d3c677c 100644 --- a/crates/benilla-app/src/capture/probes/ground_census.rs +++ b/crates/benilla-app/src/capture/probes/ground_census.rs @@ -121,7 +121,7 @@ fn fire_ground_census( // reader back to the server's own tables to answer it — and the cache is already warm: it is // asked at first *sight* for every unit that streams in. A `None` is a name still in flight, // never a missing one. - mut names: ResMut, + names: Res, net_commands: Res, world: benilla_world::collision::WorldCollision, point: benilla_world::world_point::WorldPoint, diff --git a/crates/benilla-app/src/creature_anim/spell_visual.rs b/crates/benilla-app/src/creature_anim/spell_visual.rs index 1b43e49b..339f7e66 100644 --- a/crates/benilla-app/src/creature_anim/spell_visual.rs +++ b/crates/benilla-app/src/creature_anim/spell_visual.rs @@ -334,7 +334,7 @@ pub(super) fn load_spell_visuals(mut commands: Commands, assets: Option { displays: Option>, - items: Option>, + items: Option>, net: Option>, units: Query<'w, 's, (Option<&'static NetEntity>, &'static ObjectStore)>, } @@ -355,7 +355,7 @@ impl WeaponVisualSrc<'_, '_> { EntityKind::Player => s .player_visible_item_entry(17) .filter(|e| *e != 0) - .and_then(|entry| self.items.as_deref_mut()?.held(entry, self.net.as_deref()?)) + .and_then(|entry| self.items.as_deref()?.held(entry, self.net.as_deref()?)) .map(|t| t.display_info_id), _ => None, } diff --git a/crates/benilla-app/src/death.rs b/crates/benilla-app/src/death.rs index ec6a1e5c..62ec55d8 100644 --- a/crates/benilla-app/src/death.rs +++ b/crates/benilla-app/src/death.rs @@ -187,14 +187,14 @@ fn feed_death( // comparison must be the same clock. time: Res>, mut feed: ResMut, - mut names: ResMut, + names: Res, net: Res, index: Res, transforms: Query<&Transform>, map: Option>, status: Res, spells: Option>, - mut items: ResMut, + items: Res, ) { // **Only a LIVE session's descriptor is a snapshot** (decision 1732). A reconnect-able // disconnect keeps the self avatar as the local puppet (0065) — descriptor and all — so @@ -259,7 +259,7 @@ fn feed_death( // `HasSoulstone()` — see [`resolve_self_res`] for the three gates and their order. Not a // per-frame inventory walk in general: the dead gate is first, so while alive this is one // health read, and while dead-unreleased the walk only runs on a zero field. - self_res_label: resolve_self_res(&store.0, &mut items, spells.as_deref(), &net) + self_res_label: resolve_self_res(&store.0, &items, spells.as_deref(), &net) .map(|r| r.label().to_owned()), }); @@ -470,7 +470,7 @@ impl SelfRes { /// the lookup fires answers within a frame or two and the next resolve sees it. fn resolve_self_res( store: &benilla_protocol::ObjectFields, - items: &mut crate::items::Items, + items: &crate::items::Items, spells: Option<&Spells>, commands: &NetCommands, ) -> Option { @@ -628,7 +628,7 @@ fn drain_death( match store.as_ref().and_then(|store| { resolve_self_res( store, - &mut ladder.items, + &ladder.items, ladder.spells.as_deref(), &ladder.commands, ) @@ -837,7 +837,7 @@ mod self_res_tests { #[test] fn the_dead_gate_precedes_the_field() { let (net, _rx) = commands(); - let mut items = Items::default(); + let items = Items::default(); let spells = catalog([(REINCARNATION, named("Reincarnation", [94, 0, 0]))]); let alive = ObjectFields::from_pairs(&[ @@ -846,7 +846,7 @@ mod self_res_tests { (F_SELF_RES, REINCARNATION), ]); assert_eq!( - resolve_self_res(&alive, &mut items, Some(&spells), &net), + resolve_self_res(&alive, &items, Some(&spells), &net), None, "alive with a self-res owed: nil" ); @@ -859,7 +859,7 @@ mod self_res_tests { (F_SELF_RES, REINCARNATION), ]); assert_eq!( - resolve_self_res(&ghost, &mut items, Some(&spells), &net), + resolve_self_res(&ghost, &items, Some(&spells), &net), None, "a released ghost still holds the field, and still answers nil" ); @@ -870,7 +870,7 @@ mod self_res_tests { (F_SELF_RES, REINCARNATION), ]); assert_eq!( - resolve_self_res(&dead, &mut items, Some(&spells), &net), + resolve_self_res(&dead, &items, Some(&spells), &net), Some(SelfRes::Spell { spell: REINCARNATION, label: "Reincarnation".into(), @@ -883,12 +883,12 @@ mod self_res_tests { #[test] fn an_unresolvable_spell_id_reads_unknown_not_nil() { let (net, _rx) = commands(); - let mut items = Items::default(); + let items = Items::default(); let dead = ObjectFields::from_pairs(&[(F_MAXHEALTH, 4000), (F_HEALTH, 0), (F_SELF_RES, 999_999)]); for spells in [None, Some(catalog([]))] { assert_eq!( - resolve_self_res(&dead, &mut items, spells.as_ref(), &net), + resolve_self_res(&dead, &items, spells.as_ref(), &net), Some(SelfRes::Spell { spell: 999_999, label: "UNKNOWN".into(), @@ -948,7 +948,7 @@ mod self_res_tests { } let dead = ObjectFields::from_pairs(&pairs); - match resolve_self_res(&dead, &mut items, Some(&spells), &net) { + match resolve_self_res(&dead, &items, Some(&spells), &net) { Some(SelfRes::Item { entry, label, .. }) => { assert_eq!( entry, 12, @@ -961,9 +961,6 @@ mod self_res_tests { // Take it away and the answer is nil again — not "UNKNOWN", which is the spell leg's. let bare = ObjectFields::from_pairs(&[(F_MAXHEALTH, 4000), (F_HEALTH, 0)]); - assert_eq!( - resolve_self_res(&bare, &mut items, Some(&spells), &net), - None - ); + assert_eq!(resolve_self_res(&bare, &items, Some(&spells), &net), None); } } diff --git a/crates/benilla-app/src/debug_panel/inspect.rs b/crates/benilla-app/src/debug_panel/inspect.rs index 77a7f322..41a3b096 100644 --- a/crates/benilla-app/src/debug_panel/inspect.rs +++ b/crates/benilla-app/src/debug_panel/inspect.rs @@ -209,7 +209,7 @@ pub(super) fn inspect_ui( drivers: Query<&crate::creature_anim::AnimDriver>, anim_data: Option>, spells: Option>, - mut names: ResMut, + names: Res, net_commands: Res, // Bundled into one param (Bevy's system-function arity ceiling): the copy-click button, and // the flag it must yield to — a left press this frame the UI already consumed as a diff --git a/crates/benilla-app/src/debug_panel/journal.rs b/crates/benilla-app/src/debug_panel/journal.rs index 06a7041f..fcae929c 100644 --- a/crates/benilla-app/src/debug_panel/journal.rs +++ b/crates/benilla-app/src/debug_panel/journal.rs @@ -161,7 +161,7 @@ pub(super) fn journal_ui( inspect: Res, journal: Res, self_guid: Res, - mut names: ResMut, + names: Res, net_commands: Res, time: Res