comparison Main.lua @ 358:703714202ffe 6.0.2-1

Merge with WoD
author MMOSimca <MMOSimca@gmail.com>
date Tue, 14 Oct 2014 00:36:05 -0400
parents f8190e012c92
children 7e7d57948295
comparison
equal deleted inserted replaced
328:3487529df8e5 358:703714202ffe
8 8
9 local bit = _G.bit 9 local bit = _G.bit
10 local math = _G.math 10 local math = _G.math
11 local table = _G.table 11 local table = _G.table
12 12
13 local next = _G.next
13 local select = _G.select 14 local select = _G.select
14 local unpack = _G.unpack 15 local unpack = _G.unpack
15 16
16 17
17 -- ADDON NAMESPACE ---------------------------------------------------- 18 -- ADDON NAMESPACE ----------------------------------------------------
45 local PLAYER_FACTION = _G.UnitFactionGroup("player") 46 local PLAYER_FACTION = _G.UnitFactionGroup("player")
46 local PLAYER_GUID 47 local PLAYER_GUID
47 local PLAYER_NAME = _G.UnitName("player") 48 local PLAYER_NAME = _G.UnitName("player")
48 local PLAYER_RACE = _G.select(2, _G.UnitRace("player")) 49 local PLAYER_RACE = _G.select(2, _G.UnitRace("player"))
49 50
50 local SPELL_ID_CHI_WAVE = 132464 51 local TIMBER_ITEM_ID = 114781
51 local SPELL_ID_DISGUISE = 121308 52
53 -- Ignoring NPC casts of the following spells
54 local CHI_WAVE_SPELL_ID = 132464
55 local DISGUISE_SPELL_ID = 121308
56
57 -- For timer-based loot gathering of abnormal containers (that don't use SHOW_LOOT_TOAST, sadly)
58 local BAG_OF_SALVAGE_ITEM_ID = private.SALVAGE_SPELL_ID_TO_ITEM_ID_MAP[168178]
59 local CRATE_OF_SALVAGE_ITEM_ID = private.SALVAGE_SPELL_ID_TO_ITEM_ID_MAP[168179]
60 local BIG_CRATE_OF_SALVAGE_ITEM_ID = private.SALVAGE_SPELL_ID_TO_ITEM_ID_MAP[168180]
61
62 -- Constant for duplicate boss data; a dirty hack to get around world bosses that cannot be identified individually and cannot be linked on wowdb because they are not in a raid
63 local DUPLICATE_WORLD_BOSS_IDS = {
64 [71952] = { 71953, 71954, 71955, },
65 }
52 66
53 local ALLOWED_LOCALES = { 67 local ALLOWED_LOCALES = {
54 enUS = true, 68 enUS = true,
55 enGB = true, 69 enGB = true,
70 enTW = true,
71 enCN = true,
56 } 72 }
57 73
58 local DATABASE_DEFAULTS = { 74 local DATABASE_DEFAULTS = {
59 char = {}, 75 char = {},
60 global = { 76 global = {
147 local loot_toast_data_timer_handle 163 local loot_toast_data_timer_handle
148 local name_to_id_map = {} 164 local name_to_id_map = {}
149 local killed_boss_id_timer_handle 165 local killed_boss_id_timer_handle
150 local killed_npc_id 166 local killed_npc_id
151 local target_location_timer_handle 167 local target_location_timer_handle
168 local last_timber_spell_id
169 local last_garrison_cache_object_id
170 local chat_loot_timer_handle
152 local current_target_id 171 local current_target_id
153 local current_area_id 172 local current_area_id
154 local current_loot 173 local current_loot
155 174
156 175
170 189
171 190
172 -- HELPERS ------------------------------------------------------------ 191 -- HELPERS ------------------------------------------------------------
173 192
174 local function Debug(message, ...) 193 local function Debug(message, ...)
175 if not DEBUGGING or not message or not ... then 194 if not DEBUGGING or not message then
176 return 195 return
177 end 196 end
178 local args = { ... } 197
179 198 if ... then
180 for index = 1, #args do 199 local args = { ... }
181 if args[index] == nil then 200
182 args[index] = "nil" 201 for index = 1, #args do
183 end 202 if args[index] == nil then
184 end 203 args[index] = "nil"
185 _G.print(message:format(unpack(args))) 204 end
205 end
206 _G.print(message:format(unpack(args)))
207 else
208 _G.print(message)
209 end
186 end 210 end
187 211
188 212
189 local TradeSkillExecutePer 213 local TradeSkillExecutePer
190 do 214 do
215 239
216 if have_skillup then 240 if have_skillup then
217 _G.TradeSkillFrame.filterTbl.hasSkillUp = false 241 _G.TradeSkillFrame.filterTbl.hasSkillUp = false
218 _G.TradeSkillOnlyShowSkillUps(false) 242 _G.TradeSkillOnlyShowSkillUps(false)
219 end 243 end
220 _G.SetTradeSkillInvSlotFilter(0, 1, 1) 244 _G.SetTradeSkillInvSlotFilter(0, true, true)
221 _G.TradeSkillUpdateFilterBar() 245 _G.TradeSkillUpdateFilterBar()
222 _G.TradeSkillFrame_Update() 246 _G.TradeSkillFrame_Update()
223 247
224 -- Expand all headers so we can see all the recipes there are 248 -- Expand all headers so we can see all the recipes there are
225 for tradeskill_index = 1, _G.GetNumTradeSkills() do 249 for tradeskill_index = 1, _G.GetNumTradeSkills() do
290 return math.floor(copper_cost / modifier) 314 return math.floor(copper_cost / modifier)
291 end 315 end
292 end -- do-block 316 end -- do-block
293 317
294 318
295 -- constant for duplicate boss data; a dirty hack to get around world bosses that cannot be identified individually and cannot be linked on wowdb because they are not in a raid
296 local DUPLICATE_WORLD_BOSS_IDS = {
297 [71952] = { 71953, 71954, 71955, },
298 }
299
300
301 -- Called on a timer
302 local function ClearKilledNPC()
303 killed_npc_id = nil
304 end
305
306
307 local function ClearKilledBossID()
308 if killed_boss_id_timer_handle then
309 WDP:CancelTimer(killed_boss_id_timer_handle)
310 killed_boss_id_timer_handle = nil
311 end
312
313 table.wipe(boss_loot_toasting)
314 private.raid_finder_boss_id = nil
315 private.world_boss_id = nil
316 end
317
318
319 local function ClearLootToastContainerID()
320 if loot_toast_container_timer_handle then
321 WDP:CancelTimer(loot_toast_container_timer_handle)
322 loot_toast_container_timer_handle = nil
323 end
324
325 private.container_loot_toasting = false
326 private.loot_toast_container_id = nil
327 end
328
329
330 local function ClearLootToastData()
331 -- cancel existing timer if found
332 if loot_toast_data_timer_handle then
333 WDP:CancelTimer(loot_toast_data_timer_handle)
334 loot_toast_data_timer_handle = nil
335 end
336
337 if loot_toast_data then
338 table.wipe(loot_toast_data)
339 end
340 end
341
342
343 local function InstanceDifficultyToken() 319 local function InstanceDifficultyToken()
344 local _, instance_type, instance_difficulty, _, _, _, is_dynamic = _G.GetInstanceInfo() 320 local _, instance_type, instance_difficulty, _, _, _, is_dynamic = _G.GetInstanceInfo()
345 321
346 if not instance_type or instance_type == "" then 322 if not instance_type or instance_type == "" then
347 instance_type = "NONE" 323 instance_type = "NONE"
378 __index = npc_prototype 354 __index = npc_prototype
379 } 355 }
380 356
381 function NPCEntry(identifier) 357 function NPCEntry(identifier)
382 local npc = DBEntry("npcs", identifier) 358 local npc = DBEntry("npcs", identifier)
383 359 return npc and _G.setmetatable(npc, npc_meta) or nil
384 if not npc then
385 return
386 end
387 return _G.setmetatable(npc, npc_meta)
388 end 360 end
389 361
390 function npc_prototype:EncounterData(difficulty_token) 362 function npc_prototype:EncounterData(difficulty_token)
391 self.encounter_data = self.encounter_data or {} 363 self.encounter_data = self.encounter_data or {}
392 self.encounter_data[difficulty_token] = self.encounter_data[difficulty_token] or {} 364 self.encounter_data[difficulty_token] = self.encounter_data[difficulty_token] or {}
461 433
462 434
463 local ParseGUID 435 local ParseGUID
464 do 436 do
465 local UNIT_TYPES = private.UNIT_TYPES 437 local UNIT_TYPES = private.UNIT_TYPES
466 local UNIT_TYPE_BITMASK = 0x007
467 438
468 local NPC_ID_MAPPING = { 439 local NPC_ID_MAPPING = {
469 [62164] = 63191, -- Garalon 440 [62164] = 63191, -- Garalon
470 } 441 }
471 442
472 443
444 local function MatchUnitTypes(unit_type_name)
445 if not unit_type_name then
446 return UNIT_TYPES.UNKNOWN
447 end
448
449 for def, text in next, UNIT_TYPES do
450 if unit_type_name == text then
451 return UNIT_TYPES[def]
452 end
453 end
454 return UNIT_TYPES.UNKNOWN
455 end
456
457
473 function ParseGUID(guid) 458 function ParseGUID(guid)
474 if not guid then 459 if not guid then
475 return 460 return
476 end 461 end
477 local bitfield = tonumber(guid:sub(1, 5)) 462
478 463 -- We might want to use some of this new information later, but leaving the returns alone for now
479 if not bitfield then 464 local unit_type_name, unk_id1, server_id, instance_id, unk_id2, unit_idnum, spawn_id = ("-"):split(guid)
480 return UNIT_TYPES.UNKNOWN 465
481 end 466 local unit_type = MatchUnitTypes(unit_type_name)
482 local unit_type = _G.bit.band(bitfield, UNIT_TYPE_BITMASK) 467 if unit_type ~= UNIT_TYPES.PLAYER and unit_type ~= UNIT_TYPES.PET and unit_type ~= UNIT_TYPES.ITEM then
483 468
484 if unit_type ~= UNIT_TYPES.PLAYER and unit_type ~= UNIT_TYPES.PET then
485 local unit_idnum = tonumber(guid:sub(6, 10), 16)
486 local id_mapping = NPC_ID_MAPPING[unit_idnum] 469 local id_mapping = NPC_ID_MAPPING[unit_idnum]
487 470
488 if id_mapping and UnitTypeIsNPC(unit_type) then 471 if id_mapping and UnitTypeIsNPC(unit_type) then
489 unit_idnum = id_mapping 472 unit_idnum = id_mapping
490 end 473 end
581 564
582 for line_index = 1, DatamineTT:NumLines() do 565 for line_index = 1, DatamineTT:NumLines() do
583 local current_line = _G["WDPDatamineTTTextLeft" .. line_index] 566 local current_line = _G["WDPDatamineTTTextLeft" .. line_index]
584 567
585 if not current_line then 568 if not current_line then
586 Debug("HandleItemUse: Item with ID %d and link %s had an invalid tooltip.", item_id, item_link, _G.ITEM_OPENABLE) 569 Debug("HandleItemUse: Item with ID %d and link %s had an invalid tooltip.", item_id, item_link)
587 return 570 return
588 end 571 end
589 572
590 if current_line:GetText() == _G.ITEM_OPENABLE then 573 if current_line:GetText() == _G.ITEM_OPENABLE then
591 table.wipe(current_action) 574 table.wipe(current_action)
595 current_action.identifier = item_id 578 current_action.identifier = item_id
596 current_action.loot_label = "contains" 579 current_action.loot_label = "contains"
597 return 580 return
598 end 581 end
599 end 582 end
600
601 Debug("HandleItemUse: Item with ID %d and link %s did not have a tooltip that contained the string %s.", item_id, item_link, _G.ITEM_OPENABLE) 583 Debug("HandleItemUse: Item with ID %d and link %s did not have a tooltip that contained the string %s.", item_id, item_link, _G.ITEM_OPENABLE)
602 end 584 end
603 585
604 586
605 local UnitFactionStanding 587 local UnitFactionStanding
672 654
673 if current_loot.target_type == AF.ITEM then 655 if current_loot.target_type == AF.ITEM then
674 -- Items return the player as the source, so we need to use the item's ID (disenchant, milling, etc) 656 -- Items return the player as the source, so we need to use the item's ID (disenchant, milling, etc)
675 source_id = current_loot.identifier 657 source_id = current_loot.identifier
676 else 658 else
677 local unit_ID = select(2, ParseGUID(source_guid)) 659 local _, unit_ID = ParseGUID(source_guid)
678 if unit_ID then 660 if unit_ID then
679 if current_loot.target_type == AF.OBJECT then 661 if current_loot.target_type == AF.OBJECT then
680 source_id = ("%s:%s"):format(current_loot.spell_label, unit_ID) 662 source_id = ("%s:%s"):format(current_loot.spell_label, unit_ID)
681 else 663 else
682 source_id = unit_ID 664 source_id = unit_ID
803 785
804 if not map_visible then 786 if not map_visible then
805 _G.SetCVar("Sound_EnableSFX", 0) 787 _G.SetCVar("Sound_EnableSFX", 0)
806 world_map:Show() 788 world_map:Show()
807 end 789 end
808 local micro_dungeon_id = MICRO_DUNGEON_IDS[select(5, _G.GetMapInfo())] 790 local _, _, _, _, micro_dungeon_map_name = _G.GetMapInfo()
791 local micro_dungeon_id = MICRO_DUNGEON_IDS[micro_dungeon_map_name]
809 792
810 _G.SetMapToCurrentZone() 793 _G.SetMapToCurrentZone()
811 794
812 if micro_dungeon_id then 795 if micro_dungeon_id then
813 current_area_id = micro_dungeon_id 796 current_area_id = micro_dungeon_id
845 } 828 }
846 829
847 table.wipe(current_action) 830 table.wipe(current_action)
848 end 831 end
849 832
833
834 -- TIMERS -------------------------------------------------------------
835
836 local function ClearKilledNPC()
837 killed_npc_id = nil
838 end
839
840
841 local function ClearKilledBossID()
842 if killed_boss_id_timer_handle then
843 WDP:CancelTimer(killed_boss_id_timer_handle)
844 killed_boss_id_timer_handle = nil
845 end
846
847 table.wipe(boss_loot_toasting)
848 private.raid_boss_id = nil
849 end
850
851
852 local function ClearLootToastContainerID()
853 if loot_toast_container_timer_handle then
854 WDP:CancelTimer(loot_toast_container_timer_handle)
855 loot_toast_container_timer_handle = nil
856 end
857
858 private.container_loot_toasting = false
859 private.loot_toast_container_id = nil
860 end
861
862
863 local function ClearLootToastData()
864 -- cancel existing timer if found
865 if loot_toast_data_timer_handle then
866 WDP:CancelTimer(loot_toast_data_timer_handle)
867 loot_toast_data_timer_handle = nil
868 end
869
870 if loot_toast_data then
871 table.wipe(loot_toast_data)
872 end
873 end
874
875
876 local function ClearTimeBasedLootData()
877 Debug("ClearTimeBasedLootData: Ending salvage loot timer.")
878 if chat_loot_timer_handle then
879 WDP:CancelTimer(chat_loot_timer_handle)
880 chat_loot_timer_handle = nil
881 end
882
883 if current_loot and current_loot.identifier and (current_loot.identifier == BAG_OF_SALVAGE_ITEM_ID or current_loot.identifier == CRATE_OF_SALVAGE_ITEM_ID or current_loot.identifier == BIG_CRATE_OF_SALVAGE_ITEM_ID) then
884 GenericLootUpdate("items")
885 end
886 current_loot = nil
887 end
888
889
850 -- METHODS ------------------------------------------------------------ 890 -- METHODS ------------------------------------------------------------
851 891
852 function WDP:OnInitialize() 892 function WDP:OnInitialize()
853 local db = LibStub("AceDB-3.0"):New("WoWDBProfilerData", DATABASE_DEFAULTS, "Default") 893 local db = LibStub("AceDB-3.0"):New("WoWDBProfilerData", DATABASE_DEFAULTS, "Default")
854 private.db = db 894 private.db = db
856 char_db = db.char 896 char_db = db.char
857 897
858 local raw_db = _G.WoWDBProfilerData 898 local raw_db = _G.WoWDBProfilerData
859 local build_num = tonumber(private.build_num) 899 local build_num = tonumber(private.build_num)
860 900
901 -- Disable if using a MoP build
902 if build_num < 19000 then
903 WDP:Disable()
904 return
905 end
906
861 if (raw_db.version and raw_db.version < DB_VERSION) or (raw_db.build_num and raw_db.build_num < build_num) then 907 if (raw_db.version and raw_db.version < DB_VERSION) or (raw_db.build_num and raw_db.build_num < build_num) then
862 for entry in pairs(DATABASE_DEFAULTS.global) do 908 for entry in pairs(DATABASE_DEFAULTS.global) do
863 global_db[entry] = {} 909 global_db[entry] = {}
864 end 910 end
865 end 911 end
866 raw_db.build_num = build_num 912 raw_db.build_num = build_num
913 raw_db.region = private.region
867 raw_db.version = DB_VERSION 914 raw_db.version = DB_VERSION
868 915
869 private.InitializeCommentSystem() 916 private.InitializeCommentSystem()
870 self:RegisterChatCommand("comment", private.ProcessCommentCommand) 917 self:RegisterChatCommand("comment", private.ProcessCommentCommand)
871 end 918 end
947 break 994 break
948 end 995 end
949 local amount, stat = left_text:match("+(.-) (.*)") 996 local amount, stat = left_text:match("+(.-) (.*)")
950 997
951 if amount and stat then 998 if amount and stat then
952 if reforge_id and reforge_id ~= 0 then
953 local reforge_string = stat:find("Reforged")
954
955 if reforge_string then
956 stat = stat:sub(0, reforge_string - 3)
957 intermediary.reforge_id = reforge_id
958 end
959 end
960 create_entry = true 999 create_entry = true
961 intermediary[stat:lower():gsub(" ", "_"):gsub("|r", "")] = tonumber((amount:gsub(",", ""))) 1000 intermediary[stat:lower():gsub(" ", "_"):gsub("|r", "")] = tonumber((amount:gsub(",", "")))
962 end 1001 end
963 end 1002 end
964 1003
972 1011
973 for stat, amount in pairs(intermediary) do 1012 for stat, amount in pairs(intermediary) do
974 item.upgrades[upgrade_id][stat] = amount 1013 item.upgrades[upgrade_id][stat] = amount
975 end 1014 end
976 end 1015 end
977 end 1016 end -- do-block
978 1017
979 -- do-block 1018
980 1019 local function RecordItemData(item_id, item_link, process_bonus_ids, durability)
981 1020 local _, _, item_string = item_link:find("^|%x+|H(.+)|h%[.+%]")
982 local function RecordItemData(item_id, item_link, durability)
983 local item_string = select(3, item_link:find("^|%x+|H(.+)|h%[.+%]"))
984 local item 1021 local item
985 1022
986 if item_string then 1023 if item_string then
987 local _, _, _, _, _, _, _, suffix_id, unique_id, _, reforge_id, upgrade_id = (":"):split(item_string) 1024 local item_results = { (":"):split(item_string) }
988 suffix_id = tonumber(suffix_id) 1025
989 upgrade_id = tonumber(upgrade_id) 1026 local suffix_id = tonumber(item_results[8])
990 1027 local unique_id = item_results[9]
991 if suffix_id and suffix_id ~= 0 then 1028 local upgrade_id = tonumber(item_results[11])
1029 local instance_difficulty_id = tonumber(item_results[12])
1030 local num_bonus_ids = tonumber(item_results[13])
1031
1032 if not num_bonus_ids or num_bonus_ids == 0 or not process_bonus_ids then
1033 if (suffix_id and suffix_id ~= 0) or (instance_difficulty_id and instance_difficulty_id ~= 0) then
1034 item = DBEntry("items", item_id)
1035 item.unique_id = bit.band(unique_id, 0xFFFF)
1036
1037 if suffix_id and suffix_id ~= 0 then
1038 item.suffix_id = suffix_id
1039 end
1040
1041 if instance_difficulty_id and instance_difficulty_id ~= 0 then
1042 item.instance_difficulty_id = instance_difficulty_id
1043 end
1044 end
1045 elseif num_bonus_ids > 0 then
992 item = DBEntry("items", item_id) 1046 item = DBEntry("items", item_id)
993 item.suffix_id = suffix_id 1047
994 item.unique_id = bit.band(unique_id, 0xFFFF) 1048 item.unique_id = bit.band(unique_id, 0xFFFF)
995 end 1049 item.instance_difficulty_id = instance_difficulty_id
996 1050
1051 if not item.bonus_ids then
1052 item.bonus_ids = {}
1053 end
1054
1055 for bonus_index = 1, num_bonus_ids do
1056 item.bonus_ids[tonumber(item_results[13 + bonus_index])] = true
1057 end
1058 else
1059 Debug("RecordItemData: Item_system is supposed to be 0 or positive, instead it was %s.", item_system)
1060 end
997 if upgrade_id and upgrade_id ~= 0 then 1061 if upgrade_id and upgrade_id ~= 0 then
998 DatamineTT:SetHyperlink(item_link) 1062 DatamineTT:SetHyperlink(item_link)
999 ScrapeItemUpgradeStats(item_id, upgrade_id, reforge_id) 1063 ScrapeItemUpgradeStats(item_id, upgrade_id)
1000 end 1064 end
1001 end 1065 end
1002 1066
1003 if durability and durability > 0 then 1067 if durability and durability > 0 then
1004 item = item or DBEntry("items", item_id) 1068 item = item or DBEntry("items", item_id)
1011 for slot_index = _G.INVSLOT_FIRST_EQUIPPED, _G.INVSLOT_LAST_EQUIPPED do 1075 for slot_index = _G.INVSLOT_FIRST_EQUIPPED, _G.INVSLOT_LAST_EQUIPPED do
1012 local item_id = _G.GetInventoryItemID("player", slot_index) 1076 local item_id = _G.GetInventoryItemID("player", slot_index)
1013 1077
1014 if item_id and item_id > 0 then 1078 if item_id and item_id > 0 then
1015 local _, max_durability = _G.GetInventoryItemDurability(slot_index) 1079 local _, max_durability = _G.GetInventoryItemDurability(slot_index)
1016 RecordItemData(item_id, _G.GetInventoryItemLink("player", slot_index), max_durability) 1080 RecordItemData(item_id, _G.GetInventoryItemLink("player", slot_index), false, max_durability)
1017 end 1081 end
1018 end 1082 end
1019 1083
1020 for bag_index = 0, _G.NUM_BAG_SLOTS do 1084 for bag_index = 0, _G.NUM_BAG_SLOTS do
1021 for slot_index = 1, _G.GetContainerNumSlots(bag_index) do 1085 for slot_index = 1, _G.GetContainerNumSlots(bag_index) do
1022 local item_id = _G.GetContainerItemID(bag_index, slot_index) 1086 local item_id = _G.GetContainerItemID(bag_index, slot_index)
1023 1087
1024 if item_id and item_id > 0 then 1088 if item_id and item_id > 0 then
1025 local _, max_durability = _G.GetContainerItemDurability(bag_index, slot_index) 1089 local _, max_durability = _G.GetContainerItemDurability(bag_index, slot_index)
1026 RecordItemData(item_id, _G.GetContainerItemLink(bag_index, slot_index), max_durability) 1090 RecordItemData(item_id, _G.GetContainerItemLink(bag_index, slot_index), false, max_durability)
1027 end 1091 end
1028 end 1092 end
1029 end 1093 end
1030 end 1094 end
1031 1095
1090 encounter_data[npc_level] = level_data 1154 encounter_data[npc_level] = level_data
1091 end 1155 end
1092 level_data.max_health = level_data.max_health or _G.UnitHealthMax("target") 1156 level_data.max_health = level_data.max_health or _G.UnitHealthMax("target")
1093 1157
1094 if not level_data.power then 1158 if not level_data.power then
1095 local max_power = _G.UnitManaMax("target") 1159 local max_power = _G.UnitPowerMax("target")
1096 1160
1097 if max_power > 0 then 1161 if max_power > 0 then
1098 local power_type = _G.UnitPowerType("target") 1162 local power_type = _G.UnitPowerType("target")
1099 level_data.power = ("%s:%d"):format(POWER_TYPE_NAMES[tostring(power_type)] or power_type, max_power) 1163 level_data.power = ("%s:%d"):format(POWER_TYPE_NAMES[tostring(power_type)] or power_type, max_power)
1100 end 1164 end
1207 function WDP:UNIT_PET(event_name, unit_id) 1271 function WDP:UNIT_PET(event_name, unit_id)
1208 UpdateUnitPet(_G.UnitGUID(unit_id), unit_id) 1272 UpdateUnitPet(_G.UnitGUID(unit_id), unit_id)
1209 end 1273 end
1210 1274
1211 1275
1212 function WDP:SHOW_LOOT_TOAST(event_name, loot_type, item_link, quantity) 1276 function WDP:SHOW_LOOT_TOAST(event_name, loot_type, item_link, quantity, specID, sex, isPersonal, lootSource)
1213 if not loot_type or (loot_type ~= "item" and loot_type ~= "money" and loot_type ~= "currency") then 1277 if not loot_type or (loot_type ~= "item" and loot_type ~= "money" and loot_type ~= "currency") then
1214 Debug("%s: loot_type is %s. Item link is %s, and quantity is %d.", event_name, loot_type, item_link, quantity) 1278 Debug("%s: loot_type is %s. Item link is %s, and quantity is %d.", event_name, loot_type, item_link, quantity)
1215 return 1279 return
1216 end 1280 end
1217 local container_id = private.loot_toast_container_id 1281 local container_id = private.loot_toast_container_id
1218 local npc_id = private.raid_finder_boss_id or private.world_boss_id 1282 local npc_id = private.raid_boss_id
1219 1283
1220 if npc_id then 1284 -- Handle Garrison cache specially
1221 -- slightly messy hack to workaround duplicate world bosses 1285 if lootSource and last_garrison_cache_object_id and (lootSource == private.GARRISON_CACHE_LOOT_SOURCE_ID) then
1286 -- Record location data for cache
1287 UpdateDBEntryLocation("objects", ("OPENING:%d"):format(last_garrison_cache_object_id))
1288
1289 -- Add drop data
1290 local currency_texture = CurrencyLinkToTexture(item_link)
1291 if currency_texture and currency_texture ~= "" then
1292 -- Check for top level object data
1293 local object_entry = DBEntry("objects", ("OPENING:%d"):format(last_garrison_cache_object_id))
1294 local difficulty_token = InstanceDifficultyToken()
1295 if object_entry[difficulty_token] then
1296 -- Increment loot count
1297 object_entry[difficulty_token]["opening_count"] = (object_entry[difficulty_token]["opening_count"] or 0) + 1
1298
1299 Debug("%s: %s X %d", event_name, currency_texture, quantity)
1300 object_entry[difficulty_token]["opening"] = object_entry[difficulty_token]["opening"] or {}
1301 table.insert(object_entry[difficulty_token]["opening"], ("currency:%d:%s"):format(quantity, currency_texture))
1302 else
1303 Debug("%s: When handling the Garrison cache, the top level loot data was missing for objectID %d.", event_name, last_garrison_cache_object_id)
1304 end
1305 else
1306 Debug("%s: Currency texture is nil, from currency link %s", event_name, item_link)
1307 end
1308 elseif npc_id then
1309 -- Slightly messy hack to workaround duplicate world bosses
1222 local upper_limit = 0 1310 local upper_limit = 0
1223 if DUPLICATE_WORLD_BOSS_IDS[npc_id] then 1311 if DUPLICATE_WORLD_BOSS_IDS[npc_id] then
1224 upper_limit = #DUPLICATE_WORLD_BOSS_IDS[npc_id] 1312 upper_limit = #DUPLICATE_WORLD_BOSS_IDS[npc_id]
1225 end 1313 end
1226 for i = 0, upper_limit do 1314 for i = 0, upper_limit do
1239 1327
1240 if loot_type == "item" then 1328 if loot_type == "item" then
1241 local item_id = ItemLinkToID(item_link) 1329 local item_id = ItemLinkToID(item_link)
1242 if item_id then 1330 if item_id then
1243 Debug("%s: %s X %d (%d)", event_name, item_link, quantity, item_id) 1331 Debug("%s: %s X %d (%d)", event_name, item_link, quantity, item_id)
1332 RecordItemData(item_id, item_link, true)
1244 table.insert(encounter_data[loot_label], ("%d:%d"):format(item_id, quantity)) 1333 table.insert(encounter_data[loot_label], ("%d:%d"):format(item_id, quantity))
1245 else 1334 else
1246 Debug("%s: ItemID is nil, from item link %s", event_name, item_link) 1335 Debug("%s: ItemID is nil, from item link %s", event_name, item_link)
1247 return 1336 return
1248 end 1337 end
1251 table.insert(encounter_data[loot_label], ("money:%d"):format(quantity)) 1340 table.insert(encounter_data[loot_label], ("money:%d"):format(quantity))
1252 elseif loot_type == "currency" then 1341 elseif loot_type == "currency" then
1253 local currency_texture = CurrencyLinkToTexture(item_link) 1342 local currency_texture = CurrencyLinkToTexture(item_link)
1254 if currency_texture and currency_texture ~= "" then 1343 if currency_texture and currency_texture ~= "" then
1255 Debug("%s: %s X %d", event_name, currency_texture, quantity) 1344 Debug("%s: %s X %d", event_name, currency_texture, quantity)
1256 -- workaround for Patch 5.4.0 bug with Flexible raid Siege of Orgrimmar bosses and Valor Points
1257 if quantity > 1000 and currency_texture == "pvecurrency-valor" then
1258 quantity = math.floor(quantity / 100)
1259 end
1260 table.insert(encounter_data[loot_label], ("currency:%d:%s"):format(quantity, currency_texture)) 1345 table.insert(encounter_data[loot_label], ("currency:%d:%s"):format(quantity, currency_texture))
1261 else 1346 else
1262 Debug("%s: Currency texture is nil, from currency link %s", event_name, item_link) 1347 Debug("%s: Currency texture is nil, from currency link %s", event_name, item_link)
1263 return 1348 return
1264 end 1349 end
1282 1367
1283 if loot_type == "item" then 1368 if loot_type == "item" then
1284 local item_id = ItemLinkToID(item_link) 1369 local item_id = ItemLinkToID(item_link)
1285 if item_id then 1370 if item_id then
1286 Debug("%s: %s X %d (%d)", event_name, item_link, quantity, item_id) 1371 Debug("%s: %s X %d (%d)", event_name, item_link, quantity, item_id)
1372 RecordItemData(item_id, item_link, true)
1287 current_loot.sources[container_id][item_id] = current_loot.sources[container_id][item_id] or 0 + quantity 1373 current_loot.sources[container_id][item_id] = current_loot.sources[container_id][item_id] or 0 + quantity
1288 else 1374 else
1289 Debug("%s: ItemID is nil, from item link %s", event_name, item_link) 1375 Debug("%s: ItemID is nil, from item link %s", event_name, item_link)
1290 current_loot = nil 1376 current_loot = nil
1291 return 1377 return
1313 Debug("%s: NPC and Container are nil, storing loot toast data for 5 seconds.", event_name) 1399 Debug("%s: NPC and Container are nil, storing loot toast data for 5 seconds.", event_name)
1314 1400
1315 loot_toast_data = loot_toast_data or {} 1401 loot_toast_data = loot_toast_data or {}
1316 loot_toast_data[#loot_toast_data + 1] = { loot_type, item_link, quantity } 1402 loot_toast_data[#loot_toast_data + 1] = { loot_type, item_link, quantity }
1317 1403
1404 local item_id = ItemLinkToID(item_link)
1405 if item_id then
1406 RecordItemData(item_id, item_link, true)
1407 end
1408
1318 loot_toast_data_timer_handle = WDP:ScheduleTimer(ClearLootToastData, 5) 1409 loot_toast_data_timer_handle = WDP:ScheduleTimer(ClearLootToastData, 5)
1319 end 1410 end
1320 end 1411 end
1321 1412
1322 1413
1323 do 1414 do
1324 local CHAT_MSG_LOOT_UPDATE_FUNCS = { 1415 local CHAT_MSG_LOOT_UPDATE_FUNCS = {
1416 [AF.ITEM] = function(item_id, quantity)
1417 local container_id = current_loot.identifier -- For faster access, since this is going to be called 9 times in the next 3 lines
1418 -- Verify that we're still assigning data to the right items
1419 if container_id and (container_id == BAG_OF_SALVAGE_ITEM_ID or container_id == CRATE_OF_SALVAGE_ITEM_ID or container_id == BIG_CRATE_OF_SALVAGE_ITEM_ID) then
1420 Debug("CHAT_MSG_LOOT: AF.ITEM %d (%d)", item_id, quantity)
1421 current_loot.sources[container_id] = current_loot.sources[container_id] or {}
1422 current_loot.sources[container_id][item_id] = (current_loot.sources[container_id][item_id] or 0) + quantity
1423 else -- If not, cancel the timer and wipe the loot table early
1424 Debug("CHAT_MSG_LOOT: We would have assigned the wrong loot to salvage crates!")
1425 ClearTimeBasedLootData()
1426 end
1427 end,
1325 [AF.NPC] = function(item_id, quantity) 1428 [AF.NPC] = function(item_id, quantity)
1326 Debug("CHAT_MSG_LOOT: %d (%d)", item_id, quantity) 1429 Debug("CHAT_MSG_LOOT: AF.NPC %d (%d)", item_id, quantity)
1430 end,
1431 [AF.OBJECT] = function(item_id, quantity)
1432 Debug("CHAT_MSG_LOOT: AF.OBJECT %d (%d)", item_id, quantity)
1433 --for timber_variant = 1, #private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[last_timber_spell_id] do
1434 -- Check for top level object data
1435 local object_entry = DBEntry("objects", ("OPENING:%s"):format(private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[last_timber_spell_id]))
1436 local difficulty_token = InstanceDifficultyToken()
1437 if object_entry[difficulty_token] then
1438 -- Increment loot count
1439 object_entry[difficulty_token]["opening_count"] = (object_entry[difficulty_token]["opening_count"] or 0) + 1
1440
1441 -- Add drop data
1442 object_entry[difficulty_token]["opening"] = object_entry[difficulty_token]["opening"] or {}
1443 table.insert(object_entry[difficulty_token]["opening"], ("%d:%d"):format(item_id, quantity))
1444 else
1445 Debug("CHAT_MSG_LOOT: When handling timber, the top level loot data was missing for objectID %s.", private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[last_timber_spell_id])
1446 end
1447 --end
1327 end, 1448 end,
1328 [AF.ZONE] = function(item_id, quantity) 1449 [AF.ZONE] = function(item_id, quantity)
1450 Debug("CHAT_MSG_LOOT: AF.ZONE %d (%d)", item_id, quantity)
1329 InitializeCurrentLoot() 1451 InitializeCurrentLoot()
1330 current_loot.list[1] = ("%d:%d"):format(item_id, quantity) 1452 current_loot.list[1] = ("%d:%d"):format(item_id, quantity)
1331 GenericLootUpdate("zones") 1453 GenericLootUpdate("zones")
1332 current_loot = nil 1454 current_loot = nil
1333 end, 1455 end,
1335 1457
1336 1458
1337 function WDP:CHAT_MSG_LOOT(event_name, message) 1459 function WDP:CHAT_MSG_LOOT(event_name, message)
1338 local category 1460 local category
1339 1461
1340 if current_action.spell_label ~= "EXTRACT_GAS" then
1341 category = AF.ZONE
1342 elseif private.raid_finder_boss_id then
1343 category = AF.NPC
1344 end
1345 local update_func = CHAT_MSG_LOOT_UPDATE_FUNCS[category]
1346
1347 if not category or not update_func then
1348 return
1349 end
1350 local item_link, quantity = deformat(message, _G.LOOT_ITEM_PUSHED_SELF_MULTIPLE) 1462 local item_link, quantity = deformat(message, _G.LOOT_ITEM_PUSHED_SELF_MULTIPLE)
1351
1352 if not item_link then 1463 if not item_link then
1353 quantity, item_link = 1, deformat(message, _G.LOOT_ITEM_PUSHED_SELF) 1464 quantity, item_link = 1, deformat(message, _G.LOOT_ITEM_PUSHED_SELF)
1354 end 1465 end
1355 local item_id = ItemLinkToID(item_link) 1466 local item_id = ItemLinkToID(item_link)
1356 1467
1357 if not item_id then 1468 if not item_id then
1469 return
1470 end
1471
1472 -- Set update category
1473 if last_timber_spell_id and item_id == TIMBER_ITEM_ID then
1474 category = AF.OBJECT
1475 -- Recently changed from ~= "EXTRACT_GAS" because of some occassional bad data, and, as far as I know, no benefit.
1476 elseif current_action.spell_label == "FISHING" then
1477 category = AF.ZONE
1478 elseif private.raid_boss_id then
1479 category = AF.NPC
1480 elseif chat_loot_timer_handle then
1481 category = AF.ITEM
1482 end
1483
1484 -- Take action based on update category
1485 local update_func = CHAT_MSG_LOOT_UPDATE_FUNCS[category]
1486 if not category or not update_func then
1487 -- We still want to record the item's data, even if it doesn't need its drop location recorded
1488 RecordItemData(item_id, item_link, true)
1358 return 1489 return
1359 end 1490 end
1360 update_func(item_id, quantity) 1491 update_func(item_id, quantity)
1361 end 1492 end
1362 end 1493 end
1441 end 1572 end
1442 end 1573 end
1443 end 1574 end
1444 1575
1445 1576
1446 do -- do-block 1577 do
1447 local FLAGS_NPC = bit.bor(_G.COMBATLOG_OBJECT_TYPE_GUARDIAN, _G.COMBATLOG_OBJECT_CONTROL_NPC) 1578 local FLAGS_NPC = bit.bor(_G.COMBATLOG_OBJECT_TYPE_GUARDIAN, _G.COMBATLOG_OBJECT_CONTROL_NPC)
1448 local FLAGS_NPC_CONTROL = bit.bor(_G.COMBATLOG_OBJECT_AFFILIATION_OUTSIDER, _G.COMBATLOG_OBJECT_CONTROL_NPC) 1579 local FLAGS_NPC_CONTROL = bit.bor(_G.COMBATLOG_OBJECT_AFFILIATION_OUTSIDER, _G.COMBATLOG_OBJECT_CONTROL_NPC)
1449 1580
1450 local function RecordNPCSpell(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id, spell_name) 1581 local function RecordNPCSpell(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id, spell_name)
1451 if not spell_id or spell_id == SPELL_ID_CHI_WAVE or spell_id == SPELL_ID_DISGUISE then 1582 if not spell_id or spell_id == CHI_WAVE_SPELL_ID or spell_id == DISGUISE_SPELL_ID then
1452 return 1583 return
1453 end 1584 end
1454 local source_type, source_id = ParseGUID(source_guid) 1585 local source_type, source_id = ParseGUID(source_guid)
1455 1586
1456 if not source_id or not UnitTypeIsNPC(source_type) then 1587 if not source_id or not UnitTypeIsNPC(source_type) then
1546 if NON_DAMAGE_EVENTS[sub_event] then 1677 if NON_DAMAGE_EVENTS[sub_event] then
1547 table.wipe(previous_combat_event) 1678 table.wipe(previous_combat_event)
1548 end 1679 end
1549 end 1680 end
1550 1681
1682
1551 local DIPLOMACY_SPELL_ID = 20599 1683 local DIPLOMACY_SPELL_ID = 20599
1552 local MR_POP_RANK1_SPELL_ID = 78634 1684 local MR_POP_RANK1_SPELL_ID = 78634
1553 local MR_POP_RANK2_SPELL_ID = 78635 1685 local MR_POP_RANK2_SPELL_ID = 78635
1554 1686 local FACTION_DATA = private.FACTION_DATA
1555 local REP_BUFFS = { 1687 local REP_BUFFS = private.REP_BUFFS
1556 [_G.GetSpellInfo(30754)] = "CENARION_FAVOR",
1557 [_G.GetSpellInfo(24705)] = "GRIM_VISAGE",
1558 [_G.GetSpellInfo(32098)] = "HONOR_HOLD_FAVOR",
1559 [_G.GetSpellInfo(39913)] = "NAZGRELS_FERVOR",
1560 [_G.GetSpellInfo(39953)] = "SONG_OF_BATTLE",
1561 [_G.GetSpellInfo(61849)] = "SPIRIT_OF_SHARING",
1562 [_G.GetSpellInfo(32096)] = "THRALLMARS_FAVOR",
1563 [_G.GetSpellInfo(39911)] = "TROLLBANES_COMMAND",
1564 [_G.GetSpellInfo(95987)] = "UNBURDENED",
1565 [_G.GetSpellInfo(100951)] = "WOW_ANNIVERSARY",
1566 }
1567
1568
1569 local FACTION_NAMES = {
1570 CENARION_CIRCLE = _G.GetFactionInfoByID(609),
1571 HONOR_HOLD = _G.GetFactionInfoByID(946),
1572 THE_SHATAR = _G.GetFactionInfoByID(935),
1573 THRALLMAR = _G.GetFactionInfoByID(947),
1574 }
1575
1576
1577 local MODIFIERS = {
1578 CENARION_FAVOR = {
1579 faction = FACTION_NAMES.CENARION_CIRCLE,
1580 modifier = 0.25,
1581 },
1582 GRIM_VISAGE = {
1583 modifier = 0.1,
1584 },
1585 HONOR_HOLD_FAVOR = {
1586 faction = FACTION_NAMES.HONOR_HOLD,
1587 modifier = 0.25,
1588 },
1589 NAZGRELS_FERVOR = {
1590 faction = FACTION_NAMES.THRALLMAR,
1591 modifier = 0.1,
1592 },
1593 SONG_OF_BATTLE = {
1594 faction = FACTION_NAMES.THE_SHATAR,
1595 modifier = 0.1,
1596 },
1597 SPIRIT_OF_SHARING = {
1598 modifier = 0.1,
1599 },
1600 THRALLMARS_FAVOR = {
1601 faction = FACTION_NAMES.THRALLMAR,
1602 modifier = 0.25,
1603 },
1604 TROLLBANES_COMMAND = {
1605 faction = FACTION_NAMES.HONOR_HOLD,
1606 modifier = 0.1,
1607 },
1608 UNBURDENED = {
1609 modifier = 0.1,
1610 },
1611 WOW_ANNIVERSARY = {
1612 modifier = 0.08,
1613 }
1614 }
1615 1688
1616 1689
1617 function WDP:COMBAT_TEXT_UPDATE(event_name, message_type, faction_name, amount) 1690 function WDP:COMBAT_TEXT_UPDATE(event_name, message_type, faction_name, amount)
1618 if message_type ~= "FACTION" or not killed_npc_id then 1691 if message_type ~= "FACTION" or not killed_npc_id then
1619 return 1692 return
1633 npc.harvested = private.harvesting 1706 npc.harvested = private.harvesting
1634 private.harvesting = nil 1707 private.harvesting = nil
1635 1708
1636 local modifier = 1 1709 local modifier = 1
1637 1710
1711 -- Check for modifiers from known spells
1638 if _G.IsSpellKnown(DIPLOMACY_SPELL_ID) then 1712 if _G.IsSpellKnown(DIPLOMACY_SPELL_ID) then
1639 modifier = modifier + 0.1 1713 modifier = modifier + 0.1
1640 end 1714 end
1641
1642 if _G.IsSpellKnown(MR_POP_RANK2_SPELL_ID) then 1715 if _G.IsSpellKnown(MR_POP_RANK2_SPELL_ID) then
1643 modifier = modifier + 0.1 1716 modifier = modifier + 0.1
1644 elseif _G.IsSpellKnown(MR_POP_RANK1_SPELL_ID) then 1717 elseif _G.IsSpellKnown(MR_POP_RANK1_SPELL_ID) then
1645 modifier = modifier + 0.05 1718 modifier = modifier + 0.05
1646 end 1719 end
1647 1720
1648 for buff_name, buff_label in pairs(REP_BUFFS) do 1721 -- Determine faction ID
1722 local faction_ID
1723 for pseudo_faction_name, faction_data_table in pairs(FACTION_DATA) do
1724 if faction_name == faction_data_table[2] then
1725 faction_ID = faction_data_table[1]
1726 end
1727 end
1728 if faction_ID and faction_ID > 0 then
1729 -- Check for modifiers from Commendations (applied directly to the faction, account-wide)
1730 local _, _, _, _, _, _, _, _, _, _, _, _, _, _, has_bonus_rep_gain = GetFactionInfoByID(faction_ID)
1731 if has_bonus_rep_gain then
1732 modifier = modifier + 1
1733 end
1734 end
1735
1736 -- Check for modifiers from buffs
1737 for buff_name, buff_data_table in pairs(REP_BUFFS) do
1649 if _G.UnitBuff("player", buff_name) then 1738 if _G.UnitBuff("player", buff_name) then
1650 local modded_faction = MODIFIERS[buff_label].faction 1739 local modded_faction = buff_data_table.faction
1651 1740
1652 if not modded_faction or faction_name == modded_faction then 1741 if not modded_faction or faction_name == modded_faction then
1653 modifier = modifier + MODIFIERS[buff_label].modifier 1742 if buff_data_table.ignore then
1654 end 1743 -- Some buffs from tabards convert all rep of other factions into rep for a specific faction.
1655 end 1744 -- We can't know what faction the rep was orginally from, so we must ignore the data entirely in these cases.
1656 end 1745 return
1746 else
1747 modifier = modifier + buff_data_table.modifier
1748 end
1749 end
1750 end
1751 end
1752
1657 npc.reputations = npc.reputations or {} 1753 npc.reputations = npc.reputations or {}
1658 npc.reputations[("%s:%s"):format(faction_name, faction_standings[faction_name])] = math.floor(amount / modifier) 1754 npc.reputations[("%s:%s"):format(faction_name, faction_standings[faction_name])] = math.floor(amount / modifier)
1659 end 1755 end
1660 end -- do-block 1756 end -- do-block
1661 1757
1662 1758
1663 function WDP:CURSOR_UPDATE(event_name) 1759 function WDP:CURSOR_UPDATE(event_name)
1664 if current_action.fishing_target or _G.Minimap:IsMouseOver() or current_action.spell_label ~= "FISHING" then 1760 if current_action.fishing_target or _G.Minimap:IsMouseOver() then
1665 return 1761 return
1666 end 1762 end
1667 local text = _G["GameTooltipTextLeft1"]:GetText() 1763 local text = _G["GameTooltipTextLeft1"]:GetText()
1668 1764
1669 if not text or text == "Fishing Bobber" then 1765 -- Handle Fishing
1670 text = "NONE" 1766 if (current_action.spell_label == "FISHING") then
1671 else 1767 if not text or text == "Fishing Bobber" then
1672 current_action.fishing_target = true 1768 text = "NONE"
1673 end 1769 else
1674 current_action.identifier = ("%s:%s"):format(current_action.spell_label, text) 1770 current_action.fishing_target = true
1771 end
1772 current_action.identifier = ("%s:%s"):format(current_action.spell_label, text)
1773 -- Handle Garrison Cache
1774 elseif private.GARRISON_CACHE_OBJECT_NAME_TO_OBJECT_ID_MAP[text] then
1775 last_garrison_cache_object_id = private.GARRISON_CACHE_OBJECT_NAME_TO_OBJECT_ID_MAP[text]
1776 end
1675 end 1777 end
1676 1778
1677 1779
1678 function WDP:ITEM_TEXT_BEGIN(event_name) 1780 function WDP:ITEM_TEXT_BEGIN(event_name)
1679 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc")) 1781 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc"))
1729 local difficulty_token = InstanceDifficultyToken() 1831 local difficulty_token = InstanceDifficultyToken()
1730 local loot_label = current_loot.label 1832 local loot_label = current_loot.label
1731 local source_list = {} 1833 local source_list = {}
1732 1834
1733 for source_guid, loot_data in pairs(current_loot.sources) do 1835 for source_guid, loot_data in pairs(current_loot.sources) do
1734 local source_id = select(2, ParseGUID(source_guid)) 1836 local _, source_id = ParseGUID(source_guid)
1735 local npc = NPCEntry(source_id) 1837 local npc = NPCEntry(source_id)
1736 1838
1737 if npc then 1839 if npc then
1738 local encounter_data = npc:EncounterData(difficulty_token) 1840 local encounter_data = npc:EncounterData(difficulty_token)
1739 encounter_data[loot_label] = encounter_data[loot_label] or {} 1841 encounter_data[loot_label] = encounter_data[loot_label] or {}
1806 1908
1807 local function ExtrapolatedCurrentActionFromLootData(event_name) 1909 local function ExtrapolatedCurrentActionFromLootData(event_name)
1808 local extrapolated_guid_registry = {} 1910 local extrapolated_guid_registry = {}
1809 local num_guids = 0 1911 local num_guids = 0
1810 1912
1913 -- Loot extrapolation cannot handle objects that need special spell labels (like HERBALISM or MINING) (MIND_CONTROL is okay)
1914 if private.SPELL_FLAGS_BY_LABEL[current_action.spell_label] and not private.NON_LOOT_SPELL_LABELS[current_action.spell_label] then
1915 Debug("%s: Problematic spell label %s found. Loot extrapolation for this set of loot would have run an increased risk of introducing bad data into the system.", log_source, private.previous_spell_id)
1916 table.wipe(current_action)
1917 return false
1918 end
1919
1811 table.wipe(current_action) 1920 table.wipe(current_action)
1812 1921
1813 for loot_slot = 1, _G.GetNumLootItems() do 1922 for loot_slot = 1, _G.GetNumLootItems() do
1814 local loot_info = { 1923 local loot_info = {
1815 _G.GetLootSourceInfo(loot_slot) 1924 _G.GetLootSourceInfo(loot_slot)
1834 end 1943 end
1835 local log_source = event_name .. "- ExtrapolatedCurrentActionFromLootData" 1944 local log_source = event_name .. "- ExtrapolatedCurrentActionFromLootData"
1836 1945
1837 if num_guids == 0 then 1946 if num_guids == 0 then
1838 Debug("%s: No GUIDs found in loot. Blank loot window?", log_source) 1947 Debug("%s: No GUIDs found in loot. Blank loot window?", log_source)
1839 return false
1840 end
1841
1842 if private.previous_spell_id and private.EXTRAPOLATION_BANNED_SPELL_IDS[private.previous_spell_id] then
1843 Debug("%s: Problematic spell id %s found. Loot extrapolation for this set of loot would have run an increased risk of introducing bad data into the system.", log_source, private.previous_spell_id)
1844 return false 1948 return false
1845 end 1949 end
1846 1950
1847 local num_npcs = 0 1951 local num_npcs = 0
1848 local num_objects = 0 1952 local num_objects = 0
1866 elseif UnitTypeIsNPC(unit_type) then 1970 elseif UnitTypeIsNPC(unit_type) then
1867 current_action.loot_label = loot_label 1971 current_action.loot_label = loot_label
1868 current_action.target_type = AF.NPC 1972 current_action.target_type = AF.NPC
1869 current_action.identifier = unit_idnum 1973 current_action.identifier = unit_idnum
1870 num_npcs = num_npcs + 1 1974 num_npcs = num_npcs + 1
1871 -- Item container GUIDs are currently of the 'PLAYER' type; this may be unintended and could change in the future.
1872 elseif unit_type == private.UNIT_TYPES.PLAYER then 1975 elseif unit_type == private.UNIT_TYPES.PLAYER then
1976 -- Item container GUIDs are currently of the 'PLAYER' type; this may be unintended and could change in the future.
1873 current_action.loot_label = loot_label 1977 current_action.loot_label = loot_label
1874 current_action.target_type = AF.ITEM 1978 current_action.target_type = AF.ITEM
1875 -- current_action.identifier assigned during loot verification. 1979 -- current_action.identifier assigned during loot verification.
1876 num_itemcontainers = num_itemcontainers + 1 1980 num_itemcontainers = num_itemcontainers + 1
1877 end 1981 end
1945 for loot_index = 1, #loot_info, 2 do 2049 for loot_index = 1, #loot_info, 2 do
1946 local source_guid = loot_info[loot_index] 2050 local source_guid = loot_info[loot_index]
1947 2051
1948 if not loot_guid_registry[current_loot.label][source_guid] then 2052 if not loot_guid_registry[current_loot.label][source_guid] then
1949 local loot_quantity = loot_info[loot_index + 1] 2053 local loot_quantity = loot_info[loot_index + 1]
1950
1951 -- There is a new bug in 5.4.0 that causes GetLootSlotInfo() to (rarely) return nil values for slot_quantity. 2054 -- There is a new bug in 5.4.0 that causes GetLootSlotInfo() to (rarely) return nil values for slot_quantity.
1952 if slot_quantity then 2055 if slot_quantity then
1953 -- We need slot_quantity to account for an old bug where loot_quantity is sometimes '1' for stacks of items, such as cloth. 2056 -- We need slot_quantity to account for an old bug where loot_quantity is sometimes '1' for stacks of items, such as cloth.
1954 if slot_quantity > loot_quantity then 2057 if slot_quantity > loot_quantity then
1955 loot_quantity = slot_quantity 2058 loot_quantity = slot_quantity
1956 end 2059 end
1957
1958 local source_type, source_id = ParseGUID(source_guid) 2060 local source_type, source_id = ParseGUID(source_guid)
1959 local source_key = ("%s:%d"):format(private.UNIT_TYPE_NAMES[source_type + 1], source_id) 2061 local source_key = ("%s:%d"):format(source_type, source_id)
1960 2062
1961 if slot_type == _G.LOOT_SLOT_ITEM then 2063 if slot_type == _G.LOOT_SLOT_ITEM then
1962 local item_id = ItemLinkToID(_G.GetLootSlotLink(loot_slot)) 2064 local item_id = ItemLinkToID(_G.GetLootSlotLink(loot_slot))
1963 Debug("GUID: %s - Type:ID: %s - ItemID: %d - Amount: %d (%d)", loot_info[loot_index], source_key, item_id, loot_info[loot_index + 1], slot_quantity) 2065 Debug("GUID: %s - Type:ID: %s - ItemID: %d - Amount: %d (%d)", loot_info[loot_index], source_key, item_id, loot_info[loot_index + 1], slot_quantity)
1964 current_loot.sources[source_guid] = current_loot.sources[source_guid] or {} 2066 current_loot.sources[source_guid] = current_loot.sources[source_guid] or {}
1990 Debug("%s: Slot quantity is nil for loot slot %d of the entity with GUID %s and Type:ID: %s.", event_name, loot_slot, loot_info[loot_index], source_key) 2092 Debug("%s: Slot quantity is nil for loot slot %d of the entity with GUID %s and Type:ID: %s.", event_name, loot_slot, loot_info[loot_index], source_key)
1991 end 2093 end
1992 end 2094 end
1993 else 2095 else
1994 -- If this is nil, then the item's quantity could be wrong if loot_quantity is wrong, so we won't process this slot's loot. 2096 -- If this is nil, then the item's quantity could be wrong if loot_quantity is wrong, so we won't process this slot's loot.
1995 Debug("%s: Slot quantity is nil for loot slot %d of the entity with GUID %s and Type:ID: %s.", event_name, loot_slot, loot_info[loot_index], source_key) 2097 Debug("%s: Slot quantity is nil for loot slot %d.", event_name, loot_slot)
1996 end 2098 end
1997 end 2099 end
1998 end 2100 end
1999 end 2101 end
2000 2102
2043 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc")) 2145 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc"))
2044 2146
2045 if not unit_idnum or not UnitTypeIsNPC(unit_type) then 2147 if not unit_idnum or not UnitTypeIsNPC(unit_type) then
2046 return 2148 return
2047 end 2149 end
2048 merchant_standing = select(2, UnitFactionStanding("npc")) 2150 local _, faction_standing = UnitFactionStanding("npc")
2151 merchant_standing = faction_standing
2049 current_merchant = NPCEntry(unit_idnum) 2152 current_merchant = NPCEntry(unit_idnum)
2050 current_merchant.sells = current_merchant.sells or {} 2153 current_merchant.sells = current_merchant.sells or {}
2051 end 2154 end
2052 local current_filters = _G.GetMerchantFilter() 2155 local current_filters = _G.GetMerchantFilter()
2053 _G.SetMerchantFilter(_G.LE_LOOT_FILTER_ALL) 2156 _G.SetMerchantFilter(_G.LE_LOOT_FILTER_ALL)
2063 DatamineTT:SetMerchantItem(item_index) 2166 DatamineTT:SetMerchantItem(item_index)
2064 2167
2065 if not item_id then 2168 if not item_id then
2066 local item_name, item_link = DatamineTT:GetItem() 2169 local item_name, item_link = DatamineTT:GetItem()
2067 item_id = ItemLinkToID(item_link) 2170 item_id = ItemLinkToID(item_link)
2068 if item_id then 2171 -- GetMerchantItemLink() still ocassionally fails as of Patch 6.0.2. It fails so badly that debug functions cause considerable slowdown.
2069 Debug("%s: GetMerchantItemLink() still ocassionally fails, apparently. Failed item's ID - %s", event_name, item_id)
2070 else
2071 Debug("%s: GetMerchantItemLink() still ocassionally fails, apparently. Failed item's ID - nil", event_name)
2072 end
2073 end 2172 end
2074 2173
2075 if item_id and item_id > 0 then 2174 if item_id and item_id > 0 then
2076 local price_string = ActualCopperCost(copper_price, merchant_standing) 2175 local price_string = ActualCopperCost(copper_price, merchant_standing)
2077 2176
2166 end 2265 end
2167 end -- do-block 2266 end -- do-block
2168 2267
2169 2268
2170 function WDP:PET_BAR_UPDATE(event_name) 2269 function WDP:PET_BAR_UPDATE(event_name)
2171 if current_action.spell_label ~= "MIND_CONTROL" then 2270 if not private.NON_LOOT_SPELL_LABELS[current_action.spell_label] then
2172 return 2271 return
2173 end 2272 end
2174 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("pet")) 2273 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("pet"))
2175 2274
2176 if not unit_idnum or not UnitTypeIsNPC(unit_type) then 2275 if not unit_idnum or not UnitTypeIsNPC(unit_type) then
2243 2342
2244 if not unit_name then 2343 if not unit_name then
2245 return 2344 return
2246 end 2345 end
2247 local unit_type, unit_id = ParseGUID(_G.UnitGUID("questnpc")) 2346 local unit_type, unit_id = ParseGUID(_G.UnitGUID("questnpc"))
2248 2347 Debug("UpdateQuestJuncture: Updating quest juncture for %s.", ("%s:%d"):format(private.UNIT_TYPE_NAMES[unit_type], unit_id))
2249 if unit_type == private.UNIT_TYPES.OBJECT then 2348 if unit_type == private.UNIT_TYPES.OBJECT then
2250 UpdateDBEntryLocation("objects", unit_id) 2349 UpdateDBEntryLocation("objects", unit_id)
2251 end 2350 end
2252 local quest = DBEntry("quests", _G.GetQuestID()) 2351 local quest = DBEntry("quests", _G.GetQuestID())
2253 quest[point] = quest[point] or {} 2352 quest[point] = quest[point] or {}
2254 quest[point][("%s:%d"):format(private.UNIT_TYPE_NAMES[unit_type + 1], unit_id)] = true 2353 quest[point][("%s:%d"):format(private.UNIT_TYPE_NAMES[unit_type], unit_id)] = true
2255 2354
2256 return quest 2355 return quest
2257 end 2356 end
2258 2357
2259 2358
2287 local selected_quest = _G.GetQuestLogSelection() -- Save current selection to be restored when we're done. 2386 local selected_quest = _G.GetQuestLogSelection() -- Save current selection to be restored when we're done.
2288 local entry_index, processed_quests = 1, 0 2387 local entry_index, processed_quests = 1, 0
2289 local _, num_quests = _G.GetNumQuestLogEntries() 2388 local _, num_quests = _G.GetNumQuestLogEntries()
2290 2389
2291 while processed_quests <= num_quests do 2390 while processed_quests <= num_quests do
2292 local _, _, _, _, is_header, _, _, _, quest_id = _G.GetQuestLogTitle(entry_index) 2391 local _, _, _, is_header, _, _, _, quest_id = _G.GetQuestLogTitle(entry_index)
2293 2392
2294 if quest_id == 0 then 2393 if quest_id == 0 then
2295 processed_quests = processed_quests + 1 2394 processed_quests = processed_quests + 1
2296 elseif not is_header then 2395 elseif not is_header then
2297 _G.SelectQuestLogEntry(entry_index); 2396 _G.SelectQuestLogEntry(entry_index);
2330 Forge = forge_spell_ids, 2429 Forge = forge_spell_ids,
2331 } 2430 }
2332 2431
2333 2432
2334 local function RegisterTools(tradeskill_name, tradeskill_index) 2433 local function RegisterTools(tradeskill_name, tradeskill_index)
2335 local spell_id = tonumber(_G.GetTradeSkillRecipeLink(tradeskill_index):match("^|c%x%x%x%x%x%x%x%x|H%w+:(%d+)")) 2434 local link = _G.GetTradeSkillRecipeLink(tradeskill_index)
2336 local required_tool = _G.GetTradeSkillTools(tradeskill_index) 2435 if link then
2337 2436 local spell_id = tonumber(link:match("^|c%x%x%x%x%x%x%x%x|H%w+:(%d+)"))
2338 if required_tool then 2437 local required_tool = _G.GetTradeSkillTools(tradeskill_index)
2339 for tool_name, registry in pairs(TRADESKILL_TOOLS) do 2438
2340 if required_tool:find(tool_name) then 2439 if required_tool then
2341 registry[spell_id] = true 2440 for tool_name, registry in pairs(TRADESKILL_TOOLS) do
2441 if required_tool:find(tool_name) then
2442 registry[spell_id] = true
2443 end
2342 end 2444 end
2343 end 2445 end
2344 end 2446 end
2345 end 2447 end
2346 2448
2366 local trainer = NPCEntry(unit_idnum) 2468 local trainer = NPCEntry(unit_idnum)
2367 2469
2368 if not trainer then 2470 if not trainer then
2369 return 2471 return
2370 end 2472 end
2371 local trainer_standing = select(2, UnitFactionStanding("npc")) 2473 local _, trainer_standing = UnitFactionStanding("npc")
2372 trainer.teaches = trainer.teaches or {} 2474 trainer.teaches = trainer.teaches or {}
2373 2475
2374 private.trainer_shown = true 2476 private.trainer_shown = true
2375 2477
2376 -- Get the initial trainer filters 2478 -- Get the initial trainer filters
2377 local available = _G.GetTrainerServiceTypeFilter("available") 2479 local available = _G.GetTrainerServiceTypeFilter("available") and 1 or 0
2378 local unavailable = _G.GetTrainerServiceTypeFilter("unavailable") 2480 local unavailable = _G.GetTrainerServiceTypeFilter("unavailable") and 1 or 0
2379 local used = _G.GetTrainerServiceTypeFilter("used") 2481 local used = _G.GetTrainerServiceTypeFilter("used") and 1 or 0
2380 2482
2381 -- Clear the trainer filters 2483 -- Clear the trainer filters
2382 _G.SetTrainerServiceTypeFilter("available", 1) 2484 _G.SetTrainerServiceTypeFilter("available", 1)
2383 _G.SetTrainerServiceTypeFilter("unavailable", 1) 2485 _G.SetTrainerServiceTypeFilter("unavailable", 1)
2384 _G.SetTrainerServiceTypeFilter("used", 1) 2486 _G.SetTrainerServiceTypeFilter("used", 1)
2427 2529
2428 if not spell_label then 2530 if not spell_label then
2429 return 2531 return
2430 end 2532 end
2431 2533
2534 Debug("UNIT_SPELLCAST_SENT: %s was cast.", spell_name)
2432 local item_name, item_link = _G.GameTooltip:GetItem() 2535 local item_name, item_link = _G.GameTooltip:GetItem()
2433 local unit_name, unit_id = _G.GameTooltip:GetUnit() 2536 local unit_name, unit_id = _G.GameTooltip:GetUnit()
2434 2537
2435 if not unit_name and _G.UnitName("target") == target_name then 2538 if not unit_name and _G.UnitName("target") == target_name then
2436 unit_name = target_name 2539 unit_name = target_name
2466 current_action.target_type = AF.ITEM 2569 current_action.target_type = AF.ITEM
2467 2570
2468 if item_name and item_name == target_name then 2571 if item_name and item_name == target_name then
2469 current_action.identifier = ItemLinkToID(item_link) 2572 current_action.identifier = ItemLinkToID(item_link)
2470 elseif target_name and target_name ~= "" then 2573 elseif target_name and target_name ~= "" then
2471 current_action.identifier = ItemLinkToID(select(2, _G.GetItemInfo(target_name))) 2574 local _, item_link = _G.GetItemInfo(target_name)
2575 current_action.identifier = ItemLinkToID(item_link)
2472 end 2576 end
2473 elseif not item_name and not unit_name then 2577 elseif not item_name and not unit_name then
2474 if bit.band(spell_flags, AF.OBJECT) == AF.OBJECT then 2578 if bit.band(spell_flags, AF.OBJECT) == AF.OBJECT then
2475 if target_name == "" then 2579 if target_name == "" then
2476 return 2580 return
2487 2591
2488 function WDP:SPELL_CONFIRMATION_PROMPT(event_name, spell_id, confirm_type, text, duration, currency_id_cost) 2592 function WDP:SPELL_CONFIRMATION_PROMPT(event_name, spell_id, confirm_type, text, duration, currency_id_cost)
2489 if private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id] then 2593 if private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id] then
2490 ClearKilledBossID() 2594 ClearKilledBossID()
2491 ClearLootToastContainerID() 2595 ClearLootToastContainerID()
2492 private.raid_finder_boss_id = private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id] 2596 private.raid_boss_id = private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id]
2493 elseif private.WORLD_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id] then
2494 ClearKilledBossID()
2495 ClearLootToastContainerID()
2496 private.world_boss_id = private.WORLD_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id]
2497 else 2597 else
2498 Debug("%s: Spell ID %d is not a known raid or world boss 'Bonus' spell.", event_name, spell_id) 2598 Debug("%s: Spell ID %d is not a known raid boss 'Bonus' spell.", event_name, spell_id)
2499 return 2599 return
2500 end 2600 end
2501 2601
2502 -- Assign existing loot data to boss if it exists 2602 -- Assign existing loot data to boss if it exists
2503 if loot_toast_data then 2603 if loot_toast_data then
2504 local npc_id = private.raid_finder_boss_id or private.world_boss_id 2604 local npc_id = private.raid_boss_id
2505 2605
2506 -- Slightly messy hack to workaround duplicate world bosses 2606 -- Slightly messy hack to workaround duplicate world bosses
2507 local upper_limit = 0 2607 local upper_limit = 0
2508 if DUPLICATE_WORLD_BOSS_IDS[npc_id] then 2608 if DUPLICATE_WORLD_BOSS_IDS[npc_id] then
2509 upper_limit = #DUPLICATE_WORLD_BOSS_IDS[npc_id] 2609 upper_limit = #DUPLICATE_WORLD_BOSS_IDS[npc_id]
2569 return 2669 return
2570 end 2670 end
2571 private.tracked_line = nil 2671 private.tracked_line = nil
2572 private.previous_spell_id = spell_id 2672 private.previous_spell_id = spell_id
2573 2673
2674 -- Handle Logging spell casts
2675 if private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[spell_id] then
2676 last_timber_spell_id = spell_id
2677 --for timber_variant = 1, #private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[spell_id] do
2678 UpdateDBEntryLocation("objects", ("OPENING:%s"):format(private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[spell_id]))
2679 --end
2680 return
2681 end
2682
2683 -- Handle Loot Toast spell casts
2574 if private.LOOT_SPELL_ID_TO_ITEM_ID_MAP[spell_id] then 2684 if private.LOOT_SPELL_ID_TO_ITEM_ID_MAP[spell_id] then
2575 ClearKilledBossID() 2685 ClearKilledBossID()
2576 ClearLootToastContainerID() 2686 ClearLootToastContainerID()
2577 ClearLootToastData() 2687 ClearLootToastData()
2578 2688
2579 private.loot_toast_container_id = private.LOOT_SPELL_ID_TO_ITEM_ID_MAP[spell_id] 2689 private.loot_toast_container_id = private.LOOT_SPELL_ID_TO_ITEM_ID_MAP[spell_id]
2580 loot_toast_container_timer_handle = WDP:ScheduleTimer(ClearLootToastContainerID, 1) -- we need to assign a handle here to cancel it later 2690 loot_toast_container_timer_handle = WDP:ScheduleTimer(ClearLootToastContainerID, 1) -- we need to assign a handle here to cancel it later
2691 return
2692 end
2693
2694 -- For Crates of Salvage (and potentially other items based on spell casts in the future which need manual handling)
2695 if private.SALVAGE_SPELL_ID_TO_ITEM_ID_MAP[spell_id] then
2696 -- Set up timer
2697 Debug("%s: Beginning Salvage loot timer for spellID %d", event_name, spell_id)
2698 chat_loot_timer_handle = WDP:ScheduleTimer(ClearTimeBasedLootData, 1)
2699
2700 -- Standard item handling setup
2701 table.wipe(current_action)
2702 current_loot = nil
2703 current_action.target_type = AF.ITEM
2704 current_action.identifier = private.SALVAGE_SPELL_ID_TO_ITEM_ID_MAP[spell_id]
2705 current_action.loot_label = "contains"
2706 InitializeCurrentLoot()
2707 return
2581 end 2708 end
2582 2709
2583 if anvil_spell_ids[spell_id] then 2710 if anvil_spell_ids[spell_id] then
2584 UpdateDBEntryLocation("objects", OBJECT_ID_ANVIL) 2711 UpdateDBEntryLocation("objects", OBJECT_ID_ANVIL)
2585 elseif forge_spell_ids[spell_id] then 2712 elseif forge_spell_ids[spell_id] then
2586 UpdateDBEntryLocation("objects", OBJECT_ID_FORGE) 2713 UpdateDBEntryLocation("objects", OBJECT_ID_FORGE)
2587 elseif spell_name:match("^Harvest.+") then 2714 elseif spell_name:match("^Harvest.+") then
2588 killed_npc_id = current_target_id 2715 killed_npc_id = current_target_id
2589 private.harvesting = true 2716 private.harvesting = true -- Used to track which NPCs can be harvested (can we get this from CreatureCache instead?)
2590 end 2717 end
2591 end 2718 end
2592 2719
2593 2720
2594 function WDP:HandleSpellFailure(event_name, unit_id, spell_name, spell_rank, spell_line, spell_id) 2721 function WDP:HandleSpellFailure(event_name, unit_id, spell_name, spell_rank, spell_line, spell_id)