/target, /assist and /follow, and right click opens a clam
- /target and /assist find units by name like the original, and /follow walks you behind your target - right click opens clams and lockboxes, and the tooltip promises it - the freeze thaws clean, poses release instead of sticking, and you drop to neutral standing before it grips - unit frame art layers over the bars like the original Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -306,6 +306,53 @@ impl ZKey {
|
||||
pub const fn raw(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Unpack this key back into its fields.
|
||||
#[inline]
|
||||
pub const fn parts(self) -> ZParts {
|
||||
unpack(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The fields of a packed [`ZKey`], in significance order — the readable face of an opaque `u64`.
|
||||
///
|
||||
/// A draw-order key is a single number by design (one sort produces the whole render list), which
|
||||
/// makes every question *about* an order — "why is this over that?", "which pairs would a re-rank
|
||||
/// invert?" — unanswerable at a glance. [`unpack`] is that answer, and it is why
|
||||
/// [`crate::script::ExtractedQuad::z`] can stay a bare `u64`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct ZParts {
|
||||
/// The frame strata bucket id (0..=9, [`Strata::index`]).
|
||||
pub strata: u8,
|
||||
/// The frame level within the strata bucket.
|
||||
pub level: u16,
|
||||
/// The draw layer (0..=4, [`DrawLayer::index`]) — bucket-wide, above the frame (decision 0884).
|
||||
pub layer: u8,
|
||||
/// `true` for a font string: all textures of a `(strata, level, layer)` precede all its text.
|
||||
pub is_fontstring: bool,
|
||||
/// The owning frame's link-stamp — its position in the client's intrusive bucket list.
|
||||
pub insertion: u32,
|
||||
/// `false` for a frame's own slot, `true` for one of its regions.
|
||||
pub is_region: bool,
|
||||
/// The Era `textureSubLevel` knob; inert for 1.12 content (always 0 there).
|
||||
pub sub_level: i8,
|
||||
/// Declaration order within the owning frame's layer.
|
||||
pub decl: u16,
|
||||
}
|
||||
|
||||
/// Unpack a raw [`ZKey`] (e.g. [`crate::script::ExtractedQuad::z`]) into its fields.
|
||||
#[inline]
|
||||
pub const fn unpack(raw: u64) -> ZParts {
|
||||
ZParts {
|
||||
strata: ((raw >> STRATUM_SHIFT) & 0xf) as u8,
|
||||
level: ((raw >> LEVEL_SHIFT) & 0xffff) as u16,
|
||||
layer: ((raw >> LAYER_SHIFT) & 0x7) as u8,
|
||||
is_fontstring: (raw >> FONTSTRING_SHIFT) & 1 == 1,
|
||||
insertion: ((raw >> INSERTION_SHIFT) & ((1 << INSERTION_BITS) - 1)) as u32,
|
||||
is_region: (raw >> IS_REGION_SHIFT) & 1 == 1,
|
||||
sub_level: (((raw >> SUBLEVEL_SHIFT) & 0xff) as i16 - 128) as i8,
|
||||
decl: ((raw >> DECL_SHIFT) & ((1 << DECL_BITS) - 1)) as u16,
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────────────────────
|
||||
@@ -329,9 +376,9 @@ pub enum ZTarget {
|
||||
/// (`propagation.md`) — a child of a hidden frame already carries `effective_visible == false` and is
|
||||
/// skipped here. Each visible frame emits its own [`ZTarget::Frame`] entry followed by one
|
||||
/// [`ZTarget::Region`] per owned region; the returned vec is sorted ascending by [`ZKey`], which *is*
|
||||
/// the total draw order (strata → **layer** → frame link-stamp → is-region → sub-level →
|
||||
/// texture<fontstring → decl; the layer outranks the frame — see the `ZKey` bit-layout note and
|
||||
/// decision 0884). Ties are impossible: distinct frames differ in link-stamp, a frame and its
|
||||
/// the total draw order (strata → frame level → **draw layer** → texture<fontstring → frame
|
||||
/// link-stamp → is-region → sub-level → decl; the layer outranks the frame — see the `ZKey`
|
||||
/// bit-layout note and decision 0884). Ties are impossible: distinct frames differ in link-stamp, a frame and its
|
||||
/// regions differ in the is-region bit, and a frame's regions differ in the remaining fields.
|
||||
///
|
||||
/// Ordering only: this emits an entry for *every* region of a visible frame. Region-level
|
||||
|
||||
@@ -425,8 +425,9 @@ fn required_level_one_is_hidden() {
|
||||
/// (white) when the instance carries letter text, the green-escaped "<Made by %s>" otherwise —
|
||||
/// then READABLE off the instance text id (its template's PageText is 0 — the
|
||||
/// director-reported gap). An unresolved creator (name query in flight) emits no line. LOCKED
|
||||
/// yields to the instance UNLOCKED bit — and ITEM_OPENABLE never emits from `SetBagItem` (a
|
||||
/// p6=1 source: the `0x52e2e8` bypass), so the unlocked chest shows its bare name.
|
||||
/// yields to the instance UNLOCKED bit — which is also what un-gates ITEM_OPENABLE: a bag hover
|
||||
/// on an openable instance DOES show the green `<Right Click to Open>` (director-observed on a
|
||||
/// clam; see `ItemInstance::openable_source` for why this overrides §1-OPENABLE's p6 reading).
|
||||
#[test]
|
||||
fn instance_tail_creator_and_readable() {
|
||||
let mut s = script();
|
||||
@@ -458,16 +459,26 @@ fn instance_tail_creator_and_readable() {
|
||||
flags,
|
||||
..Default::default()
|
||||
};
|
||||
s.set_item_template(
|
||||
7973,
|
||||
ItemTemplateView {
|
||||
name: "Small Barnacled Clam".into(),
|
||||
quality: 1,
|
||||
flags: 0x4,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let mut slots = HashMap::new();
|
||||
slots.insert(1, slot(889, true, Some("One"), 0)); // the mail letter
|
||||
slots.insert(2, slot(889, true, None, 0)); // creator name still in flight
|
||||
slots.insert(3, slot(2589, false, Some("Geoffrey"), 0)); // crafted, still locked
|
||||
slots.insert(4, slot(2589, false, None, 0x4)); // unlocked → LOCKED gone, and NO open line
|
||||
slots.insert(4, slot(2589, false, None, 0x4)); // unlocked → LOCKED gone, open line on
|
||||
slots.insert(5, slot(7973, false, None, 0)); // the clam: lockless, openable outright
|
||||
s.set_container(
|
||||
0,
|
||||
Some(ContainerState {
|
||||
name: Some("Backpack".into()),
|
||||
num_slots: 4,
|
||||
num_slots: 5,
|
||||
slots,
|
||||
}),
|
||||
);
|
||||
@@ -502,11 +513,18 @@ fn instance_tail_creator_and_readable() {
|
||||
vec!["Heavy Chest", "Locked", "|cff00ff00<Made by Geoffrey>|r"],
|
||||
"CREATED_BY carries the string's own green escape; locked chest hides OPENABLE"
|
||||
);
|
||||
// The instance UNLOCKED bit retires the LOCKED line — and OPENABLE still never emits
|
||||
// (SetBagItem is p6=1; only send-mail/auction/buyback sources reach the emitter in 5875).
|
||||
// The instance UNLOCKED bit retires the LOCKED line AND satisfies the openable lock sub-gate.
|
||||
s.run(r#"TT:SetOwner(getglobal("SlotL"), "ANCHOR_RIGHT"); TT:SetBagItem(0, 4)"#)
|
||||
.unwrap();
|
||||
let lines = lines_of(&mut s);
|
||||
let texts: Vec<&str> = lines.iter().map(|(t, _)| t.as_str()).collect();
|
||||
assert_eq!(texts, vec!["Heavy Chest", "<Right Click to Open>"]);
|
||||
assert_eq!(lines[1].1, [0.0, 1.0, 0.0, 1.0], "OPENABLE is green");
|
||||
// The director's case: a lockless LOOTABLE template (a clam) is openable outright — name +
|
||||
// the green line, nothing between them.
|
||||
s.run(r#"TT:SetOwner(getglobal("SlotL"), "ANCHOR_RIGHT"); TT:SetBagItem(0, 5)"#)
|
||||
.unwrap();
|
||||
let texts: Vec<String> = lines_of(&mut s).into_iter().map(|(t, _)| t).collect();
|
||||
assert_eq!(texts, vec!["Heavy Chest"]);
|
||||
assert_eq!(texts, vec!["Small Barnacled Clam", "<Right Click to Open>"]);
|
||||
assert!(s.take_errors().is_empty());
|
||||
}
|
||||
|
||||
@@ -309,7 +309,10 @@ pub(super) fn install_methods(lua: &Lua, m: &Table) -> mlua::Result<()> {
|
||||
creator: s.creator.clone(),
|
||||
has_text: false,
|
||||
flags: s.flags,
|
||||
// p6=1 in the ref — SetInventoryItem can never emit OPENABLE.
|
||||
// Unobservable either way: nothing openable is equippable, so the
|
||||
// doll hover has no case to show. Left `false` (wow-re
|
||||
// §1-OPENABLE's p6 reading) rather than flipped on the bag's
|
||||
// evidence — see `ItemInstance::openable_source`.
|
||||
openable_source: false,
|
||||
},
|
||||
)
|
||||
@@ -375,8 +378,11 @@ pub(super) fn install_methods(lua: &Lua, m: &Table) -> mlua::Result<()> {
|
||||
creator: s.creator.clone(),
|
||||
has_text: s.readable,
|
||||
flags: s.flags,
|
||||
// p6=1 in the ref — SetBagItem can never emit OPENABLE (§1-OPENABLE).
|
||||
openable_source: false,
|
||||
// The bag hover DOES emit `<Right Click to Open>` — director-observed
|
||||
// (a clam in a bag), against wow-re §1-OPENABLE's p6 reading. See
|
||||
// `ItemInstance::openable_source`: that gate is under re-derivation,
|
||||
// and the observation is the ground truth we build on.
|
||||
openable_source: true,
|
||||
},
|
||||
),
|
||||
None => return Ok(MultiValue::from_vec(vec![Value::Nil])),
|
||||
|
||||
@@ -29,11 +29,24 @@ pub(super) struct ItemInstance {
|
||||
/// Instance `ITEM_FIELD_FLAGS` — the openable lock sub-gate reads UNLOCKED `0x4`
|
||||
/// (`0x52e30c`) and the wrapped-gift arm WRAPPED `0x8` (`0x52e31d`).
|
||||
pub flags: u32,
|
||||
/// The ref's `p6=0` source class (wow-re §1-OPENABLE, byte-census 2026-07-20): ITEM_OPENABLE
|
||||
/// is reachable ONLY from object-present p6=0 sources (SetSendMailItem, SetAuctionSellItem,
|
||||
/// SetBuybackItem) — `SetBagItem`/`SetInventoryItem` pass p6=1 and a `[this+0x440]` gate
|
||||
/// (`0x52e2e8`) bypasses the openable tree entirely. Every current benilla source is p6=1
|
||||
/// or template-only, so this stays `false` until those tooltips carry real instances.
|
||||
/// May this source emit the ITEM_OPENABLE line at all?
|
||||
///
|
||||
/// wow-re §1-OPENABLE (byte-census 2026-07-20) reads a `[this+0x440]` gate at `0x52e2e8` as
|
||||
/// bypassing the whole openable tree for every `p6=1` binding, and concludes that
|
||||
/// `SetBagItem` — the binding your own bags hover through — can NEVER emit it. **That
|
||||
/// conclusion is contradicted by ground truth:** the director's screenshot shows a Small
|
||||
/// Barnacled Clam hovered IN A BAG with the green `<Right Click to Open>` line, and vmangos's
|
||||
/// own `ITEM_FLAG_LOOTABLE` comment names that exact client behaviour ("It or lockid set
|
||||
/// enable for client show 'Right click to open'"). A line that could only ever appear on the
|
||||
/// send-mail / auction-sell / buyback tooltips would also be behaviourally absurd. The note is
|
||||
/// additionally self-inconsistent there (§1-CREATOR puts the GIFTCREATOR read at `0x52e32a`,
|
||||
/// inside the region §1-OPENABLE calls the openable emit + jump), so the transcription — not
|
||||
/// the observation — is the suspect. A wow-re §5 re-derivation of `[0x52e2e0, 0x52e376)` is in
|
||||
/// flight; until it lands, the bag hover follows the director's eyes.
|
||||
///
|
||||
/// The flag is kept rather than deleted because the *template-vs-instance* distinction it also
|
||||
/// carries is settled (gate 1, `0x52e2e0`: no object ⇒ no line — you can't right-click a
|
||||
/// hyperlink), and because the p6 split may still be real for the sources we cannot observe.
|
||||
pub openable_source: bool,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user