vanilla-wow-addons – Blame information for rev 1
?pathlinks?
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 |