diff --git a/BetterCharacterStats.lua b/BetterCharacterStats.lua index fb0adee..c1b16ad 100644 --- a/BetterCharacterStats.lua +++ b/BetterCharacterStats.lua @@ -1,1016 +1,1512 @@ -BCS = BCS or {} -BCSConfig = BCSConfig or {} - -local L, IndexLeft, IndexRight -L = BCS.L - -BCS.PLAYERSTAT_DROPDOWN_OPTIONS = { - "PLAYERSTAT_BASE_STATS", - "PLAYERSTAT_MELEE_COMBAT", - "PLAYERSTAT_RANGED_COMBAT", - "PLAYERSTAT_SPELL_COMBAT", - "PLAYERSTAT_SPELL_SCHOOLS", - "PLAYERSTAT_DEFENSES", -} - -BCS.MELEEHIT = { - ["ROGUE"] = { - 5, -- pvp - 8, -- yellow cap - 24.6, -- white cap - }, -} - -BCS.SPELLHIT = { - -- soon(tm) -} - -BCS.PaperDollFrame = PaperDollFrame - -BCS.Debug = false -BCS.DebugStack = {} - -function BCS:DebugTrace(start, limit) - BCS.Debug = nil - local length = getn(BCS.DebugStack) - if not start then start = 1 end - if start > length then start = length end - if not limit then limit = start + 30 end - - BCS:Print("length: " .. length) - BCS:Print("start: " .. start) - BCS:Print("limit: " .. limit) - - for i = start, length, 1 do - BCS:Print("[" .. i .. "] Event: " .. BCS.DebugStack[i].E) - BCS:Print(format( - "[%d] `- Arguments: %s, %s, %s, %s, %s", - i, - BCS.DebugStack[i].arg1, - BCS.DebugStack[i].arg2, - BCS.DebugStack[i].arg3, - BCS.DebugStack[i].arg4, - BCS.DebugStack[i].arg5 - )) - if i >= limit then i = length end - end - -end - -function BCS:Print(message) - ChatFrame2:AddMessage("[BCS] " .. message, 0.63, 0.86, 1.0) -end - -function BCS:OnLoad() - CharacterAttributesFrame:Hide() - PaperDollFrame:UnregisterEvent('UNIT_DAMAGE') - PaperDollFrame:UnregisterEvent('PLAYER_DAMAGE_DONE_MODS') - PaperDollFrame:UnregisterEvent('UNIT_ATTACK_SPEED') - PaperDollFrame:UnregisterEvent('UNIT_RANGEDDAMAGE') - PaperDollFrame:UnregisterEvent('UNIT_ATTACK') - PaperDollFrame:UnregisterEvent('UNIT_STATS') - PaperDollFrame:UnregisterEvent('UNIT_ATTACK_POWER') - PaperDollFrame:UnregisterEvent('UNIT_RANGED_ATTACK_POWER') - - self.Frame = BCSFrame - self.needUpdate = nil - - self.Frame:RegisterEvent("ADDON_LOADED") - self.Frame:RegisterEvent("UNIT_INVENTORY_CHANGED") -- fires when equipment changes - self.Frame:RegisterEvent("CHARACTER_POINTS_CHANGED") -- fires when learning talent - self.Frame:RegisterEvent("PLAYER_AURAS_CHANGED") -- buffs/warrior stances - - local _, classFileName = UnitClass("Player") - self.playerClass = strupper(classFileName) -end - -function BCS:OnEvent() - --[[if BCS.Debug then - local t = { - E = event, - arg1 = arg1 or "nil", - arg2 = arg2 or "nil", - arg3 = arg3 or "nil", - arg4 = arg4 or "nil", - arg5 = arg5 or "nil", - } - tinsert(BCS.DebugStack, t) - end]] - - if - event == "PLAYER_AURAS_CHANGED" or - event == "CHARACTER_POINTS_CHANGED" - then - if BCS.PaperDollFrame:IsVisible() then - BCS:UpdateStats() - else - BCS.needUpdate = true - end - elseif event == "UNIT_INVENTORY_CHANGED" and arg1 == "player" then - if BCS.PaperDollFrame:IsVisible() then - BCS:UpdateStats() - else - BCS.needUpdate = true - end - elseif event == "ADDON_LOADED" and arg1 == "BetterCharacterStats" then - IndexLeft = BCSConfig["DropdownLeft"] or BCS.PLAYERSTAT_DROPDOWN_OPTIONS[1] - IndexRight = BCSConfig["DropdownRight"] or BCS.PLAYERSTAT_DROPDOWN_OPTIONS[2] - - UIDropDownMenu_SetSelectedValue(PlayerStatFrameLeftDropDown, IndexLeft) - UIDropDownMenu_SetSelectedValue(PlayerStatFrameRightDropDown, IndexRight) - end -end - -function BCS:OnShow() - if BCS.needUpdate then - BCS.needUpdate = nil - BCS:UpdateStats() - end -end - --- debugging / profiling ---local avgV = {} ---local avg = 0 -function BCS:UpdateStats() - --[[if BCS.Debug then - local e = event or "nil" - BCS:Print("Update due to " .. e) - end - local beginTime = debugprofilestop()]] - - BCS:UpdatePaperdollStats("PlayerStatFrameLeft", IndexLeft) - BCS:UpdatePaperdollStats("PlayerStatFrameRight", IndexRight) - - --[[local timeUsed = debugprofilestop()-beginTime - table.insert(avgV, timeUsed) - avg = 0 - - for i,v in ipairs(avgV) do - avg = avg + v - end - avg = avg / getn(avgV) - - BCS:Print(format("Average: %d (%d results), Exact: %d", avg, getn(avgV), timeUsed))]] -end - -function BCS:SetStat(statFrame, statIndex) - local label = getglobal(statFrame:GetName().."Label") - local text = getglobal(statFrame:GetName().."StatText") - local stat - local effectiveStat - local posBuff - local negBuff - local statIndexTable = { - "STRENGTH", - "AGILITY", - "STAMINA", - "INTELLECT", - "SPIRIT", - } - - statFrame:SetScript("OnEnter", function() - PaperDollStatTooltip("player", statIndexTable[statIndex]) - end) - - statFrame:SetScript("OnLeave", function() - GameTooltip:Hide() - end) - - label:SetText(TEXT(getglobal("SPELL_STAT"..(statIndex-1).."_NAME"))..":") - stat, effectiveStat, posBuff, negBuff = UnitStat("player", statIndex) - - -- Set the tooltip text - local tooltipText = HIGHLIGHT_FONT_COLOR_CODE..getglobal("SPELL_STAT"..(statIndex-1).."_NAME").." " - - if ( ( posBuff == 0 ) and ( negBuff == 0 ) ) then - text:SetText(effectiveStat) - statFrame.tooltip = tooltipText..effectiveStat..FONT_COLOR_CODE_CLOSE - else - tooltipText = tooltipText..effectiveStat - if ( posBuff > 0 or negBuff < 0 ) then - tooltipText = tooltipText.." ("..(stat - posBuff - negBuff)..FONT_COLOR_CODE_CLOSE - end - if ( posBuff > 0 ) then - tooltipText = tooltipText..FONT_COLOR_CODE_CLOSE..GREEN_FONT_COLOR_CODE.."+"..posBuff..FONT_COLOR_CODE_CLOSE - end - if ( negBuff < 0 ) then - tooltipText = tooltipText..RED_FONT_COLOR_CODE.." "..negBuff..FONT_COLOR_CODE_CLOSE - end - if ( posBuff > 0 or negBuff < 0 ) then - tooltipText = tooltipText..HIGHLIGHT_FONT_COLOR_CODE..")"..FONT_COLOR_CODE_CLOSE - end - statFrame.tooltip = tooltipText - - -- If there are any negative buffs then show the main number in red even if there are - -- positive buffs. Otherwise show in green. - if ( negBuff < 0 ) then - text:SetText(RED_FONT_COLOR_CODE..effectiveStat..FONT_COLOR_CODE_CLOSE) - else - text:SetText(GREEN_FONT_COLOR_CODE..effectiveStat..FONT_COLOR_CODE_CLOSE) - end - end -end - -function BCS:SetArmor(statFrame) - - local base, effectiveArmor, armor, posBuff, negBuff = UnitArmor("player") - local totalBufs = posBuff + negBuff - local frame = statFrame - local label = getglobal(frame:GetName() .. "Label") - local text = getglobal(frame:GetName() .. "StatText") - - PaperDollFormatStat(ARMOR, base, posBuff, negBuff, frame, text) - label:SetText(TEXT(ARMOR_COLON)) - - local playerLevel = UnitLevel("player") - local armorReduction = effectiveArmor/((85 * playerLevel) + 400) - armorReduction = 100 * (armorReduction/(armorReduction + 1)) - - frame.tooltipSubtext = format(ARMOR_TOOLTIP, playerLevel, armorReduction) - - frame:SetScript("OnEnter", function() - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:SetText(this.tooltip) - GameTooltip:AddLine(this.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) - GameTooltip:Show() - end) - frame:SetScript("OnLeave", function() - GameTooltip:Hide() - end) - -end - -function BCS:SetDamage(statFrame) - local label = getglobal(statFrame:GetName() .. "Label") - label:SetText(TEXT(DAMAGE_COLON)) - local damageText = getglobal(statFrame:GetName() .. "StatText") - local damageFrame = statFrame - - damageFrame:SetScript("OnEnter", CharacterDamageFrame_OnEnter) - damageFrame:SetScript("OnLeave", function() - GameTooltip:Hide() - end) - - local speed, offhandSpeed = UnitAttackSpeed("player") - - local minDamage - local maxDamage - local minOffHandDamage - local maxOffHandDamage - local physicalBonusPos - local physicalBonusNeg - local percent - minDamage, maxDamage, minOffHandDamage, maxOffHandDamage, physicalBonusPos, physicalBonusNeg, percent = UnitDamage("player") - local displayMin = max(floor(minDamage),1) - local displayMax = max(ceil(maxDamage),1) - - minDamage = (minDamage / percent) - physicalBonusPos - physicalBonusNeg - maxDamage = (maxDamage / percent) - physicalBonusPos - physicalBonusNeg - - local baseDamage = (minDamage + maxDamage) * 0.5 - local fullDamage = (baseDamage + physicalBonusPos + physicalBonusNeg) * percent - local totalBonus = (fullDamage - baseDamage) - local damagePerSecond = (max(fullDamage,1) / speed) - local damageTooltip = max(floor(minDamage),1).." - "..max(ceil(maxDamage),1) - - local colorPos = "|cff20ff20" - local colorNeg = "|cffff2020" - if ( totalBonus == 0 ) then - if ( ( displayMin < 100 ) and ( displayMax < 100 ) ) then - damageText:SetText(displayMin.." - "..displayMax) - else - damageText:SetText(displayMin.."-"..displayMax) - end - else - - local color - if ( totalBonus > 0 ) then - color = colorPos - else - color = colorNeg - end - if ( ( displayMin < 100 ) and ( displayMax < 100 ) ) then - damageText:SetText(color..displayMin.." - "..displayMax.."|r") - else - damageText:SetText(color..displayMin.."-"..displayMax.."|r") - end - if ( physicalBonusPos > 0 ) then - damageTooltip = damageTooltip..colorPos.." +"..physicalBonusPos.."|r" - end - if ( physicalBonusNeg < 0 ) then - damageTooltip = damageTooltip..colorNeg.." "..physicalBonusNeg.."|r" - end - if ( percent > 1 ) then - damageTooltip = damageTooltip..colorPos.." x"..floor(percent*100+0.5).."%|r" - elseif ( percent < 1 ) then - damageTooltip = damageTooltip..colorNeg.." x"..floor(percent*100+0.5).."%|r" - end - - end - damageFrame.damage = damageTooltip - damageFrame.attackSpeed = speed - damageFrame.dps = damagePerSecond - - -- If there's an offhand speed then add the offhand info to the tooltip - if ( offhandSpeed ) then - minOffHandDamage = (minOffHandDamage / percent) - physicalBonusPos - physicalBonusNeg - maxOffHandDamage = (maxOffHandDamage / percent) - physicalBonusPos - physicalBonusNeg - - local offhandBaseDamage = (minOffHandDamage + maxOffHandDamage) * 0.5 - local offhandFullDamage = (offhandBaseDamage + physicalBonusPos + physicalBonusNeg) * percent - local offhandDamagePerSecond = (max(offhandFullDamage,1) / offhandSpeed) - local offhandDamageTooltip = max(floor(minOffHandDamage),1).." - "..max(ceil(maxOffHandDamage),1) - if ( physicalBonusPos > 0 ) then - offhandDamageTooltip = offhandDamageTooltip..colorPos.." +"..physicalBonusPos.."|r" - end - if ( physicalBonusNeg < 0 ) then - offhandDamageTooltip = offhandDamageTooltip..colorNeg.." "..physicalBonusNeg.."|r" - end - if ( percent > 1 ) then - offhandDamageTooltip = offhandDamageTooltip..colorPos.." x"..floor(percent*100+0.5).."%|r" - elseif ( percent < 1 ) then - offhandDamageTooltip = offhandDamageTooltip..colorNeg.." x"..floor(percent*100+0.5).."%|r" - end - damageFrame.offhandDamage = offhandDamageTooltip - damageFrame.offhandAttackSpeed = offhandSpeed - damageFrame.offhandDps = offhandDamagePerSecond - else - damageFrame.offhandAttackSpeed = nil - end - -end - -function BCS:SetAttackSpeed(statFrame) - local speed, offhandSpeed = UnitAttackSpeed("player") - speed = format("%.2f", speed) - if ( offhandSpeed ) then - offhandSpeed = format("%.2f", offhandSpeed) - end - local text - if ( offhandSpeed ) then - text = speed.." / "..offhandSpeed - else - text = speed - end - - local label = getglobal(statFrame:GetName() .. "Label") - local value = getglobal(statFrame:GetName() .. "StatText") - - label:SetText(TEXT(SPEED)..":") - value:SetText(text) - - --[[statFrame.tooltip = HIGHLIGHT_FONT_COLOR_CODE..format(PAPERDOLLFRAME_TOOLTIP_FORMAT, ATTACK_SPEED).." "..text..FONT_COLOR_CODE_CLOSE; - statFrame.tooltip2 = format(CR_HASTE_RATING_TOOLTIP, GetCombatRating(CR_HASTE_MELEE), GetCombatRatingBonus(CR_HASTE_MELEE));]] - - statFrame:Show() -end - -function BCS:SetAttackPower(statFrame) - local base, posBuff, negBuff = UnitAttackPower("player") - - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - label:SetText(TEXT(ATTACK_POWER_COLON)) - - PaperDollFormatStat(MELEE_ATTACK_POWER, base, posBuff, negBuff, frame, text) - frame.tooltipSubtext = format(MELEE_ATTACK_POWER_TOOLTIP, max((base+posBuff+negBuff), 0)/ATTACK_POWER_MAGIC_NUMBER) -end - -function BCS:SetSpellPower(statFrame, school) - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - local colorPos = "|cff20ff20" - local colorNeg = "|cffff2020" - - if school then - label:SetText(L["SPELL_SCHOOL_"..strupper(school)]) - local base = BCS:GetSpellPower() - local fromSchool = BCS:GetSpellPower(school) - local output = base + fromSchool - - if fromSchool > 0 then - output = colorPos .. output .. "|r" - end - - text:SetText(output) - else - local power, secondaryPower, secondaryName = BCS:GetSpellPower() - - label:SetText(L.SPELL_POWER_COLON) - text:SetText(power+secondaryPower) - - if secondaryPower > 0 then - frame.tooltip = format(L.SPELL_POWER_SECONDARY_TOOLTIP, (power+secondaryPower), power, secondaryPower, secondaryName) - - frame:SetScript("OnEnter", function() - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:SetText(this.tooltip) - GameTooltip:Show() - end) - frame:SetScript("OnLeave", function() - GameTooltip:Hide() - end) - end - - end -end - -function BCS:SetRating(statFrame, ratingType) - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - label:SetText(L.MELEE_HIT_RATING_COLON) - - local colorPos = "|cff20ff20" - local colorNeg = "|cffff2020" - - if ratingType == "MELEE" then - local rating = BCS:GetHitRating() - if BCS.MELEEHIT[BCS.playerClass] then - if rating < BCS.MELEEHIT[BCS.playerClass][1] then - rating = colorNeg .. rating .. "%|r" - elseif rating >= BCS.MELEEHIT[BCS.playerClass][2] then - rating = colorPos .. rating .. "%|r" - else - rating = rating .. "%" - end - else - rating = rating .. "%" - end - text:SetText(rating) - - frame.tooltip = L.MELEE_HIT_TOOLTIP - if L[BCS.playerClass .. "_MELEE_HIT_TOOLTIP"] then - frame.tooltipSubtext = L[BCS.playerClass .. "_MELEE_HIT_TOOLTIP"] - end - elseif ratingType == "RANGED" then - local rating = BCS:GetRangedHitRating() - if BCS.MELEEHIT[BCS.playerClass] then - if rating < BCS.MELEEHIT[BCS.playerClass][1] then - rating = colorNeg .. rating .. "%|r" - elseif rating >= BCS.MELEEHIT[BCS.playerClass][2] then - rating = colorPos .. rating .. "%|r" - else - rating = rating .. "%" - end - else - rating = rating .. "%" - end - text:SetText(rating) - - frame.tooltip = L.MELEE_HIT_TOOLTIP - if L[BCS.playerClass .. "_MELEE_HIT_TOOLTIP"] then - frame.tooltipSubtext = L[BCS.playerClass .. "_MELEE_HIT_TOOLTIP"] - end - elseif ratingType == "SPELL" then - local spell_hit, spell_hit_fire, spell_hit_frost, spell_hit_arcane, spell_hit_shadow = BCS:GetSpellHitRating() - --[[if BCS.SPELLHIT[BCS.playerClass] then - if spell_hit < BCS.SPELLHIT[BCS.playerClass][1] then - spell_hit = colorNeg .. spell_hit .. "%|r" - elseif spell_hit >= BCS.SPELLHIT[BCS.playerClass][2] then - spell_hit = colorPos .. spell_hit .. "%|r" - else - spell_hit = spell_hit .. "%" - end - else - spell_hit = spell_hit .. "%" - end]] - - if spell_hit_fire > 0 or spell_hit_frost > 0 or spell_hit_arcane > 0 or spell_hit_shadow > 0 then - -- got spell hit from talents - local spell_hit_other, spell_hit_other_type - - spell_hit_other = 0 - spell_hit_other_type = "" - - if spell_hit_fire > spell_hit_other then - spell_hit_other = spell_hit_fire - spell_hit_other_type = L.SPELL_SCHOOL_FIRE - end - if spell_hit_frost > spell_hit_other then - spell_hit_other = spell_hit_frost - spell_hit_other_type = L.SPELL_SCHOOL_FROST - end - if spell_hit_arcane > spell_hit_other then - spell_hit_other = spell_hit_arcane - spell_hit_other_type = L.SPELL_SCHOOL_ARCANE - end - if spell_hit_shadow > spell_hit_other then - spell_hit_other = spell_hit_shadow - spell_hit_other_type = L.SPELL_SCHOOL_SHADOW - end - - frame.tooltip = format(L.SPELL_HIT_SECONDARY_TOOLTIP, spell_hit+spell_hit_other, spell_hit, spell_hit_other, spell_hit_other_type) - text:SetText(spell_hit+spell_hit_other.."%") - else - frame.tooltip = L.SPELL_HIT_TOOLTIP - text:SetText(spell_hit.."%") - end - - -- class specific tooltip - if L[BCS.playerClass .. "_SPELL_HIT_TOOLTIP"] then - frame.tooltipSubtext = L[BCS.playerClass .. "_SPELL_HIT_TOOLTIP"] - end - end - - if frame.tooltip then - frame:SetScript("OnEnter", function() - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:SetText(this.tooltip) - GameTooltip:AddLine(this.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) - GameTooltip:Show() - end) - frame:SetScript("OnLeave", function() - GameTooltip:Hide() - end) - end - -end - -function BCS:SetMeleeCritChance(statFrame) - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - label:SetText(L.MELEE_CRIT_COLON) - text:SetText(format("%.2f%%", BCS:GetCritChance())) -end - -function BCS:SetSpellCritChance(statFrame) - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - label:SetText(L.SPELL_CRIT_COLON) - text:SetText(format("%.2f%%", BCS:GetSpellCritChance())) -end - -function BCS:SetRangedCritChance(statFrame) - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - label:SetText(L.RANGED_CRIT_COLON) - text:SetText(format("%.2f%%", BCS:GetRangedCritChance())) -end - -function BCS:SetHealing(statFrame) - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - local power,_,_,dmg = BCS:GetSpellPower() - local heal = BCS:GetHealingPower() - - power = power-dmg - - label:SetText(L.HEAL_POWER_COLON) - text:SetText(power+heal) - - frame.tooltip = format(L.SPELL_HEALING_POWER_TOOLTIP, (power+heal), power, heal) - - frame:SetScript("OnEnter", function() - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:SetText(this.tooltip) - GameTooltip:Show() - end) - frame:SetScript("OnLeave", function() - GameTooltip:Hide() - end) -end - -function BCS:SetManaRegen(statFrame) - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - local base, casting, mp5 = BCS:GetManaRegen() - local mp2 = mp5*0.4 - local totalRegen = base + mp2 - local totalRegenWhileCasting = (casting/100)*base + mp2 - - label:SetText(L.MANA_REGEN_COLON) - text:SetText(format("%d |cffBF40BF(%d)|r", totalRegen, totalRegenWhileCasting)) - - frame.tooltip = format(L.SPELL_MANA_REGEN_TOOLTIP, totalRegen, totalRegenWhileCasting, base, casting, mp5, mp2) - - frame:SetScript("OnEnter", function() - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:SetText(this.tooltip) - GameTooltip:Show() - end) - frame:SetScript("OnLeave", function() - GameTooltip:Hide() - end) -end - -function BCS:SetDodge(statFrame) - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - label:SetText(L.DODGE_COLON) - text:SetText(format("%.2f%%", GetDodgeChance())) -end - -function BCS:SetParry(statFrame) - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - label:SetText(L.PARRY_COLON) - text:SetText(format("%.2f%%", GetParryChance())) -end - -function BCS:SetBlock(statFrame) - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - label:SetText(L.BLOCK_COLON) - text:SetText(format("%.2f%%", GetBlockChance())) -end - -function BCS:SetDefense(statFrame) - local base, modifier = UnitDefense("player") - - local frame = statFrame - local label = getglobal(statFrame:GetName() .. "Label") - local text = getglobal(statFrame:GetName() .. "StatText") - - label:SetText(TEXT(DEFENSE_COLON)) - - local posBuff = 0 - local negBuff = 0 - if ( modifier > 0 ) then - posBuff = modifier - elseif ( modifier < 0 ) then - negBuff = modifier - end - PaperDollFormatStat(DEFENSE_COLON, base, posBuff, negBuff, frame, text) -end - -function BCS:SetRangedDamage(statFrame) - local label = getglobal(statFrame:GetName() .. "Label") - local damageText = getglobal(statFrame:GetName() .. "StatText") - local damageFrame = statFrame - - label:SetText(TEXT(DAMAGE_COLON)) - - damageFrame:SetScript("OnEnter", CharacterRangedDamageFrame_OnEnter) - damageFrame:SetScript("OnLeave", function() - GameTooltip:Hide() - end) - - -- If no ranged attack then set to n/a - if ( PaperDollFrame.noRanged ) then - damageText:SetText(NOT_APPLICABLE) - damageFrame.damage = nil - return - end - - local rangedAttackSpeed, minDamage, maxDamage, physicalBonusPos, physicalBonusNeg, percent = UnitRangedDamage("player") - local displayMin = max(floor(minDamage),1) - local displayMax = max(ceil(maxDamage),1) - - minDamage = (minDamage / percent) - physicalBonusPos - physicalBonusNeg - maxDamage = (maxDamage / percent) - physicalBonusPos - physicalBonusNeg - - local baseDamage = (minDamage + maxDamage) * 0.5 - local fullDamage = (baseDamage + physicalBonusPos + physicalBonusNeg) * percent - local totalBonus = (fullDamage - baseDamage) - local damagePerSecond = (max(fullDamage,1) / rangedAttackSpeed) - local tooltip = max(floor(minDamage),1).." - "..max(ceil(maxDamage),1) - - if ( totalBonus == 0 ) then - if ( ( displayMin < 100 ) and ( displayMax < 100 ) ) then - damageText:SetText(displayMin.." - "..displayMax) - else - damageText:SetText(displayMin.."-"..displayMax) - end - else - local colorPos = "|cff20ff20" - local colorNeg = "|cffff2020" - local color - if ( totalBonus > 0 ) then - color = colorPos - else - color = colorNeg - end - if ( ( displayMin < 100 ) and ( displayMax < 100 ) ) then - damageText:SetText(color..displayMin.." - "..displayMax.."|r") - else - damageText:SetText(color..displayMin.."-"..displayMax.."|r") - end - if ( physicalBonusPos > 0 ) then - tooltip = tooltip..colorPos.." +"..physicalBonusPos.."|r" - end - if ( physicalBonusNeg < 0 ) then - tooltip = tooltip..colorNeg.." "..physicalBonusNeg.."|r" - end - if ( percent > 1 ) then - tooltip = tooltip..colorPos.." x"..floor(percent*100+0.5).."%|r" - elseif ( percent < 1 ) then - tooltip = tooltip..colorNeg.." x"..floor(percent*100+0.5).."%|r" - end - damageFrame.tooltip = tooltip.." "..format(TEXT(DPS_TEMPLATE), damagePerSecond) - end - damageFrame.attackSpeed = rangedAttackSpeed - damageFrame.damage = tooltip - damageFrame.dps = damagePerSecond -end - -function BCS:SetRangedAttackSpeed(startFrame) - local label = getglobal(startFrame:GetName() .. "Label") - local damageText = getglobal(startFrame:GetName() .. "StatText") - local damageFrame = startFrame - - label:SetText(TEXT(SPEED)..":") - - -- If no ranged attack then set to n/a - if ( PaperDollFrame.noRanged ) then - damageText:SetText(NOT_APPLICABLE) - damageFrame.damage = nil - return - end - - local rangedAttackSpeed, minDamage, maxDamage, physicalBonusPos, physicalBonusNeg, percent = UnitRangedDamage("player") - local displayMin = max(floor(minDamage),1) - local displayMax = max(ceil(maxDamage),1) - - minDamage = (minDamage / percent) - physicalBonusPos - physicalBonusNeg - maxDamage = (maxDamage / percent) - physicalBonusPos - physicalBonusNeg - - local baseDamage = (minDamage + maxDamage) * 0.5 - local fullDamage = (baseDamage + physicalBonusPos + physicalBonusNeg) * percent - local totalBonus = (fullDamage - baseDamage) - local damagePerSecond = (max(fullDamage,1) / rangedAttackSpeed) - local tooltip = max(floor(minDamage),1).." - "..max(ceil(maxDamage),1) - - if ( totalBonus == 0 ) then - if ( ( displayMin < 100 ) and ( displayMax < 100 ) ) then - damageText:SetText(displayMin.." - "..displayMax) - else - damageText:SetText(displayMin.."-"..displayMax) - end - else - local colorPos = "|cff20ff20" - local colorNeg = "|cffff2020" - local color - if ( totalBonus > 0 ) then - color = colorPos - else - color = colorNeg - end - if ( ( displayMin < 100 ) and ( displayMax < 100 ) ) then - damageText:SetText(color..displayMin.." - "..displayMax.."|r") - else - damageText:SetText(color..displayMin.."-"..displayMax.."|r") - end - if ( physicalBonusPos > 0 ) then - tooltip = tooltip..colorPos.." +"..physicalBonusPos.."|r" - end - if ( physicalBonusNeg < 0 ) then - tooltip = tooltip..colorNeg.." "..physicalBonusNeg.."|r" - end - if ( percent > 1 ) then - tooltip = tooltip..colorPos.." x"..floor(percent*100+0.5).."%|r" - elseif ( percent < 1 ) then - tooltip = tooltip..colorNeg.." x"..floor(percent*100+0.5).."%|r" - end - damageFrame.tooltip = tooltip.." "..format(TEXT(DPS_TEMPLATE), damagePerSecond) - end - - damageText:SetText(format("%.2f", rangedAttackSpeed)) - - damageFrame.attackSpeed = rangedAttackSpeed - damageFrame.damage = tooltip - damageFrame.dps = damagePerSecond -end - -function BCS:SetRangedAttackPower(statFrame) - local frame = statFrame - local text = getglobal(statFrame:GetName() .. "StatText") - local label = getglobal(statFrame:GetName() .. "Label") - - label:SetText(TEXT(ATTACK_POWER_COLON)) - - -- If no ranged attack then set to n/a - if ( PaperDollFrame.noRanged ) then - text:SetText(NOT_APPLICABLE) - frame.tooltip = nil - return - end - if ( HasWandEquipped() ) then - text:SetText("--") - frame.tooltip = nil - return - end - - local base, posBuff, negBuff = UnitRangedAttackPower("player") - PaperDollFormatStat(RANGED_ATTACK_POWER, base, posBuff, negBuff, frame, text) - frame.tooltipSubtext = format(RANGED_ATTACK_POWER_TOOLTIP, base/ATTACK_POWER_MAGIC_NUMBER) -end - -function BCS:UpdatePaperdollStats(prefix, index) - local stat1 = getglobal(prefix..1) - local stat2 = getglobal(prefix..2) - local stat3 = getglobal(prefix..3) - local stat4 = getglobal(prefix..4) - local stat5 = getglobal(prefix..5) - local stat6 = getglobal(prefix..6) - - stat1:SetScript("OnEnter", nil) - stat2:SetScript("OnEnter", nil) - stat3:SetScript("OnEnter", nil) - stat4:SetScript("OnEnter", nil) - stat4:SetScript("OnEnter", nil) - stat5:SetScript("OnEnter", nil) - stat6:SetScript("OnEnter", nil) - - stat1.tooltip = nil - stat2.tooltip = nil - stat3.tooltip = nil - stat4.tooltip = nil - stat4.tooltip = nil - stat5.tooltip = nil - stat6.tooltip = nil - - stat4:Show() - stat5:Show() - stat6:Show() - - if ( index == "PLAYERSTAT_BASE_STATS" ) then - BCS:SetStat(stat1, 1) - BCS:SetStat(stat2, 2) - BCS:SetStat(stat3, 3) - BCS:SetStat(stat4, 4) - BCS:SetStat(stat5, 5) - BCS:SetArmor(stat6) - elseif ( index == "PLAYERSTAT_MELEE_COMBAT" ) then - BCS:SetDamage(stat1) - BCS:SetAttackSpeed(stat2) - BCS:SetAttackPower(stat3) - BCS:SetRating(stat4, "MELEE") - BCS:SetMeleeCritChance(stat5) - stat6:Hide() - elseif ( index == "PLAYERSTAT_RANGED_COMBAT" ) then - BCS:SetRangedDamage(stat1) - BCS:SetRangedAttackSpeed(stat2) - BCS:SetRangedAttackPower(stat3) - BCS:SetRating(stat4, "RANGED") - BCS:SetRangedCritChance(stat5) - stat6:Hide() - elseif ( index == "PLAYERSTAT_SPELL_COMBAT" ) then - BCS:SetSpellPower(stat1) - BCS:SetRating(stat2, "SPELL") - BCS:SetSpellCritChance(stat3) - BCS:SetHealing(stat4) - BCS:SetManaRegen(stat5) - stat6:Hide() - elseif ( index == "PLAYERSTAT_SPELL_SCHOOLS" ) then - BCS:SetSpellPower(stat1, "Arcane") - BCS:SetSpellPower(stat2, "Fire") - BCS:SetSpellPower(stat3, "Frost") - BCS:SetSpellPower(stat4, "Holy") - BCS:SetSpellPower(stat5, "Nature") - BCS:SetSpellPower(stat6, "Shadow") - elseif ( index == "PLAYERSTAT_DEFENSES" ) then - BCS:SetArmor(stat1) - BCS:SetDefense(stat2) - BCS:SetDodge(stat3) - BCS:SetParry(stat4) - BCS:SetBlock(stat5) - stat6:Hide() - end -end - -local function PlayerStatFrameLeftDropDown_OnClick() - UIDropDownMenu_SetSelectedValue(getglobal(this.owner), this.value) - IndexLeft = this.value - BCSConfig["DropdownLeft"] = IndexLeft - BCS:UpdatePaperdollStats("PlayerStatFrameLeft", this.value) -end - -local function PlayerStatFrameRightDropDown_OnClick() - UIDropDownMenu_SetSelectedValue(getglobal(this.owner), this.value) - IndexRight = this.value - BCSConfig["DropdownRight"] = IndexRight - BCS:UpdatePaperdollStats("PlayerStatFrameRight", this.value) -end - -local function PlayerStatFrameLeftDropDown_Initialize() - local info = {} - local checked = nil - for i=1, getn(BCS.PLAYERSTAT_DROPDOWN_OPTIONS) do - info.text = BCS.L[BCS.PLAYERSTAT_DROPDOWN_OPTIONS[i]] - info.func = PlayerStatFrameLeftDropDown_OnClick - info.value = BCS.PLAYERSTAT_DROPDOWN_OPTIONS[i] - info.checked = checked - info.owner = UIDROPDOWNMENU_OPEN_MENU - UIDropDownMenu_AddButton(info) - end -end - -local function PlayerStatFrameRightDropDown_Initialize() - local info = {} - local checked = nil - for i=1, getn(BCS.PLAYERSTAT_DROPDOWN_OPTIONS) do - info.text = BCS.L[BCS.PLAYERSTAT_DROPDOWN_OPTIONS[i]] - info.func = PlayerStatFrameRightDropDown_OnClick - info.value = BCS.PLAYERSTAT_DROPDOWN_OPTIONS[i] - info.checked = checked - info.owner = UIDROPDOWNMENU_OPEN_MENU - UIDropDownMenu_AddButton(info) - end -end - -function PlayerStatFrameLeftDropDown_OnLoad() - RaiseFrameLevel(this) - RaiseFrameLevel(getglobal(this:GetName() .. "Button")) - UIDropDownMenu_Initialize(this, PlayerStatFrameLeftDropDown_Initialize) - UIDropDownMenu_SetWidth(99, this) - UIDropDownMenu_JustifyText("LEFT") -end - -function PlayerStatFrameRightDropDown_OnLoad() - RaiseFrameLevel(this) - RaiseFrameLevel(getglobal(this:GetName() .. "Button")) - UIDropDownMenu_Initialize(this, PlayerStatFrameRightDropDown_Initialize) - UIDropDownMenu_SetWidth(99, this) - UIDropDownMenu_JustifyText("LEFT") -end - ---pfUI.api.strsplit -function hcstrsplit(delimiter, subject) - if not subject then return nil end - local delimiter, fields = delimiter or ":", {} - local pattern = string.format("([^%s]+)", delimiter) - string.gsub(subject, pattern, function(c) fields[table.getn(fields)+1] = c end) - return unpack(fields) -end - ---Update announcing code taken from pfUI -local major, minor, fix = hcstrsplit(".", tostring(GetAddOnMetadata("BetterCharacterStats", "Version"))) - -local alreadyshown = false -local localversion = tonumber(major*10000 + minor*100 + fix) -local remoteversion = tonumber(bcsupdateavailable) or 0 -local loginchannels = { "BATTLEGROUND", "RAID", "GUILD", "PARTY" } -local groupchannels = { "BATTLEGROUND", "RAID", "PARTY" } - -bcsupdater = CreateFrame("Frame") -bcsupdater:RegisterEvent("CHAT_MSG_ADDON") -bcsupdater:RegisterEvent("PLAYER_ENTERING_WORLD") -bcsupdater:RegisterEvent("PARTY_MEMBERS_CHANGED") -bcsupdater:SetScript("OnEvent", function() - if event == "CHAT_MSG_ADDON" and arg1 == "bcs" then - local v, remoteversion = hcstrsplit(":", arg2) - local remoteversion = tonumber(remoteversion) - if v == "VERSION" and remoteversion then - if remoteversion > localversion then - bcsupdateavailable = remoteversion - if not alreadyshown then - DEFAULT_CHAT_FRAME:AddMessage("|cffffffffBetterCharacterStats|r New version available! https://github.com/Lexiebean/BetterCharacterStats") - alreadyshown = true - end - end - end - --This is a little check that I can use to see if people are actually using the addon. - if v == "PING?" then - for _, chan in pairs(loginchannels) do - SendAddonMessage("bcs", "PONG!:"..GetAddOnMetadata("BetterCharacterStats", "Version"), chan) - end - end - if v == "PONG!" then - --print(arg1 .." "..arg2.." "..arg3.." "..arg4) - end - end - - if event == "PARTY_MEMBERS_CHANGED" then - local groupsize = GetNumRaidMembers() > 0 and GetNumRaidMembers() or GetNumPartyMembers() > 0 and GetNumPartyMembers() or 0 - if ( this.group or 0 ) < groupsize then - for _, chan in pairs(groupchannels) do - SendAddonMessage("bcs", "VERSION:" .. localversion, chan) - end - end - this.group = groupsize - end - - if event == "PLAYER_ENTERING_WORLD" then - if not alreadyshown and localversion < remoteversion then - DEFAULT_CHAT_FRAME:AddMessage("|cffffffffBetterCharacterStats|r New version available! https://github.com/Lexiebean/BetterCharacterStats") - bcsupdateavailable = localversion - alreadyshown = true - end - - for _, chan in pairs(loginchannels) do - SendAddonMessage("bcs", "VERSION:" .. localversion, chan) - end - end - end) \ No newline at end of file +BCS = BCS or {} +BCSConfig = BCSConfig or {} + +local L, IndexLeft, IndexRight +L = BCS.L + +local AceEvent = AceLibrary:HasInstance("AceEvent-2.0") and AceLibrary("AceEvent-2.0") + +-- Tree of Life aura bonus from other players, your own is calculated in GetHealingPower() +local aura = .0 + +BCS.PLAYERSTAT_DROPDOWN_OPTIONS = { + "PLAYERSTAT_BASE_STATS", + "PLAYERSTAT_MELEE_COMBAT", + "PLAYERSTAT_MELEE_BOSS", + "PLAYERSTAT_RANGED_COMBAT", + "PLAYERSTAT_SPELL_COMBAT", + "PLAYERSTAT_SPELL_SCHOOLS", + "PLAYERSTAT_DEFENSES", + "PLAYERSTAT_DEFENSES_BOSS", +} + +BCS.PaperDollFrame = PaperDollFrame + +BCS.Debug = false +BCS.DebugStack = {} + +function BCS:DebugTrace(start, limit) + BCS.Debug = nil + local length = getn(BCS.DebugStack) + if not start then + start = 1 + end + if start > length then + start = length + end + if not limit then + limit = start + 30 + end + + BCS:Print("length: " .. length) + BCS:Print("start: " .. start) + BCS:Print("limit: " .. limit) + + for i = start, length, 1 do + BCS:Print("[" .. i .. "] Event: " .. BCS.DebugStack[i].E) + BCS:Print(format( + "[%d] `- Arguments: %s, %s, %s, %s, %s", + i, + BCS.DebugStack[i].arg1, + BCS.DebugStack[i].arg2, + BCS.DebugStack[i].arg3, + BCS.DebugStack[i].arg4, + BCS.DebugStack[i].arg5 + )) + if i >= limit then + i = length + end + end + +end + +function BCS:Print(message) + ChatFrame2:AddMessage("[BCS] " .. message, 0.63, 0.86, 1.0) +end + +function BCS:OnLoad() + CharacterAttributesFrame:Hide() + PaperDollFrame:UnregisterEvent('UNIT_DAMAGE') + PaperDollFrame:UnregisterEvent('PLAYER_DAMAGE_DONE_MODS') + PaperDollFrame:UnregisterEvent('UNIT_ATTACK_SPEED') + PaperDollFrame:UnregisterEvent('UNIT_RANGEDDAMAGE') + PaperDollFrame:UnregisterEvent('UNIT_ATTACK') + PaperDollFrame:UnregisterEvent('UNIT_STATS') + PaperDollFrame:UnregisterEvent('UNIT_ATTACK_POWER') + PaperDollFrame:UnregisterEvent('UNIT_RANGED_ATTACK_POWER') + + self.Frame = BCSFrame + self.needUpdate = nil + + self.Frame:RegisterEvent("ADDON_LOADED") + self.Frame:RegisterEvent("CHARACTER_POINTS_CHANGED") -- fires when learning talent + self.Frame:RegisterEvent("PLAYER_AURAS_CHANGED") -- buffs/warrior stances + self.Frame:RegisterEvent("CHAT_MSG_SKILL") --gaining weapon skill + self.Frame:RegisterEvent("CHAT_MSG_ADDON") --needed to recieve aura bonuses from other people + AceEvent:RegisterBucketEvent("UNIT_INVENTORY_CHANGED", 0.3, function(args) + if args["player"] then + BCS.needScanGear = true + BCS.needScanSkills = true + if BCS.PaperDollFrame:IsVisible() then + BCS:UpdateStats() + else + BCS.needUpdate = true + end + end + end) +end + +local function PostHookFunction(original, hook) + return function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) + original(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) + hook(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) + end +end + +-- there is less space for player character model with this addon, zoom out and move it up slightly +local z, x, y = -0.2, 0, 0.1 +function BCS_PaperDollFrame_OnEvent(event, unit) + if (event == "PLAYER_ENTERING_WORLD") then + CharacterModelFrame:SetPosition(0, 0, 0) + CharacterModelFrame:SetUnit("player") + CharacterModelFrame:SetPosition(z, x, y) + return + end + if (unit and unit == "player") then + if (event == "UNIT_MODEL_CHANGED") then + CharacterModelFrame:SetPosition(0, 0, 0) + CharacterModelFrame:SetUnit("player") + CharacterModelFrame:SetPosition(z, x, y) + return + end + end +end + +function BCS_PaperDollFrame_OnShow() + CharacterModelFrame:SetPosition(0, 0, 0) + CharacterModelFrame:SetUnit("player") + CharacterModelFrame:SetPosition(z, x, y) +end + +PaperDollFrame_OnShow = PostHookFunction(PaperDollFrame_OnShow, BCS_PaperDollFrame_OnShow) +PaperDollFrame_OnEvent = PostHookFunction(PaperDollFrame_OnEvent, BCS_PaperDollFrame_OnEvent) + +local function strsplit(delimiter, subject) + if not subject then + return nil + end + local delimiter, fields = delimiter or ":", {} + local pattern = string.format("([^%s]+)", delimiter) + string.gsub(subject, pattern, function(c) + fields[table.getn(fields) + 1] = c + end) + return unpack(fields) +end + +-- Scan stuff depending on event, but make sure to scan everything when addon is loaded +function BCS:OnEvent() + --[[if BCS.Debug then + local t = { + E = event, + arg1 = arg1 or "nil", + arg2 = arg2 or "nil", + arg3 = arg3 or "nil", + arg4 = arg4 or "nil", + arg5 = arg5 or "nil", + } + tinsert(BCS.DebugStack, t) + end]] + if event == "CHAT_MSG_ADDON" and arg1 == "bcs" then + BCS.needScanAuras = true + local type, player, amount = strsplit(",", arg2) + if type and player and amount then + if player ~= UnitName("player") then + amount = tonumber(amount) + if type == "TREE" then + --BCS:Print("got tree response amount="..amount) + if amount >= aura then + aura = amount + if BCS.PaperDollFrame:IsVisible() then + BCS:UpdateStats() + else + BCS.needUpdate = true + end + end + end + end + end + elseif event == "PLAYER_AURAS_CHANGED" then + BCS.needScanAuras = true + if not BCS:GetPlayerAura("Tree of Life Aura") then + aura = 0 + end + if BCS.PaperDollFrame:IsVisible() then + BCS:UpdateStats() + else + BCS.needUpdate = true + end + elseif event == "CHARACTER_POINTS_CHANGED" then + BCS.needScanTalents = true + if BCS.PaperDollFrame:IsVisible() then + BCS:UpdateStats() + else + BCS.needUpdate = true + end + elseif event == "CHAT_MSG_SKILL" then + BCS.needScanSkills = true + if BCS.PaperDollFrame:IsVisible() then + BCS:UpdateStats() + else + BCS.needUpdate = true + end + elseif event == "ADDON_LOADED" and arg1 == "BetterCharacterStats" then + BCSFrame:UnregisterEvent("ADDON_LOADED") + + local _, race = UnitRace("player") + if race == "Gnome" then + y = 0 + elseif race == "Dwarf" then + y = 0.05 + elseif race == "Troll" then + y = 0.15 + end + + BCS.needScanGear = true + BCS.needScanTalents = true + BCS.needScanAuras = true + BCS.needScanSkills = true + + IndexLeft = BCSConfig["DropdownLeft"] or BCS.PLAYERSTAT_DROPDOWN_OPTIONS[1] + IndexRight = BCSConfig["DropdownRight"] or BCS.PLAYERSTAT_DROPDOWN_OPTIONS[2] + + UIDropDownMenu_SetSelectedValue(PlayerStatFrameLeftDropDown, IndexLeft) + UIDropDownMenu_SetSelectedValue(PlayerStatFrameRightDropDown, IndexRight) + end +end + +--sending messages +local sender = CreateFrame("Frame", "BCSsender") +sender:RegisterEvent("PLAYER_AURAS_CHANGED") +sender:RegisterEvent("CHAT_MSG_ADDON") +sender:SetScript("OnEvent", function() + if not (UnitInParty("player") or UnitInRaid("player")) then + return + end + if event then + local player = UnitName("player") + if event == "PLAYER_AURAS_CHANGED" then + if BCS:GetPlayerAura("Tree of Life Aura") then + SendAddonMessage("bcs", "TREE"..","..player, "PARTY") + --BCS:Print("sent tree request") + end + end + if event == "CHAT_MSG_ADDON" and arg1 == "bcs" then + local type, name, amount = strsplit(",", arg2) + if name ~= player then + local _, treebonus = BCS:GetHealingPower() + if not amount and type == "TREE" and treebonus then + SendAddonMessage("bcs", "TREE"..","..player..","..treebonus, "PARTY") + --BCS:Print("sent tree response, amount="..treebonus) + end + end + end + end +end) + +function BCS:OnShow() + if BCS.needUpdate then + BCS.needUpdate = nil + BCS:UpdateStats() + end +end + +-- debugging / profiling +--local avgV = {} +--local avg = 0 +function BCS:UpdateStats() + --[[if BCS.Debug then + local e = event or "nil" + BCS:Print("Update due to " .. e) + end + local beginTime = debugprofilestop()]] + + BCS:UpdatePaperdollStats("PlayerStatFrameLeft", IndexLeft) + BCS:UpdatePaperdollStats("PlayerStatFrameRight", IndexRight) + BCS.needScanGear = false + BCS.needScanTalents = false + BCS.needScanAuras = false + BCS.needScanSkills = false + --[[local timeUsed = debugprofilestop()-beginTime + table.insert(avgV, timeUsed) + avg = 0 + + for i,v in ipairs(avgV) do + avg = avg + v + end + avg = avg / getn(avgV) + + BCS:Print(format("Average: %d (%d results), Exact: %d", avg, getn(avgV), timeUsed))]] +end + +local function BCS_AddTooltip(statFrame, tooltipExtra) + if statFrame.tooltip then + statFrame:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText(this.tooltip) + GameTooltip:AddLine(this.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) + if tooltipExtra then + GameTooltip:AddLine(tooltipExtra) + end + GameTooltip:Show() + end) + statFrame:SetScript("OnLeave", function() + GameTooltip:Hide() + end) + end +end + +local function BCS_AddDamageTooltip(damageText, statFrame, speed, offhandSpeed, ranged) + local rangedAttackSpeed, minDamage, maxDamage, physicalBonusPos, physicalBonusNeg, percent + local minOffHandDamage, maxOffHandDamage + + if ranged then + rangedAttackSpeed, minDamage, maxDamage, physicalBonusPos, physicalBonusNeg, percent = UnitRangedDamage("player") + speed = rangedAttackSpeed + else + minDamage, maxDamage, minOffHandDamage, maxOffHandDamage, physicalBonusPos, physicalBonusNeg, percent = UnitDamage("player") + end + + local displayMin = max(floor(minDamage), 1) + local displayMax = max(ceil(maxDamage), 1) + + minDamage = (minDamage / percent) - physicalBonusPos - physicalBonusNeg + maxDamage = (maxDamage / percent) - physicalBonusPos - physicalBonusNeg + + local baseDamage = (minDamage + maxDamage) * 0.5 + local fullDamage = (baseDamage + physicalBonusPos + physicalBonusNeg) * percent + local totalBonus = (fullDamage - baseDamage) + local damagePerSecond = (max(fullDamage, 1) / speed) + local damageTooltip = max(floor(minDamage), 1) .. " - " .. max(ceil(maxDamage), 1) + local green = "|cff20ff20" + local red = "|cffff2020" + + if (totalBonus == 0) then + if ((displayMin < 100) and (displayMax < 100)) then + damageText:SetText(displayMin .. " - " .. displayMax) + else + damageText:SetText(displayMin .. "-" .. displayMax) + end + else + local color + if (totalBonus > 0) then + color = green + else + color = red + end + if ((displayMin < 100) and (displayMax < 100)) then + damageText:SetText(color .. displayMin .. " - " .. displayMax .. "|r") + else + damageText:SetText(color .. displayMin .. "-" .. displayMax .. "|r") + end + if (physicalBonusPos > 0) then + damageTooltip = damageTooltip .. green .. " +" .. physicalBonusPos .. "|r" + end + if (physicalBonusNeg < 0) then + damageTooltip = damageTooltip .. red .. " " .. physicalBonusNeg .. "|r" + end + if (percent > 1) then + damageTooltip = damageTooltip .. green .. " x" .. floor(percent * 100 + 0.5) .. "%|r" + elseif (percent < 1) then + damageTooltip = damageTooltip .. red .. " x" .. floor(percent * 100 + 0.5) .. "%|r" + end + end + statFrame.damage = damageTooltip + statFrame.attackSpeed = speed + statFrame.dps = damagePerSecond + + -- If there's an offhand speed then add the offhand info to the tooltip + if (offhandSpeed) then + minOffHandDamage = (minOffHandDamage / percent) - physicalBonusPos - physicalBonusNeg + maxOffHandDamage = (maxOffHandDamage / percent) - physicalBonusPos - physicalBonusNeg + + local offhandBaseDamage = (minOffHandDamage + maxOffHandDamage) * 0.5 + local offhandFullDamage = (offhandBaseDamage + physicalBonusPos + physicalBonusNeg) * percent + local offhandDamagePerSecond = (max(offhandFullDamage, 1) / offhandSpeed) + local offhandDamageTooltip = max(floor(minOffHandDamage), 1) .. " - " .. max(ceil(maxOffHandDamage), 1) + if (physicalBonusPos > 0) then + offhandDamageTooltip = offhandDamageTooltip .. green .. " +" .. physicalBonusPos .. "|r" + end + if (physicalBonusNeg < 0) then + offhandDamageTooltip = offhandDamageTooltip .. red .. " " .. physicalBonusNeg .. "|r" + end + if (percent > 1) then + offhandDamageTooltip = offhandDamageTooltip .. green .. " x" .. floor(percent * 100 + 0.5) .. "%|r" + elseif (percent < 1) then + offhandDamageTooltip = offhandDamageTooltip .. red .. " x" .. floor(percent * 100 + 0.5) .. "%|r" + end + statFrame.offhandDamage = offhandDamageTooltip + statFrame.offhandAttackSpeed = offhandSpeed + statFrame.offhandDps = offhandDamagePerSecond + else + statFrame.offhandAttackSpeed = nil + end + + if ranged then + statFrame:SetScript("OnEnter", CharacterRangedDamageFrame_OnEnter) + else + statFrame:SetScript("OnEnter", CharacterDamageFrame_OnEnter) + end + + statFrame:SetScript("OnLeave", function() + GameTooltip:Hide() + end) +end + +function BCS:SetStat(statFrame, statIndex) + local label = getglobal(statFrame:GetName() .. "Label") + local text = getglobal(statFrame:GetName() .. "StatText") + local statIndexTable = { + "STRENGTH", + "AGILITY", + "STAMINA", + "INTELLECT", + "SPIRIT", + } + + statFrame:SetScript("OnEnter", function() + PaperDollStatTooltip("player", statIndexTable[statIndex]) + end) + + statFrame:SetScript("OnLeave", function() + GameTooltip:Hide() + end) + + label:SetText(TEXT(getglobal("SPELL_STAT" .. (statIndex - 1) .. "_NAME")) .. ":") + local stat, effectiveStat, posBuff, negBuff = UnitStat("player", statIndex) + + -- Set the tooltip text + local tooltipText = HIGHLIGHT_FONT_COLOR_CODE .. getglobal("SPELL_STAT" .. (statIndex - 1) .. "_NAME") .. " " + + if ((posBuff == 0) and (negBuff == 0)) then + text:SetText(effectiveStat) + statFrame.tooltip = tooltipText .. effectiveStat .. FONT_COLOR_CODE_CLOSE + else + tooltipText = tooltipText .. effectiveStat + if (posBuff > 0 or negBuff < 0) then + tooltipText = tooltipText .. " (" .. (stat - posBuff - negBuff) .. FONT_COLOR_CODE_CLOSE + end + if (posBuff > 0) then + tooltipText = tooltipText .. FONT_COLOR_CODE_CLOSE .. GREEN_FONT_COLOR_CODE .. "+" .. posBuff .. FONT_COLOR_CODE_CLOSE + end + if (negBuff < 0) then + tooltipText = tooltipText .. RED_FONT_COLOR_CODE .. " " .. negBuff .. FONT_COLOR_CODE_CLOSE + end + if (posBuff > 0 or negBuff < 0) then + tooltipText = tooltipText .. HIGHLIGHT_FONT_COLOR_CODE .. ")" .. FONT_COLOR_CODE_CLOSE + end + statFrame.tooltip = tooltipText + + -- If there are any negative buffs then show the main number in red even if there are + -- positive buffs. Otherwise show in green. + if (negBuff < 0) then + text:SetText(RED_FONT_COLOR_CODE .. effectiveStat .. FONT_COLOR_CODE_CLOSE) + else + text:SetText(GREEN_FONT_COLOR_CODE .. effectiveStat .. FONT_COLOR_CODE_CLOSE) + end + end +end + +function BCS:SetArmor(statFrame) + local base, effectiveArmor, armor, posBuff, negBuff = UnitArmor("player") + local label = getglobal(statFrame:GetName() .. "Label") + local text = getglobal(statFrame:GetName() .. "StatText") + + PaperDollFormatStat(ARMOR, base, posBuff, negBuff, statFrame, text) + label:SetText(TEXT(ARMOR_COLON)) + + local playerLevel = UnitLevel("player") + local armorReduction = effectiveArmor / ((85 * playerLevel) + 400) + armorReduction = 100 * (armorReduction / (armorReduction + 1)) + + statFrame.tooltipSubtext = format(ARMOR_TOOLTIP, playerLevel, armorReduction) + + BCS_AddTooltip(statFrame) +end + +function BCS:GetMissChanceRaw(wepSkill) + local _, ver = pcall(GetBuildInfo) + local diff = wepSkill - 315 + local miss = 5 + + if ver == "1.17.2" then + miss = miss - (diff * 0.2) - BCS:GetHitRating() + else + if diff < -10 then + miss = miss - diff * 0.2; + else + miss = miss - diff * 0.1; + end + + local hitChance = BCS:GetHitRating() + -- if skill diff < -10 then subtract one from +hit, if there is any +hit + if (diff < -10) and (hitChance > 0) then + hitChance = hitChance - 1 + end + miss = miss - hitChance + end + return miss +end + +function BCS:GetMissChance(wepSkill) + return max(0, min(BCS:GetMissChanceRaw(wepSkill), 60)) +end + +function BCS:GetDualWieldMissChance(wepSkill) + return max(0, min(BCS:GetMissChanceRaw(wepSkill) + 19, 60)) +end + +function BCS:GetGlanceChance(wepSkill) + return 10 + 15 * 2; +end + +function BCS:GetGlanceReduction(wepSkill) + local _, ver = pcall(GetBuildInfo) + if ver == "1.17.2" then + return 65 + (wepSkill - 300) * 2 + else + local diff = 315 - wepSkill; + local low = math.max(math.min(1.3 - 0.05 * diff, 0.91), 0.01); + local high = math.max(math.min(1.2 - 0.03 * diff, 0.99), 0.2); + return 100 * ((high - low) / 2 + low); + end +end + +function BCS:GetDodgeChance(wepSkill) + return math.max(5 + (315 - wepSkill) * 0.1, 0); +end + +function BCS:GetDualWieldCritCap(wepSkill) + local cap = 100 - self:GetDualWieldMissChance(wepSkill) - self:GetGlanceChance(wepSkill) - self:GetDodgeChance(wepSkill); + if (cap > 100) then + cap = 100; + end + if (cap < 0) then + cap = 0; + end + return cap; +end + +function BCS:GetCritCap(wepSkill) + local cap = 100 - self:GetMissChance(wepSkill) - self:GetGlanceChance(wepSkill) - self:GetDodgeChance(wepSkill); + if (cap > 100) then + cap = 100; + end + if (cap < 0) then + cap = 0; + end + return cap; +end + +function BCS:GetEffectiveBlockChance(leveldiff) + local block = GetBlockChance() - ((5 * leveldiff) * 0.04) + if block < 0 then + block = 0 + end + return block +end + +function BCS:GetEffectiveParryChance(leveldiff) + local parry = GetParryChance() - ((5 * leveldiff) * 0.04) + if parry < 0 then + parry = 0 + end + return parry +end + +function BCS:GetEffectiveDodgeChance(leveldiff) + local dodge = GetDodgeChance() - ((5 * leveldiff) * 0.04) + if dodge < 0 then + dodge = 0 + end + return dodge +end + +function BCS:SetDamage(statFrame) + local damageText = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + local speed, offhandSpeed = UnitAttackSpeed("player") + + BCS_AddDamageTooltip(damageText, statFrame, speed, offhandSpeed) + + label:SetText(TEXT(DAMAGE_COLON)) +end + +function BCS:SetAttackSpeed(statFrame) + local damageText = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + local speed, offhandSpeed = UnitAttackSpeed("player") + + speed = format("%.2f", speed) + if (offhandSpeed) then + offhandSpeed = format("%.2f", offhandSpeed) + end + local text + if (offhandSpeed) then + text = speed .. " | " .. offhandSpeed + else + text = speed + end + + BCS_AddDamageTooltip(damageText, statFrame, speed, offhandSpeed) + + label:SetText(TEXT(SPEED) .. ":") + damageText:SetText(text) +end + +function BCS:SetAttackPower(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + local tooltipText = HIGHLIGHT_FONT_COLOR_CODE .. MELEE_ATTACK_POWER .. " " + local base, posBuff, negBuff = UnitAttackPower("player") + local effectiveStat = base + posBuff + negBuff + + if ((posBuff == 0) and (negBuff == 0)) then + text:SetText(effectiveStat) + statFrame.tooltip = tooltipText .. base .. FONT_COLOR_CODE_CLOSE + else + tooltipText = tooltipText .. effectiveStat + if (posBuff > 0 or negBuff < 0) then + tooltipText = tooltipText .. " (" .. (base - posBuff - negBuff) .. FONT_COLOR_CODE_CLOSE + end + if (posBuff > 0) then + tooltipText = tooltipText .. FONT_COLOR_CODE_CLOSE .. GREEN_FONT_COLOR_CODE .. "+" .. posBuff .. FONT_COLOR_CODE_CLOSE + end + if (negBuff < 0) then + tooltipText = tooltipText .. RED_FONT_COLOR_CODE .. " " .. negBuff .. FONT_COLOR_CODE_CLOSE + end + if (posBuff > 0 or negBuff < 0) then + tooltipText = tooltipText .. HIGHLIGHT_FONT_COLOR_CODE .. ")" .. FONT_COLOR_CODE_CLOSE + end + statFrame.tooltip = tooltipText + + if (negBuff < 0) then + text:SetText(RED_FONT_COLOR_CODE .. effectiveStat .. FONT_COLOR_CODE_CLOSE) + else + text:SetText(GREEN_FONT_COLOR_CODE .. effectiveStat .. FONT_COLOR_CODE_CLOSE) + end + end + + label:SetText(TEXT(ATTACK_POWER_COLON)) + PaperDollFormatStat(MELEE_ATTACK_POWER, base, posBuff, negBuff, statFrame, text) + statFrame.tooltipSubtext = format(MELEE_ATTACK_POWER_TOOLTIP, max((base + posBuff + negBuff), 0) / ATTACK_POWER_MAGIC_NUMBER) + + BCS_AddTooltip(statFrame) +end + +function BCS:SetSpellPower(statFrame, school) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + local green = "|cff20ff20" + + if school then + local base, _, _, dmgOnly = BCS:GetSpellPower() + local fromSchool = BCS:GetSpellPower(school) + base = base + dmgOnly + local output = base + fromSchool + + if fromSchool > 0 then + output = green .. output .. "|r" + end + + label:SetText(L["SPELL_SCHOOL_" .. strupper(school)]) + text:SetText(output) + + if fromSchool > 0 then + statFrame.tooltip = format(L.SPELL_SCHOOL_SECONDARY_TOOLTIP, school, base + fromSchool, base, fromSchool) + else + statFrame.tooltip = format(L.SPELL_SCHOOL_TOOLTIP, school, base) + end + statFrame.tooltipSubtext = format(L.SPELL_SCHOOL_TOOLTIP_SUB, strlower(school)) + else + local damageAndHealing, secondaryPower, secondaryName, damageOnly = BCS:GetSpellPower() + local total = damageAndHealing + damageOnly + + label:SetText(L.SPELL_POWER_COLON) + if secondaryPower > 0 then + text:SetText(green .. total + secondaryPower) + else + text:SetText(total + secondaryPower) + end + + if secondaryPower ~= 0 then + statFrame.tooltip = format(L.SPELL_POWER_SECONDARY_TOOLTIP, (total + secondaryPower), total, secondaryPower, secondaryName) + statFrame.tooltipSubtext = format(L.SPELL_POWER_SECONDARY_TOOLTIP_SUB) + else + statFrame.tooltip = format(L.SPELL_POWER_TOOLTIP, total) + statFrame.tooltipSubtext = format(L.SPELL_POWER_TOOLTIP_SUB) + end + end + + BCS_AddTooltip(statFrame) +end + +function BCS:SetHitRating(statFrame, ratingType) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + local _, class = UnitClass("player") + label:SetText(L.MELEE_HIT_RATING_COLON) + + if ratingType == "MELEE" then + local rating = BCS:GetHitRating() + rating = rating .. "%" + text:SetText(rating) + + statFrame.tooltip = (L.MELEE_HIT_TOOLTIP) + statFrame.tooltipSubtext = format(L.MELEE_HIT_TOOLTIP_SUB) + + elseif ratingType == "RANGED" then + -- If no ranged attack then set to n/a + if UnitHasRelicSlot("player") or not (GetInventoryItemLink("player", 18)) then + text:SetText(NOT_APPLICABLE) + return + end + + local rating = BCS:GetRangedHitRating() + rating = rating .. "%" + text:SetText(rating) + + statFrame.tooltip = (L.RANGED_HIT_TOOLTIP) + statFrame.tooltipSubtext = format(L.RANGED_HIT_TOOLTIP_SUB) + + elseif ratingType == "SPELL" then + local spell_hit, spell_hit_fire, spell_hit_frost, spell_hit_arcane, spell_hit_shadow, spell_hit_holy = BCS:GetSpellHitRating() + + text:SetText(spell_hit .. "%") + + statFrame.tooltip = format(L.SPELL_HIT_TOOLTIP) + statFrame.tooltipSubtext = format(L.SPELL_HIT_TOOLTIP_SUB) + + if statFrame.tooltip then + statFrame:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText(this.tooltip) + GameTooltip:AddLine(this.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) + + if spell_hit_fire > 0 then + GameTooltip:AddLine(format(L.SPELL_SCHOOL_FIRE .. " spells: %.f%%", spell_hit + spell_hit_fire)) + end + if spell_hit_frost > 0 then + GameTooltip:AddLine(format(L.SPELL_SCHOOL_FROST .. " spells: %.f%%", spell_hit + spell_hit_frost)) + end + if spell_hit_arcane > 0 then + GameTooltip:AddLine(format(L.SPELL_SCHOOL_ARCANE .. " spells: %.f%%", spell_hit + spell_hit_arcane)) + end + if spell_hit_shadow > 0 then + if class == "WARLOCK" then + GameTooltip:AddLine(format("Affliction spells: %.f%%", spell_hit + spell_hit_shadow)) + else + GameTooltip:AddLine(format(L.SPELL_SCHOOL_SHADOW .. " spells: %.f%%", spell_hit + spell_hit_shadow)) + end + end + if spell_hit_holy > 0 then + GameTooltip:AddLine(format(L.SPELL_SCHOOL_HOLY .. " and Discipline spells: %.f%%", spell_hit + spell_hit_holy)) + end + + GameTooltip:Show() + end) + + statFrame:SetScript("OnLeave", function() + GameTooltip:Hide() + end) + end + end + + if ratingType ~= "SPELL" then + BCS_AddTooltip(statFrame) + end +end + +function BCS:SetMeleeCritChance(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + + label:SetText(L.MELEE_CRIT_COLON) + text:SetText(format("%.2f%%", BCS:GetCritChance())) + + statFrame.tooltip = (L.MELEE_CRIT_TOOLTIP) + statFrame.tooltipSubtext = (L.MELEE_CRIT_TOOLTIP_SUB) + + BCS_AddTooltip(statFrame) +end + +function BCS:SetWeaponSkill(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + + label:SetText(L.WEAPON_SKILL_COLON) + + if OffhandHasWeapon() == 1 then + text:SetText(format("%d | %d", BCS:GetMHWeaponSkill(), BCS:GetOHWeaponSkill())) + else + text:SetText(format("%d", BCS:GetMHWeaponSkill())) + end + + statFrame.tooltip = format(L.MELEE_WEAPON_SKILL_TOOLTIP) + statFrame.tooltipSubtext = format(L.MELEE_WEAPON_SKILL_TOOLTIP_SUB) + + BCS_AddTooltip(statFrame) +end + +function BCS:SetRangedWeaponSkill(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + + label:SetText(L.WEAPON_SKILL_COLON) + + -- If no ranged attack then set to n/a + if UnitHasRelicSlot("player") or not (GetInventoryItemLink("player", 18)) then + text:SetText(NOT_APPLICABLE) + return + end + + text:SetText(format("%d", BCS:GetRangedWeaponSkill())) + + statFrame.tooltip = format(L.RANGED_WEAPON_SKILL_TOOLTIP) + statFrame.tooltipSubtext = format(L.RANGED_WEAPON_SKILL_TOOLTIP_SUB) + BCS_AddTooltip(statFrame) +end + +function BCS:SetBossMissChance(statFrame) + local frame = statFrame + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + label:SetText(L.MISS_CHANCE_COLON) + + local mh_miss = BCS:GetMissChance(BCS:GetMHWeaponSkill()) + + if OffhandHasWeapon() == 1 then + text:SetText(format("%.1f%% | %.1f%%", + BCS:GetDualWieldMissChance(BCS:GetMHWeaponSkill()), + BCS:GetDualWieldMissChance(BCS:GetOHWeaponSkill()))) + else + text:SetText(format("%.1f%%", mh_miss)) + end + + statFrame.tooltip = format(L.MELEE_MISS_VS_BOSS_TOOLTIP) + statFrame.tooltipSubtext = format(L.MELEE_MISS_VS_BOSS_TOOLTIP_SUB) + BCS_AddTooltip(statFrame) +end + +function BCS:SetBossGlanceReduction(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + label:SetText(L.GLANCE_REDUCTION_COLON) + + if OffhandHasWeapon() == 1 then + text:SetText(format("%d%% | %d%%", + BCS:GetGlanceReduction(BCS:GetMHWeaponSkill()), + BCS:GetGlanceReduction(BCS:GetOHWeaponSkill()))) + else + text:SetText(format("%d%%", BCS:GetGlanceReduction(BCS:GetMHWeaponSkill()))) + end + + statFrame.tooltip = format(L.MELEE_GLANCE_VS_BOSS_TOOLTIP) + statFrame.tooltipSubtext = format(L.MELEE_GLANCE_VS_BOSS_TOOLTIP_SUB) + BCS_AddTooltip(statFrame) +end + +function BCS:SetBossDodgeChance(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + label:SetText(L.DODGE_CHANCE_COLON) + if OffhandHasWeapon() == 1 then + text:SetText(format("%.1f%% | %.1f%%", + BCS:GetDodgeChance(BCS:GetMHWeaponSkill()), + BCS:GetDodgeChance(BCS:GetOHWeaponSkill()))) + else + text:SetText(format("%.1f%%", BCS:GetDodgeChance(BCS:GetMHWeaponSkill()))) + end + + statFrame.tooltip = format(L.MELEE_DODGE_VS_BOSS_TOOLTIP) + statFrame.tooltipSubtext = format(L.MELEE_DODGE_VS_BOSS_TOOLTIP_SUB) + BCS_AddTooltip(statFrame) +end + +function BCS:SetBossCritCap(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + label:SetText(L.CRIT_CAP_COLON) + + if OffhandHasWeapon() == 1 then + text:SetText(format("%.1f%% | %.1f%%", + BCS:GetDualWieldCritCap(BCS:GetMHWeaponSkill()), + BCS:GetDualWieldCritCap(BCS:GetOHWeaponSkill()))) + else + text:SetText(format("%.1f%%", BCS:GetCritCap(BCS:GetMHWeaponSkill()))) + end + + BCS_AddTooltip(statFrame) +end + +function BCS:SetEffectiveBossCrit(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + label:SetText(L.BOSS_CRIT_COLON) + + local critChance = BCS:GetCritChance() - 3 -- 3 % crit reduction vs lvl 63 + if OffhandHasWeapon() == 1 then + text:SetText(format("%.1f%% | %.1f%%", + math.min(critChance, BCS:GetDualWieldCritCap(BCS:GetMHWeaponSkill())), + math.min(critChance, BCS:GetDualWieldCritCap(BCS:GetOHWeaponSkill())) + )) + else + text:SetText(format("%.1f%%", math.min(critChance, BCS:GetCritCap(BCS:GetMHWeaponSkill())))) + end + + statFrame.tooltip = format(L.MELEE_EFF_CRIT_VS_BOSS_TOOLTIP) + statFrame.tooltipSubtext = format(L.MELEE_EFF_CRIT_VS_BOSS_TOOLTIP_SUB) + BCS_AddTooltip(statFrame) +end + +function BCS:SetSpellCritChance(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + local _, class = UnitClass("player") + + label:SetText(L.SPELL_CRIT_COLON) + + local generic = BCS:GetSpellCritChance() + local spell1, spell2, spell3, spell4, spell5, spell6 = BCS:GetSpellCritFromClass(class) + local total1 = generic + spell1 + local total2 = generic + spell2 + local total3 = generic + spell3 + local total4 = generic + spell4 + local total5 = generic + spell5 + local total6 = generic + spell6 + + if total1 > 100 then + total1 = 100 + end + if total2 > 100 then + total2 = 100 + end + if total3 > 100 then + total3 = 100 + end + if total4 > 100 then + total4 = 100 + end + if total5 > 100 then + total5 = 100 + end + if total6 > 100 then + total6 = 100 + end + + text:SetText(format("%.2f%%", generic)) + + -- warlock spells that can crit are all destruction so just add this to generic + if class == "WARLOCK" and spell1 > 0 then + text:SetText(format("%.2f%%", generic + spell1)) + + -- if priest have both talents add lowest to generic cos there will be no more spells left that can crit + elseif class == "PRIEST" and spell3 > 0 and spell2 > 0 then + if spell2 < spell3 then + text:SetText(format("%.2f%%", generic + spell2)) + elseif spell2 >= spell3 then + text:SetText(format("%.2f%%", generic + spell3)) + end + end + + statFrame.tooltip = format(L.SPELL_CRIT_TOOLTIP) + statFrame.tooltipSubtext = format(L.SPELL_CRIT_TOOLTIP_SUB) + + statFrame:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText(this.tooltip) + GameTooltip:AddLine(this.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) + + if class == "DRUID" then + if spell1 > 0 then + GameTooltip:AddLine(format("Moonfire: %.2f%%", total1)) + end + if spell2 > 0 then + GameTooltip:AddLine(format("Regrowth: %.2f%%", total2)) + end + + elseif class == "PALADIN" then + if spell1 > 0 then + GameTooltip:AddLine(format("Holy Light: %.2f%%", total1)) + end + if spell2 > 0 then + GameTooltip:AddLine(format("Flash of Light: %.2f%%", total2)) + end + if spell3 > 0 then + GameTooltip:AddLine(format("Holy Shock: %.2f%%", total3)) + end + + elseif class == "WARLOCK" then + if spell2 > 0 and spell2 ~= spell1 then + GameTooltip:AddLine(format("Searing Pain: %.2f%%", total2)) + end + + elseif class == "PRIEST" then + -- all healing spells are holy, change tooltip if player have both talents + if spell1 > 0 then + if spell3 > 0 then + GameTooltip:AddLine(format("Healing spells: %.2f%%", total1)) + end + GameTooltip:AddLine(format("Holy spells: %.2f%%", total1 + spell3)) + end + if spell2 > 0 then + GameTooltip:AddLine(format("Discipline spells: %.2f%%", total2 + spell3)) + end + if spell3 > 0 then + if spell2 > 0 then + GameTooltip:AddLine(format("Shadow spells: %.2f%%", total3)) + else + GameTooltip:AddLine(format("Offensive spells: %.2f%%", total3)) + end + GameTooltip:AddLine(format("Smite: %.2f%%", total4)) + end + if spell4 > 0 then + GameTooltip:AddLine(format("Prayer of Healing: %.2f%%", total4 + spell1)) + end + + elseif class == "MAGE" then + -- dont show specific spells if they have same chance as fire spells + if spell1 > 0 then + GameTooltip:AddLine(format("Arcane spells: %.2f%%", total1)) + end + if spell2 > 0 then + GameTooltip:AddLine(format("Fire spells: %.2f%%", total2)) + end + if spell3 > 0 and spell3 ~= spell2 then + GameTooltip:AddLine(format("Fire Blast: %.2f%%", total3)) + end + if spell4 > 0 and spell4 ~= spell2 then + GameTooltip:AddLine(format("Scorch: %.2f%%", total4)) + end + if spell5 > 0 and spell5 ~= spell2 then + GameTooltip:AddLine(format("Flamestrike: %.2f%%", total5)) + end + if spell6 > 0 then + GameTooltip:AddLine(format("Frozen targets: %.2f%%", total6)) + end + + elseif class == "SHAMAN" then + if spell1 > 0 then + GameTooltip:AddLine(format("Lightning Bolt: %.2f%%", total1)) + end + if spell2 > 0 then + GameTooltip:AddLine(format("Chain Lightning: %.2f%%", total2)) + end + if spell3 > 0 then + GameTooltip:AddLine(format("Lightning Shield: %.2f%%", total3)) + end + if spell4 > 0 then + GameTooltip:AddLine(format("Fire and Frost spells: %.2f%%", total4)) + end + if spell5 > 0 then + GameTooltip:AddLine(format("Healing spells: %.2f%%", total5)) + end + end + + GameTooltip:Show() + end) + + statFrame:SetScript("OnLeave", function() + GameTooltip:Hide() + end) +end + +function BCS:SetRangedCritChance(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + + label:SetText(L.RANGED_CRIT_COLON) + + -- If no ranged attack then set to n/a + if UnitHasRelicSlot("player") or not (GetInventoryItemLink("player", 18)) then + text:SetText(NOT_APPLICABLE) + return + end + + local crit = BCS:GetRangedCritChance() + -- apply skill difference modifier + local skill = BCS:GetRangedWeaponSkill() + local level = UnitLevel("player") + local skillDiff = skill - (level * 5) + + if (skill >= (level * 5)) then + crit = crit + (skillDiff * 0.04) + else + crit = crit + (skillDiff * 0.2) + end + + if crit < 0 then + crit = 0 + end + + text:SetText(format("%.2f%%", crit)) + + statFrame.tooltip = (L.RANGED_CRIT_TOOLTIP) + statFrame.tooltipSubtext = (L.RANGED_CRIT_TOOLTIP_SUB) + + BCS_AddTooltip(statFrame) +end + +function BCS:SetHealing(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + local damageAndHealing = BCS:GetSpellPower() + local healingOnly, treebonus, ironclad = BCS:GetHealingPower() + local total = damageAndHealing + healingOnly + local tooltipExtra + + if ironclad > 0 then + tooltipExtra = format("Healing power from Ironclad: %d", ironclad) + end + + if treebonus and aura <= treebonus then + total = total + treebonus + elseif (not treebonus and aura > 0) or (treebonus and aura > treebonus) then + total = total + aura + end + + label:SetText(L.HEAL_POWER_COLON) + text:SetText(format("%d", total)) + + if healingOnly ~= 0 then + statFrame.tooltip = format(L.SPELL_HEALING_POWER_SECONDARY_TOOLTIP, (total), damageAndHealing, healingOnly) + else + statFrame.tooltip = format(L.SPELL_HEALING_POWER_TOOLTIP, (total)) + end + statFrame.tooltipSubtext = format(L.SPELL_HEALING_POWER_TOOLTIP_SUB) + + BCS_AddTooltip(statFrame, tooltipExtra) +end + +function BCS:SetManaRegen(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + + label:SetText(L.MANA_REGEN_COLON) + + -- if not a mana user and not a druid set to N/A + local _, class = UnitClass("player") + if (UnitPowerType("player") ~= 0 and not (class == "DRUID")) then + text:SetText(NOT_APPLICABLE) + statFrame.tooltip = nil + return + end + + local base, casting, mp5 = BCS:GetManaRegen() + local mp2 = mp5 * 0.4 + local totalRegen = base + mp2 + local totalRegenWhileCasting = (casting / 100) * base + mp2 + + text:SetText(format("%d (%d)", totalRegen, totalRegenWhileCasting)) + + statFrame.tooltip = format(L.SPELL_MANA_REGEN_TOOLTIP, totalRegen, totalRegenWhileCasting) + statFrame.tooltipSubtext = format(L.SPELL_MANA_REGEN_TOOLTIP_SUB, base, casting, mp5, mp2) + + BCS_AddTooltip(statFrame) +end + +function BCS:SetDodge(statFrame, leveldiff) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + local dodge = BCS:GetEffectiveDodgeChance(leveldiff) + + label:SetText(L.DODGE_COLON) + text:SetText(format("%.2f%%", dodge)) + + statFrame.tooltip = format(L.PLAYER_DODGE_TOOLTIP) + statFrame.tooltipSubtext = format(L.PLAYER_DODGE_TOOLTIP_SUB) + + BCS_AddTooltip(statFrame) +end + +function BCS:SetParry(statFrame, leveldiff) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + local parry = BCS:GetEffectiveParryChance(leveldiff) + + label:SetText(L.PARRY_COLON) + text:SetText(format("%.2f%%", parry)) + + statFrame.tooltip = format(L.PLAYER_PARRY_TOOLTIP) + statFrame.tooltipSubtext = format(L.PLAYER_PARRY_TOOLTIP_SUB) + + BCS_AddTooltip(statFrame) +end + +function BCS:SetBlock(statFrame, leveldiff) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + local block = BCS:GetEffectiveBlockChance(leveldiff) + local tooltipExtra + + if block > 0 then + tooltipExtra = "Block Value: " .. BCS:GetBlockValue() + end + + label:SetText(L.BLOCK_COLON) + text:SetText(format("%.2f%%", block)) + + statFrame.tooltip = format(L.PLAYER_BLOCK_TOOLTIP) + statFrame.tooltipSubtext = format(L.PLAYER_BLOCK_TOOLTIP_SUB) + + BCS_AddTooltip(statFrame, tooltipExtra) +end + +function BCS:SetTotalAvoidance(statFrame, leveldiff) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + -- apply skill modifier + local base, mod = UnitDefense("player") + local skillDiff = (base + mod) - ((300) + (leveldiff * 5)) + local missChance = 5 + (skillDiff * 0.04) + + local block = BCS:GetEffectiveBlockChance(leveldiff) + local parry = BCS:GetEffectiveParryChance(leveldiff) + local dodge = BCS:GetEffectiveDodgeChance(leveldiff) + + local total = missChance + (block + parry + dodge) + + if total < 0 then + total = 0 + end + + label:SetText(L.TOTAL_COLON) + text:SetText(format("%.2f%%", total)) + + statFrame.tooltip = format(L.TOTAL_AVOIDANCE_TOOLTIP) + statFrame.tooltipSubtext = format(L.TOTAL_AVOIDANCE_TOOLTIP_SUB) + + BCS_AddTooltip(statFrame) +end + +function BCS:SetDefense(statFrame) + local base, modifier = UnitDefense("player") + local label = getglobal(statFrame:GetName() .. "Label") + local text = getglobal(statFrame:GetName() .. "StatText") + local posBuff = 0 + local negBuff = 0 + + label:SetText(TEXT(DEFENSE_COLON)) + + if (modifier > 0) then + posBuff = modifier + elseif (modifier < 0) then + negBuff = modifier + end + + PaperDollFormatStat(DEFENSE_COLON, base, posBuff, negBuff, statFrame, text) + statFrame.tooltip = format(L.DEFENSE_TOOLTIP) + statFrame.tooltipSubtext = format(L.DEFENSE_TOOLTIP_SUB) + + BCS_AddTooltip(statFrame) +end + +function BCS:SetRangedDamage(statFrame) + local label = getglobal(statFrame:GetName() .. "Label") + local damageText = getglobal(statFrame:GetName() .. "StatText") + + label:SetText(TEXT(DAMAGE_COLON)) + + -- If no ranged attack then set to n/a + if UnitHasRelicSlot("player") or not (GetInventoryItemLink("player", 18)) then + damageText:SetText(NOT_APPLICABLE) + statFrame.damage = nil + return + end + + BCS_AddDamageTooltip(damageText, statFrame, nil, nil, true) +end + +function BCS:SetRangedAttackSpeed(statFrame) + local label = getglobal(statFrame:GetName() .. "Label") + local damageText = getglobal(statFrame:GetName() .. "StatText") + + label:SetText(TEXT(SPEED) .. ":") + + -- If no ranged attack then set to n/a + if UnitHasRelicSlot("player") or not (GetInventoryItemLink("player", 18)) then + damageText:SetText(NOT_APPLICABLE) + statFrame.damage = nil + return + end + + BCS_AddDamageTooltip(damageText, statFrame, nil, nil, true) + + damageText:SetText(format("%.2f", UnitRangedDamage("player"))) +end + +function BCS:SetRangedAttackPower(statFrame) + local text = getglobal(statFrame:GetName() .. "StatText") + local label = getglobal(statFrame:GetName() .. "Label") + + label:SetText(TEXT(ATTACK_POWER_COLON)) + + -- If no ranged attack then set to n/a + if UnitHasRelicSlot("player") or not (GetInventoryItemLink("player", 18)) then + text:SetText(NOT_APPLICABLE) + statFrame.tooltip = nil + return + end + + if (HasWandEquipped()) then + text:SetText("--"); + statFrame.tooltip = nil; + return ; + end + + local base, posBuff, negBuff = UnitRangedAttackPower("player") + local tooltipText = HIGHLIGHT_FONT_COLOR_CODE .. RANGED_ATTACK_POWER .. " " + local effectiveStat = base + posBuff + negBuff + + PaperDollFormatStat(RANGED_ATTACK_POWER, base, posBuff, negBuff, statFrame, text) + statFrame.tooltipSubtext = format(RANGED_ATTACK_POWER_TOOLTIP, base / ATTACK_POWER_MAGIC_NUMBER) + + if ((posBuff == 0) and (negBuff == 0)) then + text:SetText(effectiveStat) + statFrame.tooltip = tooltipText .. base .. FONT_COLOR_CODE_CLOSE + else + tooltipText = tooltipText .. effectiveStat + if (posBuff > 0 or negBuff < 0) then + tooltipText = tooltipText .. " (" .. (base - posBuff - negBuff) .. FONT_COLOR_CODE_CLOSE + end + if (posBuff > 0) then + tooltipText = tooltipText .. FONT_COLOR_CODE_CLOSE .. GREEN_FONT_COLOR_CODE .. "+" .. posBuff .. FONT_COLOR_CODE_CLOSE + end + if (negBuff < 0) then + tooltipText = tooltipText .. RED_FONT_COLOR_CODE .. " " .. negBuff .. FONT_COLOR_CODE_CLOSE + end + if (posBuff > 0 or negBuff < 0) then + tooltipText = tooltipText .. HIGHLIGHT_FONT_COLOR_CODE .. ")" .. FONT_COLOR_CODE_CLOSE + end + statFrame.tooltip = tooltipText + + if (negBuff < 0) then + text:SetText(RED_FONT_COLOR_CODE .. effectiveStat .. FONT_COLOR_CODE_CLOSE) + else + text:SetText(GREEN_FONT_COLOR_CODE .. effectiveStat .. FONT_COLOR_CODE_CLOSE) + end + end + + label:SetText(TEXT(ATTACK_POWER_COLON)) + + PaperDollFormatStat(RANGED_ATTACK_POWER, base, posBuff, negBuff, statFrame, text) + statFrame.tooltipSubtext = format(RANGED_ATTACK_POWER_TOOLTIP, max((base + posBuff + negBuff), 0) / ATTACK_POWER_MAGIC_NUMBER) + + BCS_AddTooltip(statFrame) +end + +function BCS:UpdatePaperdollStats(prefix, index) + local stat1 = getglobal(prefix .. 1) + local stat2 = getglobal(prefix .. 2) + local stat3 = getglobal(prefix .. 3) + local stat4 = getglobal(prefix .. 4) + local stat5 = getglobal(prefix .. 5) + local stat6 = getglobal(prefix .. 6) + + stat1:SetScript("OnEnter", nil) + stat2:SetScript("OnEnter", nil) + stat3:SetScript("OnEnter", nil) + stat4:SetScript("OnEnter", nil) + stat4:SetScript("OnEnter", nil) + stat5:SetScript("OnEnter", nil) + stat6:SetScript("OnEnter", nil) + + stat1.tooltip = nil + stat2.tooltip = nil + stat3.tooltip = nil + stat4.tooltip = nil + stat4.tooltip = nil + stat5.tooltip = nil + stat6.tooltip = nil + + stat4:Show() + stat5:Show() + stat6:Show() + + if (index == "PLAYERSTAT_BASE_STATS") then + BCS:SetStat(stat1, 1) + BCS:SetStat(stat2, 2) + BCS:SetStat(stat3, 3) + BCS:SetStat(stat4, 4) + BCS:SetStat(stat5, 5) + BCS:SetArmor(stat6) + elseif (index == "PLAYERSTAT_MELEE_COMBAT") then + BCS:SetWeaponSkill(stat1) + BCS:SetDamage(stat2) + BCS:SetAttackSpeed(stat3) + BCS:SetAttackPower(stat4) + BCS:SetHitRating(stat5, "MELEE") + BCS:SetMeleeCritChance(stat6) + elseif (index == "PLAYERSTAT_MELEE_BOSS") then + BCS:SetWeaponSkill(stat1) + BCS:SetBossMissChance(stat2) + BCS:SetBossDodgeChance(stat3) + BCS:SetBossGlanceReduction(stat4) + BCS:SetBossCritCap(stat5) + BCS:SetEffectiveBossCrit(stat6) + elseif (index == "PLAYERSTAT_RANGED_COMBAT") then + BCS:SetRangedWeaponSkill(stat1) + BCS:SetRangedDamage(stat2) + BCS:SetRangedAttackSpeed(stat3) + BCS:SetRangedAttackPower(stat4) + BCS:SetHitRating(stat5, "RANGED") + BCS:SetRangedCritChance(stat6) + elseif (index == "PLAYERSTAT_SPELL_COMBAT") then + BCS:SetSpellPower(stat1) + BCS:SetHitRating(stat2, "SPELL") + BCS:SetSpellCritChance(stat3) + BCS:SetHealing(stat4) + BCS:SetManaRegen(stat5) + stat6:Hide() + elseif (index == "PLAYERSTAT_SPELL_SCHOOLS") then + BCS:SetSpellPower(stat1, "Arcane") + BCS:SetSpellPower(stat2, "Fire") + BCS:SetSpellPower(stat3, "Frost") + BCS:SetSpellPower(stat4, "Holy") + BCS:SetSpellPower(stat5, "Nature") + BCS:SetSpellPower(stat6, "Shadow") + elseif (index == "PLAYERSTAT_DEFENSES") then + BCS:SetArmor(stat1) + BCS:SetDefense(stat2) + BCS:SetDodge(stat3, 0) + BCS:SetParry(stat4, 0) + BCS:SetBlock(stat5, 0) + BCS:SetTotalAvoidance(stat6, 0) + elseif (index == "PLAYERSTAT_DEFENSES_BOSS") then + BCS:SetArmor(stat1) + BCS:SetDefense(stat2) + BCS:SetDodge(stat3, 3) + BCS:SetParry(stat4, 3) + BCS:SetBlock(stat5, 3) + BCS:SetTotalAvoidance(stat6, 3) + end +end + +local function PlayerStatFrameLeftDropDown_OnClick() + BCS.needScanGear = true + BCS.needScanTalents = true + BCS.needScanAuras = true + BCS.needScanSkills = true + + UIDropDownMenu_SetSelectedValue(getglobal(this.owner), this.value) + IndexLeft = this.value + BCSConfig["DropdownLeft"] = IndexLeft + BCS:UpdatePaperdollStats("PlayerStatFrameLeft", this.value) + + BCS.needScanGear = false + BCS.needScanTalents = false + BCS.needScanAuras = false + BCS.needScanSkills = false +end + +local function PlayerStatFrameRightDropDown_OnClick() + BCS.needScanGear = true + BCS.needScanTalents = true + BCS.needScanAuras = true + BCS.needScanSkills = true + + UIDropDownMenu_SetSelectedValue(getglobal(this.owner), this.value) + IndexRight = this.value + BCSConfig["DropdownRight"] = IndexRight + BCS:UpdatePaperdollStats("PlayerStatFrameRight", this.value) + + BCS.needScanGear = false + BCS.needScanTalents = false + BCS.needScanAuras = false + BCS.needScanSkills = false +end + +local function PlayerStatFrameLeftDropDown_Initialize() + local info = {} + local checked = nil + for i = 1, getn(BCS.PLAYERSTAT_DROPDOWN_OPTIONS) do + info.text = BCS.L[BCS.PLAYERSTAT_DROPDOWN_OPTIONS[i]] + info.func = PlayerStatFrameLeftDropDown_OnClick + info.value = BCS.PLAYERSTAT_DROPDOWN_OPTIONS[i] + info.checked = checked + info.owner = UIDROPDOWNMENU_OPEN_MENU + if not (UnitHasRelicSlot("player") and info.value == "PLAYERSTAT_RANGED_COMBAT") then + UIDropDownMenu_AddButton(info) + end + end +end + +local function PlayerStatFrameRightDropDown_Initialize() + local info = {} + local checked = nil + for i = 1, getn(BCS.PLAYERSTAT_DROPDOWN_OPTIONS) do + info.text = BCS.L[BCS.PLAYERSTAT_DROPDOWN_OPTIONS[i]] + info.func = PlayerStatFrameRightDropDown_OnClick + info.value = BCS.PLAYERSTAT_DROPDOWN_OPTIONS[i] + info.checked = checked + info.owner = UIDROPDOWNMENU_OPEN_MENU + if not (UnitHasRelicSlot("player") and info.value == "PLAYERSTAT_RANGED_COMBAT") then + UIDropDownMenu_AddButton(info) + end + end +end + +function PlayerStatFrameLeftDropDown_OnLoad() + RaiseFrameLevel(this) + RaiseFrameLevel(getglobal(this:GetName() .. "Button")) + UIDropDownMenu_Initialize(this, PlayerStatFrameLeftDropDown_Initialize) + UIDropDownMenu_SetWidth(99, this) + UIDropDownMenu_JustifyText("LEFT") +end + +function PlayerStatFrameRightDropDown_OnLoad() + RaiseFrameLevel(this) + RaiseFrameLevel(getglobal(this:GetName() .. "Button")) + UIDropDownMenu_Initialize(this, PlayerStatFrameRightDropDown_Initialize) + UIDropDownMenu_SetWidth(99, this) + UIDropDownMenu_JustifyText("LEFT") +end diff --git a/BetterCharacterStats.toc b/BetterCharacterStats.toc index 8eaba5f..f60ef0e 100644 --- a/BetterCharacterStats.toc +++ b/BetterCharacterStats.toc @@ -1,7 +1,11 @@ ## Interface: 11200 ## Title: BetterCharacterStats -## Author: moh, Bennylava, |cffbe5effLexie|r -## Version: 1.12.5 -## SavedVariablesPerCharacter: BCSConfig bcsupdateavailable +## Author: moh, Bennylava, |cffbe5effLexie|r, Spit, Pepopo +## Version: 1.12.7 +## SavedVariablesPerCharacter: BCSConfig BCScache -BetterCharacterStats.xml \ No newline at end of file +Libs\Ace2\AceLibrary\AceLibrary.lua +Libs\Ace2\AceOO-2.0\AceOO-2.0.lua +Libs\Ace2\AceEvent-2.0\AceEvent-2.0.lua + +BetterCharacterStats.xml diff --git a/BetterCharacterStats.xml b/BetterCharacterStats.xml index e0ea65a..5540da1 100644 --- a/BetterCharacterStats.xml +++ b/BetterCharacterStats.xml @@ -43,6 +43,10 @@ --GameTooltip:Hide(); + + getglobal(this:GetName() .. "Label"):SetFont("Interface\\AddOns\\BetterCharacterStats\\Fonts\\Myriad-Pro.ttf", 11); + getglobal(this:GetName() .. "StatText"):SetFont("Interface\\AddOns\\BetterCharacterStats\\Fonts\\Myriad-Pro.ttf", 11); + @@ -298,4 +302,4 @@ - \ No newline at end of file + diff --git a/Fonts/Myriad-Pro.ttf b/Fonts/Myriad-Pro.ttf new file mode 100644 index 0000000..42237fc Binary files /dev/null and b/Fonts/Myriad-Pro.ttf differ diff --git a/Libs/Ace2/AceEvent-2.0/AceEvent-2.0.lua b/Libs/Ace2/AceEvent-2.0/AceEvent-2.0.lua new file mode 100644 index 0000000..c654568 --- /dev/null +++ b/Libs/Ace2/AceEvent-2.0/AceEvent-2.0.lua @@ -0,0 +1,973 @@ +--[[ +Name: AceEvent-2.0 +Revision: $Rev: 17803 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Ace 1.x by Turan (turan@gryphon.com) +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/index.php/AceEvent-2.0 +SVN: http://svn.wowace.com/root/trunk/Ace2/AceEvent-2.0 +Description: Mixin to allow for event handling, scheduling, and inter-addon + communication. +Dependencies: AceLibrary, AceOO-2.0 +]] + +local MAJOR_VERSION = "AceEvent-2.0" +local MINOR_VERSION = "$Revision: 17803 $" + +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check +if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end + +local AceOO = AceLibrary:GetInstance("AceOO-2.0") +local Mixin = AceOO.Mixin +local AceEvent = Mixin { + "RegisterEvent", + "RegisterAllEvents", + "UnregisterEvent", + "UnregisterAllEvents", + "TriggerEvent", + "ScheduleEvent", + "ScheduleRepeatingEvent", + "CancelScheduledEvent", + "CancelAllScheduledEvents", + "IsEventRegistered", + "IsEventScheduled", + "RegisterBucketEvent", + "UnregisterBucketEvent", + "UnregisterAllBucketEvents", + "IsBucketEventRegistered", + } + +local table_setn +do + local version = GetBuildInfo() + if string.find(version, "^2%.") then + -- 2.0.0 + table_setn = function() end + else + table_setn = table.setn + end +end + +local weakKey = {__mode="k"} +local new, del +do + local list = setmetatable({}, weakKey) + function new() + local t = next(list) + if t then + list[t] = nil + return t + else + return {} + end + end + + function del(t) + setmetatable(t, nil) + for k in pairs(t) do + t[k] = nil + end + list[t] = true + end +end + +local FAKE_NIL +local RATE + +local eventsWhichHappenOnce = { + PLAYER_LOGIN = true, + AceEvent_FullyInitialized = true, + VARIABLES_LOADED = true, + PLAYER_LOGOUT = true, +} + +local registeringFromAceEvent +function AceEvent:RegisterEvent(event, method, once) + AceEvent:argCheck(event, 2, "string") + if self == AceEvent and not registeringFromAceEvent then + AceEvent:argCheck(method, 3, "function") + self = method + else + AceEvent:argCheck(method, 3, "string", "function", "nil", "boolean", "number") + if type(method) == "boolean" or type(method) == "number" then + AceEvent:argCheck(once, 4, "nil") + once, method = method, event + end + end + AceEvent:argCheck(once, 4, "number", "boolean", "nil") + if eventsWhichHappenOnce[event] then + once = true + end + local throttleRate + if type(once) == "number" then + throttleRate, once = once + end + if not method then + method = event + end + if type(method) == "string" and type(self[method]) ~= "function" then + AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method) + else + assert(type(method) == "function" or type(method) == "string") + end + + local AceEvent_registry = AceEvent.registry + if not AceEvent_registry[event] then + AceEvent_registry[event] = new() + AceEvent.frame:RegisterEvent(event) + end + + local remember = true + if AceEvent_registry[event][self] then + remember = false + end + AceEvent_registry[event][self] = method + + local AceEvent_onceRegistry = AceEvent.onceRegistry + if once then + if not AceEvent_onceRegistry then + AceEvent.onceRegistry = new() + AceEvent_onceRegistry = AceEvent.onceRegistry + end + if not AceEvent_onceRegistry[event] then + AceEvent_onceRegistry[event] = new() + end + AceEvent_onceRegistry[event][self] = true + else + if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then + AceEvent_onceRegistry[event][self] = nil + if not next(AceEvent_onceRegistry[event]) then + AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event]) + end + end + end + + local AceEvent_throttleRegistry = AceEvent.throttleRegistry + if throttleRate then + if not AceEvent_throttleRegistry then + AceEvent.throttleRegistry = new() + AceEvent_throttleRegistry = AceEvent.throttleRegistry + end + if not AceEvent_throttleRegistry[event] then + AceEvent_throttleRegistry[event] = new() + end + if AceEvent_throttleRegistry[event][self] then + AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self]) + end + AceEvent_throttleRegistry[event][self] = setmetatable(new(), weakKey) + local t = AceEvent_throttleRegistry[event][self] + t[RATE] = throttleRate + else + if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] then + if AceEvent_throttleRegistry[event][self] then + AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self]) + end + if not next(AceEvent_throttleRegistry[event]) then + AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event]) + end + end + end + + if remember then + AceEvent:TriggerEvent("AceEvent_EventRegistered", self, event) + end +end + +local ALL_EVENTS + +function AceEvent:RegisterAllEvents(method) + if self == AceEvent then + AceEvent:argCheck(method, 1, "function") + self = method + else + AceEvent:argCheck(method, 1, "string", "function") + if type(method) == "string" and type(self[method]) ~= "function" then + AceEvent:error("Cannot register all events to method %q, it does not exist", method) + end + end + + local AceEvent_registry = AceEvent.registry + if not AceEvent_registry[ALL_EVENTS] then + AceEvent_registry[ALL_EVENTS] = new() + AceEvent.frame:RegisterAllEvents() + end + + AceEvent_registry[ALL_EVENTS][self] = method +end + +local _G = getfenv(0) +local memstack, timestack = {}, {} +local memdiff, timediff +function AceEvent:TriggerEvent(event, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + AceEvent:argCheck(event, 2, "string") + local AceEvent_registry = AceEvent.registry + if (not AceEvent_registry[event] or not next(AceEvent_registry[event])) and (not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS])) then + return + end + local _G_event = _G.event + _G.event = event + local lastEvent = AceEvent.currentEvent + AceEvent.currentEvent = event + + local AceEvent_onceRegistry = AceEvent.onceRegistry + local AceEvent_debugTable = AceEvent.debugTable + if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then + local tmp = new() + for obj, method in pairs(AceEvent_onceRegistry[event]) do + tmp[obj] = AceEvent_registry[event] and AceEvent_registry[event][obj] or nil + end + local obj = next(tmp) + while obj do + local mem, time + if AceEvent_debugTable then + if not AceEvent_debugTable[event] then + AceEvent_debugTable[event] = new() + end + if not AceEvent_debugTable[event][obj] then + AceEvent_debugTable[event][obj] = new() + AceEvent_debugTable[event][obj].mem = 0 + AceEvent_debugTable[event][obj].time = 0 + AceEvent_debugTable[event][obj].count = 0 + end + if memdiff then + table.insert(memstack, memdiff) + table.insert(timestack, timediff) + end + memdiff, timediff = 0, 0 + mem, time = gcinfo(), GetTime() + end + local method = tmp[obj] + AceEvent.UnregisterEvent(obj, event) + if type(method) == "string" then + local obj_method = obj[method] + if obj_method then + obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + elseif method then -- function + method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + if AceEvent_debugTable then + local dmem, dtime = memdiff, timediff + mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff + AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem + AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time + AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1 + + memdiff, timediff = table.remove(memstack), table.remove(timestack) + if memdiff then + memdiff = memdiff + mem + dmem + timediff = timediff + time + dtime + end + end + tmp[obj] = nil + obj = next(tmp) + end + del(tmp) + end + + local AceEvent_throttleRegistry = AceEvent.throttleRegistry + local throttleTable = AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] + if AceEvent_registry[event] then + local tmp = new() + for obj, method in pairs(AceEvent_registry[event]) do + tmp[obj] = method + end + local obj = next(tmp) + while obj do + local method = tmp[obj] + local continue = false + if throttleTable and throttleTable[obj] then + local a1 = a1 + if a1 == nil then + a1 = FAKE_NIL + end + if not throttleTable[obj][a1] or GetTime() - throttleTable[obj][a1] >= throttleTable[obj][RATE] then + throttleTable[obj][a1] = GetTime() + else + continue = true + end + end + if not continue then + local mem, time + if AceEvent_debugTable then + if not AceEvent_debugTable[event] then + AceEvent_debugTable[event] = new() + end + if not AceEvent_debugTable[event][obj] then + AceEvent_debugTable[event][obj] = new() + AceEvent_debugTable[event][obj].mem = 0 + AceEvent_debugTable[event][obj].time = 0 + AceEvent_debugTable[event][obj].count = 0 + end + if memdiff then + table.insert(memstack, memdiff) + table.insert(timestack, timediff) + end + memdiff, timediff = 0, 0 + mem, time = gcinfo(), GetTime() + end + if type(method) == "string" then + local obj_method = obj[method] + if obj_method then + obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + elseif method then -- function + method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + if AceEvent_debugTable then + local dmem, dtime = memdiff, timediff + mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff + AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem + AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time + AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1 + + memdiff, timediff = table.remove(memstack), table.remove(timestack) + if memdiff then + memdiff = memdiff + mem + dmem + timediff = timediff + time + dtime + end + end + end + tmp[obj] = nil + obj = next(tmp) + end + del(tmp) + end + if AceEvent_registry[ALL_EVENTS] then + local tmp = new() + for obj, method in pairs(AceEvent_registry[ALL_EVENTS]) do + tmp[obj] = method + end + local obj = next(tmp) + while obj do + local method = tmp[obj] + local mem, time + if AceEvent_debugTable then + if not AceEvent_debugTable[event] then + AceEvent_debugTable[event] = new() + end + if not AceEvent_debugTable[event][obj] then + AceEvent_debugTable[event][obj] = new() + AceEvent_debugTable[event][obj].mem = 0 + AceEvent_debugTable[event][obj].time = 0 + AceEvent_debugTable[event][obj].count = 0 + end + if memdiff then + table.insert(memstack, memdiff) + table.insert(timestack, timediff) + end + memdiff, timediff = 0, 0 + mem, time = gcinfo(), GetTime() + end + if type(method) == "string" then + local obj_method = obj[method] + if obj_method then + obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + elseif method then -- function + method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + if AceEvent_debugTable then + local dmem, dtime = memdiff, timediff + mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff + AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem + AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time + AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1 + + memdiff, timediff = table.remove(memstack), table.remove(timestack) + if memdiff then + memdiff = memdiff + mem + dmem + timediff = timediff + time + dtime + end + end + tmp[obj] = nil + obj = next(tmp) + end + del(tmp) + end + _G.event = _G_event + AceEvent.currentEvent = lastEvent +end + +-- local accessors +local getn = table.getn +local tinsert = table.insert +local tremove = table.remove +local floor = math.floor +local GetTime = GetTime +local next = next +local pairs = pairs +local unpack = unpack + +local delayRegistry +local tmp = {} +local function OnUpdate() + local t = GetTime() + for k,v in pairs(delayRegistry) do + tmp[k] = true + end + for k in pairs(tmp) do + local v = delayRegistry[k] + if v then + local v_time = v.time + if not v_time then + delayRegistry[k] = del(v) + elseif v_time <= t then + local v_repeatDelay = v.repeatDelay + if v_repeatDelay then + -- use the event time, not the current time, else timing inaccuracies add up over time + v.time = v_time + v_repeatDelay + end + local event = v.event + local mem, time + if AceEvent_debugTable then + mem, time = gcinfo(), GetTime() + end + if type(event) == "function" then + event(unpack(v)) + else + AceEvent:TriggerEvent(event, unpack(v)) + end + if AceEvent_debugTable then + mem, time = gcinfo() - mem, GetTime() - time + v.mem = v.mem + mem + v.timeSpent = v.timeSpent + time + v.count = v.count + 1 + end + if not v_repeatDelay then + local x = delayRegistry[k] + if x and x.time == v_time then -- check if it was manually reset + delayRegistry[k] = del(v) + end + end + end + end + end + for k in pairs(tmp) do + tmp[k] = nil + end + if not next(delayRegistry) then + AceEvent.frame:Hide() + end +end + +local function ScheduleEvent(self, repeating, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + local id + if type(event) == "string" or type(event) == "table" then + if type(event) == "table" then + if not delayRegistry or not delayRegistry[event] then + AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") + end + end + if type(delay) ~= "number" then + id, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 + AceEvent:argCheck(event, 3, "string", "function", --[[ so message is right ]] "number") + AceEvent:argCheck(delay, 4, "number") + self:CancelScheduledEvent(id) + end + else + AceEvent:argCheck(event, 2, "string", "function") + AceEvent:argCheck(delay, 3, "number") + end + + if not delayRegistry then + AceEvent.delayRegistry = new() + delayRegistry = AceEvent.delayRegistry + AceEvent.frame:SetScript("OnUpdate", OnUpdate) + end + local t + if type(id) == "table" then + for k in pairs(id) do + id[k] = nil + end + t = id + else + t = new() + end + t[1] = a1 + t[2] = a2 + t[3] = a3 + t[4] = a4 + t[5] = a5 + t[6] = a6 + t[7] = a7 + t[8] = a8 + t[9] = a9 + t[10] = a10 + t[11] = a11 + t[12] = a12 + t[13] = a13 + t[14] = a14 + t[15] = a15 + t[16] = a16 + t[17] = a17 + t[18] = a18 + t[19] = a19 + t[20] = a20 + table_setn(t, 20) + t.event = event + t.time = GetTime() + delay + t.self = self + t.id = id or t + t.repeatDelay = repeating and delay + if AceEvent_debugTable then + t.mem = 0 + t.count = 0 + t.timeSpent = 0 + end + delayRegistry[t.id] = t + AceEvent.frame:Show() + return t.id +end + +function AceEvent:ScheduleEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + if type(event) == "string" or type(event) == "table" then + if type(event) == "table" then + if not delayRegistry or not delayRegistry[event] then + AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") + end + end + if type(delay) ~= "number" then + AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number") + AceEvent:argCheck(a1, 4, "number") + end + else + AceEvent:argCheck(event, 2, "string", "function") + AceEvent:argCheck(delay, 3, "number") + end + + return ScheduleEvent(self, false, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) +end + +function AceEvent:ScheduleRepeatingEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + if type(event) == "string" or type(event) == "table" then + if type(event) == "table" then + if not delayRegistry or not delayRegistry[event] then + AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") + end + end + if type(delay) ~= "number" then + AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number") + AceEvent:argCheck(a1, 4, "number") + end + else + AceEvent:argCheck(event, 2, "string", "function") + AceEvent:argCheck(delay, 3, "number") + end + + return ScheduleEvent(self, true, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) +end + +function AceEvent:CancelScheduledEvent(t) + AceEvent:argCheck(t, 2, "string", "table") + if delayRegistry then + local v = delayRegistry[t] + if v then + delayRegistry[t] = del(v) + if not next(delayRegistry) then + AceEvent.frame:Hide() + end + return true + end + end + return false +end + +function AceEvent:IsEventScheduled(t) + AceEvent:argCheck(t, 2, "string", "table") + if delayRegistry then + local v = delayRegistry[t] + if v then + return true, v.time - GetTime() + end + end + return false, nil +end + +function AceEvent:UnregisterEvent(event) + AceEvent:argCheck(event, 2, "string") + local AceEvent_registry = AceEvent.registry + if AceEvent_registry[event] and AceEvent_registry[event][self] then + AceEvent_registry[event][self] = nil + local AceEvent_onceRegistry = AceEvent.onceRegistry + if AceEvent_onceRegistry and AceEvent_onceRegistry[event] and AceEvent_onceRegistry[event][self] then + AceEvent_onceRegistry[event][self] = nil + if not next(AceEvent_onceRegistry[event]) then + AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event]) + end + end + local AceEvent_throttleRegistry = AceEvent.throttleRegistry + if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] and AceEvent_throttleRegistry[event][self] then + AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self]) + if not next(AceEvent_throttleRegistry[event]) then + AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event]) + end + end + if not next(AceEvent_registry[event]) then + AceEvent_registry[event] = del(AceEvent_registry[event]) + if not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS]) then + AceEvent.frame:UnregisterEvent(event) + end + end + else + if self == AceEvent then + error(string.format("Cannot unregister event %q. Improperly unregistering from AceEvent-2.0.", event), 2) + else + AceEvent:error("Cannot unregister event %q. %q is not registered with it.", event, self) + end + end + AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event) +end + +function AceEvent:UnregisterAllEvents() + local AceEvent_registry = AceEvent.registry + if AceEvent_registry[ALL_EVENTS] and AceEvent_registry[ALL_EVENTS][self] then + AceEvent_registry[ALL_EVENTS][self] = nil + if not next(AceEvent_registry[ALL_EVENTS]) then + del(AceEvent_registry[ALL_EVENTS]) + AceEvent.frame:UnregisterAllEvents() + for k,v in pairs(AceEvent_registry) do + if k ~= ALL_EVENTS then + AceEvent.frame:RegisterEvent(k) + end + end + AceEvent_registry[event] = nil + end + end + local first = true + for event, data in pairs(AceEvent_registry) do + if first then + if AceEvent_registry.AceEvent_EventUnregistered then + event = "AceEvent_EventUnregistered" + else + first = false + end + end + local x = data[self] + data[self] = nil + if x and event ~= ALL_EVENTS then + if not next(data) then + del(data) + if not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS]) then + AceEvent.frame:UnregisterEvent(event) + end + AceEvent_registry[event] = nil + end + AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event) + end + if first then + event = nil + end + end + if AceEvent.onceRegistry then + for event, data in pairs(AceEvent.onceRegistry) do + data[self] = nil + end + end +end + +function AceEvent:CancelAllScheduledEvents() + if delayRegistry then + for k,v in pairs(delayRegistry) do + if v.self == self then + delayRegistry[k] = del(v) + end + end + if not next(delayRegistry) then + AceEvent.frame:Hide() + end + end +end + +function AceEvent:IsEventRegistered(event) + AceEvent:argCheck(event, 2, "string") + local AceEvent_registry = AceEvent.registry + if self == AceEvent then + return AceEvent_registry[event] and next(AceEvent_registry[event]) and true or false + end + if AceEvent_registry[event] and AceEvent_registry[event][self] then + return true, AceEvent_registry[event][self] + end + return false, nil +end + +local bucketfunc +function AceEvent:RegisterBucketEvent(event, delay, method) + AceEvent:argCheck(event, 2, "string", "table") + if type(event) == "table" then + for k,v in pairs(event) do + if type(k) ~= "number" then + AceEvent:error("All keys to argument #2 to `RegisterBucketEvent' must be numbers.") + elseif type(v) ~= "string" then + AceEvent:error("All values to argument #2 to `RegisterBucketEvent' must be strings.") + end + end + end + AceEvent:argCheck(delay, 3, "number") + if AceEvent == self then + AceEvent:argCheck(method, 4, "function") + self = method + else + if type(event) == "string" then + AceEvent:argCheck(method, 4, "string", "function", "nil") + if not method then + method = event + end + else + AceEvent:argCheck(method, 4, "string", "function") + end + + if type(method) == "string" and type(self[method]) ~= "function" then + AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method) + end + end + if not AceEvent.buckets then + AceEvent.buckets = new() + end + if not AceEvent.buckets[event] then + AceEvent.buckets[event] = new() + end + if not AceEvent.buckets[event][self] then + AceEvent.buckets[event][self] = new() + AceEvent.buckets[event][self].current = new() + AceEvent.buckets[event][self].self = self + else + AceEvent.CancelScheduledEvent(self, AceEvent.buckets[event][self].id) + end + local bucket = AceEvent.buckets[event][self] + bucket.method = method + + local func = function(arg1) + bucket.run = true + if arg1 then + bucket.current[arg1] = true + end + end + AceEvent.buckets[event][self].func = func + if type(event) == "string" then + AceEvent.RegisterEvent(self, event, func) + else + for _,v in ipairs(event) do + AceEvent.RegisterEvent(self, v, func) + end + end + if not bucketfunc then + bucketfunc = function(bucket) + local current = bucket.current + local method = bucket.method + local self = bucket.self + if bucket.run then + if type(method) == "string" then + self[method](self, current) + elseif method then -- function + method(current) + end + for k in pairs(current) do + current[k] = nil + k = nil + end + bucket.run = false + end + end + end + bucket.id = AceEvent.ScheduleRepeatingEvent(self, bucketfunc, delay, bucket) +end + +function AceEvent:IsBucketEventRegistered(event) + AceEvent:argCheck(event, 2, "string", "table") + return AceEvent.buckets and AceEvent.buckets[event] and AceEvent.buckets[event][self] +end + +function AceEvent:UnregisterBucketEvent(event) + AceEvent:argCheck(event, 2, "string", "table") + if not AceEvent.buckets or not AceEvent.buckets[event] or not AceEvent.buckets[event][self] then + AceEvent:error("Cannot unregister bucket event %q. %q is not registered with it.", event, self) + end + + local bucket = AceEvent.buckets[event][self] + + if type(event) == "string" then + AceEvent.UnregisterEvent(self, event) + else + for _,v in ipairs(event) do + AceEvent.UnregisterEvent(self, v) + end + end + AceEvent:CancelScheduledEvent(bucket.id) + + del(bucket.current) + AceEvent.buckets[event][self] = del(AceEvent.buckets[event][self]) + if not next(AceEvent.buckets[event]) then + AceEvent.buckets[event] = del(AceEvent.buckets[event]) + end +end + +function AceEvent:UnregisterAllBucketEvents() + if not AceEvent.buckets or not next(AceEvent.buckets) then + return + end + for k,v in pairs(AceEvent.buckets) do + if v == self then + AceEvent.UnregisterBucketEvent(self, k) + k = nil + end + end +end + +function AceEvent:OnEmbedDisable(target) + self.UnregisterAllEvents(target) + + self.CancelAllScheduledEvents(target) + + self.UnregisterAllBucketEvents(target) +end + +function AceEvent:EnableDebugging() + if not self.debugTable then + self.debugTable = new() + + if delayRegistry then + for k,v in pairs(self.delayRegistry) do + if not v.mem then + v.mem = 0 + v.count = 0 + v.timeSpent = 0 + end + end + end + end +end + +function AceEvent:IsFullyInitialized() + return self.postInit or false +end + +function AceEvent:IsPostPlayerLogin() + return self.playerLogin or false +end + +function AceEvent:activate(oldLib, oldDeactivate) + AceEvent = self + + if oldLib then + self.onceRegistry = oldLib.onceRegistry + self.throttleRegistry = oldLib.throttleRegistry + self.delayRegistry = oldLib.delayRegistry + self.buckets = oldLib.buckets + self.registry = oldLib.registry + self.frame = oldLib.frame + self.debugTable = oldLib.debugTable + self.playerLogin = oldLib.pew or DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage and true + self.postInit = oldLib.postInit or self.playerLogin and ChatTypeInfo and ChatTypeInfo.WHISPER and ChatTypeInfo.WHISPER.r and true + self.ALL_EVENTS = oldLib.ALL_EVENTS + self.FAKE_NIL = oldLib.FAKE_NIL + self.RATE = oldLib.RATE + end + if not self.registry then + self.registry = {} + end + if not self.frame then + self.frame = CreateFrame("Frame", "AceEvent20Frame") + end + if not self.ALL_EVENTS then + self.ALL_EVENTS = {} + end + if not self.FAKE_NIL then + self.FAKE_NIL = {} + end + if not self.RATE then + self.RATE = {} + end + ALL_EVENTS = self.ALL_EVENTS + FAKE_NIL = self.FAKE_NIL + RATE = self.RATE + local inPlw = false + local blacklist = { + UNIT_INVENTORY_CHANGED = true, + BAG_UPDATE = true, + ITEM_LOCK_CHANGED = true, + ACTIONBAR_SLOT_CHANGED = true, + } + self.frame:SetScript("OnEvent", function() + local event = event + if event == "PLAYER_ENTERING_WORLD" then + inPlw = false + elseif event == "PLAYER_LEAVING_WORLD" then + inPlw = true + end + if event and (not inPlw or not blacklist[event]) then + self:TriggerEvent(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) + end + end) + if self.delayRegistry then + delayRegistry = self.delayRegistry + self.frame:SetScript("OnUpdate", OnUpdate) + end + + self:UnregisterAllEvents() + self:CancelAllScheduledEvents() + + registeringFromAceEvent = true + self:RegisterEvent("LOOT_OPENED", function() + SendAddonMessage("LOOT_OPENED", "", "RAID") + end) + registeringFromAceEvent = nil + + if not self.playerLogin then + registeringFromAceEvent = true + self:RegisterEvent("PLAYER_LOGIN", function() + self.playerLogin = true + end, true) + registeringFromAceEvent = nil + end + + if not self.postInit then + local isReload = true + local function func() + self.postInit = true + self:TriggerEvent("AceEvent_FullyInitialized") + if self.registry["CHAT_MSG_CHANNEL_NOTICE"] and self.registry["CHAT_MSG_CHANNEL_NOTICE"][self] then + self:UnregisterEvent("CHAT_MSG_CHANNEL_NOTICE") + end + if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then + self:UnregisterEvent("MEETINGSTONE_CHANGED") + end + if self.registry["MINIMAP_ZONE_CHANGED"] and self.registry["MINIMAP_ZONE_CHANGED"][self] then + self:UnregisterEvent("MINIMAP_ZONE_CHANGED") + end + if self.registry["LANGUAGE_LIST_CHANGED"] and self.registry["LANGUAGE_LIST_CHANGED"][self] then + self:UnregisterEvent("LANGUAGE_LIST_CHANGED") + end + end + registeringFromAceEvent = true + local f = function() + self.playerLogin = true + self:ScheduleEvent("AceEvent_FullyInitialized", func, 1) + end + self:RegisterEvent("MEETINGSTONE_CHANGED", f, true) + self:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE", function() + self:ScheduleEvent("AceEvent_FullyInitialized", func, 0.05) + end) + self:RegisterEvent("LANGUAGE_LIST_CHANGED", function() + if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then + registeringFromAceEvent = true + self:UnregisterEvent("MEETINGSTONE_CHANGED") + self:RegisterEvent("MINIMAP_ZONE_CHANGED", f, true) + registeringFromAceEvent = nil + end + end) + self:ScheduleEvent("AceEvent_FullyInitialized", func, 10) + registeringFromAceEvent = nil + end + + self.super.activate(self, oldLib, oldDeactivate) + if oldLib then + oldDeactivate(oldLib) + end +end + +AceLibrary:Register(AceEvent, MAJOR_VERSION, MINOR_VERSION, AceEvent.activate) +AceEvent = AceLibrary(MAJOR_VERSION) diff --git a/Libs/Ace2/AceLibrary/AceLibrary.lua b/Libs/Ace2/AceLibrary/AceLibrary.lua new file mode 100644 index 0000000..0f2b237 --- /dev/null +++ b/Libs/Ace2/AceLibrary/AceLibrary.lua @@ -0,0 +1,757 @@ +--[[ +Name: AceLibrary +Revision: $Rev: 17722 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Iriel (iriel@vigilance-committee.org) + Tekkub (tekkub@gmail.com) + Revision: $Rev: 17722 $ +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/index.php/AceLibrary +SVN: http://svn.wowace.com/root/trunk/Ace2/AceLibrary +Description: Versioning library to handle other library instances, upgrading, + and proper access. + It also provides a base for libraries to work off of, providing + proper error tools. It is handy because all the errors occur in the + file that called it, not in the library file itself. +Dependencies: None +]] + +local ACELIBRARY_MAJOR = "AceLibrary" +local ACELIBRARY_MINOR = "$Revision: 17722 $" + +if loadstring("return function(...) return ... end") and AceLibrary and AceLibrary:HasInstance(ACELIBRARY_MAJOR) then return end -- lua51 check +local table_setn +do + local version = GetBuildInfo() + if string.find(version, "^2%.") then + -- 2.0.0 + table_setn = function() end + else + table_setn = table.setn + end +end + +local string_gfind = string.gmatch or string.gfind + +local _G = getfenv(0) +local previous = _G[ACELIBRARY_MAJOR] +if previous and not previous:IsNewVersion(ACELIBRARY_MAJOR, ACELIBRARY_MINOR) then return end + +local function safecall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10) + local success, err = pcall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10) + if not success then geterrorhandler()(err) end +end + +-- @table AceLibrary +-- @brief System to handle all versioning of libraries. +local AceLibrary = {} +local AceLibrary_mt = {} +setmetatable(AceLibrary, AceLibrary_mt) + +local tmp +local function error(self, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + if type(self) ~= "table" then + _G.error(string.format("Bad argument #1 to `error' (table expected, got %s)", type(self)), 2) + end + if not tmp then + tmp = {} + else + for k in pairs(tmp) do tmp[k] = nil end + table_setn(tmp, 0) + end + + table.insert(tmp, a1) + table.insert(tmp, a2) + table.insert(tmp, a3) + table.insert(tmp, a4) + table.insert(tmp, a5) + table.insert(tmp, a6) + table.insert(tmp, a7) + table.insert(tmp, a8) + table.insert(tmp, a9) + table.insert(tmp, a10) + table.insert(tmp, a11) + table.insert(tmp, a12) + table.insert(tmp, a13) + table.insert(tmp, a14) + table.insert(tmp, a15) + table.insert(tmp, a16) + table.insert(tmp, a17) + table.insert(tmp, a18) + table.insert(tmp, a19) + table.insert(tmp, a20) + + local stack = debugstack() + if not message then + local _,_,second = string.find(stack, "\n(.-)\n") + message = "error raised! " .. second + else + for i = 1,table.getn(tmp) do + tmp[i] = tostring(tmp[i]) + end + for i = 1,10 do + table.insert(tmp, "nil") + end + message = string.format(message, unpack(tmp)) + end + + if getmetatable(self) and getmetatable(self).__tostring then + message = string.format("%s: %s", tostring(self), message) + elseif type(rawget(self, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self:GetLibraryVersion()) then + message = string.format("%s: %s", self:GetLibraryVersion(), message) + elseif type(rawget(self, 'class')) == "table" and type(rawget(self.class, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self.class:GetLibraryVersion()) then + message = string.format("%s: %s", self.class:GetLibraryVersion(), message) + end + + local first = string.gsub(stack, "\n.*", "") + local file = string.gsub(first, ".*\\(.*).lua:%d+: .*", "%1") + file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") + + local i = 0 + for s in string_gfind(stack, "\n([^\n]*)") do + i = i + 1 + if not string.find(s, file .. "%.lua:%d+:") then + file = string.gsub(s, "^.*\\(.*).lua:%d+: .*", "%1") + file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") + break + end + end + local j = 0 + for s in string_gfind(stack, "\n([^\n]*)") do + j = j + 1 + if j > i and not string.find(s, file .. "%.lua:%d+:") then + _G.error(message, j + 1) + return + end + end + _G.error(message, 2) + return +end + +local function assert(self, condition, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + if not condition then + if not message then + local stack = debugstack() + local _,_,second = string.find(stack, "\n(.-)\n") + message = "assertion failed! " .. second + end + error(self, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + return + end + return condition +end + +local function argCheck(self, arg, num, kind, kind2, kind3, kind4, kind5) + if type(num) ~= "number" then + error(self, "Bad argument #3 to `argCheck' (number expected, got %s)", type(num)) + elseif type(kind) ~= "string" then + error(self, "Bad argument #4 to `argCheck' (string expected, got %s)", type(kind)) + end + local errored = false + arg = type(arg) + if arg ~= kind and arg ~= kind2 and arg ~= kind3 and arg ~= kind4 and arg ~= kind5 then + local _,_,func = string.find(debugstack(), "`argCheck'.-([`<].-['>])") + if not func then + _,_,func = string.find(debugstack(), "([`<].-['>])") + end + if kind5 then + error(self, "Bad argument #%s to %s (%s, %s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, kind5, arg) + elseif kind4 then + error(self, "Bad argument #%s to %s (%s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, arg) + elseif kind3 then + error(self, "Bad argument #%s to %s (%s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, arg) + elseif kind2 then + error(self, "Bad argument #%s to %s (%s or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, arg) + else + error(self, "Bad argument #%s to %s (%s expected, got %s)", tonumber(num) or 0/0, func, kind, arg) + end + end +end + +local function pcall(self, func, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = _G.pcall(func, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + if not a1 then + error(self, string.gsub(a2, ".-%.lua:%d-: ", "")) + else + return a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 + end +end + +local recurse = {} +local function addToPositions(t, major) + if not AceLibrary.positions[t] or AceLibrary.positions[t] == major then + rawset(t, recurse, true) + AceLibrary.positions[t] = major + for k,v in pairs(t) do + if type(v) == "table" and not rawget(v, recurse) then + addToPositions(v, major) + end + if type(k) == "table" and not rawget(k, recurse) then + addToPositions(k, major) + end + end + local mt = getmetatable(t) + if mt and not rawget(mt, recurse) then + addToPositions(mt, major) + end + rawset(t, recurse, nil) + end +end + +local function svnRevisionToNumber(text) + if type(text) == "string" then + if string.find(text, "^%$Revision: (%d+) %$$") then + return tonumber((string.gsub(text, "^%$Revision: (%d+) %$$", "%1"))) + elseif string.find(text, "^%$Rev: (%d+) %$$") then + return tonumber((string.gsub(text, "^%$Rev: (%d+) %$$", "%1"))) + elseif string.find(text, "^%$LastChangedRevision: (%d+) %$$") then + return tonumber((string.gsub(text, "^%$LastChangedRevision: (%d+) %$$", "%1"))) + end + elseif type(text) == "number" then + return text + end + return nil +end + +local crawlReplace +do + local recurse = {} + local function func(t, to, from) + if recurse[t] then + return + end + recurse[t] = true + local mt = getmetatable(t) + setmetatable(t, nil) + rawset(t, to, rawget(t, from)) + rawset(t, from, nil) + for k,v in pairs(t) do + if v == from then + t[k] = to + elseif type(v) == "table" then + if not recurse[v] then + func(v, to, from) + end + end + + if type(k) == "table" then + if not recurse[k] then + func(k, to, from) + end + end + end + setmetatable(t, mt) + if mt then + if mt == from then + setmetatable(t, to) + elseif not recurse[mt] then + func(mt, to, from) + end + end + end + function crawlReplace(t, to, from) + func(t, to, from) + for k in pairs(recurse) do + recurse[k] = nil + end + end +end + +-- @function destroyTable +-- @brief remove all the contents of a table +-- @param t table to destroy +local function destroyTable(t) + setmetatable(t, nil) + for k,v in pairs(t) do t[k] = nil end + table_setn(t, 0) +end + +local function isFrame(frame) + return type(frame) == "table" and type(rawget(frame, 0)) == "userdata" and type(rawget(frame, 'IsFrameType')) == "function" and getmetatable(frame) and type(rawget(getmetatable(frame), '__index')) == "function" +end + +local new, del +do + local tables = setmetatable({}, {__mode = "k"}) + + function new() + local t = next(tables) + if t then + tables[t] = nil + return t + else + return {} + end + end + + function del(t, depth) + if depth and depth > 0 then + for k,v in pairs(t) do + if type(v) == "table" and not isFrame(v) then + del(v, depth - 1) + end + end + end + destroyTable(t) + tables[t] = true + end +end + +-- @function copyTable +-- @brief Create a shallow copy of a table and return it. +-- @param from The table to copy from +-- @return A shallow copy of the table +local function copyTable(from) + local to = new() + for k,v in pairs(from) do to[k] = v end + table_setn(to, table.getn(from)) + setmetatable(to, getmetatable(from)) + return to +end + +-- @function deepTransfer +-- @brief Fully transfer all data, keeping proper previous table +-- backreferences stable. +-- @param to The table with which data is to be injected into +-- @param from The table whose data will be injected into the first +-- @param saveFields If available, a shallow copy of the basic data is saved +-- in here. +-- @param list The account of table references +-- @param list2 The current status on which tables have been traversed. +local deepTransfer +do + -- @function examine + -- @brief Take account of all the table references to be shared + -- between the to and from tables. + -- @param to The table with which data is to be injected into + -- @param from The table whose data will be injected into the first + -- @param list An account of the table references + local function examine(to, from, list, major) + list[from] = to + for k,v in pairs(from) do + if rawget(to, k) and type(from[k]) == "table" and type(to[k]) == "table" and not list[from[k]] then + if from[k] == to[k] then + list[from[k]] = to[k] + elseif AceLibrary.positions[from[v]] ~= major and AceLibrary.positions[from[v]] then + list[from[k]] = from[k] + elseif not list[from[k]] then + examine(to[k], from[k], list, major) + end + end + end + return list + end + + function deepTransfer(to, from, saveFields, major, list, list2) + setmetatable(to, nil) + local createdList + if not list then + createdList = true + list = new() + list2 = new() + examine(to, from, list, major) + end + list2[to] = to + for k,v in pairs(to) do + if type(rawget(from, k)) ~= "table" or type(v) ~= "table" or isFrame(v) then + if saveFields then + saveFields[k] = v + end + to[k] = nil + elseif v ~= _G then + if saveFields then + saveFields[k] = copyTable(v) + end + end + end + for k in pairs(from) do + if rawget(to, k) and to[k] ~= from[k] and AceLibrary.positions[to[k]] == major and from[k] ~= _G then + if not list2[to[k]] then + deepTransfer(to[k], from[k], nil, major, list, list2) + end + to[k] = list[to[k]] or list2[to[k]] + else + rawset(to, k, from[k]) + end + end + table_setn(to, table.getn(from)) + setmetatable(to, getmetatable(from)) + local mt = getmetatable(to) + if mt then + if list[mt] then + setmetatable(to, list[mt]) + elseif mt.__index and list[mt.__index] then + mt.__index = list[mt.__index] + end + end + destroyTable(from) + if createdList then + del(list) + del(list2) + end + end +end + +-- @method TryToLoadStandalone +-- @brief Attempt to find and load a standalone version of the requested library +-- @param major A string representing the major version +-- @return If library is found, return values from the call to LoadAddOn are returned +-- If the library has been requested previously, nil is returned. +local function TryToLoadStandalone(major) + if not AceLibrary.scannedlibs then AceLibrary.scannedlibs = {} end + if AceLibrary.scannedlibs[major] then return end + + AceLibrary.scannedlibs[major] = true + + local name, _, _, enabled, loadable = GetAddOnInfo(major) + if loadable then + return LoadAddOn(name) + end + + for i=1,GetNumAddOns() do + if GetAddOnMetadata(i, "X-AceLibrary-"..major) then + local name, _, _, enabled, loadable = GetAddOnInfo(i) + if loadable then + return LoadAddOn(name) + end + end + end +end + +-- @method IsNewVersion +-- @brief Obtain whether the supplied version would be an upgrade to the +-- current version. This allows for bypass code in library +-- declaration. +-- @param major A string representing the major version +-- @param minor An integer or an svn revision string representing the minor version +-- @return whether the supplied version would be newer than what is +-- currently available. +function AceLibrary:IsNewVersion(major, minor) + argCheck(self, major, 2, "string") + TryToLoadStandalone(major) + + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(string.format("Bad argument #3 to `IsNewVersion'. Must be a number or SVN revision string. %q is not appropriate", minor), 2) + end + end + argCheck(self, minor, 3, "number") + local data = self.libs[major] + if not data then + return true + end + return data.minor < minor +end + +-- @method HasInstance +-- @brief Returns whether an instance exists. This allows for optional support of a library. +-- @param major A string representing the major version. +-- @param minor (optional) An integer or an svn revision string representing the minor version. +-- @return Whether an instance exists. +function AceLibrary:HasInstance(major, minor) + argCheck(self, major, 2, "string") + TryToLoadStandalone(major) + + if minor then + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(string.format("Bad argument #3 to `HasInstance'. Must be a number or SVN revision string. %q is not appropriate", minor), 2) + end + end + argCheck(self, minor, 3, "number") + if not self.libs[major] then + return + end + return self.libs[major].minor == minor + end + return self.libs[major] and true +end + +-- @method GetInstance +-- @brief Returns the library with the given major/minor version. +-- @param major A string representing the major version. +-- @param minor (optional) An integer or an svn revision string representing the minor version. +-- @return The library with the given major/minor version. +function AceLibrary:GetInstance(major, minor) + argCheck(self, major, 2, "string") + TryToLoadStandalone(major) + + local data = self.libs[major] + if not data then + _G.error(string.format("Cannot find a library instance of %s.", major), 2) + return + end + if minor then + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(string.format("Bad argument #3 to `GetInstance'. Must be a number or SVN revision string. %q is not appropriate", minor), 2) + end + end + argCheck(self, minor, 2, "number") + if data.minor ~= minor then + _G.error(string.format("Cannot find a library instance of %s, minor version %d.", major, minor), 2) + return + end + end + return data.instance +end + +-- Syntax sugar. AceLibrary("FooBar-1.0") +AceLibrary_mt.__call = AceLibrary.GetInstance + +local donothing + +local AceEvent + +-- @method Register +-- @brief Registers a new version of a given library. +-- @param newInstance the library to register +-- @param major the major version of the library +-- @param minor the minor version of the library +-- @param activateFunc (optional) A function to be called when the library is +-- fully activated. Takes the arguments +-- (newInstance [, oldInstance, oldDeactivateFunc]). If +-- oldInstance is given, you should probably call +-- oldDeactivateFunc(oldInstance). +-- @param deactivateFunc (optional) A function to be called by a newer library's +-- activateFunc. +-- @param externalFunc (optional) A function to be called whenever a new +-- library is registered. +function AceLibrary:Register(newInstance, major, minor, activateFunc, deactivateFunc, externalFunc) + argCheck(self, newInstance, 2, "table") + argCheck(self, major, 3, "string") + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(string.format("Bad argument #4 to `Register'. Must be a number or SVN revision string. %q is not appropriate", minor), 2) + end + end + argCheck(self, minor, 4, "number") + if math.floor(minor) ~= minor or minor < 0 then + error(self, "Bad argument #4 to `Register' (integer >= 0 expected, got %s)", minor) + end + argCheck(self, activateFunc, 5, "function", "nil") + argCheck(self, deactivateFunc, 6, "function", "nil") + argCheck(self, externalFunc, 7, "function", "nil") + if not deactivateFunc then + if not donothing then + donothing = function() end + end + deactivateFunc = donothing + end + local data = self.libs[major] + if not data then + -- This is new + local instance = copyTable(newInstance) + crawlReplace(instance, instance, newInstance) + destroyTable(newInstance) + if AceLibrary == newInstance then + self = instance + AceLibrary = instance + end + self.libs[major] = { + instance = instance, + minor = minor, + deactivateFunc = deactivateFunc, + externalFunc = externalFunc, + } + rawset(instance, 'GetLibraryVersion', function(self) + return major, minor + end) + if not rawget(instance, 'error') then + rawset(instance, 'error', error) + end + if not rawget(instance, 'assert') then + rawset(instance, 'assert', assert) + end + if not rawget(instance, 'argCheck') then + rawset(instance, 'argCheck', argCheck) + end + if not rawget(instance, 'pcall') then + rawset(instance, 'pcall', pcall) + end + addToPositions(instance, major) + if activateFunc then + safecall(activateFunc, instance, nil, nil) -- no old version, so explicit nil + end + + if externalFunc then + for k,data in pairs(self.libs) do + if k ~= major then + safecall(externalFunc, instance, k, data.instance) + end + end + end + + for k,data in pairs(self.libs) do + if k ~= major and data.externalFunc then + safecall(data.externalFunc, data.instance, major, instance) + end + end + if major == "AceEvent-2.0" then + AceEvent = instance + end + if AceEvent then + AceEvent.TriggerEvent(self, "AceLibrary_Register", major, instance) + end + + return instance + end + local instance = data.instance + if minor <= data.minor then + -- This one is already obsolete, raise an error. + _G.error(string.format("Obsolete library registered. %s is already registered at version %d. You are trying to register version %d. Hint: if not AceLibrary:IsNewVersion(%q, %d) then return end", major, data.minor, minor, major, minor), 2) + return + end + -- This is an update + local oldInstance = new() + + addToPositions(newInstance, major) + local isAceLibrary = (AceLibrary == newInstance) + local old_error, old_assert, old_argCheck, old_pcall + if isAceLibrary then + self = instance + AceLibrary = instance + + old_error = instance.error + old_assert = instance.assert + old_argCheck = instance.argCheck + old_pcall = instance.pcall + + self.error = error + self.assert = assert + self.argCheck = argCheck + self.pcall = pcall + end + deepTransfer(instance, newInstance, oldInstance, major) + crawlReplace(instance, instance, newInstance) + local oldDeactivateFunc = data.deactivateFunc + data.minor = minor + data.deactivateFunc = deactivateFunc + data.externalFunc = externalFunc + rawset(instance, 'GetLibraryVersion', function(self) + return major, minor + end) + if not rawget(instance, 'error') then + rawset(instance, 'error', error) + end + if not rawget(instance, 'assert') then + rawset(instance, 'assert', assert) + end + if not rawget(instance, 'argCheck') then + rawset(instance, 'argCheck', argCheck) + end + if not rawget(instance, 'pcall') then + rawset(instance, 'pcall', pcall) + end + if isAceLibrary then + for _,v in pairs(self.libs) do + local i = type(v) == "table" and v.instance + if type(i) == "table" then + if not rawget(i, 'error') or i.error == old_error then + rawset(i, 'error', error) + end + if not rawget(i, 'assert') or i.assert == old_assert then + rawset(i, 'assert', assert) + end + if not rawget(i, 'argCheck') or i.argCheck == old_argCheck then + rawset(i, 'argCheck', argCheck) + end + if not rawget(i, 'pcall') or i.pcall == old_pcall then + rawset(i, 'pcall', pcall) + end + end + end + end + if activateFunc then + safecall(activateFunc, instance, oldInstance, oldDeactivateFunc) + else + safecall(oldDeactivateFunc, oldInstance) + end + del(oldInstance) + + if externalFunc then + for k,data in pairs(self.libs) do + if k ~= major then + safecall(externalFunc, instance, k, data.instance) + end + end + end + + return instance +end + +local iter +function AceLibrary:IterateLibraries() + if not iter then + local function iter(t, k) + k = next(t, k) + if not k then + return nil + else + return k, t[k].instance + end + end + end + return iter, self.libs, nil +end + +-- @function Activate +-- @brief The activateFunc for AceLibrary itself. Called when +-- AceLibrary properly registers. +-- @param self Reference to AceLibrary +-- @param oldLib (optional) Reference to an old version of AceLibrary +-- @param oldDeactivate (optional) Function to deactivate the old lib +local function activate(self, oldLib, oldDeactivate) + if not self.libs then + if oldLib then + self.libs = oldLib.libs + self.scannedlibs = oldLib.scannedlibs + end + if not self.libs then + self.libs = {} + end + if not self.scannedlibs then + self.scannedlibs = {} + end + end + if not self.positions then + if oldLib then + self.positions = oldLib.positions + end + if not self.positions then + self.positions = setmetatable({}, { __mode = "k" }) + end + end + + -- Expose the library in the global environment + _G[ACELIBRARY_MAJOR] = self + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +if not previous then + previous = AceLibrary +end +if not previous.libs then + previous.libs = {} +end +AceLibrary.libs = previous.libs +if not previous.positions then + previous.positions = setmetatable({}, { __mode = "k" }) +end +AceLibrary.positions = previous.positions +AceLibrary:Register(AceLibrary, ACELIBRARY_MAJOR, ACELIBRARY_MINOR, activate) diff --git a/Libs/Ace2/AceOO-2.0/AceOO-2.0.lua b/Libs/Ace2/AceOO-2.0/AceOO-2.0.lua new file mode 100644 index 0000000..cd372b4 --- /dev/null +++ b/Libs/Ace2/AceOO-2.0/AceOO-2.0.lua @@ -0,0 +1,1006 @@ +--[[ +Name: AceOO-2.0 +Revision: $Rev: 17638 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Ace 1.x by Turan (turan@gryphon.com) +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/index.php/AceOO-2.0 +SVN: http://svn.wowace.com/root/trunk/Ace2/AceOO-2.0 +Description: Library to provide an object-orientation framework. +Dependencies: AceLibrary +]] + +local MAJOR_VERSION = "AceOO-2.0" +local MINOR_VERSION = "$Revision: 17639 $" + +-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check +local table_setn +do + local version = GetBuildInfo() + if string.find(version, "^2%.") then + -- 2.0.0 + table_setn = function() end + else + table_setn = table.setn + end +end + +local AceOO = { + error = AceLibrary.error, + argCheck = AceLibrary.argCheck +} + +-- @function getuid +-- @brief Obtain a unique string identifier for the object in question. +-- @param t The object to obtain the uid for. +-- @return The uid string. +local function pad(cap) + return string.rep('0', 8 - string.len(cap)) .. cap +end +local function getuid(t) + local mt = getmetatable(t) + setmetatable(t, nil) + local str = tostring(t) + setmetatable(t, mt) + local _,_,cap = string.find(str, '[^:]*: 0x(.*)$') + if cap then return pad(cap) end + _,_,cap = string.find(str, '[^:]*: (.*)$') + if cap then return pad(cap) end +end + +local function getlibrary(o) + if type(o) == "table" then + return o + elseif type(o) == "string" then + if not AceLibrary:HasInstance(o) then + AceOO:error("Library %q does not exist.", o) + end + return AceLibrary(o) + end +end + +-- @function Factory +-- @brief Construct a factory for the creation of objects. +-- @param obj The object whose init method will be called on the new factory +-- object. +-- @param newobj The object whose init method will be called on the new +-- objects that the Factory creates, to initialize them. +-- @param (a1..a20) Arguments which will be passed to obj.init() in addition +-- to the Factory object. +-- @return The new factory which creates a newobj when its new method is called, +-- or when it is called directly (__call metamethod). +local Factory +do + local function new(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + local t = {} + local uid = getuid(t) + local l = getlibrary + obj:init(t, l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), + l(a8), l(a9), l(a10), l(a11), l(a12), l(a13), + l(a14), l(a15), l(a16), l(a17), l(a18), l(a19), + l(a20)) + t.uid = uid + return t + end + + local function createnew(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + a11, a12, a13, a14, a15, a16, a17, a18, + a19, a20) + local o = self.prototype + local l = getlibrary + return new(o, l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), + l(a8), l(a9), l(a10), l(a11), l(a12), l(a13), + l(a14), l(a15), l(a16), l(a17), l(a18), l(a19), + l(a20)) + end + + function Factory(obj, newobj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + a11, a12, a13, a14, a15, a16, a17, a18, + a19, a20) + local t = new(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + t.prototype = newobj + t.new = createnew + getmetatable(t).__call = t.new + return t + end +end + + +local function objtostring(self) + if self.ToString then + return self:ToString() + elseif self.GetLibraryVersion then + return (self:GetLibraryVersion()) + elseif self.super then + local s = "Sub-" .. tostring(self.super) + local first = true + if self.interfaces then + for interface in pairs(self.interfaces) do + if first then + s = s .. "(" .. tostring(interface) + first = false + else + s = s .. ", " .. tostring(interface) + end + end + end + if self.mixins then + for mixin in pairs(self.mixins) do + if first then + s = s .. tostring(mixin) + first = false + else + s = s .. ", " .. tostring(mixin) + end + end + end + if first then + if self.uid then + return s .. ":" .. self.uid + else + return s + end + else + return s .. ")" + end + else + return self.uid and 'Subclass:' .. self.uid or 'Subclass' + end +end + +-- @table Object +-- @brief Base of all objects, including Class. +-- +-- @method init +-- @brief Initialize a new object. +-- @param newobject The object to initialize +-- @param class The class to make newobject inherit from +local Object +do + Object = {} + function Object:init(newobject, class) + local parent = class or self + if not rawget(newobject, 'uid') then + newobject.uid = getuid(newobject) + end + local mt = { + __index = parent, + __tostring = objtostring, + } + setmetatable(newobject, mt) + end + Object.uid = getuid(Object) + setmetatable(Object, { __tostring = function() return 'Object' end }) +end + +local Interface + +local function validateInterface(object, interface) + if not object.class and object.prototype then + object = object.prototype + end + for k,v in pairs(interface.interface) do + if tostring(type(object[k])) ~= v then + return false + end + end + if interface.superinterfaces then + for superinterface in pairs(interface.superinterfaces) do + if not validateInterface(object, superinterface) then + return false + end + end + end + if type(object.class) == "table" and rawequal(object.class.prototype, object) then + if not object.class.interfaces then + rawset(object.class, 'interfaces', {}) + end + object.class.interfaces[interface] = true + elseif type(object.class) == "table" and type(object.class.prototype) == "table" then + validateInterface(object.class.prototype, interface) + -- check if class is proper, thus preventing future checks. + end + return true +end + +-- @function inherits +-- @brief Return whether an Object or Class inherits from a given +-- parent. +-- @param object Object or Class to check +-- @param parent Parent to test inheritance from +-- @return whether an Object or Class inherits from a given +-- parent. +local function inherits(object, parent) + object = getlibrary(object) + if type(parent) == "string" then + if not AceLibrary:HasInstance(parent) then + return false + else + parent = AceLibrary(parent) + end + end + AceOO:argCheck(parent, 2, "table") + if type(object) ~= "table" then + return false + end + local current + if object.class then + current = object.class + else + current = object + end + if type(current) ~= "table" then + return false + end + if rawequal(current, parent) then + return true + end + if parent.class then + while true do + if rawequal(current, Object) then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if rawequal(mixin, parent) then + return true + end + end + end + if current.interfaces then + for interface in pairs(current.interfaces) do + if rawequal(interface, parent) then + return true + end + end + end + current = current.super + if type(current) ~= "table" then + break + end + end + + local isInterface = false + local curr = parent.class + while true do + if rawequal(curr, Object) then + break + elseif rawequal(curr, Interface) then + isInterface = true + break + end + curr = curr.super + if type(curr) ~= "table" then + break + end + end + return isInterface and validateInterface(object, parent) + else + while true do + if rawequal(current, parent) then + return true + elseif rawequal(current, Object) then + return false + end + current = current.super + if type(current) ~= "table" then + return false + end + end + end +end + +-- @table Class +-- @brief An object factory which sets up inheritence and supports +-- 'mixins'. +-- +-- @metamethod Class call +-- @brief Call ClassFactory:new() to create a new class. +-- +-- @method Class new +-- @brief Construct a new object. +-- @param (a1..a20) Arguments to pass to the object init function. +-- @return The new object. +-- +-- @method Class init +-- @brief Initialize a new class. +-- @param parent Superclass. +-- @param (a1..a20) Mixins. +-- +-- @method Class ToString +-- @return A string representing the object, in this case 'Class'. +local initStatus +local Class +local Mixin +local autoEmbed = false +local function traverseInterfaces(bit, total) + if bit.superinterfaces then + for interface in pairs(bit.superinterfaces) do + if not total[interface] then + total[interface] = true + traverseInterfaces(interface, total) + end + end + end +end +local class_new +do + Class = Factory(Object, setmetatable({}, {__index = Object}), Object) + Class.super = Object + + local function protostring(t) + return '<' .. tostring(t.class) .. ' prototype>' + end + local function classobjectstring(t) + if t.ToString then + return t:ToString() + elseif t.GetLibraryVersion then + return (t:GetLibraryVersion()) + else + return '<' .. tostring(t.class) .. ' instance>' + end + end + local function classobjectequal(self, other) + if type(self) == "table" and self.Equals then + return self:Equals(other) + elseif type(other) == "table" and other.Equals then + return other:Equals(self) + elseif type(self) == "table" and self.CompareTo then + return self:CompareTo(other) == 0 + elseif type(other) == "table" and other.CompareTo then + return other:CompareTo(self) == 0 + else + return rawequal(self, other) + end + end + local function classobjectlessthan(self, other) + if type(self) == "table" and self.IsLessThan then + return self:IsLessThan(other) + elseif type(other) == "table" and other.IsLessThanOrEqualTo then + return not other:IsLessThanOrEqualTo(self) + elseif type(self) == "table" and self.CompareTo then + return self:CompareTo(other) < 0 + elseif type(other) == "table" and other.CompareTo then + return other:CompareTo(self) > 0 + elseif type(other) == "table" and other.IsLessThan and other.Equals then + return other:Equals(self) or other:IsLessThan(self) + else + AceOO:error("cannot compare two objects") + end + end + local function classobjectlessthanequal(self, other) + if type(self) == "table" and self.IsLessThanOrEqualTo then + return self:IsLessThanOrEqualTo(other) + elseif type(other) == "table" and other.IsLessThan then + return not other:IsLessThan(self) + elseif type(self) == "table" and self.CompareTo then + return self:CompareTo(other) <= 0 + elseif type(other) == "table" and other.CompareTo then + return other:CompareTo(self) >= 0 + elseif type(self) == "table" and self.IsLessThan and self.Equals then + return self:Equals(other) or self:IsLessThan(other) + else + AceOO:error("cannot compare two incompatible objects") + end + end + local function classobjectadd(self, other) + if type(self) == "table" and self.Add then + return self:Add(other) + else + AceOO:error("cannot add two incompatible objects") + end + end + local function classobjectsub(self, other) + if type(self) == "table" and self.Subtract then + return self:Subtract(other) + else + AceOO:error("cannot subtract two incompatible objects") + end + end + local function classobjectunm(self, other) + if type(self) == "table" and self.UnaryNegation then + return self:UnaryNegation(other) + else + AceOO:error("attempt to negate an incompatible object") + end + end + local function classobjectmul(self, other) + if type(self) == "table" and self.Multiply then + return self:Multiply(other) + else + AceOO:error("cannot multiply two incompatible objects") + end + end + local function classobjectdiv(self, other) + if type(self) == "table" and self.Divide then + return self:Divide(other) + else + AceOO:error("cannot divide two incompatible objects") + end + end + local function classobjectpow(self, other) + if type(self) == "table" and self.Exponent then + return self:Exponent(other) + else + AceOO:error("cannot exponentiate two incompatible objects") + end + end + local function classobjectconcat(self, other) + if type(self) == "table" and self.Concatenate then + return self:Concatenate(other) + else + AceOO:error("cannot concatenate two incompatible objects") + end + end + function class_new(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + if self.virtual then + AceOO:error("Cannot instantiate a virtual class.") + end + + local o = self.prototype + local newobj = {} + if o.class and o.class.instancemeta then + setmetatable(newobj, o.class.instancemeta) + else + Object:init(newobj, o) + end + + if self.interfaces and not self.interfacesVerified then + -- Verify the interfaces + + for interface in pairs(self.interfaces) do + for field,kind in pairs(interface.interface) do + if tostring(type(newobj[field])) ~= kind then + AceOO:error("Class did not satisfy all interfaces. %q is required to be a %s. It is a %s", field, kind, tostring(type(newobj[field]))) + end + end + end + self.interfacesVerified = true + end + local tmp = initStatus + initStatus = newobj + newobj:init(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + if initStatus then + initStatus = tmp + AceOO:error("Initialization not completed, be sure to call the superclass's init method.") + return + end + initStatus = tmp + return newobj + end + local classmeta = { + __tostring = objtostring, + __call = function(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + return self:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + end, + } + function Class:init(newclass, parent, a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10, a11, a12, a13, a14, a15, a16, + a17, a18, a19, a20) + parent = parent or self + + local total + + if parent.class then + total = { + parent, a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10, a11, a12, a13, a14, a15, a16, + a17, a18, a19, a20 + } + parent = self + else + total = { + a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10, a11, a12, a13, a14, a15, a16, + a17, a18, a19, a20 + } + end + if not inherits(parent, Class) then + AceOO:error("Classes must inherit from a proper class") + end + if parent.sealed then + AceOO:error("Cannot inherit from a sealed class") + end + for i,v in ipairs(total) do + if inherits(v, Mixin) and v.class then + if not newclass.mixins then + newclass.mixins = {} + end + if newclass.mixins[v] then + AceOO:error("Cannot explicitly inherit from the same mixin twice") + end + newclass.mixins[v] = true + elseif inherits(v, Interface) and v.class then + if not newclass.interfaces then + newclass.interfaces = {} + end + if newclass.interfaces[v] then + AceOO:error("Cannot explicitly inherit from the same interface twice") + end + newclass.interfaces[v] = true + else + AceOO:error("Classes can only inherit from one or zero classes and any number of mixins or interfaces") + end + end + if parent.interfaces then + if newclass.interfaces then + for interface in pairs(parent.interfaces) do + newclass.interfaces[interface] = true + end + else + newclass.interfaces = parent.interfaces + end + end + for k in pairs(total) do + total[k] = nil + end + table_setn(total, 0) + + newclass.super = parent + + newclass.prototype = setmetatable(total, { + __index = parent.prototype, + __tostring = protostring, + }) + total = nil + + newclass.instancemeta = { + __index = newclass.prototype, + __tostring = classobjectstring, + __eq = classobjectequal, + __lt = classobjectlessthan, + __le = classobjectlessthanequal, + __add = classobjectadd, + __sub = classobjectsub, + __unm = classobjectunm, + __mul = classobjectmul, + __div = classobjectdiv, + __pow = classobjectpow, + __concat = classobjectconcat, + } + + setmetatable(newclass, classmeta) + + newclass.new = class_new + + if newclass.mixins then + -- Fold in the mixins + local err, msg + for mixin in pairs(newclass.mixins) do + local ret + autoEmbed = true + ret, msg = pcall(mixin.embed, mixin, newclass.prototype) + autoEmbed = false + if not ret then + err = true + break + end + end + + if err then + local pt = newclass.prototype + for k,v in pairs(pt) do + pt[k] = nil + end + + -- method conflict + AceOO:error(msg) + end + end + + newclass.prototype.class = newclass + + if newclass.interfaces then + for interface in pairs(newclass.interfaces) do + traverseInterfaces(interface, newclass.interfaces) + end + end + if newclass.mixins then + for mixin in pairs(newclass.mixins) do + if mixin.interfaces then + if not newclass.interfaces then + newclass.interfaces = {} + end + for interface in pairs(mixin.interfaces) do + newclass.interfaces[interface] = true + end + end + end + end + end + function Class:ToString() + if type(self.GetLibraryVersion) == "function" then + return (self:GetLibraryVersion()) + else + return "Class" + end + end + + local tmp + function Class.prototype:init() + if rawequal(self, initStatus) then + initStatus = nil + else + AceOO:error("Improper self passed to init. You must do MyClass.super.prototype.init(self, ...)", 2) + end + self.uid = getuid(self) + local current = self.class + while true do + if current == Class then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnInstanceInit) == "function" then + mixin:OnInstanceInit(self) + end + end + end + current = current.super + end + end +end + + +-- @object ClassFactory +-- @brief A factory for creating classes. Rarely used directly. +local ClassFactory = Factory(Object, Class, Object) + +function Class:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, + a12, a13, a14, a15, a16, a17, a18, a19, a20) + local x = ClassFactory:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, + a12, a13, a14, a15, a16, a17, a18, a19, a20) + if AceOO.classes then + AceOO.classes[x] = true + end + return x +end +getmetatable(Class).__call = Class.new + +-- @class Mixin +-- @brief A class to create mixin objects, which contain methods that get +-- "mixed in" to class prototypes. +-- +-- @object Mixin prototype +-- @brief The prototype that mixin objects inherit their methods from. +-- +-- @method Mixin prototype embed +-- @brief Mix in the methods of our object which are listed in our interface +-- to the supplied target table. +-- +-- @method Mixin prototype init +-- @brief Initialize the mixin object. +-- @param newobj The new object we're initializing. +-- @param interface The interface we implement (the list of methods our +-- prototype provides which should be mixed into the target +-- table by embed). +do + Mixin = Class() + function Mixin:ToString() + if self.GetLibraryVersion then + return (self:GetLibraryVersion()) + else + return 'Mixin' + end + end + local function _Embed(state, field, target) + field = next(state.export, field) + if field == nil then + return + end + + if rawget(target, field) or (target[field] and target[field] ~= state[field]) then + AceOO:error("Method conflict in attempt to mixin. Field %q", field) + end + + target[field] = state[field] + + local ret,msg = pcall(_Embed, state, field, target) + if not ret then + -- Mix in the next method according to the defined interface. If that + -- fails due to a conflict, re-raise to back out the previous mixed + -- methods. + + target[field] = nil + AceOO:error(msg) + end + end + function Mixin.prototype:embed(target) + local mt = getmetatable(target) + setmetatable(target, nil) + local err, msg = pcall(_Embed, self, nil, target) + if not err then + setmetatable(target, mt) + AceOO:error(msg) + return + end + if type(self.embedList) == "table" then + self.embedList[target] = true + end + if type(target.class) ~= "table" then + target[self] = true + end + if not autoEmbed and type(self.OnManualEmbed) == "function" then + if not target.modules then + self:OnManualEmbed(target) + end + end + setmetatable(target, mt) + end + + function Mixin.prototype:activate(oldLib, oldDeactivate) + if oldLib and oldLib.embedList then + for target in pairs(oldLib.embedList) do + local mt = getmetatable(target) + setmetatable(target, nil) + for field in pairs(oldLib.export) do + target[field] = nil + end + setmetatable(target, mt) + end + self.embedList = oldLib.embedList + for target in pairs(self.embedList) do + self:embed(target) + end + else + self.embedList = setmetatable({}, {__mode="k"}) + end + end + + function Mixin.prototype:init(export, a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10, a11, a12, a13, a14, a15, a16, + a17, a18, a19, a20) + AceOO:argCheck(export, 2, "table") + for k,v in pairs(export) do + if type(k) ~= "number" then + AceOO:error("All keys to argument #2 must be numbers.") + elseif type(v) ~= "string" then + AceOO:error("All values to argument #2 must be strings.") + end + end + local num = table.getn(export) + for i = 1, num do + local v = export[i] + export[i] = nil + export[v] = true + end + table_setn(export, 0) + local interfaces + if a1 then + local l = getlibrary + interfaces = { l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), l(a8), + l(a9), l(a10), l(a11), l(a12), l(a13), l(a14), l(a15), l(a16), + l(a17), l(a18), l(a19), l(a20) } + for _,v in ipairs(interfaces) do + if not v.class or not inherits(v, Interface) then + AceOO:error("Mixins can inherit only from interfaces") + end + end + local num = table.getn(interfaces) + for i = 1, num do + local v = interfaces[i] + interfaces[i] = nil + interfaces[v] = true + end + table_setn(interfaces, 0) + for interface in pairs(interfaces) do + traverseInterfaces(interface, interfaces) + end + for interface in pairs(interfaces) do + for field,kind in pairs(interface.interface) do + if kind ~= "nil" then + local good = false + for bit in pairs(export) do + if bit == field then + good = true + break + end + end + if not good then + AceOO:error("Mixin does not fully accommodate field %q", field) + end + end + end + end + end + self.super = Mixin.prototype + Mixin.super.prototype.init(self) + self.export = export + self.interfaces = interfaces + end +end + +-- @class Interface +-- @brief A class to create interfaces, which contain contracts that classes +-- which inherit from this must comply with. +-- +-- @object Interface prototype +-- @brief The prototype that interface objects must adhere to. +-- +-- @method Interface prototype init +-- @brief Initialize the mixin object. +-- @param interface The interface we contract (the hash of fields forced). +-- @param (a1..a20) Superinterfaces +do + Interface = Class() + function Interface:ToString() + if self.GetLibraryVersion then + return (self:GetLibraryVersion()) + else + return 'Instance' + end + end + function Interface.prototype:init(interface, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + Interface.super.prototype.init(self) + AceOO:argCheck(interface, 2, "table") + for k,v in pairs(interface) do + if type(k) ~= "string" then + AceOO:error("All keys to argument #2 must be numbers.") + elseif type(v) ~= "string" then + AceOO:error("All values to argument #2 must be strings.") + elseif v ~= "nil" and v ~= "string" and v ~= "number" and v ~= "table" and v ~= "function" then + AceOO:error('All values to argument #2 must either be "nil", "string", "number", "table", or "function".') + end + end + local l = getlibrary + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), l(a8), l(a9), l(a10), l(a11), l(a12), l(a13), l(a14), l(a15), l(a16), l(a17), l(a18), l(a19), l(a20) + if a1 then + self.superinterfaces = {a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20} + for k,v in ipairs(self.superinterfaces) do + if not inherits(v, Interface) or not v.class then + AceOO:error('Cannot provide a non-Interface to inherit from') + end + end + local num = table.getn(self.superinterfaces) + for i = 1, num do + local v = self.superinterfaces[i] + self.superinterfaces[i] = nil + self.superinterfaces[v] = true + end + table_setn(self.superinterfaces, 0) + end + self.interface = interface + end +end + +-- @function Classpool +-- @brief Obtain a read only class from our pool of classes, indexed by the +-- superclass and mixins. +-- @param sc The superclass of the class we want. +-- @param (m1..m20) Mixins of the class we want's objects. +-- @return A read only class from the class pool. +local Classpool +do + local pool = setmetatable({}, {__mode = 'v'}) + local function newindex(k, v) + AceOO:error('Attempt to modify a read-only class.') + end + local function protonewindex(k, v) + AceOO:error('Attempt to modify a read-only class prototype.') + end + local function ts(bit) + if type(bit) ~= "table" then + return tostring(bit) + elseif getmetatable(bit) and bit.__tostring then + return tostring(bit) + elseif type(bit.GetLibraryVersion) == "function" then + return bit:GetLibraryVersion() + else + return tostring(bit) + end + end + local t + local function getcomplexuid(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, + m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20) + if not t then t = {} end + if sc then if sc.uid then table.insert(t, sc.uid) else AceOO:error("%s is not an appropriate class/mixin", ts(sc)) end + if m1 then if m1.uid then table.insert(t, m1.uid) else AceOO:error("%s is not an appropriate mixin", ts(m1)) end + if m2 then if m2.uid then table.insert(t, m2.uid) else AceOO:error("%s is not an appropriate mixin", ts(m2)) end + if m3 then if m3.uid then table.insert(t, m3.uid) else AceOO:error("%s is not an appropriate mixin", ts(m3)) end + if m4 then if m4.uid then table.insert(t, m4.uid) else AceOO:error("%s is not an appropriate mixin", ts(m4)) end + if m5 then if m5.uid then table.insert(t, m5.uid) else AceOO:error("%s is not an appropriate mixin", ts(m5)) end + if m6 then if m6.uid then table.insert(t, m6.uid) else AceOO:error("%s is not an appropriate mixin", ts(m6)) end + if m7 then if m7.uid then table.insert(t, m7.uid) else AceOO:error("%s is not an appropriate mixin", ts(m7)) end + if m8 then if m8.uid then table.insert(t, m8.uid) else AceOO:error("%s is not an appropriate mixin", ts(m8)) end + if m9 then if m9.uid then table.insert(t, m9.uid) else AceOO:error("%s is not an appropriate mixin", ts(m9)) end + if m10 then if m10.uid then table.insert(t, m10.uid) else AceOO:error("%s is not an appropriate mixin", ts(m10)) end + if m11 then if m11.uid then table.insert(t, m11.uid) else AceOO:error("%s is not an appropriate mixin", ts(m11)) end + if m12 then if m12.uid then table.insert(t, m12.uid) else AceOO:error("%s is not an appropriate mixin", ts(m12)) end + if m13 then if m13.uid then table.insert(t, m13.uid) else AceOO:error("%s is not an appropriate mixin", ts(m13)) end + if m14 then if m14.uid then table.insert(t, m14.uid) else AceOO:error("%s is not an appropriate mixin", ts(m14)) end + if m15 then if m15.uid then table.insert(t, m15.uid) else AceOO:error("%s is not an appropriate mixin", ts(m15)) end + if m16 then if m16.uid then table.insert(t, m16.uid) else AceOO:error("%s is not an appropriate mixin", ts(m16)) end + if m17 then if m17.uid then table.insert(t, m17.uid) else AceOO:error("%s is not an appropriate mixin", ts(m17)) end + if m18 then if m18.uid then table.insert(t, m18.uid) else AceOO:error("%s is not an appropriate mixin", ts(m18)) end + if m19 then if m19.uid then table.insert(t, m19.uid) else AceOO:error("%s is not an appropriate mixin", ts(m19)) end + if m20 then if m20.uid then table.insert(t, m20.uid) else AceOO:error("%s is not an appropriate mixin", ts(m20)) end + end end end end end end end end end end end end end end end end end end end end end + table.sort(t) + local uid = table.concat(t, '') + for k in pairs(t) do t[k] = nil end + table_setn(t, 0) + return uid + end + local classmeta + function Classpool(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, + m10, m11, m12, m13, m14, m15, m16, + m17, m18, m19, m20) + local l = getlibrary + sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20 = l(sc), l(m1), l(m2), l(m3), l(m4), l(m5), l(m6), l(m7), l(m8), l(m9), l(m10), l(m11), l(m12), l(m13), l(m14), l(m15), l(m16), l(m17), l(m18), l(m19), l(m20) + if sc and sc.class then + sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20 = Class, sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19 + end + sc = sc or Class + local key = getcomplexuid(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20) + if not pool[key] then + local class = Class(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, + m10, m11, m12, m13, m14, m15, m16, m17, + m18, m19, m20) + if not classmeta then + classmeta = {} + local mt = getmetatable(class) + for k,v in pairs(mt) do + classmeta[k] = v + end + classmeta.__newindex = newindex + end + -- Prevent the user from adding methods to this class. + -- NOTE: I'm not preventing modifications of existing class members, + -- but it's likely that only a truly malicious user will be doing so. + class.sealed = true + setmetatable(class, classmeta) + getmetatable(class.prototype).__newindex = protonewindex + pool[key] = class + end + return pool[key] + end +end + +AceOO.Factory = Factory +AceOO.Object = Object +AceOO.Class = Class +AceOO.Mixin = Mixin +AceOO.Interface = Interface +AceOO.Classpool = Classpool +AceOO.inherits = inherits + +-- Library handling bits + +local function activate(self, oldLib, oldDeactivate) + AceOO = self + Factory = self.Factory + Object = self.Object + Class = self.Class + ClassFactory.prototype = Class + Mixin = self.Mixin + Interface = self.Interface + Classpool = self.Classpool + + if oldLib then + self.classes = oldLib.classes + end + if not self.classes then + self.classes = setmetatable({}, {__mode="k"}) + else + for class in pairs(self.classes) do + class.new = class_new + end + end + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +AceLibrary:Register(AceOO, MAJOR_VERSION, MINOR_VERSION, activate) +AceOO = AceLibrary(MAJOR_VERSION) diff --git a/Localization.lua b/Localization.lua index 115faf7..ed1d67c 100644 --- a/Localization.lua +++ b/Localization.lua @@ -1,172 +1,246 @@ -BCS = BCS or {} - -BCS["L"] = { - - ["([%d.]+)%% chance to crit"] = "([%d.]+)%% chance to crit", - - ["^Set: Improves your chance to hit by (%d)%%."] = "^Set: Improves your chance to hit by (%d)%%.", - ["^Set: Improves your chance to get a critical strike with spells by (%d)%%."] = "^Set: Improves your chance to get a critical strike with spells by (%d)%%.", - ["^Set: Improves your chance to hit with spells by (%d)%%."] = "^Set: Improves your chance to hit with spells by (%d)%%.", - ["^Set: Increases damage and healing done by magical spells and effects by up to (%d+)%."] = "^Set: Increases damage and healing done by magical spells and effects by up to (%d+)%.", - ["^Set: Increases healing done by spells and effects by up to (%d+)%."] = "^Set: Increases healing done by spells and effects by up to (%d+)%.", - ["^Set: Allows (%d+)%% of your Mana regeneration to continue while casting."] = "^Set: Allows (%d+)%% of your Mana regeneration to continue while casting.", - - ["Equip: Improves your chance to hit by (%d)%%."] = "Equip: Improves your chance to hit by (%d)%%.", - ["Equip: Improves your chance to get a critical strike with spells by (%d)%%."] = "Equip: Improves your chance to get a critical strike with spells by (%d)%%.", - ["Equip: Improves your chance to hit with spells by (%d)%%."] = "Equip: Improves your chance to hit with spells by (%d)%%.", - - ["Increases your chance to hit with melee weapons by (%d)%%."] = "Increases your chance to hit with melee weapons by (%d)%%.", - ["Increases your critical strike chance with ranged weapons by (%d)%%."] = "Increases your critical strike chance with ranged weapons by (%d)%%.", - ["Increases hit chance by (%d)%% and increases the chance movement impairing effects will be resisted by an additional %d+%%."] = "Increases hit chance by (%d)%% and increases the chance movement impairing effects will be resisted by an additional %d+%%.", - ["Increases your critical strike chance with all attacks by (%d)%%."] = "Increases your critical strike chance with all attacks by (%d)%%.", - ["Increases spell damage and healing by up to (%d+)%% of your total Spirit."] = "Increases spell damage and healing by up to (%d+)%% of your total Spirit.", - ["Allows (%d+)%% of your Mana regeneration to continue while casting."] = "Allows (%d+)%% of your Mana regeneration to continue while casting.", - ["Reduces the chance that the opponent can resist your Frost and Fire spells by (%d)%%."] = "Reduces the chance that the opponent can resist your Frost and Fire spells by (%d)%%.", - ["Reduces the chance that the opponent can resist your Arcane spells by (%d+)%%."] = "Reduces the chance that the opponent can resist your Arcane spells by (%d+)%%.", - ["Reduces your target's chance to resist your Shadow spells by (%d+)%%."] = "Reduces your target's chance to resist your Shadow spells by (%d+)%%.", - - ["Equip: Increases damage done by Arcane spells and effects by up to (%d+)."] = "Equip: Increases damage done by Arcane spells and effects by up to (%d+).", - ["Equip: Increases damage done by Fire spells and effects by up to (%d+)."] = "Equip: Increases damage done by Fire spells and effects by up to (%d+).", - ["Equip: Increases damage done by Frost spells and effects by up to (%d+)."] = "Equip: Increases damage done by Frost spells and effects by up to (%d+).", - ["Equip: Increases damage done by Holy spells and effects by up to (%d+)."] = "Equip: Increases damage done by Holy spells and effects by up to (%d+).", - ["Equip: Increases damage done by Nature spells and effects by up to (%d+)."] = "Equip: Increases damage done by Nature spells and effects by up to (%d+).", - ["Equip: Increases damage done by Shadow spells and effects by up to (%d+)."] = "Equip: Increases damage done by Shadow spells and effects by up to (%d+).", - - ["Shadow Damage %+(%d+)"] = "Shadow Damage %+(%d+)", - ["Spell Damage %+(%d+)"] = "Spell Damage %+(%d+)", - ["Fire Damage %+(%d+)"] = "Fire Damage %+(%d+)", - ["Frost Damage %+(%d+)"] = "Frost Damage %+(%d+)", - ["Healing Spells %+(%d+)"] = "Healing Spells %+(%d+)", - ["^Healing %+(%d+) and %d+ mana per 5 sec."] = "^Healing %+(%d+) and %d+ mana per 5 sec.", - - ["Equip: Restores (%d+) mana per 5 sec."] = "Equip: Restores (%d+) mana per 5 sec.", - ["+(%d)%% Hit"] = "+(%d)%% Hit", - - -- Random Bonuses // https://wow.gamepedia.com/index.php?title=SuffixId&oldid=204406 - ["^%+(%d+) Damage and Healing Spells"] = "^%+(%d+) Damage and Healing Spells", - ["^%+(%d+) Arcane Spell Damage"] = "^%+(%d+) Arcane Spell Damage", - ["^%+(%d+) Fire Spell Damage"] = "^%+(%d+) Fire Spell Damage", - ["^%+(%d+) Frost Spell Damage"] = "^%+(%d+) Frost Spell Damage", - ["^%+(%d+) Holy Spell Damage"] = "^%+(%d+) Holy Spell Damage", - ["^%+(%d+) Nature Spell Damage"] = "^%+(%d+) Nature Spell Damage", - ["^%+(%d+) Shadow Spell Damage"] = "^%+(%d+) Shadow Spell Damage", - ["^%+(%d+) mana every 5 sec."] = "^%+(%d+) mana every 5 sec.", - ["Restores (%d+) mana every 1 sec."] = "Restores (%d+) mana every 1 sec.", - ["(%d+)%% of your Mana regeneration continuing while casting."] = "(%d+)%% of your Mana regeneration continuing while casting.", - - - -- Mana Oils - ["^Brilliant Mana Oil %((%d+) min%"] = "^Brilliant Mana Oil %((%d+) min%", - ["^Lesser Mana Oil ((%d+) min)"] = "^Lesser Mana Oil ((%d+) min)", - ["^Minor Mana Oil ((%d+) min)"] = "^Minor Mana Oil ((%d+) min)", - - -- snowflakes ZG enchants - ["/Hit %+(%d+)"] = "/Hit %+(%d+)", - ["/Spell Hit %+(%d+)"] = "/Spell Hit %+(%d+)", - ["^Mana Regen %+(%d+)"] = "^Mana Regen %+(%d+)", - ["^Healing %+%d+ and (%d+) mana per 5 sec."] = "^Healing %+%d+ and (%d+) mana per 5 sec.", - ["^%+(%d+) Healing Spells"] = "^%+(%d+) Healing Spells", - ["^%+(%d+) Spell Damage and Healing"] = "^%+(%d+) Spell Damage and Healing", - - -- Mana Oils - ["^Brilliant Mana Oil %((%d+) min%"] = "^Brilliant Mana Oil %((%d+) min%", - - ["Equip: Increases damage and healing done by magical spells and effects by up to (%d+)."] = "Equip: Increases damage and healing done by magical spells and effects by up to (%d+).", - ["Equip: Increases healing done by spells and effects by up to (%d+)."] = "Equip: Increases healing done by spells and effects by up to (%d+).", - - -- auras - ["Chance to hit increased by (%d)%%."] = "Chance to hit increased by (%d)%%.", - ["Magical damage dealt is increased by up to (%d+)."] = "Magical damage dealt is increased by up to (%d+).", - ["Healing done by magical spells is increased by up to (%d+)."] = "Healing done by magical spells is increased by up to (%d+).", - ["Increases healing done by magical spells by up to (%d+) for 3600 sec."] = "Increases healing done by magical spells by up to (%d+) for 3600 sec.", - ["Healing increased by up to (%d+)."] = "Healing increased by up to (%d+).", - ["Healing spells increased by up to (%d+)."] = "Healing spells increased by up to (%d+).", - ["Chance to hit reduced by (%d+)%%."] = "Chance to hit reduced by (%d+)%%.", - ["Chance to hit decreased by (%d+)%% and %d+ Nature damage every %d+ sec."] = "Chance to hit decreased by (%d+)%% and %d+ Nature damage every %d+ sec.", - ["Lowered chance to hit."] = "Lowered chance to hit.", -- 5917 Fumble (25%) - ["Increases hitpoints by 300. 15%% haste to melee attacks. (%d+) mana regen every 5 seconds."] = "Increases hitpoints by 300. 15%% haste to melee attacks. (%d+) mana regen every 5 seconds.", - ["Restores (%d+) mana per 5 sec."] = "Restores (%d+) mana per 5 sec.", - ["Regenerating (%d+) Mana every 5 seconds."] = "Regenerating (%d+) Mana every 5 seconds.", - ["Regenerate (%d+) mana per 5 sec."] = "Regenerate (%d+) mana per 5 sec.", - ["Mana Regeneration increased by (%d+) every 5 seconds."] = "Mana Regeneration increased by (%d+) every 5 seconds.", - ["Improves your chance to hit by (%d+)%%."] = "Improves your chance to hit by (%d+)%%.", - ["Chance for a critical hit with a spell increased by (%d+)%%."] = "Chance for a critical hit with a spell increased by (%d+)%%.", - ["While active, target's critical hit chance with spells and attacks increases by 10%%."] = "While active, target's critical hit chance with spells and attacks increases by 10%%.", - ["Increases attack power by %d+ and chance to hit by (%d+)%%."] = "Increases attack power by %d+ and chance to hit by (%d+)%%.", - ["Holy spell critical hit chance increased by (%d+)%%."] = "Holy spell critical hit chance increased by (%d+)%%.", - ["Destruction spell critical hit chance increased by (%d+)%%."] = "Destruction spell critical hit chance increased by (%d+)%%.", - ["Arcane spell critical hit chance increased by (%d+)%%.\r\nArcane spell critical hit damage increased by (%d+)%%."] = "Arcane spell critical hit chance increased by (%d+)%%.\r\nArcane spell critical hit damage increased by (%d+)%%.", - ["Spell hit chance increased by (%d+)%%."] = "Spell hit chance increased by (%d+)%%.", - - ["Increases chance for a melee, ranged, or spell critical by (%d+)%% and all attributes by %d+."] = "Increases chance for a melee, ranged, or spell critical by (%d+)%% and all attributes by %d+.", - ["Melee critical-hit chance reduced by (%d+)%%.\r\nSpell critical-hit chance reduced by (%d+)%%."] = "Melee critical-hit chance reduced by (%d+)%%.\r\nSpell critical-hit chance reduced by (%d+)%%.", - ["Increases critical chance of spells by 10%%, melee and ranged by 5%% and grants 140 attack power. 120 minute duration."] = "Increases critical chance of spells by 10%%, melee and ranged by 5%% and grants 140 attack power. 120 minute duration.", - ["Holy spell critical hit chance increased by (%d+)%%."] = "Holy spell critical hit chance increased by (%d+)%%.", - ["Destruction spell critical hit chance increased by (%d+)%%."] = "Destruction spell critical hit chance increased by (%d+)%%.", - ["Critical strike chance with spells and melee attacks increased by (%d+)%%."] = "Critical strike chance with spells and melee attacks increased by (%d+)%%.", - - - ["MELEE_HIT_TOOLTIP"] = [[|cffffffffHit|r - Result of an attack made with - melee or ranged weapons.]], - - ["SPELL_HIT_TOOLTIP"] = [[|cffffffffHit|r - Result of an attack made with - spells.]], - - ["SPELL_HIT_SECONDARY_TOOLTIP"] = [[|cffffffffHit %d%% (%d%%|cff20ff20+%d%% %s|r|cffffffff)|r - Result of an attack made with - spells.]], - - ["SPELL_POWER_TOOLTIP"] = [[|cffffffffSpell Power %d|r - Increases damage done by spells and effects.]], - - ["SPELL_POWER_SECONDARY_TOOLTIP"] = [[|cffffffffSpell Power %d (%d|cff20ff20+%d %s|r|cffffffff)|r - Increases damage done by spells and effects.]], - - ["SPELL_HEALING_POWER_TOOLTIP"] = [[|cffffffffHealing Power %d (%d|cff20ff20+%d|r|cffffffff)|r - Increases healing done by spells and effects.]], - - ["SPELL_MANA_REGEN_TOOLTIP"] = [[|cffffffffMana regen: %d |cffBF40BF(%d)|r - Mana regen when not casting and |cffBF40BF(while casting)|r. - Mana regenerates every 2 seconds and the amount - is dependent on your total spirit and MP5. - Spirit Regen: |cff7DF9FF%d|r - %%Regen while casting: |cffBF40BF%d%%|r - MP5 Regen: |cff20ff20%d|r - MP5 Regen (2s): |cff20ff20%d|r]], - - ["ROGUE_MELEE_HIT_TOOLTIP"] = [[ -+5% hit to always hit enemy players. -+8% hit to always hit with your special abilities against a raid boss. -+24.6% hit to always hit a raid boss.]], - - PLAYERSTAT_BASE_STATS = "Base Stats", - PLAYERSTAT_DEFENSES = "Defenses", - PLAYERSTAT_MELEE_COMBAT = "Melee", - PLAYERSTAT_RANGED_COMBAT = "Ranged", - PLAYERSTAT_SPELL_COMBAT = "Spell", - PLAYERSTAT_SPELL_SCHOOLS = "Schools", - - MELEE_HIT_RATING_COLON = "Hit Rating:", - RANGED_HIT_RATING_COLON = "Hit Rating:", - SPELL_HIT_RATING_COLON = "Hit Rating:", - MELEE_CRIT_COLON = "Crit Chance:", - RANGED_CRIT_COLON = "Crit Chance:", - SPELL_CRIT_COLON = "Crit Chance:", - MANA_REGEN_COLON = "Mana regen:", - HEAL_POWER_COLON = "Healing:", - DODGE_COLON = DODGE .. ":", - PARRY_COLON = PARRY .. ":", - BLOCK_COLON = BLOCK .. ":", - - SPELL_POWER_COLON = "Power:", - - SPELL_SCHOOL_ARCANE = "Arcane", - SPELL_SCHOOL_FIRE = "Fire", - SPELL_SCHOOL_FROST = "Frost", - SPELL_SCHOOL_HOLY = "Holy", - SPELL_SCHOOL_NATURE = "Nature", - SPELL_SCHOOL_SHADOW = "Shadow", - -} \ No newline at end of file +BCS = BCS or {} + +BCS["L"] = { + ["%+(%d+)%% Critical Strike"] = "%+(%d+)%% Critical Strike", + ["([%d.]+)%% chance to crit"] = "([%d.]+)%% chance to crit", + + ["^Set: Improves your chance to hit by (%d)%%."] = "^Set: Improves your chance to hit by (%d)%%.", + ["^Set: Improves your chance to get a critical strike with spells by (%d)%%."] = "^Set: Improves your chance to get a critical strike with spells by (%d)%%.", + ["^Set: Improves your chance to hit with spells by (%d)%%."] = "^Set: Improves your chance to hit with spells by (%d)%%.", + ["^Set: Increases damage and healing done by magical spells and effects by up to (%d+)%."] = "^Set: Increases damage and healing done by magical spells and effects by up to (%d+)%.", + ["^Set: Increases healing done by spells and effects by up to (%d+)%."] = "^Set: Increases healing done by spells and effects by up to (%d+)%.", + ["^Set: Allows (%d+)%% of your Mana regeneration to continue while casting."] = "^Set: Allows (%d+)%% of your Mana regeneration to continue while casting.", + ["^Set: Improves your chance to get a critical strike by (%d)%%."] = "^Set: Improves your chance to get a critical strike by (%d)%%.", + + ["Equip: Improves your chance to hit by (%d)%%."] = "Equip: Improves your chance to hit by (%d)%%.", + ["Equip: Improves your chance to get a critical strike with spells by (%d)%%."] = "Equip: Improves your chance to get a critical strike with spells by (%d)%%.", + ["Equip: Improves your chance to hit with spells by (%d)%%."] = "Equip: Improves your chance to hit with spells by (%d)%%.", + ["Equip: Improves your chance to get a critical strike by (%d)%%."] = "Equip: Improves your chance to get a critical strike by (%d)%%.", + ["Increases your chance to hit with melee weapons by (%d)%%."] = "Increases your chance to hit with melee weapons by (%d)%%.", + ["Increases your critical strike chance with ranged weapons by (%d)%%."] = "Increases your critical strike chance with ranged weapons by (%d)%%.", + ["Increases hit chance by (%d)%% and increases the chance movement impairing effects will be resisted by an additional %d+%%."] = "Increases hit chance by (%d)%% and increases the chance movement impairing effects will be resisted by an additional %d+%%.", + ["Increases your critical strike chance with all attacks by (%d)%%."] = "Increases your critical strike chance with all attacks by (%d)%%.", + ["Increases spell damage and healing by up to (%d+)%% of your total Spirit."] = "Increases spell damage and healing by up to (%d+)%% of your total Spirit.", + ["Allows (%d+)%% of your Mana regeneration to continue while casting."] = "Allows (%d+)%% of your Mana regeneration to continue while casting.", + ["Reduces the chance that the opponent can resist your Frost and Fire spells by (%d)%%."] = "Reduces the chance that the opponent can resist your Frost and Fire spells by (%d)%%.", + ["Reduces the chance that the opponent can resist your Arcane spells by (%d+)%%."] = "Reduces the chance that the opponent can resist your Arcane spells by (%d+)%%.", + ["Reduces your target's chance to resist your Shadow spells by (%d+)%%."] = "Reduces your target's chance to resist your Shadow spells by (%d+)%%.", + + ["Equip: Increases damage done by Arcane spells and effects by up to (%d+)."] = "Equip: Increases damage done by Arcane spells and effects by up to (%d+).", + ["Equip: Increases damage done by Fire spells and effects by up to (%d+)."] = "Equip: Increases damage done by Fire spells and effects by up to (%d+).", + ["Equip: Increases damage done by Frost spells and effects by up to (%d+)."] = "Equip: Increases damage done by Frost spells and effects by up to (%d+).", + ["Equip: Increases damage done by Holy spells and effects by up to (%d+)."] = "Equip: Increases damage done by Holy spells and effects by up to (%d+).", + ["Equip: Increases damage done by Nature spells and effects by up to (%d+)."] = "Equip: Increases damage done by Nature spells and effects by up to (%d+).", + ["Equip: Increases damage done by Shadow spells and effects by up to (%d+)."] = "Equip: Increases damage done by Shadow spells and effects by up to (%d+).", + + ["Shadow Damage %+(%d+)"] = "Shadow Damage %+(%d+)", + ["Spell Damage %+(%d+)"] = "Spell Damage %+(%d+)", + ["Arcane Damage %+(%d+)"] = "Arcane Damage %+(%d+)", + ["Fire Damage %+(%d+)"] = "Fire Damage %+(%d+)", + ["Frost Damage %+(%d+)"] = "Frost Damage %+(%d+)", + ["Healing Spells %+(%d+)"] = "Healing Spells %+(%d+)", + ["^Healing %+(%d+) and %d+ mana per 5 sec."] = "^Healing %+(%d+) and %d+ mana per 5 sec.", + + ["Equip: Restores (%d+) mana per 5 sec."] = "Equip: Restores (%d+) mana per 5 sec.", + ["+(%d)%% Ranged Hit"] = "+(%d)%% Ranged Hit", + + -- Random Bonuses // https://wow.gamepedia.com/index.php?title=SuffixId&oldid=204406 + ["^%+(%d+) Damage and Healing Spells"] = "^%+(%d+) Damage and Healing Spells", + ["^%+(%d+) Arcane Spell Damage"] = "^%+(%d+) Arcane Spell Damage", + ["^%+(%d+) Fire Spell Damage"] = "^%+(%d+) Fire Spell Damage", + ["^%+(%d+) Frost Spell Damage"] = "^%+(%d+) Frost Spell Damage", + ["^%+(%d+) Holy Spell Damage"] = "^%+(%d+) Holy Spell Damage", + ["^%+(%d+) Nature Spell Damage"] = "^%+(%d+) Nature Spell Damage", + ["^%+(%d+) Shadow Spell Damage"] = "^%+(%d+) Shadow Spell Damage", + ["^%+(%d+) mana every 5 sec."] = "^%+(%d+) mana every 5 sec.", + ["Restores (%d+) mana every 1 sec."] = "Restores (%d+) mana every 1 sec.", + ["(%d+)%% of your Mana regeneration continuing while casting."] = "(%d+)%% of your Mana regeneration continuing while casting.", + ["(%d+)%% of your mana regeneration to continue while casting."] = "(%d+)%% of your mana regeneration to continue while casting.", + + -- Mana Oils + ["^Brilliant Mana Oil %((%d+) min%"] = "^Brilliant Mana Oil %((%d+) min%", + ["^Lesser Mana Oil ((%d+) min)"] = "^Lesser Mana Oil ((%d+) min)", + ["^Minor Mana Oil ((%d+) min)"] = "^Minor Mana Oil ((%d+) min)", + + -- snowflakes ZG enchants + ["/Hit %+(%d+)"] = "/Hit %+(%d+)", + ["/Spell Hit %+(%d+)"] = "/Spell Hit %+(%d+)", + ["^Mana Regen %+(%d+)"] = "^Mana Regen %+(%d+)", + ["^Healing %+%d+ and (%d+) mana per 5 sec."] = "^Healing %+%d+ and (%d+) mana per 5 sec.", + ["^%+(%d+) Healing Spells"] = "^%+(%d+) Healing Spells", + ["^%+(%d+) Spell Damage and Healing"] = "^%+(%d+) Spell Damage and Healing", + + -- sapphiron shoulder enchants + ["%+(%d+)%% Critical Strike"] = "%+(%d+)%% Critical Strike", + + ["Equip: Increases damage and healing done by magical spells and effects by up to (%d+)."] = "Equip: Increases damage and healing done by magical spells and effects by up to (%d+).", + ["Equip: Increases healing done by spells and effects by up to (%d+)."] = "Equip: Increases healing done by spells and effects by up to (%d+).", + + -- auras + ["Chance to hit increased by (%d)%%."] = "Chance to hit increased by (%d)%%.", + ["Magical damage dealt is increased by up to (%d+)."] = "Magical damage dealt is increased by up to (%d+).", + ["Healing done by magical spells is increased by up to (%d+)."] = "Healing done by magical spells is increased by up to (%d+).", + ["Increases healing done by magical spells by up to (%d+) for 3600 sec."] = "Increases healing done by magical spells by up to (%d+) for 3600 sec.", + ["Healing increased by up to (%d+)."] = "Healing increased by up to (%d+).", + ["Healing spells increased by up to (%d+)."] = "Healing spells increased by up to (%d+).", + ["Chance to hit reduced by (%d+)%%."] = "Chance to hit reduced by (%d+)%%.", + ["Chance to hit decreased by (%d+)%% and %d+ Nature damage every %d+ sec."] = "Chance to hit decreased by (%d+)%% and %d+ Nature damage every %d+ sec.", + ["Lowered chance to hit."] = "Lowered chance to hit.", -- 5917 Fumble (25%) + ["Increases hitpoints by 300. 15%% haste to melee attacks. (%d+) mana regen every 5 seconds."] = "Increases hitpoints by 300. 15%% haste to melee attacks. (%d+) mana regen every 5 seconds.", + ["Restores (%d+) mana per 5 sec."] = "Restores (%d+) mana per 5 sec.", + ["Regenerating (%d+) Mana every 5 seconds."] = "Regenerating (%d+) Mana every 5 seconds.", + ["Regenerate (%d+) mana per 5 sec."] = "Regenerate (%d+) mana per 5 sec.", + ["Mana Regeneration increased by (%d+) every 5 seconds."] = "Mana Regeneration increased by (%d+) every 5 seconds.", + ["Improves your chance to hit by (%d+)%%."] = "Improves your chance to hit by (%d+)%%.", + ["Chance for a critical hit with a spell increased by (%d+)%%."] = "Chance for a critical hit with a spell increased by (%d+)%%.", + ["While active, target's critical hit chance with spells and attacks increases by 10%%."] = "While active, target's critical hit chance with spells and attacks increases by 10%%.", --?? + ["Increases attack power by %d+ and chance to hit by (%d+)%%."] = "Increases attack power by %d+ and chance to hit by (%d+)%%.", + ["Holy spell critical hit chance increased by (%d+)%%."] = "Holy spell critical hit chance increased by (%d+)%%.", + ["Destruction spell critical hit chance increased by (%d+)%%."] = "Destruction spell critical hit chance increased by (%d+)%%.", + ["Arcane spell critical hit chance increased by (%d+)%%.\r\nArcane spell critical hit damage increased by (%d+)%%."] = "Arcane spell critical hit chance increased by (%d+)%%.\r\nArcane spell critical hit damage increased by (%d+)%%.", + ["Spell hit chance increased by (%d+)%%."] = "Spell hit chance increased by (%d+)%%.", + ["Agility increased by 25, Critical hit chance increases by (%d)%%."] = "Agility increased by 25, Critical hit chance increases by (%d)%%.", + ["Increases chance for a melee, ranged, or spell critical by (%d+)%% and all attributes by %d+."] = "Increases chance for a melee, ranged, or spell critical by (%d+)%% and all attributes by %d+.", + ["Melee critical-hit chance reduced by (%d+)%%.\r\nSpell critical-hit chance reduced by (%d+)%%."] = "Melee critical-hit chance reduced by (%d+)%%.\r\nSpell critical-hit chance reduced by (%d+)%%.", + ["Increases critical chance of spells by 10%%, melee and ranged by 5%% and grants 140 attack power. 120 minute duration."] = "Increases critical chance of spells by 10%%, melee and ranged by 5%% and grants 140 attack power. 120 minute duration.", + ["Critical strike chance with spells and melee attacks increased by (%d+)%%."] = "Critical strike chance with spells and melee attacks increased by (%d+)%%.", + ["Increases ranged and melee critical chance by (%d+)%%."] = "Increases ranged and melee critical chance by (%d+)%%.", + ["Equip: Increases the critical chance provided by Leader of the Pack and Moonkin Aura by (%d)%%."] = "Equip: Increases the critical chance provided by Leader of the Pack and Moonkin Aura by (%d)%%.", + -- druid + ["Increases the damage and critical strike chance of your Moonfire spell by (%d+)%%."] = "Increases the damage and critical strike chance of your Moonfire spell by (%d+)%%.", + ["Increases the critical effect chance of your Regrowth spell by (%d+)%%."] = "Increases the critical effect chance of your Regrowth spell by (%d+)%%.", + -- paladin + ["Increases your healing power by (%d+)%% of your Armor."] = "Increases your healing power by (%d+)%% of your Armor.", + ["Increases the critical effect chance of your Holy Light and Flash of Light by (%d+)%%."] = "Increases the critical effect chance of your Holy Light and Flash of Light by (%d+)%%.", + ["Improves your chance to get a critical strike with Holy Shock by (%d+)%%."] = "Improves your chance to get a critical strike with Holy Shock by (%d+)%%.", + -- shaman + ["Increases the critical strike chance of your Lightning Bolt and Chain Lightning spells by an additional (%d+)%%."] = "Increases the critical strike chance of your Lightning Bolt and Chain Lightning spells by an additional (%d+)%%.", + ["Increases the critical effect chance of your healing and lightning spells by (%d+)%%."] = "Increases the critical effect chance of your healing and lightning spells by (%d+)%%.", + -- warlock + ["Increases the critical strike chance of your Destruction spells by (%d+)%%."] = "Increases the critical strike chance of your Destruction spells by (%d+)%%.", + ["Increases the critical strike chance of your Searing Pain spell by (%d+)%%."] = "Increases the critical strike chance of your Searing Pain spell by (%d+)%%.", + ["Reduces the chance for enemies to resist your Affliction spells by (%d+)%%."] = "Reduces the chance for enemies to resist your Affliction spells by (%d+)%%.", + -- mage + ["Increases the critical strike chance of your Arcane Explosion and Arcane Missiles spells by an additional (%d+)%%."] = "Increases the critical strike chance of your Arcane Explosion and Arcane Missiles spells by an additional (%d+)%%.", + ["Increases the critical strike chance of your Fire Blast and Scorch spells by (%d+)%%."] = "Increases the critical strike chance of your Fire Blast and Scorch spells by (%d+)%%.", + ["Increases the critical strike chance of your Flamestrike spell by (%d+)%%."] = "Increases the critical strike chance of your Flamestrike spell by (%d+)%%.", + ["Increases the critical strike chance of your Fire spells by (%d+)%%."] = "Increases the critical strike chance of your Fire spells by (%d+)%%.", + ["Increases the critical strike chance of all your spells against frozen targets by (%d+)%%."] = "Increases the critical strike chance of all your spells against frozen targets by (%d+)%%.", + ["Increases your spell damage and critical srike chance by (%d+)%%."] = "Increases your spell damage and critical srike chance by (%d+)%%.", + ["Increases critical strike chance from Fire damage spells by (%d+)%%."] = "Increases critical strike chance from Fire damage spells by (%d+)%%.", + -- priest + ["Reduces the chance for enemies to resist your Holy and Discipline spells by (%d+)%%."] = "Reduces the chance for enemies to resist your Holy and Discipline spells by (%d+)%%.", + ["Increases the critical effect chance of your Holy and Discipline spells by (%d+)%%."] = "Increases the critical effect chance of your Holy and Discipline spells by (%d+)%%.", + ["Increases your spell damage by %d+%% and the critical strike chance of your offensive spells by (%d)%%"] = "Increases your spell damage by %d+%% and the critical strike chance of your offensive spells by (%d)%%", + ["^Set: Improves your chance to get a critical strike with Holy spells by (%d)%%."] = "^Set: Improves your chance to get a critical strike with Holy spells by (%d)%%.", + ["^Set: Increases your chance of a critical hit with Prayer of Healing by (%d+)%%."] = "^Set: Increases your chance of a critical hit with Prayer of Healing by (%d+)%%.", + --defense + ["DEFENSE_TOOLTIP"] = [[|cffffffffDefense Skill|r]], + ["DEFENSE_TOOLTIP_SUB"] = [[Higher defense makes you harder to hit and makes monsters less likely to land a crushing blow.]], + + ["PLAYER_DODGE_TOOLTIP"] = [[|cffffffffDodge|r]], + ["PLAYER_DODGE_TOOLTIP_SUB"] = [[Your chance to dodge enemy melee attacks. + Players can not dodge attacks from behind.]], + + ["PLAYER_PARRY_TOOLTIP"] = [[|cffffffffParry|r]], + ["PLAYER_PARRY_TOOLTIP_SUB"] = [[Your chance to parry enemy melee attacks. + Players and monsters can not parry attacks from behind.]], + + ["PLAYER_BLOCK_TOOLTIP"] = [[|cffffffffBlock|r]], + ["PLAYER_BLOCK_TOOLTIP_SUB"] = [[Your chance to block enemy physical attacks with a shield. + Players and monsters can not block attacks from behind.]], + + ["TOTAL_AVOIDANCE_TOOLTIP"] = [[|cffffffffAvoidance|r]], + ["TOTAL_AVOIDANCE_TOOLTIP_SUB"] = [[Your total chance to avoid enemy physical attacks. Subtract 2.4 vs bosses.]], + + --melee + ["MELEE_HIT_TOOLTIP"] = [[|cffffffffMelee Hit|r]], + ["MELEE_HIT_TOOLTIP_SUB"] = [[Increases chance to hit with melee attacks.]], + ["MELEE_CRIT_TOOLTIP"] = [[|cffffffffMelee Crit|r]], + ["MELEE_CRIT_TOOLTIP_SUB"] = [[Your chance to land a critical strike with melee attacks.]], + ["MELEE_WEAPON_SKILL_TOOLTIP"] = [[|cffffffffMelee Weapon Skill|r]], + ["MELEE_WEAPON_SKILL_TOOLTIP_SUB"] = [[Higher weapon skill reduces your chance to miss and increases damage of your glancing blows, while using melee weapons.]], + + --boss + ["MELEE_MISS_VS_BOSS_TOOLTIP"] = [[|cffffffffChance To Miss Boss(lvl 63)|r]], + ["MELEE_MISS_VS_BOSS_TOOLTIP_SUB"] = [[Melee attack miss chance due to mob being higher level than you.]], + ["MELEE_DODGE_VS_BOSS_TOOLTIP"] = [[|cffffffffChance Boss(lvl 63) Dodges|r]], + ["MELEE_DODGE_VS_BOSS_TOOLTIP_SUB"] = [[The formula is 5%% dodge chance + (your weapon skill - 315) * 0.1%%.]], + ["MELEE_GLANCE_VS_BOSS_TOOLTIP"] = [[|cffffffffPercent Glancing Damage vs Boss(lvl 63)|r]], + ["MELEE_GLANCE_VS_BOSS_TOOLTIP_SUB"] = [[Against lvl 63 Boss you have a 40%% chance to do a glancing blow that does reduced damage. The amount of damage reduction is based on your weapon skill but the formula is too complicated to show here.]], + ["MELEE_CRIT_CAP_VS_BOSS_TOOLTIP"] = [[|cffffffffCritical Chance Cap|r]], + ["MELEE_CRIT_CAP_VS_BOSS_TOOLTIP_SUB"] = [[A critical hit can only happen if an attack is not already a miss, dodged, or glancing. This means your crit cap is (100%% - miss chance%% - dodge chance%% - glance chance%%). Any crit chance you have over your crit cap is wasted.]], + ["MELEE_EFF_CRIT_VS_BOSS_TOOLTIP"] = [[|cffffffffEffective Critical Chance|r]], + ["MELEE_EFF_CRIT_VS_BOSS_TOOLTIP_SUB"] = [[If you are under your crit cap, this will match your normal crit chance. If you are over your crit cap, this will be your crit cap.]], + + --ranged + ["RANGED_WEAPON_SKILL_TOOLTIP"] = [[|cffffffffRanged Weapon Skill|r]], + ["RANGED_WEAPON_SKILL_TOOLTIP_SUB"] = [[Higher weapon skill reduces your chance to miss with a ranged weapon.]], + ["RANGED_CRIT_TOOLTIP"] = [[|cffffffffRanged Crit|r]], + ["RANGED_CRIT_TOOLTIP_SUB"] = [[Your chance to land a critical strike with ranged weapons.]], + ["RANGED_HIT_TOOLTIP"] = [[|cffffffffRanged Hit|r]], + ["RANGED_HIT_TOOLTIP_SUB"] = [[Increases chance to hit with ranged weapons.]], + + --spells + ["SPELL_HIT_TOOLTIP"] = [[|cffffffffSpell Hit|r]], + ["SPELL_HIT_SECONDARY_TOOLTIP"] = [[|cffffffffSpell Hit (%d%%|cff20ff20+%d%% %s|r|cffffffff)|r]], + ["SPELL_HIT_TOOLTIP_SUB"] = [[Increases chance to land a harmful spell.]], + + ["SPELL_CRIT_TOOLTIP"] = [[|cffffffffSpell Crit|r]], + ["SPELL_CRIT_TOOLTIP_SUB"] = [[Your chance to land a critical strike with spells.]], + + ["SPELL_POWER_TOOLTIP"] = [[|cffffffffSpell Power %d|r]], + ["SPELL_POWER_TOOLTIP_SUB"] = [[Increases damage done by spells and effects.]], + ["SPELL_POWER_SECONDARY_TOOLTIP"] = [[|cffffffffSpell Power %d (%d|cff20ff20+%d %s|r|cffffffff)|r]], + ["SPELL_POWER_SECONDARY_TOOLTIP_SUB"] = [[Increases damage done by spells and effects.]], + + ["SPELL_SCHOOL_TOOLTIP"] = [[|cffffffff%s Spell Power %s|r]], + ["SPELL_SCHOOL_SECONDARY_TOOLTIP"] = [[|cffffffff%s Spell Power %d (%d|cff20ff20+%d|r|cffffffff)|r]], + ["SPELL_SCHOOL_TOOLTIP_SUB"] = [[Increases damage done by %s spells and effects.]], + + ["SPELL_HEALING_POWER_TOOLTIP"] = [[|cffffffffHealing Power %d|r]], + ["SPELL_HEALING_POWER_SECONDARY_TOOLTIP"] = [[|cffffffffHealing Power %d (%d|cff20ff20+%d|r|cffffffff)|r]], + ["SPELL_HEALING_POWER_TOOLTIP_SUB"] = [[Increases healing done by spells and effects.]], + + ["SPELL_MANA_REGEN_TOOLTIP"] = [[|cffffffffMana regen: %d |cffffffff(%d)|r]], + ["SPELL_MANA_REGEN_TOOLTIP_SUB"] = [[Mana regen when not casting and (while casting). + Mana regenerates every 2 seconds and the amount is dependent on your total spirit and MP5. + Spirit Regen: %d + Regen while casting: %d%% + MP5 Regen: %d + MP5 Regen (2s): %d]], + + PLAYERSTAT_BASE_STATS = "Base Stats", + PLAYERSTAT_DEFENSES = "Defenses", + PLAYERSTAT_DEFENSES_BOSS = "Defenses vs Boss", + PLAYERSTAT_MELEE_COMBAT = "Melee", + PLAYERSTAT_MELEE_BOSS = "Melee vs Boss", + PLAYERSTAT_RANGED_COMBAT = "Ranged", + PLAYERSTAT_SPELL_COMBAT = "Spell", + PLAYERSTAT_SPELL_SCHOOLS = "Schools", + WEAPON_SKILL_COLON = "Wep Skill:", + MISS_CHANCE_COLON = "Miss %:", + DODGE_CHANCE_COLON = "Dodge %:", + GLANCE_REDUCTION_COLON = "Glance Dmg:", + CRIT_CAP_COLON = "Crit Cap:", + BOSS_CRIT_COLON = "Eff. Crit:", + MELEE_HIT_RATING_COLON = "Hit Rating:", + RANGED_HIT_RATING_COLON = "Hit Rating:", + SPELL_HIT_RATING_COLON = "Hit Rating:", + MELEE_CRIT_COLON = "Crit Chance:", + RANGED_CRIT_COLON = "Crit Chance:", + SPELL_CRIT_COLON = "Crit Chance:", + MANA_REGEN_COLON = "Mana regen:", + HEAL_POWER_COLON = "Healing:", + DODGE_COLON = DODGE .. ":", + PARRY_COLON = PARRY .. ":", + BLOCK_COLON = BLOCK .. ":", + TOTAL_COLON = "Total:", + SPELL_POWER_COLON = "Power:", + SPELL_SCHOOL_ARCANE = "Arcane", + SPELL_SCHOOL_FIRE = "Fire", + SPELL_SCHOOL_FROST = "Frost", + SPELL_SCHOOL_HOLY = "Holy", + SPELL_SCHOOL_NATURE = "Nature", + SPELL_SCHOOL_SHADOW = "Shadow", +} diff --git a/README.md b/README.md index 8e36f63..ebcce4e 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,20 @@ -BetterCharacterStats - a World of Warcraft (1.12.1) AddOn -=================================================== - -![preview](https://raw.githubusercontent.com/yutsuku/BetterCharacterStats/gh-pages/images/BetterCharacterStats.png) - -Installation: - -Put "BetterCharacterStats" folder into ".../World of Warcraft/Interface/AddOns/". -Create AddOns folder if necessary - -After Installation directory tree should look like the following - - World of Warcraft - `- Interface - `- AddOns - `- BetterCharacterStats - |- README.md - |- BetterCharacterStats.lua - |- BetterCharacterStats.toc - |- BetterCharacterStats.xml - |- helper.lua - `- Localization.lua - -Features: -- Displays character statistics in one place (just like the character tab in Burning Crusade). - -Known Issues: -- May be lacking things related to spell hit/spell crit. - -Thanks to: -- All people who keeps reporting to me that some things are missing or are broken. \ No newline at end of file +# BetterCharacterStats for Turtle WoW +This addon shows your character stats that are not present in default UI.
+This version is designed specifically for TurtleWoW and its custom changes. +## Features + - Base stats: strength, agility, stamina, intellect, spirit, armor + - Melee/Ranged: weapon skill, damage, attack speed, attack power, hit, crit + - Spells: spell power, spell hit, crit, healing power, mana regeneration + - Schools: your spell power for each school of magic + - Defenses: armor, defense skill, dodge, parry, block, avoidance +## Preview +![preview1](https://github.com/user-attachments/assets/d342aed0-812c-40f9-a4d4-9b33eb48caa3) + +## Installation +1. Click Code -> Download ZIP +2. Extract ZIP file into your Interface/AddOnns folder, remove ``-main`` +3. Restart the game +## Feedback +If you find any bugs, inaccuracies, typos or just want to suggest something, open an [issue](https://github.com/Otari98/BetterCharacterStats/issues) on GitHub +## Thanks to original author and all contributors +Moh, Bennylava, Lexie, Spit, Pepopo diff --git a/helper.lua b/helper.lua index 2bb87c0..bb71f0c 100644 --- a/helper.lua +++ b/helper.lua @@ -21,6 +21,65 @@ local function tContains(table, item) return nil end +BCScache = BCScache or { + ["gear"] = { + damage_and_healing = 0, + arcane = 0, + fire = 0, + frost = 0, + holy = 0, + nature = 0, + shadow = 0, + healing = 0, + mp5 = 0, + casting = 0, + spell_hit = 0, + spell_crit = 0, + hit = 0, + ranged_hit = 0, + ranged_crit = 0 + }, + ["talents"] = { + damage_and_healing = 0, + healing = 0, + spell_hit = 0, + spell_hit_fire = 0, + spell_hit_frost = 0, + spell_hit_arcane = 0, + spell_hit_shadow = 0, + spell_hit_holy = 0, + spell_crit = 0, + casting = 0, + mp5 = 0, + hit = 0, + ranged_hit = 0, + ranged_crit = 0 + }, + ["auras"] = { + damage_and_healing = 0, + only_damage = 0, -- +dmg to all schools, needed to calculate healing + arcane = 0, + fire = 0, + frost = 0, + holy = 0, + nature = 0, + shadow = 0, + healing = 0, + mp5 = 0, + casting = 0, + spell_hit = 0, + spell_crit = 0, + hit = 0, + ranged_hit = 0, + ranged_crit = 0, + hit_debuff = 0 + }, + ["skills"] = { + mh = 0, + oh = 0, + ranged = 0 + } +} function BCS:GetPlayerAura(searchText, auraType) if not auraType then -- buffs @@ -30,9 +89,7 @@ function BCS:GetPlayerAura(searchText, auraType) local index = GetPlayerBuff(i, 'HELPFUL') if index > -1 then BCS_Tooltip:SetPlayerBuff(index) - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do + for line=1, BCS_Tooltip:NumLines() do local left = getglobal(BCS_Prefix .. "TextLeft" .. line) if left:GetText() then if left:GetText() == "Power of the Guardian" and searchText == "Power of the Guardian Crit" then @@ -52,9 +109,7 @@ function BCS:GetPlayerAura(searchText, auraType) local index = GetPlayerBuff(i, auraType) if index > -1 then BCS_Tooltip:SetPlayerBuff(index) - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do + for line=1, BCS_Tooltip:NumLines() do local left = getglobal(BCS_Prefix .. "TextLeft" .. line) if left:GetText() then local value = {strfind(left:GetText(), searchText)} @@ -68,174 +123,128 @@ function BCS:GetPlayerAura(searchText, auraType) end end -local Cache_GetHitRating_Tab, Cache_GetHitRating_Talent -local hit_debuff = 0 function BCS:GetHitRating(hitOnly) local Hit_Set_Bonus = {} - local hit = 0; - local MAX_INVENTORY_SLOTS = 19; - hit_debuff = 0; - - for slot=0, MAX_INVENTORY_SLOTS do - local hasItem = BCS_Tooltip:SetInventoryItem("player", slot) - if hasItem then - local MAX_LINES = BCS_Tooltip:NumLines() - local SET_NAME = nil - - for line=1, MAX_LINES do - local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["Equip: Improves your chance to hit by (%d)%%."]) - if value then - hit = hit + tonumber(value) - end - _,_, value = strfind(left:GetText(), L["/Hit %+(%d+)"]) - if value then - hit = hit + tonumber(value) - end - _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") - if value then - SET_NAME = value - end - _,_, value = strfind(left:GetText(), L["^Set: Improves your chance to hit by (%d)%%."]) - if value and SET_NAME and not tContains(Hit_Set_Bonus, SET_NAME) then - tinsert(Hit_Set_Bonus, SET_NAME) - hit = hit + tonumber(value) - line = MAX_LINES + local hit = 0 + + if BCS.needScanGear then + BCScache["gear"].hit = 0 + --scan gear + for slot=1, 19 do + if BCS_Tooltip:SetInventoryItem('player', slot) then + local _, _, eqItemLink = strfind(GetInventoryItemLink('player', slot), "(item:%d+:%d+:%d+:%d+)") + if eqItemLink then BCS_Tooltip:ClearLines() BCS_Tooltip:SetHyperlink(eqItemLink) end + local SET_NAME = nil + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _,_, value = strfind(left:GetText(), L["Equip: Improves your chance to hit by (%d)%%."]) + if value then + BCScache["gear"].hit = BCScache["gear"].hit + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["/Hit %+(%d+)"]) + if value then + BCScache["gear"].hit = BCScache["gear"].hit + tonumber(value) + end + + _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") + if value then + SET_NAME = value + end + _,_, value = strfind(left:GetText(), L["^Set: Improves your chance to hit by (%d)%%."]) + if value and SET_NAME and not tContains(Hit_Set_Bonus, SET_NAME) then + tinsert(Hit_Set_Bonus, SET_NAME) + BCScache["gear"].hit = BCScache["gear"].hit + tonumber(value) + break + end end end end - end end - -- buffs - local _, _, hitFromAura = BCS:GetPlayerAura(L["Chance to hit increased by (%d)%%."]) - if hitFromAura then - hit = hit + tonumber(hitFromAura) - end - _, _, hitFromAura = BCS:GetPlayerAura(L["Improves your chance to hit by (%d+)%%."]) - if hitFromAura then - hit = hit + tonumber(hitFromAura) - end - _, _, hitFromAura = BCS:GetPlayerAura(L["Increases attack power by %d+ and chance to hit by (%d+)%%."]) - if hitFromAura then - hit = hit + tonumber(hitFromAura) - end - -- debuffs - _, _, hitFromAura = BCS:GetPlayerAura(L["Chance to hit reduced by (%d+)%%."], 'HARMFUL') - if hitFromAura then - hit_debuff = hit_debuff + tonumber(hitFromAura) - end - _, _, hitFromAura = BCS:GetPlayerAura(L["Chance to hit decreased by (%d+)%% and %d+ Nature damage every %d+ sec."], 'HARMFUL') - if hitFromAura then - hit_debuff = hit_debuff + tonumber(hitFromAura) - end - hitFromAura = BCS:GetPlayerAura(L["Lowered chance to hit."], 'HARMFUL') - if hitFromAura then - hit_debuff = hit_debuff + 25 - end - - local MAX_TABS = GetNumTalentTabs() - -- speedup - if Cache_GetHitRating_Tab and Cache_GetHitRating_Talent then - BCS_Tooltip:SetTalent(Cache_GetHitRating_Tab, Cache_GetHitRating_Talent) - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do - local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - -- rogues - local _,_, value = strfind(left:GetText(), L["Increases your chance to hit with melee weapons by (%d)%%."]) - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(Cache_GetHitRating_Tab, Cache_GetHitRating_Talent) - if value and rank > 0 then - hit = hit + tonumber(value) - line = MAX_LINES - end - - -- hunters - _,_, value = strfind(left:GetText(), L["Increases hit chance by (%d)%% and increases the chance movement impairing effects will be resisted by an additional %d+%%."]) - name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(Cache_GetHitRating_Tab, Cache_GetHitRating_Talent) - if value and rank > 0 then - hit = hit + tonumber(value) - line = MAX_LINES - end - end + if BCS.needScanAuras then + BCScache["auras"].hit = 0 + BCScache["auras"].hit_debuff = 0 + -- buffs + local _, _, hitFromAura = BCS:GetPlayerAura(L["Chance to hit increased by (%d)%%."]) + if hitFromAura then + BCScache["auras"].hit = BCScache["auras"].hit + tonumber(hitFromAura) end - - if not hitOnly then - hit = hit - hit_debuff - if hit < 0 then hit = 0 end - return hit - else - return hit + _, _, hitFromAura = BCS:GetPlayerAura(L["Improves your chance to hit by (%d+)%%."]) + if hitFromAura then + BCScache["auras"].hit = BCScache["auras"].hit + tonumber(hitFromAura) + end + _, _, hitFromAura = BCS:GetPlayerAura(L["Increases attack power by %d+ and chance to hit by (%d+)%%."]) + if hitFromAura then + BCScache["auras"].hit = BCScache["auras"].hit + tonumber(hitFromAura) + end + -- debuffs + _, _, hitFromAura = BCS:GetPlayerAura(L["Chance to hit reduced by (%d+)%%."], 'HARMFUL') + if hitFromAura then + BCScache["auras"].hit_debuff = BCScache["auras"].hit_debuff + tonumber(hitFromAura) + end + _, _, hitFromAura = BCS:GetPlayerAura(L["Chance to hit decreased by (%d+)%% and %d+ Nature damage every %d+ sec."], 'HARMFUL') + if hitFromAura then + BCScache["auras"].hit_debuff = BCScache["auras"].hit_debuff + tonumber(hitFromAura) + end + hitFromAura = BCS:GetPlayerAura(L["Lowered chance to hit."], 'HARMFUL') + if hitFromAura then + BCScache["auras"].hit_debuff = BCScache["auras"].hit_debuff + 25 end end - - for tab=1, MAX_TABS do - local MAX_TALENTS = GetNumTalents(tab) - - for talent=1, MAX_TALENTS do - BCS_Tooltip:SetTalent(tab, talent); - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do - local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - -- rogues - local _,_, value = strfind(left:GetText(), L["Increases your chance to hit with melee weapons by (%d)%%."]) - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - hit = hit + tonumber(value) - - Cache_GetHitRating_Tab = tab - Cache_GetHitRating_Talent = talent - - line = MAX_LINES - talent = MAX_TALENTS - tab = MAX_TABS - end - - -- hunters - _,_, value = strfind(left:GetText(), L["Increases hit chance by (%d)%% and increases the chance movement impairing effects will be resisted by an additional %d+%%."]) - name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - hit = hit + tonumber(value) - - Cache_GetHitRating_Tab = tab - Cache_GetHitRating_Talent = talent - - line = MAX_LINES - talent = MAX_TALENTS - tab = MAX_TABS - end - - -- Druid - -- Natural Weapons - _,_, value = strfind(left:GetText(), "Also increases chance to hit with melee attacks and spells by (%d+)%%.") - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - hit = hit + tonumber(value) - line = MAX_LINES - end - - -- Paladin & Shaman - -- Precision & Nature's Guidance - _,_, value = strfind(left:GetText(), "Increases your chance to hit with melee attacks and spells by (%d+)%%.") - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - hit = hit + tonumber(value) - line = MAX_LINES + + if BCS.needScanTalents then + BCScache["talents"].hit = 0 + --scan talents + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Rogue + local _,_, value = strfind(left:GetText(), L["Increases your chance to hit with melee weapons by (%d)%%."]) + if value and rank > 0 then + BCScache["talents"].hit = BCScache["talents"].hit + tonumber(value) + break + end + -- Hunter + _,_, value = strfind(left:GetText(), L["Increases hit chance by (%d)%% and increases the chance movement impairing effects will be resisted by an additional %d+%%."]) + if value and rank > 0 then + BCScache["talents"].hit = BCScache["talents"].hit + tonumber(value) + break + end + -- Druid + -- Natural Weapons + _,_, value = strfind(left:GetText(), "Also increases chance to hit with melee attacks and spells by (%d+)%%.") + if value and rank > 0 then + BCScache["talents"].hit = BCScache["talents"].hit + tonumber(value) + break + end + -- Paladin + -- Precision + _,_, value = strfind(left:GetText(), "Increases your chance to hit with melee attacks and spells by (%d+)%%.") + if value and rank > 0 then + BCScache["talents"].hit = BCScache["talents"].hit + tonumber(value) + break + end + -- Shaman + -- Elemental Devastation + _,_, value = strfind(left:GetText(), "Increases your chance to hit with spells and melee attacks by (%d+)%%") + if value and rank > 0 then + BCScache["talents"].hit = BCScache["talents"].hit + tonumber(value) + break + end end - - end + end end - end end - + hit = BCScache["talents"].hit + BCScache["gear"].hit + BCScache["auras"].hit if not hitOnly then - hit = hit - hit_debuff + hit = hit - BCScache["auras"].hit_debuff if hit < 0 then hit = 0 end -- Dust Cloud OP return hit else @@ -244,26 +253,22 @@ function BCS:GetHitRating(hitOnly) end function BCS:GetRangedHitRating() - local melee_hit = BCS:GetHitRating(true) - local ranged_hit = melee_hit - local debuff = hit_debuff - - local hasItem = BCS_Tooltip:SetInventoryItem("player", 18) -- ranged enchant - if hasItem then - local MAX_LINES = BCS_Tooltip:NumLines() - for line=1, MAX_LINES do - local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["+(%d)%% Hit"]) - if value then - ranged_hit = ranged_hit + tonumber(value) - line = MAX_LINES + if BCS.needScanGear then + BCScache["gear"].ranged_hit = 0 + if BCS_Tooltip:SetInventoryItem("player", 18) then + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _,_, value = strfind(left:GetText(), L["+(%d)%% Ranged Hit"]) + if value then + BCScache["gear"].ranged_hit = BCScache["gear"].ranged_hit + tonumber(value) + break + end end end end end - - ranged_hit = ranged_hit - debuff + local ranged_hit = BCS:GetHitRating(true) + BCScache["gear"].ranged_hit - BCScache["auras"].hit_debuff if ranged_hit < 0 then ranged_hit = 0 end return ranged_hit end @@ -274,885 +279,1676 @@ function BCS:GetSpellHitRating() local hit_frost = 0 local hit_arcane = 0 local hit_shadow = 0 + local hit_holy = 0 local hit_Set_Bonus = {} - - -- scan gear - local MAX_INVENTORY_SLOTS = 19 - for slot=0, MAX_INVENTORY_SLOTS do - local hasItem = BCS_Tooltip:SetInventoryItem("player", slot) - - if hasItem then - local SET_NAME - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do - local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["Equip: Improves your chance to hit with spells by (%d)%%."]) - if value then - hit = hit + tonumber(value) - end - _,_, value = strfind(left:GetText(), L["/Spell Hit %+(%d+)"]) - if value then - hit = hit + tonumber(value) - end - - _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") - if value then - SET_NAME = value - end - _, _, value = strfind(left:GetText(), L["^Set: Improves your chance to hit with spells by (%d)%%."]) - if value and SET_NAME and not tContains(hit_Set_Bonus, SET_NAME) then - tinsert(hit_Set_Bonus, SET_NAME) - hit = hit + tonumber(value) + if BCS.needScanGear then + BCScache["gear"].spell_hit = 0 + -- scan gear + for slot=1, 19 do + if BCS_Tooltip:SetInventoryItem('player', slot) then + local _, _, eqItemLink = strfind(GetInventoryItemLink('player', slot), "(item:%d+:%d+:%d+:%d+)") + if eqItemLink then BCS_Tooltip:ClearLines() BCS_Tooltip:SetHyperlink(eqItemLink) end + local SET_NAME + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _,_, value = strfind(left:GetText(), L["Equip: Improves your chance to hit with spells by (%d)%%."]) + if value then + BCScache["gear"].spell_hit = BCScache["gear"].spell_hit + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["/Spell Hit %+(%d+)"]) + if value then + BCScache["gear"].spell_hit = BCScache["gear"].spell_hit + tonumber(value) + end + + _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") + if value then + SET_NAME = value + end + _, _, value = strfind(left:GetText(), L["^Set: Improves your chance to hit with spells by (%d)%%."]) + if value and SET_NAME and not tContains(hit_Set_Bonus, SET_NAME) then + tinsert(hit_Set_Bonus, SET_NAME) + BCScache["gear"].spell_hit = BCScache["gear"].spell_hit + tonumber(value) + end end end end - end end - - -- scan talents - local MAX_TABS = GetNumTalentTabs() - - for tab=1, MAX_TABS do - local MAX_TALENTS = GetNumTalents(tab) - - for talent=1, MAX_TALENTS do - BCS_Tooltip:SetTalent(tab, talent) - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do - local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - -- Mage - -- Elemental Precision - local _,_, value = strfind(left:GetText(), L["Reduces the chance that the opponent can resist your Frost and Fire spells by (%d)%%."]) - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - hit_fire = hit_fire + tonumber(value) - hit_frost = hit_frost + tonumber(value) - line = MAX_LINES - end - - -- Arcane Focus - _,_, value = strfind(left:GetText(), L["Reduces the chance that the opponent can resist your Arcane spells by (%d+)%%."]) - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - hit_arcane = hit_arcane + tonumber(value) - line = MAX_LINES - end - - -- Priest - -- Shadow Focus - _,_, value = strfind(left:GetText(), L["Reduces your target's chance to resist your Shadow spells by (%d+)%%."]) - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - hit_shadow = hit_shadow + tonumber(value) - line = MAX_LINES - end - - -- Druid - -- Natural Weapons - _,_, value = strfind(left:GetText(), "Also increases chance to hit with melee attacks and spells by (%d+)%%.") - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - hit = hit + tonumber(value) - line = MAX_LINES - end - - -- Paladin & Shaman - -- Precision & Nature's Guidance - _,_, value = strfind(left:GetText(), "Increases your chance to hit with melee attacks and spells by (%d+)%%.") - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - hit = hit + tonumber(value) - line = MAX_LINES + if BCS.needScanTalents then + BCScache["talents"].spell_hit = 0 + BCScache["talents"].spell_hit_fire = 0 + BCScache["talents"].spell_hit_frost = 0 + BCScache["talents"].spell_hit_arcane = 0 + BCScache["talents"].spell_hit_shadow = 0 + BCScache["talents"].spell_hit_holy = 0 + -- scan talents + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Mage + -- Elemental Precision + local _,_, value = strfind(left:GetText(), L["Reduces the chance that the opponent can resist your Frost and Fire spells by (%d)%%."]) + if value and rank > 0 then + BCScache["talents"].spell_hit_fire = BCScache["talents"].spell_hit_fire + tonumber(value) + BCScache["talents"].spell_hit_frost = BCScache["talents"].spell_hit_frost + tonumber(value) + break + end + -- Arcane Focus + _,_, value = strfind(left:GetText(), L["Reduces the chance that the opponent can resist your Arcane spells by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].spell_hit_arcane = BCScache["talents"].spell_hit_arcane + tonumber(value) + break + end + -- Priest + -- Piercing Light + _,_, value = strfind(left:GetText(), L["Reduces the chance for enemies to resist your Holy and Discipline spells by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].spell_hit_holy = BCScache["talents"].spell_hit_holy + tonumber(value) + break + end + -- Shadow Focus + _,_, value = strfind(left:GetText(), L["Reduces your target's chance to resist your Shadow spells by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].spell_hit_shadow = BCScache["talents"].spell_hit_shadow + tonumber(value) + break + end + -- Druid + -- Natural Weapons + _,_, value = strfind(left:GetText(), "Also increases chance to hit with melee attacks and spells by (%d+)%%.") + if value and rank > 0 then + BCScache["talents"].spell_hit = BCScache["talents"].spell_hit + tonumber(value) + break + end + -- Paladin + -- Precision + _,_, value = strfind(left:GetText(), "Increases your chance to hit with melee attacks and spells by (%d+)%%.") + if value and rank > 0 then + BCScache["talents"].spell_hit = BCScache["talents"].spell_hit + tonumber(value) + break + end + -- Shaman + -- Elemental Devastation + _,_, value = strfind(left:GetText(), "Increases your chance to hit with spells and melee attacks by (%d+)%%") + if value and rank > 0 then + BCScache["talents"].spell_hit = BCScache["talents"].spell_hit + tonumber(value) + break + end + -- Warlock + -- Suppression + _,_, value = strfind(left:GetText(), L["Reduces the chance for enemies to resist your Affliction spells by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].spell_hit_shadow = BCScache["talents"].spell_hit_shadow + tonumber(value) + break + end end - end + end end - end end - -- buffs - local _, _, hitFromAura = BCS:GetPlayerAura(L["Spell hit chance increased by (%d+)%%."]) - if hitFromAura then - hit = hit + tonumber(hitFromAura) + if BCS.needScanAuras then + BCScache["auras"].spell_hit = 0 + local _, _, hitFromAura = BCS:GetPlayerAura(L["Spell hit chance increased by (%d+)%%."]) + if hitFromAura then + BCScache["auras"].spell_hit = BCScache["auras"].spell_hit + tonumber(hitFromAura) + end + -- Elemental Devastation + _, _, hitFromAura = BCS:GetPlayerAura("Increases your chance to hit with spells by (%d+)%%") + if hitFromAura then + BCScache["auras"].spell_hit = BCScache["auras"].spell_hit + tonumber(hitFromAura) + end end - - return hit, hit_fire, hit_frost, hit_arcane, hit_shadow + hit = BCScache["gear"].spell_hit + BCScache["talents"].spell_hit + BCScache["auras"].spell_hit + hit_fire = BCScache["talents"].spell_hit_fire + hit_frost = BCScache["talents"].spell_hit_frost + hit_arcane = BCScache["talents"].spell_hit_arcane + hit_shadow = BCScache["talents"].spell_hit_shadow + hit_holy = BCScache["talents"].spell_hit_holy + return hit, hit_fire, hit_frost, hit_arcane, hit_shadow, hit_holy end -local Cache_GetCritChance_SpellID, Cache_GetCritChance_BookType, Cache_GetCritChance_Line -local Cache_GetCritChance_Tab, Cache_GetCritChance_Talent function BCS:GetCritChance() local crit = 0 - local _, class = UnitClass('player') - - if class == 'HUNTER' then - - local MAX_TABS = GetNumTalentTabs() - -- speedup - if Cache_GetCritChance_Tab and Cache_GetCritChance_Talent then - BCS_Tooltip:SetTalent(Cache_GetCritChance_Tab, Cache_GetCritChance_Talent) - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do - local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["Increases your critical strike chance with all attacks by (%d)%%."]) - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(Cache_GetCritChance_Tab, Cache_GetCritChance_Talent) - if value and rank > 0 then - crit = crit + tonumber(value) - line = MAX_LINES - end - end - end - else - for tab=1, MAX_TABS do - local MAX_TALENTS = GetNumTalents(tab) - for talent=1, MAX_TALENTS do - BCS_Tooltip:SetTalent(tab, talent); - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do - local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["Increases your critical strike chance with all attacks by (%d)%%."]) - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - crit = crit + tonumber(value) - - Cache_GetCritChance_Tab = tab - Cache_GetCritChance_Talent = talent - - line = MAX_LINES - talent = MAX_TALENTS - tab = MAX_TABS - end - end - end - - end - end - end - - end - - -- speedup - if Cache_GetCritChance_SpellID and Cache_GetCritChance_BookType and Cache_GetCritChance_Line then - - BCS_Tooltip:SetSpell(Cache_GetCritChance_SpellID, Cache_GetCritChance_BookType) - local left = getglobal(BCS_Prefix .. "TextLeft" .. Cache_GetCritChance_Line) - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["([%d.]+)%% chance to crit"]) - if value then - crit = crit + tonumber(value) - end - end - - return crit - end - - local MAX_TABS = GetNumSpellTabs() - - for tab=1, MAX_TABS do - local name, texture, offset, numSpells = GetSpellTabInfo(tab) - + --scan spellbook + for tab=1, GetNumSpellTabs() do + local _, _, offset, numSpells = GetSpellTabInfo(tab) for spell=1, numSpells do local currentPage = ceil(spell/SPELLS_PER_PAGE) local SpellID = spell + offset + ( SPELLS_PER_PAGE * (currentPage - 1)) - BCS_Tooltip:SetSpell(SpellID, BOOKTYPE_SPELL) - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do + for line=1, BCS_Tooltip:NumLines() do local left = getglobal(BCS_Prefix .. "TextLeft" .. line) if left:GetText() then local _,_, value = strfind(left:GetText(), L["([%d.]+)%% chance to crit"]) if value then crit = crit + tonumber(value) - - Cache_GetCritChance_SpellID = SpellID - Cache_GetCritChance_BookType = BOOKTYPE_SPELL - Cache_GetCritChance_Line = line - - line = MAX_LINES - spell = numSpells - tab = MAX_TABS + break end end end - end end - + return crit end -local Cache_GetRangedCritChance_Tab, Cache_GetRangedCritChance_Talent, Cache_GetRangedCritChance_Line function BCS:GetRangedCritChance() - local crit = BCS:GetCritChance() - - if Cache_GetRangedCritChance_Tab and Cache_GetRangedCritChance_Talent and Cache_GetRangedCritChance_Line then - BCS_Tooltip:SetTalent(Cache_GetRangedCritChance_Tab, Cache_GetRangedCritChance_Talent) - local left = getglobal(BCS_Prefix .. "TextLeft" .. Cache_GetRangedCritChance_Line) - - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["Increases your critical strike chance with ranged weapons by (%d)%%."]) - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(Cache_GetRangedCritChance_Tab, Cache_GetRangedCritChance_Talent) - if value and rank > 0 then - crit = crit + tonumber(value) + -- values from vmangos core + local crit = 0 + local _, class = UnitClass("player") + local _, agility = UnitStat("player", 2) + local vallvl1 = 0 + local vallvl60 = 0 + local classrate = 0 + + if class == "MAGE" then + vallvl1 = 12.9 + vallvl60 = 20 + elseif class == "ROGUE" then + vallvl1 = 2.2 + vallvl60 = 29 + elseif class == "HUNTER" then + vallvl1 = 3.5 + vallvl60 = 53 + elseif class == "PRIEST" then + vallvl1 = 11 + vallvl60 = 20 + elseif class == "WARLOCK" then + vallvl1 = 8.4 + vallvl60 = 20 + elseif class == "WARRIOR" then + vallvl1 = 3.9 + vallvl60 = 20 + else + return crit + end + + classrate = vallvl1 * (60 - UnitLevel("player")) / 59 + vallvl60 * (UnitLevel("player") - 1) / 59 + crit = agility / classrate + + if BCS.needScanTalents then + BCScache["talents"].ranged_crit = 0 + --scan talents + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + local _,_, value = strfind(left:GetText(), L["Increases your critical strike chance with ranged weapons by (%d)%%."]) + if value and rank > 0 then + BCScache["talents"].ranged_crit = BCScache["talents"].ranged_crit + tonumber(value) + break + end + _,_, value = strfind(left:GetText(), L["Increases your critical strike chance with all attacks by (%d)%%."]) + if value and rank > 0 then + BCScache["talents"].ranged_crit = BCScache["talents"].ranged_crit + tonumber(value) + break + end + end + end end end - - return crit end - - local MAX_TABS = GetNumTalentTabs() - - for tab=1, MAX_TABS do - local MAX_TALENTS = GetNumTalents(tab) - - for talent=1, MAX_TALENTS do - BCS_Tooltip:SetTalent(tab, talent); - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do - local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["Increases your critical strike chance with ranged weapons by (%d)%%."]) - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - crit = crit + tonumber(value) - - line = MAX_LINES - talent = MAX_TALENTS - tab = MAX_TABS + + if BCS.needScanGear then + BCScache["gear"].ranged_crit = 0 + --scan gear + local Crit_Set_Bonus = {} + for slot=1, 19 do + if BCS_Tooltip:SetInventoryItem('player', slot) then + local _, _, eqItemLink = strfind(GetInventoryItemLink('player', slot), "(item:%d+:%d+:%d+:%d+)") + if eqItemLink then BCS_Tooltip:ClearLines() BCS_Tooltip:SetHyperlink(eqItemLink) end + local SET_NAME + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _,_, value = strfind(left:GetText(), L["Equip: Improves your chance to get a critical strike by (%d)%%."]) + if value then + BCScache["gear"].ranged_crit = BCScache["gear"].ranged_crit + tonumber(value) + end + _,_, value = strfind(left:GetText(), "Equip: Improves your chance to get a critical strike with missile weapons by (%d)%%.") + if value then + BCScache["gear"].ranged_crit = BCScache["gear"].ranged_crit + tonumber(value) + end + -- Might of the Scourge (shoulder enchant) + _,_, value = strfind(left:GetText(), L["%+(%d+)%% Critical Strike"]) + if value then + BCScache["gear"].ranged_crit = BCScache["gear"].ranged_crit + tonumber(value) + end + + _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") + if value then + SET_NAME = value + end + _, _, value = strfind(left:GetText(), L["^Set: Improves your chance to get a critical strike by (%d)%%."]) + if value and SET_NAME and not tContains(Crit_Set_Bonus, SET_NAME) then + tinsert(Crit_Set_Bonus, SET_NAME) + BCScache["gear"].ranged_crit = BCScache["gear"].ranged_crit + tonumber(value) + end end end end - end end - + if BCS.needScanAuras then + BCScache["auras"].ranged_crit = 0 + --buffs + --ony head + local critFromAura = BCS:GetPlayerAura(L["Increases critical chance of spells by 10%%, melee and ranged by 5%% and grants 140 attack power. 120 minute duration."]) + if critFromAura then + BCScache["auras"].ranged_crit = BCScache["auras"].ranged_crit + 5 + end + --mongoose + _, _, critFromAura = BCS:GetPlayerAura(L["Agility increased by 25, Critical hit chance increases by (%d)%%."]) + if critFromAura then + BCScache["auras"].ranged_crit = BCScache["auras"].ranged_crit + tonumber(critFromAura) + end + --songflower + _, _, critFromAura = BCS:GetPlayerAura(L["Increases chance for a melee, ranged, or spell critical by (%d+)%% and all attributes by %d+."]) + if critFromAura then + BCScache["auras"].ranged_crit = BCScache["auras"].ranged_crit + tonumber(critFromAura) + end + --leader of the pack + _, _, critFromAura = BCS:GetPlayerAura(L["Increases ranged and melee critical chance by (%d+)%%."]) + if critFromAura then + BCScache["auras"].ranged_crit = BCScache["auras"].ranged_crit + tonumber(critFromAura) + --check if druid is shapeshifted and have Idol of the Moonfang equipped + for i=1, GetNumPartyMembers() do + local _, partyClass = UnitClass("party"..i) + if partyClass == "DRUID" then + if BCS_Tooltip:SetInventoryItem("party"..i, 18) and UnitCreatureType("party"..i) == "Beast" then + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + _, _, critFromAura = strfind(left:GetText(), L["Equip: Increases the critical chance provided by Leader of the Pack and Moonkin Aura by (%d)%%."]) + if critFromAura then + BCScache["auras"].ranged_crit = BCScache["auras"].ranged_crit + tonumber(critFromAura) + break + end + end + end + end + end + end + end + end + + if class == "MAGE" then + crit = crit + 3.2 + elseif class == "PRIEST" then + crit = crit + 3 + elseif class == "WARLOCK" then + crit = crit + 2 + end + + crit = crit + BCScache["gear"].ranged_crit + BCScache["talents"].ranged_crit + BCScache["auras"].ranged_crit + return crit end function BCS:GetSpellCritChance() - -- school crit: most likely never local Crit_Set_Bonus = {} local spellCrit = 0; - local _, intelect = UnitStat("player", 4) + local _, intellect = UnitStat("player", 4) local _, class = UnitClass("player") - -- values from theorycraft / http://wow.allakhazam.com/forum.html?forum=21&mid=1157230638252681707 + -- values from vmangos core + local playerLevel = UnitLevel("player") if class == "MAGE" then - spellCrit = 0.2 + (intelect / 59.5) + spellCrit = 3.7 + intellect / (14.77 + .65 * playerLevel) elseif class == "WARLOCK" then - spellCrit = 1.7 + (intelect / 60.6) + spellCrit = 3.18 + intellect / (11.30 + .82 * playerLevel) elseif class == "PRIEST" then - spellCrit = 0.8 + (intelect / 59.56) + spellCrit = 2.97 + intellect / (10.03 + .82 * playerLevel) elseif class == "DRUID" then - spellCrit = 1.8 + (intelect / 60) + spellCrit = 3.33 + intellect / (12.41 + .79 * playerLevel) elseif class == "SHAMAN" then - spellCrit = 1.8 + (intelect / 59.2) + spellCrit = 3.54 + intellect / (11.51 + .8 * playerLevel) elseif class == "PALADIN" then - spellCrit = intelect / 29.5 + spellCrit = 3.7 + intellect / (14.77 + .65 * playerLevel) end - - local MAX_INVENTORY_SLOTS = 19 - - for slot=0, MAX_INVENTORY_SLOTS do - local hasItem = BCS_Tooltip:SetInventoryItem("player", slot) - - if hasItem then - local SET_NAME = nil - - for line=1, BCS_Tooltip:NumLines() do - local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["Equip: Improves your chance to get a critical strike with spells by (%d)%%."]) - if value then - spellCrit = spellCrit + tonumber(value) - end - - _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") - if value then - SET_NAME = value - end + if BCS.needScanGear then + BCScache["gear"].spell_crit = 0 + --scan gear + for slot=1, 19 do + if BCS_Tooltip:SetInventoryItem('player', slot) then + local _, _, eqItemLink = strfind(GetInventoryItemLink('player', slot), "(item:%d+:%d+:%d+:%d+)") + if eqItemLink then BCS_Tooltip:ClearLines() BCS_Tooltip:SetHyperlink(eqItemLink) end + local SET_NAME = nil + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _,_, value = strfind(left:GetText(), L["Equip: Improves your chance to get a critical strike with spells by (%d)%%."]) + if value then + BCScache["gear"].spell_crit = BCScache["gear"].spell_crit + tonumber(value) + end - _, _, value = strfind(left:GetText(), L["^Set: Improves your chance to get a critical strike with spells by (%d)%%."]) - if value and SET_NAME and not tContains(Crit_Set_Bonus, SET_NAME) then - tinsert(Crit_Set_Bonus, SET_NAME) - spellCrit = spellCrit + tonumber(value) + _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") + if value then + SET_NAME = value + end + _, _, value = strfind(left:GetText(), L["^Set: Improves your chance to get a critical strike with spells by (%d)%%."]) + if value and SET_NAME and not tContains(Crit_Set_Bonus, SET_NAME) then + tinsert(Crit_Set_Bonus, SET_NAME) + BCScache["gear"].spell_crit = BCScache["gear"].spell_crit + tonumber(value) + end + _,_, value = strfind(left:GetText(), "(%d)%% Spell Critical Strike") + if value then + BCScache["gear"].spell_crit = BCScache["gear"].spell_crit + tonumber(value) + end end - - local _,_, value = strfind(left:GetText(), "(%d)%% Spell Critical Strike") - if value then - spellCrit = spellCrit + tonumber(value) + end + end + end + if BCS_Tooltip:SetInventoryItem("player", 16) then + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local found = strfind(left:GetText(), "Brilliant Wizard Oil") + if found then + BCScache["gear"].spell_crit = BCScache["gear"].spell_crit + 1 end - end end end - - end - - -- buffs - local _, _, critFromAura = BCS:GetPlayerAura(L["Chance for a critical hit with a spell increased by (%d+)%%."]) - if critFromAura then - spellCrit = spellCrit + tonumber(critFromAura) - end - _, _, critFromAura = BCS:GetPlayerAura("(Moonkin Aura)") - if critFromAura then - spellCrit = spellCrit + 3 - end - _, _, critFromAura = BCS:GetPlayerAura("Power of the Guardian Crit") - if critFromAura then - spellCrit = spellCrit + tonumber(critFromAura) - end - _, _, critFromAura = BCS:GetPlayerAura("Chance to get a critical strike with spells is increased by (%d+)%%") - if critFromAura then - spellCrit = spellCrit + tonumber(critFromAura) - end - _, _, critFromAura = BCS:GetPlayerAura(L["While active, target's critical hit chance with spells and attacks increases by 10%%."]) - if critFromAura then - spellCrit = spellCrit + 10 - end - _, _, critFromAura = BCS:GetPlayerAura(L["Increases chance for a melee, ranged, or spell critical by (%d+)%% and all attributes by %d+."]) - if critFromAura then - spellCrit = spellCrit + tonumber(critFromAura) - end - critFromAura = BCS:GetPlayerAura(L["Increases critical chance of spells by 10%%, melee and ranged by 5%% and grants 140 attack power. 120 minute duration."]) - if critFromAura then - spellCrit = spellCrit + 10 - end - _, _, critFromAura = BCS:GetPlayerAura(L["Critical strike chance with spells and melee attacks increased by (%d+)%%."]) - if critFromAura then - spellCrit = spellCrit + tonumber(critFromAura) - end - -- debuffs - _, _, _, critFromAura = BCS:GetPlayerAura(L["Melee critical-hit chance reduced by (%d+)%%.\r\nSpell critical-hit chance reduced by (%d+)%%."], 'HARMFUL') - if critFromAura then - spellCrit = spellCrit - tonumber(critFromAura) end - return spellCrit -end - -function BCS:GetSpellPower(school) - if school then - if not L["Equip: Increases damage done by "..school.." spells and effects by up to (%d+)."] then return -1 end - local spellPower = 0; - local MAX_INVENTORY_SLOTS = 19 - - for slot=0, MAX_INVENTORY_SLOTS do - local hasItem = BCS_Tooltip:SetInventoryItem("player", slot) - - if hasItem then + if BCS.needScanAuras then + BCScache["auras"].spell_crit = 0 + -- buffs + local _, _, critFromAura = BCS:GetPlayerAura(L["Chance for a critical hit with a spell increased by (%d+)%%."]) + if critFromAura then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit + tonumber(critFromAura) + end + _, _, critFromAura = BCS:GetPlayerAura("(Moonkin Aura)") + if critFromAura then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit + 3 + if BCS:GetPlayerAura("Moonkin Form") and BCS_Tooltip:SetInventoryItem("player", 18) then for line=1, BCS_Tooltip:NumLines() do local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by "..school.." spells and effects by up to (%d+)."]) - if value then - spellPower = spellPower + tonumber(value) - end - if L[school.." Damage %+(%d+)"] then - _,_, value = strfind(left:GetText(), L[school.." Damage %+(%d+)"]) - if value then - spellPower = spellPower + tonumber(value) - end + _, _, critFromAura = strfind(left:GetText(), L["Equip: Increases the critical chance provided by Leader of the Pack and Moonkin Aura by (%d)%%."]) + if critFromAura then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit + tonumber(critFromAura) end - if L["^%+(%d+) "..school.." Spell Damage"] then - _,_, value = strfind(left:GetText(), L["^%+(%d+) "..school.." Spell Damage"]) - if value then - spellPower = spellPower + tonumber(value) + end + end + else + --check if druid is shapeshifted and have Idol of the Moonfang equipped + for i=1, GetNumPartyMembers() do + local _, partyClass = UnitClass("party"..i) + if partyClass == "DRUID" then + if BCS_Tooltip:SetInventoryItem("party"..i, 18) then + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + _, _, critFromAura = strfind(left:GetText(), L["Equip: Increases the critical chance provided by Leader of the Pack and Moonkin Aura by (%d)%%."]) + if critFromAura then + for buff = 1, 32 do + if UnitBuff("party"..i, buff) and UnitBuff("party"..i, buff) == "Interface\\Icons\\Spell_Nature_ForceOfNature" then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit + tonumber(critFromAura) + break + end + end + end + end end end end end end - end - - return spellPower - else - local spellPower = 0; - local arcanePower = 0; - local firePower = 0; - local frostPower = 0; - local holyPower = 0; - local naturePower = 0; - local shadowPower = 0; - local damagePower = 0; - local MAX_INVENTORY_SLOTS = 19 - - local SpellPower_Set_Bonus = {} - - -- scan gear - for slot=0, MAX_INVENTORY_SLOTS do - local hasItem = BCS_Tooltip:SetInventoryItem("player", slot) - - if hasItem then - local SET_NAME - + critFromAura = BCS:GetPlayerAura("Inner Focus") + if critFromAura then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit + 25 + end + _, _, critFromAura = BCS:GetPlayerAura("Power of the Guardian Crit") + if critFromAura then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit + tonumber(critFromAura) + end + _, _, critFromAura = BCS:GetPlayerAura("Chance to get a critical strike with spells is increased by (%d+)%%") + if critFromAura then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit + tonumber(critFromAura) + end + _, _, critFromAura = BCS:GetPlayerAura(L["While active, target's critical hit chance with spells and attacks increases by 10%%."])--SoD spell? 23964 + if critFromAura then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit + 10 + end + _, _, critFromAura = BCS:GetPlayerAura(L["Increases chance for a melee, ranged, or spell critical by (%d+)%% and all attributes by %d+."]) + if critFromAura then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit + tonumber(critFromAura) + end + critFromAura = BCS:GetPlayerAura(L["Increases critical chance of spells by 10%%, melee and ranged by 5%% and grants 140 attack power. 120 minute duration."]) + if critFromAura then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit + 10 + end + _, _, critFromAura = BCS:GetPlayerAura(L["Critical strike chance with spells and melee attacks increased by (%d+)%%."]) + if critFromAura then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit + tonumber(critFromAura) + end + -- debuffs + _, _, _, critFromAura = BCS:GetPlayerAura(L["Melee critical-hit chance reduced by (%d+)%%.\r\nSpell critical-hit chance reduced by (%d+)%%."], 'HARMFUL') + if critFromAura then + BCScache["auras"].spell_crit = BCScache["auras"].spell_crit - tonumber(critFromAura) + end + end + + -- scan talents + if BCS.needScanTalents then + BCScache["talents"].spell_crit = 0 + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) for line=1, BCS_Tooltip:NumLines() do local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["Equip: Increases damage and healing done by magical spells and effects by up to (%d+)."]) - if value then - spellPower = spellPower + tonumber(value) - end - _,_, value = strfind(left:GetText(), "Equip: Increases your spell damage by up to (%d+)") - if value then - spellPower = spellPower + tonumber(value) - end - _,_, value = strfind(left:GetText(), L["Spell Damage %+(%d+)"]) - if value then - spellPower = spellPower + tonumber(value) - end - _,_, value = strfind(left:GetText(), L["^%+(%d+) Spell Damage and Healing"]) - if value then - spellPower = spellPower + tonumber(value) - end - _,_, value = strfind(left:GetText(), L["^%+(%d+) Damage and Healing Spells"]) - if value then - spellPower = spellPower + tonumber(value) - end - - _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Arcane spells and effects by up to (%d+)."]) - if value then - arcanePower = arcanePower + tonumber(value) - end - _,_, value = strfind(left:GetText(), L["^%+(%d+) Arcane Spell Damage"]) - if value then - arcanePower = arcanePower + tonumber(value) - end - - _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Fire spells and effects by up to (%d+)."]) - if value then - firePower = firePower + tonumber(value) + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Arcane Instability + local _,_, value = strfind(left:GetText(), L["Increases your spell damage and critical srike chance by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].spell_crit = BCScache["talents"].spell_crit + tonumber(value) + break end - _,_, value = strfind(left:GetText(), L["Fire Damage %+(%d+)"]) - if value then - firePower = firePower + tonumber(value) + end + end + end + end + end + + spellCrit = spellCrit + BCScache["talents"].spell_crit + BCScache["gear"].spell_crit + BCScache["auras"].spell_crit + + return spellCrit +end + +function BCS:GetSpellCritFromClass(class) + if not class then + return 0, 0, 0, 0, 0, 0 + end + + if class == "PALADIN" then + --scan talents + if BCS.needScanTalents or BCS.needScanAuras then + BCScache["talents"].paladin_holy_light = 0 + BCScache["talents"].paladin_flash = 0 + BCScache["talents"].paladin_shock = 0 + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Holy Power + local _,_, value = strfind(left:GetText(), L["Increases the critical effect chance of your Holy Light and Flash of Light by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].paladin_holy_light = BCScache["talents"].paladin_holy_light + tonumber(value) + BCScache["talents"].paladin_flash = BCScache["talents"].paladin_flash + tonumber(value) + break + end + -- Divine Favor + _,_, value = strfind(left:GetText(), L["Improves your chance to get a critical strike with Holy Shock by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].paladin_shock = BCScache["talents"].paladin_shock + tonumber(value) + break + end end - _,_, value = strfind(left:GetText(), L["^%+(%d+) Fire Spell Damage"]) - if value then - firePower = firePower + tonumber(value) + end + end + end + end + + return BCScache["talents"].paladin_holy_light, + BCScache["talents"].paladin_flash, + BCScache["talents"].paladin_shock, 0, 0, 0 + + elseif class == "DRUID" then + --scan talents + if BCS.needScanTalents then + BCScache["talents"].druid_moonfire = 0 + BCScache["talents"].druid_regrowth = 0 + -- scan talents + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Improved Moonfire + local _,_, value = strfind(left:GetText(), L["Increases the damage and critical strike chance of your Moonfire spell by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].druid_moonfire = BCScache["talents"].druid_moonfire + tonumber(value) + break + end + -- Improved Regrowth + _,_, value = strfind(left:GetText(), L["Increases the critical effect chance of your Regrowth spell by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].druid_regrowth = BCScache["talents"].druid_regrowth + tonumber(value) + break + end end - - _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Frost spells and effects by up to (%d+)."]) - if value then - frostPower = frostPower + tonumber(value) + end + end + end + end + + return BCScache["talents"].druid_moonfire, + BCScache["talents"].druid_regrowth, 0, 0, 0, 0 + + elseif class == "WARLOCK" then + --scan talents + if BCS.needScanTalents then + BCScache["talents"].warlock_destruction_spells = 0 + BCScache["talents"].warlock_searing_pain = 0 + -- scan talents + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Devastation + local _,_, value = strfind(left:GetText(), L["Increases the critical strike chance of your Destruction spells by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].warlock_destruction_spells = BCScache["talents"].warlock_destruction_spells + tonumber(value) + BCScache["talents"].warlock_searing_pain = BCScache["talents"].warlock_searing_pain + tonumber(value) + break + end + -- Improved Searing Pain + _,_, value = strfind(left:GetText(), L["Increases the critical strike chance of your Searing Pain spell by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].warlock_searing_pain = BCScache["talents"].warlock_searing_pain + tonumber(value) + break + end end - _,_, value = strfind(left:GetText(), L["Frost Damage %+(%d+)"]) - if value then - frostPower = frostPower + tonumber(value) + end + end + end + end + + return BCScache["talents"].warlock_destruction_spells, + BCScache["talents"].warlock_searing_pain, 0, 0, 0, 0 + + elseif class == "MAGE" then + --scan talents + if BCS.needScanTalents or BCS.needScanAuras then + BCScache["talents"].mage_arcane_spells = 0 + BCScache["talents"].mage_fire_spells = 0 + BCScache["talents"].mage_fireblast = 0 + BCScache["talents"].mage_scorch = 0 + BCScache["talents"].mage_flamestrike = 0 + BCScache["talents"].mage_shatter = 0 + -- scan talents + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Arcane Impact + local _,_, value = strfind(left:GetText(), L["Increases the critical strike chance of your Arcane Explosion and Arcane Missiles spells by an additional (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].mage_arcane_spells = BCScache["talents"].mage_arcane_spells + tonumber(value) + break + end + -- Incinerate + _,_, value = strfind(left:GetText(), L["Increases the critical strike chance of your Fire Blast and Scorch spells by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].mage_fireblast = BCScache["talents"].mage_fireblast + tonumber(value) + BCScache["talents"].mage_scorch = BCScache["talents"].mage_scorch + tonumber(value) + break + end + -- Improved Flamestrike + _,_, value = strfind(left:GetText(), L["Increases the critical strike chance of your Flamestrike spell by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].mage_flamestrike = BCScache["talents"].mage_flamestrike + tonumber(value) + break + end + -- Critical Mass + _,_, value = strfind(left:GetText(), L["Increases the critical strike chance of your Fire spells by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].mage_fire_spells = BCScache["talents"].mage_fire_spells + tonumber(value) + BCScache["talents"].mage_fireblast = BCScache["talents"].mage_fireblast + tonumber(value) + BCScache["talents"].mage_flamestrike = BCScache["talents"].mage_flamestrike + tonumber(value) + BCScache["talents"].mage_scorch = BCScache["talents"].mage_scorch + tonumber(value) + break + end + -- Shatter + _,_, value = strfind(left:GetText(), L["Increases the critical strike chance of all your spells against frozen targets by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].mage_shatter = BCScache["talents"].mage_shatter + tonumber(value) + break + end end - _,_, value = strfind(left:GetText(), L["^%+(%d+) Frost Spell Damage"]) - if value then - frostPower = frostPower + tonumber(value) + end + end + end + -- Buffs + local _, _, value = BCS:GetPlayerAura(L["Increases critical strike chance from Fire damage spells by (%d+)%%."]) + -- Combustion + if value then + BCScache["talents"].mage_fire_spells = BCScache["talents"].mage_fire_spells + tonumber(value) + BCScache["talents"].mage_fireblast = BCScache["talents"].mage_fireblast + tonumber(value) + BCScache["talents"].mage_flamestrike = BCScache["talents"].mage_flamestrike + tonumber(value) + BCScache["talents"].mage_scorch = BCScache["talents"].mage_scorch + tonumber(value) + end + end + + return BCScache["talents"].mage_arcane_spells, + BCScache["talents"].mage_fire_spells, + BCScache["talents"].mage_fireblast, + BCScache["talents"].mage_scorch, + BCScache["talents"].mage_flamestrike, + BCScache["talents"].mage_shatter + + elseif class == "PRIEST" then + if BCS.needScanTalents then + BCScache["talents"].priest_holy_spells = 0 + BCScache["talents"].priest_discipline_spells = 0 + BCScache["talents"].priest_offensive_spells = 0 + -- scan talents + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Divinity + local _,_, value = strfind(left:GetText(), L["Increases the critical effect chance of your Holy and Discipline spells by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].priest_holy_spells = BCScache["talents"].priest_holy_spells + tonumber(value) + BCScache["talents"].priest_discipline_spells = BCScache["talents"].priest_discipline_spells + tonumber(value) + break + end + -- Force of Will + _,_, value = strfind(left:GetText(), "Increases your spell damage and the critical strike chance of your offensive spells by (%d+)%%") + if value and rank > 0 then + BCScache["talents"].priest_offensive_spells = BCScache["talents"].priest_offensive_spells + tonumber(value) + break + end end - - _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Holy spells and effects by up to (%d+)."]) - if value then - holyPower = holyPower + tonumber(value) + end + end + end + end + -- scan gear + if BCS.needScanGear then + -- t1 set gives + 2% crit to holy and 25% to prayer of healing + BCScache["gear"].priest_holy_spells = 0 + BCScache["gear"].priest_prayer = 0 + local Crit_Set_Bonus = {} + for slot=1, 19 do + if BCS_Tooltip:SetInventoryItem('player', slot) then + local _, _, eqItemLink = strfind(GetInventoryItemLink('player', slot), "(item:%d+:%d+:%d+:%d+)") + if eqItemLink then BCS_Tooltip:ClearLines() BCS_Tooltip:SetHyperlink(eqItemLink) end + local SET_NAME = nil + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") + if value then + SET_NAME = value + end + _, _, value = strfind(left:GetText(), L["^Set: Improves your chance to get a critical strike with Holy spells by (%d)%%."]) + if value and SET_NAME and not tContains(Crit_Set_Bonus, SET_NAME) then + tinsert(Crit_Set_Bonus, SET_NAME) + BCScache["gear"].priest_holy_spells = BCScache["gear"].priest_holy_spells + tonumber(value) + end + _, _, value = strfind(left:GetText(), L["^Set: Increases your chance of a critical hit with Prayer of Healing by (%d+)%%."]) + if value and SET_NAME and not tContains(Crit_Set_Bonus, SET_NAME) then + tinsert(Crit_Set_Bonus, SET_NAME) + BCScache["gear"].priest_prayer = BCScache["gear"].priest_prayer + tonumber(value) + end end - _,_, value = strfind(left:GetText(), L["^%+(%d+) Holy Spell Damage"]) - if value then - holyPower = holyPower + tonumber(value) + end + end + end + end + + local holySpells = BCScache["talents"].priest_holy_spells + BCScache["gear"].priest_holy_spells + + return holySpells, + BCScache["talents"].priest_discipline_spells, + BCScache["talents"].priest_offensive_spells, + BCScache["gear"].priest_prayer, 0, 0 + + elseif class == "SHAMAN" then + if BCS.needScanTalents then + BCScache["talents"].shaman_lightning_bolt = 0 + BCScache["talents"].shaman_chain_lightning = 0 + BCScache["talents"].shaman_lightning_shield = 0 + BCScache["talents"].shaman_firefrost_spells = 0 + BCScache["talents"].shaman_healing_spells = 0 + -- scan talents + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Call of Thunder + local _,_, value = strfind(left:GetText(), L["Increases the critical strike chance of your Lightning Bolt and Chain Lightning spells by an additional (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].shaman_lightning_bolt = BCScache["talents"].shaman_lightning_bolt + tonumber(value) + BCScache["talents"].shaman_chain_lightning = BCScache["talents"].shaman_chain_lightning + tonumber(value) + break + end + -- Tidal Mastery + _,_, value = strfind(left:GetText(), L["Increases the critical effect chance of your healing and lightning spells by (%d+)%%."]) + if value and rank > 0 then + BCScache["talents"].shaman_lightning_bolt = BCScache["talents"].shaman_lightning_bolt + tonumber(value) + BCScache["talents"].shaman_chain_lightning = BCScache["talents"].shaman_chain_lightning + tonumber(value) + BCScache["talents"].shaman_lightning_shield = BCScache["talents"].shaman_lightning_shield + tonumber(value) + BCScache["talents"].shaman_healing_spells = BCScache["talents"].shaman_healing_spells + tonumber(value) + break + end end - - _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Nature spells and effects by up to (%d+)."]) - if value then - naturePower = naturePower + tonumber(value) + end + end + end + end + -- buffs + if BCS.needScanAuras then + BCScache["auras"].shaman_lightning_bolt = 0 + BCScache["auras"].shaman_chain_lightning = 0 + BCScache["auras"].shaman_firefrost_spells = 0 + local hasAura = BCS:GetPlayerAura("Elemental Mastery") + if hasAura then + BCScache["auras"].shaman_lightning_bolt = 100 + BCScache["auras"].shaman_chain_lightning = 100 + BCScache["auras"].shaman_firefrost_spells = 100 + end + end + + local lightningBolt = BCScache["auras"].shaman_lightning_bolt + BCScache["talents"].shaman_lightning_bolt + local chainLightning = BCScache["auras"].shaman_chain_lightning + BCScache["talents"].shaman_chain_lightning + + return lightningBolt, chainLightning, + BCScache["talents"].shaman_lightning_shield, + BCScache["auras"].shaman_firefrost_spells, + BCScache["talents"].shaman_healing_spells, 0 + + else + return 0, 0, 0, 0, 0, 0 + end +end + +local impInnerFire = nil +local spiritualGuidance = nil +function BCS:GetSpellPower(school) + if school then + local spellPower = 0; + --scan gear + if BCS.needScanGear then + if school == "Arcane" then BCScache["gear"].arcane = 0 + elseif school == "Fire" then BCScache["gear"].fire = 0 + elseif school == "Frost" then BCScache["gear"].frost = 0 + elseif school == "Holy" then BCScache["gear"].holy = 0 + elseif school == "Nature" then BCScache["gear"].nature = 0 + elseif school == "Shadow" then BCScache["gear"].shadow = 0 + end + for slot=1, 19 do + if BCS_Tooltip:SetInventoryItem("player", slot) then + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by "..school.." spells and effects by up to (%d+)."]) + if value then + spellPower = spellPower + tonumber(value) + end + _,_, value = strfind(left:GetText(), school.." Damage %+(%d+)") + if value then + spellPower = spellPower + tonumber(value) + end + _,_, value = strfind(left:GetText(), "^%+(%d+) "..school.." Spell Damage") + if value then + spellPower = spellPower + tonumber(value) + end end - _,_, value = strfind(left:GetText(), L["^%+(%d+) Nature Spell Damage"]) - if value then - naturePower = naturePower + tonumber(value) + end + end + end + if school == "Arcane" then BCScache["gear"].arcane = spellPower + elseif school == "Fire" then BCScache["gear"].fire = spellPower + elseif school == "Frost" then BCScache["gear"].frost = spellPower + elseif school == "Holy" then BCScache["gear"].holy = spellPower + elseif school == "Nature" then BCScache["gear"].nature = spellPower + elseif school == "Shadow" then BCScache["gear"].shadow = spellPower + end + else + if school == "Arcane" then spellPower = BCScache["gear"].arcane + elseif school == "Fire" then spellPower = BCScache["gear"].fire + elseif school == "Frost" then spellPower = BCScache["gear"].frost + elseif school == "Holy" then spellPower = BCScache["gear"].holy + elseif school == "Nature" then spellPower = BCScache["gear"].nature + elseif school == "Shadow" then spellPower = BCScache["gear"].shadow + end + end + + return spellPower + else + local damageAndHealing = 0 + local damageOnly = 0 + local SpellPower_Set_Bonus = {} + if BCS.needScanGear then + BCScache["gear"].damage_and_healing = 0 + BCScache["gear"].only_damage = 0 + BCScache["gear"].arcane = 0 + BCScache["gear"].fire = 0 + BCScache["gear"].frost = 0 + BCScache["gear"].holy = 0 + BCScache["gear"].nature = 0 + BCScache["gear"].shadow = 0 + -- scan gear + for slot=1, 19 do + if BCS_Tooltip:SetInventoryItem('player', slot) then + local _, _, eqItemLink = strfind(GetInventoryItemLink('player', slot), "(item:%d+:%d+:%d+:%d+)") + if eqItemLink then BCS_Tooltip:ClearLines() BCS_Tooltip:SetHyperlink(eqItemLink) end + local SET_NAME + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + -- generic bonus on most gear + local _,_, value = strfind(left:GetText(), L["Equip: Increases damage and healing done by magical spells and effects by up to (%d+)."]) + if value then + BCScache["gear"].damage_and_healing = BCScache["gear"].damage_and_healing + tonumber(value) + end + -- Spell Power (weapon/bracer enchant) apparently gives healing too + -- Arcanum of Focus (Head/Legs enchant) + -- Power of the Scourge (Shoulder enchant) + _,_, value = strfind(left:GetText(), "Spell Damage %+(%d+)") + if value then + BCScache["gear"].damage_and_healing = BCScache["gear"].damage_and_healing + tonumber(value) + end + -- Atiesh (druid/priest) + _,_, value = strfind(left:GetText(), "Equip: Increases your spell damage by up to (%d+) and your healing by up to %d+.") + if value then + BCScache["gear"].only_damage = BCScache["gear"].only_damage + tonumber(value) + end + -- Zandalar Signet of Mojo (Shoulder enchant) + _,_, value = strfind(left:GetText(), L["^%+(%d+) Spell Damage and Healing"]) + if value then + BCScache["gear"].damage_and_healing = BCScache["gear"].damage_and_healing + tonumber(value) + end + -- Enchanted Armor Kit (Leatherworking) + _,_, value = strfind(left:GetText(), L["^%+(%d+) Damage and Healing Spells"]) + if value then + BCScache["gear"].damage_and_healing = BCScache["gear"].damage_and_healing + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Arcane spells and effects by up to (%d+)."]) + if value then + BCScache["gear"].arcane = BCScache["gear"].arcane + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["^%+(%d+) Arcane Spell Damage"]) + if value then + BCScache["gear"].arcane = BCScache["gear"].arcane + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["Arcane Damage %+(%d+)"]) + if value then + BCScache["gear"].arcane = BCScache["gear"].arcane + tonumber(value) + end + + _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Fire spells and effects by up to (%d+)."]) + if value then + BCScache["gear"].fire = BCScache["gear"].fire + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["Fire Damage %+(%d+)"]) + if value then + BCScache["gear"].fire = BCScache["gear"].fire + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["^%+(%d+) Fire Spell Damage"]) + if value then + BCScache["gear"].fire = BCScache["gear"].fire + tonumber(value) + end + + _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Frost spells and effects by up to (%d+)."]) + if value then + BCScache["gear"].frost = BCScache["gear"].frost + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["Frost Damage %+(%d+)"]) + if value then + BCScache["gear"].frost = BCScache["gear"].frost + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["^%+(%d+) Frost Spell Damage"]) + if value then + BCScache["gear"].frost = BCScache["gear"].frost + tonumber(value) + end + + _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Holy spells and effects by up to (%d+)."]) + if value then + BCScache["gear"].holy = BCScache["gear"].holy + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["^%+(%d+) Holy Spell Damage"]) + if value then + BCScache["gear"].holy = BCScache["gear"].holy + tonumber(value) + end + + _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Nature spells and effects by up to (%d+)."]) + if value then + BCScache["gear"].nature = BCScache["gear"].nature + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["^%+(%d+) Nature Spell Damage"]) + if value then + BCScache["gear"].nature = BCScache["gear"].nature + tonumber(value) + end + _,_, value = strfind(left:GetText(), "Nature Damage %+(%d+)") + if value then + BCScache["gear"].nature = BCScache["gear"].nature + tonumber(value) + end + + _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Shadow spells and effects by up to (%d+)."]) + if value then + BCScache["gear"].shadow = BCScache["gear"].shadow + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["Shadow Damage %+(%d+)"]) + if value then + BCScache["gear"].shadow = BCScache["gear"].shadow + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["^%+(%d+) Shadow Spell Damage"]) + if value then + BCScache["gear"].shadow = BCScache["gear"].shadow + tonumber(value) + end + + _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") + if value then + SET_NAME = value + end + + _, _, value = strfind(left:GetText(), L["^Set: Increases damage and healing done by magical spells and effects by up to (%d+)%."]) + if value and SET_NAME and not tContains(SpellPower_Set_Bonus, SET_NAME) then + tinsert(SpellPower_Set_Bonus, SET_NAME) + BCScache["gear"].damage_and_healing = BCScache["gear"].damage_and_healing + tonumber(value) + end end - - _,_, value = strfind(left:GetText(), L["Equip: Increases damage done by Shadow spells and effects by up to (%d+)."]) - if value then - shadowPower = shadowPower + tonumber(value) + end + end + end + -- SetHyperLink doesnt show temporary enhancements, have to use SetInventoryItem + if BCS_Tooltip:SetInventoryItem("player", 16) then + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + -- apparently gives healing too + local found = strfind(left:GetText(), "Brilliant Wizard Oil") + if found then + BCScache["gear"].damage_and_healing = BCScache["gear"].damage_and_healing + 36 + break end - _,_, value = strfind(left:GetText(), L["Shadow Damage %+(%d+)"]) - if value then - shadowPower = shadowPower + tonumber(value) + found = strfind(left:GetText(), "Lesser Wizard Oil") + if found then + BCScache["gear"].damage_and_healing = BCScache["gear"].damage_and_healing + 16 + break end - _,_, value = strfind(left:GetText(), L["^%+(%d+) Shadow Spell Damage"]) - if value then - shadowPower = shadowPower + tonumber(value) + found = strfind(left:GetText(), "Minor Wizard Oil") + if found then + BCScache["gear"].damage_and_healing = BCScache["gear"].damage_and_healing + 8 + break end - - _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") - if value then - SET_NAME = value + found = strfind(left:GetText(), "Wizard Oil") + if found then + BCScache["gear"].damage_and_healing = BCScache["gear"].damage_and_healing + 24 + break end + end + end + end + end - _, _, value = strfind(left:GetText(), L["^Set: Increases damage and healing done by magical spells and effects by up to (%d+)%."]) - if value and SET_NAME and not tContains(SpellPower_Set_Bonus, SET_NAME) then - tinsert(SpellPower_Set_Bonus, SET_NAME) - spellPower = spellPower + tonumber(value) + if BCS.needScanTalents then + impInnerFire = nil + spiritualGuidance = nil + -- scan talents + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Priest + -- Spiritual Guidance + local _,_, value = strfind(left:GetText(), L["Increases spell damage and healing by up to (%d+)%% of your total Spirit."]) + if value and rank > 0 then + spiritualGuidance = tonumber(value) + break + end + -- Improved Inner Fire + _,_, value = strfind(left:GetText(), "Increases the effects of your Inner Fire spell by (%d+)%%.") + if value and rank > 0 then + impInnerFire = tonumber(value) + break + end end - end end end - end - - -- scan talents - local MAX_TABS = GetNumTalentTabs() - - for tab=1, MAX_TABS do - local MAX_TALENTS = GetNumTalents(tab) - - for talent=1, MAX_TALENTS do - BCS_Tooltip:SetTalent(tab, talent) - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do - local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - -- Priest - -- Spiritual Guidance - local _,_, value = strfind(left:GetText(), L["Increases spell damage and healing by up to (%d+)%% of your total Spirit."]) - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) - if value and rank > 0 then - local stat, effectiveStat = UnitStat("player", 5) - spellPower = spellPower + floor(((tonumber(value) / 100) * effectiveStat)) - - -- nothing more is currenlty supported, break out of the loops - line = MAX_LINES - talent = MAX_TALENTS - tab = MAX_TABS - end - end + if BCS.needScanAuras then + BCScache["auras"].damage_and_healing = 0 + BCScache["auras"].only_damage = 0 + -- buffs + local _, _, spellPowerFromAura = BCS:GetPlayerAura(L["Magical damage dealt is increased by up to (%d+)."]) + if spellPowerFromAura then + BCScache["auras"].only_damage = BCScache["auras"].only_damage + tonumber(spellPowerFromAura) + end + _, _, spellPowerFromAura = BCS:GetPlayerAura("Increases damage and healing done by magical spells and effects by up to (%d+).") + if spellPowerFromAura then + BCScache["auras"].damage_and_healing = BCScache["auras"].damage_and_healing + tonumber(spellPowerFromAura) + end + -- Dreamtonic/Arcane Elixir + _, _, spellPowerFromAura = BCS:GetPlayerAura("Magical damage dealt by spells and abilities is increased by up to (%d+)") + if spellPowerFromAura then + BCScache["auras"].only_damage = BCScache["auras"].only_damage + tonumber(spellPowerFromAura) + end + -- Dreamshard Elixir + _, _, spellPowerFromAura = BCS:GetPlayerAura("Spell damage is increased by up to (%d+)") + if spellPowerFromAura then + BCScache["auras"].only_damage = BCScache["auras"].only_damage + tonumber(spellPowerFromAura) + end + -- Flask of Supreme Power + _, _, spellPowerFromAura = BCS:GetPlayerAura("Spell damage increased by up to (%d+)") + if spellPowerFromAura then + BCScache["auras"].only_damage = BCScache["auras"].only_damage + tonumber(spellPowerFromAura) + end + -- Danonzo's Tel'Abim Delight + _, _, spellPowerFromAura = BCS:GetPlayerAura("Spell Damage increased by (%d+)") + if spellPowerFromAura then + BCScache["auras"].only_damage = BCScache["auras"].only_damage + tonumber(spellPowerFromAura) + end + --Inner Fire + _, _, spellPowerFromAura = BCS:GetPlayerAura("Increased damage done by magical spells and effects by (%d+).") + if spellPowerFromAura then + spellPowerFromAura = tonumber(spellPowerFromAura) + if impInnerFire then + spellPowerFromAura = floor((spellPowerFromAura * (impInnerFire/100)) + (spellPowerFromAura)) end - + BCScache["auras"].only_damage = BCScache["auras"].only_damage + spellPowerFromAura end end - - -- buffs - local _, _, spellPowerFromAura = BCS:GetPlayerAura(L["Magical damage dealt is increased by up to (%d+)."]) - if spellPowerFromAura then - spellPower = spellPower + tonumber(spellPowerFromAura) - damagePower = damagePower + tonumber(spellPowerFromAura) - end - - _, _, spellPowerFromAura = BCS:GetPlayerAura("Increases damage and healing done by magical spells and effects by up to (%d+).") - if spellPowerFromAura then - spellPower = spellPower + tonumber(spellPowerFromAura) - damagePower = damagePower + tonumber(spellPowerFromAura) - end - - _, _, spellPowerFromAura = BCS:GetPlayerAura("Magical damage dealt by spells and abilities is increased by up to (%d+)") - if spellPowerFromAura then - spellPower = spellPower + tonumber(spellPowerFromAura) - damagePower = damagePower + tonumber(spellPowerFromAura) - end - - _, _, spellPowerFromAura = BCS:GetPlayerAura("Spell damage is increased by up to (%d+)") - if spellPowerFromAura then - spellPower = spellPower + tonumber(spellPowerFromAura) - damagePower = damagePower + tonumber(spellPowerFromAura) - end - local secondaryPower = 0 local secondaryPowerName = "" - - if arcanePower > secondaryPower then - secondaryPower = arcanePower + + if BCScache["gear"].arcane > secondaryPower then + secondaryPower = BCScache["gear"].arcane secondaryPowerName = L.SPELL_SCHOOL_ARCANE end - if firePower > secondaryPower then - secondaryPower = firePower + if BCScache["gear"].fire > secondaryPower then + secondaryPower = BCScache["gear"].fire secondaryPowerName = L.SPELL_SCHOOL_FIRE end - if frostPower > secondaryPower then - secondaryPower = frostPower + if BCScache["gear"].frost > secondaryPower then + secondaryPower = BCScache["gear"].frost secondaryPowerName = L.SPELL_SCHOOL_FROST end - if holyPower > secondaryPower then - secondaryPower = holyPower + if BCScache["gear"].holy > secondaryPower then + secondaryPower = BCScache["gear"].holy secondaryPowerName = L.SPELL_SCHOOL_HOLY end - if naturePower > secondaryPower then - secondaryPower = naturePower + if BCScache["gear"].nature > secondaryPower then + secondaryPower = BCScache["gear"].nature secondaryPowerName = L.SPELL_SCHOOL_NATURE end - if shadowPower > secondaryPower then - secondaryPower = shadowPower + if BCScache["gear"].shadow > secondaryPower then + secondaryPower = BCScache["gear"].shadow secondaryPowerName = L.SPELL_SCHOOL_SHADOW end - - return spellPower, secondaryPower, secondaryPowerName, damagePower + + if spiritualGuidance ~= nil then + BCScache["talents"].damage_and_healing = 0 + local _, spirit = UnitStat("player", 5) + BCScache["talents"].damage_and_healing = BCScache["talents"].damage_and_healing + floor(((spiritualGuidance / 100) * spirit)) + end + + damageAndHealing = BCScache["gear"].damage_and_healing + BCScache["talents"].damage_and_healing + BCScache["auras"].damage_and_healing + damageOnly = BCScache["auras"].only_damage + BCScache["gear"].only_damage + + return damageAndHealing, secondaryPower, secondaryPowerName, damageOnly end end +local ironClad = nil +--this is stuff that gives ONLY healing, we count stuff that gives both damage and healing in GetSpellPower function BCS:GetHealingPower() local healPower = 0; local healPower_Set_Bonus = {} - local MAX_INVENTORY_SLOTS = 19 - - for slot=0, MAX_INVENTORY_SLOTS do - local hasItem = BCS_Tooltip:SetInventoryItem("player", slot) - - if hasItem then - local SET_NAME - + --talents + if BCS.needScanTalents then + ironClad = nil + BCScache["talents"].healing = 0 + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Paladin + -- Ironclad + local _,_, value = strfind(left:GetText(), L["Increases your healing power by (%d+)%% of your Armor."]) + if value and rank > 0 then + ironClad = tonumber(value) + break + end + end + end + end + end + end + if BCS.needScanGear then + BCScache["gear"].healing = 0 + --scan gear + for slot=1, 19 do + if BCS_Tooltip:SetInventoryItem('player', slot) then + local _, _, eqItemLink = strfind(GetInventoryItemLink('player', slot), "(item:%d+:%d+:%d+:%d+)") + if eqItemLink then BCS_Tooltip:ClearLines() BCS_Tooltip:SetHyperlink(eqItemLink) end + local SET_NAME + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _,_, value = strfind(left:GetText(), L["Equip: Increases healing done by spells and effects by up to (%d+)."]) + if value then + BCScache["gear"].healing = BCScache["gear"].healing + tonumber(value) + end + -- Atiesh (druid/priest) + _,_, value = strfind(left:GetText(), "Equip: Increases your spell damage by up to %d+ and your healing by up to (%d+).") + if value then + BCScache["gear"].healing = BCScache["gear"].healing + tonumber(value) + end + -- Enchant Weapon/Gloves/Bracers - Healing Power + _,_, value = strfind(left:GetText(), L["Healing Spells %+(%d+)"]) + if value then + BCScache["gear"].healing = BCScache["gear"].healing + tonumber(value) + end + -- Zandalar Signet of Serenity (Shoulder enchant) + _,_, value = strfind(left:GetText(), L["^%+(%d+) Healing Spells"]) + if value then + BCScache["gear"].healing = BCScache["gear"].healing + tonumber(value) + end + -- Beautiful Diamond Gemstone (Jewelcrafting) + -- Resilience of the Scourge (Shoulder enchant) + _,_, value = strfind(left:GetText(), "Healing %+(%d+)") + if value then + BCScache["gear"].healing = BCScache["gear"].healing + tonumber(value) + end + -- Enchanted Armor Kit (Leatherwotking) + -- Arcanum of Focus (Head/Legs enchant) + -- already included in GetSpellPower + + _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") + if value then + SET_NAME = value + end + _, _, value = strfind(left:GetText(), L["^Set: Increases healing done by spells and effects by up to (%d+)%."]) + if value and SET_NAME and not tContains(healPower_Set_Bonus, SET_NAME) then + tinsert(healPower_Set_Bonus, SET_NAME) + BCScache["gear"].healing = BCScache["gear"].healing + tonumber(value) + end + end + end + end + end + -- SetHyperLink doesnt show temporary enhancements, have to use SetInventoryItem + if BCS_Tooltip:SetInventoryItem("player", 16) then for line=1, BCS_Tooltip:NumLines() do local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["Equip: Increases healing done by spells and effects by up to (%d+)."]) - if value then - healPower = healPower + tonumber(value) - end - _,_, value = strfind(left:GetText(), "Equip: Increases your spell damage by up to 120 and your healing by up to (300).") - if value then - healPower = healPower + tonumber(value) - 120 - end - _,_, value = strfind(left:GetText(), L["Healing Spells %+(%d+)"]) - if value then - healPower = healPower + tonumber(value) - end - _,_, value = strfind(left:GetText(), L["^Healing %+(%d+) and %d+ mana per 5 sec."]) - if value then - healPower = healPower + tonumber(value) - end - _,_, value = strfind(left:GetText(), L["^%+(%d+) Healing Spells"]) - if value then - healPower = healPower + tonumber(value) - end - _,_, value = strfind(left:GetText(), "^Brilliant Mana Oil %((%d+) min%)") - if value then - healPower = healPower + 25 - end - - _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") - if value then - SET_NAME = value - end - _, _, value = strfind(left:GetText(), L["^Set: Increases healing done by spells and effects by up to (%d+)%."]) - if value and SET_NAME and not tContains(healPower_Set_Bonus, SET_NAME) then - tinsert(healPower_Set_Bonus, SET_NAME) - healPower = healPower + tonumber(value) + local found = strfind(left:GetText(), "Brilliant Mana Oil") + if found then + BCScache["gear"].healing = BCScache["gear"].healing + 25 end end end end - end - -- buffs - local _, _, healPowerFromAura = BCS:GetPlayerAura(L["Healing done by magical spells is increased by up to (%d+)."]) - if healPowerFromAura then - healPower = healPower + tonumber(healPowerFromAura) - end - --Sweet Surprise - _, _, healPowerFromAura = BCS:GetPlayerAura(L["Increases healing done by magical spells by up to (%d+) for 3600 sec."]) - if healPowerFromAura then - healPower = healPower + tonumber(healPowerFromAura) - end - --Unstable Power - _, _, healPowerFromAura = BCS:GetPlayerAura(L["Healing increased by up to (%d+)."]) - if healPowerFromAura then - healPower = healPower + tonumber(healPowerFromAura) - end - --The Eye of the Dead - _, _, healPowerFromAura = BCS:GetPlayerAura(L["Healing spells increased by up to (%d+)."]) - if healPowerFromAura then - healPower = healPower + tonumber(healPowerFromAura) - end - --Power of the Guardian - _, _, healPowerFromAura = BCS:GetPlayerAura("Increases healing done by magical spells and effects by up to (%d+).") - if healPowerFromAura then - healPower = healPower + tonumber(healPowerFromAura) - end - _, _, healPowerFromAura = BCS:GetPlayerAura("Increases damage and healing done by magical spells and effects by up to (%d+).") - if healPowerFromAura then - healPower = healPower + tonumber(healPowerFromAura) + local treebonus = nil + if BCS.needScanAuras then + BCScache["auras"].healing = 0 + local _, _, healPowerFromAura = BCS:GetPlayerAura(L["Healing done by magical spells is increased by up to (%d+)."]) + if healPowerFromAura then + BCScache["auras"].healing = BCScache["auras"].healing + tonumber(healPowerFromAura) + end + --Tree of Life (own) + local found = BCS:GetPlayerAura("Tree of Life Form") and BCS:GetPlayerAura("Tree of Life Aura") + local _, spirit = UnitStat("player", 5) + if found then + treebonus = spirit * 0.2 + end + --Sweet Surprise + _, _, healPowerFromAura = BCS:GetPlayerAura(L["Increases healing done by magical spells by up to (%d+) for 3600 sec."]) + if healPowerFromAura then + BCScache["auras"].healing = BCScache["auras"].healing + tonumber(healPowerFromAura) + end + --Unstable Power + _, _, healPowerFromAura = BCS:GetPlayerAura(L["Healing increased by up to (%d+)."]) + if healPowerFromAura then + BCScache["auras"].healing = BCScache["auras"].healing + tonumber(healPowerFromAura) + end + --The Eye of the Dead + _, _, healPowerFromAura = BCS:GetPlayerAura(L["Healing spells increased by up to (%d+)."]) + if healPowerFromAura then + BCScache["auras"].healing = BCScache["auras"].healing + tonumber(healPowerFromAura) + end + --Power of the Guardian + _, _, healPowerFromAura = BCS:GetPlayerAura("Increases healing done by magical spells and effects by up to (%d+).") + if healPowerFromAura then + BCScache["auras"].healing = BCScache["auras"].healing + tonumber(healPowerFromAura) + end + --Dreamshard Elixir + _, _, healPowerFromAura = BCS:GetPlayerAura("Healing done is increased by up to (%d+)") + if healPowerFromAura then + BCScache["auras"].healing = BCScache["auras"].healing + tonumber(healPowerFromAura) + end end - --Dreamshard Elixir - _, _, healPowerFromAura = BCS:GetPlayerAura("Healing done is increased by up to (%d+)") - if healPowerFromAura then - healPower = healPower + tonumber(healPowerFromAura) + if ironClad ~= nil then + BCScache["talents"].healing = 0 + local base = UnitArmor("player") + local _, agility = UnitStat("player", 2) + local armorFromGear = base - (agility * 2) + BCScache["talents"].healing = floor(((ironClad / 100) * armorFromGear)) end - - return healPower + healPower = BCScache["gear"].healing + BCScache["auras"].healing + BCScache["talents"].healing + + return healPower, treebonus, BCScache["talents"].healing end local function GetRegenMPPerSpirit() local addvalue = 0 - - local stat, Spirit, posBuff, negBuff = UnitStat("player", 5) - local lClass, class = UnitClass("player") - + local _, spirit = UnitStat("player", 5) + local _, class = UnitClass("player") + if class == "DRUID" then - addvalue = (Spirit / 5 + 15) + addvalue = (spirit / 5 + 15) elseif class == "HUNTER" then - addvalue = (Spirit / 5 + 15) + addvalue = (spirit / 5 + 15) elseif class == "MAGE" then - addvalue = (Spirit / 4 + 12.5) + addvalue = (spirit / 4 + 12.5) elseif class == "PALADIN" then - addvalue = (Spirit / 5 + 15) + addvalue = (spirit / 5 + 15) elseif class == "PRIEST" then - addvalue = (Spirit / 4 + 12.5) + addvalue = (spirit / 4 + 12.5) elseif class == "SHAMAN" then - addvalue = (Spirit / 5 + 17) + addvalue = (spirit / 5 + 17) elseif class == "WARLOCK" then - addvalue = (Spirit / 5 + 15) - else - return addvalue + addvalue = (spirit / 5 + 15) end + return addvalue end +local waterShield = nil function BCS:GetManaRegen() local base = GetRegenMPPerSpirit() local casting = 0 local mp5 = 0 local mp5_Set_Bonus = {} - local MAX_INVENTORY_SLOTS = 19 - - for slot=0, MAX_INVENTORY_SLOTS do - local hasItem = BCS_Tooltip:SetInventoryItem("player", slot) - - if hasItem then - local SET_NAME + -- scan talents + if BCS.needScanTalents then + waterShield = nil + BCScache["talents"].casting = 0 + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do + BCS_Tooltip:SetTalent(tab, talent) + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _, _, _, _, rank = GetTalentInfo(tab, talent) + -- Priest (Meditation) / Druid (Reflection) / Mage (Arcane Meditation) / Shaman (Improved Water Shield) + local _,_, value = strfind(left:GetText(), L["Allows (%d+)%% of your Mana regeneration to continue while casting."]) + if value and rank > 0 then + BCScache["talents"].casting = BCScache["talents"].casting + tonumber(value) + waterShield = rank + break + end + end + end + end + end + end + + if BCS.needScanGear then + BCScache["gear"].mp5 = 0 + BCScache["gear"].casting = 0 + --scan gear + for slot=1, 19 do + if BCS_Tooltip:SetInventoryItem('player', slot) then + local _, _, eqItemLink = strfind(GetInventoryItemLink('player', slot), "(item:%d+:%d+:%d+:%d+)") + if eqItemLink then BCS_Tooltip:ClearLines() BCS_Tooltip:SetHyperlink(eqItemLink) end + local SET_NAME + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _,_, value = strfind(left:GetText(), L["^Mana Regen %+(%d+)"]) + if value then + BCScache["gear"].mp5 = BCScache["gear"].mp5 + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["Equip: Restores (%d+) mana per 5 sec."]) + if value and not strfind(left:GetText(), "to all party members") then + BCScache["gear"].mp5 = BCScache["gear"].mp5 + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["^Healing %+%d+ and (%d+) mana per 5 sec."]) + if value then + BCScache["gear"].mp5 = BCScache["gear"].mp5 + tonumber(value) + end + _,_, value = strfind(left:GetText(), L["^%+(%d+) mana every 5 sec."]) + if value then + BCScache["gear"].mp5 = BCScache["gear"].mp5 + tonumber(value) + end + _,_, value = strfind(left:GetText(), "^Equip: Allows (%d+)%% of your Mana regeneration to continue while casting.") + if value then + BCScache["gear"].casting = BCScache["gear"].casting + tonumber(value) + end + + _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") + if value then + SET_NAME = value + end + _,_, value = strfind(left:GetText(), L["^Set: Allows (%d+)%% of your Mana regeneration to continue while casting."]) + if value and SET_NAME and not tContains(mp5_Set_Bonus, SET_NAME) then + tinsert(mp5_Set_Bonus, SET_NAME) + BCScache["gear"].casting = BCScache["gear"].casting + tonumber(value) + end + _,_, value = strfind(left:GetText(), "^Set: Restores (%d+) mana per 5 sec.") + if value and SET_NAME and not tContains(mp5_Set_Bonus, SET_NAME) then + tinsert(mp5_Set_Bonus, SET_NAME) + BCScache["gear"].mp5 = BCScache["gear"].mp5 + tonumber(value) + end + end + end + end + end + -- SetHyperLink doesnt show temporary enhancements, have to use SetInventoryItem + if BCS_Tooltip:SetInventoryItem("player", 16) then for line=1, BCS_Tooltip:NumLines() do local left = getglobal(BCS_Prefix .. "TextLeft" .. line) - if left:GetText() then - local _,_, value = strfind(left:GetText(), L["^Mana Regen %+(%d+)"]) - if value then - mp5 = mp5 + tonumber(value) + local found = strfind(left:GetText(), "Brilliant Mana Oil") + if found then + BCScache["gear"].mp5 = BCScache["gear"].mp5 + 12 end - _,_, value = strfind(left:GetText(), L["Equip: Restores (%d+) mana per 5 sec."]) - if value and not strfind(left:GetText(), "to all party members") then - mp5 = mp5 + tonumber(value) + found = strfind(left:GetText(), "Lesser Mana Oil") + if found then + BCScache["gear"].mp5 = BCScache["gear"].mp5 + 8 end - _,_, value = strfind(left:GetText(), L["^Healing %+%d+ and (%d+) mana per 5 sec."]) - if value then - mp5 = mp5 + tonumber(value) - end - _,_, value = strfind(left:GetText(), L["^%+(%d+) mana every 5 sec."]) - if value then - mp5 = mp5 + tonumber(value) - end - _,_, value = strfind(left:GetText(), "^Brilliant Mana Oil %((%d+) min%)") - if value then - mp5 = mp5 + 12 - end - _,_, value = strfind(left:GetText(), "^Lesser Mana Oil %((%d+) min%)") - if value then - mp5 = mp5 + 8 + found = strfind(left:GetText(), "Minor Mana Oil") + if found then + BCScache["gear"].mp5 = BCScache["gear"].mp5 + 4 end - _,_, value = strfind(left:GetText(), "^Minor Mana Oil %((%d+) min%)") + end + end + end + end + + -- buffs + if BCS.needScanAuras then + BCScache["auras"].casting = 0 + BCScache["auras"].mp5 = 0 + -- improved Shadowform + for tab=1, GetNumSpellTabs() do + local _, _, offset, numSpells = GetSpellTabInfo(tab); + for s = offset + 1, offset + numSpells do + local spell = GetSpellName(s, BOOKTYPE_SPELL); + if spell == "Improved Shadowform" and BCS:GetPlayerAura("Shadowform") then + BCScache["auras"].casting = BCScache["auras"].casting + 15 + end + end + end + -- Warchief's Blessing + local _, _, mp5FromAura = BCS:GetPlayerAura(L["Increases hitpoints by 300. 15%% haste to melee attacks. (%d+) mana regen every 5 seconds."]) + if mp5FromAura then + BCScache["auras"].mp5 = BCScache["auras"].mp5 + 10 + end + --Epiphany + _, _, mp5FromAura = BCS:GetPlayerAura(L["Restores (%d+) mana per 5 sec."]) + if mp5FromAura then + BCScache["auras"].mp5 = BCScache["auras"].mp5 + tonumber(mp5FromAura) + end + --Nightfin Soup + _, _, mp5FromAura = BCS:GetPlayerAura(L["Regenerating (%d+) Mana every 5 seconds."]) + if mp5FromAura then + BCScache["auras"].mp5 = BCScache["auras"].mp5 + tonumber(mp5FromAura)*2.5 -- had to double the mp5FromAura because the item is a true mp5 tick + end + --Mageblood Potion + _, _, mp5FromAura = BCS:GetPlayerAura(L["Regenerate (%d+) mana per 5 sec."]) + if mp5FromAura then + BCScache["auras"].mp5 = BCScache["auras"].mp5 + tonumber(mp5FromAura) + end + --Fizzy Energy Drink and Sagefin + _, _, mp5FromAura = BCS:GetPlayerAura(L["Mana Regeneration increased by (%d+) every 5 seconds."]) + if mp5FromAura then + BCScache["auras"].mp5 = BCScache["auras"].mp5 + tonumber(mp5FromAura)*2.5 + end + --Second Wind + _, _, mp5FromAura = BCS:GetPlayerAura(L["Restores (%d+) mana every 1 sec."]) + if mp5FromAura then + BCScache["auras"].mp5 = BCScache["auras"].mp5 + tonumber(mp5FromAura)*5 -- had to multiply by 5 the mp5FromAura because the item is a sec per tick + end + --Power of the Guardian + _, _, mp5FromAura = BCS:GetPlayerAura("Restores (%d+) mana per 5 seconds.") + if mp5FromAura then + BCScache["auras"].mp5 = BCScache["auras"].mp5 + tonumber(mp5FromAura) + end + --Aura of the blue dragon + local _, _, castingFromAura = BCS:GetPlayerAura(L["(%d+)%% of your Mana regeneration continuing while casting."]) + if castingFromAura then + BCScache["auras"].casting = BCScache["auras"].casting + tonumber(castingFromAura) + end + --Mage Armor + _, _, castingFromAura = BCS:GetPlayerAura(L["(%d+)%% of your mana regeneration to continue while casting."]) + if castingFromAura then + BCScache["auras"].casting = BCScache["auras"].casting + tonumber(castingFromAura) + end + --Sylvan Blessing + _, _, castingFromAura = BCS:GetPlayerAura("Allows (%d+)%% of mana regeneration while casting.") + if castingFromAura then + BCScache["auras"].casting = BCScache["auras"].casting + tonumber(castingFromAura) + end + --Improved Water Shield + if waterShield ~= nil then + for i = 1, 32 do + local icon, stacks = UnitBuff("player", i) + if icon and stacks and icon == "Interface\\Icons\\Ability_Shaman_WaterShield" then + BCScache["auras"].casting = BCScache["auras"].casting + (tonumber(stacks) * waterShield) + end + end + end + end + + casting = BCScache["auras"].casting + BCScache["talents"].casting + BCScache["gear"].casting + mp5 = BCScache["auras"].mp5 + BCScache["gear"].mp5 + + if casting > 100 then + casting = 100 + end + + return base, casting, mp5 +end + +--Weapon Skill code adapted from https://github.com/pepopo978/BetterCharacterStats +function BCS:GetWeaponSkill(skillName) + -- loop through skills + local skillIndex = 1 + while true do + local name, _, _, skillRank, _, skillModifier = GetSkillLineInfo(skillIndex) + if not name then + return 0 + end + + if name == skillName then + return skillRank + skillModifier + end + + skillIndex = skillIndex + 1 + end +end + +function BCS:GetWeaponSkillForWeaponType(weaponType) + if weaponType == "Daggers" then + return BCS:GetWeaponSkill("Daggers") + elseif weaponType == "One-Handed Swords" then + return BCS:GetWeaponSkill("Swords") + elseif weaponType == "Two-Handed Swords" then + return BCS:GetWeaponSkill("Two-Handed Swords") + elseif weaponType == "One-Handed Axes" then + return BCS:GetWeaponSkill("Axes") + elseif weaponType == "Two-Handed Axes" then + return BCS:GetWeaponSkill("Two-Handed Axes") + elseif weaponType == "One-Handed Maces" then + return BCS:GetWeaponSkill("Maces") + elseif weaponType == "Two-Handed Maces" then + return BCS:GetWeaponSkill("Two-Handed Maces") + elseif weaponType == "Staves" then + return BCS:GetWeaponSkill("Staves") + elseif weaponType == "Polearms" then + return BCS:GetWeaponSkill("Polearms") + elseif weaponType == "Fist Weapons" then + return BCS:GetWeaponSkill("Unarmed") + elseif weaponType == "Bows" then + return BCS:GetWeaponSkill("Bows") + elseif weaponType == "Crossbows" then + return BCS:GetWeaponSkill("Crossbows") + elseif weaponType == "Guns" then + return BCS:GetWeaponSkill("Guns") + elseif weaponType == "Thrown" then + return BCS:GetWeaponSkill("Thrown") + elseif weaponType == "Wands" then + return BCS:GetWeaponSkill("Wands") + end + -- no weapon equipped + return BCS:GetWeaponSkill("Unarmed") +end + +function BCS:GetItemTypeForSlot(slot) + local _, _, id = string.find(GetInventoryItemLink("player", GetInventorySlotInfo(slot)) or "", "(item:%d+:%d+:%d+:%d+)"); + if not id then + return + end + + local _, _, _, _, _, itemType = GetItemInfo(id); + + return itemType +end + +function BCS:GetMHWeaponSkill() + if not BCS.needScanSkills then + return BCScache["skills"].mh + end + local itemType = BCS:GetItemTypeForSlot("MainHandSlot") + BCScache["skills"].mh = BCS:GetWeaponSkillForWeaponType(itemType) + + return BCScache["skills"].mh +end + +function BCS:GetOHWeaponSkill() + if not BCS.needScanSkills then + return BCScache["skills"].oh + end + + local itemType = BCS:GetItemTypeForSlot("SecondaryHandSlot") + BCScache["skills"].oh = BCS:GetWeaponSkillForWeaponType(itemType) + + return BCScache["skills"].oh +end + +function BCS:GetRangedWeaponSkill() + if not BCS.needScanSkills then + return BCScache["skills"].ranged + end + + local itemType = BCS:GetItemTypeForSlot("RangedSlot") + BCScache["skills"].ranged = BCS:GetWeaponSkillForWeaponType(itemType) + + return BCScache["skills"].ranged +end + +--https://us.forums.blizzard.com/en/wow/t/block-value-formula/283718/18 +local enhancingTotems = nil +function BCS:GetBlockValue() + local blockValue = 0 + local _, strength = UnitStat("player", 1) + local mod = 0 + -- scan gear + for slot=1, 19 do + if BCS_Tooltip:SetInventoryItem('player', slot) then + local _, _, eqItemLink = strfind(GetInventoryItemLink('player', slot), "(item:%d+:%d+:%d+:%d+)") + if eqItemLink then BCS_Tooltip:ClearLines() BCS_Tooltip:SetHyperlink(eqItemLink) end + for line=1, BCS_Tooltip:NumLines() do + local left = getglobal(BCS_Prefix .. "TextLeft" .. line) + if left:GetText() then + local _,_, value = strfind(left:GetText(), "(%d+) Block") if value then - mp5 = mp5 + 4 + blockValue = blockValue + tonumber(value) end - _,_, value = strfind(left:GetText(), "^Equip: Allows (%d+)%% of your Mana regeneration to continue while casting.") + _,_, value = strfind(left:GetText(), "Equip: Increases the block value of your shield by (%d+).") if value then - casting = casting + tonumber(value) + blockValue = blockValue + tonumber(value) end - _,_, value = strfind(left:GetText(), "(.+) %(%d/%d%)") + _,_, value = strfind(left:GetText(), "Block Value %+(%d+)") if value then - SET_NAME = value - end - _,_, value = strfind(left:GetText(), L["^Set: Allows (%d+)%% of your Mana regeneration to continue while casting."]) - if value and SET_NAME and not tContains(mp5_Set_Bonus, SET_NAME) then - tinsert(mp5_Set_Bonus, SET_NAME) - casting = casting + tonumber(value) - end - _,_, value = strfind(left:GetText(), "^Set: Restores (%d+) mana per 5 sec.") - if value and SET_NAME and not tContains(mp5_Set_Bonus, SET_NAME) then - tinsert(mp5_Set_Bonus, SET_NAME) - mp5 = mp5 + tonumber(value) + blockValue = blockValue + tonumber(value) end end end end end - -- scan talents - local MAX_TABS = GetNumTalentTabs() - - for tab=1, MAX_TABS do - local MAX_TALENTS = GetNumTalents(tab) - - for talent=1, MAX_TALENTS do + for tab=1, GetNumTalentTabs() do + for talent=1, GetNumTalents(tab) do BCS_Tooltip:SetTalent(tab, talent) - local MAX_LINES = BCS_Tooltip:NumLines() - - for line=1, MAX_LINES do + for line=1, BCS_Tooltip:NumLines() do local left = getglobal(BCS_Prefix .. "TextLeft" .. line) if left:GetText() then - -- Priest (Meditation) / Druid (Reflection) - local _,_, value = strfind(left:GetText(), L["Allows (%d+)%% of your Mana regeneration to continue while casting."]) - local name, iconTexture, tier, column, rank, maxRank, isExceptional, meetsPrereq = GetTalentInfo(tab, talent) + local _, _, _, _, rank = GetTalentInfo(tab, talent) + --warrior/paladin + local _,_, value = strfind(left:GetText(), "amount of damage absorbed by your shield by (%d+)%%") + if value and rank > 0 then + mod = mod + tonumber(value) + break + end + --shaman + --shield specialization + _,_, value = strfind(left:GetText(), "increases the amount blocked by (%d+)%%") + if value and rank > 0 then + mod = mod + tonumber(value) + break + end + --enhancing totems + _,_, value = strfind(left:GetText(), "increases block amount by (%d+)%%") if value and rank > 0 then - casting = casting + tonumber(value) + enhancingTotems = tonumber(value) + break end - end + end end - end end - -- buffs - -- Warchief's Blessing - local _, _, mp5FromAura = BCS:GetPlayerAura(L["Increases hitpoints by 300. 15%% haste to melee attacks. (%d+) mana regen every 5 seconds."]) - if mp5FromAura then - mp5 = mp5 + 10 - end - --Epiphany - _, _, mp5FromAura = BCS:GetPlayerAura(L["Restores (%d+) mana per 5 sec."]) - if mp5FromAura then - mp5 = mp5 + tonumber(mp5FromAura) - end - --Nightfin Soup - _, _, mp5FromAura = BCS:GetPlayerAura(L["Regenerating (%d+) Mana every 5 seconds."]) - if mp5FromAura then - mp5 = mp5 + tonumber(mp5FromAura)*2.5 -- had to double the mp5FromAura because the item is a true mp5 tick - end - --Mageblood Potion - _, _, mp5FromAura = BCS:GetPlayerAura(L["Regenerate (%d+) mana per 5 sec."]) - if mp5FromAura then - mp5 = mp5 + tonumber(mp5FromAura) - end - --Fizzy Energy Drink and Sagefin - _, _, mp5FromAura = BCS:GetPlayerAura(L["Mana Regeneration increased by (%d+) every 5 seconds."]) - if mp5FromAura then - mp5 = mp5 + tonumber(mp5FromAura)*2.5 - end - --Second Wind - _, _, mp5FromAura = BCS:GetPlayerAura(L["Restores (%d+) mana every 1 sec."]) - if mp5FromAura then - mp5 = mp5 + tonumber(mp5FromAura)*5 -- had to multiply by 5 the mp5FromAura because the item is a sec per tick - end - --Aura of the blue dragon - _, _, castingFromAura = BCS:GetPlayerAura(L["(%d+)%% of your Mana regeneration continuing while casting."]) - if mp5FromAura then - casting = casting + tonumber(castingFromAura) + --Glyph of Deflection + local _, _, value = BCS:GetPlayerAura("Block value increased by (%d+).") + if value then + blockValue = blockValue + tonumber(value) end - --Power of the Guardian - _, _, castingFromAura = BCS:GetPlayerAura("Restores (%d+) mana per 5 seconds.") - if mp5FromAura then - mp5 = mp5 + tonumber(mp5FromAura) + if enhancingTotems and BCS:GetPlayerAura("^Stoneskin") then + mod = mod + enhancingTotems end - if casting > 100 then - casting = 100 - end - return base, casting, mp5 -end \ No newline at end of file + + mod = mod/100 + blockValue = blockValue + (strength/20 - 1) + blockValue = floor(blockValue + blockValue * mod) + + if blockValue < 0 then blockValue = 0 end + + return blockValue +end