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/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-frFR.lua b/Locales/MultiBotAceLocale-frFR.lua index 93d810b..22a6fab 100644 --- a/Locales/MultiBotAceLocale-frFR.lua +++ b/Locales/MultiBotAceLocale-frFR.lua @@ -41,6 +41,30 @@ 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...", + ["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/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"] = "设置分散距离", 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..b323791 --- /dev/null +++ b/UI/MultiBotCharacterInfoFrame.lua @@ -0,0 +1,690 @@ +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_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", + green = "|cff80be80", + 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 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 + [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) + end + + 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 + 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("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 + + 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) + + local strataLevel = MultiBot.GetGlobalStrataLevel and MultiBot.GetGlobalStrataLevel() + if strataLevel then + frame:SetFrameStrata(strataLevel) + else + frame:SetFrameStrata("DIALOG") + end + + _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", + 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 setButtonEnabled(button, enabled) + if not button then + return + end + + if enabled then + button:Enable() + else + button:Disable() + 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) + 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 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 + 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 = createAceWindow("MultiBotProfessionRecipeFrame", L("profession.recipes", "Recipes"), RECIPE_FRAME_WIDTH, 450, RECIPE_FRAME_X) + if not frame then + return nil + end + + 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, content) + row:SetPoint("TOPLEFT", content, "TOPLEFT", 18, -30 - ((i - 1) * 25)) + 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(RECIPE_TEXT_WIDTH) + 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, content, "UIPanelButtonTemplate") + frame.prev:SetPoint("BOTTOMLEFT", content, "BOTTOMLEFT", 18, 8) + frame.prev:SetWidth(48) + frame.prev:SetHeight(20) + frame.prev:SetText("<") + + frame.pageText = createText(content, "GameFontNormalSmall", "BOTTOM", 0, 12) + + 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(">") + + 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 + 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() + end + + frame:Hide() + MultiBot.professionRecipeFrame = frame + return frame +end + +local function ensureCharacterFrame() + if MultiBot.characterInfoFrame then + return MultiBot.characterInfoFrame + end + + local frame = createAceWindow("MultiBotCharacterInfoFrame", L("character.info", "Character info"), CHARACTER_FRAME_WIDTH, 450, CHARACTER_FRAME_X) + if not frame then + return nil + end + + local content = getFrameContent(frame) + + 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, -32) + frame.scrollFrame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -28, 36) + + frame.scrollChild = CreateFrame("Frame", "MultiBotCharacterInfoFrameSkillScrollChild", frame.scrollFrame) + frame.scrollChild:SetWidth(CHARACTER_SKILL_ROW_WIDTH) + 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, 0) + row:SetWidth(CHARACTER_SKILL_ROW_WIDTH) + 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(CHARACTER_SKILL_HEADER_WIDTH) + 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(CHARACTER_SKILL_NAME_WIDTH) + row.nameText:SetHeight(18) + + 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) + 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 + ensureRecipeFrame() + setWindowTitle(MultiBot.professionRecipeFrame, getSkillDisplayName(self.skill) .. " - " .. (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[index] = row + 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") .. getSkillDisplayName(skill) .. "|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 = {} + + 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 = 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 + table.insert(ordered, { skill = skill }) + end + end + end + end + + self.status:SetText(#(self.skills or {}) .. " " .. L("character.skills.count", "skill(s)")) + + local contentHeight = 0 + + for i = 1, #ordered do + local row = ensureSkillRow(i) + local item = ordered[i] + + if not item then + row:Hide() + elseif item.header then + contentHeight = contentHeight + showHeaderRow(row, item, contentHeight) + else + 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 + + frame.setSkills = function(self, botName, skills) + self.botName = botName + self.skills = skills or {} + setWindowTitle(self, L("character.info", "Character info") .. " - " .. (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 + setWindowTitle(frame, L("character.info", "Character info") .. " - " .. botName) + frame.skills = {} + frame:renderSkills() + frame.status:SetText(L("character.skills.loading", "Loading...")) + 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