A friendly or resurrection spell with no usable target brings up the targeting cursor, and autoSelfCast boots at 1.12.1's 0 (#445)
A friendly or resurrection spell that binds nothing now enters the unit targeting cursor, as `TryCast` does for any residual word without the enemy bit (`0x6e507b`–`0x6e50ab`). `autoSelfCast` boots at its reference default of `"0"`, and its `Deviates` row is gone. - **Binding.** The cursor binds a unit through a world click, and through `SpellTargetUnit` from the unit frames (`BindTarget 0x6e5b40`). The selection never moves. - **Relation checks.** Heals and buffs ask `CanAssist 0x6066f0` instead of reaction alone, so a friendly NPC without the PvP flag is no longer assistable. That is part of #50. - **Range.** `SpellCanTargetUnit` and the cursor answer per unit through `0x6e6460`'s unit leg, including the spell's min/max range (`6e677c`–`6e6802`). A click on a unit out of range raises "Out of range." or "Target too close" and keeps the cursor armed. The reference's abort there also strands the cursor, and that is not copied. - **Your own model** joins the world pick only for a word `0x6e61a0` admits (`0x850e`, no `AttributesEx 0x80000`), and never in first person. Fixes #136 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-Authored-By: Sam <samuv.eth@gmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Claude Opus 5.5
Sam
parent
6982109d86
commit
e4cc695f84
@@ -1079,12 +1079,31 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// SpellTargetUnit(unit): a no-op. A unit-target spell never enters targeting mode here, and for
|
||||
// the location, item and gameobject modes this client arms, the reference's `BindTarget`
|
||||
// (`0x6e5b40`) rejects a unit too. Stock `PetFrame_OnClick` calls it, so it must exist.
|
||||
// SpellTargetUnit(unit) (`0x6e6d90`): an argument that is not a string or a number raises
|
||||
// `Usage:`; while not targeting the call is a silent no-op, before the token is read; an
|
||||
// unknown token then raises `Unknown unit name`. The token queues for the host, which ends
|
||||
// targeting with "Out of range." when it names no unit and otherwise runs `BindTarget`
|
||||
// (`0x6e5b10` → `0x6e5b40`).
|
||||
g.set(
|
||||
"SpellTargetUnit",
|
||||
lua.create_function(|_, _token: Option<String>| Ok(()))?,
|
||||
lua.create_function(|lua, token: Value| {
|
||||
let token = crate::script::binding_abi::string_arg(
|
||||
lua,
|
||||
token,
|
||||
r#"Usage: SpellTargetUnit("unit")"#,
|
||||
)?;
|
||||
if !lua
|
||||
.app_data_ref::<Model>()
|
||||
.expect("model app_data")
|
||||
.spell_targeting
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
check_unit_token(&Some(token.clone()))?;
|
||||
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
|
||||
model.spell_target_unit.push(token);
|
||||
Ok(())
|
||||
})?,
|
||||
)?;
|
||||
|
||||
// ClearTarget(): 1 when it cleared a target, nil when there was none, which `ToggleGameMenu`'s
|
||||
|
||||
@@ -411,6 +411,11 @@ impl super::UiScript {
|
||||
pub fn take_drop_item_on_unit(&mut self) -> Vec<String> {
|
||||
std::mem::take(&mut self.model_mut().drop_item_on_unit)
|
||||
}
|
||||
|
||||
/// Drain `SpellTargetUnit` calls for the host's pending unit-target spell binder.
|
||||
pub fn take_spell_target_unit(&mut self) -> Vec<String> {
|
||||
std::mem::take(&mut self.model_mut().spell_target_unit)
|
||||
}
|
||||
}
|
||||
|
||||
/// The non-`"player"` token of a two-unit call such as `UnitIsEnemy(a, b)`, which the snapshot
|
||||
|
||||
@@ -23,6 +23,56 @@ fn player() -> UnitState {
|
||||
}
|
||||
}
|
||||
|
||||
/// `0x6e6d90`'s order: the `Usage:` check, the not-targeting no-op, the token check, the queue.
|
||||
#[test]
|
||||
fn spell_target_unit_checks_in_the_references_order() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
let usage = |s: &mut UiScript, call: &str| {
|
||||
let err = s.run(call).expect_err(call).to_string();
|
||||
assert!(
|
||||
err.contains(r#"Usage: SpellTargetUnit("unit")"#),
|
||||
"{call}: {err}"
|
||||
);
|
||||
};
|
||||
|
||||
// Not targeting: the argument is still type-checked, then nothing, not even the token check.
|
||||
usage(&mut s, "SpellTargetUnit({})");
|
||||
usage(&mut s, "SpellTargetUnit()");
|
||||
s.run(r#"SpellTargetUnit("not-a-unit") SpellTargetUnit("player")"#)
|
||||
.expect("a bad token is no error while not targeting");
|
||||
assert!(s.take_spell_target_unit().is_empty(), "and nothing queues");
|
||||
|
||||
// Targeting: an unknown token raises, a known one queues, resolved or not.
|
||||
s.set_spell_targeting(true);
|
||||
usage(&mut s, "SpellTargetUnit(nil)");
|
||||
let err = s
|
||||
.run(r#"SpellTargetUnit("not-a-unit")"#)
|
||||
.expect_err("an unknown token while targeting")
|
||||
.to_string();
|
||||
assert!(err.contains("Unknown unit name: not-a-unit"), "{err}");
|
||||
s.run(r#"SpellTargetUnit("player") SpellTargetUnit("party1")"#)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.take_spell_target_unit(),
|
||||
vec!["player".to_string(), "party1".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spell_can_target_unit_answers_per_validated_token() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_spell_targetable_units(["player", "party1"]);
|
||||
assert!(s
|
||||
.eval::<bool>(r#"return SpellCanTargetUnit("player") == true"#)
|
||||
.unwrap());
|
||||
assert!(s
|
||||
.eval::<bool>(r#"return SpellCanTargetUnit("PARTY1") == true"#)
|
||||
.unwrap());
|
||||
assert!(s
|
||||
.eval::<bool>(r#"return SpellCanTargetUnit("target") == nil"#)
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_reaction_reports_the_scale_value_or_nil() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user