diff --git a/Core/MultiBotComm.lua b/Core/MultiBotComm.lua
index 9d2c0ee..1f53269 100644
--- a/Core/MultiBotComm.lua
+++ b/Core/MultiBotComm.lua
@@ -120,6 +120,7 @@ local function ensureBridgeState()
state.rtiSeq = state.rtiSeq or 0
state.combatSeq = state.combatSeq or 0
state.positionSeq = state.positionSeq or 0
+ state.lootSeq = state.lootSeq or 0
return state
end
@@ -317,6 +318,31 @@ function Comm.RunCombatCommand(scope, target, command)
return Comm.Send("RUN", "COMBAT~" .. scope .. "~" .. urlEncodeField(target) .. "~" .. token .. "~" .. urlEncodeField(command))
end
+function Comm.RunLootCommand(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.lootSeq = (tonumber(state.lootSeq) or 0) + 1
+ local token = tostring(math.floor(safeNow() * 1000)) .. "-loot-" .. tostring(state.lootSeq)
+
+ return Comm.Send("RUN", "LOOT~" .. scope .. "~" .. urlEncodeField(target) .. "~" .. token .. "~" .. urlEncodeField(command))
+end
+
function Comm.RunPositionCommand(scope, target, command)
local state = ensureBridgeState()
@@ -1723,6 +1749,53 @@ function Comm.HandleAddonMessage(prefix, message, distribution, sender)
return true
end
+ if opcode == "LOOT_ACK" then
+ state.connected = true
+ state.lastError = nil
+ debugPrint("ADDON:RX", "LOOT_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 = string.lower(trim(urlDecodeField(encodedCommand)))
+
+ if executed <= 0 then
+ systemMessage(L("loot.confirm.none", "Loot command was not applied to any bot."))
+ return true
+ end
+
+ if MultiBot.OnLootCommandApplied then
+ MultiBot.OnLootCommandApplied(command, executed)
+ end
+
+ if command == "nc +loot" then
+ systemMessage(string.format(L("loot.confirm.enable", "Loot enabled for %d bot(s)."), executed))
+ return true
+ end
+
+ if command == "nc -loot" then
+ systemMessage(string.format(L("loot.confirm.disable", "Loot disabled for %d bot(s)."), executed))
+ return true
+ end
+
+ local profile = command:match("^ll%s+([%w_%-]+)$")
+ if profile then
+ local profileName = ({
+ all = L("loot.profile.all", "All"),
+ normal = L("loot.profile.normal", "Normal"),
+ gray = L("loot.profile.gray", "Gray"),
+ quest = L("loot.profile.quest", "Quest"),
+ skill = L("loot.profile.skill", "Skill"),
+ })[profile] or profile
+
+ systemMessage(string.format(L("loot.confirm.profile", "Loot profile set to %s for %d bot(s)."), profileName, executed))
+ 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 7ccbf0b..2ca6976 100644
--- a/Locales/MultiBotAceLocale-deDE.lua
+++ b/Locales/MultiBotAceLocale-deDE.lua
@@ -8,6 +8,24 @@ local deDEValues = {
["tips.disperse.main"] = "Disperse",
["tips.disperse.set"] = "Disperse-Distanz setzen",
["tips.disperse.disable"] = "Disperse deaktivieren",
+ ["tips.loot.main"] = "Beuteregeln",
+ ["tips.loot.enable"] = "Beute aktivieren",
+ ["tips.loot.disable"] = "Beute deaktivieren",
+ ["tips.loot.all"] = "Beuteprofil: Alles",
+ ["tips.loot.normal"] = "Beuteprofil: Normal",
+ ["tips.loot.gray"] = "Beuteprofil: Grau",
+ ["tips.loot.quest"] = "Beuteprofil: Quest",
+ ["tips.loot.skill"] = "Beuteprofil: Beruf",
+ ["loot.bridge.required"] = "Die Beute-Bridge ist nicht verbunden.",
+ ["loot.confirm.none"] = "Der Beutebefehl wurde auf keinen Bot angewendet.",
+ ["loot.confirm.enable"] = "Beute für %d Bot(s) aktiviert.",
+ ["loot.confirm.disable"] = "Beute für %d Bot(s) deaktiviert.",
+ ["loot.confirm.profile"] = "Beuteprofil auf %s für %d Bot(s) gesetzt.",
+ ["loot.profile.all"] = "Alles",
+ ["loot.profile.normal"] = "Normal",
+ ["loot.profile.gray"] = "Grau",
+ ["loot.profile.quest"] = "Quest",
+ ["loot.profile.skill"] = "Beruf",
["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.",
diff --git a/Locales/MultiBotAceLocale-enGB.lua b/Locales/MultiBotAceLocale-enGB.lua
index b38db3d..abb65fa 100644
--- a/Locales/MultiBotAceLocale-enGB.lua
+++ b/Locales/MultiBotAceLocale-enGB.lua
@@ -8,6 +8,24 @@ local enGBValues = {
["tips.disperse.main"] = "Disperse",
["tips.disperse.set"] = "Set disperse distance",
["tips.disperse.disable"] = "Disable disperse",
+ ["tips.loot.main"] = "Loot rules",
+ ["tips.loot.enable"] = "Enable loot",
+ ["tips.loot.disable"] = "Disable loot",
+ ["tips.loot.all"] = "Loot profile: All",
+ ["tips.loot.normal"] = "Loot profile: Normal",
+ ["tips.loot.gray"] = "Loot profile: Gray",
+ ["tips.loot.quest"] = "Loot profile: Quest",
+ ["tips.loot.skill"] = "Loot profile: Skill",
+ ["loot.bridge.required"] = "Loot bridge is not connected.",
+ ["loot.confirm.none"] = "Loot command was not applied to any bot.",
+ ["loot.confirm.enable"] = "Loot enabled for %d bot(s).",
+ ["loot.confirm.disable"] = "Loot disabled for %d bot(s).",
+ ["loot.confirm.profile"] = "Loot profile set to %s for %d bot(s).",
+ ["loot.profile.all"] = "All",
+ ["loot.profile.normal"] = "Normal",
+ ["loot.profile.gray"] = "Gray",
+ ["loot.profile.quest"] = "Quest",
+ ["loot.profile.skill"] = "Skill",
["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.",
diff --git a/Locales/MultiBotAceLocale-enUS.lua b/Locales/MultiBotAceLocale-enUS.lua
index a3ae60e..7431df4 100644
--- a/Locales/MultiBotAceLocale-enUS.lua
+++ b/Locales/MultiBotAceLocale-enUS.lua
@@ -8,6 +8,24 @@ local enUSValues = {
["tips.disperse.main"] = "Disperse",
["tips.disperse.set"] = "Set disperse distance",
["tips.disperse.disable"] = "Disable disperse",
+ ["tips.loot.main"] = "Loot rules",
+ ["tips.loot.enable"] = "Enable loot",
+ ["tips.loot.disable"] = "Disable loot",
+ ["tips.loot.all"] = "Loot profile: All",
+ ["tips.loot.normal"] = "Loot profile: Normal",
+ ["tips.loot.gray"] = "Loot profile: Gray",
+ ["tips.loot.quest"] = "Loot profile: Quest",
+ ["tips.loot.skill"] = "Loot profile: Skill",
+ ["loot.bridge.required"] = "Loot bridge is not connected.",
+ ["loot.confirm.none"] = "Loot command was not applied to any bot.",
+ ["loot.confirm.enable"] = "Loot enabled for %d bot(s).",
+ ["loot.confirm.disable"] = "Loot disabled for %d bot(s).",
+ ["loot.confirm.profile"] = "Loot profile set to %s for %d bot(s).",
+ ["loot.profile.all"] = "All",
+ ["loot.profile.normal"] = "Normal",
+ ["loot.profile.gray"] = "Gray",
+ ["loot.profile.quest"] = "Quest",
+ ["loot.profile.skill"] = "Skill",
["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.",
diff --git a/Locales/MultiBotAceLocale-esES.lua b/Locales/MultiBotAceLocale-esES.lua
index 8261914..00ff256 100644
--- a/Locales/MultiBotAceLocale-esES.lua
+++ b/Locales/MultiBotAceLocale-esES.lua
@@ -8,6 +8,24 @@ local esESValues = {
["tips.disperse.main"] = "Dispersar",
["tips.disperse.set"] = "Definir distancia de dispersión",
["tips.disperse.disable"] = "Desactivar dispersión",
+ ["tips.loot.main"] = "Reglas de botín",
+ ["tips.loot.enable"] = "Activar botín",
+ ["tips.loot.disable"] = "Desactivar botín",
+ ["tips.loot.all"] = "Perfil de botín: Todo",
+ ["tips.loot.normal"] = "Perfil de botín: Normal",
+ ["tips.loot.gray"] = "Perfil de botín: Gris",
+ ["tips.loot.quest"] = "Perfil de botín: Misión",
+ ["tips.loot.skill"] = "Perfil de botín: Profesión",
+ ["loot.bridge.required"] = "El bridge de botín no está conectado.",
+ ["loot.confirm.none"] = "El comando de botín no se aplicó a ningún bot.",
+ ["loot.confirm.enable"] = "Botín activado para %d bot(s).",
+ ["loot.confirm.disable"] = "Botín desactivado para %d bot(s).",
+ ["loot.confirm.profile"] = "Perfil de botín establecido en %s para %d bot(s).",
+ ["loot.profile.all"] = "Todo",
+ ["loot.profile.normal"] = "Normal",
+ ["loot.profile.gray"] = "Gris",
+ ["loot.profile.quest"] = "Misión",
+ ["loot.profile.skill"] = "Profesió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.",
diff --git a/Locales/MultiBotAceLocale-frFR.lua b/Locales/MultiBotAceLocale-frFR.lua
index bb72b5d..0c4bb3f 100644
--- a/Locales/MultiBotAceLocale-frFR.lua
+++ b/Locales/MultiBotAceLocale-frFR.lua
@@ -8,6 +8,24 @@ local frFRValues = {
["tips.disperse.main"] = "Disperse",
["tips.disperse.set"] = "Définir la distance de disperse",
["tips.disperse.disable"] = "Désactiver disperse",
+ ["tips.loot.main"] = "Règles de butin",
+ ["tips.loot.enable"] = "Activer le butin",
+ ["tips.loot.disable"] = "Désactiver le butin",
+ ["tips.loot.all"] = "Profil de butin : Tout",
+ ["tips.loot.normal"] = "Profil de butin : Normal",
+ ["tips.loot.gray"] = "Profil de butin : Gris",
+ ["tips.loot.quest"] = "Profil de butin : Quête",
+ ["tips.loot.skill"] = "Profil de butin : Métier",
+ ["loot.bridge.required"] = "Le bridge du butin n’est pas connecté.",
+ ["loot.confirm.none"] = "La commande de butin n’a été appliquée à aucun bot.",
+ ["loot.confirm.enable"] = "Butin activé pour %d bot(s).",
+ ["loot.confirm.disable"] = "Butin désactivé pour %d bot(s).",
+ ["loot.confirm.profile"] = "Profil de butin défini sur %s pour %d bot(s).",
+ ["loot.profile.all"] = "Tout",
+ ["loot.profile.normal"] = "Normal",
+ ["loot.profile.gray"] = "Gris",
+ ["loot.profile.quest"] = "Quête",
+ ["loot.profile.skill"] = "Métier",
["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.",
diff --git a/Locales/MultiBotAceLocale-koKR.lua b/Locales/MultiBotAceLocale-koKR.lua
index 248a6c2..9aeeaf7 100644
--- a/Locales/MultiBotAceLocale-koKR.lua
+++ b/Locales/MultiBotAceLocale-koKR.lua
@@ -8,6 +8,24 @@ local koKRValues = {
["tips.disperse.main"] = "분산",
["tips.disperse.set"] = "분산 거리 설정",
["tips.disperse.disable"] = "분산 비활성화",
+ ["tips.loot.main"] = "전리품 규칙",
+ ["tips.loot.enable"] = "전리품 켜기",
+ ["tips.loot.disable"] = "전리품 끄기",
+ ["tips.loot.all"] = "전리품 프로필: 전체",
+ ["tips.loot.normal"] = "전리품 프로필: 일반",
+ ["tips.loot.gray"] = "전리품 프로필: 회색",
+ ["tips.loot.quest"] = "전리품 프로필: 퀘스트",
+ ["tips.loot.skill"] = "전리품 프로필: 전문기술",
+ ["loot.bridge.required"] = "전리품 브리지가 연결되어 있지 않습니다.",
+ ["loot.confirm.none"] = "전리품 명령이 어떤 봇에도 적용되지 않았습니다.",
+ ["loot.confirm.enable"] = "%d 봇에게 전리품을 켰습니다.",
+ ["loot.confirm.disable"] = "%d 봇에게 전리품을 껐습니다.",
+ ["loot.confirm.profile"] = "전리품 프로필을 %s로 설정했습니다. 대상 봇: %d.",
+ ["loot.profile.all"] = "전체",
+ ["loot.profile.normal"] = "일반",
+ ["loot.profile.gray"] = "회색",
+ ["loot.profile.quest"] = "퀘스트",
+ ["loot.profile.skill"] = "전문기술",
["disperse.bridge.required"] = "위치 브리지가 연결되어 있지 않습니다.",
["disperse.invalid_distance"] = "1에서 100 야드 사이의 거리를 입력하세요.",
["disperse.confirm.set"] = "분산 거리가 %s 야드로 설정되었습니다.",
diff --git a/Locales/MultiBotAceLocale-ruRU.lua b/Locales/MultiBotAceLocale-ruRU.lua
index 732c01e..9db2f8a 100644
--- a/Locales/MultiBotAceLocale-ruRU.lua
+++ b/Locales/MultiBotAceLocale-ruRU.lua
@@ -8,6 +8,24 @@ local ruRUValues = {
["tips.disperse.main"] = "Рассредоточение",
["tips.disperse.set"] = "Задать дистанцию рассредоточения",
["tips.disperse.disable"] = "Отключить рассредоточение",
+ ["tips.loot.main"] = "Правила добычи",
+ ["tips.loot.enable"] = "Включить добычу",
+ ["tips.loot.disable"] = "Отключить добычу",
+ ["tips.loot.all"] = "Профиль добычи: Всё",
+ ["tips.loot.normal"] = "Профиль добычи: Обычное",
+ ["tips.loot.gray"] = "Профиль добычи: Серое",
+ ["tips.loot.quest"] = "Профиль добычи: Задания",
+ ["tips.loot.skill"] = "Профиль добычи: Профессии",
+ ["loot.bridge.required"] = "Мост добычи не подключён.",
+ ["loot.confirm.none"] = "Команда добычи не применена ни к одному боту.",
+ ["loot.confirm.enable"] = "Добыча включена для %d бот(ов).",
+ ["loot.confirm.disable"] = "Добыча отключена для %d бот(ов).",
+ ["loot.confirm.profile"] = "Профиль добычи установлен на %s для %d бот(ов).",
+ ["loot.profile.all"] = "Всё",
+ ["loot.profile.normal"] = "Обычное",
+ ["loot.profile.gray"] = "Серое",
+ ["loot.profile.quest"] = "Задания",
+ ["loot.profile.skill"] = "Профессии",
["disperse.bridge.required"] = "Мост позиционирования не подключен.",
["disperse.invalid_distance"] = "Введите дистанцию от 1 до 100 ярдов.",
["disperse.confirm.set"] = "Рассредоточение установлено на %s ярдов.",
diff --git a/Locales/MultiBotAceLocale-zhCN.lua b/Locales/MultiBotAceLocale-zhCN.lua
index 0781101..e80114b 100644
--- a/Locales/MultiBotAceLocale-zhCN.lua
+++ b/Locales/MultiBotAceLocale-zhCN.lua
@@ -8,6 +8,24 @@ local zhCNValues = {
["tips.disperse.main"] = "分散",
["tips.disperse.set"] = "设置分散距离",
["tips.disperse.disable"] = "禁用分散",
+ ["tips.loot.main"] = "拾取规则",
+ ["tips.loot.enable"] = "启用拾取",
+ ["tips.loot.disable"] = "禁用拾取",
+ ["tips.loot.all"] = "拾取配置:全部",
+ ["tips.loot.normal"] = "拾取配置:普通",
+ ["tips.loot.gray"] = "拾取配置:灰色",
+ ["tips.loot.quest"] = "拾取配置:任务",
+ ["tips.loot.skill"] = "拾取配置:专业",
+ ["loot.bridge.required"] = "拾取桥接未连接。",
+ ["loot.confirm.none"] = "拾取命令未应用到任何机器人。",
+ ["loot.confirm.enable"] = "已为 %d 个机器人启用拾取。",
+ ["loot.confirm.disable"] = "已为 %d 个机器人禁用拾取。",
+ ["loot.confirm.profile"] = "已将拾取配置设置为 %s,影响 %d 个机器人。",
+ ["loot.profile.all"] = "全部",
+ ["loot.profile.normal"] = "普通",
+ ["loot.profile.gray"] = "灰色",
+ ["loot.profile.quest"] = "任务",
+ ["loot.profile.skill"] = "专业",
["disperse.bridge.required"] = "位置桥接未连接。",
["disperse.invalid_distance"] = "请输入 1 到 100 码之间的距离。",
["disperse.confirm.set"] = "分散距离已设置为 %s 码。",
diff --git a/MultiBot.toc b/MultiBot.toc
index 814c5c8..e90e6fd 100644
--- a/MultiBot.toc
+++ b/MultiBot.toc
@@ -101,6 +101,7 @@ UI\MultiBotGmUI.lua
UI\MultiBotRTSCUI.lua
UI\MultiBotRTIUI.lua
UI\MultiBotDisperseUI.lua
+UI\MultiBotLootUI.lua
Core\MultiBotInit.lua
Data\MultiBotIconos.lua
Data\MultiBotItemus.lua
diff --git a/README.md b/README.md
index efca98b..60541ac 100644
--- a/README.md
+++ b/README.md
@@ -97,6 +97,7 @@ GET~OUTFITS
RUN~RTI
RUN~COMBAT
RUN~POSITION
+RUN~LOOT
```
Manual playerbot commands are still intentionally preserved for diagnostics and gameplay actions.
@@ -176,6 +177,10 @@ The goal is to remove automatic UI-refresh spam.
Disperse controls |
Bridge-first distance set and disable actions through RUN~POSITION |
+
+ | Loot rules |
+ Bridge-first loot enable/disable and loot list profiles through RUN~LOOT |
+
| Units / EveryBars |
Improved login, reload and AddClass refresh behavior |
@@ -406,6 +411,7 @@ Implemented bridge-first / chatless areas:
- Pull Control frame through the bridge.
- Combat strategy fine tuning through the bridge.
- Disperse controls through the bridge with `disperse set ` and `disperse disable`.
+- Loot rules through the bridge with `nc +loot`, `nc -loot` and `ll all|normal|gray|quest|skill`.
- 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.
@@ -422,11 +428,11 @@ Kept intentionally:
# Remaining Work
-The Outfits, RTI, Pull Control, Combat Strategy and Disperse migrations are implemented. The next step is final stabilization and cleanup.
+The Outfits, RTI, Pull Control, Combat Strategy, Disperse and Loot Rules migrations are implemented. The next step is final stabilization and cleanup.
Planned follow-up work:
-- Regression test login, `/reload`, large raid groups, Units, EveryBars, Stats, Inventory, Spellbook, Talents, Glyphs, Outfits, RTI, Pull Control, Combat Strategies and Disperse.
+- Regression test login, `/reload`, large raid groups, Units, EveryBars, Stats, Inventory, Spellbook, Talents, Glyphs, Outfits, RTI, Pull Control, Combat Strategies, Disperse and Loot Rules.
- 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/TODO.md b/TODO.md
index a116a4a..6b2333f 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,4 +1,5 @@
ATTENTION: Nouveau outfit par bridge il faut voire le cas de deux armes deux mains, le bot ne les équipe pas!
+* J'ai l'impression que le disperse ne fait rien
* Passer les gobject dans la bridge
* Recentrer les icones des glyphes
* Mettre un bouton à raidus pour enlever les bots inconnus et les virer en même temps de saved variables
@@ -18,6 +19,9 @@ ATTENTION: Nouveau outfit par bridge il faut voire le cas de deux armes deux mai
* trouver un moyen de charger tous les skins des pets hunter
* Implémenter Focus
* Implémenter new pull
+* Voir pour l'inventaire bridge si pas limité à 16 places
+* Ne pas faire apparaitre les quick bares pour les joueurs humain
+* Remplacer dans le menu loot Quest et Skill par desanchante
-- Nouvelles fonctions:
Gestion des Outfits
diff --git a/UI/MultiBotDisperseUI.lua b/UI/MultiBotDisperseUI.lua
index 7b1ddba..8d090a2 100644
--- a/UI/MultiBotDisperseUI.lua
+++ b/UI/MultiBotDisperseUI.lua
@@ -62,9 +62,9 @@ function MultiBot.BuildDisperseUI(tLeft)
local input = CreateFrame("EditBox", "MultiBotDisperseDistanceEditBox", menu, "InputBoxTemplate")
input:SetAutoFocus(false)
input:SetMaxLetters(3)
- input:SetWidth(25)
+ input:SetWidth(40)
input:SetHeight(20)
- input:SetPoint("TOPLEFT", menu, "TOPLEFT", 30, -23)
+ input:SetPoint("TOPLEFT", menu, "TOPLEFT", 23, -23)
input:SetText(lastDistance)
input:SetFrameLevel(menu:GetFrameLevel() + 3)
input:SetScript("OnEscapePressed", function(self)
@@ -152,7 +152,7 @@ function MultiBot.BuildDisperseUI(tLeft)
hideDisperseMenu()
else
showDisperseMenu()
- end
+ end
end
button.doRight = function()
diff --git a/UI/MultiBotLeftCoreUI.lua b/UI/MultiBotLeftCoreUI.lua
index 139f402..730dc66 100644
--- a/UI/MultiBotLeftCoreUI.lua
+++ b/UI/MultiBotLeftCoreUI.lua
@@ -92,6 +92,10 @@ function MultiBot.InitializeLeftCoreUI(tLeft)
MultiBot.BuildDisperseUI(tLeft)
end
+ if MultiBot.BuildLootUI then
+ MultiBot.BuildLootUI(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")
@@ -105,6 +109,7 @@ function MultiBot.InitializeLeftCoreUI(tLeft)
MultiBot.BindShiftRightSwapButtons(tLeft, "LeftRoot", {
{ name = "BotRTI", id = "BotRTIActionButton", frameName = "BotRTIAction" },
{ name = "Disperse", frameName = "DisperseMenu" },
+ { name = "Loot", frameName = "LootMenu" },
{ name = "Tanker" },
{ name = "Mode", frameName = "Mode" },
{ name = "Stay" },
diff --git a/UI/MultiBotLootUI.lua b/UI/MultiBotLootUI.lua
new file mode 100644
index 0000000..7c0c187
--- /dev/null
+++ b/UI/MultiBotLootUI.lua
@@ -0,0 +1,187 @@
+local function L(key, fallback)
+ if MultiBot and MultiBot.L then
+ return MultiBot.L(key, fallback)
+ end
+
+ return fallback or key
+end
+
+local LOOT_COMMANDS = {
+ { key = "enable", command = "nc +loot", icon = "inv_misc_bag_08", tip = "tips.loot.enable", fallback = "Enable loot" },
+ { key = "disable", command = "nc -loot", icon = "inv_misc_bag_07", tip = "tips.loot.disable", fallback = "Disable loot" },
+ { key = "all", command = "ll all", icon = "inv_misc_bag_10", tip = "tips.loot.all", fallback = "Loot profile: All" },
+ { key = "normal", command = "ll normal", icon = "inv_misc_bag_11", tip = "tips.loot.normal", fallback = "Loot profile: Normal" },
+ { key = "gray", command = "ll gray", icon = "inv_misc_coin_01", tip = "tips.loot.gray", fallback = "Loot profile: Gray" },
+ { key = "quest", command = "ll quest", icon = "inv_misc_note_05", tip = "tips.loot.quest", fallback = "Loot profile: Quest" },
+ { key = "skill", command = "ll skill", icon = "inv_misc_book_07", tip = "tips.loot.skill", fallback = "Loot profile: Skill" },
+}
+
+local LOOT_PROFILE_KEYS = {
+ all = true,
+ normal = true,
+ gray = true,
+ quest = true,
+ skill = true,
+}
+
+local lootVisualState = {
+ enabled = nil,
+ profile = nil,
+}
+
+local function NormalizeLootCommand(command)
+ return string.lower((command or ""):gsub("^%s+", ""):gsub("%s+$", ""))
+end
+
+local function RunLootCommand(command)
+ if not MultiBot or not MultiBot.Comm or not MultiBot.Comm.RunLootCommand then
+ DEFAULT_CHAT_FRAME:AddMessage(L("loot.bridge.required", "Loot bridge is not connected."))
+ return false
+ end
+
+ local ok = MultiBot.Comm.RunLootCommand("ALL", "", command)
+ if not ok then
+ DEFAULT_CHAT_FRAME:AddMessage(L("loot.bridge.required", "Loot bridge is not connected."))
+ end
+
+ return ok
+end
+
+function MultiBot.BuildLootUI(tLeft)
+ if not tLeft or MultiBot.frames.loot then
+ return MultiBot.frames.loot
+ end
+
+ local button
+ local menu = tLeft.addFrame("LootMenu", -277, 34, 24, 24, 170).doHide()
+ local menuOpen = false
+ local menuButtons = {}
+ local menuButtonsByKey = {}
+ menu._mbDropdownManaged = true
+ menu:SetWidth(24)
+ menu:SetHeight(170)
+
+ local function updateClickBlocker()
+ if MultiBot.RequestClickBlockerUpdate then
+ MultiBot.RequestClickBlockerUpdate(menu)
+ end
+ end
+
+ local function applyLootVisualState()
+ for _, menuButton in pairs(menuButtonsByKey) do
+ if menuButton and menuButton.setDisable then
+ menuButton.setDisable()
+ end
+ end
+
+ if lootVisualState.enabled == true and menuButtonsByKey.enable then
+ menuButtonsByKey.enable.setEnable()
+ elseif lootVisualState.enabled == false and menuButtonsByKey.disable then
+ menuButtonsByKey.disable.setEnable()
+ end
+
+ if lootVisualState.profile and menuButtonsByKey[lootVisualState.profile] then
+ menuButtonsByKey[lootVisualState.profile].setEnable()
+ end
+ end
+
+ MultiBot.OnLootCommandApplied = function(command, executed)
+ local applied = tonumber(executed) or 0
+ if applied <= 0 then
+ return
+ end
+
+ command = NormalizeLootCommand(command)
+
+ if command == "nc +loot" then
+ lootVisualState.enabled = true
+ elseif command == "nc -loot" then
+ lootVisualState.enabled = false
+ lootVisualState.profile = nil
+ else
+ local profile = command:match("^ll%s+([%w_%-]+)$")
+ if profile and LOOT_PROFILE_KEYS[profile] then
+ lootVisualState.profile = profile
+ end
+ end
+
+ applyLootVisualState()
+ end
+
+ local function setLootMenuChildrenShown(shown)
+ for _, menuButton in ipairs(menuButtons) do
+ if shown then
+ menuButton:doShow()
+ else
+ menuButton:doHide()
+ end
+ end
+
+ if button then
+ if shown then
+ button.setEnable()
+ else
+ button.setDisable()
+ end
+ end
+
+ updateClickBlocker()
+ end
+
+ local function hideLootMenu()
+ menuOpen = false
+ menu:Hide()
+ setLootMenuChildrenShown(false)
+ end
+
+ local function showLootMenu()
+ menuOpen = true
+ menu:Show()
+ setLootMenuChildrenShown(true)
+ end
+
+ menu:HookScript("OnHide", function()
+ menuOpen = false
+ setLootMenuChildrenShown(false)
+ end)
+
+ for index, entry in ipairs(LOOT_COMMANDS) do
+ local menuButton = menu.addButton("Loot" .. entry.key, 0, (index - 1) * 24, entry.icon, L(entry.tip, entry.fallback))
+ menuButton.doLeft = function()
+ RunLootCommand(entry.command)
+ end
+
+ menuButtons[index] = menuButton
+ menuButtonsByKey[entry.key] = menuButton
+ end
+ applyLootVisualState()
+
+ MultiBot.frames.lootMenu = menu
+
+ button = tLeft.addButton("Loot", -272, 0, "inv_misc_bag_10", L("tips.loot.main", "Loot rules")).setDisable()
+ button.doLeft = function()
+ if menuOpen then
+ hideLootMenu()
+ else
+ showLootMenu()
+ end
+ end
+
+ button.doRight = function()
+ hideLootMenu()
+ RunLootCommand("nc -loot")
+ end
+
+ hideLootMenu()
+
+ MultiBot.frames.loot = button
+ return button
+end
+
+function MultiBot.InitializeLootUI()
+ if not MultiBot.frames or not MultiBot.frames.tLeft then
+ return nil
+ end
+
+ return MultiBot.BuildLootUI(MultiBot.frames.tLeft)
+end
\ No newline at end of file
diff --git a/docs/multibot_bridge_chatless_roadmap.md b/docs/multibot_bridge_chatless_roadmap.md
index 94e7c6e..31fc464 100644
--- a/docs/multibot_bridge_chatless_roadmap.md
+++ b/docs/multibot_bridge_chatless_roadmap.md
@@ -191,6 +191,16 @@ Le but n’est pas de supprimer ces commandes. Le but est de ne plus les lancer
- [x] Fallback legacy `outfit ?` conservé uniquement comme compatibilité si nécessaire
- [x] Aucun changement requis dans `mod-playerbots` : la migration est faite côté addon + bridge
+### Loot Rules / Loot List
+
+- [x] Endpoint côté bridge : `RUN~LOOT~~~~`
+- [x] Whitelist stricte côté bridge : `nc +loot`, `nc -loot`, `ll all`, `ll normal`, `ll gray`, `ll quest`, `ll skill`
+- [x] Mini-frame addon `Loot` avec boutons Enable, Disable, All, Normal, Gray, Quest et Skill
+- [x] Réponse d’action : `LOOT_ACK~~~~~`
+- [x] Messages système de confirmation côté addon
+- [x] Pas de parsing automatique de réponse `nc ?` ou `ll`
+- [x] Les commandes manuelles informatives restent disponibles
+
---
## Règle importante sur les payloads volumineux
@@ -317,6 +327,7 @@ Conclusion : `Outfits` est maintenant bridge-first. Il reste seulement à survei
| Glyphes icônes / tooltips | Fait |
| Outfits bridge | Fait |
| Outfits sans spam détaillé `Equipping [item]` | Fait |
+| Loot Rules / Loot List bridge | Fait |
| Custom Talents navigation | Corrigé |
| Custom Glyphs mapping sockets | Corrigé |
| Switch `MultiBot.allowLegacyChatFallback` | Fait |
@@ -330,17 +341,20 @@ Conclusion : `Outfits` est maintenant bridge-first. Il reste seulement à survei
## Prochain pas logique recommandé
-Le prochain pas logique est maintenant : **stabilisation finale après migration Outfits**.
+Le prochain pas logique est maintenant : **stabilisation finale après migration Loot Rules**.
À valider après cette dernière migration :
-1. ouvrir la frame Outfits sur plusieurs bots avec et sans tenues existantes ;
-2. créer / mettre à jour une tenue avec `update` et vérifier qu’elle apparaît immédiatement dans la liste ;
-3. tester `equip` et `replace` et vérifier qu’il n’y a plus de spam détaillé `Equipping [item] ...` ;
-4. vérifier que le message joueur unique reste présent : `Equipping outfit ...` ou `Replacing current equip with outfit ...` ;
-5. refaire un cycle complet login, `/reload`, groupe, raid, ajout via `AddClass`, ouverture de chaque frame ;
-6. vérifier en console qu’il n’y a plus de refresh automatique legacy `stats`, `items`, `spells`, `glyphs`, `talents spec list`, `outfit ?` avec `MultiBot.allowLegacyChatFallback = false` ;
-7. vérifier que les commandes manuelles volontaires restent fonctionnelles : `who`, `co ?`, `nc ?`, `ss ?`, `.playerbot bot add`, `.playerbot bot remove`, actions inventory, `glyph equip`, sélection de spec et actions Outfits.
+1. ouvrir la mini-frame Loot et tester `Enable`, `Disable`, `All`, `Normal`, `Gray`, `Quest`, `Skill` sur un groupe et un raid ;
+2. vérifier que les confirmations système `LOOT_ACK` affichent bien le nombre de bots concernés ;
+3. vérifier que les commandes refusées côté bridge ne s’exécutent pas ;
+4. ouvrir la frame Outfits sur plusieurs bots avec et sans tenues existantes ;
+5. créer / mettre à jour une tenue avec `update` et vérifier qu’elle apparaît immédiatement dans la liste ;
+6. tester `equip` et `replace` et vérifier qu’il n’y a plus de spam détaillé `Equipping [item] ...` ;
+7. vérifier que le message joueur unique reste présent : `Equipping outfit ...` ou `Replacing current equip with outfit ...` ;
+8. refaire un cycle complet login, `/reload`, groupe, raid, ajout via `AddClass`, ouverture de chaque frame ;
+9. vérifier en console qu’il n’y a plus de refresh automatique legacy `stats`, `items`, `spells`, `glyphs`, `talents spec list`, `outfit ?` avec `MultiBot.allowLegacyChatFallback = false` ;
+10. vérifier que les commandes manuelles volontaires restent fonctionnelles : `who`, `co ?`, `nc ?`, `ss ?`, `.playerbot bot add`, `.playerbot bot remove`, actions inventory, `glyph equip`, sélection de spec et actions Outfits.
Après cette stabilisation, il restera surtout un audit de nettoyage : supprimer les debug temporaires, garder les fallbacks legacy uniquement quand ils sont utiles au diagnostic, et retirer les parsers historiques devenus morts.
diff --git a/docs/multibot_missing_commands_roadmap.md b/docs/multibot_missing_commands_roadmap.md
index 508bd60..56c8ba6 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, Combat Strategies et Disperse :
+Le principe reste le même que pour Inventory, Spellbook, Glyphs, Talents, Stats, Quests, Outfits, RTI, Pull Control, Combat Strategies, Disperse et Loot Rules :
**éviter le spam chat automatique**, utiliser le bridge quand c'est possible, et conserver les commandes manuelles utiles comme `who`, `co ?`, `nc ?`, `ss ?`.
---
@@ -36,7 +36,7 @@ Le principe reste le même que pour Inventory, Spellbook, Glyphs, Talents, Stats
---
-## Derniers lots terminés : Pull Control + Combat Strategies + Disperse
+## Derniers lots terminés : Pull Control + Combat Strategies + Disperse + Loot Rules
### Pull Control
@@ -396,22 +396,29 @@ Le contrôle du loot est utile, mais moins prioritaire que RTI/pull/combat/dispe
| Commande playerbots | Statut MultiBot | Priorité | Proposition UI |
|---|---:|---:|---|
-| `nc +loot` | Partiel | Moyenne | Toggle Loot |
-| `nc -loot` | Partiel | Moyenne | Toggle Loot |
-| `ll all` | Manquant | Moyenne | Profil Loot All |
-| `ll normal` | Manquant | Moyenne | Profil Normal |
-| `ll gray` | Manquant | Moyenne | Profil Gray |
-| `ll quest` | Manquant | Moyenne | Profil Quest |
-| `ll skill` | Manquant | Moyenne | Profil Skill |
+| `nc +loot` | Fait | Moyenne | Bouton Enable Loot via `RUN~LOOT` |
+| `nc -loot` | Fait | Moyenne | Bouton Disable Loot via `RUN~LOOT` |
+| `ll all` | Fait | Moyenne | Profil Loot All via `RUN~LOOT` |
+| `ll normal` | Fait | Moyenne | Profil Normal via `RUN~LOOT` |
+| `ll gray` | Fait | Moyenne | Profil Gray via `RUN~LOOT` |
+| `ll quest` | Fait | Moyenne | Profil Quest via `RUN~LOOT` |
+| `ll skill` | Fait | Moyenne | Profil Skill via `RUN~LOOT` |
| `ll [item]` | Manquant | Basse | Ajouter item depuis inventaire |
| `ll -[item]` | Manquant | Basse | Retirer item depuis inventaire |
### Proposition UI
-Créer une section `Loot Rules` avec profils prédéfinis.
+Loot Rules` est désormais exposé par une mini-frame de profils prédéfinis.
Les commandes `ll [item]` et `ll -[item]` peuvent être ajoutées plus tard via clic droit sur item dans l'inventaire bridge.
+Note à conserver pour une itération ultérieure :
+
+- après vérification du code `mod-playerbots`, `ll quest` et `ll skill` ne correspondent pas à de vrais profils natifs distincts ;
+- il faudra soit remplacer les boutons/profils `Quest` et `Skill` par le profil réellement supporté `Disenchant` ;
+- soit ajouter de vraies stratégies `quest` et `skill` côté `mod-playerbots` avant de les exposer proprement dans MultiBot ;
+- en attendant, éviter de considérer `Quest` et `Skill` comme des profils `ll` fiables côté UI.
+
---
## Priorité 6 - Trainer / Maintenance extras
@@ -541,7 +548,7 @@ Ces commandes sont plutôt serveur/admin/debug ou trop dangereuses pour une UI u
| 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 positionnement via `RUN~POSITION` | Moyenne | Fait / à tester |
-| 5 | Loot Rules | Petite UI profils | Moyenne | À faire |
+| 5 | Loot Rules | Petite UI profils + bridge `RUN~LOOT` | Moyenne | Fait / à tester |
| 6 | Trainer / Maintenance extras | UI maintenance | Moyenne/Basse | À faire |
| 7 | Items avancés | Extensions inventaire | Basse/Moyenne | À faire |
@@ -557,21 +564,21 @@ Ces commandes sont plutôt serveur/admin/debug ou trop dangereuses pour une UI u
- 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` et `RUN~POSITION` doivent rester whitelistés côté bridge : ne pas en faire des exécuteurs libres de n'importe quelle commande chat.
+- `RUN~COMBAT`, `RUN~POSITION` et `RUN~LOOT` 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 **Loot Rules / Loot List**.
+Le prochain bloc logique conseillé est **Vente / Roll / Vendor**.
-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.
+Raison : les blocs RTI, Pull Control, Combat Strategies, Disperse et Loot Rules couvrent maintenant le ciblage, le pull, les stratégies combat, le positionnement collectif et les profils de loot. Le prochain manque gameplay utile concerne donc les actions ponctuelles autour du loot : roll, vente vendor et vente grise.
Proposition de prochaine itération :
-- 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.
+- vérifier les commandes réellement utiles pour `roll`, `roll [item]`, `s vendor` et `s *` ;
+- décider si ces actions doivent passer par un endpoint dédié ou par un endpoint existant whitelisté ;
+- éviter toute vente automatique dangereuse sans action explicite de l'utilisateur ;
+- garder les commandes informatives manuelles inchangées.
+- A noter que `Quest` et `Skill` devront être remplacés plus tard par `Disenchant`, ou devenir de vrais profils ajoutés côté `mod-playerbots` avant exposition définitive ;