Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions Core/MultiBotComm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ local function ensureBridgeState()
state.strategyMutationCapable = state.strategyMutationCapable or false
state.strategyMutationSeq = state.strategyMutationSeq or 0
state.strategyMutationCommands = state.strategyMutationCommands or {}
state.weaponEnchantDebugSeq = state.weaponEnchantDebugSeq or 0
state.details = state.details or {}
state.professions = state.professions or {}
state.pvpStats = state.pvpStats or {}
Expand Down Expand Up @@ -526,6 +527,30 @@ function Comm.RequestStats(name)
return Comm.Send("GET", "STATS")
end

function Comm.RequestWeaponEnchantDebug(name)
local state = ensureBridgeState()
if not state.connected then
state.lastError = "WEAPON_ENCHANT_NOT_CONNECTED"
return false
end

name = trim(name)
if name == "" or #name > 64 then
state.lastError = "WEAPON_ENCHANT_BAD_BOT_NAME"
return false
end

state.weaponEnchantDebugSeq = (tonumber(state.weaponEnchantDebugSeq) or 0) + 1
local token = tostring(math.floor(safeNow() * 1000)) .. "-enchant-" .. tostring(state.weaponEnchantDebugSeq)

if not Comm.Send("GET", "WEAPON_ENCHANT~" .. urlEncodeField(name) .. "~" .. token) then
state.lastError = "WEAPON_ENCHANT_SEND_FAILED"
return false
end

return token
end

function Comm.RequestTalentSpecList(name)
local state = ensureBridgeState()
if not state.connected and not state.bootstrapPending then
Expand Down Expand Up @@ -3215,6 +3240,58 @@ function Comm.HandleAddonMessage(prefix, message, distribution, sender)
return true
end

if opcode == "WEAPON_ENCHANT" then
state.connected = true

local fields = splitFields(payload)
if #fields ~= 9 then
state.lastError = "WEAPON_ENCHANT_BAD_FIELD_COUNT"
return true
end

local token = trim(fields[1])
local botName = urlDecodeFieldStrict(fields[2], 64, false)
local status = string.upper(trim(fields[3]))
local mainItem = parseBoundedInteger(fields[4], 0, 4294967295)
local mainEnchant = parseBoundedInteger(fields[5], 0, 4294967295)
local mainDuration = parseBoundedInteger(fields[6], 0, 4294967295)
local offItem = parseBoundedInteger(fields[7], 0, 4294967295)
local offEnchant = parseBoundedInteger(fields[8], 0, 4294967295)
local offDuration = parseBoundedInteger(fields[9], 0, 4294967295)

if not isValidStateToken(token)
or not botName
or (status ~= "OK" and status ~= "RATE_LIMIT" and status ~= "BOT_NOT_VISIBLE" and status ~= "FORBIDDEN")
or mainItem == nil
or mainEnchant == nil
or mainDuration == nil
or offItem == nil
or offEnchant == nil
or offDuration == nil then
state.lastError = "WEAPON_ENCHANT_BAD_PAYLOAD"
return true
end

state.lastError = status == "OK" and nil or ("WEAPON_ENCHANT_" .. status)
debugPrint("ADDON:RX", "WEAPON_ENCHANT", payload or "")

if MultiBot.OnWeaponEnchantDebug then
MultiBot.OnWeaponEnchantDebug({
token = token,
botName = botName,
status = status,
mainItem = mainItem,
mainEnchant = mainEnchant,
mainDuration = mainDuration,
offItem = offItem,
offEnchant = offEnchant,
offDuration = offDuration,
})
end

return true
end

if opcode == "ROSTER" then
state.connected = true
state.lastError = nil
Expand Down
4 changes: 2 additions & 2 deletions Core/MultiBotEngine.lua
Original file line number Diff line number Diff line change
Expand Up @@ -528,11 +528,11 @@ MultiBot.ActionToTarget = function(pAction, oTarget)

if(tName ~= nil and tName ~= "Unknown Entity") then
if(_mbRunBridgeStrategyMutation(pAction, "BOT", tName)) then
return true
return true, "bridge"
end

SendChatMessage(pAction, "WHISPER", nil, tName)
return true
return true, "chat"
end

SendChatMessage(MultiBot.L("info.target"), "SAY")
Expand Down
58 changes: 57 additions & 1 deletion Core/MultiBotHandler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2592,7 +2592,63 @@ local function parseDebugCommandArgs(msg)
return normalizeDebugToken(action), normalizeDebugToken(subsystem)
end

MultiBot.OnWeaponEnchantDebug = function(result)
if type(result) ~= "table" then
return
end

local botName = tostring(result.botName or "?")
local status = tostring(result.status or "UNKNOWN")
if status ~= "OK" then
printToChat("[MB] Enchant " .. botName .. " => " .. status)
return
end

printToChat(string.format(
"[MB] Enchant %s | MH item=%u temp=%u duration=%ums | OH item=%u temp=%u duration=%ums",
botName,
tonumber(result.mainItem or 0) or 0,
tonumber(result.mainEnchant or 0) or 0,
tonumber(result.mainDuration or 0) or 0,
tonumber(result.offItem or 0) or 0,
tonumber(result.offEnchant or 0) or 0,
tonumber(result.offDuration or 0) or 0
))
end

local function DebugCommand(msg)
local rawAction, rawArgument = string.match(tostring(msg or ""), "^%s*(%S*)%s*(.-)%s*$")
if normalizeDebugToken(rawAction) == "enchant" then
local botName = tostring(rawArgument or ""):gsub("^%s+", ""):gsub("%s+$", "")
if botName == "" and type(UnitName) == "function" then
botName = UnitName("target") or ""
end

