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