Files
benilla-octo/crates/benilla-ui/src/script/compat/classic_api/enums.rs
T
gromandClaude Opus 5 81d96af35d Everwood: the HD/OctoWoW line of benilla, imported onto upstream
This checkout had no git history at all, so this one commit carries the whole
of the Everwood work as a single import on top of chairmanpow/benilla-octo's
`main` (706cd71). The two trees have genuinely diverged — each has files the
other does not — so this is a fork point, not a catch-up.

Today's work, which is the reason a baseline exists at all:

- Booth previews no longer cast world shadows (`NO_BOOTH_SHADOW`). Portrait
  characters were entering the sun's shadow cascades, so every skinned-rig
  shadow specialization compiled live mid-frame; the hitch detector logged
  "main thread blocked 1316 ms" beside a `compiled LIVE` warning. This was
  the Fashion/Tmog stutter.
- Per-pane dress-up: each `DressUpModel` pane assembles and stages its own
  tried-on look instead of every pane mirroring the live player.
- Item template cache is append-only JSONL written on the IO pool, replacing
  an 11.6 MB whole-map JSON rewrite on the main thread every 10 s.
- `WOW_SYSTEM_TIMING` rebuilt per-thread: it took 2 mutexes per system enter
  and 4 per exit, costing 15 ms a frame (590 fps -> 60) and corrupting the
  measurements it was taken to produce.
- The visibility-churn sweep moved to its own `WOW_FLASH_CHURN` switch, so a
  frame-cost reading no longer pays 2.7 ms a frame for it.

Local state (`benilla-config/`, `logs/`, `target/`) is excluded by the
existing .gitignore; the account credential it holds is not in this commit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-10-15 15:01:14 -06:00