if botName == "" then
printToChat("[MB] Usage: /mbdebug enchant [bot] (ou cibler un bot)")
return
end

if not (MultiBot and MultiBot.Comm and MultiBot.bridge and MultiBot.bridge.connected) then
printToChat("[MB] Bridge indisponible.")
return
end

if type(MultiBot.Comm.RequestWeaponEnchantDebug) ~= "function" then
printToChat("[MB] Diagnostic enchant indisponible.")
return
end

local token = MultiBot.Comm.RequestWeaponEnchantDebug(botName)
if not token then
printToChat("[MB] Diagnostic enchant non envoye.")
return
end

printToChat("[MB] Diagnostic enchant demande pour " .. botName .. ".")
return
end

local debugApi = MultiBot.Debug
if type(debugApi) ~= "table" then
printToChat("[MB] Debug API indisponible.")
Expand Down Expand Up @@ -2634,7 +2690,7 @@ local function DebugCommand(msg)
end

if not subsystem then
printToChat("[MB] Usage: /mbdebug list | /mbdebug on <subsystem> | /mbdebug off <subsystem> | /mbdebug toggle <subsystem> | /mbdebug all on|off | /mbdebug counters [reset]")
printToChat("[MB] Usage: /mbdebug list | /mbdebug on <subsystem> | /mbdebug off <subsystem> | /mbdebug toggle <subsystem> | /mbdebug all on|off | /mbdebug counters [reset] | /mbdebug enchant [bot]")
return
end

Expand Down
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ MBOT HELLO
MBOT PING
GET~ROSTER
GET~STATES
GET~WEAPON_ENCHANT
GET~DETAILS
GET~STATS
GET~PVP_STATS
Expand Down Expand Up @@ -139,7 +140,7 @@ STRATEGY_MUTATION_V1

`STRATEGY_MUTATION_V1` provides structured `co/nc` mutations through `RUN~STRATEGY` and completion through `STRATEGY_ACK`. The bridge reports matched, succeeded and failed bot counts, while the addon applies explicit timeout and rejection diagnostics.

The migration is intentionally incremental. Some specialized legacy UI paths still issue Playerbots chat commands directly and must be migrated before the addon can be described as fully chatless. The current known priority includes Warlock stone, soulstone, pet and curse selectors.
The migration is intentionally incremental. The Warlock stone, soulstone, pet and curse selectors are now migrated to structured `RUN~STRATEGY` mutations. When those selectors use the bridge, the addon waits for authoritative server `STATE` data before committing the selected UI state instead of applying an optimistic local state. Other specialized legacy UI paths still issue Playerbots chat commands directly and must be migrated before the addon can be described as fully chatless.

Manual playerbot commands are still intentionally preserved for diagnostics and gameplay actions.

Expand All @@ -157,6 +158,16 @@ still work when the player explicitly wants to inspect a bot state.
The goal is not to remove useful manual commands.
The goal is to remove automatic UI-refresh spam.

### Warlock weapon-enchant diagnostic

For targeted runtime diagnostics, the addon exposes:

```text
/mbdebug enchant [bot]
```

If the bot name is omitted, the current target is used. The command sends a single `GET~WEAPON_ENCHANT` request and displays the structured `WEAPON_ENCHANT` response with main-hand/off-hand item entries, temporary enchant IDs and remaining durations. This path is diagnostic only: it is on-demand, server-authorized and rate-limited, and is not used for polling or normal selector state synchronization.

---

# Features
Expand All @@ -182,6 +193,10 @@ The goal is to remove automatic UI-refresh spam.
<td>Strategy mutations</td>
<td><strong>Bridge-first where migrated</strong> — <code>STRATEGY_MUTATION_V1</code>, <code>RUN~STRATEGY</code>, <code>STRATEGY_ACK</code> and explicit rejection/timeout diagnostics</td>
</tr>
<tr>
<td>Warlock strategy selectors</td>
<td><strong>Bridge-first and runtime validated</strong> — Stones, Soulstones, Pets and Curses use structured strategy mutations; bridge-backed selections wait for authoritative state, invalid Warlock <code>dps</code>/<code>dps debuff</code> controls and the disabled Buff placeholder were removed, and the selector layout was compacted</td>
</tr>
<tr>
<td>Bot details</td>
<td><strong>Bridge-first</strong></td>
Expand Down Expand Up @@ -540,12 +555,12 @@ Validated development milestones on the current line:
- PR #50 — explicit strategy-command rejection diagnostics.
- PR #51 — mechanical deduplication of shared roster workflow helpers.
- Final static STATE/strategy audit on 2026-08-07: 57 checks, 0 failures; final manual runtime matrix remains pending.
- Warlock selector batch validated on 2026-08-08: Stones, Soulstones, Pets and Curses migrated to bridge strategy mutations; authoritative bridge state handling validated; Firestone/Spellstone temporary-enchant switching validated bidirectionally with the companion bridge.

Known migration remaining:

- Some specialized UI controls still issue direct `co/nc` chat commands. The current priority is the Warlock stone, soulstone, pet and curse selectors.
- Other `SendChatMessage` occurrences remain to be classified as manual command, diagnostic fallback, information message, UI mechanism to migrate, or dead code.
- The project should be described as **bridge-first / mostly chatless**, not fully chatless, until these remaining paths are migrated and the final runtime matrix is closed.
- Remaining direct `SendChatMessage` occurrences outside the validated Warlock selector batch still need to be classified as manual command, diagnostic fallback, information message, UI mechanism to migrate, or dead code.
- The project should be described as **bridge-first / mostly chatless**, not fully chatless, until these remaining paths are classified/migrated and the final runtime matrix is closed.

Kept intentionally:

Expand Down
Loading
Loading