diff --git a/crates/benilla-ui/src/script/compat/classic_api/unit.rs b/crates/benilla-ui/src/script/compat/classic_api/unit.rs index dcc7f2ec..a5fecd1d 100644 --- a/crates/benilla-ui/src/script/compat/classic_api/unit.rs +++ b/crates/benilla-ui/src/script/compat/classic_api/unit.rs @@ -41,6 +41,31 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> { })?, )?; + // `GetCraftReagentItemID(craftIndex, reagentIndex)` -> the reagent's item id. 1.12's + // `GetCraftReagentInfo` answers its name, icon, needed and held counts and no id at all, so + // nothing that keys a table on an item — pfUI's reagent counter does — can use the stock verb. + // + // The same `(index, reagentIndex)` pair and the same rows that verb walks + // (`script/craft.rs`), so the two cannot disagree about which reagent is meant. Out of range + // on either index is nil, as the stock verb's own nil-first return is. + g.set( + "GetCraftReagentItemID", + lua.create_function( + |lua, (index, reagent_index): (Option, Option)| { + let model = lua.app_data_ref::().expect("model app_data"); + Ok(index + .zip(reagent_index) + .and_then(|(i, r)| { + crate::script::craft::recipe(&model, i) + .zip(r.checked_sub(1)) + .and_then(|(recipe, r)| recipe.reagents.get(r)) + }) + .map(|reagent| reagent.item) + .filter(|&id| id != 0)) + }, + )?, + )?; + // `CastSpellNoToggle(spellId)`: cast without the stock toggle-off, for a spell whose aura is // already up. The toggle lives in the client's cast path (`0x6e5100`'s autorepeat and aura // tests), and benilla's cast queue does not toggle anything — a press is a send — so this is @@ -135,6 +160,72 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> { mod tests { use crate::script::{UiScript, UnitState}; + /// `GetCraftReagentItemID`: the one column 1.12's `GetCraftReagentInfo` does not answer. It + /// reads the same rows that verb walks, so the two agree about which reagent is meant — and + /// anything keying a table on an item (pfUI's reagent counter) needs the id, not the name. + #[test] + fn get_craft_reagent_item_id_matches_the_stock_reagent_row() { + let mut s = UiScript::new().unwrap(); + assert_eq!( + s.eval::>("return GetCraftReagentItemID(1, 1)") + .unwrap(), + None, + "no craft window open" + ); + s.set_craft(Some(crate::script::CraftState { + name: "Enchanting".into(), + rank: 57, + max_rank: 75, + craft_type: 2, + recipes: vec![crate::script::CraftRecipe { + spell_id: 683, + tooltip: crate::script::CraftTooltip::Spell(683), + name: "Enchant Weapon - Minor Health".into(), + sub_name: String::new(), + difficulty: crate::script::TradeSkillDifficulty::Optimal, + num_available: 1, + icon: None, + description: None, + needs_item_target: true, + reagents: vec![crate::script::CraftReagent { + item: 10940, + name: Some("Illusion Dust".into()), + icon: None, + need: 1, + have: 3, + }], + tools: Vec::new(), + spell_level: 0, + }], + })); + + assert_eq!( + s.eval::>("return GetCraftReagentItemID(1, 1)") + .unwrap(), + Some(10940) + ); + // The stock verb names the same reagent at the same pair. + assert_eq!( + s.eval::("return (GetCraftReagentInfo(1, 1))") + .unwrap(), + "Illusion Dust" + ); + // Out of range on either index, and a missing argument, are nil. + for call in [ + "GetCraftReagentItemID(1, 2)", + "GetCraftReagentItemID(2, 1)", + "GetCraftReagentItemID(1, 0)", + "GetCraftReagentItemID(1)", + "GetCraftReagentItemID()", + ] { + assert_eq!( + s.eval::>(&format!("return {call}")).unwrap(), + None, + "{call}" + ); + } + } + /// `GetPlayerInfoByGUID`'s seven, for a unit this client holds. 1.12 has no verb that takes a /// GUID at all, so every column here comes from the snapshot the unit surface already carries /// — and the GUID resolves through the same `Model::unit` as every other token spelling, so a diff --git a/crates/benilla-ui/src/script/craft.rs b/crates/benilla-ui/src/script/craft.rs index 19a74b3d..a7a4538e 100644 --- a/crates/benilla-ui/src/script/craft.rs +++ b/crates/benilla-ui/src/script/craft.rs @@ -161,7 +161,7 @@ impl super::UiScript { } /// The recipe at a 1-based index. -fn recipe(model: &Model, index: usize) -> Option<&CraftRecipe> { +pub(in crate::script) fn recipe(model: &Model, index: usize) -> Option<&CraftRecipe> { let n = index.checked_sub(1)?; model.craft.as_ref()?.recipes.get(n) } diff --git a/crates/benilla-ui/src/script/tests/reference_surface.rs b/crates/benilla-ui/src/script/tests/reference_surface.rs index 61b13e7f..03361efb 100644 --- a/crates/benilla-ui/src/script/tests/reference_surface.rs +++ b/crates/benilla-ui/src/script/tests/reference_surface.rs @@ -323,6 +323,10 @@ fn allowed_beyond_1_12() -> HashSet<&'static str> { // (script/compat/classic_api/unit.rs). 1.12 has no verb that takes a GUID at all, and no // cast path that skips its own toggle — benilla's queue does not toggle, so the second is // the plain cast by id, the same reasoning nampower's `CastSpellNoQueue` carries. + // ClassicAPI's `GetCraftReagentItemID(craftIndex, reagentIndex)` (same file): the one + // column 1.12's `GetCraftReagentInfo` does not answer — it gives the name, icon and the + // two counts and no id at all, so nothing keying a table on an item can use it. + "GetCraftReagentItemID", "CastSpellNoToggle", "GetPlayerInfoByGUID", "GetQuestIDForLogIndex",