193 lines
7.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! ClassicAPI's `Enum` namespace: the modern client's named constants, backported so an addon can
//! write `Enum.PowerType.Mana` instead of a bare `0`.
//!
//! Pure data — no client state is read, so these are const tables built once at boot. Only the
//! three groups the installed addons actually index are built (`PowerType`, `ItemQuality`,
//! `ItemClass`); the modern `Enum` has well over a hundred groups, and the rest are listed as
//! unbuilt in `docs/ADDON_COMPAT.md` rather than guessed at — a constant with the wrong number is
//! the worst kind of wrong, because it fails silently at whatever call site consumes it.
//!
//! **The values are the modern ones, deliberately**, because that is what ClassicAPI backports and
//! what an addon written against it compares against. For the three groups here that is mostly
//! free: the modern numbering *preserved* 1.12's, so `Enum.PowerType.Mana`/`Rage`/`Focus`/`Energy`
//! (0/1/2/3) compare correctly against this client's own `UnitPowerType` return, and
//! `Enum.ItemClass`/`ItemQuality` line up with 1.12's item classes and qualities for every value
//! 1.12 has.
//!
//! **One real collision, worth knowing about:** power type 4 is *Happiness* in 1.12 (hunter-pet
//! mood, `UNIT_FIELD_BYTES_0` byte 3 — see `UnitState::power_type`) and *ComboPoints* in the modern
//! enum. Both names are present and both map to 4, so `UnitPowerType("pet") ==
//! Enum.PowerType.ComboPoints` is true for a happy pet. Dropping `ComboPoints` would not fix that —
//! it would just turn the wrong comparison into a comparison against nil — and 1.12 has no combo
//! point *power type* at all (combo points are `GetComboPoints()`, not a bar), so there is no third
//! answer. The name is kept for ClassicAPI fidelity and `Happiness` is added alongside it so code
//! that means the 1.12 thing can say so.
use mlua::{Lua, Table};
/// `Enum.PowerType`. Modern values; 0–3 are also 1.12's, 4 is the documented collision above.
const POWER_TYPE: &[(&str, i64)] = &[
("HealthCost", -2),
("None", -1),
("Mana", 0),
("Rage", 1),
("Focus", 2),
("Energy", 3),
("ComboPoints", 4),
// 1.12's own name for 4, so a caller can mean the pet-mood bar explicitly.
("Happiness", 4),
("Runes", 5),
("RunicPower", 6),
("SoulShards", 7),
("LunarPower", 8),
("HolyPower", 9),
("Alternate", 10),
("Maelstrom", 11),
("Chi", 12),
("Insanity", 13),
("Obsolete", 14),
("Obsolete2", 15),
("ArcaneCharges", 16),
("Fury", 17),
("Pain", 18),
("NumPowerTypes", 19),
];
/// `Enum.ItemQuality`. 0–6 exist in 1.12 (`GetItemQualityColor`'s whole domain); 7 and 8 are later
/// additions kept so a `>= Enum.ItemQuality.Epic` comparison has the same ceiling it does upstream.
const ITEM_QUALITY: &[(&str, i64)] = &[
("Poor", 0),
("Standard", 1),
// The modern enum spells common quality `Standard`; every addon in the wild writes `Common`.
("Common", 1),
("Good", 2),
("Uncommon", 2),
("Rare", 3),
("Epic", 4),
("Legendary", 5),
("Artifact", 6),
("Heirloom", 7),
("WoWToken", 8),
];
/// `Enum.ItemClass`. 0–15 are 1.12's `Item.dbc` classes unchanged; 16+ are later additions, present
/// so a lookup keyed by one returns nothing rather than erroring.
const ITEM_CLASS: &[(&str, i64)] = &[
("Consumable", 0),
("Container", 1),
("Weapon", 2),
("Gem", 3),
("Armor", 4),
("Reagent", 5),
("Projectile", 6),
("Tradegoods", 7),
("ItemEnhancement", 8),
("Recipe", 9),
("CurrencyTokenObsolete", 10),
("Quiver", 11),
("Questitem", 12),
("Key", 13),
("PermanentObsolete", 14),
("Miscellaneous", 15),
("Glyph", 16),
("Battlepet", 17),
("WoWToken", 18),
("Profession", 19),
];
fn group(lua: &Lua, rows: &[(&str, i64)]) -> mlua::Result<Table> {
let t = lua.create_table()?;
for (name, value) in rows {
t.set(*name, *value)?;
}
Ok(t)
}
pub(super) fn install(lua: &Lua) -> mlua::Result<Table> {
let t = lua.create_table()?;
t.set("PowerType", group(lua, POWER_TYPE)?)?;
t.set("ItemQuality", group(lua, ITEM_QUALITY)?)?;
t.set("ItemClass", group(lua, ITEM_CLASS)?)?;
Ok(t)
}
#[cfg(test)]
mod tests {
use crate::script::{UiScript, UnitState};
/// The point of the whole module: the constant has to equal what this client's own getter
/// returns, or the addon's comparison is quietly always-false.
#[test]
fn power_types_agree_with_this_clients_unit_power_type() {
let mut s = UiScript::new().unwrap();
for (power_type, name) in [(0u8, "Mana"), (1, "Rage"), (2, "Focus"), (3, "Energy")] {
s.set_unit(
"target",
Some(UnitState {
exists: true,
power_type,
..Default::default()
}),
);
assert!(
s.eval::<bool>(&format!(
r#"return UnitPowerType("target") == Enum.PowerType.{name}"#
))
.unwrap(),
"Enum.PowerType.{name} must equal the {power_type} UnitPowerType answers"
);
}
}
/// 4 is happiness here and ComboPoints upstream. Both names resolve to it; the module comment
/// explains why that is the least-wrong answer, and this pins it so it cannot drift silently.
#[test]
fn four_is_both_happiness_and_combo_points() {
let s = UiScript::new().unwrap();
assert_eq!(s.eval::<i64>("return Enum.PowerType.Happiness").unwrap(), 4);
assert_eq!(
s.eval::<i64>("return Enum.PowerType.ComboPoints").unwrap(),
4
);
}
#[test]
fn item_quality_and_class_cover_the_1_12_domain_with_both_spellings() {
let s = UiScript::new().unwrap();
// The quality ramp 1.12 itself draws, in order.
assert_eq!(
s.eval::<Vec<i64>>(
"local q = Enum.ItemQuality \
return {q.Poor, q.Common, q.Uncommon, q.Rare, q.Epic, q.Legendary, q.Artifact}"
)
.unwrap(),
vec![0, 1, 2, 3, 4, 5, 6]
);
// `Standard`/`Good` are the modern enum's own spellings for 1 and 2.
assert!(s
.eval::<bool>(
"return Enum.ItemQuality.Standard == Enum.ItemQuality.Common \
and Enum.ItemQuality.Good == Enum.ItemQuality.Uncommon"
)
.unwrap());
assert_eq!(s.eval::<i64>("return Enum.ItemClass.Weapon").unwrap(), 2);
assert_eq!(s.eval::<i64>("return Enum.ItemClass.Armor").unwrap(), 4);
assert_eq!(
s.eval::<i64>("return Enum.ItemClass.Miscellaneous")
.unwrap(),
15
);
}
/// An unbuilt group must index to nil, not raise — `Enum.Foo` being nil is a shape addons
/// guard on, `Enum` itself being nil is what stopped a load.
#[test]
fn an_unbuilt_group_is_nil_rather_than_an_error() {
let s = UiScript::new().unwrap();
assert_eq!(
s.eval::<Option<i64>>("return Enum.ItemBind and 1").unwrap(),
None
);
}
}