vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 --[[
2 $Id: MobHealth3.lua 14223 2006-10-18 10:49:11Z hshh $
3 MobHealth3!
4 "Man, this mofo lasts forever!" "Dude, it only has 700hp and you're a paladin -_-"
5  
6 By Neronix of Hellscream EU
7 With tons of contributions by Mikk
8  
9 Special thanks to the following:
10 Mikk for writing the algorithm used now, helping with the metamethod proxy and for some optimisation info
11 Vika for creating the algorithm used in the frst 4 generations of this mod. Traces of it still remain today
12 Cladhaire, the current WatchDog maintainer for giving me permission to use Vika's algorithm
13 Ckknight for the pseudo event handler idea used in the second generation
14 Mikma for risking wiping his UBRS raid while testing the really borked first generation
15 Subyara of Hellscream EU for helping me test whether UnitPlayerControlled returns 1 for MC'd mobs
16 Iceroth for his feedback on how to solve the event handler order problem in the first generation
17 AndreasG for his feedback on how to solve the event handler order problem in the first generation and for being the first person to support MH3 in his mod
18 Worf for his input on what the API should be like
19 All the idlers in #wowace for testing and feedback
20  
21 API Documentation: http://wiki.wowace.com/index.php/MobHealth3_API_Documentation
22 --]]
23  
24 MobHealth3 = AceLibrary("AceAddon-2.0"):new("AceEvent-2.0", "AceConsole-2.0")
25  
26 --[[
27 File-scope local vars
28 --]]
29  
30 local MH3Cache = {}
31  
32 local AccumulatorHP = {} -- Keeps Damage-taken data for mobs that we've actually poked during this session
33 local AccumulatorPerc = {} -- Keeps Percentage-taken data for mobs that we've actually poked during this session
34 local calculationUnneeded = {} -- Keeps a list of things that don't need calculation (e.g. Beast Lore'd mobs)
35  
36 local currentAccHP
37 local currentAccPerc
38  
39 local targetName, targetLevel, targetIndex
40 local recentDamage, totalDamage = 0, 0
41 local startPercent, lastPercent = 100, 100
42  
43 local defaults = {
44 saveData = false,
45 precision = 10,
46 stableMax = false,
47 }
48  
49 -- Metatable that provides compat for mods that index MobHealthDB directly
50 local compatMT ={
51 __index = function(t, k)
52 return MH3Cache[k] and MH3Cache[k].."/100";
53 end
54 }
55  
56 -- Debug function. Not for Joe Average
57 function GetMH3Cache() return MH3Cache end
58  
59  
60 --[[
61 Init/Enable methods
62 --]]
63  
64 function MobHealth3:OnInitialize()
65 -- If config savedvars don't exist, create them
66 MobHealth3Config = MobHealth3Config or defaults
67  
68 -- If the user is saving data, then load it into the cache
69 if MobHealth3DB and MobHealth3Config.saveData then
70 MH3Cache = MobHealth3DB
71 elseif (MobHealth3Config.saveData) then
72 MobHealth3DB = MH3Cache
73 end
74  
75 self:RegisterChatCommand({"/mobhealth3", "/mh3"}, {
76 type = "group",
77 args = {
78 save = {
79 name = "Save Data",
80 desc = "Save data across sessions. This is optional, and |cff00ff00not really needed|r. A cache is always kept that has data for every enemy you fought this session. Remember, recalculating an enemy's health is |cff00ff00TRIVIAL|r",
81 type = "toggle",
82 get = function() return not not MobHealth3DB end, -- "Double negatives for the not lose!" -Wobin
83 set = function(val)
84 if val == false then
85 MobHealth3DB = nil
86 MobHealth3Config.saveData = val
87 else
88 MobHealth3DB = MH3Cache
89 MobHealth3Config.saveData = val
90 end
91 end,
92 },
93 precision = {
94 name = "Estimation Precision",
95 desc = "Adjust how accurate you want MobHealth3 to be (A number 2-99). This is how many percent a mob's health needs to to change before we will trust the estimated maximum health and display it. The lower this value is, the quicker you'll see a value and the less accurate it will be. Raiding players may want to turn this down a bit. If you don't care about accuracy and want info ASAP, set this to 1.",
96 type = "range",
97 step = 1,
98 min = 1,
99 max = 99,
100 get = function() return MobHealth3Config.precision end,
101 set = function(val) MobHealth3Config.precision = tonumber(val) end,
102 },
103 stablemax = {
104 name = "Stable Max",
105 desc = "When turned on, the max HP only updates once your target changes. If data for the target is unknown, MH3 will update once during the battle when the precision percentage is reached",
106 type = "toggle",
107 get = function() return MobHealth3Config.stableMax end,
108 set = function(val) MobHealth3Config.stableMax = val end,
109 },
110 reset = {
111 name = "Reset Cache/DB",
112 desc = "Reset the session cache and the DB if you have saving turned on",
113 type = "execute",
114 func = function()
115 MH3Cache = {}
116 AccumulatorHP = {}
117 AccumulatorPerc = {}
118 MobHealth3DB = MobHealth3Config.saveData and {} or nil
119 self:Print("Cache/Database reset")
120 end,
121 },
122 },
123 })
124  
125 -- MH/MH2 database converter. MI2 too if the user follows the instructions
126 if MobHealthDB and not MobHealthDB.thisIsADummyTable then
127 -- Turn on saving
128 MobHealth3DB = MH3Cache
129 MobHealth3Config.saveData = true
130  
131 for k,v in pairs(MobHealthDB) do
132 local _, _, pts, pct = string.find(v, "(%d+)/(%d+)")
133 if pts then
134 local maxHP = math.floor(pts/pct * 100 + 0.5)
135 MH3Cache[k] = maxHP
136 end
137 end
138 self:Print("Old MobHealth/MobHealth2/MobInfo2 database detected and converted. MH3 has also automatically turned on saving for you to preserve the data")
139 end
140  
141 MobHealthDB = { thisIsADummyTable = true }
142 setmetatable(MobHealthDB, compatMT) -- Metamethod proxy ENGAGE!! </cheesiness>
143 end
144  
145 function MobHealth3:OnEnable()
146 self:RegisterEvent("UNIT_COMBAT")
147 self:RegisterEvent("PLAYER_TARGET_CHANGED")
148 self:RegisterEvent("UNIT_HEALTH")
149 end
150  
151 --[[
152 Dummy MobHealthFrame. Some mods use this to detect MH/MH2/MI2
153 --]]
154  
155 CreateFrame("frame", "MobHealthFrame")
156  
157 --[[
158 Event Handlers
159 --]]
160  
161 function MobHealth3:UNIT_COMBAT()
162 if arg1=="target" and currentAccHP then
163 recentDamage = recentDamage + arg4
164 totalDamage = totalDamage + arg4
165 end
166 end
167  
168 function MobHealth3:PLAYER_TARGET_CHANGED()
169 if MobHealth3Config.stableMax and currentAccHP and currentAccHP > 0 and currentAccPerc > 0 then
170 -- Save now if we have actual values (0 /0 --> 1.#IND == BAD)
171 MH3Cache[targetIndex] = math.floor( currentAccHP / currentAccPerc * 100 + 0.5 )
172 end
173  
174 -- Is target valid?
175 -- We ignore pets. There's simply far too many pets that share names with players so we let players take priority
176  
177 local creatureType = UnitCreatureType("target") -- Saves us from calling it twice
178 if UnitCanAttack("player", "target") and not UnitIsDead("target") and not UnitIsFriend("player", "target") and not ( (creatureType == MOBHEALTH_UnitCreatureType_Beast or creatureType == MOBHEALTH_UnitCreatureType_Demon) and UnitPlayerControlled("target") ) then
179  
180 targetName = UnitName("target")
181 targetLevel = UnitLevel("target")
182  
183 targetIndex = string.format("%s:%d", targetName, targetLevel)
184  
185 --self:Debug("Acquired valid target: index: %s, in db: %s", targetIndex, not not MH3Cache[targetIndex])
186  
187 recentDamage, totalDamage = 0, 0, 0
188 startPercent = UnitHealth("target")
189 lastPercent = startPercent
190  
191 currentAccHP = AccumulatorHP[targetIndex]
192 currentAccPerc = AccumulatorPerc[targetIndex]
193  
194 if not UnitIsPlayer("target") then
195 -- Mob: keep accumulated percentage below 200% in case we hit mobs with different hp
196 if not currentAccHP then
197 if MH3Cache[targetIndex] then
198 -- We claim that this previous value that we have is from seeing percentage drop from 100 to 0
199 AccumulatorHP[targetIndex] = MH3Cache[targetIndex]
200 AccumulatorPerc[targetIndex] = 100
201 else
202 -- Nothing previously known. Start fresh.
203 AccumulatorHP[targetIndex] = 0
204 AccumulatorPerc[targetIndex] = 0
205 end
206 currentAccHP = AccumulatorHP[targetIndex]
207 currentAccPerc = AccumulatorPerc[targetIndex]
208 end
209  
210 if currentAccPerc>200 then
211 currentAccHP = currentAccHP / currentAccPerc * 100
212 currentAccPerc = 100
213 end
214  
215 else
216 -- Player health can change a lot. Different gear, buffs, etc.. we only assume that we've seen 10% knocked off players previously
217 if not currentAccHP then
218 if MH3Cache[targetIndex] then
219 AccumulatorHP[targetIndex] = MH3Cache[targetIndex]*0.1
220 AccumulatorPerc[targetIndex] = 10
221 else
222 AccumulatorHP[targetIndex] = 0
223 AccumulatorPerc[targetIndex] = 0
224 end
225 currentAccHP = AccumulatorHP[targetIndex]
226 currentAccPerc = AccumulatorPerc[targetIndex]
227 end
228  
229 if currentAccPerc>10 then
230 currentAccHP = currentAccHP / currentAccPerc * 10
231 currentAccPerc = 10
232 end
233  
234 end
235  
236 else
237 --self:Debug("Acquired invalid target. Ignoring")
238 currentAccHP = nil
239 currentAccPerc = nil
240 end
241 end
242  
243 function MobHealth3:UNIT_HEALTH()
244 if currentAccHP and arg1=="target" then
245 self:CalculateMaxHealth(UnitHealth("target"), UnitHealthMax("target"))
246 end
247 end
248  
249 --[[
250 The meat of the machine!
251 --]]
252  
253 function MobHealth3:CalculateMaxHealth(current, max)
254  
255 if calculationUnneeded[targetIndex] then return;
256  
257 elseif current==startPercent or current==0 then
258 --self:Debug("Targetting a dead guy?")
259  
260 elseif max > 100 then
261 -- zOMG! Beast Lore! We no need no stinking calculations!
262 MH3Cache[targetIndex] = max
263 -- print(string.format("We got beast lore! Max is %d", max))
264 calculationUnneeded[targetIndex] = true
265  
266 elseif current > lastPercent or startPercent>100 then
267 -- Oh noes! It healed! :O
268 lastPercent = current
269 startPercent = current
270 recentDamage=0
271 totalDamage=0
272 --self:Debug("O NOES IT HEALED!?")
273  
274 elseif recentDamage>0 then
275  
276 if current~=lastPercent then
277 currentAccHP = currentAccHP + recentDamage
278 currentAccPerc = currentAccPerc + (lastPercent-current)
279 recentDamage = 0
280 lastPercent = current
281  
282 if currentAccPerc >= MobHealth3Config.precision and not (MobHealth3Config.stableMax and MH3Cache[targetIndex]) then
283 MH3Cache[targetIndex] = math.floor( currentAccHP / currentAccPerc * 100 + 0.5 )
284 --self:Debug("Caching %s as %d", targetIndex, MH3Cache[targetIndex])
285 end
286  
287 end
288  
289 end
290 end
291  
292 --[[
293 Compatibility for functions MobHealth2 introduced
294 --]]
295  
296 function MobHealth_GetTargetMaxHP()
297 local currHP, maxHP, found = MobHealth3:GetUnitHealth("target", UnitHealth("target"), UnitHealthMax("target"), UnitName("target"), UnitLevel("target"))
298 return found and maxHP or nil
299 end
300  
301 function MobHealth_GetTargetCurHP()
302 local currHP, maxHP, found = MobHealth3:GetUnitHealth("target", UnitHealth("target"), UnitHealthMax("target"), UnitName("target"), UnitLevel("target"))
303 return found and currHP or nil
304 end
305  
306 --[[
307 Compatibility for MobHealth_PPP()
308 --]]
309  
310 function MobHealth_PPP(index)
311 return MH3Cache[index] and MH3Cache[index]/100 or 0
312 end
313  
314 --[[
315 MobHealth3 API
316  
317 I'm using MediaWiki formatting for the docs here so I can easily copy/paste if I make modifications
318 --]]
319  
320 --[[
321 == MobHealth3:GetUnitHealth(unit[,current][, max][, name][, level]) ==
322 Returns the current and max health of the specified unit
323  
324 === Args ===
325 ; unit : The unitID you want health for
326 ; [current] : (optional) The value of UnitHealth(unit). Passing this if your mod knows it saves MH3 from having to call UnitHealth itself<br>
327 ; [max] : (optional) The value of UnitHealthMax(unit). Passing this if your mod knows it saves MH3 from having to call UnitHealthMax itself<br>
328 ; [name] : (optional) The name of the unit. Passing this if your mod knows it saves MH3 from having to call UnitName itself<br>
329 ; [level] : (optional) The level of the unit. Passing this if your mod knows it saves MH3 from having to call UnitLevel itself
330  
331 === Returns ===
332 If the specified unit is alive, hostile and its true max health unknown, the absolute current and max value based on the cache's current entry<br>
333 If the specified unit's friendly or data was not found, returns UnitHealth(unit) and UnitHealthMax(unit)
334  
335 A third return value is a boolean stating whether an estimation was found
336  
337 === Remarks ===
338 Remember, args 2-5 are optional and passing the args if your mod already knows them saves MH3 from having to find the data itself<br>
339 Don't pass level as a string. Please.<br>
340 MobHealth3 does the target-validty checking for you
341  
342 === Example ===
343 function YourUnitFrames:UpdateTargetFrame()
344 local name, level = UnitName("target"), UnitLevel("target")
345 local cur, max, found
346 if MobHealth3 then
347 cur, max, found = MobHealth3:GetUnitHealth("target", UnitHealth("target"), UnitHealthMax("target"), name, level)
348 else
349 cur, max = UnitHealth("target"), UnitHealthMax("target")
350 end
351 YourTargetFrame.HealthText:SetText(cur .. "/" .. max)
352 YourTargetFrame.NameText:SetText(name)
353 YourTargetFrame.LevelText:SetText(level)
354 end
355 --]]
356 function MobHealth3:GetUnitHealth(unit, current, max, name, level)
357 if not UnitExists(unit) then return 0, 0, false; end
358  
359 current = current or UnitHealth(unit)
360 max = max or UnitHealthMax(unit)
361 name = name or UnitName(unit)
362 level = level or UnitLevel(unit)
363  
364 -- Mini validity check.
365 -- No need to do the full thing because indexing the cache and getting nil back is much faster.
366 -- Remember, an invalid target should never be in the cache
367 -- The only parts we actually have to do are:
368 -- Pet check
369 -- Beast Lore check (Does UnitHealthMax give us the real value?)
370  
371 local creatureType = UnitCreatureType(unit) -- Saves us from calling it twice
372 if max == 100 and not ( (creatureType == MOBHEALTH_UnitCreatureType_Demon or creatureType == MOBHEALTH_UnitCreatureType_Beast) and UnitPlayerControlled(unit) ) then
373 local maxHP = MH3Cache[string.format("%s:%d", name, level)]
374  
375 if maxHP then return math.floor(current/100 * maxHP + 0.5), maxHP, true; end
376 end
377  
378 -- If not maxHP or we're dealing with an invalid target
379 return current, max, false;
380 end