local InfoMsg=true; -- Is info messages shown local DebugMsg=false; -- Is debug messages shown local Loaded=false; -- Is addon loaded local ForceVisible=false; -- If bar is forced to be shown local FadeOut=false; -- If bar is to be faded out -- Spell data local SpellName=nil; -- Spell name, set to nil to disable local SpellRank=0; -- Spell rank local SpellHealAmount=1; -- Suspected healing amount local SpellTarget=nil; -- Spell target local SpellTargetName=nil; -- Target's name local UpdateHealth=false; -- Is target's health changed local OverhealDetected=false; -- If spell detected to do overhealing -- Party data local GroupMaxHeal={}; -- How much each party member can receive healing, sorted by name -- Healing meter local TotalHealing=0; -- Total healing local TotalOverHealing=0; -- Total overhealing -- Saved variables HealingEstimatorData= { Scale=1; -- Healing bar scale Limit=0.5; -- Overhealing alert limit Icon=true; -- If minimap icon is shown IconPos=4.98; -- Position in radians IconAnchoredAtMinimap=true; -- If icon is anchored to minimap ConvertCrits=true; -- If crits are to be converted to normal heals, new in 1.2 }; HealingEstimatorCharData={}; -- Spell data goes here --[[ TODO: - Different limits for different spells - Fix localization in menu ]]-- -------------------------------------------------------------------- -- Debug print -- function HealingEstimator_Print(Visible, Txt) if (Visible and Txt) then DEFAULT_CHAT_FRAME:AddMessage("|cFFA0A0FF[HealingEstimator]|r "..Txt,0.5,0.5,1); end end -------------------------------------------------------------------- -- Initialization -------------------------------------------------------------------- -------------------------------------------------------------------- -- OnLoad -- function HealingEstimator_OnLoad() -- Register events -- For initialization this:RegisterEvent("VARIABLES_LOADED"); end -------------------------------------------------------------------- -- Init -- function HealingEstimator_Init() if (Loaded) then return; end Loaded=true; -- Check if addon needs to be enabled if ( not HealingEstimator_IsHealingClass(UnitClass("player")) ) then return; end -- Register more events -- When unit heal changes this:RegisterEvent("UNIT_HEALTH"); this:RegisterEvent("UNIT_MAXHEALTH"); this:RegisterEvent("PARTY_MEMBERS_CHANGED"); -- Healing spell you casted this:RegisterEvent("CHAT_MSG_SPELL_SELF_BUFF"); this:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"); this:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS"); this:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS"); this:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS"); -- For spells this:RegisterEvent("UNIT_SPELLCAST_SENT"); this:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED"); this:RegisterEvent("UNIT_SPELLCAST_STOP"); this:RegisterEvent("UNIT_SPELLCAST_FAILED"); this:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED"); -- Set slash command SlashCmdList["HEALINGESTIMATORCOMMAND"] = HealingEstimator_SlashHandler; SLASH_HEALINGESTIMATORCOMMAND1 = "/healingestimator"; SLASH_HEALINGESTIMATORCOMMAND2 = "/heal"; -- Set new saved variables if (HealingEstimatorData.ConvertCrits==nil) then HealingEstimatorData.ConvertCrits=true; end -- Init data GroupMaxHeal={}; GroupMaxHeal[UnitName("player")]=UnitHealthMax("player")-UnitHealth("player"); -- Set scale HealingEstimator:SetScale( HealingEstimatorData.Scale ); -- Set BarBG color HealingEstimatorBarBG:SetVertexColor(0.4,0.4,0.4, 0.8); -- Replace text parsing functions on "difficult" locales local Locale=GetLocale(); if (Locale=="frFR") then HealingEstimator_ParseSelfBuff=HealingEstimator_ParseSelfBuff_FR; HealingEstimator_ParsePeriodicSelfBuff=HealingEstimator_ParsePeriodicSelfBuff_FR; end -- Print welcome message HealingEstimator_Print(InfoMsg,HealingEstimatorLoc.Welcome); end -------------------------------------------------------------------- -- Slash command handling -------------------------------------------------------------------- -------------------------------------------------------------------- -- Slash handler -- function HealingEstimator_SlashHandler(msg) if (msg=="debug") then -- "/heal debug" - turn debug on/off if (DebugMsg) then DebugMsg=false; else DebugMsg=true; end else -- Show menu HealingEstimatorMenu:Show(); end end -------------------------------------------------------------------- -- Event handling -------------------------------------------------------------------- -------------------------------------------------------------------- -- OnEvent -- function HealingEstimator_OnEvent(event) HealingEstimator_Print(DebugMsg,event); if (arg1) then HealingEstimator_Print(DebugMsg,"1: "..arg1) end; if (arg2) then HealingEstimator_Print(DebugMsg,"2: "..arg2) end; if (arg3) then HealingEstimator_Print(DebugMsg,"3: "..arg3) end; if (arg4) then HealingEstimator_Print(DebugMsg,"4: "..arg4) end; if (arg5) then HealingEstimator_Print(DebugMsg,"5: "..arg5) end; if (event=="UNIT_HEALTH" or event=="UNIT_MAXHEALTH") then -- Unit health changed -- Update max heal local Name=UnitName(arg1); if (Name) then GroupMaxHeal[Name]=UnitHealthMax(arg1)-UnitHealth(arg1); HealingEstimator_Print(DebugMsg,Name..": "..GroupMaxHeal[Name]); end -- Check if this is spell target if (arg1==SpellTarget) then UpdateHealth=true; -- Update maximum values if needed if (event=="UNIT_MAXHEALTH") then local MaxHealth=UnitHealthMax(arg1); HealingEstimatorBar:SetMinMaxValues(0,MaxHealth); HealingEstimatorHealingBar:SetMinMaxValues(0,MaxHealth); HealingEstimatorHealFlash:SetWidth( (SpellHealAmount*200)/MaxHealth ); end end elseif (event=="PARTY_MEMBERS_CHANGED") then -- Party changed -- Clear data GroupMaxHeal={}; -- Set player data GroupMaxHeal[UnitName("player")]=UnitHealthMax("player")-UnitHealth("player"); -- Set group data local c; local Group,Count=HealingEstimator_GetGroupInfo(); for c=1,Count do local Name=Group..c; if (UnitName(Name)) then GroupMaxHeal[UnitName(Name)]=UnitHealthMax(Name)-UnitHealth(Name); end end elseif (event=="UNIT_SPELLCAST_SENT" and arg1=="player") then -- Spellcast started, get spell info HealingEstimator_StartSpell(arg2,arg3,arg4); elseif (event=="UNIT_SPELLCAST_SUCCEEDED" and arg1=="player") then -- Spell succeeded FadeOut=true; elseif (event=="UNIT_SPELLCAST_FAILED" or event=="UNIT_SPELLCAST_INTERRUPTED" or event=="UNIT_SPELLCAST_STOP" and arg1=="player") then -- Spell failed FadeOut=true; SpellName=nil; OverhealDetected=false; elseif (event=="CHAT_MSG_SPELL_SELF_BUFF") then -- You healed someone -- Parse combat log message at localized function HealingEstimator_AddHealing( true, HealingEstimator_ParseSelfBuff(arg1) ); -- Show flash HealingEstimatorFlash:Show(); HealingEstimatorHealFlash:SetPoint("LEFT","HealingEstimatorHealingBar","LEFT"); HealingEstimatorHealFlash:Show(); elseif (event=="CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS" or event=="CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS" or event=="CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS" or event=="CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS" ) then -- Your heal-over-time healed someone -- Parse combat log message at localized function HealingEstimator_AddHealing( false, HealingEstimator_ParsePeriodicSelfBuff(arg1) ); elseif (event == "VARIABLES_LOADED") then -- Variables loaded, do initialization HealingEstimator_Init(); end end -------------------------------------------------------------------- -- Misc functions -------------------------------------------------------------------- -------------------------------------------------------------------- -- Adds healing data -- function HealingEstimator_AddHealing(UpdateSpellData, Spell, Target, Heal, Crit) -- If spell name and heal value was resolved if (not (Spell and Target and Heal)) then -- Was not, bail out return; end -- Update healing amount Heal=Heal+0; -- Convert healing amount to number TotalHealing=TotalHealing+Heal; -- Check that unit data exists if (not GroupMaxHeal[Target]) then -- Somehow unit data was missing, do not process further return; end local MaxHeal=GroupMaxHeal[Target]; -- Check overhealing if (Heal>MaxHeal) then TotalOverHealing=TotalOverHealing+(Heal-MaxHeal); local Over=floor(((Heal-MaxHeal)/Heal)*100); HealingEstimator_Print(DebugMsg,Over.."% overheal"); end HealingEstimatorPie_SetValue(TotalOverHealing/TotalHealing); -- Check if healing data is to be updated to spell data if (not UpdateSpellData) then return; end -- Check if heal was critical if (Crit and HealingEstimatorData.ConvertCrits) then Heal=Heal/1.5; end -- Make sure that data exists HealingEstimator_Print(DebugMsg,Spell.." "..Heal); if (not HealingEstimatorCharData[Spell]) then return; end if (not HealingEstimatorCharData[Spell][SpellRank]) then return; end local Avg=HealingEstimatorCharData[Spell][SpellRank]; if (Avg==0) then Avg=Heal; else Avg=(Avg*3 + Heal)/4; --Avg=(Avg+Heal)/2; end HealingEstimatorCharData[Spell][SpellRank]=Avg; end -------------------------------------------------------------------- -- Returns healing data -- function HealingEstimator_GetData() return TotalHealing,TotalOverHealing; end -------------------------------------------------------------------- -- Clears healing data -- function HealingEstimator_ClearData() TotalHealing=0; TotalOverHealing=0; HealingEstimatorPie_SetValue(0); HealingEstimator_Print(InfoMsg,"Healing data cleared"); end -------------------------------------------------------------------- -- Forces bar visible -- function HealingEstimator_ForceVisible(Value) ForceVisible=Value; if (ForceVisible) then HealingEstimatorBar:SetMinMaxValues(0,1); HealingEstimatorBar:SetValue(1); HealingEstimatorHealingBar:SetMinMaxValues(0,1); HealingEstimatorHealingBar:SetValue(0); HealingEstimatorText:SetText(HealingEstimatorLoc.TargetName); HealingEstimatorFlash:Hide(); HealingEstimatorHealFlash:Hide(); HealingEstimator:SetAlpha(1); HealingEstimator:Show(); else HealingEstimator:Hide(); end end -------------------------------------------------------------------- -- Tells if heal bar can be moved -- function HealingEstimator_CanMove() if (IsShiftKeyDown() or ForceVisible) then return true; end return false; end -------------------------------------------------------------------- -- Gets the "name" and count of group -- i.e. returns "party" or "raid", so those can be used like "partyX" or "raidX" -- function HealingEstimator_GetGroupInfo() -- Assume that player is in raid local Group="raid"; local Count=GetNumRaidMembers(); -- If he is not in the group, assume party if (Count==0) then Group="party"; Count=GetNumPartyMembers(); end -- If he isn't even in raid, then he is alone if (Count==0) then Group=nil; Count=0; end -- Return group data return Group,Count; end -------------------------------------------------------------------- -- Tries to get group name for given unit -- function HealingEstimator_GetGroupName(Name) if (UnitName("player")==Name) then return "player"; else -- Check if unit is in group or raid local Group,Count = HealingEstimator_GetGroupInfo(); if (Count~=0) then local c; for c=1,Count do if (UnitName(Group..c)==Name) then -- Unit was found, return HealingEstimator_Print(DebugMsg,"Group target: "..Group..c); return Group..c; end end end end -- Unit was not found from group or raid return nil; end -------------------------------------------------------------------- -- Sets visual data of the unit -- function HealingEstimator_SetTargetData(Unit,HealAverage) -- Check that unit is not nil if (not Unit) then return; end -- Set target health bar local Health=UnitHealth(Unit); local MaxHealth=UnitHealthMax(Unit); HealingEstimatorBar:SetMinMaxValues(0,MaxHealth); HealingEstimatorBar:SetValue(Health); HealingEstimatorText:SetText(SpellTargetName); -- Set healing bar HealingEstimatorHealingBar:SetMinMaxValues(0,MaxHealth); HealingEstimatorHealingBar:SetValue(HealAverage); local Pos=(Health*200)/MaxHealth; HealingEstimatorHealingBar:SetPoint("LEFT","HealingEstimatorBar","LEFT",Pos,0); HealingEstimatorHealingBar:SetStatusBarColor(1,1,0); -- Set flash bar width HealingEstimatorHealFlash:SetWidth( (HealAverage*200)/MaxHealth ); -- Show bars HealingEstimatorFlash:Hide(); HealingEstimatorHealFlash:Hide(); HealingEstimator:SetAlpha(1); HealingEstimator:Show(); UpdateHealth=true; FadeOut=false; end -------------------------------------------------------------------- -- Find spell heal average -- function HealingEstimator_FindSpellHealAverage(Spell,Rank) if (HealingEstimatorCharData[Spell]) then if (HealingEstimatorCharData[Spell][Rank]) then return HealingEstimatorCharData[Spell][Rank]; end end -- Search the spell from spellbook local SpellId=1; local FoundSpell=nil; while true do -- Get spell name and rank local TempName,TempRank = GetSpellName(SpellId,BOOKTYPE_SPELL); if (not TempName) then -- All spells tested, exit break; end -- Check spell name if (Spell==TempName and Rank==TempRank) then -- Correct spell found -- Set spell to tooltip and read healing average HealingEstimatorTooltip:ClearLines(); HealingEstimatorTooltip:SetOwner(HealingEstimator,"ANCHOR_NONE"); HealingEstimatorTooltip:SetSpell(SpellId,BOOKTYPE_SPELL); -- Read data from tooltip local HealAverage; local Text=HealingEstimatorTooltipTextLeft4:GetText(); if (Text) then local t1,t2; for t1,t2 in string.gmatch(Text, HealingEstimatorLoc.HealValue) do HealingEstimator_Print(DebugMsg,"Heal: "..t1.." - "..t2); HealAverage=(t1+t2)/2; break; end end -- Add data to char data if (not HealingEstimatorCharData[Spell]) then HealingEstimatorCharData[Spell]={}; end if (not HealingEstimatorCharData[Spell][Rank]) then HealingEstimatorCharData[Spell][Rank]={}; end HealingEstimatorCharData[Spell][Rank]=HealAverage; -- Return value return HealAverage; end SpellId=SpellId+1; end -- Error, spell not found return nil; end -------------------------------------------------------------------- -- StartSpell -- Does the initialization for spellcast -- function HealingEstimator_StartSpell(Spell,Rank,Target) -- Check if spell is healing spell (localized function) if (not HealingEstimator_IsHealingSpell(Spell)) then -- It wasn't return; end -- Get spell info SpellName=Spell; SpellRank=Rank; SpellTarget=HealingEstimator_GetGroupName(Target); SpellTargetName=Target; SpellHealAmount=HealingEstimator_FindSpellHealAverage(Spell,Rank); -- Check that unit exists if ((not SpellTarget) or (not SpellHealAmount)) then -- Some error, no target, but casting heal return; end HealingEstimator_Print(DebugMsg,SpellName.." "..SpellRank.." ("..SpellHealAmount..") -> "..SpellTarget); -- Try to find target unit and set data HealingEstimator_SetTargetData(SpellTarget,SpellHealAmount); end -------------------------------------------------------------------- -- OnUpdate -- Updates the target health, healing & overhealing values -- function HealingEstimator_OnUpdate(Time) -- Check that there is some spell if (SpellName==nil or SpellTarget==nil or FadeOut==true) then --HealingEstimator:Hide(); -- If this is forced to be visible don't fade out if (ForceVisible) then return; end -- Fade out local Alpha=this:GetAlpha()-Time; if (Alpha<0) then this:Hide(); end -- Set alpha this:SetAlpha(Alpha); return; end -- Check if update is needed if (not UpdateHealth) then return; end UpdateHealth=false; -- Target is lost somehow if (UnitName(SpellTarget) ~= SpellTargetName) then SpellName=nil; OverhealDetected=false; return; end -- Find target health local Health=UnitHealth(SpellTarget); local MaxHealth=UnitHealthMax(SpellTarget); local MaxHeal=GroupMaxHeal[SpellTargetName]; if (not MaxHeal) then -- Somehow unit data is missing MaxHeal=MaxHealth-Health; -- Calculate "rough" value end -- Calculate healing and overhealing values local OverHealing=SpellHealAmount-MaxHeal; HealingEstimator_Print(DebugMsg,Health.."/"..MaxHealth..": "..SpellHealAmount.."("..OverHealing..")"); if (OverHealing < 0) then -- There is no overhealing OverHealing=0; end -- Update target health bar HealingEstimatorBar:SetValue(Health); -- Set color of healing bar -- Yellow (1,1,0) if overhealing is under the limit -- Red (1,0,0) if there is overhealing local Green=1; local Ratio=OverHealing/SpellHealAmount; OverhealDetected=false; if (Ratio > HealingEstimatorData.Limit) then -- Overhealing detected, change color to red OverhealDetected=true; Green=0; end HealingEstimatorHealingBar:SetStatusBarColor(1,Green,0); -- Update healing bar position local Pos=(Health*200)/MaxHealth; if (Pos>200) then Pos=200; end HealingEstimatorHealingBar:SetPoint("LEFT","HealingEstimatorBar","LEFT",Pos,0); end