creature_anim: the loot sparkle's switch starts where its CVar does
Reported from play: no sparkle on a mining vein, while lootable mobs sparkled fine. That split is the diagnosis — only the GameObject arm reads the switch. `LootSparkleMode` derived `Default`, so it booted `false` while its row registered `"1"`. A boot fires `CvarChanged` only for rows `config.toml` actually carries, and writing `"1"` over a value already `"1"` is a no-op that fires nothing either, so nothing could ever tell the resource its real default — not the boot, and not the player toggling the CVar. The feature was inert for everyone who had never written that row. `vplates.rs`'s `VPlateMode::default` carries this warning in as many words, from `NameplateOcclusion` shipping inert the same way, and this is the same mistake one feature later. So: a hand-written `Default`, and the twin of that module's test, reading the registered row rather than restating it. Swept the rest — of the 32 resources a `CvarChanged` observer writes, 6 derive `Default` and every one of their rows registers `"0"`, so this was the only one. The doc row kept two stale claims and both are corrected: the test has had two arms (the activate bit *or* `GAMEOBJECT_TYPE_CHEST`) since it was widened, and `FxStage` is immaterial here — `Particles\LootFX.m2`, the `"HARDCODED Loot Art"` row's own model, holds one 2400 ms sequence with `flags = 0`, and bit 0 clear loops, so `FxAnimLife::arm` repeats it whatever the stage says. Still unverified in world: whether veins, herbs and chests sparkle, and whether anything sparkles that should not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -1086,11 +1086,24 @@ pub(crate) fn arm_aura_state_fx(
|
||||
/// just a corpse. The mod's own spelling; registered in `cvars.rs`.
|
||||
pub(crate) const CVAR_LOOT_SPARKLE: &str = "LootSparkle";
|
||||
|
||||
/// [`CVAR_LOOT_SPARKLE`]'s applied value, seeded by the boot-time `CvarChanged` observer as
|
||||
/// `vplates.rs`'s `VPlateMode` is.
|
||||
#[derive(Resource, Default)]
|
||||
/// [`CVAR_LOOT_SPARKLE`]'s applied value.
|
||||
///
|
||||
/// **The default must equal the registered row's**, because a boot emits `CvarChanged` only for
|
||||
/// rows `config.toml` actually carries, and a write of `"1"` over a `"1"` is a no-op that emits
|
||||
/// nothing either — so nothing would ever tell a derived `false` that the CVar reads on. That is
|
||||
/// exactly how this feature shipped inert: the row registered `"1"`, the resource derived
|
||||
/// `false`, and no vein, herb or chest sparkled for anyone who had never touched the CVar.
|
||||
/// `vplates.rs`'s `VPlateMode::default` carries the same warning for the same reason;
|
||||
/// [`tests::the_loot_sparkle_default_agrees_with_its_registered_cvar`] is the check.
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct LootSparkleMode(pub(crate) bool);
|
||||
|
||||
impl Default for LootSparkleMode {
|
||||
fn default() -> Self {
|
||||
Self(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a change of the switch.
|
||||
pub(crate) fn on_loot_sparkle_cvar(
|
||||
ev: On<crate::cvars::CvarChanged>,
|
||||
|
||||
@@ -1653,6 +1653,26 @@ fn an_anim_only_state_kit_still_arms_on_the_aura_add_edge() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The switch's applied default must equal the row it reads, or the feature ships inert.
|
||||
///
|
||||
/// A boot fires `CvarChanged` only for rows `config.toml` carries, and re-writing a row with the
|
||||
/// value it already holds is a no-op that fires nothing either — so a derived `false` beside a
|
||||
/// registered `"1"` is never corrected, by the boot or by the player. That is how the sparkle
|
||||
/// shipped off: the decision below was right, the switch feeding it was never turned on. Twin of
|
||||
/// `vplates.rs`'s `the_occlusion_default_agrees_with_its_registered_cvar`.
|
||||
#[test]
|
||||
fn the_loot_sparkle_default_agrees_with_its_registered_cvar() {
|
||||
let registered = crate::cvars::registered_pairs()
|
||||
.find(|(n, _)| n.eq_ignore_ascii_case(super::CVAR_LOOT_SPARKLE))
|
||||
.map(|(_, d)| d)
|
||||
.expect("LootSparkle is registered");
|
||||
assert_eq!(
|
||||
super::LootSparkleMode::default().0,
|
||||
registered != "0",
|
||||
"the applied default must equal the registered CVar's ({registered:?})"
|
||||
);
|
||||
}
|
||||
|
||||
/// SuperWoW's `LootSparkle`: 1.12 sparkles a lootable corpse and nothing else, so the GameObject
|
||||
/// arm is a deviation that has to be switchable, and the switch off has to answer exactly 1.12.
|
||||
///
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user