diff --git a/modules/UI/botmgr/bot.ts b/modules/UI/botmgr/bot.ts index 8811270..963f727 100644 --- a/modules/UI/botmgr/bot.ts +++ b/modules/UI/botmgr/bot.ts @@ -1,5 +1,10 @@ import { BotData } from './botmgr.server'; +import { Equipment } from './botmgr.server'; +/** + * This is the UI bot data manager class to make it easier + * to managed bot data in the UI. + */ export class BotStorage { private storage: Map = new Map(); @@ -9,7 +14,7 @@ export class BotStorage { return this.storage.get(entry); } - GetBotItem(botId: number, slot: BotEquipmentSlotNum): number | undefined { + GetBotItem(botId: number, slot: BotEquipmentSlotNum): Equipment | undefined { const bot = this.GetBotData(botId); if(bot) { return bot.equipment[slot]; @@ -20,7 +25,7 @@ export class BotStorage { } - SetBotItem(botId: number, slot: BotEquipmentSlotNum, item: number): void { + SetBotItem(botId: number, slot: BotEquipmentSlotNum, item: Equipment): void { const bot = this.GetBotData(botId); if(bot) { bot.equipment[slot] = item; diff --git a/modules/UI/botmgr/botUnit.ts b/modules/UI/botmgr/botUnit.ts new file mode 100644 index 0000000..24c6e5c --- /dev/null +++ b/modules/UI/botmgr/botUnit.ts @@ -0,0 +1,78 @@ +import * as Common from '../../constants/idmaps'; +import { Equipment, EquipmentList } from './botmgr.server'; + +type CharInfo = { + name: string, + level: number, + className: Common.CharacterClass, + classId: keyof typeof Common.ClassesMapping, + raceName: Common.CharacterRace, + raceId: keyof typeof Common.RacesMapping +} + +type CharStats = Record; +type CharTalentSpec = typeof Common.BotTalentSpecs[keyof typeof Common.BotTalentSpecs]; +type CharRoles = typeof Common.BotRoles[keyof typeof Common.BotRoles]; + +export class BotUnit { + + protected myself: Creature; + protected myOwner: Player; + protected charinfo: CharInfo; + protected equipment: EquipmentList; + protected stats: CharStats; + protected talentSpecId: CharTalentSpec; + protected roles: CharRoles; + + constructor(creature: Creature) { + if(!creature.IsNPCBot()) { + return; + } + + this.myself = creature; + this.myOwner = creature.GetOwner(); + this.charinfo = { + name: creature.GetName(), + level: creature.GetLevel(), + className: Common.ClassesMapping[creature.GetClass()], + classId: creature.GetClass(), + raceName: Common.RacesMapping[creature.GetRace()], + raceId: creature.GetRace() + }; + this.equipment = this._lookupEquipment(); + this.roles = creature.GetBotRoles(); + + } + + // public isMeleeDps(): boolean { + // const meleeClassMap = [ + // Common.Characte + + // ] + + // if(this.charinfo.classId) + + // // return this.roles === Common.BotRoles.MeleeDps; + // } + + private _lookupEquipment(): EquipmentList { + const myEquipment = {} as EquipmentList; + for(let slot=0; slot <= Common.BotEquipLast; slot++) { + const equipment = this.myself.GetBotEquipment(slot); + + if(equipment) { + myEquipment[slot] = { + entry: equipment.GetEntry(), + link: equipment.GetItemLink(), + quality: equipment.GetQuality(), + itemLevel: equipment.GetItemLevel(), + enchantmentId: equipment.GetEnchantmentId(0), // Only the permenant enchantments + } + } else { + myEquipment[slot] = undefined; + } + } + + return myEquipment; + } +} \ No newline at end of file diff --git a/modules/UI/botmgr/botmgr.client.ts b/modules/UI/botmgr/botmgr.client.ts index d1a8fb4..b14a185 100644 --- a/modules/UI/botmgr/botmgr.client.ts +++ b/modules/UI/botmgr/botmgr.client.ts @@ -19,12 +19,11 @@ let aio: AIO = {}; */ -import { UIInvSlot, BotEquipSlot, BotSlotName } from "../../constants/idmaps"; -import { BotData } from "./botmgr.server"; +import { UIInvSlot, BotEquipSlot, BotSlotName, BotStat } from "../../constants/idmaps"; +import { BotData, Equipment } from "./botmgr.server"; import { BotStorage } from "./bot"; - - +// Helper functions to create unique ids for frames and components const id = (name: string, entry: number = null) => `BotMgr${name}` + (entry ? entry : ''); const compId = (botId: number, name: string) => `${botId}:BotMgr${name}`; @@ -42,7 +41,6 @@ function ucase(input: string): string { return firstLetter + restOfTheString; } - // If we are a client file. aio.AddAddon() will return false and this file will be serialized and sent to client. if(!aio.AddAddon()) { @@ -50,7 +48,6 @@ if(!aio.AddAddon()) { const InfoFramePool: Map = new Map(); const ComponentsPool: Map = new Map(); // key botId + ":" + componentid const botStorage: BotStorage = new BotStorage(); - let BotItemTooltip: WoWAPI.GameTooltip; @@ -65,7 +62,6 @@ if(!aio.AddAddon()) { resistFrame.SetSize(32, 160); resistFrame.SetPoint("TOPRIGHT", parent, "TOPLEFT", 297, -77); - const magicRes1 = CreateFrame("Frame", id("MagicResFrame1"), resistFrame, "MagicResistanceFrameTemplate", 6); magicRes1.SetPoint("TOP", resistFrame, "TOP", 0, 0); magicRes1.SetSize(32, 32); @@ -82,7 +78,7 @@ if(!aio.AddAddon()) { /** * This is for the Characters left picture and class,race, name details. - * Only created once. + * Only created once per bot panel. */ function AddPortrait(parent: WoWAPI.Frame, botData: BotData) { const portrait = parent.CreateTexture(id("Portrait", botData.entry), "ARTWORK"); @@ -122,14 +118,16 @@ if(!aio.AddAddon()) { infoTextFont.SetPoint("CENTER",0,0); } - + function AddCharacterModel(parent: WoWAPI.Frame, botData: BotData) { const frameChar = CreateFrame("PlayerModel", id("ModelFrame", botData.entry), parent, null, botData.entry); frameChar.SetPoint("TOP", -5, -82); - frameChar.SetSize(240, 160); + frameChar.SetSize(240, 175); frameChar.SetUnit("target"); - frameChar.SetFacing(0.3); - frameChar.SetFrameStrata("MEDIUM"); + frameChar.SetFacing(0.3); + frameChar.SetAlpha(0.65); + frameChar.SetGlow(0.9); + frameChar.SetFrameStrata("MEDIUM"); } function UpdateEquipFrame(group: 'left' | 'right' | 'weapons', parent: WoWAPI.Frame, botData: BotData) { @@ -156,12 +154,13 @@ if(!aio.AddAddon()) { equipSlot.SetPoint("TOPLEFT", i*40+1, 0); else equipSlot.SetPoint("TOPLEFT", 0, -i*40-1); + equipSlot.SetSize(40, 40); equipSlot.SetScript("OnEnter", ItemSlotOnEnter); equipSlot.SetScript("OnLeave", ItemSlotOnLeave); equipSlot.SetScript("OnClick", ItemSlotOnClick); - const equippedItemId = botData.equipment[slotOrder[i]]; + const equippedItem: Equipment = botData.equipment[slotOrder[i]]; let itemIcon: WoWAPI.TexturePath; let itemId: number; let idsuffix: string | number; @@ -174,13 +173,13 @@ if(!aio.AddAddon()) { } // If we have a piece of equipment add the icon template - if(equippedItemId && equippedItemId > 0) { - itemIcon = GetItemIcon(equippedItemId); + if(equippedItem && equippedItem.entry > 0) { + itemIcon = GetItemIcon(equippedItem.entry); idsuffix = slotOrder[i]; } // If there is not a piece of equipment add the background texture - if(!equippedItemId && slotOrder[i] > 0) { + if(!equippedItem && slotOrder[i] > 0) { let slotName = BotSlotName[slotOrder[i]]; if(slotOrder[i] === BotEquipSlot.FINGER1) slotName = "FINGER0"; @@ -196,11 +195,11 @@ if(!aio.AddAddon()) { const itemTexture = equipSlot.CreateTexture(id(`ItemTexture-${idsuffix}`), "OVERLAY"); itemTexture.SetTexture(itemIcon); itemTexture.SetPoint("CENTER", 0, 0); - itemTexture.SetSize(36,36); + itemTexture.SetSize(36,36); + ComponentsPool.set(compId(botData.entry, `ItemSlotTexture-${itemSlotId}`), itemTexture); } } - function AddEquipmentFrames(parent: WoWAPI.Frame, botData: BotData) { // Get all our frames @@ -215,7 +214,7 @@ if(!aio.AddAddon()) { equipFrame = CreateFrame("Frame", id("LeftEquipment"), parent, null, 1); equipFrame.SetPoint("TOPLEFT", 20, -73); equipFrame.SetSize(40, 330); - UpdateEquipFrame('left', equipFrame, botData); + UpdateEquipFrame('left', equipFrame, botData); ComponentsPool.set(compId(botData.entry, "LeftEquipment"), equipFrame); } @@ -225,7 +224,7 @@ if(!aio.AddAddon()) { equipFrame.SetPoint("TOPRIGHT", -40, -73); equipFrame.SetSize(40, 330); ComponentsPool.set(compId(botData.entry, "RightEquipment"), equipFrame); - UpdateEquipFrame('right', equipFrame, botData); + UpdateEquipFrame('right', equipFrame, botData); } @@ -239,8 +238,68 @@ if(!aio.AddAddon()) { // placeholder.SetAllPoints(equipFrame); // placeholder.SetTexture(0,0,0,0.8); } + + } + + function CreateStats(parent: WoWAPI.Frame, botData: BotData) { + const reverseObject = (obj: { [key: string]: any }): { [key: string]: string } => + Object.fromEntries(Object.entries(obj).map(([k, v]) => [v, k])); + const lookup = reverseObject(BotStat); + + + const statsFrame = CreateFrame("Frame", id("CharacterAttr"), parent, null, 1); + statsFrame.SetSize(230,78); + statsFrame.SetPoint("TOPLEFT", 67, -231); + statsFrame.SetFrameStrata("HIGH"); + statsFrame.SetAlpha(1.0); + statsFrame.SetBackdropColor(0,0,0,1.0); + + const leftTop = statsFrame.CreateTexture(id("StatLeftTop"), "BACKGROUND"); + leftTop.SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-StatBackground"); + leftTop.SetSize(115,16); + leftTop.SetPoint("TOPLEFT", 0, 0); + leftTop.SetTexCoord(0, 0.8984375, 0, 0.125); + + const leftmiddle = statsFrame.CreateTexture(id("StatLeftMiddle"), "BACKGROUND"); + leftmiddle.SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-StatBackground"); + leftmiddle.SetSize(115,113); + leftmiddle.SetPoint("TOPLEFT", leftTop, "BOTTOMLEFT", 0, 0); + leftmiddle.SetTexCoord(0, 0.8984375, 0.125, 0.1953125); + + const leftBottom = statsFrame.CreateTexture(id("StatLeftBottom"), "BACKGROUND"); + leftBottom.SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-StatBackground"); + leftBottom.SetSize(115,16); + leftBottom.SetPoint("TOPLEFT", leftmiddle, "BOTTOMLEFT", 0, 0); + leftBottom.SetTexCoord(0, 0.8984375, 0.484375, 0.609375); + + const rightTop = statsFrame.CreateTexture(id("StatRightTop"), "BACKGROUND"); + rightTop.SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-StatBackground"); + rightTop.SetSize(115,16); + rightTop.SetPoint("TOPLEFT", leftTop, "TOPRIGHT",0, 0); + rightTop.SetTexCoord(0, 0.8984375, 0, 0.125); + + const rightMiddle = statsFrame.CreateTexture(id("StatRightMiddle"), "BACKGROUND"); + rightMiddle.SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-StatBackground"); + rightMiddle.SetSize(115,113); + rightMiddle.SetPoint("TOPLEFT", leftmiddle, "TOPRIGHT", 0, 0); + rightMiddle.SetTexCoord(0, 0.8984375, 0.125, 0.1953125); + + const rightBottom = statsFrame.CreateTexture(id("StatRightBottom"), "BACKGROUND"); + rightBottom.SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-StatBackground"); + rightBottom.SetSize(115,16); + rightBottom.SetPoint("TOPLEFT", leftBottom, "TOPRIGHT", 0, 0); + rightBottom.SetTexCoord(0, 0.8984375, 0.484375, 0.609375); + + let counter = 0; + // for(const stats in botData.stats.left) { + // const stat = statsFrame.CreateFontString(id(`Stat-${stats}`), "ARTWORK", "GameFontNormalSmall"); + // stat.SetPoint("TOPLEFT", statsFrame, "TOPLEFT", 10, -10 - (counter * 14)); + // stat.SetText(lookup[stats] + " " + botData.stats[stats]); + // counter++; + // } + } function SetBackground(parent: WoWAPI.Frame) { @@ -286,12 +345,16 @@ if(!aio.AddAddon()) { }); } + /** + * START OF EVENT HANDLERS + */ + function ItemSlotOnEnter(frame: WoWAPI.Button) { const botId = botStorage.GetActive(); - const itemId = botStorage.GetBotItem(botId, frame.GetID()); + const theItem = botStorage.GetBotItem(botId, frame.GetID()); GameTooltip.SetOwner(frame, "ANCHOR_RIGHT"); - if(itemId) { - GameTooltip.SetHyperlink(`item:${itemId}:0:0:0:0:0:0:0`); + if(theItem) { + GameTooltip.SetHyperlink(theItem.link); } else { if(frame.GetID() == 90) { GameTooltip.SetText("Tabard"); @@ -325,14 +388,14 @@ if(!aio.AddAddon()) { function ItemSlotOnClick(frame: WoWAPI.Button, button: string) { const botId = botStorage.GetActive(); - const itemId = botStorage.GetBotItem(botId, frame.GetID()); + const theItem = botStorage.GetBotItem(botId, frame.GetID()); const [compItem, compItemId, compItemLink] = GetCursorInfo(); print(`CursorHasItem: ${compItemLink}`); - if(itemId && !compItem) { + if(theItem && !compItem) { if(button == "LeftButton") { - PickupItem(itemId); + PickupItem(theItem.link); return; } } @@ -340,21 +403,29 @@ if(!aio.AddAddon()) { if(compItem) { const slot = frame.GetID(); - aio.Handle("BotMgr", "EquipItem",botId, slot, compItemId); + aio.Handle("BotMgr", "EquipTheItem", GetUnitName("player", false), botId, slot, compItemId, compItemLink); // Attempt to equip the item. PlaySound("INTERFACESOUND_CURSORDROPOBJECT"); ClearCursor(); } - - - // else { - // if(CursorHasItem()) { - // const [compItem, compItemId, compItemLink] = GetCursorInfo(); - // botStorage.EquipBotItem(botId, frame.GetID(), compItemId); - // } - // } } + botMgrHandlers.OnEquipSuccess = (botId: number, slot: BotEquipmentSlotNum, itemId: number, itemLink: string) => { + const itemTexture = ComponentsPool.get(compId(botId, `ItemSlotTexture-${slot}`)); + itemTexture.SetTexture(GetItemIcon(itemId)); + + // Hide Tooltips otherwise it will show old item. + const BotTooltip = ComponentsPool.get(compId(botId, "tooltip")); + BotTooltip.Hide(); + GameTooltip.Hide(); + } + + botMgrHandlers.OnEquipFail = (botId: number, slot: BotEquipmentSlotNum, itemId: number, itemLink: string) => { + PlaySound("ITEMGENERICSOUND"); + ClearCursor(); + } + + /** * Shows or Creates a new Bot Equipment Management Frame * Every NPC Bot that is requested to be managed will get their own unique frame. This @@ -394,7 +465,23 @@ if(!aio.AddAddon()) { mainFrame.SetScript("OnLeave", (frame) => { frame.SetFrameLevel(5); }); + + mainFrame.RegisterEvent("CURSOR_UPDATE"); + mainFrame.RegisterEvent("ITEM_LOCK_CHANGED"); + mainFrame.RegisterEvent("ITEM_UNLOCKED"); + + mainFrame.SetScript("OnEvent", (frame: WoWAPI.Frame, eventName: WoWAPI.Event, ...args) => { + if(eventName == "ITEM_LOCKED") { + GetCursorInfo() + print(args); + for(const arg of args) { + print(arg); + } + } + }); + + // mainFrame.Hide(); BotItemTooltip = CreateFrame("GameTooltip", id("ItemToolTip"+botData.entry), mainFrame, "GameTooltipTemplate", botData.entry); @@ -407,8 +494,10 @@ if(!aio.AddAddon()) { AddCharacterModel(mainFrame, botData); AddResistFrame(mainFrame); AddEquipmentFrames(mainFrame, botData); + CreateStats(mainFrame, botData); AddSoundEffects(mainFrame); + InfoFramePool.set(botData.entry, mainFrame); ComponentsPool.set(compId(botData.entry, "tooltip"), BotItemTooltip); @@ -501,11 +590,11 @@ if(!aio.AddAddon()) { } - botMgrHandlers.ShowFrame = (botData: BotData) => { - - // Update the botData manager for this Bot on panel show. + + botMgrHandlers.ShowFrame = (botData: BotData) =>{ botStorage.UpdateBotData(botData.entry, botData); - ShowBotFrame(botData); - } + ShowBotFrame(botData); + } + } diff --git a/modules/UI/botmgr/botmgr.server.ts b/modules/UI/botmgr/botmgr.server.ts index 68ae78a..22de277 100644 --- a/modules/UI/botmgr/botmgr.server.ts +++ b/modules/UI/botmgr/botmgr.server.ts @@ -14,10 +14,20 @@ import { ClassesMapping, CharacterClass, RacesMapping, - CharacterRace + CharacterRace, + ItemQuality, + QualityType } from "../../constants/idmaps"; +export type Equipment = { + entry: number, + link: string, + quality?: QualityType, + itemLevel?: number, + enchantmentId?: number, +} +export type EquipmentList = Record; /** * Everything we ever wanted to know about the bot info on load @@ -30,7 +40,7 @@ import { classId: number, race: CharacterRace, raceId: number, - equipment?: Record, // SlotName - ItemId See BotEquipSlot + equipment?: EquipmentList, // SlotName - ItemId See BotEquipSlot stats?: Record, // StatId - Value }; @@ -39,9 +49,6 @@ import { */ const NpcDetailStorage = {} as Record; -// // cheap way to load everything on the server side and force so client as access. (Don't love this at all will need to work back into plugin); -// const loader = BotStat || BotEquipSlot || BotEquipLast || BotStatLast || ClassesMapping || RacesMapping; - /** * Get the current targetted npc bot or returns undefined if not a bot. * @param player @@ -83,12 +90,42 @@ function TargetIsEligible(player: Player) { return false; } +function GetMeleeStats () { + return { + left: [ + BotStat.STRENGTH, + BotStat.AGILITY, + BotStat.DAMAGE_MIN, + BotStat.DAMAGE_MAX, + BotStat.ATTACK_POWER, + BotStat.HIT_RATING, + BotStat.CRIT_RATING, + BotStat.EXPERTISE, + BotStat.ARMOR_PENETRATION_RATING, + ], + right: [ + BotStat.HASTE_RATING, + BotStat.ARMOR, + BotStat.STAMINA, + BotStat.DEFENSE_SKILL_RATING, + BotStat.DODGE_RATING, + BotStat.PARRY_RATING, + BotStat.BLOCK_RATING, + BotStat.BLOCK_VALUE + ] + } +} + +function GetCasterStats() { + +} + /** * @noSelf */ function GetBotDetails(bot: Creature): BotData { - const owner = bot.GetBotOwner(); + const owner = bot.GetBotOwner(); // We can use bot entrys since they are 1:1 with GUIDs for shorter storage keys NpcDetailStorage[bot.GetEntry()] = { @@ -98,21 +135,44 @@ function GetBotDetails(bot: Creature): BotData { class: ClassesMapping[bot.GetBotClass()], classId: bot.GetBotClass(), race: RacesMapping[bot.GetRace()], - raceId: bot.GetRace(), - equipment: {}, - // stats: {}, + raceId: bot.GetRace(), + equipment: {} as EquipmentList, + stats: {}, }; + print(bot.GetBotRoles()); + + + // Get all the equipment for(let slot=0; slot <= BotEquipLast; slot++) { const equipment = bot.GetBotEquipment(slot); - if(equipment) { - NpcDetailStorage[bot.GetEntry()].equipment[slot] = equipment.GetEntry(); - print(`Slot: ${BotSlotName[slot]} Item: ${equipment.GetEntry()}`); + if(equipment) { + NpcDetailStorage[bot.GetEntry()].equipment[slot] = { + entry: equipment.GetEntry(), + link: equipment.GetItemLink(), + quality: equipment.GetQuality(), + itemLevel: equipment.GetItemLevel(), + enchantmentId: equipment.GetEnchantmentId(0), + } } else { NpcDetailStorage[bot.GetEntry()].equipment[slot] = undefined; } } + + // get the stats we care about by Class + // This will determine what stats to lookup for the bot. + const lookups = GetMeleeStats(); + + + lookups.left.forEach(stat => { + const result = bot.GetBotStat(stat); + NpcDetailStorage[bot.GetEntry()].stats[stat] = result; + }); + lookups.right.forEach(stat => { + const result = bot.GetBotStat(stat); + NpcDetailStorage[bot.GetEntry()].stats[stat] = result; + }); return NpcDetailStorage[bot.GetEntry()]; } @@ -124,27 +184,30 @@ function GetBotDetails(bot: Creature): BotData { * @param command * @returns */ -function EquipItem(botEntry: number, slot: BotEquipmentSlotNum, item: number): void { - +function EquipTheItem(player: string, botEntry: number, slot: BotEquipmentSlotNum, item: number, link: string ): void { + if(botEntry && typeof botEntry !== 'number') { + return; + } - print(`Bot: ${botEntry} Slot: ${slot} Item: ${item}`); - - // const isEligible = bot.BotCanEquipItem(item, slot); + const owner = GetPlayerByName(player); + const creatures = owner.GetCreaturesInRange(60, botEntry) as Creature[]; + const bot = creatures[0]; + const isEligible = bot.BotCanEquipItem(item, slot); + if(!isEligible) { + log.error(`Bot cannot equip item: ${item} in slot: ${slot}`); + return; + } - // if(!isEligible) { - // log.error(`Bot cannot equip item: ${item} in slot: ${slot}`); - // return; - // } - - // if(bot.BotEquipItem(item, slot)) { - // NpcDetailStorage[bot.GetEntry()].equipment[slot] = item; - // aio.Handle(bot.GetBotOwner(), 'BotMgr', 'EquipSuccess', { slot, item}); - // } else { - // log.error(`Bot failed to equip item: ${item} in slot: ${slot}`); - // aio.Handle(bot.GetBotOwner(), 'BotMgr', 'EquipFail', { slot, item}); - // } + if(bot.BotEquipItem(item, slot)) { + GetBotDetails(bot); + log.log(`Bot successfully equipped item: ${item} in slot: ${slot}`); + aio.Handle(bot.GetBotOwner(), 'BotMgr', 'OnEquipSuccess',botEntry, slot, item, link); + } else { + log.error(`Bot failed to equip item: ${item} in slot: ${slot}`); + aio.Handle(bot.GetBotOwner(), 'BotMgr', 'OnEquipFail', botEntry, slot, item, link); + } } @@ -201,10 +264,11 @@ function GetBotPanelInfo(player: Player): void { const botMgrHandlers = aio.AddHandlers('BotMgr', { TargetIsEligible, GetBotPanelInfo, - EquipItem + "EquipTheItem": EquipTheItem }); + RegisterPlayerEvent( PlayerEvents.PLAYER_EVENT_ON_COMMAND, (...args) => ShowBotMgr(...args) -); +); \ No newline at end of file diff --git a/modules/constants/idmaps.ts b/modules/constants/idmaps.ts index e10b3f6..3aa74ea 100644 --- a/modules/constants/idmaps.ts +++ b/modules/constants/idmaps.ts @@ -101,7 +101,7 @@ export const BotStat = { BOT_STAT_MOD_RESISTANCE_START: 51, // Assuming BOT_STAT_MOD_ARMOR is defined somewhere } as const; - export const BotStatLast = 58; +export const BotStatLast = 58; export const UIInvSlot = { AMMOSLOT: "AMMOSLOT", @@ -165,4 +165,158 @@ export const RacesMapping: Record = { 12: "Worgen", } as const; -export type CharacterRace = typeof RacesMapping[keyof typeof RacesMapping]; \ No newline at end of file +export type CharacterRace = typeof RacesMapping[keyof typeof RacesMapping]; + +export const BotTalentSpecs = { + WARRIOR_ARMS : 1, + WARRIOR_FURY : 2, + WARRIOR_PROTECTION : 3, + PALADIN_HOLY : 4, + PALADIN_PROTECTION : 5, + PALADIN_RETRIBUTION : 6, + HUNTER_BEASTMASTERY : 7, + HUNTER_MARKSMANSHIP : 8, + HUNTER_SURVIVAL : 9, + ROGUE_ASSASSINATION : 10, + ROGUE_COMBAT : 11, + ROGUE_SUBTLETY : 12, + PRIEST_DISCIPLINE : 13, + PRIEST_HOLY : 14, + PRIEST_SHADOW : 15, + DK_BLOOD : 16, + DK_FROST : 17, + DK_UNHOLY : 18, + SHAMAN_ELEMENTAL : 19, + SHAMAN_ENHANCEMENT : 20, + SHAMAN_RESTORATION : 21, + MAGE_ARCANE : 22, + MAGE_FIRE : 23, + MAGE_FROST : 24, + WARLOCK_AFFLICTION : 25, + WARLOCK_DEMONOLOGY : 26, + WARLOCK_DESTRUCTION : 27, + DRUID_BALANCE : 28, + DRUID_FERAL : 29, + DRUID_RESTORATION : 30, + DEFAULT : 31, + BEGIN : 1, + END : 31 +} as const; + +export function talentSpecName(id: number) { + return Object.keys(BotTalentSpecs).find(key => BotTalentSpecs[key] === id); +} + +export const BotRoles = { + NONE : 0, + TANK : 1, + TANK_OFF : 2, + DPS : 4, + HEAL : 8, + RANGED : 16, + PARTY : 32, // hidden + GATHERING_MINING : 64, + GATHERING_HERBALISM : 128, + GATHERING_SKINNING : 256, + GATHERING_ENGINEERING : 512, + AUTOLOOT : 1024, + AUTOLOOT_POOR : 2048, + AUTOLOOT_COMMON : 4096, + AUTOLOOT_UNCOMMON : 8192, + AUTOLOOT_RARE : 16384, + AUTOLOOT_EPIC : 32768, + AUTOLOOT_LEGENDARY : 65536, + MASK_MAIN : (1 | 2 | 4 | 8 | 16), + MASK_GATHERING : (64 | 128 | 256 | 512), + MASK_LOOTING : (2048 | 4096 | 8192 | 16384 | 32768 | 65536), + BOT_MAX_ROLE : 131072, +} as const; + + +/**************** ITEM CONSTANTS *************************/ + +export const ItemQuality = { + Poor: 0, + Common: 1, + Uncommon: 2, + Rare: 3, + Epic: 4, + Legendary: 5, + Artifact: 6, + Heirlooms: 7, +} as const; + +export const ItemStat = { + MANA: 0, + HEALTH: 1, + AGILITY: 3, + STRENGTH: 4, + INTELLECT: 5, + SPIRIT: 6, + STAMINA: 7, + DEFENSE_SKILL_RATING: 12, + DODGE_RATING: 13, + PARRY_RATING: 14, + BLOCK_RATING: 15, + HIT_MELEE_RATING: 16, + HIT_RANGED_RATING: 17, + HIT_SPELL_RATING: 18, + CRIT_MELEE_RATING: 19, + CRIT_RANGED_RATING: 20, + CRIT_SPELL_RATING: 21, + HIT_TAKEN_MELEE_RATING: 22, + HIT_TAKEN_RANGED_RATING: 23, + HIT_TAKEN_SPELL_RATING: 24, + CRIT_TAKEN_MELEE_RATING: 25, + CRIT_TAKEN_RANGED_RATING: 26, + CRIT_TAKEN_SPELL_RATING: 27, + HASTE_MELEE_RATING: 28, + HASTE_RANGED_RATING: 29, + HASTE_SPELL_RATING: 30, + HIT_RATING: 31, + CRIT_RATING: 32, + HIT_TAKEN_RATING: 33, + CRIT_TAKEN_RATING: 34, + RESILIENCE_RATING: 35, + HASTE_RATING: 36, + EXPERTISE_RATING: 37, + ATTACK_POWER: 38, + RANGED_ATTACK_POWER: 39, + FERAL_ATTACK_POWER: 40, // Note: This is not used as of 3.3 + SPELL_HEALING_DONE: 41, + SPELL_DAMAGE_DONE: 42, + MANA_REGENERATION: 43, + ARMOR_PENETRATION_RATING: 44, + SPELL_POWER: 45, + HEALTH_REGEN: 46, + SPELL_PENETRATION: 47, + BLOCK_VALUE: 48, +} as const; + +export const DamageType = { + Physical: 0, + Holy: 1, + Fire: 2, + Nature: 3, + Frost: 4, + Shadow: 5, + Arcane: 6, +} as const; + +export const SocketColor = { + Meta: 1, + Red: 2, + Yellow: 4, + Blue: 8, +} as const; + +export const SocketBonus = { + 3312: '+8 Strength', + 3313: '+8 Agility', + 3305: '+12 Stamina', + 3: '+8 Intellect', + 2872: '+9 Healing', + 3753: '+9 Spell Power', + 3877: '+16 Attack Power', +} as const; +