app+protocol: nampower's combat-log family — 35 events off one decode
Every packet this family reports was already decoded: `combat_log/` builds its chat lines from exactly these structs. `ui_np_events.rs` is a second consumer of that decode, not a second decoder, which is also why its arguments cannot drift from the chat log's. Built: `SPELL_DAMAGE_EVENT_*`, `AUTO_ATTACK_*`, the three `SPELL_HEAL_*`, the three `SPELL_ENERGIZE_*`, `SPELL_MISS_*`, `DAMAGE_SHIELD_*`, `ENVIRONMENTAL_DMG_*`, `SPELL_DISPEL_BY_*`, `UNIT_DIED`, the `*_UPDATE_DURATION_SELF` pair, the thirteen `UNIT_*_GUID`/ `PLAYER_GUILD_UPDATE_GUID` rows and `UNIT_COMBAT_GUID`. **A registration is the switch.** Upstream's README calls every `NP_Enable*Events` CVar deprecated — "registering the event is preferred" — and three of the eleven families have no CVar at all, so a CVar-only reading would leave `SPELL_DAMAGE_EVENT_*` unreachable for every addon and `SPELL_ENERGIZE_*` unreachable for all of them, since nothing installed writes its CVar. `refresh_listeners` reads the VM's own registration table (the same one `GetFramesRegisteredForEvent` reads), so a family is wanted when its CVar is set OR something is listening. That is also what keeps the cost honest: with nobody registered a damage packet queues nothing, rather than building eight arguments for a fire that reaches no frame. Nine `NP_*` CVars move from stored-only to LIVE, and `NP_EnableSpellEnergizeEvents` is registered for the first time — the first pass took the names off `NampowerSettings`' writes rather than off the DLL's own CVar table. The two reasons written at the auto-attack and heal rows were both about the *cast* path, and both were answered by the combat-log decode that was already there. The descriptor indices the GUID-keyed family watches are written out and then cross-checked against the two tables the tree already keeps — `unit_rec::FIELDS` and the `FIELD_UNIT_*` constants. A hand-written descriptor index is exactly the number that is plausibly wrong and silently so: the event simply never fires, which no amount of play distinguishes from "the server never sent one". `AttackerState` gains `sub_damage_count`, which is `AUTO_ATTACK_*`'s arg6; vmangos writes one block, but a server splitting a swing across schools sends more. Two test notes worth keeping: the edge tests run through the schedule, because `run_system_once` builds a fresh system each call and the `MessageReader` cursor restarts — a test written that way passes whatever the system does with the second edge. And the data-gated suite held at exactly 269, after the event gate correctly named all 35 new names. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -1289,44 +1289,42 @@ pub(crate) const REGISTERED: &[Registered] = &[
|
||||
// GUID-keyed unit events, so an addon can cut the stream to the units it watches.
|
||||
// `SuperCleveRoidMacros`'s `NampowerAPI.lua` writes all six at setup.
|
||||
//
|
||||
// STORED ONLY, and the reason is one step further back than the other `NP_Enable*` rows:
|
||||
// benilla fires no GUID-keyed unit event at all — no `UNIT_HEALTH_GUID`, `UNIT_MANA_GUID`,
|
||||
// `UNIT_RAGE_GUID`, `UNIT_ENERGY_GUID` or `UNIT_COMBAT_GUID` — so there is no stream for a
|
||||
// filter to narrow. Registering them is what stops the engine's unknown-CVar warning and lets
|
||||
// the addon's writes persist; it is not a claim that the events arrive. Defaults are
|
||||
// upstream's own documented `0`.
|
||||
// **All six are LIVE**, which reverses the reason written here for one round: it said there
|
||||
// was no stream for a filter to narrow, which was true until `ui_np_events.rs` built one.
|
||||
// The thirteen `UNIT_*_GUID` names and `UNIT_COMBAT_GUID` now fire off the descriptor-edge
|
||||
// stream `net::FieldChanged` already carries, so the switches select rather than pretend.
|
||||
// Defaults stay upstream's own documented `0`.
|
||||
ours(
|
||||
"NP_EnableUnitEventsGuid",
|
||||
"0",
|
||||
"nampower v2 addon-compat, STORED ONLY: benilla fires no GUID-keyed unit event, so there \
|
||||
is no stream for this switch to open",
|
||||
"nampower v2 addon-compat, LIVE: the gate on the thirteen `UNIT_*_GUID` names and \
|
||||
`UNIT_COMBAT_GUID` — see crate::ui_np_events::NpEventGates",
|
||||
),
|
||||
ours(
|
||||
"NP_EnableUnitEventsGuidFiltering",
|
||||
"0",
|
||||
"nampower v2 addon-compat, STORED ONLY: the filter half of `NP_EnableUnitEventsGuid`, and \
|
||||
there is nothing to filter",
|
||||
"nampower v2 addon-compat, LIVE: whether the four token switches below narrow the family \
|
||||
at all; off, every unit passes — see crate::ui_np_events::UnitContext::passes",
|
||||
),
|
||||
ours(
|
||||
"NP_EnableUnitEventsMouseover",
|
||||
"0",
|
||||
"nampower v2 addon-compat, STORED ONLY: which tokens fire the GUID-keyed unit events, \
|
||||
none of which this client raises",
|
||||
"nampower v2 addon-compat, LIVE: lets the mouseover through the filter above",
|
||||
),
|
||||
ours(
|
||||
"NP_EnableUnitEventsParty",
|
||||
"0",
|
||||
"nampower v2 addon-compat, STORED ONLY: see `NP_EnableUnitEventsMouseover`",
|
||||
"nampower v2 addon-compat, LIVE: lets a party member through the filter",
|
||||
),
|
||||
ours(
|
||||
"NP_EnableUnitEventsPet",
|
||||
"0",
|
||||
"nampower v2 addon-compat, STORED ONLY: see `NP_EnableUnitEventsMouseover`",
|
||||
"nampower v2 addon-compat, LIVE: lets the player's pet through the filter",
|
||||
),
|
||||
ours(
|
||||
"NP_EnableUnitEventsRaid",
|
||||
"0",
|
||||
"nampower v2 addon-compat, STORED ONLY: see `NP_EnableUnitEventsMouseover`",
|
||||
"nampower v2 addon-compat, LIVE: lets a raid member through the filter",
|
||||
),
|
||||
ours(
|
||||
"NP_EnableSpellGoEvents",
|
||||
@@ -1334,17 +1332,31 @@ pub(crate) const REGISTERED: &[Registered] = &[
|
||||
"nampower v2 addon-compat, LIVE: the gate on `SPELL_GO_SELF`/`_OTHER`, whose two target \
|
||||
counts come off the same `SMSG_SPELL_GO` hit and miss lists",
|
||||
),
|
||||
// Both were STORED ONLY for the reason written at each: no melee swing edge, and nothing
|
||||
// reading `SMSG_SPELLHEALLOG`. Both reasons were about the *cast* path, and both turned out
|
||||
// to be answered from the combat-log decode that was already there — the swing is
|
||||
// `SMSG_ATTACKERSTATEUPDATE`, which `combat_log/` has parsed all along to write its chat
|
||||
// line. `ui_np_events.rs` is the second consumer of that same decode.
|
||||
ours(
|
||||
"NP_EnableAutoAttackEvents",
|
||||
"0",
|
||||
"nampower v2 addon-compat, STORED ONLY: benilla fires no `AUTO_ATTACK_SELF`/`_OTHER` — \
|
||||
the melee swing raises no cast edge here to hang one on",
|
||||
"nampower v2 addon-compat, LIVE: the gate on `AUTO_ATTACK_SELF`/`_OTHER`, off \
|
||||
`SMSG_ATTACKERSTATEUPDATE` — see crate::ui_np_events::NpEventGates",
|
||||
),
|
||||
ours(
|
||||
"NP_EnableSpellHealEvents",
|
||||
"0",
|
||||
"nampower v2 addon-compat, STORED ONLY: benilla fires no `SPELL_HEAL_BY_SELF`/`_OTHER`/\
|
||||
`_ON_SELF` — nothing reads `SMSG_SPELLHEALLOG`'s amount",
|
||||
"nampower v2 addon-compat, LIVE: the gate on `SPELL_HEAL_BY_SELF`/`_BY_OTHER`/`_ON_SELF`, \
|
||||
off `SMSG_SPELLHEALLOG` and a `PERIODIC_HEAL` tick",
|
||||
),
|
||||
// The seventh `NP_Enable*` row, registered a round after the other six: upstream has it and
|
||||
// nothing installed here writes it, which is why it was missed — the first pass took the
|
||||
// names off `NampowerSettings`' own writes rather than off the DLL's CVar table.
|
||||
ours(
|
||||
"NP_EnableSpellEnergizeEvents",
|
||||
"0",
|
||||
"nampower v2 addon-compat, LIVE: the gate on `SPELL_ENERGIZE_BY_SELF`/`_BY_OTHER`/\
|
||||
`_ON_SELF`, off `SMSG_SPELLENERGIZELOG` and a `PERIODIC_ENERGIZE` tick",
|
||||
),
|
||||
// ── SuperWoW-era `ShowVKeyCastbar` ────────────────────────────────────────────────────────
|
||||
//
|
||||
@@ -4925,48 +4937,10 @@ mod tests {
|
||||
"NP_RetryServerRejectedSpells",
|
||||
"NampowerSettings' behaviour toggles",
|
||||
),
|
||||
// nampower v2's event-class switches. Three are deliberately NOT in this list —
|
||||
// `NP_EnableAuraCastEvents`, `NP_EnableSpellStartEvents` and
|
||||
// `NP_EnableSpellGoEvents` — because `ui_cast_events.rs` reads each as a real event
|
||||
// gate and must keep proving so. The two below name event classes benilla emits none
|
||||
// of (each row in `REGISTERED` says what it would take), so pfUI's libdebuff — which
|
||||
// writes all five at `PLAYER_LOGIN` — is their only reader.
|
||||
(
|
||||
"NP_EnableAutoAttackEvents",
|
||||
"pfUI libs/libdebuff.lua writes it at PLAYER_LOGIN; no AUTO_ATTACK_* here",
|
||||
),
|
||||
(
|
||||
"NP_EnableSpellHealEvents",
|
||||
"pfUI libs/libdebuff.lua writes it at PLAYER_LOGIN; no SPELL_HEAL_* here",
|
||||
),
|
||||
// The six unit-event switches: `SuperCleveRoidMacros`'s `NampowerAPI.lua` writes all
|
||||
// six at setup. One step further back than the four above — there is no GUID-keyed
|
||||
// unit event here for a token filter to narrow, so nothing could read them even in
|
||||
// principle until that family exists.
|
||||
(
|
||||
"NP_EnableUnitEventsGuid",
|
||||
"SuperCleveRoidMacros NampowerAPI.lua writes it; no GUID-keyed unit events here",
|
||||
),
|
||||
(
|
||||
"NP_EnableUnitEventsGuidFiltering",
|
||||
"SuperCleveRoidMacros NampowerAPI.lua writes it; no GUID-keyed unit events here",
|
||||
),
|
||||
(
|
||||
"NP_EnableUnitEventsMouseover",
|
||||
"SuperCleveRoidMacros NampowerAPI.lua writes it; no GUID-keyed unit events here",
|
||||
),
|
||||
(
|
||||
"NP_EnableUnitEventsParty",
|
||||
"SuperCleveRoidMacros NampowerAPI.lua writes it; no GUID-keyed unit events here",
|
||||
),
|
||||
(
|
||||
"NP_EnableUnitEventsPet",
|
||||
"SuperCleveRoidMacros NampowerAPI.lua writes it; no GUID-keyed unit events here",
|
||||
),
|
||||
(
|
||||
"NP_EnableUnitEventsRaid",
|
||||
"SuperCleveRoidMacros NampowerAPI.lua writes it; no GUID-keyed unit events here",
|
||||
),
|
||||
// **Every `NP_Enable*` row has left this list.** All seven are read by
|
||||
// `ui_cast_events.rs` or `ui_np_events.rs` as a real event gate and must keep
|
||||
// proving so — which is the whole point of the orphan test: the day one of them
|
||||
// stops being read, it belongs back here with a reason, not silently.
|
||||
// SuperWoW-era plate cast bar: pfUI's nameplates module writes 0 at load. benilla's
|
||||
// V-key plates draw no cast bar, so nothing here reads it — see the row's own note.
|
||||
(
|
||||
|
||||
@@ -67,6 +67,7 @@ use crate::ui_mail::UiMailPlugin;
|
||||
use crate::ui_merchant::UiMerchantPlugin;
|
||||
use crate::ui_mirror::UiMirrorPlugin;
|
||||
use crate::ui_net::UiNetPlugin;
|
||||
use crate::ui_np_events::NpEventsPlugin;
|
||||
use crate::ui_party::UiPartyPlugin;
|
||||
use crate::ui_pass::PlayerUiPlugin;
|
||||
use crate::ui_pet::UiPetPlugin;
|
||||
@@ -261,6 +262,7 @@ impl PluginGroup for GamePlugins {
|
||||
.add(UiCastPlugin)
|
||||
.add(UiMirrorPlugin)
|
||||
.add(SuperWowEventsPlugin)
|
||||
.add(NpEventsPlugin)
|
||||
.add(UiSelfCastPlugin)
|
||||
.add(crate::combat_text::CombatTextPlugin)
|
||||
.add(crate::nameplates::NameplatesPlugin)
|
||||
|
||||
@@ -177,6 +177,7 @@ mod ui_merchant;
|
||||
mod ui_mirror;
|
||||
mod ui_models;
|
||||
mod ui_net;
|
||||
mod ui_np_events;
|
||||
mod ui_party;
|
||||
mod ui_pass;
|
||||
mod ui_pet;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2760,6 +2760,168 @@ mod tests {
|
||||
"BAG_NEW_ITEMS_UPDATED",
|
||||
"ClassicAPI's new-item marker, fired from its own callback registry",
|
||||
),
|
||||
// ── nampower's combat-log family (`ui_np_events.rs`) ──────────────────────────
|
||||
//
|
||||
// All of these exist for one reason, so it is written once here rather than
|
||||
// thirty-five times: 1.12 reports a hit by composing a **localized sentence** and
|
||||
// firing `CHAT_MSG_SPELL_*` with it, so every damage meter of the era is a parser of
|
||||
// grammar. nampower hooks the same packet handlers and fires the numbers. An addon
|
||||
// written against it registers the exact spelling below or hears nothing.
|
||||
(
|
||||
"SPELL_DAMAGE_EVENT_SELF",
|
||||
"nampower: a spell's damage as numbers — target, caster, spell id, amount, the \
|
||||
absorb/block/resist triple, hitInfo, school, and the spell's own effect columns. \
|
||||
The alternative on the 1.12 surface is parsing CHAT_MSG_SPELL_SELF_DAMAGE's \
|
||||
sentence, which is localized and lossy",
|
||||
),
|
||||
(
|
||||
"SPELL_DAMAGE_EVENT_OTHER",
|
||||
"nampower: the same hit when somebody else dealt it; the split is by caster",
|
||||
),
|
||||
(
|
||||
"AUTO_ATTACK_SELF",
|
||||
"nampower: a melee swing's own report off SMSG_ATTACKERSTATEUPDATE, with the \
|
||||
victim state and the sub-damage count. 1.12 fires UNIT_COMBAT for the player \
|
||||
and target tokens only, and names no attacker",
|
||||
),
|
||||
(
|
||||
"AUTO_ATTACK_OTHER",
|
||||
"nampower: the swing half above for every other attacker",
|
||||
),
|
||||
(
|
||||
"SPELL_HEAL_BY_SELF",
|
||||
"nampower: a heal's amount off SMSG_SPELLHEALLOG, which 1.12 publishes only as \
|
||||
chat text. pfUI's libpredict reads the family to correct its own prediction",
|
||||
),
|
||||
(
|
||||
"SPELL_HEAL_BY_OTHER",
|
||||
"nampower: a heal cast by anyone else",
|
||||
),
|
||||
(
|
||||
"SPELL_HEAL_ON_SELF",
|
||||
"nampower: a heal landing on the player, whoever cast it. A self-heal fires this \
|
||||
AND BY_SELF, which is the only reading under which all three names mean \
|
||||
something distinct",
|
||||
),
|
||||
(
|
||||
"SPELL_ENERGIZE_BY_SELF",
|
||||
"nampower: a power gain's type and amount off SMSG_SPELLENERGIZELOG. 1.12's own \
|
||||
UNIT_MANA says a bar moved and never says why",
|
||||
),
|
||||
(
|
||||
"SPELL_ENERGIZE_BY_OTHER",
|
||||
"nampower: a power gain somebody else caused",
|
||||
),
|
||||
(
|
||||
"SPELL_ENERGIZE_ON_SELF",
|
||||
"nampower: a power gain landing on the player; the same two-of-three shape as \
|
||||
the heal family",
|
||||
),
|
||||
(
|
||||
"SPELL_MISS_SELF",
|
||||
"nampower: one row of SMSG_SPELLLOGMISS, with the miss reason as a number rather \
|
||||
than as the word a chat line would have spelled it",
|
||||
),
|
||||
(
|
||||
"SPELL_MISS_OTHER",
|
||||
"nampower: the miss half above for another caster",
|
||||
),
|
||||
(
|
||||
"DAMAGE_SHIELD_SELF",
|
||||
"nampower: a Thorns-style return hit off SMSG_SPELLDAMAGESHIELD; arg1 is the \
|
||||
shield's bearer, which is the packet's own first guid",
|
||||
),
|
||||
(
|
||||
"DAMAGE_SHIELD_OTHER",
|
||||
"nampower: the return hit above when the shield was not the player's",
|
||||
),
|
||||
(
|
||||
"ENVIRONMENTAL_DMG_SELF",
|
||||
"nampower: falling, drowning, lava and fatigue as a type and three numbers, off \
|
||||
SMSG_ENVIRONMENTALDAMAGELOG",
|
||||
),
|
||||
(
|
||||
"ENVIRONMENTAL_DMG_OTHER",
|
||||
"nampower: the same for another unit",
|
||||
),
|
||||
(
|
||||
"SPELL_DISPEL_BY_SELF",
|
||||
"nampower: one event per aura a dispel removed, by spell id. 1.12's chat line \
|
||||
names the aura in the player's own locale and nothing else",
|
||||
),
|
||||
(
|
||||
"SPELL_DISPEL_BY_OTHER",
|
||||
"nampower: a dispel somebody else landed",
|
||||
),
|
||||
(
|
||||
"UNIT_DIED",
|
||||
"nampower: a unit's death by guid. 1.12 has PLAYER_DEAD for the player alone and \
|
||||
nothing at all for anyone else — a nameplate addon otherwise learns of a death \
|
||||
only by polling UnitIsDead. Ungated, as upstream gates it with nothing",
|
||||
),
|
||||
(
|
||||
"BUFF_UPDATE_DURATION_SELF",
|
||||
"nampower: SMSG_UPDATE_AURA_DURATION as a slot and a time. 1.12 exposes a \
|
||||
duration through GetPlayerBuffTimeLeft only by polling, so a bar either polls \
|
||||
every frame or drifts",
|
||||
),
|
||||
(
|
||||
"DEBUFF_UPDATE_DURATION_SELF",
|
||||
"nampower: the harmful half of the pair above, split at aura slot 32",
|
||||
),
|
||||
// The thirteen GUID-keyed unit events share one argument shape — `guid, isPlayer, \
|
||||
// isTarget, isMouseover, isPet, partyIndex, raidIndex` — and one reason: 1.12's own \
|
||||
// `UNIT_*` events carry a **unit token**, so they fire only for the handful of units \
|
||||
// the player happens to have a token for. An addon watching a crowd (a raid frame \
|
||||
// set, a nameplate stack) cannot hear about a unit it has no token for at all.
|
||||
(
|
||||
"UNIT_HEALTH_GUID",
|
||||
"nampower: UNIT_HEALTH for a unit the player holds no token for",
|
||||
),
|
||||
("UNIT_MANA_GUID", "nampower: UNIT_MANA by guid"),
|
||||
("UNIT_RAGE_GUID", "nampower: UNIT_RAGE by guid"),
|
||||
("UNIT_ENERGY_GUID", "nampower: UNIT_ENERGY by guid"),
|
||||
(
|
||||
"UNIT_PET_GUID",
|
||||
"nampower: UNIT_PET by guid, off the summon field",
|
||||
),
|
||||
("UNIT_FLAGS_GUID", "nampower: UNIT_FLAGS by guid"),
|
||||
(
|
||||
"UNIT_AURA_GUID",
|
||||
"nampower: UNIT_AURA by guid, on an edge anywhere in the unit's aura region",
|
||||
),
|
||||
(
|
||||
"UNIT_DYNAMIC_FLAGS_GUID",
|
||||
"nampower: UNIT_DYNAMIC_FLAGS by guid (tapped, lootable, feign death)",
|
||||
),
|
||||
(
|
||||
"UNIT_NAME_UPDATE_GUID",
|
||||
"nampower: UNIT_NAME_UPDATE by guid. The one name in the family whose edge is \
|
||||
not a descriptor field — it comes from SMSG_NAME_QUERY_RESPONSE",
|
||||
),
|
||||
(
|
||||
"UNIT_PORTRAIT_UPDATE_GUID",
|
||||
"nampower: UNIT_PORTRAIT_UPDATE by guid, off the native display id",
|
||||
),
|
||||
(
|
||||
"UNIT_MODEL_CHANGED_GUID",
|
||||
"nampower: UNIT_MODEL_CHANGED by guid, off the display id",
|
||||
),
|
||||
(
|
||||
"UNIT_INVENTORY_CHANGED_GUID",
|
||||
"nampower: UNIT_INVENTORY_CHANGED by guid, off another player's public \
|
||||
PLAYER_VISIBLE_ITEM block",
|
||||
),
|
||||
(
|
||||
"PLAYER_GUILD_UPDATE_GUID",
|
||||
"nampower: PLAYER_GUILD_UPDATE by guid, off PLAYER_GUILDID",
|
||||
),
|
||||
(
|
||||
"UNIT_COMBAT_GUID",
|
||||
"nampower: 1.12's UNIT_COMBAT with a guid and the hit's school, so a damage \
|
||||
display can follow a unit it has no token for. The action word is the \
|
||||
reference's own classification, shared with the stock event",
|
||||
),
|
||||
(
|
||||
"QUEST_TURNED_IN",
|
||||
"ClassicAPI. Carries the one fact reachable nowhere else on this surface: the \
|
||||
|
||||
@@ -22,6 +22,18 @@
|
||||
use benilla_protocol::ObjectFields;
|
||||
use benilla_ui::script::RecValue;
|
||||
|
||||
/// The descriptor index [`FIELDS`] gives for one name, for a reader that needs the index and
|
||||
/// not the kind — `ui_np_events.rs`'s own index table checks itself against this, which is the
|
||||
/// point of exposing it: two hand-written tables of the same numbers, each able to catch the
|
||||
/// other, rather than one table nothing disagrees with.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn index_of(name: &str) -> Option<u16> {
|
||||
FIELDS
|
||||
.iter()
|
||||
.find(|(n, _, _)| *n == name)
|
||||
.map(|(_, i, _)| *i)
|
||||
}
|
||||
|
||||
/// `OBJECT_END`: every `UnitFields` index is relative to this.
|
||||
const OBJECT_END: u16 = 6;
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ pub struct AttackerState {
|
||||
/// `meleeSpellId` (`rec+0xac`), nonzero for an on-next-swing ability such as Heroic Strike; the
|
||||
/// reference only ever compares it to zero (`0x625e40`, `0x6246ca`).
|
||||
pub melee_spell_id: u32,
|
||||
/// How many sub-damage blocks the packet carried. vmangos writes one, but the field is the
|
||||
/// `subDamageCount` nampower's `AUTO_ATTACK_*` events report (`arg6`), so it is kept rather
|
||||
/// than inferred: a server that splits a swing across schools sends more than one.
|
||||
pub sub_damage_count: u8,
|
||||
/// The first sub-damage block's school (0 physical to 6 arcane, `GetFirstSchoolInMask`), 0
|
||||
/// with no blocks; a non-physical swing takes the combat log's `…SCHOOL` wording.
|
||||
pub school: u8,
|
||||
@@ -87,6 +91,7 @@ pub(super) fn read_attacker_state(r: &mut impl Read) -> io::Result<AttackerState
|
||||
victim,
|
||||
hit_info,
|
||||
damage,
|
||||
sub_damage_count: subs,
|
||||
victim_state,
|
||||
absorb,
|
||||
resist,
|
||||
|
||||
@@ -60,6 +60,22 @@ impl UiScript {
|
||||
fire_event_into(&self.lua, event, args);
|
||||
}
|
||||
|
||||
/// Whether any frame is registered for `event`.
|
||||
///
|
||||
/// For a producer whose work is only worth doing if somebody is listening — nampower's
|
||||
/// combat-log family, whose own CVar switches upstream now calls *deprecated* in favour of
|
||||
/// exactly this test ("registering the event is preferred"). The same lookup
|
||||
/// `GetFramesRegisteredForEvent` reads, so the two can never disagree about who is listening.
|
||||
#[must_use]
|
||||
pub fn has_listener(&self, event: &str) -> bool {
|
||||
self.lua
|
||||
.app_data_ref::<Model>()
|
||||
.expect("model app_data set")
|
||||
.event_to_frames
|
||||
.get(event)
|
||||
.is_some_and(|f| !f.is_empty())
|
||||
}
|
||||
|
||||
/// [`Self::fire_event`], answering what the dispatch reached ([`EventReach`]). Nothing about
|
||||
/// the fire changes; the number is simply kept rather than dropped, for the one caller that
|
||||
/// has to explain a silent event afterwards (`benilla-app`'s `area::feed_zone_events`).
|
||||
|
||||
Reference in New Issue
Block a user