Merge branch 'master' of git://cams.pavlovian.net/questhelper
[QuestHelper.git] / collect_loot.lua
blob5224d2e1c7a5c20a05a40544cc61a479add0a986
1 QuestHelper_File["collect_loot.lua"] = "Development Version"
2 QuestHelper_Loadtime["collect_loot.lua"] = GetTime()
4 local debug_output = false
5 if QuestHelper_File["collect_loot.lua"] == "Development Version" then debug_output = true end
7 local QHC
9 local GetMonsterUID
10 local GetMonsterType
12 local GetItemType
14 local GetLoc
16 local Patterns
18 local members = {}
19 local members_count = 0
20 local members_refs = {} -- "raid6" and the like
22 local function MembersUpdate()
23 -- We want to keep track of exactly who is in a group with this player, so we can watch for combat messages involving them, so we can see who's been tapped, so we can record the right deaths, so we can know who the player should be able to loot.
24 -- I hate my life.
25 -- >:(
27 local alone = false
29 --QuestHelper:TextOut("MU start")
30 members = {} -- we burn a table every time this updates, but whatever
31 members_count = 0
32 if GetNumRaidMembers() > 0 then
33 -- we is in a raid
34 for i = 1, 40 do
35 local ite = string.format("raid%d", i)
36 local gud = UnitGUID(ite)
37 if gud then
38 members[gud] = true
39 table.insert(members_refs, ite)
40 --QuestHelper:TextOut(string.format("raid member %s added", UnitName(ite)))
41 end
42 end
43 elseif GetNumPartyMembers() > 0 then
44 -- we is in a party
45 for i = 1, 4 do
46 local ite = string.format("party%d", i)
47 local gud = UnitGUID(ite)
48 if gud then
49 members[gud] = true
50 table.insert(members_refs, ite)
51 --QuestHelper:TextOut(string.format("party member %s added", UnitName(ite)))
52 end
53 end
54 members[UnitGUID("player")] = true
55 table.insert(members_refs, "player")
56 --QuestHelper:TextOut(string.format("player %s added", UnitName("player")))
57 else
58 -- we is alone ;.;
59 if UnitGUID("player") then members[UnitGUID("player")] = true end -- it's possible that we haven't logged in entirely yet
60 table.insert(members_refs, "player")
61 --QuestHelper:TextOut(string.format("player %s added as %s", UnitName("player"), tostring(UnitGUID("player"))))
62 alone = true
63 end
65 if GetLootMethod() == "master" and not alone then members = {} members_refs = {} end -- We're not going to bother trying to deal with master loot right now - it's just too different and I just don't care enough.
67 for _, _ in pairs(members) do members_count = members_count + 1 end -- lulz
68 end
70 local MS_TAPPED_US = 1
71 local MS_TAPPED_US_TRIVIAL = 2
72 local MS_TAPPED_OTHER = 3
73 local MS_TAPPED_LOOTABLE = 4
74 local MS_TAPPED_LOOTABLE_TRIVIAL = 5
75 local MS_TAPPED_LOOTED = 6
77 local last_cleanup = GetTime()
79 local monsterstate = {}
80 local monsterrefresh = {}
81 local monstertimeout = {}
83 -- This all does something quite horrible.
84 -- Some monsters don't become lootable when they're killed and didn't drop anything. We need to record this so we can get real numbers for them.
85 -- Unfortunately, we can't just record when "something" is killed. We have to record when "our group" killed it, so we know that there *was* a chance of looting it.
86 -- As such, we need to check for monster deaths that the player may never have actually targeted. It gets, to put it mildly, grim, and unfortunately we'll never be able to solve it entirely.
87 -- Worse, we need to *not* record item drops for things that we never actually "saw" but that were lootable anyway, because if we do, we bias the results towards positive (i.e. if we AOE ten monsters down, and two of them drop, and we loot those, that's 2/2 if we record the drops, and 0/0 if we don't, while what we really want is 2/10. 0/0 is at least "not wrong".)
88 -- On top of this, we want to avoid looting "discarded items", but unfortunately there's no real good way to determine this. Welp.
89 local last_scan = 0
90 -- Here's a little bottleneck - we're going to try to only do one scan per second. Fingers crossed.
92 local function CombatLogEvent(_, event, sourceguid, _, _, destguid, _, _, _, spellname)
93 -- There's many things that are handled here.
94 -- First, if there's any damage messages coming either to or from a party member, we check to see if that monster is tapped by us. If it's tapped, we cache the value for 15 seconds, expiring entirely in 30.
95 -- Second, there's the Death message. If it's tapped by us, increases the kill count by 1/partymembers and changes its state to lootable.
96 if event ~= "UNIT_DIED" then
97 if last_scan > GetTime() then return end -- welp
99 -- Something has been attacked by something, maybe.
100 if not string.find(event, "_DAMAGE$") then return end -- We only care about something punching something else.
102 local target, source
103 if members[sourceguid] then
104 source = sourceguid
105 target = destguid
106 elseif members[destguid] then
107 source = targetguid
108 target = sourceguid
109 end -- If one of the items is in our party, the other is our target.
111 if not target then return end -- If we don't have a target, then nobody is in our party, and we don't care.
113 if monsterrefresh[target] and monsterrefresh[target] > GetTime() then return end -- we already have fresh data, so we're good
115 -- Now comes the tricky part. We can't just look at the target because we're not allowed to target by GUID. So we iterate through all the party/raid members and hope *someone* has it targeted. Luckily, we can stop once we find someone who does.
116 local targ
117 for _, v in pairs(members_refs) do
118 targ = v .. "target" if UnitGUID(targ) == target then break end
119 targ = nil
121 last_scan = GetTime() + 1
123 if not targ then
124 --monsterrefresh[target] = GetTime() + 5
125 --monstertimeout[target] = GetTime() + 5
126 return
127 end -- Well, nobody seems to be targeting it. That's . . . odd, and annoying. We were thinking of tossing on a 5-second timeout. But we didn't, because we decided the global one-second lockout might help. So we'll do that for now.
129 -- Okay. So we know who's targeting it. Now, let's see who has it tapped, if anyone.
130 if not UnitIsTapped(targ) then
131 -- Great. Nobody is. That is just *great*. Look how exuberant I feel at this moment. You know what? 5-second timeout.
132 monsterstate[target] = nil
133 monsterrefresh[target] = GetTime() + 5
134 monstertimeout[target] = GetTime() + 5
135 --if debug_output then QuestHelper:TextOut(string.format("Monster ignorified")) end
136 else
137 -- We know someone is, so we're going to set up our caching . . .
138 monsterrefresh[target] = GetTime() + 15
139 monstertimeout[target] = GetTime() + 30
140 monsterstate[target] = (UnitIsTappedByPlayer(targ)) and (not UnitIsTrivial(targ) and MS_TAPPED_US or MS_TAPPED_US_TRIVIAL) or MS_TAPPED_OTHER -- and figure out if it's us. Or if it's trivial. We somewhat-ignore it if it's trivial, since it's much less likely to be looted and that could throw off our numbers
141 --if debug_output then QuestHelper:TextOut(string.format("Monster %s set to %d, %s, %s", target, monsterstate[target], tostring(UnitIsTappedByPlayer(targ)), tostring(UnitIsTrivial(targ)))) end
144 -- DONE
145 else
146 -- It's dead. Hooray!
148 if monsterstate[destguid] and monstertimeout[destguid] > GetTime() and (monsterstate[destguid] == MS_TAPPED_US or monsterstate[destguid] == MS_TAPPED_US_TRIVIAL) and members_count > 0 then -- yaaay
149 local type = GetMonsterType(destguid)
150 if not QHC.monster[type] then QHC.monster[type] = {} end
151 if monsterstate[destguid] == MS_TAPPED_US then
152 QHC.monster[type].kills = (QHC.monster[type].kills or 0) + 1 / members_count -- Hopefully, most people loot their kills. Divide by members_count 'cause there's a 1/members chance that we get to loot. We'll flag the loot count some other way if it was a trivial monster.
155 monsterstate[destguid] = (monsterstate[destguid] == MS_TAPPED_US and MS_TAPPED_LOOTABLE or MS_TAPPED_LOOTABLE_TRIVIAL)
156 monsterrefresh[destguid] = GetTime() + 600
157 monstertimeout[destguid] = GetTime() + 600
158 --QuestHelper:TextOut(string.format("Tapped monster %s slain, set to lootable", destguid))
159 else
160 monsterstate[destguid] = nil
161 monsterrefresh[destguid] = nil
162 monstertimeout[destguid] = nil
163 --QuestHelper:TextOut(string.format("Untapped monster %s slain, cleared", destguid))
168 local skintypes = {}
170 skintypes[UNIT_SKINNABLE_ROCK] = "mine"
171 skintypes[UNIT_SKINNABLE_HERB] = "herb"
172 skintypes[UNIT_SKINNABLE_BOLTS] = "eng"
173 skintypes[UNIT_SKINNABLE_LEATHER] = "skin"
175 local function SkinnableflagsTooltipy(self, ...)
176 --QuestHelper:TextOut("tooltipy")
177 if UnitExists("mouseover") and UnitIsVisible("mouseover") and not UnitIsPlayer("mouseover") and not UnitPlayerControlled("mouseover") and UnitIsDead("mouseover") then
178 local guid = UnitGUID("mouseover")
179 --QuestHelper:TextOut("critar")
180 if not monsterstate[guid] or monsterstate[guid] ~= MS_TAPPED_LOOTED or monsterrefresh[guid] > GetTime() then return end
181 --QuestHelper:TextOut("runin")
183 local cid = GetMonsterType(guid)
185 local skintype = nil
187 local lines = GameTooltip:NumLines()
188 for i = 3, lines do
189 --QuestHelper:TextOut(_G["GameTooltipTextLeft" .. tostring(i)]:GetText())
190 local skeen = skintypes[_G["GameTooltipTextLeft" .. tostring(i)]:GetText()]
191 if skeen then QuestHelper: Assert(not skintype) skintype = skeen end
194 if not QHC.monster[cid] then QHC.monster[cid] = {} end
195 local qhci = QHC.monster[cid]
197 for _, v in pairs(skintypes) do
198 if v == skintype then
199 --QuestHelper:TextOut(v .. "_yes")
200 qhci[v .. "_yes"] = (qhci[v .. "_yes"] or 0) + 1
201 else
202 --QuestHelper:TextOut(v .. "_no")
203 qhci[v .. "_no"] = (qhci[v .. "_no"] or 0) + 1
209 -- Logic behind this module:
210 -- Watch for the spell to be sent
211 -- Watch for it to start
212 -- Check out the combat log and see what GUID we get
213 -- If the GUID is null, we're targeting an object, otherwise, we're targeting a critter
214 -- Wait for spell to succeed
215 -- If anything doesn't synch up, or the spell is interrupted, nil out all these items.
216 -- We've got a little special case for pickpocketing, because people often use macros, so we detect that case specifically.
218 local PP_PHASE_IDLE
219 local PP_PHASE_SENT
220 local PP_PHASE_COMPLETE
222 local pickpocket_phase = PP_PHASE_IDLE
223 local pickpocket_target
224 local pickpocket_otarget_guid
225 local pickpocket_timestamp
227 local pickpocket_name = GetSpellInfo(921) -- this is the pickpocket spell ID
229 local function pp_reset()
230 pickpocket_target, pickpocket_otarget_guid, pickpocket_timestamp, pickpocket_phase = nil, nil, nil, PP_PHASE_IDLE
232 pp_reset()
234 local function PPSent(player, spell, _, target)
235 if player ~= "player" then return end
236 if spell ~= pickpocket_name then return end
237 if UnitName("target") ~= target then return end -- DENY
239 pickpocket_timestamp, pickpocket_target, pickpocket_otarget_guid, pickpocket_phase = GetTime(), target, UnitGUID("target"), PP_PHASE_SENT
242 local function PPSucceed(player, spell, rank)
243 if player ~= "player" then return end
244 if spell ~= pickpocket_name then return end
246 if pickpocket_phase ~= PP_PHASE_SENT and (not pickpocket_otarget_guid or last_timestamp + 1 < GetTime()) then
247 pp_reset()
248 return
251 pickpocket_timestamp, pickpocket_phase = GetTime(), PP_PHASE_COMPLETE
255 -- This segment deals with openable containers
257 local touched_itemid
258 local touched_timestamp
260 local function ItemLock(bag, slot)
261 if not bag or not slot then return end -- probably changing equipment
263 local _, _, locked = GetContainerItemInfo(bag, slot)
264 --QuestHelper:TextOut(string.format("trying lock %s", tostring(locked)))
265 if locked then
266 touched_itemid = GetItemType(GetContainerItemLink(bag, slot))
267 --[[QuestHelper:TextOut(string.format("trying lock %s", tostring(touched_itemid)))
268 QuestHelper:TextOut(string.format("trying lock %s", tostring(type(touched_itemid))))
269 QuestHelper:TextOut(string.format("trying lock %s", tostring(QHC.item[touched_itemid].open_yes)))
270 QuestHelper:TextOut(string.format("trying lock %s", tostring(QHC.item[touched_itemid].open_no)))]]
271 touched_timestamp = GetTime()
272 if not QHC.item[touched_itemid] or (QHC.item[touched_itemid].open_yes or 0) <= (QHC.item[touched_itemid].open_no or 0) then
273 touched_itemid = nil
274 touched_timestamp = nil
276 else
277 touched_itemid = nil
278 touched_timestamp = nil
283 -- Here's the segment for longer spells. There aren't any instant spells we currently care about, besides pickpocketing. This will probably change eventually (arrows in the DK starting zone?)
285 local LAST_PHASE_IDLE = 0
286 local LAST_PHASE_SENT = 1
287 local LAST_PHASE_START = 2
288 local LAST_PHASE_COMBATLOG = 3
289 local LAST_PHASE_COMPLETE = 4
291 local last_phase = LAST_PHASE_IDLE
292 local last_spell
293 local last_rank
294 local last_target
295 local last_target_guid
296 local last_otarget
297 local last_otarget_guid
298 local last_timestamp
299 local last_succeed = false
300 local last_succeed_trade = GetTime()
302 local gathereffects = {}
304 gathereffects[GetSpellInfo(51306)] = {token = "eng", noclog = true}
305 gathereffects[GetSpellInfo(32606)] = {token = "mine"}
306 gathereffects[GetSpellInfo(2366)] = {token = "herb"}
307 gathereffects[GetSpellInfo(8613)] = {token = "skin"}
308 gathereffects[GetSpellInfo(21248)] = {token = "open", noclog = true}
309 gathereffects[GetSpellInfo(30427)] = {token = "extract", noclog = true} -- not a loot window, so it won't really work, but hey
310 gathereffects[GetSpellInfo(13262)] = {token = "de", noclog = true, ignore = true}
311 gathereffects[GetSpellInfo(31252)] = {token = "prospect", noclog = true, ignore = true}
312 gathereffects[GetSpellInfo(51005)] = {token = "mill", noclog = true, ignore = true}
315 local function normalize_spell(spell)
316 for k in pairs(gathereffects) do
317 if spell:find(k) then return k end
319 return spell
323 local function last_reset()
324 last_timestamp, last_spell, last_rank, last_target, last_target_guid, last_otarget, last_otarget_guid, last_succeed, last_phase = nil, nil, nil, nil, nil, nil, false, LAST_PHASE_IDLE
326 last_reset()
328 -- This all doesn't work with instant spells. Luckily, I don't care about instant spells (yet).
329 local function SpellSent(player, spell, rank, target)
330 if player ~= "player" then return end
331 spell = normalize_spell(spell)
333 last_timestamp, last_spell, last_rank, last_target, last_target_guid, last_otarget, last_otarget_guid, last_succeed, last_phase = GetTime(), spell, rank, target, nil, UnitName("target"), UnitGUID("target"), false, LAST_PHASE_SENT
335 if last_otarget and last_otarget ~= last_target then last_reset() return end
337 --QuestHelper:TextOut(string.format("ss %s", spell))
340 local function SpellStart(player, spell, rank)
341 if player ~= "player" then return end
342 spell = normalize_spell(spell)
344 if spell ~= last_spell or rank ~= last_rank or last_target_guid or last_phase ~= LAST_PHASE_SENT or last_timestamp + 1 < GetTime() then
345 last_reset()
346 else
347 --QuestHelper:TextOut(string.format("sst %s", spell))
348 last_timestamp, last_phase = GetTime(), LAST_PHASE_START
352 local function SpellCombatLog(_, event, sourceguid, _, _, destguid, _, _, _, spellname)
353 if event ~= "SPELL_CAST_START" then return end
355 if sourceguid ~= UnitGUID("player") then return end
356 spellname = normalize_spell(spellname)
358 --QuestHelper:TextOut(string.format("cle_ss enter %s %s %s %s", tostring(spellname ~= last_spell), tostring(not last_target), tostring(not not last_target_guid), tostring(last_timestamp + 1 < GetTime())))
360 if spellname ~= last_spell or not last_target or last_target_guid or last_timestamp + 1 < GetTime() then
361 last_reset()
362 return
365 --QuestHelper:TextOut("cle_ss enter")
367 if last_phase ~= LAST_PHASE_START then
368 last_reset()
369 return
372 --QuestHelper:TextOut(string.format("cesst %s", spellname))
373 last_timestamp, last_target_guid, last_phase = GetTime(), destguid, LAST_PHASE_COMBATLOG
375 if last_target_guid == "0x0000000000000000" then last_target_guid = nil end
376 if last_target_guid and last_target_guid ~= last_otarget_guid then last_reset() return end
379 local function SpellSucceed(player, spell, rank)
380 if player ~= "player" then return end
381 spell = normalize_spell(spell)
383 if gathereffects[spell] then last_succeed_trade = GetTime() end
385 --QuestHelper:TextOut(string.format("sscu enter %s %s %s %s %s", tostring(last_spell), tostring(last_target), tostring(last_rank), tostring(spell), tostring(rank)))
387 if not last_spell or not last_target or last_spell ~= spell or last_rank ~= rank then
388 last_reset()
389 return
392 --QuestHelper:TextOut("sscu enter")
394 if gathereffects[spell] and gathereffects[spell].noclog then
395 if last_phase ~= LAST_PHASE_START or last_timestamp + 10 < GetTime() then
396 last_reset()
397 return
399 else
400 if last_phase ~= LAST_PHASE_COMBATLOG or last_timestamp + 10 < GetTime() then
401 last_reset()
402 return
406 --QuestHelper:TextOut(string.format("sscu %s, %d, %s, %s", spell, last_phase, tostring(last_phase == LAST_PHASE_SENT), tostring((last_phase == LAST_PHASE_SENT) and LAST_PHASE_SHORT_SUCCEEDED)))
407 last_timestamp, last_succeed, last_phase = GetTime(), true, LAST_PHASE_COMPLETE
408 --QuestHelper:TextOut(string.format("last_phase %d", last_phase))
410 --[[if last_phase == LAST_PHASE_COMPLETE then
411 QuestHelper:TextOut(string.format("spell succeeded, casting %s %s on %s/%s", last_spell, last_rank, tostring(last_target), tostring(last_target_guid)))
412 end]]
415 local function SpellInterrupt(player, spell, rank)
416 if player ~= "player" then return end
418 -- I don't care what they were casting, they're certainly not doing it now
419 --QuestHelper:TextOut(string.format("si %s", spell))
420 last_reset()
423 local function LootOpened()
425 local targetguid = UnitGUID("target")
427 -- We're cleaning up the monster charts here, on the theory that if someone is looting, they're okay with a tiny lag spike.
428 if last_cleanup + 300 < GetTime() then
429 local cleanup = {}
430 for k, v in pairs(monstertimeout) do
431 if v < GetTime() then table.insert(cleanup, k) end
434 for _, v in pairs(cleanup) do
435 monsterstate[v] = nil
436 monsterrefresh[v] = nil
437 monstertimeout[v] = nil
441 -- First off, we try to figure out where the hell these items came from.
443 local spot = nil
444 local prefix = nil
446 if IsFishingLoot() then
447 -- yaaaaay
448 --if debug_output then QuestHelper:TextOut("Fishing loot") end
449 local loc = GetLoc()
450 if not QHC.fishing[loc] then QHC.fishing[loc] = {} end
451 spot = QHC.fishing[loc]
452 prefix = "fish"
454 elseif pickpocket_phase == PP_PHASE_COMPLETE and pickpocket_timestamp and pickpocket_timestamp + 1 > GetTime() and targetguid == pickpocket_otarget_guid then
455 --if debug_output then QuestHelper:TextOut(string.format("Pickpocketing from %s/%s", pickpocket_target, UnitName("target"), targetguid)) end
456 local mid = GetMonsterType(targetguid)
457 if not QHC.monster[mid] then QHC.monster[mid] = {} end
458 spot = QHC.monster[mid]
459 prefix = "rob"
461 elseif last_phase == LAST_PHASE_COMPLETE and gathereffects[last_spell] and last_timestamp + 1 > GetTime() then
462 local beef = string.format("%s/%s %s/%s", tostring(last_target), tostring(last_target_guid), tostring(last_otarget), tostring(last_otarget_guid))
464 if gathereffects[last_spell].ignore then return end
466 prefix = gathereffects[last_spell].token
468 -- this one is sort of grim actually
469 -- If we have an last_otarget_guid, it's the right one, and it's a monster
470 -- If we don't, use last_target, and it's an object
471 -- This is probably going to be buggy. Welp.
472 if last_otarget_guid then
473 if debug_output then QuestHelper:TextOut(string.format("%s from monster %s", gathereffects[last_spell].token, beef)) end
474 local mid = GetMonsterType(last_otarget_guid)
475 if not QHC.monster[mid] then QHC.monster[mid] = {} end
476 spot = QHC.monster[mid]
477 else
478 if debug_output then QuestHelper:TextOut(string.format("%s from object %s", gathereffects[last_spell].token, beef)) end
479 if not QHC.object[last_target] then QHC.object[last_target] = {} end
480 spot = QHC.object[last_target]
483 elseif touched_timestamp and touched_timestamp + 1 > GetTime() then
484 -- Opening a container, possibly
485 --if debug_output then QuestHelper:TextOut(string.format("Opening container %d", touched_itemid)) end
486 if not QHC.item[touched_itemid] then QHC.item[touched_itemid] = {} end
487 spot = QHC.item[touched_itemid]
488 prefix = "open"
490 elseif targetguid and (monsterstate[targetguid] == MS_TAPPED_LOOTABLE or monsterstate[targetguid] == MS_TAPPED_LOOTABLE_TRIVIAL) and monstertimeout[targetguid] > GetTime() and (not pickpocket_timestamp or pickpocket_timestamp + 5 < GetTime()) and (not last_timestamp or last_timestamp + 5 < GetTime()) and (last_succeed_trade + 5 < GetTime()) then -- haha holy shit
491 -- Monster is lootable, so we loot the monster. Should we check to see if it's dead first? Probably.
492 --if debug_output then QuestHelper:TextOut(string.format("%s from %s/%s", (monsterstate[targetguid] == MS_TAPPED_LOOTABLE and "Monsterloot" or "Trivial monsterloot"), UnitName("target"), targetguid)) end
494 local mid = GetMonsterType(targetguid)
495 if not QHC.monster[mid] then QHC.monster[mid] = {} end
496 spot = QHC.monster[mid]
497 if monsterstate[targetguid] == MS_TAPPED_LOOTABLE then
498 prefix = "loot"
499 elseif monsterstate[targetguid] == MS_TAPPED_LOOTABLE_TRIVIAL then
500 prefix = "loot_trivial" -- might be a better way to do this, but we'll see
503 monsterstate[targetguid] = MS_TAPPED_LOOTED
504 monstertimeout[targetguid] = GetTime() + 300
505 monsterrefresh[targetguid] = GetTime() + 2
506 else
507 --if debug_output then QuestHelper:TextOut("Who knows") end -- ugh
508 local loc = GetLoc()
509 if not QHC.worldloot[loc] then QHC.worldloot[loc] = {} end
510 spot = QHC.worldloot[loc]
511 prefix = "loot"
517 local items = {}
518 items.gold = 0
519 for i = 1, GetNumLootItems() do
520 _, name, quant, _ = GetLootSlotInfo(i)
521 link = GetLootSlotLink(i)
522 if quant == 0 then
523 -- moneys
524 local _, _, amount = string.find(name, Patterns.GOLD_AMOUNT)
525 if amount then items.gold = items.gold + tonumber(amount) * 10000 end
527 local _, _, amount = string.find(name, Patterns.SILVER_AMOUNT)
528 if amount then items.gold = items.gold + tonumber(amount) * 100 end
530 local _, _, amount = string.find(name, Patterns.COPPER_AMOUNT)
531 if amount then items.gold = items.gold + tonumber(amount) * 1 end
532 else
533 local itype = GetItemType(link)
534 items[itype] = (items[itype] or 0) + quant
538 spot[prefix .. "_count"] = (spot[prefix .. "_count"] or 0) + 1
539 if not spot[prefix .. "_loot"] then spot[prefix .. "_loot"] = {} end
540 local pt = spot[prefix .. "_loot"]
541 for k, v in pairs(items) do
542 if v > 0 then pt[k] = (pt[k] or 0) + v end
546 function QH_Collect_Loot_Init(QHCData, API)
547 QHC = QHCData
549 if not QHC.monster then QHC.monster = {} end
550 if not QHC.worldloot then QHC.worldloot = {} end
551 if not QHC.fishing then QHC.fishing = {} end
552 if not QHC.item then QHC.item = {} end
554 QH_Event("PLAYER_ENTERING_WORLD", MembersUpdate)
555 QH_Event("RAID_ROSTER_UPDATE", MembersUpdate)
556 QH_Event("PARTY_MEMBERS_CHANGED", MembersUpdate)
557 QH_Event("COMBAT_LOG_EVENT_UNFILTERED", CombatLogEvent)
559 QH_Event("UPDATE_MOUSEOVER_UNIT", SkinnableflagsTooltipy)
561 QH_Event("UNIT_SPELLCAST_SENT", PPSent)
562 QH_Event("UNIT_SPELLCAST_SUCCEEDED", PPSucceed)
564 QH_Event("ITEM_LOCK_CHANGED", ItemLock)
566 QH_Event("UNIT_SPELLCAST_SENT", SpellSent)
567 QH_Event("UNIT_SPELLCAST_START", SpellStart)
568 QH_Event("COMBAT_LOG_EVENT_UNFILTERED", SpellCombatLog)
569 QH_Event("UNIT_SPELLCAST_SUCCEEDED", SpellSucceed)
570 QH_Event("UNIT_SPELLCAST_INTERRUPTED", SpellInterrupt)
572 QH_Event("LOOT_OPENED", LootOpened)
574 MembersUpdate() -- to get self, probably won't work but hey
576 GetMonsterUID = API.Utility_GetMonsterUID
577 GetMonsterType = API.Utility_GetMonsterType
578 GetItemType = API.Utility_GetItemType
579 QuestHelper: Assert(GetMonsterUID)
580 QuestHelper: Assert(GetMonsterType)
581 QuestHelper: Assert(GetItemType)
583 Patterns = API.Patterns
584 API.Patterns_RegisterNumber("GOLD_AMOUNT")
585 API.Patterns_RegisterNumber("SILVER_AMOUNT")
586 API.Patterns_RegisterNumber("COPPER_AMOUNT")
588 GetLoc = API.Callback_LocationBolusCurrent
589 QuestHelper: Assert(GetLoc)
591 -- What I want to know is whether it was tagged by me or my group when dead
592 -- Check target-of-each-groupmember? Once we see him tapped once, and by us, it's probably sufficient.
593 -- Notes:
594 --[[
596 COMBAT_LOG_EVENT_UNFILTERED arg2 UNIT_DIED, PLAYER_TARGET_CHANGED, LOOT_OPENED, (LOOT_CLOSED, [LOOT_SLOT_CLEARED, ITEM_PUSH, CHAT_MSG_LOOT]), PLAYER_TARGET_CHANGED, SPELLCAST_SENT, SPELLCAST_START, SUCCEEDED/INTERRUPTED, STOP, LOOT_OPENED (etc)
598 ITEM_PUSH can happen after LOOT_CLOSED, but it still happens.
599 Between LOOT_OPENED and LOOT_CLOSED, the lootable target is still targeted. Unsure what happens when looting items. LOOT_CLOSED triggers first if we target someone else.
600 ITEM_PUSH happens, then CHAT_MSG_LOOT. CHAT_MSG_LOOT includes quite a lot of potentially useful arguments.
601 PLAYER_TARGET_CHANGED before either looting or skinning.
602 SPELLCAST_SENT, SPELLCAST_START, SUCCEEDED/INTERRUPTED, STOP in that order. Arg4 on SENT seems to be the target's name. Arg4 on the others appears to be a unique identifier.
603 When started, we target the right thing. After that, we don't seem to. Check the combat log.