From 0f1cab44a95a4791361accc328da94f435ba6396 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sat, 2 May 2026 12:35:19 +0200 Subject: [PATCH 1/6] Add new frame with bots information Skills Profession others ... --- Core/MultiBotComm.lua | 253 +++++++++++++++++++- Core/MultiBotEvery.lua | 6 + Core/MultiBotInit.lua | 4 + MultiBot.toc | 1 + TODO.md | 14 +- UI/MultiBotCharacterInfoFrame.lua | 385 ++++++++++++++++++++++++++++++ UI/MultiBotQuestsMenu.lua | 2 +- 7 files changed, 656 insertions(+), 9 deletions(-) create mode 100644 UI/MultiBotCharacterInfoFrame.lua diff --git a/Core/MultiBotComm.lua b/Core/MultiBotComm.lua index a461858..a93d474 100644 --- a/Core/MultiBotComm.lua +++ b/Core/MultiBotComm.lua @@ -105,7 +105,7 @@ local function ensureBridgeState() state.questActive = state.questActive or {} state.gameObjects = state.gameObjects or {} state.gameObjectSeq = state.gameObjectSeq or 0 - state.gameObjectActive = state.gameObjectActive or {} + state.gameObjectActive = state.gameObjectActive or {} state.talentSpecs = state.talentSpecs or {} state.talentSpecSeq = state.talentSpecSeq or 0 state.talentSpecActive = state.talentSpecActive or nil @@ -115,6 +115,12 @@ local function ensureBridgeState() state.inventoryActive = state.inventoryActive or nil state.spellbookSeq = state.spellbookSeq or 0 state.spellbookActive = state.spellbookActive or nil + state.botSkills = state.botSkills or {} + state.botSkillSeq = state.botSkillSeq or 0 + state.botSkillActive = state.botSkillActive or nil + state.professionRecipes = state.professionRecipes or {} + state.professionRecipeSeq = state.professionRecipeSeq or 0 + state.professionRecipeActive = state.professionRecipeActive or nil state.outfitSeq = state.outfitSeq or 0 state.outfitActive = state.outfitActive or nil state.outfitCommands = state.outfitCommands or {} @@ -564,6 +570,58 @@ function Comm.RequestSpellbook(name) return true end +function Comm.RequestBotSkills(name) + local state = ensureBridgeState() + name = trim(name) + if name == "" or not state.connected then + return false + end + + state.botSkillSeq = (tonumber(state.botSkillSeq) or 0) + 1 + local token = tostring(math.floor(safeNow() * 1000)) .. "-" .. tostring(state.botSkillSeq) + state.botSkillActive = { + botName = name, + botNameKey = string.lower(name), + token = token, + startedAt = safeNow(), + items = {}, + } + + if not Comm.Send("GET", "BOT_SKILLS~" .. name .. "~" .. token) then + state.botSkillActive = nil + return false + end + + return true +end + +function Comm.RequestProfessionRecipes(name, skillId) + local state = ensureBridgeState() + name = trim(name) + skillId = tonumber(skillId or 0) or 0 + if name == "" or skillId <= 0 or not state.connected then + return false + end + + state.professionRecipeSeq = (tonumber(state.professionRecipeSeq) or 0) + 1 + local token = tostring(math.floor(safeNow() * 1000)) .. "-" .. tostring(state.professionRecipeSeq) + state.professionRecipeActive = { + botName = name, + botNameKey = string.lower(name), + skillId = skillId, + token = token, + startedAt = safeNow(), + recipes = {}, + } + + if not Comm.Send("GET", "PROFESSION_RECIPES~" .. name .. "~" .. skillId .. "~" .. token) then + state.professionRecipeActive = nil + return false + end + + return true +end + function Comm.MarkDisconnected(reason) local state = ensureBridgeState() state.connected = false @@ -572,6 +630,8 @@ function Comm.MarkDisconnected(reason) state.lastError = reason or nil state.inventoryActive = nil state.spellbookActive = nil + state.botSkillActive = nil + state.professionRecipeActive = nil state.outfitActive = nil state.outfitCommands = {} end @@ -1533,6 +1593,60 @@ local function getSpellbookFrame() return MultiBot and MultiBot.spellbook or nil end +local function getActiveBotSkillRequest(botName, token) + local state = ensureBridgeState() + local active = state.botSkillActive + if type(active) ~= "table" then + return nil + end + + if botName and botName ~= "" and string.lower(trim(botName)) ~= trim(active.botNameKey or "") then + return nil + end + + if token and token ~= "" and tostring(token) ~= tostring(active.token or "") then + return nil + end + + return active +end + +local function getActiveProfessionRecipeRequest(botName, token, skillId) + local state = ensureBridgeState() + local active = state.professionRecipeActive + if type(active) ~= "table" then + return nil + end + + if botName and botName ~= "" and string.lower(trim(botName)) ~= trim(active.botNameKey or "") then + return nil + end + + if token and token ~= "" and tostring(token) ~= tostring(active.token or "") then + return nil + end + + if skillId and tonumber(skillId or 0) ~= tonumber(active.skillId or 0) then + return nil + end + + return active +end + +local function parseRecipeMaterials(raw) + local materials = {} + for token in string.gmatch(raw or "", "([^;]+)") do + local itemId, rest = splitOnce(token, ":") + local required, available = splitOnce(rest or "", ":") + table.insert(materials, { + itemId = tonumber(itemId or "0") or 0, + required = tonumber(required or "0") or 0, + available = tonumber(available or "0") or 0, + }) + end + return materials +end + function Comm.HandleAddonMessage(prefix, message, distribution, sender) if prefix ~= Comm.prefix then return false @@ -1913,6 +2027,139 @@ function Comm.HandleAddonMessage(prefix, message, distribution, sender) return true end + if opcode == "BOT_SKILLS_BEGIN" then + local botName, token = splitOnce(payload or "", "~") + botName = trim(urlDecodeField(botName)) + token = trim(token) + state.connected = true + state.lastError = nil + + local active = getActiveBotSkillRequest(botName, token) + if active then + active.items = {} + end + + return true + end + + if opcode == "BOT_SKILLS_ITEM" then + local botName, rest = splitOnce(payload or "", "~") + local token, rest2 = splitOnce(rest or "", "~") + local category, rest3 = splitOnce(rest2 or "", "~") + local skillId, rest4 = splitOnce(rest3 or "", "~") + local key, rest5 = splitOnce(rest4 or "", "~") + local skillName, rest6 = splitOnce(rest5 or "", "~") + local value, maxValue = splitOnce(rest6 or "", "~") + + botName = trim(urlDecodeField(botName)) + token = trim(token) + state.connected = true + state.lastError = nil + + local active = getActiveBotSkillRequest(botName, token) + if active then + table.insert(active.items, { + category = trim(urlDecodeField(category)), + skillId = tonumber(skillId or "0") or 0, + key = trim(urlDecodeField(key)), + name = trim(urlDecodeField(skillName)), + value = tonumber(value or "0") or 0, + max = tonumber(maxValue or "0") or 0, + }) + end + + return true + end + + if opcode == "BOT_SKILLS_END" then + local botName, token = splitOnce(payload or "", "~") + botName = trim(urlDecodeField(botName)) + token = trim(token) + state.connected = true + state.lastError = nil + + local active = getActiveBotSkillRequest(botName, token) + if active then + local key = string.lower(botName) + state.botSkills[key] = active.items or {} + if MultiBot.OnBridgeBotSkills then + MultiBot.OnBridgeBotSkills(botName, state.botSkills[key], token) + end + state.botSkillActive = nil + end + + return true + end + + if opcode == "PROFESSION_RECIPES_BEGIN" then + local botName, rest = splitOnce(payload or "", "~") + local token, skillId = splitOnce(rest or "", "~") + botName = trim(urlDecodeField(botName)) + token = trim(token) + skillId = tonumber(skillId or "0") or 0 + state.connected = true + state.lastError = nil + + local active = getActiveProfessionRecipeRequest(botName, token, skillId) + if active then + active.recipes = {} + end + + return true + end + + if opcode == "PROFESSION_RECIPES_ITEM" then + local botName, rest = splitOnce(payload or "", "~") + local token, rest2 = splitOnce(rest or "", "~") + local skillId, rest3 = splitOnce(rest2 or "", "~") + local spellId, rest4 = splitOnce(rest3 or "", "~") + local itemId, rest5 = splitOnce(rest4 or "", "~") + local difficulty, rest6 = splitOnce(rest5 or "", "~") + local craftable, materials = splitOnce(rest6 or "", "~") + + botName = trim(urlDecodeField(botName)) + token = trim(token) + skillId = tonumber(skillId or "0") or 0 + state.connected = true + state.lastError = nil + + local active = getActiveProfessionRecipeRequest(botName, token, skillId) + if active then + table.insert(active.recipes, { + skillId = skillId, + spellId = tonumber(spellId or "0") or 0, + itemId = tonumber(itemId or "0") or 0, + difficulty = trim(urlDecodeField(difficulty)), + craftable = tonumber(craftable or "0") or 0, + materials = parseRecipeMaterials(urlDecodeField(materials)), + }) + end + + return true + end + + if opcode == "PROFESSION_RECIPES_END" then + local botName, rest = splitOnce(payload or "", "~") + local token, skillId = splitOnce(rest or "", "~") + botName = trim(urlDecodeField(botName)) + token = trim(token) + skillId = tonumber(skillId or "0") or 0 + state.connected = true + state.lastError = nil + + local active = getActiveProfessionRecipeRequest(botName, token, skillId) + if active then + local key = string.lower(botName) .. ":" .. tostring(skillId) + state.professionRecipes[key] = active.recipes or {} + if MultiBot.OnBridgeProfessionRecipes then + MultiBot.OnBridgeProfessionRecipes(botName, skillId, state.professionRecipes[key], token) + end + state.professionRecipeActive = nil + end + + return true + end + if opcode == "RTI_ACK" then state.connected = true state.lastError = nil @@ -2038,6 +2285,10 @@ function Comm.OnPlayerEnteringWorld() state.talentSpecActive = nil state.inventoryActive = nil state.spellbookActive = nil + state.botSkills = {} + state.botSkillActive = nil + state.professionRecipes = {} + state.professionRecipeActive = nil state.outfitActive = nil state.outfitCommands = {} Comm.MarkDisconnected(nil) diff --git a/Core/MultiBotEvery.lua b/Core/MultiBotEvery.lua index eebcec9..6c4c515 100644 --- a/Core/MultiBotEvery.lua +++ b/Core/MultiBotEvery.lua @@ -125,6 +125,12 @@ MultiBot.addEvery = function(pFrame, pCombat, pNormal) unitsBtn.doLeft(unitsBtn, "favorites", unitsBtn.filter) end end + }, + { "CharacterInfo", "inv_misc_note_05", MultiBot.L("tips.every.characterinfo", "Infos personnage"), function(b) + if MultiBot.OpenCharacterInfo then + MultiBot.OpenCharacterInfo(b.getName()) + end + end }, { "Maintenance", "Achievement_Halloween_Smiley_01", MultiBot.L("tips.every.maintenance"), function(b) SendChatMessage("maintenance", "WHISPER", nil, b.getName()) diff --git a/Core/MultiBotInit.lua b/Core/MultiBotInit.lua index 805444c..6d77fd2 100644 --- a/Core/MultiBotInit.lua +++ b/Core/MultiBotInit.lua @@ -69,6 +69,10 @@ MultiBot.InitializeIconosFrame() MultiBot.InitializeSpellBookFrame() +if MultiBot.InitializeCharacterInfoFrame then + MultiBot.InitializeCharacterInfoFrame() +end + if MultiBot.InitializeRewardFrame then MultiBot.InitializeRewardFrame() end diff --git a/MultiBot.toc b/MultiBot.toc index 43b65f6..2f4f231 100644 --- a/MultiBot.toc +++ b/MultiBot.toc @@ -64,6 +64,7 @@ Core\MultiBotEvery.lua UI\MultiBotStats.lua UI\MultiBotSpell.lua UI\MultiBotSpellBookFrame.lua +UI\MultiBotCharacterInfoFrame.lua UI\MultiBotRewardFrame.lua UI\MultiBotOutfitUI.lua UI\MultiBotInventoryFrame.lua diff --git a/TODO.md b/TODO.md index 87ebbdf..9379983 100644 --- a/TODO.md +++ b/TODO.md @@ -16,12 +16,15 @@ * Talents / glyphes : revoir `UI/MultiBotTalent`, car il y a eu des modifications dans le fichier `.conf` de MultiBot. * Menus déroulants de la main bar : fermer automatiquement les autres menus quand on en ouvre un nouveau. * J'ai l'impression que le disperse ne fait rien -* Voir pouquoi quand on se groupe avec un randombot il ne montre pas ses spell ni son inventaire +* Quand on fait apparaitre les boutons creator ou/et Maitre des bêtes, les deux boutons Disperse et Règles de buttin ne bougent pas +ce qui fait que quand on ajoute Creator + maitre des betes le bouton attaque tank se retrouve caché par le bouton disperse ## Informations bot -** Faire une frame qui affiche les infos du bot comme la profession ou peut être ajouter la profession à la frame inventaire? -* Y afficher proffeions et niveau -* Monnaies (emblemes etc..) +** Frame faite +* Retravailler l'ergonomie et le design +* Mettre les niveaux en barre de progression +* Il y'a encore des spell des professions qui fuitent vers le spellbook +* y ajouter Monnaies (emblemes etc..) * Réputations ## Frame Loot @@ -196,9 +199,6 @@ * gray ; * quest ; * skill. -* Loot Master Frame : - * automatically shown when the player is the loot master ; - * assigns loot to bots from a dedicated window. ### Quick bars / classes diff --git a/UI/MultiBotCharacterInfoFrame.lua b/UI/MultiBotCharacterInfoFrame.lua new file mode 100644 index 0000000..b965eec --- /dev/null +++ b/UI/MultiBotCharacterInfoFrame.lua @@ -0,0 +1,385 @@ +if not MultiBot then + return +end + +local CATEGORY_ORDER = { "class", "profession", "secondary", "weapon", "armor" } +local CATEGORY_TITLES = { + class = "Compétences de classe", + profession = "Métiers", + secondary = "Compétences secondaires", + weapon = "Compétences d'arme", + armor = "Armures", +} + +local DIFFICULTY_COLORS = { + orange = "|cffff8040", + yellow = "|cffffff00", + green = "|cff80be80", + gray = "|cff808080", +} + +local function L(key, fallback) + if MultiBot.L then + return MultiBot.L(key, fallback) + end + + return fallback or key +end + +local function setBackdrop(frame) + frame:SetBackdrop({ + bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, + tileSize = 16, + edgeSize = 16, + insets = { left = 4, right = 4, top = 4, bottom = 4 }, + }) + frame:SetBackdropColor(0.06, 0.06, 0.06, 0.96) + frame:SetBackdropBorderColor(0.35, 0.35, 0.35, 1) +end + +local function createCloseButton(parent) + local button = CreateFrame("Button", nil, parent, "UIPanelCloseButton") + button:SetPoint("TOPRIGHT", parent, "TOPRIGHT", -4, -4) + return button +end + +local function createTabButton(parent, text, x) + local button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate") + button:SetPoint("TOPLEFT", parent, "TOPLEFT", x, -28) + button:SetWidth(112) + button:SetHeight(20) + button:SetText(text) + return button +end + +local function setButtonEnabled(button, enabled) + if not button then + return + end + + if enabled then + button:Enable() + else + button:Disable() + end +end + +local function createText(parent, template, point, x, y) + local text = parent:CreateFontString(nil, "OVERLAY", template or "GameFontNormal") + text:SetPoint(point or "TOPLEFT", parent, point or "TOPLEFT", x or 0, y or 0) + text:SetJustifyH("LEFT") + return text +end + +local function getSkillBarText(skill) + local value = tonumber(skill and skill.value or 0) or 0 + local max = tonumber(skill and skill.max or 0) or 0 + if max <= 0 then + return tostring(value) + end + + return value .. "/" .. max +end + +local function getItemName(itemId) + itemId = tonumber(itemId or 0) or 0 + if itemId <= 0 then + return "" + end + + local name = GetItemInfo(itemId) + return name or ("item:" .. itemId) +end + +local function getSpellDisplay(spellId) + spellId = tonumber(spellId or 0) or 0 + if spellId <= 0 then + return "spell:0", "Interface\\Icons\\INV_Misc_QuestionMark" + end + + local name, rank, icon = GetSpellInfo(spellId) + return name or ("spell:" .. spellId), icon or "Interface\\Icons\\INV_Misc_QuestionMark", rank or "" +end + +local function buildMaterialsText(recipe) + local parts = {} + for _, material in ipairs(recipe.materials or {}) do + local itemId = tonumber(material.itemId or 0) or 0 + local required = tonumber(material.required or 0) or 0 + local available = tonumber(material.available or 0) or 0 + if itemId > 0 and required > 0 then + table.insert(parts, getItemName(itemId) .. " " .. available .. "/" .. required) + end + end + + return table.concat(parts, ", ") +end + +local function ensureRecipeFrame() + if MultiBot.professionRecipeFrame then + return MultiBot.professionRecipeFrame + end + + local frame = CreateFrame("Frame", "MultiBotProfessionRecipeFrame", UIParent) + frame:SetWidth(430) + frame:SetHeight(450) + frame:SetPoint("CENTER", UIParent, "CENTER", 130, 0) + frame:SetFrameStrata("DIALOG") + frame:SetMovable(true) + frame:EnableMouse(true) + frame:RegisterForDrag("LeftButton") + frame:SetScript("OnDragStart", frame.StartMoving) + frame:SetScript("OnDragStop", frame.StopMovingOrSizing) + setBackdrop(frame) + + frame.title = createText(frame, "GameFontNormalLarge", "TOPLEFT", 16, -14) + createCloseButton(frame) + + frame.status = createText(frame, "GameFontHighlightSmall", "TOPLEFT", 18, -42) + frame.rows = {} + frame.page = 1 + frame.pageSize = 14 + frame.recipes = {} + + for i = 1, frame.pageSize do + local row = CreateFrame("Button", nil, frame) + row:SetPoint("TOPLEFT", frame, "TOPLEFT", 18, -62 - ((i - 1) * 25)) + row:SetWidth(392) + row:SetHeight(22) + row.icon = row:CreateTexture(nil, "ARTWORK") + row.icon:SetPoint("LEFT", row, "LEFT", 0, 0) + row.icon:SetWidth(20) + row.icon:SetHeight(20) + row.text = createText(row, "GameFontHighlightSmall", "LEFT", 26, 0) + row.text:SetWidth(360) + row.text:SetHeight(20) + row:SetScript("OnEnter", function(self) + if not self.recipe or not GameTooltip then return end + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + if self.recipe.spellId and self.recipe.spellId > 0 then + GameTooltip:SetHyperlink("spell:" .. self.recipe.spellId) + end + local materials = buildMaterialsText(self.recipe) + if materials ~= "" then + GameTooltip:AddLine(" ") + GameTooltip:AddLine(materials, 1, 1, 1, true) + end + GameTooltip:Show() + end) + row:SetScript("OnLeave", function() + if GameTooltip then GameTooltip:Hide() end + end) + frame.rows[i] = row + end + + frame.prev = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") + frame.prev:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 18, 14) + frame.prev:SetWidth(48) + frame.prev:SetHeight(20) + frame.prev:SetText("<") + + frame.pageText = createText(frame, "GameFontNormalSmall", "BOTTOM", 0, 18) + + frame.next = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") + frame.next:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -18, 14) + frame.next:SetWidth(48) + frame.next:SetHeight(20) + frame.next:SetText(">") + + frame.render = function(self) + local maxPage = math.max(1, math.ceil(#self.recipes / self.pageSize)) + if self.page > maxPage then self.page = maxPage end + if self.page < 1 then self.page = 1 end + + self.pageText:SetText(self.page .. "/" .. maxPage) + setButtonEnabled(self.prev, self.page > 1) + setButtonEnabled(self.next, self.page < maxPage) + + local from = ((self.page - 1) * self.pageSize) + 1 + for i = 1, self.pageSize do + local row = self.rows[i] + local recipe = self.recipes[from + i - 1] + row.recipe = recipe + if recipe then + local name, icon = getSpellDisplay(recipe.spellId) + local color = DIFFICULTY_COLORS[recipe.difficulty or ""] or "|cffffffff" + local craftable = tonumber(recipe.craftable or 0) or 0 + row.icon:SetTexture(MultiBot.SafeTexturePath(icon)) + row.text:SetText(color .. name .. "|r |cff999999x" .. craftable .. "|r") + row:Show() + else + row:Hide() + end + end + end + + frame.prev:SetScript("OnClick", function() + frame.page = frame.page - 1 + frame:render() + end) + + frame.next:SetScript("OnClick", function() + frame.page = frame.page + 1 + frame:render() + end) + + frame.setRecipes = function(self, botName, skill, recipes) + self.botName = botName + self.skill = skill + self.recipes = recipes or {} + self.page = 1 + self.title:SetText((skill and skill.name or L("profession.recipes", "Recettes")) .. " - " .. (botName or "")) + self.status:SetText(#self.recipes .. " " .. L("profession.recipes.count", "recette(s) connue(s)")) + self:render() + self:Show() + end + + frame:Hide() + MultiBot.professionRecipeFrame = frame + return frame +end + +local function ensureCharacterFrame() + if MultiBot.characterInfoFrame then + return MultiBot.characterInfoFrame + end + + local frame = CreateFrame("Frame", "MultiBotCharacterInfoFrame", UIParent) + frame:SetWidth(390) + frame:SetHeight(450) + frame:SetPoint("CENTER", UIParent, "CENTER", -130, 0) + frame:SetFrameStrata("DIALOG") + frame:SetMovable(true) + frame:EnableMouse(true) + frame:RegisterForDrag("LeftButton") + frame:SetScript("OnDragStart", frame.StartMoving) + frame:SetScript("OnDragStop", frame.StopMovingOrSizing) + setBackdrop(frame) + + frame.title = createText(frame, "GameFontNormalLarge", "TOPLEFT", 16, -14) + createCloseButton(frame) + + frame.skillsTab = createTabButton(frame, L("character.skills", "Compétences"), 16) + setButtonEnabled(frame.skillsTab, false) + + frame.status = createText(frame, "GameFontHighlightSmall", "TOPLEFT", 18, -56) + frame.rows = {} + frame.skills = {} + + for i = 1, 18 do + local row = CreateFrame("Button", nil, frame) + row:SetPoint("TOPLEFT", frame, "TOPLEFT", 18, -80 - ((i - 1) * 19)) + row:SetWidth(350) + row:SetHeight(18) + row.text = createText(row, "GameFontHighlightSmall", "LEFT", 0, 0) + row.text:SetWidth(340) + row:SetScript("OnClick", function(self) + if not self.skill then return end + if self.skill.category ~= "profession" and self.skill.category ~= "secondary" then return end + if MultiBot.Comm and MultiBot.Comm.RequestProfessionRecipes then + ensureRecipeFrame() + MultiBot.professionRecipeFrame.title:SetText(self.skill.name .. " - " .. (frame.botName or "")) + MultiBot.professionRecipeFrame.status:SetText(L("profession.recipes.loading", "Chargement...")) + MultiBot.professionRecipeFrame.recipes = {} + MultiBot.professionRecipeFrame:render() + MultiBot.professionRecipeFrame:Show() + MultiBot.Comm.RequestProfessionRecipes(frame.botName, self.skill.skillId) + end + end) + frame.rows[i] = row + end + + frame.renderSkills = function(self) + local ordered = {} + for _, category in ipairs(CATEGORY_ORDER) do + local categoryItems = {} + for _, skill in ipairs(self.skills or {}) do + if skill.category == category then + table.insert(categoryItems, skill) + end + end + if #categoryItems > 0 then + table.insert(ordered, { header = CATEGORY_TITLES[category] or category }) + table.sort(categoryItems, function(a, b) return tostring(a.name or "") < tostring(b.name or "") end) + for _, skill in ipairs(categoryItems) do + table.insert(ordered, { skill = skill }) + end + end + end + + self.status:SetText(#(self.skills or {}) .. " " .. L("character.skills.count", "compétence(s)")) + for i = 1, #self.rows do + local row = self.rows[i] + local item = ordered[i] + row.skill = item and item.skill or nil + if not item then + row:Hide() + elseif item.header then + row.text:SetText("|cffffcc00" .. item.header .. "|r") + row:Show() + else + local skill = item.skill + local clickable = (skill.category == "profession" or skill.category == "secondary") and "|cff80d0ff" or "|cffffffff" + row.text:SetText(clickable .. (skill.name or skill.key or ("Skill " .. tostring(skill.skillId))) .. "|r |cff999999" .. getSkillBarText(skill) .. "|r") + row:Show() + end + end + end + + frame.setSkills = function(self, botName, skills) + self.botName = botName + self.skills = skills or {} + self.title:SetText(L("character.info", "Infos personnage") .. " - " .. (botName or "")) + self:renderSkills() + self:Show() + end + + frame:Hide() + MultiBot.characterInfoFrame = frame + return frame +end + +function MultiBot.OpenCharacterInfo(botName) + botName = tostring(botName or "") + if botName == "" then + return false + end + + local frame = ensureCharacterFrame() + frame.botName = botName + frame.title:SetText(L("character.info", "Infos personnage") .. " - " .. botName) + frame.skills = {} + frame:renderSkills() + frame.status:SetText(L("character.skills.loading", "Chargement...")) + frame:Show() + + if MultiBot.Comm and MultiBot.Comm.RequestBotSkills then + return MultiBot.Comm.RequestBotSkills(botName) + end + + return false +end + +function MultiBot.OnBridgeBotSkills(botName, skills) + ensureCharacterFrame():setSkills(botName, skills or {}) +end + +function MultiBot.OnBridgeProfessionRecipes(botName, skillId, recipes) + local skill + local frame = ensureCharacterFrame() + for _, candidate in ipairs(frame.skills or {}) do + if tonumber(candidate.skillId or 0) == tonumber(skillId or 0) then + skill = candidate + break + end + end + + ensureRecipeFrame():setRecipes(botName, skill or { name = "Skill " .. tostring(skillId), skillId = skillId }, recipes or {}) +end + +function MultiBot.InitializeCharacterInfoFrame() + ensureCharacterFrame() + ensureRecipeFrame() +end \ No newline at end of file diff --git a/UI/MultiBotQuestsMenu.lua b/UI/MultiBotQuestsMenu.lua index d64684f..b5e9baa 100644 --- a/UI/MultiBotQuestsMenu.lua +++ b/UI/MultiBotQuestsMenu.lua @@ -487,7 +487,7 @@ function MultiBot.InitializeQuestsMenu(tRight) gobSearchButton.doLeft = function() if MultiBot.RequestGameObjectResults and MultiBot.RequestGameObjectResults() then return - end + end MultiBot.ActionToGroup("los") end tRight.buttons["BotUseGOB"] = gobButton From 15b7d0da9a44f772d958383a7e7b4beaac767531 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sat, 2 May 2026 14:16:44 +0200 Subject: [PATCH 2/6] Convert frame to ACE3 and display all lines --- Locales/MultiBotAceLocale-frFR.lua | 7 ++ UI/MultiBotCharacterInfoFrame.lua | 178 +++++++++++++++++++---------- 2 files changed, 122 insertions(+), 63 deletions(-) diff --git a/Locales/MultiBotAceLocale-frFR.lua b/Locales/MultiBotAceLocale-frFR.lua index 93d810b..442b871 100644 --- a/Locales/MultiBotAceLocale-frFR.lua +++ b/Locales/MultiBotAceLocale-frFR.lua @@ -41,6 +41,13 @@ local frFRValues = { ["lootmaster.profession.fishing"] = "Pêche", ["lootmaster.profession.firstaid"] = "Secourisme", ["lootmaster.gear_score"] = "GearScore : %d", + ["profession.recipes"] = "Recettes", + ["profession.recipes.count"] = "recette(s) connue(s)", + ["character.info"] = "Infos personnage", + ["character.skills"] = "Compétences", + ["profession.recipes.loading"] = "Chargement...", + ["character.skills.count"] = "compétence(s)", + ["character.skills.loading"] = "Chargement...", ["tips.units.rti"] = "RTI", ["tips.disperse.main"] = "Disperse", ["tips.disperse.set"] = "Définir la distance de disperse", diff --git a/UI/MultiBotCharacterInfoFrame.lua b/UI/MultiBotCharacterInfoFrame.lua index b965eec..8b0a486 100644 --- a/UI/MultiBotCharacterInfoFrame.lua +++ b/UI/MultiBotCharacterInfoFrame.lua @@ -2,6 +2,8 @@ if not MultiBot then return end +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) + local CATEGORY_ORDER = { "class", "profession", "secondary", "weapon", "armor" } local CATEGORY_TITLES = { class = "Compétences de classe", @@ -26,6 +28,43 @@ local function L(key, fallback) return fallback or key end +local function createAceWindow(name, title, width, height, x) + if not AceGUI then + return nil + end + + local widget = AceGUI:Create("Frame") + widget:SetTitle(title or "") + widget:SetWidth(width) + widget:SetHeight(height) + widget:SetLayout("Absolute") + + if widget.EnableResize then + widget:EnableResize(false) + end + + local frame = widget.frame + frame.aceWidget = widget + frame.content = widget.content or frame + frame:SetPoint("CENTER", UIParent, "CENTER", x or 0, 0) + frame:SetFrameStrata("DIALOG") + _G[name] = frame + + return frame +end + +local function setWindowTitle(frame, title) + if frame and frame.aceWidget and frame.aceWidget.SetTitle then + frame.aceWidget:SetTitle(title or "") + elseif frame and frame.title then + frame.title:SetText(title or "") + end +end + +local function getFrameContent(frame) + return (frame and frame.content) or frame +end + local function setBackdrop(frame) frame:SetBackdrop({ bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", @@ -45,15 +84,6 @@ local function createCloseButton(parent) return button end -local function createTabButton(parent, text, x) - local button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate") - button:SetPoint("TOPLEFT", parent, "TOPLEFT", x, -28) - button:SetWidth(112) - button:SetHeight(20) - button:SetText(text) - return button -end - local function setButtonEnabled(button, enabled) if not button then return @@ -66,6 +96,15 @@ local function setButtonEnabled(button, enabled) end end +local function createTabButton(parent, text, x) + local button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate") + button:SetPoint("TOPLEFT", parent, "TOPLEFT", x, -28) + button:SetWidth(112) + button:SetHeight(20) + button:SetText(text) + return button +end + local function createText(parent, template, point, x, y) local text = parent:CreateFontString(nil, "OVERLAY", template or "GameFontNormal") text:SetPoint(point or "TOPLEFT", parent, point or "TOPLEFT", x or 0, y or 0) @@ -122,30 +161,21 @@ local function ensureRecipeFrame() return MultiBot.professionRecipeFrame end - local frame = CreateFrame("Frame", "MultiBotProfessionRecipeFrame", UIParent) - frame:SetWidth(430) - frame:SetHeight(450) - frame:SetPoint("CENTER", UIParent, "CENTER", 130, 0) - frame:SetFrameStrata("DIALOG") - frame:SetMovable(true) - frame:EnableMouse(true) - frame:RegisterForDrag("LeftButton") - frame:SetScript("OnDragStart", frame.StartMoving) - frame:SetScript("OnDragStop", frame.StopMovingOrSizing) - setBackdrop(frame) - - frame.title = createText(frame, "GameFontNormalLarge", "TOPLEFT", 16, -14) - createCloseButton(frame) + local frame = createAceWindow("MultiBotProfessionRecipeFrame", L("profession.recipes", "Recipes"), 430, 450, 130) + if not frame then + return nil + end - frame.status = createText(frame, "GameFontHighlightSmall", "TOPLEFT", 18, -42) + local content = getFrameContent(frame) + frame.status = createText(content, "GameFontHighlightSmall", "TOPLEFT", 18, -10) frame.rows = {} frame.page = 1 frame.pageSize = 14 frame.recipes = {} for i = 1, frame.pageSize do - local row = CreateFrame("Button", nil, frame) - row:SetPoint("TOPLEFT", frame, "TOPLEFT", 18, -62 - ((i - 1) * 25)) + local row = CreateFrame("Button", nil, content) + row:SetPoint("TOPLEFT", content, "TOPLEFT", 18, -30 - ((i - 1) * 25)) row:SetWidth(392) row:SetHeight(22) row.icon = row:CreateTexture(nil, "ARTWORK") @@ -174,16 +204,16 @@ local function ensureRecipeFrame() frame.rows[i] = row end - frame.prev = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") - frame.prev:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 18, 14) + frame.prev = CreateFrame("Button", nil, content, "UIPanelButtonTemplate") + frame.prev:SetPoint("BOTTOMLEFT", content, "BOTTOMLEFT", 18, 8) frame.prev:SetWidth(48) frame.prev:SetHeight(20) frame.prev:SetText("<") - frame.pageText = createText(frame, "GameFontNormalSmall", "BOTTOM", 0, 18) + frame.pageText = createText(content, "GameFontNormalSmall", "BOTTOM", 0, 12) - frame.next = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") - frame.next:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -18, 14) + frame.next = CreateFrame("Button", nil, content, "UIPanelButtonTemplate") + frame.next:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -18, 8) frame.next:SetWidth(48) frame.next:SetHeight(20) frame.next:SetText(">") @@ -230,8 +260,8 @@ local function ensureRecipeFrame() self.skill = skill self.recipes = recipes or {} self.page = 1 - self.title:SetText((skill and skill.name or L("profession.recipes", "Recettes")) .. " - " .. (botName or "")) - self.status:SetText(#self.recipes .. " " .. L("profession.recipes.count", "recette(s) connue(s)")) + setWindowTitle(self, (skill and skill.name or L("profession.recipes", "Recipes")) .. " - " .. (botName or "")) + self.status:SetText(#self.recipes .. " " .. L("profession.recipes.count", "unknown recipe(s)")) self:render() self:Show() end @@ -246,74 +276,88 @@ local function ensureCharacterFrame() return MultiBot.characterInfoFrame end - local frame = CreateFrame("Frame", "MultiBotCharacterInfoFrame", UIParent) - frame:SetWidth(390) - frame:SetHeight(450) - frame:SetPoint("CENTER", UIParent, "CENTER", -130, 0) - frame:SetFrameStrata("DIALOG") - frame:SetMovable(true) - frame:EnableMouse(true) - frame:RegisterForDrag("LeftButton") - frame:SetScript("OnDragStart", frame.StartMoving) - frame:SetScript("OnDragStop", frame.StopMovingOrSizing) - setBackdrop(frame) - - frame.title = createText(frame, "GameFontNormalLarge", "TOPLEFT", 16, -14) - createCloseButton(frame) + local frame = createAceWindow("MultiBotCharacterInfoFrame", L("character.info", "Character info"), 390, 450, -130) + if not frame then + return nil + end - frame.skillsTab = createTabButton(frame, L("character.skills", "Compétences"), 16) + local content = getFrameContent(frame) + frame.skillsTab = createTabButton(content, L("character.skills", "Skills"), 16) setButtonEnabled(frame.skillsTab, false) - frame.status = createText(frame, "GameFontHighlightSmall", "TOPLEFT", 18, -56) + frame.status = createText(content, "GameFontHighlightSmall", "TOPLEFT", 18, -36) frame.rows = {} frame.skills = {} - for i = 1, 18 do - local row = CreateFrame("Button", nil, frame) - row:SetPoint("TOPLEFT", frame, "TOPLEFT", 18, -80 - ((i - 1) * 19)) - row:SetWidth(350) + frame.scrollFrame = CreateFrame("ScrollFrame", "MultiBotCharacterInfoFrameSkillScrollFrame", content, "UIPanelScrollFrameTemplate") + frame.scrollFrame:SetPoint("TOPLEFT", content, "TOPLEFT", 18, -58) + frame.scrollFrame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -32, 36) + + frame.scrollChild = CreateFrame("Frame", "MultiBotCharacterInfoFrameSkillScrollChild", frame.scrollFrame) + frame.scrollChild:SetWidth(330) + frame.scrollChild:SetHeight(1) + frame.scrollFrame:SetScrollChild(frame.scrollChild) + + local function ensureSkillRow(index) + if frame.rows[index] then + return frame.rows[index] + end + + local row = CreateFrame("Button", nil, frame.scrollChild) + row:SetPoint("TOPLEFT", frame.scrollChild, "TOPLEFT", 0, -((index - 1) * 19)) + row:SetWidth(330) row:SetHeight(18) row.text = createText(row, "GameFontHighlightSmall", "LEFT", 0, 0) - row.text:SetWidth(340) + row.text:SetWidth(320) row:SetScript("OnClick", function(self) if not self.skill then return end if self.skill.category ~= "profession" and self.skill.category ~= "secondary" then return end if MultiBot.Comm and MultiBot.Comm.RequestProfessionRecipes then ensureRecipeFrame() - MultiBot.professionRecipeFrame.title:SetText(self.skill.name .. " - " .. (frame.botName or "")) - MultiBot.professionRecipeFrame.status:SetText(L("profession.recipes.loading", "Chargement...")) + setWindowTitle(MultiBot.professionRecipeFrame, self.skill.name .. " - " .. (frame.botName or "")) + MultiBot.professionRecipeFrame.status:SetText(L("profession.recipes.loading", "Loading...")) MultiBot.professionRecipeFrame.recipes = {} MultiBot.professionRecipeFrame:render() MultiBot.professionRecipeFrame:Show() MultiBot.Comm.RequestProfessionRecipes(frame.botName, self.skill.skillId) end end) - frame.rows[i] = row + + frame.rows[index] = row + return row end frame.renderSkills = function(self) local ordered = {} + for _, category in ipairs(CATEGORY_ORDER) do local categoryItems = {} + for _, skill in ipairs(self.skills or {}) do if skill.category == category then table.insert(categoryItems, skill) end end + if #categoryItems > 0 then table.insert(ordered, { header = CATEGORY_TITLES[category] or category }) table.sort(categoryItems, function(a, b) return tostring(a.name or "") < tostring(b.name or "") end) + for _, skill in ipairs(categoryItems) do table.insert(ordered, { skill = skill }) end end end - self.status:SetText(#(self.skills or {}) .. " " .. L("character.skills.count", "compétence(s)")) - for i = 1, #self.rows do - local row = self.rows[i] + self.status:SetText(#(self.skills or {}) .. " " .. L("character.skills.count", "skill(s)")) + + self.scrollChild:SetHeight(math.max(1, #ordered * 19)) + + for i = 1, #ordered do + local row = ensureSkillRow(i) local item = ordered[i] row.skill = item and item.skill or nil + if not item then row:Hide() elseif item.header then @@ -326,12 +370,20 @@ local function ensureCharacterFrame() row:Show() end end + + for i = #ordered + 1, #self.rows do + local row = self.rows[i] + row.skill = nil + row:Hide() + end + + self.scrollFrame:SetVerticalScroll(0) end frame.setSkills = function(self, botName, skills) self.botName = botName self.skills = skills or {} - self.title:SetText(L("character.info", "Infos personnage") .. " - " .. (botName or "")) + setWindowTitle(self, L("character.info", "Character info") .. " - " .. (botName or "")) self:renderSkills() self:Show() end @@ -349,10 +401,10 @@ function MultiBot.OpenCharacterInfo(botName) local frame = ensureCharacterFrame() frame.botName = botName - frame.title:SetText(L("character.info", "Infos personnage") .. " - " .. botName) + setWindowTitle(frame, L("character.info", "Character info") .. " - " .. botName) frame.skills = {} frame:renderSkills() - frame.status:SetText(L("character.skills.loading", "Chargement...")) + frame.status:SetText(L("character.skills.loading", "Loading...")) frame:Show() if MultiBot.Comm and MultiBot.Comm.RequestBotSkills then From 037cc29306cd794b1efc1ee9c254913f002e8323 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sat, 2 May 2026 15:23:46 +0200 Subject: [PATCH 3/6] Update MultiBotCharacterInfoFrame.lua --- UI/MultiBotCharacterInfoFrame.lua | 194 ++++++++++++++++++++++++++---- 1 file changed, 173 insertions(+), 21 deletions(-) diff --git a/UI/MultiBotCharacterInfoFrame.lua b/UI/MultiBotCharacterInfoFrame.lua index 8b0a486..706992f 100644 --- a/UI/MultiBotCharacterInfoFrame.lua +++ b/UI/MultiBotCharacterInfoFrame.lua @@ -5,6 +5,7 @@ end local AceGUI = LibStub and LibStub("AceGUI-3.0", true) local CATEGORY_ORDER = { "class", "profession", "secondary", "weapon", "armor" } + local CATEGORY_TITLES = { class = "Compétences de classe", profession = "Métiers", @@ -13,6 +14,7 @@ local CATEGORY_TITLES = { armor = "Armures", } + local DIFFICULTY_COLORS = { orange = "|cffff8040", yellow = "|cffffff00", @@ -20,6 +22,14 @@ local DIFFICULTY_COLORS = { gray = "|cff808080", } +local HEADER_ROW_HEIGHT = 22 +local SKILL_ROW_HEIGHT = 24 +local SKILL_BAR_TEXTURE = "Interface\\TargetingFrame\\UI-StatusBar" +local SKILL_BAR_BACKGROUND = "Interface\\Buttons\\WHITE8X8" +local TOGGLE_PLUS_TEXTURE = "Interface\\Buttons\\UI-PlusButton-Up" +local TOGGLE_MINUS_TEXTURE = "Interface\\Buttons\\UI-MinusButton-Up" +local TOGGLE_DISABLED_TEXTURE = "Interface\\Buttons\\UI-PlusButton-Disabled" + local function L(key, fallback) if MultiBot.L then return MultiBot.L(key, fallback) @@ -28,17 +38,44 @@ local function L(key, fallback) return fallback or key end +local function addSimpleBackdrop(frame, bgAlpha) + if not frame or not frame.SetBackdrop then + return + end + + frame:SetBackdrop({ + bgFile = "Interface\\Buttons\\WHITE8x8", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, + tileSize = 16, + edgeSize = 14, + insets = { left = 3, right = 3, top = 3, bottom = 3 }, + }) + + if frame.SetBackdropColor then + frame:SetBackdropColor(0.06, 0.06, 0.08, bgAlpha or 0.92) + end + + if frame.SetBackdropBorderColor then + frame:SetBackdropBorderColor(0.35, 0.35, 0.35, 0.95) + end +end + local function createAceWindow(name, title, width, height, x) if not AceGUI then return nil end - local widget = AceGUI:Create("Frame") + local widget = AceGUI:Create("Window") widget:SetTitle(title or "") widget:SetWidth(width) widget:SetHeight(height) widget:SetLayout("Absolute") + if widget.SetLayout then + widget:SetLayout("Manual") + end + if widget.EnableResize then widget:EnableResize(false) end @@ -46,8 +83,27 @@ local function createAceWindow(name, title, width, height, x) local frame = widget.frame frame.aceWidget = widget frame.content = widget.content or frame + + if frame.SetClampedToScreen then + frame:SetClampedToScreen(true) + end + + if frame.content and frame.content.ClearAllPoints then + frame.content:ClearAllPoints() + frame.content:SetPoint("TOPLEFT", frame, "TOPLEFT", 10, -30) + frame.content:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -10, 10) + addSimpleBackdrop(frame.content, 0.90) + end + frame:SetPoint("CENTER", UIParent, "CENTER", x or 0, 0) - frame:SetFrameStrata("DIALOG") + + local strataLevel = MultiBot.GetGlobalStrataLevel and MultiBot.GetGlobalStrataLevel() + if strataLevel then + frame:SetFrameStrata(strataLevel) + else + frame:SetFrameStrata("DIALOG") + end + _G[name] = frame return frame @@ -122,6 +178,12 @@ local function getSkillBarText(skill) return value .. "/" .. max end +local function getSkillBarValues(skill) + local value = tonumber(skill and skill.value or 0) or 0 + local max = tonumber(skill and skill.max or 0) or 0 + return value, math.max(1, max) +end + local function getItemName(itemId) itemId = tonumber(itemId or 0) or 0 if itemId <= 0 then @@ -276,7 +338,7 @@ local function ensureCharacterFrame() return MultiBot.characterInfoFrame end - local frame = createAceWindow("MultiBotCharacterInfoFrame", L("character.info", "Character info"), 390, 450, -130) + local frame = createAceWindow("MultiBotCharacterInfoFrame", L("character.info", "Character info"), 340, 450, -130) if not frame then return nil end @@ -288,13 +350,14 @@ local function ensureCharacterFrame() frame.status = createText(content, "GameFontHighlightSmall", "TOPLEFT", 18, -36) frame.rows = {} frame.skills = {} + frame.collapsedCategories = frame.collapsedCategories or {} frame.scrollFrame = CreateFrame("ScrollFrame", "MultiBotCharacterInfoFrameSkillScrollFrame", content, "UIPanelScrollFrameTemplate") frame.scrollFrame:SetPoint("TOPLEFT", content, "TOPLEFT", 18, -58) - frame.scrollFrame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -32, 36) + frame.scrollFrame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -28, 36) frame.scrollChild = CreateFrame("Frame", "MultiBotCharacterInfoFrameSkillScrollChild", frame.scrollFrame) - frame.scrollChild:SetWidth(330) + frame.scrollChild:SetWidth(280) frame.scrollChild:SetHeight(1) frame.scrollFrame:SetScrollChild(frame.scrollChild) @@ -304,12 +367,55 @@ local function ensureCharacterFrame() end local row = CreateFrame("Button", nil, frame.scrollChild) - row:SetPoint("TOPLEFT", frame.scrollChild, "TOPLEFT", 0, -((index - 1) * 19)) - row:SetWidth(330) - row:SetHeight(18) - row.text = createText(row, "GameFontHighlightSmall", "LEFT", 0, 0) - row.text:SetWidth(320) + row:SetPoint("TOPLEFT", frame.scrollChild, "TOPLEFT", 0, 0) + row:SetWidth(280) + row:SetHeight(SKILL_ROW_HEIGHT) + + row.toggle = row:CreateTexture(nil, "ARTWORK") + row.toggle:SetPoint("LEFT", row, "LEFT", 0, 0) + row.toggle:SetWidth(16) + row.toggle:SetHeight(16) + row.toggle:Hide() + + row.headerText = createText(row, "GameFontNormal", "LEFT", 20, 0) + row.headerText:SetWidth(300) + row.headerText:SetHeight(18) + row.headerText:Hide() + + row.bar = CreateFrame("StatusBar", nil, row) + row.bar:SetPoint("LEFT", row, "LEFT", 20, 0) + row.bar:SetPoint("RIGHT", row, "RIGHT", 0, 0) + row.bar:SetHeight(18) + row.bar:SetStatusBarTexture(SKILL_BAR_TEXTURE) + row.bar:SetStatusBarColor(0.05, 0.12, 0.70, 0.85) + row.bar:SetMinMaxValues(0, 1) + row.bar:SetValue(0) + row.bar:SetBackdrop({ + bgFile = SKILL_BAR_BACKGROUND, + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = false, + tileSize = 0, + edgeSize = 8, + insets = { left = 2, right = 2, top = 2, bottom = 2 }, + }) + row.bar:SetBackdropColor(0.02, 0.02, 0.10, 0.75) + row.bar:SetBackdropBorderColor(0.35, 0.35, 0.35, 1) + + row.nameText = createText(row.bar, "GameFontNormalSmall", "LEFT", 8, 0) + row.nameText:SetWidth(170) + row.nameText:SetHeight(18) + + row.valueText = createText(row.bar, "GameFontHighlightSmall", "LEFT", 102, 0) + row.valueText:SetWidth(90) + row.valueText:SetHeight(18) + row:SetScript("OnClick", function(self) + if self.categoryHeader then + frame.collapsedCategories[self.categoryHeader] = not frame.collapsedCategories[self.categoryHeader] + frame:renderSkills() + return + end + if not self.skill then return end if self.skill.category ~= "profession" and self.skill.category ~= "secondary" then return end if MultiBot.Comm and MultiBot.Comm.RequestProfessionRecipes then @@ -327,6 +433,53 @@ local function ensureCharacterFrame() return row end + local function showHeaderRow(row, item, y) + row:SetPoint("TOPLEFT", frame.scrollChild, "TOPLEFT", 0, -y) + row:SetHeight(HEADER_ROW_HEIGHT) + row.skill = nil + row.categoryHeader = item.category + + row.bar:Hide() + row.nameText:Hide() + row.valueText:Hide() + + row.toggle:SetTexture(MultiBot.SafeTexturePath(frame.collapsedCategories[item.category] and TOGGLE_PLUS_TEXTURE or TOGGLE_MINUS_TEXTURE)) + row.toggle:Show() + + row.headerText:SetText("|cffffcc00" .. item.header .. "|r") + row.headerText:Show() + + row:Show() + return HEADER_ROW_HEIGHT + end + + local function showSkillRow(row, item, y) + local skill = item.skill + local value, max = getSkillBarValues(skill) + local clickable = skill.category == "profession" or skill.category == "secondary" + + row:SetPoint("TOPLEFT", frame.scrollChild, "TOPLEFT", 0, -y) + row:SetHeight(SKILL_ROW_HEIGHT) + row.skill = skill + row.categoryHeader = nil + + row.toggle:Hide() + row.headerText:Hide() + + row.bar:SetMinMaxValues(0, max) + row.bar:SetValue(math.min(value, max)) + row.bar:Show() + + row.nameText:SetText((clickable and "|cffffcc00" or "|cffffffff") .. (skill.name or skill.key or ("Skill " .. tostring(skill.skillId))) .. "|r") + row.nameText:Show() + + row.valueText:SetText("|cffffffff" .. getSkillBarText(skill) .. "|r") + row.valueText:Show() + + row:Show() + return SKILL_ROW_HEIGHT + end + frame.renderSkills = function(self) local ordered = {} @@ -340,43 +493,42 @@ local function ensureCharacterFrame() end if #categoryItems > 0 then - table.insert(ordered, { header = CATEGORY_TITLES[category] or category }) + table.insert(ordered, { header = CATEGORY_TITLES[category] or category, category = category }) table.sort(categoryItems, function(a, b) return tostring(a.name or "") < tostring(b.name or "") end) - for _, skill in ipairs(categoryItems) do - table.insert(ordered, { skill = skill }) + if not self.collapsedCategories[category] then + for _, skill in ipairs(categoryItems) do + table.insert(ordered, { skill = skill }) + end end end end self.status:SetText(#(self.skills or {}) .. " " .. L("character.skills.count", "skill(s)")) - self.scrollChild:SetHeight(math.max(1, #ordered * 19)) + local contentHeight = 0 for i = 1, #ordered do local row = ensureSkillRow(i) local item = ordered[i] - row.skill = item and item.skill or nil if not item then row:Hide() elseif item.header then - row.text:SetText("|cffffcc00" .. item.header .. "|r") - row:Show() + contentHeight = contentHeight + showHeaderRow(row, item, contentHeight) else - local skill = item.skill - local clickable = (skill.category == "profession" or skill.category == "secondary") and "|cff80d0ff" or "|cffffffff" - row.text:SetText(clickable .. (skill.name or skill.key or ("Skill " .. tostring(skill.skillId))) .. "|r |cff999999" .. getSkillBarText(skill) .. "|r") - row:Show() + contentHeight = contentHeight + showSkillRow(row, item, contentHeight) end end for i = #ordered + 1, #self.rows do local row = self.rows[i] row.skill = nil + row.categoryHeader = nil row:Hide() end + self.scrollChild:SetHeight(math.max(1, contentHeight)) self.scrollFrame:SetVerticalScroll(0) end From d304e1077753092f9e7098301bb4dad3a129e344 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sat, 2 May 2026 16:05:03 +0200 Subject: [PATCH 4/6] Add multilingual variables --- Locales/MultiBotAceLocale-frFR.lua | 17 ++++ UI/MultiBotCharacterInfoFrame.lua | 122 +++++++++++++++++++++++++---- 2 files changed, 123 insertions(+), 16 deletions(-) diff --git a/Locales/MultiBotAceLocale-frFR.lua b/Locales/MultiBotAceLocale-frFR.lua index 442b871..22a6fab 100644 --- a/Locales/MultiBotAceLocale-frFR.lua +++ b/Locales/MultiBotAceLocale-frFR.lua @@ -48,6 +48,23 @@ local frFRValues = { ["profession.recipes.loading"] = "Chargement...", ["character.skills.count"] = "compétence(s)", ["character.skills.loading"] = "Chargement...", + ["character.skills.category.class"] = "Compétences de classe", + ["character.skills.category.profession"] = "Métiers", + ["character.skills.category.secondary"] = "Métiers secondaires", + ["character.skills.category.weapon"] = "Compétences d'arme", + ["character.skills.category.armor"] = "Armures", + ["lootmaster.profession.alchemy"] = "Alchimie", + ["lootmaster.profession.blacksmithing"] = "Forge", + ["lootmaster.profession.enchanting"] = "Enchantement", + ["lootmaster.profession.herbalism"] = "Herboristerie", + ["lootmaster.profession.inscription"] = "Calligraphie", + ["lootmaster.profession.leatherworking"] = "Travail du cuir", + ["lootmaster.profession.mining"] = "Minage", + ["lootmaster.profession.skinning"] = "Dépeçage", + ["lootmaster.profession.tailoring"] = "Couture", + ["character.skills.skill.arms"] = "Armes", + ["character.skills.skill.fury"] = "Fureur", + ["character.skills.skill.protection"] = "Protection", ["tips.units.rti"] = "RTI", ["tips.disperse.main"] = "Disperse", ["tips.disperse.set"] = "Définir la distance de disperse", diff --git a/UI/MultiBotCharacterInfoFrame.lua b/UI/MultiBotCharacterInfoFrame.lua index 706992f..230eb78 100644 --- a/UI/MultiBotCharacterInfoFrame.lua +++ b/UI/MultiBotCharacterInfoFrame.lua @@ -6,15 +6,14 @@ local AceGUI = LibStub and LibStub("AceGUI-3.0", true) local CATEGORY_ORDER = { "class", "profession", "secondary", "weapon", "armor" } -local CATEGORY_TITLES = { - class = "Compétences de classe", - profession = "Métiers", - secondary = "Compétences secondaires", - weapon = "Compétences d'arme", - armor = "Armures", +local CATEGORY_TITLE_KEYS = { + class = "character.skills.category.class", + profession = "character.skills.category.profession", + secondary = "character.skills.category.secondary", + weapon = "character.skills.category.weapon", + armor = "character.skills.category.armor", } - local DIFFICULTY_COLORS = { orange = "|cffff8040", yellow = "|cffffff00", @@ -30,6 +29,59 @@ local TOGGLE_PLUS_TEXTURE = "Interface\\Buttons\\UI-PlusButton-Up" local TOGGLE_MINUS_TEXTURE = "Interface\\Buttons\\UI-MinusButton-Up" local TOGGLE_DISABLED_TEXTURE = "Interface\\Buttons\\UI-PlusButton-Disabled" +local SKILL_DISPLAY_SPELL_IDS = { + [171] = 2259, -- Alchemy + [164] = 2018, -- Blacksmithing + [333] = 7411, -- Enchanting + [202] = 4036, -- Engineering + [182] = 2366, -- Herbalism + [773] = 45357, -- Inscription + [755] = 25229, -- Jewelcrafting + [165] = 2108, -- Leatherworking + [186] = 2575, -- Mining + [393] = 8613, -- Skinning + [197] = 3908, -- Tailoring + [185] = 2550, -- Cooking + [129] = 3273, -- First Aid + [356] = 7620, -- Fishing + [43] = 201, -- Swords + [44] = 196, -- Axes + [45] = 264, -- Bows + [46] = 266, -- Guns + [54] = 198, -- Maces + [55] = 202, -- Two-Handed Swords + [95] = 204, -- Defense + [118] = 674, -- Dual Wield + [136] = 227, -- Staves + [160] = 199, -- Two-Handed Maces + [162] = 203, -- Unarmed + [172] = 197, -- Two-Handed Axes + [173] = 1180, -- Daggers + [176] = 2567, -- Thrown + [226] = 5011, -- Crossbows + [228] = 5009, -- Wands + [229] = 200, -- Polearms + [473] = 15590, -- Fist Weapons + [293] = 750, -- Plate Mail + [413] = 8737, -- Mail + [414] = 9077, -- Leather + [415] = 9078, -- Cloth + [433] = 9116, -- Shield +} + +local function getClientSkillName(skill) + local spellId = tonumber(skill and (skill.displaySpellId or SKILL_DISPLAY_SPELL_IDS[tonumber(skill.skillId or 0) or 0]) or 0) or 0 + if spellId <= 0 or not GetSpellInfo then + return nil + end + + local name = GetSpellInfo(spellId) + if type(name) == "string" and name ~= "" then + return name + end + return nil +end + local function L(key, fallback) if MultiBot.L then return MultiBot.L(key, fallback) @@ -38,6 +90,46 @@ local function L(key, fallback) return fallback or key end +local function getCategoryTitle(category) + return L(CATEGORY_TITLE_KEYS[category] or "", category) +end + +local function localizedOrNil(key) + local value = L(key) + if value ~= key then + return value + end + return nil +end + +local function getSkillDisplayName(skill) + if type(skill) ~= "table" then + return "" + end + + local clientName = getClientSkillName(skill) + if clientName then + return clientName + end + + local key = tostring(skill.key or "") + if key ~= "" then + if skill.category == "profession" or skill.category == "secondary" then + local professionName = localizedOrNil("lootmaster.profession." .. key) + if professionName then + return professionName + end + end + + local skillName = localizedOrNil("character.skills.skill." .. key) + if skillName then + return skillName + end + end + + return skill.name or skill.key or ("Skill " .. tostring(skill.skillId or 0)) +end + local function addSimpleBackdrop(frame, bgAlpha) if not frame or not frame.SetBackdrop then return @@ -322,7 +414,7 @@ local function ensureRecipeFrame() self.skill = skill self.recipes = recipes or {} self.page = 1 - setWindowTitle(self, (skill and skill.name or L("profession.recipes", "Recipes")) .. " - " .. (botName or "")) + setWindowTitle(self, (skill and getSkillDisplayName(skill) or L("profession.recipes", "Recipes")) .. " - " .. (botName or "")) self.status:SetText(#self.recipes .. " " .. L("profession.recipes.count", "unknown recipe(s)")) self:render() self:Show() @@ -344,16 +436,14 @@ local function ensureCharacterFrame() end local content = getFrameContent(frame) - frame.skillsTab = createTabButton(content, L("character.skills", "Skills"), 16) - setButtonEnabled(frame.skillsTab, false) - frame.status = createText(content, "GameFontHighlightSmall", "TOPLEFT", 18, -36) + frame.status = createText(content, "GameFontHighlightSmall", "TOPLEFT", 18, -10) frame.rows = {} frame.skills = {} frame.collapsedCategories = frame.collapsedCategories or {} frame.scrollFrame = CreateFrame("ScrollFrame", "MultiBotCharacterInfoFrameSkillScrollFrame", content, "UIPanelScrollFrameTemplate") - frame.scrollFrame:SetPoint("TOPLEFT", content, "TOPLEFT", 18, -58) + frame.scrollFrame:SetPoint("TOPLEFT", content, "TOPLEFT", 18, -32) frame.scrollFrame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -28, 36) frame.scrollChild = CreateFrame("Frame", "MultiBotCharacterInfoFrameSkillScrollChild", frame.scrollFrame) @@ -420,7 +510,7 @@ local function ensureCharacterFrame() if self.skill.category ~= "profession" and self.skill.category ~= "secondary" then return end if MultiBot.Comm and MultiBot.Comm.RequestProfessionRecipes then ensureRecipeFrame() - setWindowTitle(MultiBot.professionRecipeFrame, self.skill.name .. " - " .. (frame.botName or "")) + setWindowTitle(MultiBot.professionRecipeFrame, getSkillDisplayName(self.skill) .. " - " .. (frame.botName or "")) MultiBot.professionRecipeFrame.status:SetText(L("profession.recipes.loading", "Loading...")) MultiBot.professionRecipeFrame.recipes = {} MultiBot.professionRecipeFrame:render() @@ -470,7 +560,7 @@ local function ensureCharacterFrame() row.bar:SetValue(math.min(value, max)) row.bar:Show() - row.nameText:SetText((clickable and "|cffffcc00" or "|cffffffff") .. (skill.name or skill.key or ("Skill " .. tostring(skill.skillId))) .. "|r") + row.nameText:SetText((clickable and "|cffffcc00" or "|cffffffff") .. getSkillDisplayName(skill) .. "|r") row.nameText:Show() row.valueText:SetText("|cffffffff" .. getSkillBarText(skill) .. "|r") @@ -493,8 +583,8 @@ local function ensureCharacterFrame() end if #categoryItems > 0 then - table.insert(ordered, { header = CATEGORY_TITLES[category] or category, category = category }) - table.sort(categoryItems, function(a, b) return tostring(a.name or "") < tostring(b.name or "") end) + table.insert(ordered, { header = getCategoryTitle(category), category = category }) + table.sort(categoryItems, function(a, b) return getSkillDisplayName(a) < getSkillDisplayName(b) end) if not self.collapsedCategories[category] then for _, skill in ipairs(categoryItems) do From aa478cf74bfa34f843fad364a96a7c6b3bc908cd Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sat, 2 May 2026 16:47:37 +0200 Subject: [PATCH 5/6] Update MultiBotCharacterInfoFrame.lua --- UI/MultiBotCharacterInfoFrame.lua | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/UI/MultiBotCharacterInfoFrame.lua b/UI/MultiBotCharacterInfoFrame.lua index 230eb78..b323791 100644 --- a/UI/MultiBotCharacterInfoFrame.lua +++ b/UI/MultiBotCharacterInfoFrame.lua @@ -28,6 +28,17 @@ local SKILL_BAR_BACKGROUND = "Interface\\Buttons\\WHITE8X8" local TOGGLE_PLUS_TEXTURE = "Interface\\Buttons\\UI-PlusButton-Up" local TOGGLE_MINUS_TEXTURE = "Interface\\Buttons\\UI-MinusButton-Up" local TOGGLE_DISABLED_TEXTURE = "Interface\\Buttons\\UI-PlusButton-Disabled" +local CHARACTER_FRAME_WIDTH = 300 +local CHARACTER_FRAME_X = -150 +local CHARACTER_SKILL_ROW_WIDTH = 240 +local CHARACTER_SKILL_HEADER_WIDTH = CHARACTER_SKILL_ROW_WIDTH - 20 +local CHARACTER_SKILL_NAME_WIDTH = 132 +local CHARACTER_SKILL_VALUE_X = 142 +local CHARACTER_SKILL_VALUE_WIDTH = 72 +local RECIPE_FRAME_WIDTH = 340 +local RECIPE_FRAME_X = 145 +local RECIPE_ROW_WIDTH = 302 +local RECIPE_TEXT_WIDTH = 270 local SKILL_DISPLAY_SPELL_IDS = { [171] = 2259, -- Alchemy @@ -315,7 +326,7 @@ local function ensureRecipeFrame() return MultiBot.professionRecipeFrame end - local frame = createAceWindow("MultiBotProfessionRecipeFrame", L("profession.recipes", "Recipes"), 430, 450, 130) + local frame = createAceWindow("MultiBotProfessionRecipeFrame", L("profession.recipes", "Recipes"), RECIPE_FRAME_WIDTH, 450, RECIPE_FRAME_X) if not frame then return nil end @@ -330,14 +341,14 @@ local function ensureRecipeFrame() for i = 1, frame.pageSize do local row = CreateFrame("Button", nil, content) row:SetPoint("TOPLEFT", content, "TOPLEFT", 18, -30 - ((i - 1) * 25)) - row:SetWidth(392) + row:SetWidth(RECIPE_ROW_WIDTH) row:SetHeight(22) row.icon = row:CreateTexture(nil, "ARTWORK") row.icon:SetPoint("LEFT", row, "LEFT", 0, 0) row.icon:SetWidth(20) row.icon:SetHeight(20) row.text = createText(row, "GameFontHighlightSmall", "LEFT", 26, 0) - row.text:SetWidth(360) + row.text:SetWidth(RECIPE_TEXT_WIDTH) row.text:SetHeight(20) row:SetScript("OnEnter", function(self) if not self.recipe or not GameTooltip then return end @@ -430,7 +441,7 @@ local function ensureCharacterFrame() return MultiBot.characterInfoFrame end - local frame = createAceWindow("MultiBotCharacterInfoFrame", L("character.info", "Character info"), 340, 450, -130) + local frame = createAceWindow("MultiBotCharacterInfoFrame", L("character.info", "Character info"), CHARACTER_FRAME_WIDTH, 450, CHARACTER_FRAME_X) if not frame then return nil end @@ -447,7 +458,7 @@ local function ensureCharacterFrame() frame.scrollFrame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -28, 36) frame.scrollChild = CreateFrame("Frame", "MultiBotCharacterInfoFrameSkillScrollChild", frame.scrollFrame) - frame.scrollChild:SetWidth(280) + frame.scrollChild:SetWidth(CHARACTER_SKILL_ROW_WIDTH) frame.scrollChild:SetHeight(1) frame.scrollFrame:SetScrollChild(frame.scrollChild) @@ -458,7 +469,7 @@ local function ensureCharacterFrame() local row = CreateFrame("Button", nil, frame.scrollChild) row:SetPoint("TOPLEFT", frame.scrollChild, "TOPLEFT", 0, 0) - row:SetWidth(280) + row:SetWidth(CHARACTER_SKILL_ROW_WIDTH) row:SetHeight(SKILL_ROW_HEIGHT) row.toggle = row:CreateTexture(nil, "ARTWORK") @@ -468,7 +479,7 @@ local function ensureCharacterFrame() row.toggle:Hide() row.headerText = createText(row, "GameFontNormal", "LEFT", 20, 0) - row.headerText:SetWidth(300) + row.headerText:SetWidth(CHARACTER_SKILL_HEADER_WIDTH) row.headerText:SetHeight(18) row.headerText:Hide() @@ -492,11 +503,11 @@ local function ensureCharacterFrame() row.bar:SetBackdropBorderColor(0.35, 0.35, 0.35, 1) row.nameText = createText(row.bar, "GameFontNormalSmall", "LEFT", 8, 0) - row.nameText:SetWidth(170) + row.nameText:SetWidth(CHARACTER_SKILL_NAME_WIDTH) row.nameText:SetHeight(18) - row.valueText = createText(row.bar, "GameFontHighlightSmall", "LEFT", 102, 0) - row.valueText:SetWidth(90) + row.valueText = createText(row.bar, "GameFontHighlightSmall", "LEFT", CHARACTER_SKILL_VALUE_X, 0) + row.valueText:SetWidth(CHARACTER_SKILL_VALUE_WIDTH) row.valueText:SetHeight(18) row:SetScript("OnClick", function(self) From 4f7f9640a4229bccc524bcbb3ccfcac697148a23 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sat, 2 May 2026 16:54:02 +0200 Subject: [PATCH 6/6] Add missing translations --- Locales/MultiBotAceLocale-deDE.lua | 24 ++++++++++++++++++++++++ Locales/MultiBotAceLocale-enGB.lua | 24 ++++++++++++++++++++++++ Locales/MultiBotAceLocale-enUS.lua | 24 ++++++++++++++++++++++++ Locales/MultiBotAceLocale-esES.lua | 24 ++++++++++++++++++++++++ Locales/MultiBotAceLocale-koKR.lua | 24 ++++++++++++++++++++++++ Locales/MultiBotAceLocale-ruRU.lua | 24 ++++++++++++++++++++++++ Locales/MultiBotAceLocale-zhCN.lua | 24 ++++++++++++++++++++++++ 7 files changed, 168 insertions(+) diff --git a/Locales/MultiBotAceLocale-deDE.lua b/Locales/MultiBotAceLocale-deDE.lua index fe3e836..d61109a 100644 --- a/Locales/MultiBotAceLocale-deDE.lua +++ b/Locales/MultiBotAceLocale-deDE.lua @@ -41,6 +41,30 @@ local deDEValues = { ["lootmaster.profession.fishing"] = "Angeln", ["lootmaster.profession.firstaid"] = "Erste Hilfe", ["lootmaster.gear_score"] = "GearScore: %d", + ["profession.recipes"] = "Rezepte", + ["profession.recipes.count"] = "bekannte(r) Rezept(e)", + ["character.info"] = "Charakterinformationen", + ["character.skills"] = "Fertigkeiten", + ["profession.recipes.loading"] = "Wird geladen...", + ["character.skills.count"] = "Fertigkeit(en)", + ["character.skills.loading"] = "Wird geladen...", + ["character.skills.category.class"] = "Klassenfertigkeiten", + ["character.skills.category.profession"] = "Berufe", + ["character.skills.category.secondary"] = "Sekundäre Berufe", + ["character.skills.category.weapon"] = "Waffenfertigkeiten", + ["character.skills.category.armor"] = "Rüstungen", + ["lootmaster.profession.alchemy"] = "Alchemie", + ["lootmaster.profession.blacksmithing"] = "Schmiedekunst", + ["lootmaster.profession.enchanting"] = "Verzauberkunst", + ["lootmaster.profession.herbalism"] = "Kräuterkunde", + ["lootmaster.profession.inscription"] = "Inschriftenkunde", + ["lootmaster.profession.leatherworking"] = "Lederverarbeitung", + ["lootmaster.profession.mining"] = "Bergbau", + ["lootmaster.profession.skinning"] = "Kürschnerei", + ["lootmaster.profession.tailoring"] = "Schneiderei", + ["character.skills.skill.arms"] = "Waffen", + ["character.skills.skill.fury"] = "Furor", + ["character.skills.skill.protection"] = "Schutz", ["tips.units.rti"] = "RTI", ["tips.disperse.main"] = "Disperse", ["tips.disperse.set"] = "Disperse-Distanz setzen", diff --git a/Locales/MultiBotAceLocale-enGB.lua b/Locales/MultiBotAceLocale-enGB.lua index ac7f39a..f7fb2f2 100644 --- a/Locales/MultiBotAceLocale-enGB.lua +++ b/Locales/MultiBotAceLocale-enGB.lua @@ -41,6 +41,30 @@ local enGBValues = { ["lootmaster.profession.fishing"] = "Fishing", ["lootmaster.profession.firstaid"] = "First Aid", ["lootmaster.gear_score"] = "GearScore: %d", + ["profession.recipes"] = "Recipes", + ["profession.recipes.count"] = "known recipe(s)", + ["character.info"] = "Character Info", + ["character.skills"] = "Skills", + ["profession.recipes.loading"] = "Loading...", + ["character.skills.count"] = "skill(s)", + ["character.skills.loading"] = "Loading...", + ["character.skills.category.class"] = "Class Skills", + ["character.skills.category.profession"] = "Professions", + ["character.skills.category.secondary"] = "Secondary Professions", + ["character.skills.category.weapon"] = "Weapon Skills", + ["character.skills.category.armor"] = "Armor", + ["lootmaster.profession.alchemy"] = "Alchemy", + ["lootmaster.profession.blacksmithing"] = "Blacksmithing", + ["lootmaster.profession.enchanting"] = "Enchanting", + ["lootmaster.profession.herbalism"] = "Herbalism", + ["lootmaster.profession.inscription"] = "Inscription", + ["lootmaster.profession.leatherworking"] = "Leatherworking", + ["lootmaster.profession.mining"] = "Mining", + ["lootmaster.profession.skinning"] = "Skinning", + ["lootmaster.profession.tailoring"] = "Tailoring", + ["character.skills.skill.arms"] = "Arms", + ["character.skills.skill.fury"] = "Fury", + ["character.skills.skill.protection"] = "Protection", ["tips.units.rti"] = "RTI", ["tips.disperse.main"] = "Disperse", ["tips.disperse.set"] = "Set disperse distance", diff --git a/Locales/MultiBotAceLocale-enUS.lua b/Locales/MultiBotAceLocale-enUS.lua index 7a36364..3b0aeee 100644 --- a/Locales/MultiBotAceLocale-enUS.lua +++ b/Locales/MultiBotAceLocale-enUS.lua @@ -41,6 +41,30 @@ local enUSValues = { ["lootmaster.profession.fishing"] = "Fishing", ["lootmaster.profession.firstaid"] = "First Aid", ["lootmaster.gear_score"] = "GearScore: %d", + ["profession.recipes"] = "Recipes", + ["profession.recipes.count"] = "known recipe(s)", + ["character.info"] = "Character Info", + ["character.skills"] = "Skills", + ["profession.recipes.loading"] = "Loading...", + ["character.skills.count"] = "skill(s)", + ["character.skills.loading"] = "Loading...", + ["character.skills.category.class"] = "Class Skills", + ["character.skills.category.profession"] = "Professions", + ["character.skills.category.secondary"] = "Secondary Professions", + ["character.skills.category.weapon"] = "Weapon Skills", + ["character.skills.category.armor"] = "Armor", + ["lootmaster.profession.alchemy"] = "Alchemy", + ["lootmaster.profession.blacksmithing"] = "Blacksmithing", + ["lootmaster.profession.enchanting"] = "Enchanting", + ["lootmaster.profession.herbalism"] = "Herbalism", + ["lootmaster.profession.inscription"] = "Inscription", + ["lootmaster.profession.leatherworking"] = "Leatherworking", + ["lootmaster.profession.mining"] = "Mining", + ["lootmaster.profession.skinning"] = "Skinning", + ["lootmaster.profession.tailoring"] = "Tailoring", + ["character.skills.skill.arms"] = "Arms", + ["character.skills.skill.fury"] = "Fury", + ["character.skills.skill.protection"] = "Protection", ["tips.units.rti"] = "RTI", ["tips.disperse.main"] = "Disperse", ["tips.disperse.set"] = "Set disperse distance", diff --git a/Locales/MultiBotAceLocale-esES.lua b/Locales/MultiBotAceLocale-esES.lua index d88d8a6..767d284 100644 --- a/Locales/MultiBotAceLocale-esES.lua +++ b/Locales/MultiBotAceLocale-esES.lua @@ -41,6 +41,30 @@ local esESValues = { ["lootmaster.profession.fishing"] = "Pesca", ["lootmaster.profession.firstaid"] = "Primeros auxilios", ["lootmaster.gear_score"] = "GearScore: %d", + ["profession.recipes"] = "Recetas", + ["profession.recipes.count"] = "receta(s) conocida(s)", + ["character.info"] = "Información del personaje", + ["character.skills"] = "Habilidades", + ["profession.recipes.loading"] = "Cargando...", + ["character.skills.count"] = "habilidad(es)", + ["character.skills.loading"] = "Cargando...", + ["character.skills.category.class"] = "Habilidades de clase", + ["character.skills.category.profession"] = "Profesiones", + ["character.skills.category.secondary"] = "Profesiones secundarias", + ["character.skills.category.weapon"] = "Habilidades de arma", + ["character.skills.category.armor"] = "Armaduras", + ["lootmaster.profession.alchemy"] = "Alquimia", + ["lootmaster.profession.blacksmithing"] = "Herrería", + ["lootmaster.profession.enchanting"] = "Encantamiento", + ["lootmaster.profession.herbalism"] = "Herboristería", + ["lootmaster.profession.inscription"] = "Inscripción", + ["lootmaster.profession.leatherworking"] = "Peletería", + ["lootmaster.profession.mining"] = "Minería", + ["lootmaster.profession.skinning"] = "Desuello", + ["lootmaster.profession.tailoring"] = "Sastrería", + ["character.skills.skill.arms"] = "Armas", + ["character.skills.skill.fury"] = "Furia", + ["character.skills.skill.protection"] = "Protección", ["tips.units.rti"] = "RTI", ["tips.disperse.main"] = "Dispersar", ["tips.disperse.set"] = "Definir distancia de dispersión", diff --git a/Locales/MultiBotAceLocale-koKR.lua b/Locales/MultiBotAceLocale-koKR.lua index 90881e7..7b34a01 100644 --- a/Locales/MultiBotAceLocale-koKR.lua +++ b/Locales/MultiBotAceLocale-koKR.lua @@ -41,6 +41,30 @@ local koKRValues = { ["lootmaster.profession.fishing"] = "낚시", ["lootmaster.profession.firstaid"] = "응급치료", ["lootmaster.gear_score"] = "GearScore: %d", + ["profession.recipes"] = "제조법", + ["profession.recipes.count"] = "알고 있는 제조법", + ["character.info"] = "캐릭터 정보", + ["character.skills"] = "기술", + ["profession.recipes.loading"] = "불러오는 중...", + ["character.skills.count"] = "기술", + ["character.skills.loading"] = "불러오는 중...", + ["character.skills.category.class"] = "직업 기술", + ["character.skills.category.profession"] = "전문 기술", + ["character.skills.category.secondary"] = "보조 기술", + ["character.skills.category.weapon"] = "무기 기술", + ["character.skills.category.armor"] = "방어구", + ["lootmaster.profession.alchemy"] = "연금술", + ["lootmaster.profession.blacksmithing"] = "대장기술", + ["lootmaster.profession.enchanting"] = "마법부여", + ["lootmaster.profession.herbalism"] = "약초채집", + ["lootmaster.profession.inscription"] = "주문각인", + ["lootmaster.profession.leatherworking"] = "가죽세공", + ["lootmaster.profession.mining"] = "채광", + ["lootmaster.profession.skinning"] = "무두질", + ["lootmaster.profession.tailoring"] = "재봉술", + ["character.skills.skill.arms"] = "무기", + ["character.skills.skill.fury"] = "분노", + ["character.skills.skill.protection"] = "방어", ["tips.units.rti"] = "RTI", ["tips.disperse.main"] = "분산", ["tips.disperse.set"] = "분산 거리 설정", diff --git a/Locales/MultiBotAceLocale-ruRU.lua b/Locales/MultiBotAceLocale-ruRU.lua index d02deb0..252818d 100644 --- a/Locales/MultiBotAceLocale-ruRU.lua +++ b/Locales/MultiBotAceLocale-ruRU.lua @@ -41,6 +41,30 @@ local ruRUValues = { ["lootmaster.profession.fishing"] = "Рыбная ловля", ["lootmaster.profession.firstaid"] = "Первая помощь", ["lootmaster.gear_score"] = "GearScore: %d", + ["profession.recipes"] = "Рецепты", + ["profession.recipes.count"] = "известные рецепт(ы)", + ["character.info"] = "Информация о персонаже", + ["character.skills"] = "Навыки", + ["profession.recipes.loading"] = "Загрузка...", + ["character.skills.count"] = "навык(и)", + ["character.skills.loading"] = "Загрузка...", + ["character.skills.category.class"] = "Классовые навыки", + ["character.skills.category.profession"] = "Профессии", + ["character.skills.category.secondary"] = "Вторичные профессии", + ["character.skills.category.weapon"] = "Оружейные навыки", + ["character.skills.category.armor"] = "Броня", + ["lootmaster.profession.alchemy"] = "Алхимия", + ["lootmaster.profession.blacksmithing"] = "Кузнечное дело", + ["lootmaster.profession.enchanting"] = "Наложение чар", + ["lootmaster.profession.herbalism"] = "Травничество", + ["lootmaster.profession.inscription"] = "Начертание", + ["lootmaster.profession.leatherworking"] = "Кожевничество", + ["lootmaster.profession.mining"] = "Горное дело", + ["lootmaster.profession.skinning"] = "Снятие шкур", + ["lootmaster.profession.tailoring"] = "Портняжное дело", + ["character.skills.skill.arms"] = "Оружие", + ["character.skills.skill.fury"] = "Неистовство", + ["character.skills.skill.protection"] = "Защита", ["tips.units.rti"] = "RTI", ["tips.disperse.main"] = "Рассредоточение", ["tips.disperse.set"] = "Задать дистанцию рассредоточения", diff --git a/Locales/MultiBotAceLocale-zhCN.lua b/Locales/MultiBotAceLocale-zhCN.lua index bc49f2e..04d9672 100644 --- a/Locales/MultiBotAceLocale-zhCN.lua +++ b/Locales/MultiBotAceLocale-zhCN.lua @@ -41,6 +41,30 @@ local zhCNValues = { ["lootmaster.profession.fishing"] = "钓鱼", ["lootmaster.profession.firstaid"] = "急救", ["lootmaster.gear_score"] = "GearScore:%d", + ["profession.recipes"] = "配方", + ["profession.recipes.count"] = "已知配方", + ["character.info"] = "角色信息", + ["character.skills"] = "技能", + ["profession.recipes.loading"] = "加载中...", + ["character.skills.count"] = "技能", + ["character.skills.loading"] = "加载中...", + ["character.skills.category.class"] = "职业技能", + ["character.skills.category.profession"] = "专业技能", + ["character.skills.category.secondary"] = "辅助专业", + ["character.skills.category.weapon"] = "武器技能", + ["character.skills.category.armor"] = "护甲", + ["lootmaster.profession.alchemy"] = "炼金术", + ["lootmaster.profession.blacksmithing"] = "锻造", + ["lootmaster.profession.enchanting"] = "附魔", + ["lootmaster.profession.herbalism"] = "草药学", + ["lootmaster.profession.inscription"] = "铭文", + ["lootmaster.profession.leatherworking"] = "制皮", + ["lootmaster.profession.mining"] = "采矿", + ["lootmaster.profession.skinning"] = "剥皮", + ["lootmaster.profession.tailoring"] = "裁缝", + ["character.skills.skill.arms"] = "武器", + ["character.skills.skill.fury"] = "狂怒", + ["character.skills.skill.protection"] = "防护", ["tips.units.rti"] = "RTI", ["tips.disperse.main"] = "分散", ["tips.disperse.set"] = "设置分散距离",