diff --git a/Core/MultiBotComm.lua b/Core/MultiBotComm.lua index d7df4ca..af0cbd1 100644 --- a/Core/MultiBotComm.lua +++ b/Core/MultiBotComm.lua @@ -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 {} @@ -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 @@ -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 diff --git a/Core/MultiBotEngine.lua b/Core/MultiBotEngine.lua index f70c2b5..c936d55 100644 --- a/Core/MultiBotEngine.lua +++ b/Core/MultiBotEngine.lua @@ -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") diff --git a/Core/MultiBotHandler.lua b/Core/MultiBotHandler.lua index beaf524..d964558 100644 --- a/Core/MultiBotHandler.lua +++ b/Core/MultiBotHandler.lua @@ -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.") @@ -2634,7 +2690,7 @@ local function DebugCommand(msg) end if not subsystem then - printToChat("[MB] Usage: /mbdebug list | /mbdebug on | /mbdebug off | /mbdebug toggle | /mbdebug all on|off | /mbdebug counters [reset]") + printToChat("[MB] Usage: /mbdebug list | /mbdebug on | /mbdebug off | /mbdebug toggle | /mbdebug all on|off | /mbdebug counters [reset] | /mbdebug enchant [bot]") return end diff --git a/README.md b/README.md index 79578ab..0aa8863 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ MBOT HELLO MBOT PING GET~ROSTER GET~STATES +GET~WEAPON_ENCHANT GET~DETAILS GET~STATS GET~PVP_STATS @@ -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. @@ -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 @@ -182,6 +193,10 @@ The goal is to remove automatic UI-refresh spam. Strategy mutations Bridge-first where migratedSTRATEGY_MUTATION_V1, RUN~STRATEGY, STRATEGY_ACK and explicit rejection/timeout diagnostics + + Warlock strategy selectors + Bridge-first and runtime validated — Stones, Soulstones, Pets and Curses use structured strategy mutations; bridge-backed selections wait for authoritative state, invalid Warlock dps/dps debuff controls and the disabled Buff placeholder were removed, and the selector layout was compacted + Bot details Bridge-first @@ -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: diff --git a/Strategies/MultiBotWarlock.lua b/Strategies/MultiBotWarlock.lua index d98af11..0852d20 100644 --- a/Strategies/MultiBotWarlock.lua +++ b/Strategies/MultiBotWarlock.lua @@ -49,14 +49,6 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) end end]]-- - -- BUFF — non supporté pour Warlock bouton placeholder désactivé - local btnBuff = pFrame.addButton( - "Buff", 0, 0, "spell_shadow_lifedrain02", - (MultiBot.L("tips.warlock.buff.master") ~= "tips.warlock.buff.master" and MultiBot.L("tips.warlock.buff.master") or "Buffs") - .. "|n|cffff0000Not available for Warlock.|r" - ) - btnBuff.setDisable() - btnBuff.doLeft = function() end -- Helper commun pour (dé)saturer les icônes (réutilisé par pierres / pets / malédictions) local _MB_setDesat = _MB_setDesat @@ -113,12 +105,12 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) end -- STONES (Spellstone / Firestone) -- - local btnStones = pFrame.addButton("StonesSelect", -150, 0, + local btnStones = pFrame.addButton("StonesSelect", -120, 0, "inv_misc_orb_05", MultiBot.L("tips.warlock.stones.master")) btnStones._defaultIcon = "inv_misc_orb_05" - local fStones = pFrame.addFrame("Stones", -152, 30) + local fStones = pFrame.addFrame("Stones", -122, 30) fStones:Hide() fStones.activeStone = nil @@ -155,19 +147,28 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) local function ToggleStone(pButton, label, cmd) local target = pButton.getName() + local desired = nil + local action + if fStones.activeStone == label then - SendChatMessage("nc -" .. cmd .. ",?", "WHISPER", nil, target) - fStones.activeStone = nil + action = "nc -" .. cmd .. ",?" else + desired = label if fStones.activeStone then local old = fStones.activeStone local oldCmd = (old=="Spellstone") and "spellstone" or "firestone" - SendChatMessage("nc -" .. oldCmd, "WHISPER", nil, target) + action = "nc -" .. oldCmd .. ",+" .. cmd .. ",?" + else + action = "nc +" .. cmd .. ",?" end - SendChatMessage("nc +" .. cmd .. ",?", "WHISPER", nil, target) - fStones.activeStone = label end - UpdateStoneIcons(fStones.activeStone) + + local sent, transport = MultiBot.ActionToTarget(action, target) + if not sent then return end + if transport ~= "bridge" then + fStones.activeStone = desired + UpdateStoneIcons(fStones.activeStone) + end fStones:Hide() end @@ -188,12 +189,12 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) -- FIN STONES -- -- SOULSTONES (stratégies) -- - local btnSoulstones = pFrame.addButton("SoulstonesSelect", -180, 0, + local btnSoulstones = pFrame.addButton("SoulstonesSelect", -150, 0, "inv_misc_orb_04", MultiBot.L("tips.warlock.soulstones.masterbutton")) btnSoulstones._defaultIcon = "inv_misc_orb_04" - local fSoul = pFrame.addFrame("Soulstones", -182, 30) + local fSoul = pFrame.addFrame("Soulstones", -152, 30) fSoul:Hide() fSoul.activeSS = nil @@ -231,23 +232,38 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) local function ToggleSS(pButton, label, cmd) local target = pButton.getName() + local desired = nil + local action + if fSoul.activeSS == label then - SendChatMessage("nc -" .. cmd .. ",?", "WHISPER", nil, target) - fSoul.activeSS = nil + action = "nc -" .. cmd .. ",?" else + desired = label if fSoul.activeSS then local old = fSoul.activeSS + local oldCmd = nil for _,v in ipairs(ssList) do if v[1]==old then - SendChatMessage("nc -" .. v[2], "WHISPER", nil, target) + oldCmd = v[2] break end end + if oldCmd then + action = "nc -" .. oldCmd .. ",+" .. cmd .. ",?" + else + action = "nc +" .. cmd .. ",?" + end + else + action = "nc +" .. cmd .. ",?" end - SendChatMessage("nc +" .. cmd .. ",?", "WHISPER", nil, target) - fSoul.activeSS = label end - UpdateSSIcons(fSoul.activeSS) + + local sent, transport = MultiBot.ActionToTarget(action, target) + if not sent then return end + if transport ~= "bridge" then + fSoul.activeSS = desired + UpdateSSIcons(fSoul.activeSS) + end fSoul:Hide() end @@ -269,13 +285,13 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) -- PETS -- local btnPets = pFrame.addButton( - "PetsSelect", -210, 0, + "PetsSelect", -180, 0, "ability_druid_forceofnature", MultiBot.L("tips.warlock.pets.master") ) btnPets._defaultIcon = "ability_druid_forceofnature" - local fPets = pFrame.addFrame("Pets", -212, 30) + local fPets = pFrame.addFrame("Pets", -182, 30) fPets:Hide() fPets.activePet = nil btnPets.doLeft = function() MultiBot.ShowHideSwitch(fPets) end @@ -315,23 +331,38 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) local function TogglePet(pButton, label, cmd) local target = pButton.getName() + local desired = nil + local action + if fPets.activePet == label then - SendChatMessage("nc -" .. cmd .. ",?", "WHISPER", nil, target) - fPets.activePet = nil + action = "nc -" .. cmd .. ",?" else + desired = label if fPets.activePet then local old = fPets.activePet + local oldCmd = nil for _,v in ipairs(petList) do if v[1]==old then - SendChatMessage("nc -" .. v[2], "WHISPER", nil, target) + oldCmd = v[2] break end end + if oldCmd then + action = "nc -" .. oldCmd .. ",+" .. cmd .. ",?" + else + action = "nc +" .. cmd .. ",?" + end + else + action = "nc +" .. cmd .. ",?" end - SendChatMessage("nc +" .. cmd .. ",?", "WHISPER", nil, target) - fPets.activePet = label end - UpdatePetIcons(fPets.activePet) + + local sent, transport = MultiBot.ActionToTarget(action, target) + if not sent then return end + if transport ~= "bridge" then + fPets.activePet = desired + UpdatePetIcons(fPets.activePet) + end fPets:Hide() end @@ -362,12 +393,12 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) -- COMBAT STRATEGIES -- -- DPS -- - pFrame.addButton("DpsControl", -30, 0, "ability_warrior_challange", MultiBot.L("tips.warlock.dps.master")) + pFrame.addButton("DpsControl", 0, 0, "ability_warrior_challange", MultiBot.L("tips.warlock.dps.master")) .doLeft = function(pButton) MultiBot.ShowHideSwitch(pButton.getFrame("DpsControl")) end - local tFrame = pFrame.addFrame("DpsControl", -32, 30) + local tFrame = pFrame.addFrame("DpsControl", -2, 30) tFrame:Hide() tFrame.addButton("DpsAssist", 0, 0, "spell_holy_heroism", MultiBot.L("tips.warlock.dps.dpsAssist")).setDisable() @@ -378,12 +409,8 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) end end - tFrame.addButton("DpsDebuff", 0, 26, "spell_holy_restoration", MultiBot.L("tips.warlock.dps.dpsDebuff")).setDisable() - .doLeft = function(pButton) - MultiBot.OnOffActionToTarget(pButton, "co +dps debuff,?", "co -dps debuff,?", pButton.getName()) - end - tFrame.addButton("DpsAoe", 0, 52, "spell_holy_surgeoflight", MultiBot.L("tips.warlock.dps.dpsAoe")).setDisable() + tFrame.addButton("DpsAoe", 0, 26, "spell_holy_surgeoflight", MultiBot.L("tips.warlock.dps.dpsAoe")).setDisable() .doLeft = function(pButton) if(MultiBot.OnOffActionToTarget(pButton, "co +dps aoe,?", "co -dps aoe,?", pButton.getName())) then pButton.getButton("TankAssist").setDisable() @@ -391,16 +418,10 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) end end - tFrame.addButton("Dps", 0, 78, "spell_holy_divinepurpose", MultiBot.L("tips.warlock.dps.dps")).setDisable() - .doLeft = function(pButton) - if(MultiBot.OnOffActionToTarget(pButton, "co +dps,?", "co -dps,?", pButton.getName())) then - pButton.getButton("Tank").setDisable() - end - end -- META MELEE (Démonologie) -- local btnMeta = tFrame.addButton( - "MetaMelee", 0, 105, "Spell_Shadow_DemonForm", + "MetaMelee", 0, 52, "Spell_Shadow_DemonForm", (MultiBot.L("tips.warlock.dps.metamelee") ~= "tips.warlock.dps.metamelee" and MultiBot.L("tips.warlock.dps.metamelee") or "Meta Melee") ) btnMeta.setDisable() @@ -410,12 +431,12 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) end if MultiBot.AddCommonCombatStrategyButtons then - MultiBot.AddCommonCombatStrategyButtons(pFrame, tFrame, pCombat, 131) + MultiBot.AddCommonCombatStrategyButtons(pFrame, tFrame, pCombat, 78) end -- ASSIST -- - pFrame.addButton("TankAssist", -60, 0, "ability_warrior_innerrage", MultiBot.L("tips.warlock.tankAssist")).setDisable() + pFrame.addButton("TankAssist", -30, 0, "ability_warrior_innerrage", MultiBot.L("tips.warlock.tankAssist")).setDisable() .doLeft = function(pButton) if(MultiBot.OnOffActionToTarget(pButton, "co +tank assist,?", "co -tank assist,?", pButton.getName())) then pButton.getButton("DpsAssist").setDisable() @@ -425,22 +446,20 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) -- TANK -- - pFrame.addButton("Tank", -90, 0, "ability_warrior_shieldmastery", MultiBot.L("tips.warlock.tank")).setDisable() + pFrame.addButton("Tank", -60, 0, "ability_warrior_shieldmastery", MultiBot.L("tips.warlock.tank")).setDisable() .doLeft = function(pButton) - if(MultiBot.OnOffActionToTarget(pButton, "co +tank,?", "co -tank,?", pButton.getName())) then - pButton.getButton("Dps").setDisable() - end + MultiBot.OnOffActionToTarget(pButton, "co +tank,?", "co -tank,?", pButton.getName()) end -- CURSES -- local btnCurses = pFrame.addButton( - "CursesSelect", -120, 0, + "CursesSelect", -90, 0, "ability_warlock_avoidance", MultiBot.L("tips.warlock.curses.master") ) btnCurses._defaultIcon = "ability_warlock_avoidance" - local fCurses = pFrame.addFrame("Curses", -122, 30) + local fCurses = pFrame.addFrame("Curses", -92, 30) fCurses:Hide() fCurses.activeCurse = nil @@ -492,28 +511,38 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) b.doLeft = function(pButton) local target = pButton.getName() + local desired = nil + local action if fCurses.activeCurse == label then - SendChatMessage("co -" .. cmd .. ",?", "WHISPER", nil, target) - fCurses.activeCurse = nil - UpdateCurseIcons(nil) - fCurses:Hide() - return - end - - if fCurses.activeCurse then - local old = fCurses.activeCurse - for _,vv in ipairs(curseList) do - if vv[1]==old then - SendChatMessage("co -" .. vv[2], "WHISPER", nil, target) - break + action = "co -" .. cmd .. ",?" + else + desired = label + if fCurses.activeCurse then + local old = fCurses.activeCurse + local oldCmd = nil + for _,vv in ipairs(curseList) do + if vv[1]==old then + oldCmd = vv[2] + break + end + end + if oldCmd then + action = "co -" .. oldCmd .. ",+" .. cmd .. ",?" + else + action = "co +" .. cmd .. ",?" end + else + action = "co +" .. cmd .. ",?" end end - SendChatMessage("co +" .. cmd .. ",?", "WHISPER", nil, target) - fCurses.activeCurse = label - UpdateCurseIcons(fCurses.activeCurse) + local sent, transport = MultiBot.ActionToTarget(action, target) + if not sent then return end + if transport ~= "bridge" then + fCurses.activeCurse = desired + UpdateCurseIcons(fCurses.activeCurse) + end fCurses:Hide() end end @@ -530,9 +559,9 @@ MultiBot.addWarlock = function(pFrame, pCombat, pNormal) -- STRATEGIES -- - if(MultiBot.hasStrategy(pCombat, "dps")) then pFrame.getButton("Dps").setEnable() end + if(MultiBot.hasStrategy(pCombat, "dps aoe")) then pFrame.getButton("DpsAoe").setEnable() end - if(MultiBot.hasStrategy(pCombat, "dps debuff")) then pFrame.getButton("DpsDebuff").setEnable() end + if(MultiBot.hasStrategy(pCombat, "dps assist")) then pFrame.getButton("DpsAssist").setEnable() end if(MultiBot.hasStrategy(pCombat, "tank assist")) then pFrame.getButton("TankAssist").setEnable() end if(MultiBot.hasStrategy(pCombat, "tank")) then pFrame.getButton("Tank").setEnable() end diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 5f52c96..71bdba8 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,7 +1,7 @@ # Multibot Chatless + Bridge — Roadmap de reprise Statut : roadmap active issue de l'audit initial v1c du 1er août 2026. -Dernière mise à jour : 07/08/2026 — état post-PR #49/#50/#51 et audit final statique STATE/stratégies v1. +Dernière mise à jour : 08/08/2026 — validation runtime du lot Warlock chatless, diagnostic TEMP_ENCHANT et bascule Firestone/Spellstone bridge-only. Cette roadmap est la source de vérité active du projet. Les anciens trackers et le fichier `TODO.md` ont été consolidés ici. ## Baseline auditée @@ -170,9 +170,41 @@ Preuve d'audit final statique : Reste à terminer avant de fermer définitivement ce bloc : -- migrer les reliquats UI `co/nc` directs encore présents, notamment les sélecteurs Warlock de pierres, soulstones, pets et curses ; +- le lot Warlock Stones/Soulstones/Pets/Curses est validé au 08/08/2026 et ne fait plus partie des reliquats `co/nc` prioritaires ; - exécuter/consolider la matrice runtime finale : zéro/un/plusieurs bots, listes longues, fragment manquant/dupliqué/désordonné, réponse tardive, timeouts, déconnexion en cours de transaction, mutations valides/invalides, bot absent, plusieurs bots, smoke test toutes classes, zéro erreur Lua, contrôle chat et logs ; -- ne pas déclarer le projet entièrement chatless tant que ces reliquats et les autres familles legacy ne sont pas classifiés/migrés. +- classifier puis migrer les autres familles legacy réellement automatiques avant de déclarer le projet entièrement chatless. + +## Validation livrée — Sélecteurs Warlock chatless + Stones — VALIDÉE LE 08/08/2026 + +Périmètre addon validé : + +- les sélecteurs Warlock Stones, Soulstones, Pets et Curses ne contiennent plus de `SendChatMessage` direct pour leurs mutations `co/nc` ; ils passent par `MultiBot.ActionToTarget()` puis `STRATEGY_MUTATION_V1` / `RUN~STRATEGY` lorsque le bridge est disponible ; +- `MultiBot.ActionToTarget()` distingue désormais le transport `bridge` du fallback `chat` ; avec le bridge, les sélecteurs n'appliquent plus d'état local optimiste et attendent l'état serveur autoritatif ; le fallback chat conserve son comportement immédiat de compatibilité ; +- les contrôles Warlock invalides `dps` et `dps debuff` ont été retirés, le placeholder Buff désactivé a été supprimé et le layout des contrôles a été compacté ; +- les quatre avertissements LuaLint ciblés sur les variables `action` ont été corrigés sans modifier le comportement. + +Diagnostic TEMP_ENCHANT validé : + +- `/mbdebug enchant [bot]` envoie à la demande `GET~WEAPON_ENCHANT` et affiche la réponse structurée `WEAPON_ENCHANT` ; +- le bridge lit l'item, l'ID de `TEMP_ENCHANTMENT_SLOT` et sa durée sur main-hand/off-hand ; +- l'endpoint est limité au bot visible et contrôlable, conserve `CheckLevelFor(...)`, et applique un rate-limit de 500 ms par requester ; +- aucun polling automatique n'est introduit et `mod-playerbots` n'est pas modifié. + +Cause et correction Firestone/Spellstone : + +- l'audit Playerbots en lecture seule a confirmé que `ItemForSpellValue` et `UseItemAction::UseItem()` refusent de cibler une arme dont `TEMP_ENCHANTMENT_SLOT` est déjà occupé ; la stratégie peut donc changer sans remplacer la pierre déjà appliquée ; +- le correctif reste dans `mod-multibot-bridge` : uniquement pour un Warlock, en `BOT_STATE_NON_COMBAT`, lors d'un vrai switch exclusif `firestone` ↔ `spellstone` ; +- le bridge découvre dynamiquement les enchant IDs des Firestone/Spellstone portées par le bot, refuse d'effacer un enchantement temporaire non reconnu, retire proprement l'ancien enchantement reconnu, puis réutilise l'action Playerbots existante avec `DoSpecificAction()` ; +- aucun ID Firestone/Spellstone n'est hardcodé dans le correctif et aucun fichier de `mod-playerbots` n'est modifié. + +Preuves runtime : + +- compilation Visual Studio `RelWithDebInfo x64` : 3 projets réussis, 0 échec ; worldserver démarré sans erreur bridge ; +- Apha, Spellstone → Firestone : `TEMP_ENCHANTMENT_SLOT` `3620` → `3614`, durée finale `3600000 ms`, utilisation réelle de Grand Firestone observée ; +- Apha, Firestone → Spellstone : `TEMP_ENCHANTMENT_SLOT` `3614` → `3620`, durée finale `3600000 ms`, utilisation réelle de Grand Spellstone observée ; +- audit final : `audit-multibot-warlock-stone-force-switch-final-v1-2026-08-08-160400-2026-08-08-160706.zip`, SHA-256 `C0025FCAC7817711B0D5493EA3349B5F57A3AA620C260E76F59E1CAA92F7EA1A` ; +- archivage patch : `patch-multibot-warlock-stone-force-switch-v1b-2026-08-08-154300-results-2026-08-08-162451.zip`, SHA-256 `8FABF24B50EA459EF6C7EE4A0D0BE21CFB251D1C7DEF6505C7C483BF43141C5B` ; +- `mod-playerbots` reste strictement en lecture seule. ## Phase 1 — Baseline de compilation et tests de non-régression @@ -264,7 +296,7 @@ Ordre recommandé : 1. **Formations — application par clic gauche : VALIDÉE** via `RUN~FORMATION~GROUP` par `patch-multibot-formation-chatless-v1c-2026-08-01-181300`. 2. **Consultation de la formation actuelle par clic droit : VALIDÉE** via `GET~FORMATIONS~GROUP`, `FORMATIONS_BEGIN/ITEM/END` et un tooltip local traduit. 3. **Infrastructure mutations stratégies `co/nc` : VALIDÉE STATIQUEMENT** via `STRATEGY_MUTATION_V1`, `RUN~STRATEGY`, `STRATEGY_ACK`, timeouts, limites et diagnostics explicites. -4. **Reliquats UI `co/nc` directs : À MIGRER EN PRIORITÉ** — l'audit final relève notamment les sélecteurs Warlock de pierres, soulstones, pets et curses encore basés sur `SendChatMessage`. +4. **Sélecteurs Warlock Stones/Soulstones/Pets/Curses : VALIDÉS** — mutations via `STRATEGY_MUTATION_V1` / `RUN~STRATEGY`, état UI autoritatif côté bridge et bascule réelle Firestone/Spellstone validée sans modification de Playerbots. 5. `s *` — vente générale bridge-first. 6. `s vendor` — vente vendeur bridge-first, sans whisper item par item. 7. `open items` — ouverture de conteneurs bridge-first.