pets get their action bar, macros arrive, and hunters draw the bow
- a hunter's or warlock's pet has its action bar now: attack, follow, stay, the pet's own spells with cooldowns and autocast sparkles - the macro system lands: the macro window, the icon chooser, /cast and /macro, and macros dragged onto the action bar - hunter shots look right at last: the bow draws and releases on the shot, and the arrow lays a spark trail its whole flight - the options window re-dresses in 1.12's own art with the classic dialog look, and gets a working search box and clickable steppers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,7 +42,7 @@ use bevy::prelude::*;
|
||||
/// `SLASH_<INDEX><n>` alias-string prefix ([`SlashIndex::key`]). Adding a command is adding a
|
||||
/// variant + its handler arm; its aliases arrive with the shipped strings.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum SlashIndex {
|
||||
pub(crate) enum SlashIndex {
|
||||
Reply,
|
||||
Join,
|
||||
Leave,
|
||||
@@ -74,6 +74,14 @@ pub(super) enum SlashIndex {
|
||||
Target,
|
||||
Assist,
|
||||
Follow,
|
||||
/// `/cast <name>` — the reference's `SlashCmdList["CAST"]` is one line, `CastSpellByName(msg)`,
|
||||
/// and that binding is the engine seam benilla implements over the spell book (decision 0983).
|
||||
/// The command the whole macro system exists to run.
|
||||
Cast,
|
||||
/// `/macro` `/m` — opens the macro window (the ref's `ShowMacroFrame()`).
|
||||
MacroUi,
|
||||
/// `/macrohelp` — the reference's own five-line help text.
|
||||
MacroHelp,
|
||||
}
|
||||
|
||||
impl SlashIndex {
|
||||
@@ -111,13 +119,16 @@ impl SlashIndex {
|
||||
Self::Target => "TARGET",
|
||||
Self::Assist => "ASSIST",
|
||||
Self::Follow => "FOLLOW",
|
||||
Self::Cast => "CAST",
|
||||
Self::MacroUi => "MACRO",
|
||||
Self::MacroHelp => "MACROHELP",
|
||||
}
|
||||
}
|
||||
|
||||
/// Every registered index — the registry proper. A reference command NOT in this list resolves
|
||||
/// nowhere and answers `HELP_TEXT_SIMPLE`, exactly as an unknown command does in the reference
|
||||
/// (better than a registered handler that silently does nothing).
|
||||
const ALL: [Self; 31] = [
|
||||
const ALL: [Self; 34] = [
|
||||
Self::Reply,
|
||||
Self::Join,
|
||||
Self::Leave,
|
||||
@@ -149,6 +160,9 @@ impl SlashIndex {
|
||||
Self::Target,
|
||||
Self::Assist,
|
||||
Self::Follow,
|
||||
Self::Cast,
|
||||
Self::MacroUi,
|
||||
Self::MacroHelp,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -156,7 +170,7 @@ impl SlashIndex {
|
||||
/// (decisions 0099 `/castvis`, 0288 `/chattest`, 0434 `/partytest`, 0600 `/shot`, 0634 `/liquid`,
|
||||
/// 0637 `/reaction`). Their aliases are literals because there is nothing shipped to read.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum DevCmd {
|
||||
pub(crate) enum DevCmd {
|
||||
CastVis,
|
||||
ChatTest,
|
||||
PartyTest,
|
||||
@@ -189,7 +203,7 @@ impl DevCmd {
|
||||
|
||||
/// What a typed `/command` resolved to.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum Command {
|
||||
pub(crate) enum Command {
|
||||
/// A reference `SlashCmdList` command benilla implements.
|
||||
Slash(SlashIndex),
|
||||
/// An emote alias — carrying the `EmotesText` id its token resolved to (`/lol` → LAUGH → 45).
|
||||
@@ -210,14 +224,14 @@ pub(crate) struct SlashCommands {
|
||||
|
||||
impl SlashCommands {
|
||||
/// Resolve a typed command (no leading `/`, any case).
|
||||
pub(super) fn lookup(&self, cmd: &str) -> Option<Command> {
|
||||
pub(crate) fn lookup(&self, cmd: &str) -> Option<Command> {
|
||||
self.by_alias.get(&cmd.to_ascii_lowercase()).copied()
|
||||
}
|
||||
|
||||
/// Build from the reference's alias strings. `get` reads a global string (the UI VM's globals in
|
||||
/// production, a stub map in tests); `text_id` resolves an `EmotesText` NAME to its id (the
|
||||
/// emote catalog). Pure over those two, so the whole table is testable without a VM.
|
||||
pub(super) fn build(
|
||||
pub(crate) fn build(
|
||||
get: impl Fn(&str) -> Option<String>,
|
||||
text_id: impl Fn(&str) -> Option<u32>,
|
||||
) -> Self {
|
||||
|
||||
@@ -562,6 +562,8 @@ pub(super) fn drain_chat_input(
|
||||
"Duel: /duel [name — bare uses your target], /forfeit (/concede /yield)",
|
||||
"Social: /who [filter], /friends, /ignore, /trade, /inspect",
|
||||
"Emotes: /sit /stand /sleep /kneel and every /wave-style emote",
|
||||
"Spells: /cast <name> [(Rank N)]",
|
||||
"Macros: /macro (/m) opens the window, /macrohelp explains them",
|
||||
"Misc: /afk, /dnd, /random [min] [max], /played, /logout, /quit",
|
||||
"Instruments: /shot, /liquid, /reaction, /castvis, /chattest, /partytest",
|
||||
] {
|
||||
@@ -571,6 +573,23 @@ pub(super) fn drain_chat_input(
|
||||
));
|
||||
}
|
||||
}
|
||||
// `ChatFrame_DisplayMacroHelpText` (ChatFrame.lua): the five shipped lines, read off
|
||||
// the VM's own GlobalStrings so they are the install's text, never a transcription
|
||||
// (decision 0983; the 0881 posture — the strings are data, the handler is ours).
|
||||
ParsedChat::MacroHelp => {
|
||||
for i in 1..=5 {
|
||||
let key = format!("MACRO_HELP_TEXT_LINE{i}");
|
||||
let Ok(line) = script.lua().globals().get::<String>(key.as_str()) else {
|
||||
continue;
|
||||
};
|
||||
if !line.is_empty() {
|
||||
chat_log.push_event(super::event::ChatEvent::text_only(
|
||||
super::event::ChatEventKind::System,
|
||||
line,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
// `DoEmote` (`0x5ef560`) end to end — wow-re `object-layer/scratch/emote-posture-
|
||||
// gate.md` §1. The gates in the client's own order, then the two things it DOES: set a
|
||||
// posture (the `/sit` family) and send the packet.
|
||||
|
||||
@@ -172,6 +172,10 @@ pub(in crate::ui_chat) enum ParsedChat {
|
||||
/// only living assistable ones (the ref's filter mode 2); bare follows the current selection,
|
||||
/// creature or player. Client-side movement — nothing goes on the wire.
|
||||
Follow { name: Option<String> },
|
||||
/// `/macrohelp` — the reference's `ChatFrame_DisplayMacroHelpText`: `MACRO_HELP_TEXT_LINE1..5`
|
||||
/// straight out of the shipped `GlobalStrings.lua` (decision 0983). Resolved in the drain,
|
||||
/// which holds the VM, so the text can never go stale against the install.
|
||||
MacroHelp,
|
||||
/// A slash line matching neither a chat command nor an `EmotesText` name — dropped.
|
||||
Unknown,
|
||||
}
|
||||
@@ -358,6 +362,26 @@ fn slash_command(index: SlashIndex, args: &str) -> ParsedChat {
|
||||
},
|
||||
None => ParsedChat::Unknown,
|
||||
},
|
||||
// `/cast <name>` = the ref's `SlashCmdList["CAST"]` verbatim: `if msg ~= "" then
|
||||
// CastSpellByName(msg) end` (ChatFrame.lua:1120). The binding does the book lookup
|
||||
// (`benilla_ui::script::spellbook::resolve_spell_by_name`) and queues onto the ONE cast
|
||||
// path, so a typed `/cast` and a macro's `/cast` line are the same code — decision 0983.
|
||||
S::Cast => {
|
||||
if args.is_empty() {
|
||||
ParsedChat::Unknown
|
||||
} else {
|
||||
ParsedChat::Lua {
|
||||
body: format!("CastSpellByName(\"{}\")", escape_lua_string(args)),
|
||||
}
|
||||
}
|
||||
}
|
||||
// `/macro` `/m` = the ref's own body: `ShowMacroFrame()` (the `RunMacro(msg)` branch beside
|
||||
// it is commented out in the shipped 1.12 ChatFrame.lua — there is no `RunMacro` binding in
|
||||
// the client at all, confirmed against the registered-binding scan).
|
||||
S::MacroUi => ParsedChat::Lua {
|
||||
body: "ShowMacroFrame()".into(),
|
||||
},
|
||||
S::MacroHelp => ParsedChat::MacroHelp,
|
||||
// `/script` = the ref's `RunScript(msg)`: the typed text IS the chunk, un-escaped.
|
||||
S::Script => {
|
||||
if args.is_empty() {
|
||||
|
||||
@@ -650,12 +650,43 @@ fn real_alias_table_resolves_the_shipped_commands() {
|
||||
}
|
||||
);
|
||||
assert_eq!(parse_line("/assist"), ParsedChat::Assist { name: None });
|
||||
// The macro family (decision 0983): `/cast` (with its `/spell` alias — SLASH_CAST1-4 spell two
|
||||
// distinct strings across four slots) runs the ref's own one-line body `CastSpellByName(msg)`;
|
||||
// `/macro`/`/m` open the window; `/macrohelp` prints the shipped five lines.
|
||||
assert_eq!(
|
||||
parse_line("/cast Fireball(Rank 1)"),
|
||||
ParsedChat::Lua {
|
||||
body: "CastSpellByName(\"Fireball(Rank 1)\")".into()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_line("/spell Frostbolt"),
|
||||
ParsedChat::Lua {
|
||||
body: "CastSpellByName(\"Frostbolt\")".into()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
parse_line("/cast"),
|
||||
ParsedChat::Unknown,
|
||||
"a bare /cast is the ref's own no-op (`if msg ~= \"\"`)"
|
||||
);
|
||||
for line in ["/macro", "/m"] {
|
||||
assert_eq!(
|
||||
parse_line(line),
|
||||
ParsedChat::Lua {
|
||||
body: "ShowMacroFrame()".into()
|
||||
},
|
||||
"{line}"
|
||||
);
|
||||
}
|
||||
assert_eq!(parse_line("/macrohelp"), ParsedChat::MacroHelp);
|
||||
// The whole shipped surface, so a table that half-loaded fails loudly: **225 distinct emote
|
||||
// commands** over the 169 `EmotesText` names (the strings repeat — `EMOTE87_CMD1` and `_CMD2`
|
||||
// are both "/sit" — and EMOTE27 "UNUSED" has no row, so it contributes none), and **62 distinct
|
||||
// aliases** across the 31 registered `SlashCmdList` indices (0886 added TARGET's `/target`
|
||||
// `/tar` and ASSIST's `/assist` `/a` to 0881's 55; 0890 added FOLLOW's `/f` `/follow` `/fol`).
|
||||
assert_eq!(table.counts(), (62, 225), "(slash, emote) aliases");
|
||||
// are both "/sit" — and EMOTE27 "UNUSED" has no row, so it contributes none), and **67 distinct
|
||||
// aliases** across the 34 registered `SlashCmdList` indices (0886 added TARGET's `/target`
|
||||
// `/tar` and ASSIST's `/assist` `/a` to 0881's 55; 0890 added FOLLOW's `/f` `/follow` `/fol`;
|
||||
// 0983 added CAST's `/cast` `/spell`, MACRO's `/macro` `/m`, and MACROHELP's `/macrohelp`).
|
||||
assert_eq!(table.counts(), (67, 225), "(slash, emote) aliases");
|
||||
}
|
||||
|
||||
// ── The send-side posture-eligibility gate (`emote_send_eligible`) — the director-verified rows
|
||||
|
||||
Reference in New Issue
Block a user