diff --git a/Core/MultiBotComm.lua b/Core/MultiBotComm.lua index 0e558e9..9d2c0ee 100644 --- a/Core/MultiBotComm.lua +++ b/Core/MultiBotComm.lua @@ -119,6 +119,7 @@ local function ensureBridgeState() state.glyphActive = state.glyphActive or nil state.rtiSeq = state.rtiSeq or 0 state.combatSeq = state.combatSeq or 0 + state.positionSeq = state.positionSeq or 0 return state end @@ -128,6 +129,27 @@ local function debugPrint(...) end end +local function L(key, fallback) + if MultiBot and type(MultiBot.L) == "function" then + return MultiBot.L(key, fallback) + end + + return fallback or key +end + +local function systemMessage(message) + message = trim(message) + if message == "" then + return + end + + if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.AddMessage then + DEFAULT_CHAT_FRAME:AddMessage(message) + elseif type(print) == "function" then + print(message) + end +end + local function buildMessage(opcode, payload) local message = trim(opcode) if payload ~= nil and payload ~= "" then @@ -295,6 +317,31 @@ function Comm.RunCombatCommand(scope, target, command) return Comm.Send("RUN", "COMBAT~" .. scope .. "~" .. urlEncodeField(target) .. "~" .. token .. "~" .. urlEncodeField(command)) end +function Comm.RunPositionCommand(scope, target, command) + local state = ensureBridgeState() + + if not state.connected then + return false + end + + command = trim(command or "") + if command == "" then + return false + end + + scope = string.upper(trim(scope or "ALL")) + target = trim(target or "") + + if scope ~= "ALL" and scope ~= "GROUP" and scope ~= "BOT" then + return false + end + + state.positionSeq = (tonumber(state.positionSeq) or 0) + 1 + local token = tostring(math.floor(safeNow() * 1000)) .. "-position-" .. tostring(state.positionSeq) + + return Comm.Send("RUN", "POSITION~" .. scope .. "~" .. urlEncodeField(target) .. "~" .. token .. "~" .. urlEncodeField(command)) +end + function Comm.RequestOutfits(name) local state = ensureBridgeState() name = trim(name) @@ -1648,6 +1695,34 @@ function Comm.HandleAddonMessage(prefix, message, distribution, sender) return true end + if opcode == "POSITION_ACK" then + state.connected = true + state.lastError = nil + debugPrint("ADDON:RX", "POSITION_ACK", payload or "") + + local scope, rest = splitOnce(payload or "", "~") + local encodedTarget, rest2 = splitOnce(rest, "~") + local token, rest3 = splitOnce(rest2, "~") + local executedText, encodedCommand = splitOnce(rest3, "~") + local executed = tonumber(executedText) or 0 + local command = trim(urlDecodeField(encodedCommand)) + + if executed > 0 then + local distance = string.match(command, "^disperse set%s+(.+)$") + + if distance then + systemMessage(string.format( + L("disperse.confirm.set", "Disperse set to %s yards."), + distance + )) + elseif command == "disperse disable" then + systemMessage(L("disperse.confirm.disable", "Disperse disabled.")) + end + end + + return true + end + if opcode == "ERR" then state.lastError = payload debugPrint("ADDON:RX", "ERR", payload or "") diff --git a/Locales/MultiBotAceLocale-deDE.lua b/Locales/MultiBotAceLocale-deDE.lua index e11cbae..7ccbf0b 100644 --- a/Locales/MultiBotAceLocale-deDE.lua +++ b/Locales/MultiBotAceLocale-deDE.lua @@ -5,6 +5,13 @@ end local deDEValues = { ["tips.units.rti"] = "RTI", + ["tips.disperse.main"] = "Disperse", + ["tips.disperse.set"] = "Disperse-Distanz setzen", + ["tips.disperse.disable"] = "Disperse deaktivieren", + ["disperse.bridge.required"] = "Position-Bridge ist nicht verbunden.", + ["disperse.invalid_distance"] = "Gib eine Distanz zwischen 1 und 100 Yards ein.", + ["disperse.confirm.set"] = "Disperse auf %s Yards gesetzt.", + ["disperse.confirm.disable"] = "Disperse deaktiviert.", ["rti.bridge.required"] = "Die RTI-Bridge ist nicht verbunden.", ["info.rti.no_bot_selection"] = "Kein botspezifisches RTI-Symbol ausgewählt.", ["rti.icon.star"] = "Stern", diff --git a/Locales/MultiBotAceLocale-enGB.lua b/Locales/MultiBotAceLocale-enGB.lua index b56a3a9..b38db3d 100644 --- a/Locales/MultiBotAceLocale-enGB.lua +++ b/Locales/MultiBotAceLocale-enGB.lua @@ -5,6 +5,13 @@ end local enGBValues = { ["tips.units.rti"] = "RTI", + ["tips.disperse.main"] = "Disperse", + ["tips.disperse.set"] = "Set disperse distance", + ["tips.disperse.disable"] = "Disable disperse", + ["disperse.bridge.required"] = "Position bridge is not connected.", + ["disperse.invalid_distance"] = "Enter a distance between 1 and 100 yards.", + ["disperse.confirm.set"] = "Disperse set to %s yards.", + ["disperse.confirm.disable"] = "Disperse disabled.", ["rti.bridge.required"] = "RTI bridge is not connected.", ["info.rti.no_bot_selection"] = "No bot-specific RTI icon selected.", ["rti.icon.star"] = "Star", diff --git a/Locales/MultiBotAceLocale-enUS.lua b/Locales/MultiBotAceLocale-enUS.lua index f595d59..a3ae60e 100644 --- a/Locales/MultiBotAceLocale-enUS.lua +++ b/Locales/MultiBotAceLocale-enUS.lua @@ -5,6 +5,13 @@ end local enUSValues = { ["tips.units.rti"] = "RTI", + ["tips.disperse.main"] = "Disperse", + ["tips.disperse.set"] = "Set disperse distance", + ["tips.disperse.disable"] = "Disable disperse", + ["disperse.bridge.required"] = "Position bridge is not connected.", + ["disperse.invalid_distance"] = "Enter a distance between 1 and 100 yards.", + ["disperse.confirm.set"] = "Disperse set to %s yards.", + ["disperse.confirm.disable"] = "Disperse disabled.", ["rti.bridge.required"] = "RTI bridge is not connected.", ["info.rti.no_bot_selection"] = "No bot-specific RTI icon selected.", ["rti.icon.star"] = "Star", diff --git a/Locales/MultiBotAceLocale-esES.lua b/Locales/MultiBotAceLocale-esES.lua index 9a894a4..8261914 100644 --- a/Locales/MultiBotAceLocale-esES.lua +++ b/Locales/MultiBotAceLocale-esES.lua @@ -5,6 +5,13 @@ end local esESValues = { ["tips.units.rti"] = "RTI", + ["tips.disperse.main"] = "Dispersar", + ["tips.disperse.set"] = "Definir distancia de dispersión", + ["tips.disperse.disable"] = "Desactivar dispersión", + ["disperse.bridge.required"] = "El puente de posición no está conectado.", + ["disperse.invalid_distance"] = "Introduce una distancia entre 1 y 100 yardas.", + ["disperse.confirm.set"] = "Dispersión establecida a %s yardas.", + ["disperse.confirm.disable"] = "Dispersión desactivada.", ["rti.bridge.required"] = "El puente RTI no está conectado.", ["info.rti.no_bot_selection"] = "No hay ningún icono RTI específico de bot seleccionado.", ["rti.icon.star"] = "Estrella", diff --git a/Locales/MultiBotAceLocale-frFR.lua b/Locales/MultiBotAceLocale-frFR.lua index 21e576e..bb72b5d 100644 --- a/Locales/MultiBotAceLocale-frFR.lua +++ b/Locales/MultiBotAceLocale-frFR.lua @@ -5,6 +5,13 @@ end local frFRValues = { ["tips.units.rti"] = "RTI", + ["tips.disperse.main"] = "Disperse", + ["tips.disperse.set"] = "Définir la distance de disperse", + ["tips.disperse.disable"] = "Désactiver disperse", + ["disperse.bridge.required"] = "Le bridge de position n’est pas connecté.", + ["disperse.invalid_distance"] = "Entre une distance entre 1 et 100 yards.", + ["disperse.confirm.set"] = "Disperse défini à %s yards.", + ["disperse.confirm.disable"] = "Disperse désactivé.", ["rti.bridge.required"] = "Le bridge RTI n'est pas connecté.", ["info.rti.no_bot_selection"] = "Aucun bot avec une icône RTI personnelle sélectionnée.", ["rti.icon.star"] = "Étoile", diff --git a/Locales/MultiBotAceLocale-koKR.lua b/Locales/MultiBotAceLocale-koKR.lua index ea7db72..248a6c2 100644 --- a/Locales/MultiBotAceLocale-koKR.lua +++ b/Locales/MultiBotAceLocale-koKR.lua @@ -5,6 +5,13 @@ end local koKRValues = { ["tips.units.rti"] = "RTI", + ["tips.disperse.main"] = "분산", + ["tips.disperse.set"] = "분산 거리 설정", + ["tips.disperse.disable"] = "분산 비활성화", + ["disperse.bridge.required"] = "위치 브리지가 연결되어 있지 않습니다.", + ["disperse.invalid_distance"] = "1에서 100 야드 사이의 거리를 입력하세요.", + ["disperse.confirm.set"] = "분산 거리가 %s 야드로 설정되었습니다.", + ["disperse.confirm.disable"] = "분산이 비활성화되었습니다.", ["rti.bridge.required"] = "RTI 브리지가 연결되어 있지 않습니다.", ["info.rti.no_bot_selection"] = "봇 전용 RTI 아이콘이 선택되지 않았습니다.", ["rti.icon.star"] = "별", diff --git a/Locales/MultiBotAceLocale-ruRU.lua b/Locales/MultiBotAceLocale-ruRU.lua index a3f9b41..732c01e 100644 --- a/Locales/MultiBotAceLocale-ruRU.lua +++ b/Locales/MultiBotAceLocale-ruRU.lua @@ -5,6 +5,13 @@ end local ruRUValues = { ["tips.units.rti"] = "RTI", + ["tips.disperse.main"] = "Рассредоточение", + ["tips.disperse.set"] = "Задать дистанцию рассредоточения", + ["tips.disperse.disable"] = "Отключить рассредоточение", + ["disperse.bridge.required"] = "Мост позиционирования не подключен.", + ["disperse.invalid_distance"] = "Введите дистанцию от 1 до 100 ярдов.", + ["disperse.confirm.set"] = "Рассредоточение установлено на %s ярдов.", + ["disperse.confirm.disable"] = "Рассредоточение отключено.", ["rti.bridge.required"] = "RTI-мост не подключен.", ["info.rti.no_bot_selection"] = "Не выбрана RTI-иконка для отдельного бота.", ["rti.icon.star"] = "Звезда", diff --git a/Locales/MultiBotAceLocale-zhCN.lua b/Locales/MultiBotAceLocale-zhCN.lua index 4fb7fed..0781101 100644 --- a/Locales/MultiBotAceLocale-zhCN.lua +++ b/Locales/MultiBotAceLocale-zhCN.lua @@ -5,6 +5,13 @@ end local zhCNValues = { ["tips.units.rti"] = "RTI", + ["tips.disperse.main"] = "分散", + ["tips.disperse.set"] = "设置分散距离", + ["tips.disperse.disable"] = "禁用分散", + ["disperse.bridge.required"] = "位置桥接未连接。", + ["disperse.invalid_distance"] = "请输入 1 到 100 码之间的距离。", + ["disperse.confirm.set"] = "分散距离已设置为 %s 码。", + ["disperse.confirm.disable"] = "分散已禁用。", ["rti.bridge.required"] = "RTI 桥接未连接。", ["info.rti.no_bot_selection"] = "未选择任何机器人专用 RTI 图标。", ["rti.icon.star"] = "星星", diff --git a/MultiBot.toc b/MultiBot.toc index 5e73fb0..814c5c8 100644 --- a/MultiBot.toc +++ b/MultiBot.toc @@ -100,6 +100,7 @@ UI\MultiBotMainUI.lua UI\MultiBotGmUI.lua UI\MultiBotRTSCUI.lua UI\MultiBotRTIUI.lua +UI\MultiBotDisperseUI.lua Core\MultiBotInit.lua Data\MultiBotIconos.lua Data\MultiBotItemus.lua diff --git a/README.md b/README.md index cd796c3..efca98b 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ GET~GLYPHS GET~OUTFITS RUN~RTI RUN~COMBAT +RUN~POSITION ``` Manual playerbot commands are still intentionally preserved for diagnostics and gameplay actions. @@ -171,6 +172,10 @@ The goal is to remove automatic UI-refresh spam. Combat strategy fine tuning Bridge-first avoid AoE, save mana, threat and behind controls + + Disperse controls + Bridge-first distance set and disable actions through RUN~POSITION + Units / EveryBars Improved login, reload and AddClass refresh behavior @@ -400,6 +405,7 @@ Implemented bridge-first / chatless areas: - RTI controls through the bridge. - Pull Control frame through the bridge. - Combat strategy fine tuning through the bridge. +- Disperse controls through the bridge with `disperse set ` and `disperse disable`. - Custom glyph socket mapping and apply order. - Talent tab navigation stability after switching between tabs. - Automatic bot reconnect on login/reload for bots already present in the group or raid. @@ -416,12 +422,11 @@ Kept intentionally: # Remaining Work -The Outfits, RTI, Pull Control and Combat Strategy migrations are implemented. The next step is Disperse, followed by final stabilization and cleanup. +The Outfits, RTI, Pull Control, Combat Strategy and Disperse migrations are implemented. The next step is final stabilization and cleanup. Planned follow-up work: -- Start the Disperse command migration. -- Regression test login, `/reload`, large raid groups, Units, EveryBars, Stats, Inventory, Spellbook, Talents, Glyphs, Outfits, RTI, Pull Control and Combat Strategies. +- Regression test login, `/reload`, large raid groups, Units, EveryBars, Stats, Inventory, Spellbook, Talents, Glyphs, Outfits, RTI, Pull Control, Combat Strategies and Disperse. - Verify that `MultiBot.allowLegacyChatFallback = false` prevents automatic legacy refresh spam on all migrated UI paths. - Keep manual diagnostic commands documented and functional. - Remove obsolete debug prints. diff --git a/UI/MultiBotDisperseUI.lua b/UI/MultiBotDisperseUI.lua new file mode 100644 index 0000000..7b1ddba --- /dev/null +++ b/UI/MultiBotDisperseUI.lua @@ -0,0 +1,175 @@ +local function L(key, fallback) + if MultiBot and MultiBot.L then + return MultiBot.L(key, fallback) + end + + return fallback or key +end + +local function TrimText(value) + if type(value) ~= "string" then + return "" + end + + return (value:gsub("^%s+", ""):gsub("%s+$", "")) +end + +local function NormalizeDistance(value) + local distance = tonumber(TrimText(value)) + if not distance or distance <= 0 then + return nil + end + + if distance > 100 then + return nil + end + + distance = math.floor(distance * 10 + 0.5) / 10 + if distance == math.floor(distance) then + return tostring(math.floor(distance)) + end + + return tostring(distance) +end + +local function RunDisperseCommand(command) + if not MultiBot or not MultiBot.Comm or not MultiBot.Comm.RunPositionCommand then + DEFAULT_CHAT_FRAME:AddMessage(L("disperse.bridge.required", "Position bridge is not connected.")) + return false + end + + local ok = MultiBot.Comm.RunPositionCommand("ALL", "", command) + if not ok then + DEFAULT_CHAT_FRAME:AddMessage(L("disperse.bridge.required", "Position bridge is not connected.")) + end + + return ok +end + +function MultiBot.BuildDisperseUI(tLeft) + if not tLeft or MultiBot.frames.disperse then + return MultiBot.frames.disperse + end + + local lastDistance = "10" + local button + local menu = tLeft.addFrame("DisperseMenu", -238, 34, 24, 56, 96).doHide() + local menuOpen = false + menu._mbDropdownManaged = true + menu:SetWidth(56) + menu:SetHeight(96) + + local input = CreateFrame("EditBox", "MultiBotDisperseDistanceEditBox", menu, "InputBoxTemplate") + input:SetAutoFocus(false) + input:SetMaxLetters(3) + input:SetWidth(25) + input:SetHeight(20) + input:SetPoint("TOPLEFT", menu, "TOPLEFT", 30, -23) + input:SetText(lastDistance) + input:SetFrameLevel(menu:GetFrameLevel() + 3) + input:SetScript("OnEscapePressed", function(self) + self:ClearFocus() + end) + + local function setDistance() + local distance = NormalizeDistance(input:GetText()) + if not distance then + DEFAULT_CHAT_FRAME:AddMessage(L("disperse.invalid_distance", "Enter a distance between 1 and 100 yards.")) + return + end + + lastDistance = distance + input:SetText(distance) + input:ClearFocus() + RunDisperseCommand("disperse set " .. distance) + end + + input:SetScript("OnEnterPressed", function() + setDistance() + end) + + local setButton = menu.addButton("Set", -3.5, 1, "spell_nature_wispsplode", L("tips.disperse.set", "Set disperse distance")) + setButton.doLeft = setDistance + + local disableButton = menu.addButton("Disable", -3.5, 27, "spell_nature_sleep", L("tips.disperse.disable", "Disable disperse")) + disableButton.doLeft = function() + input:ClearFocus() + RunDisperseCommand("disperse disable") + end + + local function updateClickBlocker() + if MultiBot.RequestClickBlockerUpdate then + MultiBot.RequestClickBlockerUpdate(menu) + end + end + + local function setDisperseMenuChildrenShown(shown) + if shown then + input:Show() + setButton:doShow() + disableButton:doShow() + + if button then + button.setEnable() + end + else + input:Hide() + setButton:doHide() + disableButton:doHide() + + if button then + button.setDisable() + end + end + + updateClickBlocker() + end + + local function hideDisperseMenu() + input:ClearFocus() + menuOpen = false + menu:Hide() + setDisperseMenuChildrenShown(false) + end + + local function showDisperseMenu() + menuOpen = true + input:SetText(lastDistance) + menu:Show() + setDisperseMenuChildrenShown(true) + end + + menu:HookScript("OnHide", function() + menuOpen = false + setDisperseMenuChildrenShown(false) + end) + + MultiBot.frames.disperseMenu = menu + + button = tLeft.addButton("Disperse", -238, 0, "spell_nature_wispsplode", L("tips.disperse.main", "Disperse")).setDisable() + button.doLeft = function() + if menuOpen then + hideDisperseMenu() + else + showDisperseMenu() + end + end + + button.doRight = function() + hideDisperseMenu() + RunDisperseCommand("disperse disable") + end + + hideDisperseMenu() + + MultiBot.frames.disperse = button + return button +end + +function MultiBot.InitializeDisperseUI() + if not MultiBot.frames or not MultiBot.frames.tLeft then + return nil + end + + return MultiBot.BuildDisperseUI(MultiBot.frames.tLeft) +end \ No newline at end of file diff --git a/UI/MultiBotLeftCoreUI.lua b/UI/MultiBotLeftCoreUI.lua index f84a45f..139f402 100644 --- a/UI/MultiBotLeftCoreUI.lua +++ b/UI/MultiBotLeftCoreUI.lua @@ -88,6 +88,10 @@ function MultiBot.InitializeLeftCoreUI(tLeft) MultiBot.BuildBotRTIActionUI(tLeft, -238, 0) end + if MultiBot.BuildDisperseUI then + MultiBot.BuildDisperseUI(tLeft) + end + tLeft.addButton("Tanker", -170, 0, "ability_warrior_shieldbash", MultiBot.L("tips.tanker.master")).doLeft = function() if MultiBot.isTarget() then MultiBot.ActionToGroup("@tank do attack my target") @@ -100,6 +104,7 @@ function MultiBot.InitializeLeftCoreUI(tLeft) if MultiBot.BindShiftRightSwapButtons then MultiBot.BindShiftRightSwapButtons(tLeft, "LeftRoot", { { name = "BotRTI", id = "BotRTIActionButton", frameName = "BotRTIAction" }, + { name = "Disperse", frameName = "DisperseMenu" }, { name = "Tanker" }, { name = "Mode", frameName = "Mode" }, { name = "Stay" }, diff --git a/UI/MultiBotMainUI.lua b/UI/MultiBotMainUI.lua index d06c3a8..ef41651 100644 --- a/UI/MultiBotMainUI.lua +++ b/UI/MultiBotMainUI.lua @@ -1393,7 +1393,7 @@ function MultiBot.InitializeMainUI(tMultiBar) local rewardButton = createRewardButton(mainFrame) wireShiftRightSwap(rewardButton, "Reward") - + local combatStrategiesButton = createCombatStrategiesButton(mainFrame) wireShiftRightSwap(combatStrategiesButton, "CombatStrategies") diff --git a/docs/multibot_missing_commands_roadmap.md b/docs/multibot_missing_commands_roadmap.md index 79ae027..508bd60 100644 --- a/docs/multibot_missing_commands_roadmap.md +++ b/docs/multibot_missing_commands_roadmap.md @@ -9,7 +9,7 @@ Ce document suit les commandes `mod-playerbots` encore intéressantes à intégr - les commandes serveur/admin à ne pas intégrer dans l'addon ; - les priorités d'intégration bridge-first/chatless. -Le principe reste le même que pour Inventory, Spellbook, Glyphs, Talents, Stats, Quests, Outfits, RTI, Pull Control et Combat Strategies : +Le principe reste le même que pour Inventory, Spellbook, Glyphs, Talents, Stats, Quests, Outfits, RTI, Pull Control, Combat Strategies et Disperse : **éviter le spam chat automatique**, utiliser le bridge quand c'est possible, et conserver les commandes manuelles utiles comme `who`, `co ?`, `nc ?`, `ss ?`. --- @@ -32,10 +32,11 @@ Le principe reste le même que pour Inventory, Spellbook, Glyphs, Talents, Stats | RTI / Target Icons | Fait | UI complète + bridge `RUN~RTI`, scopes `ALL`, `GROUP`, `BOT`. | | Pull Control | Fait / à fignoler | Mini-frame MainBar + bridge `RUN~COMBAT`, séquences de commandes, scopes et presets. | | Combat Strategies | Fait / à tester large | Toggles individuels dans les EveryBars + mini-frame Party/Raid, via `RUN~COMBAT`. | +| Disperse | Fait / à tester large | Mini-frame MainBar + bridge `RUN~POSITION`, distance 1-100 yards et disable. | --- -## Derniers lots terminés : Pull Control + Combat Strategies +## Derniers lots terminés : Pull Control + Combat Strategies + Disperse ### Pull Control @@ -104,6 +105,34 @@ Notes fonctionnelles importantes : - `wait for attack time X` n'est pas une stratégie `co`, donc il ne faut pas attendre qu'elle apparaisse forcément dans `co ?`. - Pour les nouvelles stratégies, le test principal est le comportement réel du bot et la présence d'un `COMBAT_ACK` côté bridge, pas uniquement l'affichage dans `co ?`. +### Disperse + +Le bloc `Disperse` a été ajouté pour contrôler rapidement l'espacement collectif des bots sans repasser par le chat. + +Fonctionnalités terminées : + +- bouton `Disperse` ajouté dans la MainBar ; +- mini-frame compacte ouverte depuis le bouton `Disperse` ; +- input distance configurable ; +- validation addon des distances autorisées entre `1` et `100` yards ; +- refus des valeurs invalides ou supérieures à `100`, sans clamp silencieux ; +- bouton `Set` envoyant `disperse set ` ; +- bouton `Disable` envoyant `disperse disable` ; +- clic droit sur le bouton principal pour désactiver rapidement `Disperse` ; +- endpoint bridge `RUN~POSITION~~~~` ; +- whitelist serveur limitée aux commandes `disperse set ` et `disperse disable` ; +- application native côté serveur via les valeurs playerbots de dispersion, sans parser une réponse chat ; +- ACK bridge `POSITION_ACK` ; +- confirmation en message système après ACK : distance définie ou dispersion désactivée ; +- état visuel normal au login : bouton principal grisé, input et boutons secondaires cachés ; +- ouverture du menu dès le premier clic après login. + +À tester plus largement : + +- comportement en groupe complet et raid ; +- interaction avec les mécaniques de déplacement existantes des bots ; +- lisibilité/position exacte de la mini-frame selon les résolutions et skins UI. + --- ## Priorité 1 - RTI / Target Icons @@ -315,31 +344,45 @@ Recommandation UX conservée : ### Statut -**Prochaine étape logique.** +**Terminé côté MultiBot + bridge, à tester en conditions réelles.** ### Pourquoi Très utile pour les mécaniques AoE ou les combats où les bots doivent s'espacer. -### Commandes à couvrir +### Commandes couvertes -| Commande playerbots | Statut MultiBot | Priorité | Proposition UI | +| Commande playerbots | Statut MultiBot | Priorité | UI actuelle | |---|---:|---:|---| -| `disperse set ` | Manquant | Moyenne | Champ distance + bouton Apply | -| `disperse disable` | Manquant | Moyenne | Bouton Disable | +| `disperse set ` | Fait | Moyenne | Input distance + bouton Set | +| `disperse disable` | Fait | Moyenne | Bouton Disable + clic droit sur le bouton principal | -### Proposition UI +### UX actuelle -Section simple : +```text +MainBar +└─ Disperse + ├─ Distance: [ 10 ] + ├─ Set + └─ Disable +``` + +### Flux bridge final ```text -Disperse distance: [ 8 ] yards -[Apply] [Disable] +RUN~POSITION~ALL~~~disperse set 10 +RUN~POSITION~ALL~~~disperse disable +POSITION_ACK~ALL~~~~disperse set 10 +POSITION_ACK~ALL~~~~disperse disable ``` -### Note +### Notes -À faire après stabilisation RTI/Pull Control/Combat Strategies, sauf si un autre besoin gameplay devient prioritaire. +- `Disperse` utilise un endpoint séparé `RUN~POSITION` pour éviter de mélanger le positionnement collectif avec les stratégies combat `RUN~COMBAT`. +- La distance est validée entre `1` et `100` yards côté addon et côté bridge. +- Les valeurs invalides ou supérieures à `100` sont refusées avec un message d'erreur localisé. +- La confirmation utilisateur est affichée seulement après réception du `POSITION_ACK`. +- Aucun parsing automatique de réponse chat n'est nécessaire. --- @@ -497,7 +540,7 @@ Ces commandes sont plutôt serveur/admin/debug ou trop dangereuses pour une UI u | 1 | RTI bridge-first | UI + bridge command | Haute | Fait | | 2 | Pull Control avancé | Nouvelle UI + séquences commandes | Haute | Fait / à fignoler | | 3 | Advanced Combat Strategies | EveryBars + mini-frame Party/Raid via `RUN~COMBAT` | Haute/Moyenne | Fait / à tester | -| 4 | Disperse | Petite UI + commande combat/mouvement | Moyenne | Prochaine étape | +| 4 | Disperse | Petite UI + commande positionnement via `RUN~POSITION` | Moyenne | Fait / à tester | | 5 | Loot Rules | Petite UI profils | Moyenne | À faire | | 6 | Trainer / Maintenance extras | UI maintenance | Moyenne/Basse | À faire | | 7 | Items avancés | Extensions inventaire | Basse/Moyenne | À faire | @@ -509,25 +552,26 @@ Ces commandes sont plutôt serveur/admin/debug ou trop dangereuses pour une UI u - Toute nouvelle commande utilisée automatiquement par l'addon devrait passer par le bridge quand possible. - Les commandes manuelles informatives doivent rester fonctionnelles en whisper/party/raid. - Ne pas réintroduire de parsing chat automatique pour peupler l'UI. -- Pour les commandes qui ne nécessitent aucun retour structuré, un endpoint générique de type `RUN~COMMAND` ou un endpoint spécialisé comme `RUN~RTI` / `RUN~COMBAT` peut suffire. +- Pour les commandes qui ne nécessitent aucun retour structuré, un endpoint générique de type `RUN~COMMAND` ou un endpoint spécialisé comme `RUN~RTI` / `RUN~COMBAT` / `RUN~POSITION` peut suffire. - Pour les commandes qui doivent alimenter une frame, préférer un endpoint structuré dédié. - Les commandes serveur/admin ne doivent pas être exposées dans l'addon utilisateur. - Les boutons ajoutés dans les barres doivent conserver une position cohérente avec `MultiBotLeftCoreUI.lua` et la position par défaut de `MultiBar` dans `MultiBotInit.lua` / reset dans `MultiBotMainUI.lua`. - Les tooltips nouvellement ajoutés doivent passer par AceLocale, comme les tooltips RTI, Pull Control et Combat Strategies. -- `RUN~COMBAT` doit rester whitelisté côté bridge : ne pas en faire un exécuteur libre de n'importe quelle commande chat. +- `RUN~COMBAT` et `RUN~POSITION` doivent rester whitelistés côté bridge : ne pas en faire des exécuteurs libres de n'importe quelle commande chat. - Éviter les doublons UI : si une stratégie dispose déjà d'un bouton EveryBar dédié, ne pas la rajouter dans une nouvelle frame sauf besoin UX clairement identifié. --- ## Point logique suivant -Le prochain bloc logique est **Disperse**. +Le prochain bloc logique est **Loot Rules / Loot List**. -Raison : les blocs RTI, Pull Control et Combat Strategies couvrent maintenant le ciblage, le pull et les stratégies combat de base. `Disperse` complète naturellement ce trio en ajoutant un contrôle de positionnement collectif très utile contre les AoE et les mécaniques de donjon/raid. +Raison : les blocs RTI, Pull Control, Combat Strategies et Disperse couvrent maintenant le ciblage, le pull, les stratégies combat et le positionnement collectif. Le prochain manque gameplay utile est donc le contrôle du loot, idéalement via une petite UI de profils et des commandes bridge-first whitelistées. Proposition de prochaine itération : -- vérifier d'abord la syntaxe exacte supportée par playerbots : `disperse set ` et `disperse disable` ; -- décider si l'action doit passer par `RUN~COMBAT` ou par un endpoint plus neutre, par exemple `RUN~MOVE` / `RUN~POSITION` si on veut garder `RUN~COMBAT` réservé aux stratégies combat ; -- ajouter une mini-frame simple avec distance configurable et bouton disable ; -- garder le fonctionnement chatless, sans parsing automatique de réponse. +- vérifier la syntaxe exacte réellement supportée par playerbots pour `nc +loot`, `nc -loot` et les variantes `ll` ; +- décider si le loot doit passer par un endpoint dédié, par exemple `RUN~LOOT`, ou par un endpoint générique whitelisté ; +- ajouter une mini-frame simple avec profils `All`, `Normal`, `Gray`, `Quest`, `Skill` ; +- garder les ajouts/retraits item par item pour une itération ultérieure depuis l'inventaire bridge ; +- conserver le fonctionnement chatless, sans parsing automatique de réponse.