channel lines get their warm color, and a /wave gets its sentence
- a channel line renders in the channel family's color, not the grey notice row the client consults and throws away - a text emote composes its sentence — gender and target picked from the game's own tables — so a /wave is a line in chat, not just an animation - a FontString's `font=` resolves a font object by name and gates the whole block, `<Gradient>` tints the art it sits on, and buttons' state fonts read `font=` too — Bagnon's white boxes and bare stack counts are fixed - the money frame sizes its coins from the digits themselves, so Bagnon's gold stops opening cramped Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -519,10 +519,12 @@ impl UiScript {
|
||||
worldmap::install(&lua)?;
|
||||
net_stats::install(&lua)?;
|
||||
|
||||
Ok(UiScript {
|
||||
let s = UiScript {
|
||||
lua,
|
||||
instructions: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0)),
|
||||
})
|
||||
};
|
||||
s.publish_number_width()?;
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// The embedded VM — for the Bevy plugin / TOC-XML loader to add the game-state API bindings
|
||||
@@ -879,12 +881,18 @@ impl UiScript {
|
||||
}
|
||||
|
||||
/// Feed the number font's per-digit advance widths (logical px, `'0'..='9'` in order) as the
|
||||
/// Lua global table `BENILLA_DIGIT_W` (keyed by digit *string*, `["0"].."["9"]`). The
|
||||
/// synchronous half of the money layout's metrics (the real `SmallMoneyFrame` calls
|
||||
/// `GetTextWidth` mid-update — MoneyFrame.lua l.202 — while our `GetStringWidth` measure
|
||||
/// round-trip is a frame late): the app measures NumberFontNormal's digits once per atlas
|
||||
/// scale and `BenillaNumberWidth` (MerchantFrame.xml) sums them per price.
|
||||
/// Lua global table `BENILLA_DIGIT_W` (keyed by digit *string*, `["0"].."["9"]`).
|
||||
///
|
||||
/// The synchronous half of the money layout's metrics. The real `SmallMoneyFrame` calls
|
||||
/// `GetTextWidth` mid-update (MoneyFrame.lua l.202) and the real client answers it inline;
|
||||
/// our `GetStringWidth` is served from the measure round-trip, which lands at extract — a frame
|
||||
/// AFTER the tick that set the text. A kit that sizes itself from its own label therefore reads
|
||||
/// 0 the first time and stays wrong until something redraws it.
|
||||
///
|
||||
/// The sum lives in [`Self::publish_number_width`] beside this, not in XML: its two callers are
|
||||
/// in different shipped files and no third file is a dependency of both.
|
||||
pub fn set_digit_advances(&mut self, advances: &[f32; 10]) {
|
||||
self.model_mut().digit_advances = Some(*advances);
|
||||
let set = || -> mlua::Result<()> {
|
||||
let t = self.lua.create_table()?;
|
||||
for (i, w) in advances.iter().enumerate() {
|
||||
@@ -899,6 +907,53 @@ impl UiScript {
|
||||
}
|
||||
}
|
||||
|
||||
/// Publish `BenillaNumberWidth(n)` — the laid-out width of a number in NumberFontNormal, the
|
||||
/// sum of its digits' advances from the [`Self::set_digit_advances`] feed.
|
||||
///
|
||||
/// **Exact, not an approximation.** The client's width law is a floor-and-add over a per-glyph
|
||||
/// advance with no kerning term, so a number's width IS the sum of its digits'. That is equally
|
||||
/// why it stops at numbers: arbitrary text still needs the real measure, and a caller that wants
|
||||
/// one still waits a frame.
|
||||
///
|
||||
/// **Published from here rather than written in XML** — which is where it started, and the move
|
||||
/// is the point. Its callers are `BenillaMoney_Set` (MerchantFrame.xml) and `ShowCoin`
|
||||
/// (MoneyFrame.xml), and there is no third shipped file both of them load: fixtures take
|
||||
/// arbitrary subsets, so whichever file owned it, some fixture met it as nil. Seated at VM
|
||||
/// construction it is simply always there, and the headless fallback lives in one place.
|
||||
///
|
||||
/// The 7px fallback is for a VM whose host never fed the advances — every engine test, since
|
||||
/// there is no font atlas without the app. In-app the feed lands on the first frame the atlas
|
||||
/// exists.
|
||||
fn publish_number_width(&self) -> mlua::Result<()> {
|
||||
let f = self.lua.create_function(|lua, n: mlua::Value| {
|
||||
// `tostring(n)` semantics: the callers pass a number, but a string is as valid an
|
||||
// argument here as it is to the Lua original this replaces.
|
||||
let s = match &n {
|
||||
mlua::Value::Integer(i) => i.to_string(),
|
||||
mlua::Value::Number(x) => {
|
||||
if *x == x.trunc() && x.is_finite() {
|
||||
format!("{}", *x as i64)
|
||||
} else {
|
||||
x.to_string()
|
||||
}
|
||||
}
|
||||
mlua::Value::String(s) => s.to_str()?.to_string(),
|
||||
_ => String::new(),
|
||||
};
|
||||
let model = lua.app_data_ref::<Model>().expect("model");
|
||||
let adv = model.digit_advances;
|
||||
Ok(s.chars()
|
||||
.map(|c| match (c.to_digit(10), adv) {
|
||||
(Some(d), Some(a)) => a[d as usize],
|
||||
// A non-digit (a minus sign, a separator) has no advance in the feed, and
|
||||
// neither does anything before the host has fed one.
|
||||
_ => 7.0,
|
||||
})
|
||||
.sum::<f32>())
|
||||
})?;
|
||||
self.lua.globals().set("BenillaNumberWidth", f)
|
||||
}
|
||||
|
||||
// ── Input: pointer-leaves-window cleanup (decision 0216 §3; the hit-test/mouse dispatch that
|
||||
// used to sit here now lives in [`pointer`]) ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ pub(crate) struct Model {
|
||||
pub(crate) addons: Vec<super::addon::AddOnInfo>,
|
||||
/// The AddOns folder, so `LoadAddOn` can read an addon's files from inside a Lua binding.
|
||||
pub(crate) addons_root: Option<std::path::PathBuf>,
|
||||
/// NumberFontNormal's per-digit advances, `'0'..='9'` ([`super::UiScript::set_digit_advances`])
|
||||
/// — the synchronous stand-in for a text measure, read by the `BenillaNumberWidth` binding.
|
||||
/// `None` until the host feeds them, which is every VM without a font atlas (every engine test).
|
||||
pub(crate) digit_advances: Option<[f32; 10]>,
|
||||
/// Where per-addon saved variables live: the account-scoped folder, then this character's.
|
||||
/// Both are directories holding one `<Addon>.lua` per declaring addon.
|
||||
pub(crate) addons_saved_account: Option<std::path::PathBuf>,
|
||||
@@ -1010,6 +1014,7 @@ impl Model {
|
||||
Model {
|
||||
addons: Vec::new(),
|
||||
addons_root: None,
|
||||
digit_advances: None,
|
||||
addons_saved_account: None,
|
||||
addons_saved_character: None,
|
||||
framexml_templates: Default::default(),
|
||||
|
||||
Reference in New Issue
Block a user