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
193 lines
7.1 KiB
Rust
193 lines
7.1 KiB
Rust
//! 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
|
||
);
|
||
}
|
||
}
|