jcallahan@246: -- LUA API ------------------------------------------------------------ jcallahan@246: jcallahan@0: local _G = getfenv(0) jcallahan@0: jcallahan@0: local pairs = _G.pairs jcallahan@312: local tostring = _G.tostring jcallahan@1: local tonumber = _G.tonumber jcallahan@1: jcallahan@1: local bit = _G.bit jcallahan@1: local math = _G.math jcallahan@1: local table = _G.table jcallahan@1: jcallahan@334: local next = _G.next jcallahan@78: local select = _G.select mmosimca@485: local type = _G.type jcallahan@306: local unpack = _G.unpack jcallahan@78: MMOSimca@383: local C_Timer = _G.C_Timer MMOSimca@383: jcallahan@0: jcallahan@246: -- ADDON NAMESPACE ---------------------------------------------------- jcallahan@246: jcallahan@0: local ADDON_NAME, private = ... jcallahan@0: jcallahan@0: local LibStub = _G.LibStub MMOSimca@383: local WDP = LibStub("AceAddon-3.0"):NewAddon(ADDON_NAME, "AceConsole-3.0", "AceEvent-3.0") jcallahan@0: jcallahan@48: local deformat = LibStub("LibDeformat-3.0") catherton@465: local HereBeDragons = LibStub("HereBeDragons-1.0") mmosimca@484: local LibRealmInfo = LibStub("LibRealmInfo") jcallahan@48: jcallahan@4: local DatamineTT = _G.CreateFrame("GameTooltip", "WDPDatamineTT", _G.UIParent, "GameTooltipTemplate") jcallahan@5: DatamineTT:SetOwner(_G.WorldFrame, "ANCHOR_NONE") jcallahan@5: jcallahan@0: jcallahan@246: -- CONSTANTS ---------------------------------------------------------- jcallahan@246: jcallahan@246: local AF = private.ACTION_TYPE_FLAGS jcallahan@246: local CLIENT_LOCALE = _G.GetLocale() jcallahan@313: local DB_VERSION = 18 MMOSimca@346: local DEBUGGING = false jcallahan@156: local EVENT_DEBUG = false jcallahan@322: mmosimca@485: -- Timer durations in seconds mmosimca@485: local DELAY_PROCESS_ITEMS = 30 mmosimca@485: local DELAY_PROCESS_WORLD_QUESTS = 60 mmosimca@485: local DELAY_UPDATE_TARGET_LOCATION = 0.5 mmosimca@485: MMOSimca@405: local ITEM_ID_TIMBER = 114781 MMOSimca@405: MMOSimca@422: local LOOT_SOURCE_ID_REDUNDANT = 3 MMOSimca@422: local LOOT_SOURCE_ID_GARRISON_CACHE = 10 MMOSimca@422: jcallahan@246: local OBJECT_ID_ANVIL = 192628 jcallahan@322: local OBJECT_ID_FISHING_BOBBER = 35591 jcallahan@246: local OBJECT_ID_FORGE = 1685 jcallahan@322: MMOSimca@454: local PLAYER_CLASS, PLAYER_CLASS_ID = _G.select(2, _G.UnitClass("player")) jcallahan@246: local PLAYER_FACTION = _G.UnitFactionGroup("player") jcallahan@300: local PLAYER_GUID mmosimca@485: local PLAYER_LEVEL = _G.UnitLevel("player") jcallahan@246: local PLAYER_NAME = _G.UnitName("player") jcallahan@246: local PLAYER_RACE = _G.select(2, _G.UnitRace("player")) jcallahan@246: MMOSimca@377: local LOOT_SLOT_CURRENCY = _G.LOOT_SLOT_CURRENCY MMOSimca@377: local LOOT_SLOT_ITEM = _G.LOOT_SLOT_ITEM MMOSimca@377: local LOOT_SLOT_MONEY = _G.LOOT_SLOT_MONEY MMOSimca@377: mmosimca@485: local WORLD_MAP_ID_BROKEN_ISLES = 1007 mmosimca@485: catherton@472: -- Removed in Legion but still needed catherton@472: local ERR_QUEST_REWARD_ITEM_MULT_IS = _G.ERR_QUEST_REWARD_ITEM_MULT_IS or "Received %d of item: %s." catherton@472: local ERR_QUEST_REWARD_ITEM_S = _G.ERR_QUEST_REWARD_ITEM_S or "Received item: %s." catherton@472: jcallahan@246: local ALLOWED_LOCALES = { jcallahan@246: enUS = true, jcallahan@246: enGB = true, MMOSimca@336: enTW = true, MMOSimca@336: enCN = true, jcallahan@246: } jcallahan@157: jcallahan@0: local DATABASE_DEFAULTS = { jcallahan@128: char = {}, jcallahan@0: global = { jcallahan@270: config = { jcallahan@270: minimap_icon = { jcallahan@270: hide = true, jcallahan@270: }, jcallahan@270: }, jcallahan@0: items = {}, jcallahan@0: npcs = {}, jcallahan@0: objects = {}, jcallahan@0: quests = {}, jcallahan@167: spells = {}, jcallahan@17: zones = {}, jcallahan@0: } jcallahan@0: } jcallahan@0: jcallahan@1: local EVENT_MAPPING = { MMOSimca@436: AUCTION_HOUSE_CLOSED = "ResumeChatLootRecording", MMOSimca@436: AUCTION_HOUSE_SHOW = true, -- also triggers StopChatLootRecording MMOSimca@436: BANKFRAME_CLOSED = "ResumeChatLootRecording", MMOSimca@436: BANKFRAME_OPENED = true, -- also triggers StopChatLootRecording jcallahan@90: BATTLEFIELDS_SHOW = true, jcallahan@56: BLACK_MARKET_ITEM_UPDATE = true, MMOSimca@408: BONUS_ROLL_RESULT = true, MMOSimca@388: CHAT_MSG_CURRENCY = true, jcallahan@48: CHAT_MSG_LOOT = true, jcallahan@95: CHAT_MSG_MONSTER_SAY = "RecordQuote", jcallahan@95: CHAT_MSG_MONSTER_WHISPER = "RecordQuote", jcallahan@95: CHAT_MSG_MONSTER_YELL = "RecordQuote", jcallahan@40: CHAT_MSG_SYSTEM = true, jcallahan@23: COMBAT_LOG_EVENT_UNFILTERED = true, jcallahan@18: COMBAT_TEXT_UPDATE = true, jcallahan@140: CURSOR_UPDATE = true, jcallahan@90: FORGE_MASTER_OPENED = true, MMOSimca@436: GARRISON_MISSION_NPC_CLOSED = "ResumeChatLootRecording", MMOSimca@436: GARRISON_MISSION_NPC_OPENED = "StopChatLootRecording", MMOSimca@450: GARRISON_SHIPYARD_NPC_CLOSED = "ResumeChatLootRecording", MMOSimca@450: GARRISON_SHIPYARD_NPC_OPENED = "StopChatLootRecording", MMOSimca@436: GOSSIP_CLOSED = "ResumeChatLootRecording", MMOSimca@436: GOSSIP_SHOW = true, -- also triggers StopChatLootRecording jcallahan@290: GROUP_ROSTER_UPDATE = true, MMOSimca@436: GUILDBANKFRAME_CLOSED = "ResumeChatLootRecording", MMOSimca@436: GUILDBANKFRAME_OPENED = true, -- also triggers StopChatLootRecording jcallahan@42: ITEM_TEXT_BEGIN = true, jcallahan@189: ITEM_UPGRADE_MASTER_OPENED = true, jcallahan@124: LOOT_CLOSED = true, MMOSimca@343: LOOT_OPENED = true, MMOSimca@412: LOOT_SLOT_CLEARED = "HandleBadChatLootData", MMOSimca@436: MAIL_CLOSED = "ResumeChatLootRecording", MMOSimca@436: MAIL_SHOW = true, -- also triggers StopChatLootRecording MMOSimca@436: MERCHANT_CLOSED = true, -- also triggers ResumeChatLootRecording MMOSimca@436: MERCHANT_SHOW = "UpdateMerchantItems", -- also triggers StopChatLootRecording jcallahan@61: MERCHANT_UPDATE = "UpdateMerchantItems", jcallahan@25: PET_BAR_UPDATE = true, MMOSimca@368: --PET_JOURNAL_LIST_UPDATE = true, jcallahan@156: PLAYER_REGEN_DISABLED = true, jcallahan@156: PLAYER_REGEN_ENABLED = true, jcallahan@2: PLAYER_TARGET_CHANGED = true, jcallahan@9: QUEST_COMPLETE = true, jcallahan@9: QUEST_DETAIL = true, jcallahan@9: QUEST_LOG_UPDATE = true, jcallahan@97: QUEST_PROGRESS = true, jcallahan@178: SHOW_LOOT_TOAST = true, jcallahan@306: SPELL_CONFIRMATION_PROMPT = true, jcallahan@88: TAXIMAP_OPENED = true, MMOSimca@437: TRADE_CLOSED = "ResumeChatLootRecording", MMOSimca@437: TRADE_SHOW = "StopChatLootRecording", jcallahan@92: TRADE_SKILL_SHOW = true, jcallahan@167: TRAINER_CLOSED = true, jcallahan@27: TRAINER_SHOW = true, jcallahan@90: TRANSMOGRIFY_OPEN = true, jcallahan@246: UNIT_PET = true, jcallahan@4: UNIT_QUEST_LOG_CHANGED = true, jcallahan@1: UNIT_SPELLCAST_FAILED = "HandleSpellFailure", jcallahan@1: UNIT_SPELLCAST_FAILED_QUIET = "HandleSpellFailure", jcallahan@1: UNIT_SPELLCAST_INTERRUPTED = "HandleSpellFailure", jcallahan@1: UNIT_SPELLCAST_SENT = true, jcallahan@1: UNIT_SPELLCAST_SUCCEEDED = true, jcallahan@90: VOID_STORAGE_OPEN = true, jcallahan@0: } jcallahan@0: jcallahan@4: jcallahan@246: -- VARIABLES ---------------------------------------------------------- jcallahan@246: jcallahan@92: local anvil_spell_ids = {} jcallahan@92: local currently_drunk jcallahan@128: local char_db jcallahan@128: local global_db jcallahan@299: local group_member_guids = {} jcallahan@246: local group_owner_guids_to_pet_guids = {} jcallahan@246: local group_pet_guids = {} jcallahan@299: local in_instance jcallahan@187: local item_process_timer_handle jcallahan@92: local faction_standings = {} jcallahan@92: local forge_spell_ids = {} jcallahan@95: local languages_known = {} jcallahan@317: local boss_loot_toasting = {} MMOSimca@387: local container_loot_toasting MMOSimca@387: local loot_toast_container_id MMOSimca@387: local raid_boss_id jcallahan@306: local loot_toast_container_timer_handle jcallahan@307: local loot_toast_data jcallahan@307: local loot_toast_data_timer_handle jcallahan@95: local name_to_id_map = {} jcallahan@306: local killed_boss_id_timer_handle jcallahan@177: local killed_npc_id jcallahan@2: local target_location_timer_handle MMOSimca@345: local last_timber_spell_id MMOSimca@355: local last_garrison_cache_object_id MMOSimca@436: local block_chat_loot_data MMOSimca@435: local chat_loot_data = {} MMOSimca@347: local chat_loot_timer_handle mmosimca@485: local world_quest_timer_handle mmosimca@485: local world_quest_event_timestamp = 0 jcallahan@86: local current_target_id jcallahan@131: local current_loot jcallahan@1: jcallahan@312: jcallahan@121: -- Data for our current action. Including possible values as a reference. jcallahan@122: local current_action = { jcallahan@121: identifier = nil, jcallahan@121: loot_label = nil, jcallahan@121: loot_list = nil, jcallahan@121: loot_sources = nil, jcallahan@121: map_level = nil, jcallahan@121: spell_label = nil, jcallahan@123: target_type = nil, jcallahan@121: x = nil, jcallahan@121: y = nil, jcallahan@121: zone_data = nil, jcallahan@121: } jcallahan@92: jcallahan@246: MMOSimca@393: -- Timer prototypes MMOSimca@393: local ClearKilledNPC, ClearKilledBossID, ClearLootToastContainerID, ClearLootToastData, ClearChatLootData MMOSimca@393: MMOSimca@393: jcallahan@246: -- HELPERS ------------------------------------------------------------ jcallahan@246: jcallahan@245: local function Debug(message, ...) MMOSimca@350: if not DEBUGGING or not message then jcallahan@151: return jcallahan@151: end catherton@465: MMOSimca@350: if ... then MMOSimca@350: local args = { ... } MMOSimca@350: MMOSimca@350: for index = 1, #args do MMOSimca@377: args[index] = tostring(args[index]) jcallahan@306: end MMOSimca@350: _G.print(message:format(unpack(args))) MMOSimca@350: else MMOSimca@350: _G.print(message) jcallahan@306: end jcallahan@151: end jcallahan@151: jcallahan@151: MMOSimca@393: local function InitializeCurrentLoot() MMOSimca@393: current_loot = { MMOSimca@393: list = {}, MMOSimca@393: sources = {}, MMOSimca@393: identifier = current_action.identifier, MMOSimca@393: label = current_action.loot_label or "drops", MMOSimca@393: map_level = current_action.map_level, MMOSimca@393: object_name = current_action.object_name, MMOSimca@393: spell_label = current_action.spell_label, MMOSimca@393: target_type = current_action.target_type, MMOSimca@393: x = current_action.x, MMOSimca@393: y = current_action.y, MMOSimca@393: zone_data = current_action.zone_data, MMOSimca@393: } MMOSimca@393: MMOSimca@393: table.wipe(current_action) MMOSimca@393: end MMOSimca@393: MMOSimca@393: jcallahan@169: local TradeSkillExecutePer jcallahan@169: do jcallahan@169: local header_list = {} jcallahan@169: jcallahan@169: function TradeSkillExecutePer(iter_func) jcallahan@169: if not _G.TradeSkillFrame or not _G.TradeSkillFrame:IsVisible() then jcallahan@169: return jcallahan@169: end catherton@465: catherton@479: local recipes = _G.C_TradeSkillUI.GetAllRecipeIDs() catherton@479: catherton@479: if recipes and (#recipes > 0) then catherton@479: for i = 1, #recipes do catherton@479: if iter_func(_G.C_TradeSkillUI.GetRecipeInfo(recipes[i]).name, recipes[i]) then catherton@465: break catherton@465: end catherton@465: end catherton@465: end jcallahan@167: end jcallahan@169: end -- do-block jcallahan@167: jcallahan@167: jcallahan@39: local ActualCopperCost jcallahan@39: do jcallahan@39: local BARTERING_SPELL_ID = 83964 jcallahan@39: jcallahan@39: local STANDING_DISCOUNTS = { jcallahan@39: HATED = 0, jcallahan@39: HOSTILE = 0, jcallahan@39: UNFRIENDLY = 0, jcallahan@39: NEUTRAL = 0, jcallahan@39: FRIENDLY = 0.05, jcallahan@39: HONORED = 0.1, jcallahan@39: REVERED = 0.15, jcallahan@39: EXALTED = 0.2, jcallahan@39: } jcallahan@39: jcallahan@39: jcallahan@39: function ActualCopperCost(copper_cost, rep_standing) jcallahan@39: if not copper_cost or copper_cost == 0 then jcallahan@39: return 0 jcallahan@39: end jcallahan@39: local modifier = 1 jcallahan@39: jcallahan@39: if _G.IsSpellKnown(BARTERING_SPELL_ID) then jcallahan@39: modifier = modifier - 0.1 jcallahan@39: end jcallahan@39: jcallahan@39: if rep_standing then jcallahan@39: if PLAYER_RACE == "Goblin" then jcallahan@39: modifier = modifier - STANDING_DISCOUNTS["EXALTED"] jcallahan@39: elseif STANDING_DISCOUNTS[rep_standing] then jcallahan@39: modifier = modifier - STANDING_DISCOUNTS[rep_standing] jcallahan@39: end jcallahan@39: end jcallahan@39: return math.floor(copper_cost / modifier) jcallahan@39: end jcallahan@39: end -- do-block jcallahan@39: jcallahan@39: jcallahan@29: local function InstanceDifficultyToken() MMOSimca@440: -- Sometimes, instance information is returned when not in an instance. This check protects against that. MMOSimca@440: if _G.IsInInstance() then MMOSimca@440: local _, instance_type, instance_difficulty, _, _, _, is_dynamic = _G.GetInstanceInfo() MMOSimca@440: MMOSimca@440: if not instance_type or instance_type == "" then MMOSimca@440: instance_type = "NONE" MMOSimca@440: end MMOSimca@440: return ("%s:%d:%s"):format(instance_type:upper(), instance_difficulty, tostring(is_dynamic)) jcallahan@59: end MMOSimca@440: return "NONE:0:false" jcallahan@29: end jcallahan@29: jcallahan@29: jcallahan@1: local function CurrentLocationData() mmosimca@488: local x, y, current_area_id, map_level, map_file, is_micro_dungeon = HereBeDragons:GetPlayerZonePosition(false) catherton@468: local zone_name = _G.GetRealZoneText() catherton@465: mmosimca@488: -- Remove micro-dungeon-ness by translating back to the parent world map (at floor 0) if possible mmosimca@488: if (is_micro_dungeon and x and y and current_area_id and map_level and map_level > 0) then mmosimca@488: x, y = HereBeDragons:TranslateZoneCoordinates(x, y, current_area_id, map_level, current_area_id, 0, false) mmosimca@488: map_level = 0 mmosimca@488: end mmosimca@488: catherton@465: -- Put coordinates into expected format (as integers, they don't get a billion decimals output in the SavedVariables) catherton@468: local x_int = nil catherton@468: if (x and type(x) == "number") then catherton@468: x_int = _G.floor(x * 1000) mmosimca@485: mmosimca@482: -- Limit precision to 0.2 catherton@468: if x_int % 2 ~= 0 then catherton@468: x_int = x_int + 1 catherton@468: end mmosimca@485: mmosimca@482: -- Prevent out of bounds coordinates mmosimca@482: if (x_int < 0 or x_int > 1000) then mmosimca@482: x_int = nil mmosimca@482: end jcallahan@145: end catherton@468: local y_int = nil catherton@468: if (y and type(y) == "number") then catherton@468: y_int = _G.floor(y * 1000) mmosimca@485: mmosimca@482: -- Limit precision to 0.2 catherton@468: if y_int % 2 ~= 0 then catherton@468: y_int = y_int + 1 catherton@468: end mmosimca@485: mmosimca@482: -- Prevent out of bounds coordinates mmosimca@482: if (y_int < 0 or y_int > 1000) then mmosimca@482: y_int = nil mmosimca@482: end jcallahan@1: end jcallahan@1: catherton@468: return zone_name, current_area_id, x_int, y_int, map_level, InstanceDifficultyToken() jcallahan@1: end jcallahan@1: jcallahan@1: MMOSimca@441: local function DBEntry(data_type, unit_id) MMOSimca@441: if not data_type or not unit_id then jcallahan@312: return jcallahan@312: end MMOSimca@441: local category = global_db[data_type] MMOSimca@441: MMOSimca@441: if not category then MMOSimca@441: category = {} MMOSimca@441: global_db[data_type] = category MMOSimca@441: end MMOSimca@441: local unit = category[unit_id] MMOSimca@441: MMOSimca@441: if not unit then MMOSimca@441: unit = {} MMOSimca@441: category[unit_id] = unit MMOSimca@441: end MMOSimca@441: return unit jcallahan@312: end jcallahan@312: MMOSimca@441: private.DBEntry = DBEntry MMOSimca@441: MMOSimca@441: local NPCEntry MMOSimca@441: do MMOSimca@441: local npc_prototype = {} MMOSimca@441: local npc_meta = { MMOSimca@441: __index = npc_prototype MMOSimca@441: } MMOSimca@441: MMOSimca@441: function NPCEntry(identifier) MMOSimca@441: local npc = DBEntry("npcs", identifier) MMOSimca@441: return npc and _G.setmetatable(npc, npc_meta) or nil jcallahan@1: end MMOSimca@441: MMOSimca@441: function npc_prototype:EncounterData(difficulty_token) MMOSimca@441: self.encounter_data = self.encounter_data or {} MMOSimca@441: self.encounter_data[difficulty_token] = self.encounter_data[difficulty_token] or {} MMOSimca@441: self.encounter_data[difficulty_token].stats = self.encounter_data[difficulty_token].stats or {} MMOSimca@441: MMOSimca@441: return self.encounter_data[difficulty_token] MMOSimca@441: end jcallahan@1: end jcallahan@270: jcallahan@4: jcallahan@141: local UpdateDBEntryLocation jcallahan@141: do jcallahan@141: -- Fishing node coordinate code based on code in GatherMate2 with permission from Kagaro. jcallahan@141: local function FishingCoordinates(x, y, yard_width, yard_height) jcallahan@141: local facing = _G.GetPlayerFacing() jcallahan@141: jcallahan@141: if not facing then jcallahan@141: return x, y jcallahan@141: end jcallahan@246: local rad = facing + math.pi jcallahan@141: return x + math.sin(rad) * 15 / yard_width, y + math.cos(rad) * 15 / yard_height jcallahan@10: end jcallahan@10: jcallahan@24: jcallahan@141: function UpdateDBEntryLocation(entry_type, identifier) jcallahan@141: if not identifier then jcallahan@141: return jcallahan@141: end jcallahan@141: local zone_name, area_id, x, y, map_level, difficulty_token = CurrentLocationData() MMOSimca@328: if not (zone_name and area_id and x and y and map_level) then catherton@468: Debug("UpdateDBEntryLocation: Missing current location data - %s, %s, %s, %s, %s.", tostring(zone_name), tostring(area_id), tostring(x), tostring(y), tostring(map_level)) MMOSimca@328: return MMOSimca@328: end jcallahan@141: local entry = DBEntry(entry_type, identifier) jcallahan@141: entry[difficulty_token] = entry[difficulty_token] or {} jcallahan@141: entry[difficulty_token].locations = entry[difficulty_token].locations or {} jcallahan@141: jcallahan@141: local zone_token = ("%s:%d"):format(zone_name, area_id) jcallahan@141: local zone_data = entry[difficulty_token].locations[zone_token] jcallahan@141: jcallahan@141: if not zone_data then jcallahan@141: zone_data = {} jcallahan@141: entry[difficulty_token].locations[zone_token] = zone_data jcallahan@141: end jcallahan@141: jcallahan@141: -- Special case for Fishing. jcallahan@141: if current_action.spell_label == "FISHING" then MMOSimca@441: local _, qx, qy = _G.GetWorldLocFromMapPos(0, 0) MMOSimca@441: local _, wx, wy = _G.GetWorldLocFromMapPos(1, 1) MMOSimca@441: local yard_width, yard_height = qy - wy, qx - wx jcallahan@141: jcallahan@141: if yard_width > 0 and yard_height > 0 then jcallahan@141: x, y = FishingCoordinates(x, y, yard_width, yard_height) jcallahan@141: current_action.x = x jcallahan@141: current_action.y = y jcallahan@141: end jcallahan@141: end jcallahan@141: local location_token = ("%d:%d:%d"):format(map_level, x, y) jcallahan@141: jcallahan@141: zone_data[location_token] = zone_data[location_token] or true jcallahan@141: return zone_data jcallahan@10: end jcallahan@141: end -- do-block jcallahan@10: jcallahan@10: MMOSimca@441: local function CurrencyLinkToTexture(currency_link) MMOSimca@441: if not currency_link then MMOSimca@441: return MMOSimca@441: end MMOSimca@441: local _, _, texture_path = _G.GetCurrencyInfo(tonumber(currency_link:match("currency:(%d+)"))) MMOSimca@441: return texture_path:match("[^\\]+$"):lower() MMOSimca@441: end MMOSimca@441: MMOSimca@441: MMOSimca@441: local function ItemLinkToID(item_link) MMOSimca@441: if not item_link then MMOSimca@441: return MMOSimca@441: end MMOSimca@441: return tonumber(tostring(item_link):match("item:(%d+)")) MMOSimca@441: end MMOSimca@441: MMOSimca@441: private.ItemLinkToID = ItemLinkToID MMOSimca@441: MMOSimca@441: local function UnitTypeIsNPC(unit_type) MMOSimca@441: return unit_type == private.UNIT_TYPES.NPC or unit_type == private.UNIT_TYPES.VEHICLE MMOSimca@441: end MMOSimca@441: MMOSimca@441: MMOSimca@441: local ParseGUID MMOSimca@441: do MMOSimca@441: local UNIT_TYPES = private.UNIT_TYPES MMOSimca@441: MMOSimca@441: local NPC_ID_MAPPING = { MMOSimca@441: [62164] = 63191, -- Garalon MMOSimca@441: } MMOSimca@441: MMOSimca@441: MMOSimca@441: local function MatchUnitTypes(unit_type_name) MMOSimca@441: if not unit_type_name then MMOSimca@441: return UNIT_TYPES.UNKNOWN MMOSimca@441: end MMOSimca@441: MMOSimca@441: for def, text in next, UNIT_TYPES do MMOSimca@441: if unit_type_name == text then MMOSimca@441: return UNIT_TYPES[def] MMOSimca@441: end MMOSimca@441: end MMOSimca@441: return UNIT_TYPES.UNKNOWN MMOSimca@441: end MMOSimca@441: MMOSimca@441: MMOSimca@441: function ParseGUID(guid) MMOSimca@441: if not guid then MMOSimca@441: return MMOSimca@441: end MMOSimca@441: MMOSimca@441: -- We might want to use some of this new information later, but leaving the returns alone for now MMOSimca@441: local unit_type_name, unk_id1, server_id, instance_id, unk_id2, unit_idnum, spawn_id = ("-"):split(guid) MMOSimca@441: MMOSimca@441: local unit_type = MatchUnitTypes(unit_type_name) MMOSimca@441: if unit_type ~= UNIT_TYPES.PLAYER and unit_type ~= UNIT_TYPES.PET and unit_type ~= UNIT_TYPES.ITEM then MMOSimca@441: MMOSimca@441: local id_mapping = NPC_ID_MAPPING[unit_idnum] MMOSimca@441: MMOSimca@441: if id_mapping and UnitTypeIsNPC(unit_type) then MMOSimca@441: unit_idnum = id_mapping MMOSimca@441: end MMOSimca@441: return unit_type, unit_idnum MMOSimca@441: end MMOSimca@441: return unit_type MMOSimca@441: end MMOSimca@441: MMOSimca@441: private.ParseGUID = ParseGUID MMOSimca@441: end -- do-block MMOSimca@441: MMOSimca@441: jcallahan@19: local function HandleItemUse(item_link, bag_index, slot_index) jcallahan@19: if not item_link then jcallahan@19: return jcallahan@19: end jcallahan@19: local item_id = ItemLinkToID(item_link) jcallahan@19: jcallahan@19: if not bag_index or not slot_index then jcallahan@19: for new_bag_index = 0, _G.NUM_BAG_FRAMES do jcallahan@19: for new_slot_index = 1, _G.GetContainerNumSlots(new_bag_index) do jcallahan@19: if item_id == ItemLinkToID(_G.GetContainerItemLink(new_bag_index, new_slot_index)) then jcallahan@19: bag_index = new_bag_index jcallahan@19: slot_index = new_slot_index jcallahan@19: break jcallahan@19: end jcallahan@19: end jcallahan@19: end jcallahan@19: end jcallahan@19: MMOSimca@410: local any_loot = false MMOSimca@410: MMOSimca@411: -- Check if Blizzard has marked this item as officially having a chance of containing loot MMOSimca@410: if bag_index and slot_index then MMOSimca@410: local _, _, _, _, _, is_lootable = _G.GetContainerItemInfo(bag_index, slot_index) MMOSimca@410: if is_lootable then MMOSimca@410: any_loot = true MMOSimca@410: end jcallahan@19: end catherton@465: MMOSimca@410: -- Check if we've marked this item as one Blizzard provides bad is_lootable data for MMOSimca@410: if private.CONTAINER_ITEM_ID_LIST[item_id] ~= nil then MMOSimca@410: any_loot = true jcallahan@19: end MMOSimca@368: MMOSimca@436: -- Going to block 'chat-loot data' at this level for now because I don't think we actually want normal item containers being recorded in these scenarios either. MMOSimca@436: if any_loot and not block_chat_loot_data then MMOSimca@414: -- For item containers that open instantly with no spell cast MMOSimca@410: if (private.CONTAINER_ITEM_ID_LIST[item_id] == true) and ((not _G.GetNumLootItems()) or (_G.GetNumLootItems() == 0)) then MMOSimca@410: ClearChatLootData() MMOSimca@410: Debug("HandleItemUse: Beginning chat-based loot timer for item with ID %d.", item_id) MMOSimca@423: chat_loot_timer_handle = C_Timer.NewTimer(1.5, ClearChatLootData) MMOSimca@414: chat_loot_data.identifier = item_id MMOSimca@414: -- For normal item containers MMOSimca@414: else MMOSimca@414: table.wipe(current_action) MMOSimca@414: current_loot = nil MMOSimca@414: current_action.target_type = AF.ITEM MMOSimca@414: current_action.identifier = item_id MMOSimca@414: current_action.loot_label = "contains" MMOSimca@410: end MMOSimca@393: end jcallahan@19: end jcallahan@19: jcallahan@19: jcallahan@39: local UnitFactionStanding jcallahan@32: local UpdateFactionData jcallahan@32: do jcallahan@32: local MAX_FACTION_INDEX = 1000 jcallahan@20: jcallahan@32: local STANDING_NAMES = { jcallahan@32: "HATED", jcallahan@32: "HOSTILE", jcallahan@32: "UNFRIENDLY", jcallahan@32: "NEUTRAL", jcallahan@32: "FRIENDLY", jcallahan@32: "HONORED", jcallahan@32: "REVERED", jcallahan@32: "EXALTED", jcallahan@32: } jcallahan@32: jcallahan@39: jcallahan@39: function UnitFactionStanding(unit) jcallahan@135: local unit_name = _G.UnitName(unit) jcallahan@39: UpdateFactionData() jcallahan@39: DatamineTT:ClearLines() jcallahan@39: DatamineTT:SetUnit(unit) jcallahan@39: jcallahan@39: for line_index = 1, DatamineTT:NumLines() do jcallahan@64: local faction_name = _G["WDPDatamineTTTextLeft" .. line_index]:GetText():trim() jcallahan@39: jcallahan@135: if faction_name and faction_name ~= unit_name and faction_standings[faction_name] then jcallahan@39: return faction_name, faction_standings[faction_name] jcallahan@39: end jcallahan@39: end jcallahan@39: end jcallahan@39: jcallahan@39: jcallahan@32: function UpdateFactionData() jcallahan@32: for faction_index = 1, MAX_FACTION_INDEX do jcallahan@32: local faction_name, _, current_standing, _, _, _, _, _, is_header = _G.GetFactionInfo(faction_index) jcallahan@32: jcallahan@86: if faction_name then jcallahan@32: faction_standings[faction_name] = STANDING_NAMES[current_standing] jcallahan@32: elseif not faction_name then jcallahan@32: break jcallahan@32: end jcallahan@20: end jcallahan@20: end jcallahan@32: end -- do-block jcallahan@20: jcallahan@48: MMOSimca@429: local GenericLootUpdate, LootTable jcallahan@75: do MMOSimca@429: function LootTable(entry, loot_type, top_field) jcallahan@75: if top_field then jcallahan@75: entry[top_field] = entry[top_field] or {} jcallahan@75: entry[top_field][loot_type] = entry[top_field][loot_type] or {} jcallahan@75: return entry[top_field][loot_type] jcallahan@75: end jcallahan@48: entry[loot_type] = entry[loot_type] or {} jcallahan@75: return entry[loot_type] jcallahan@48: end jcallahan@48: jcallahan@75: function GenericLootUpdate(data_type, top_field) jcallahan@132: local loot_type = current_loot.label jcallahan@75: local loot_count = ("%s_count"):format(loot_type) jcallahan@77: local source_list = {} jcallahan@75: jcallahan@131: if current_loot.sources then jcallahan@131: for source_guid, loot_data in pairs(current_loot.sources) do jcallahan@304: local source_id jcallahan@78: jcallahan@131: if current_loot.target_type == AF.ITEM then jcallahan@119: -- Items return the player as the source, so we need to use the item's ID (disenchant, milling, etc) jcallahan@131: source_id = current_loot.identifier jcallahan@119: else jcallahan@331: local _, unit_ID = ParseGUID(source_guid) MMOSimca@328: if unit_ID then MMOSimca@328: if current_loot.target_type == AF.OBJECT then MMOSimca@328: source_id = ("%s:%s"):format(current_loot.spell_label, unit_ID) MMOSimca@328: else MMOSimca@328: source_id = unit_ID MMOSimca@328: end MMOSimca@328: end jcallahan@119: end jcallahan@304: local entry = DBEntry(data_type, source_id) jcallahan@75: jcallahan@119: if entry then jcallahan@119: local loot_table = LootTable(entry, loot_type, top_field) jcallahan@77: jcallahan@304: if not source_list[source_id] then jcallahan@119: if top_field then jcallahan@119: entry[top_field][loot_count] = (entry[top_field][loot_count] or 0) + 1 MMOSimca@387: elseif not container_loot_toasting then jcallahan@119: entry[loot_count] = (entry[loot_count] or 0) + 1 jcallahan@119: end jcallahan@304: source_list[source_id] = true jcallahan@77: end jcallahan@119: UpdateDBEntryLocation(data_type, source_id) jcallahan@75: jcallahan@309: if current_loot.target_type == AF.ZONE then jcallahan@309: for item_id, quantity in pairs(loot_data) do jcallahan@309: table.insert(loot_table, ("%d:%d"):format(item_id, quantity)) jcallahan@309: end jcallahan@309: else jcallahan@308: for loot_token, quantity in pairs(loot_data) do jcallahan@308: local label, currency_texture = (":"):split(loot_token) jcallahan@308: jcallahan@308: if label == "currency" and currency_texture then jcallahan@308: table.insert(loot_table, ("currency:%d:%s"):format(quantity, currency_texture)) jcallahan@308: elseif loot_token == "money" then jcallahan@308: table.insert(loot_table, ("money:%d"):format(quantity)) jcallahan@308: else jcallahan@308: table.insert(loot_table, ("%d:%d"):format(loot_token, quantity)) jcallahan@308: end jcallahan@308: end jcallahan@119: end jcallahan@75: end jcallahan@75: end jcallahan@75: end jcallahan@121: jcallahan@121: -- This is used for Gas Extractions. jcallahan@131: if #current_loot.list <= 0 then jcallahan@78: return jcallahan@78: end jcallahan@82: local entry jcallahan@82: jcallahan@82: -- At this point we only have a name if it's an object. MMOSimca@388: -- (As of 5.x, the above statement is almost never true, but there are a few cases, like gas extractions.) jcallahan@131: if current_loot.target_type == AF.OBJECT then jcallahan@131: entry = DBEntry(data_type, ("%s:%s"):format(current_loot.spell_label, current_loot.object_name)) jcallahan@82: else jcallahan@131: entry = DBEntry(data_type, current_loot.identifier) jcallahan@82: end jcallahan@75: jcallahan@75: if not entry then jcallahan@75: return jcallahan@75: end jcallahan@77: local loot_table = LootTable(entry, loot_type, top_field) jcallahan@77: jcallahan@307: if current_loot.identifier then jcallahan@307: if not source_list[current_loot.identifier] then jcallahan@307: if top_field then jcallahan@307: entry[top_field][loot_count] = (entry[top_field][loot_count] or 0) + 1 jcallahan@307: else jcallahan@307: entry[loot_count] = (entry[loot_count] or 0) + 1 jcallahan@307: end jcallahan@307: source_list[current_loot.identifier] = true jcallahan@77: end jcallahan@77: end jcallahan@75: jcallahan@131: for index = 1, #current_loot.list do jcallahan@131: table.insert(loot_table, current_loot.list[index]) jcallahan@75: end jcallahan@48: end jcallahan@75: end -- do-block jcallahan@48: jcallahan@97: jcallahan@97: local ReplaceKeywords jcallahan@97: do jcallahan@97: local KEYWORD_SUBSTITUTIONS = { jcallahan@97: class = PLAYER_CLASS, jcallahan@97: name = PLAYER_NAME, jcallahan@97: race = PLAYER_RACE, jcallahan@97: } jcallahan@97: jcallahan@97: jcallahan@97: function ReplaceKeywords(text) jcallahan@97: if not text or text == "" then jcallahan@97: return "" jcallahan@97: end jcallahan@97: jcallahan@97: for category, lookup in pairs(KEYWORD_SUBSTITUTIONS) do jcallahan@97: local category_format = ("<%s>"):format(category) jcallahan@97: text = text:gsub(lookup, category_format):gsub(lookup:lower(), category_format) jcallahan@97: end jcallahan@97: return text jcallahan@97: end jcallahan@97: end -- do-block jcallahan@97: jcallahan@97: MMOSimca@347: -- TIMERS ------------------------------------------------------------- MMOSimca@347: MMOSimca@393: function ClearKilledNPC() MMOSimca@347: killed_npc_id = nil MMOSimca@347: end MMOSimca@347: MMOSimca@347: MMOSimca@393: function ClearKilledBossID() MMOSimca@347: if killed_boss_id_timer_handle then MMOSimca@383: killed_boss_id_timer_handle:Cancel() MMOSimca@347: killed_boss_id_timer_handle = nil MMOSimca@347: end MMOSimca@347: MMOSimca@347: table.wipe(boss_loot_toasting) MMOSimca@387: raid_boss_id = nil MMOSimca@347: end MMOSimca@347: MMOSimca@347: MMOSimca@393: function ClearLootToastContainerID() MMOSimca@347: if loot_toast_container_timer_handle then MMOSimca@383: loot_toast_container_timer_handle:Cancel() MMOSimca@347: loot_toast_container_timer_handle = nil MMOSimca@347: end MMOSimca@347: MMOSimca@387: container_loot_toasting = false MMOSimca@387: loot_toast_container_id = nil MMOSimca@347: end MMOSimca@347: MMOSimca@347: MMOSimca@393: function ClearLootToastData() MMOSimca@347: if loot_toast_data_timer_handle then MMOSimca@383: loot_toast_data_timer_handle:Cancel() MMOSimca@347: loot_toast_data_timer_handle = nil MMOSimca@347: end MMOSimca@347: MMOSimca@347: if loot_toast_data then MMOSimca@347: table.wipe(loot_toast_data) MMOSimca@347: end MMOSimca@347: end MMOSimca@347: MMOSimca@347: MMOSimca@393: function ClearChatLootData() MMOSimca@398: if not chat_loot_timer_handle then MMOSimca@435: table.wipe(chat_loot_data) MMOSimca@398: return MMOSimca@398: end MMOSimca@398: Debug("ClearChatLootData: Ending chat-based loot timer.") MMOSimca@398: chat_loot_timer_handle:Cancel() MMOSimca@398: chat_loot_timer_handle = nil MMOSimca@398: MMOSimca@435: if chat_loot_data.identifier and (private.CONTAINER_ITEM_ID_LIST[chat_loot_data.identifier] ~= nil) and chat_loot_data.loot then MMOSimca@414: -- A slimmed down (and more importantly, separate) version of GenericLootUpdate, specifically for AF.ITEM and chat_loot_data MMOSimca@414: local entry = DBEntry("items", chat_loot_data.identifier) MMOSimca@414: MMOSimca@414: if entry then MMOSimca@414: local loot_table = LootTable(entry, "contains") MMOSimca@414: entry["contains_count"] = (entry["contains_count"] or 0) + 1 MMOSimca@414: MMOSimca@435: for loot_token, quantity in pairs(chat_loot_data.loot) do MMOSimca@414: local label, currency_texture = (":"):split(loot_token) MMOSimca@414: MMOSimca@414: if label == "currency" and currency_texture then MMOSimca@414: table.insert(loot_table, ("currency:%d:%s"):format(quantity, currency_texture)) MMOSimca@414: elseif loot_token == "money" then MMOSimca@414: table.insert(loot_table, ("money:%d"):format(quantity)) MMOSimca@414: else MMOSimca@414: table.insert(loot_table, ("%d:%d"):format(loot_token, quantity)) MMOSimca@414: end MMOSimca@414: end MMOSimca@414: end MMOSimca@347: end MMOSimca@435: table.wipe(chat_loot_data) MMOSimca@347: end MMOSimca@347: MMOSimca@347: jcallahan@246: -- METHODS ------------------------------------------------------------ jcallahan@246: jcallahan@0: function WDP:OnInitialize() jcallahan@128: local db = LibStub("AceDB-3.0"):New("WoWDBProfilerData", DATABASE_DEFAULTS, "Default") jcallahan@270: private.db = db jcallahan@128: global_db = db.global jcallahan@128: char_db = db.char jcallahan@14: jcallahan@270: local raw_db = _G.WoWDBProfilerData jcallahan@18: local build_num = tonumber(private.build_num) mmosimca@485: mmosimca@484: -- Get current region from LibRealmInfo (and account for the fact that PTR and Beta return nil) mmosimca@484: local current_region = LibRealmInfo:GetCurrentRegion() or "XX" mmosimca@484: mmosimca@484: -- Wipe all data if DB version or build number changed jcallahan@136: if (raw_db.version and raw_db.version < DB_VERSION) or (raw_db.build_num and raw_db.build_num < build_num) then jcallahan@74: for entry in pairs(DATABASE_DEFAULTS.global) do jcallahan@128: global_db[entry] = {} jcallahan@74: end jcallahan@74: end mmosimca@485: -- Wipe World Quest data if region changed mmosimca@485: if raw_db.region and raw_db.region ~= current_region and global_db["world_quests"] then mmosimca@485: global_db["world_quests"] = {} mmosimca@485: end mmosimca@485: jcallahan@35: raw_db.build_num = build_num mmosimca@484: raw_db.region = current_region jcallahan@63: raw_db.version = DB_VERSION jcallahan@249: jcallahan@312: private.InitializeCommentSystem() jcallahan@312: self:RegisterChatCommand("comment", private.ProcessCommentCommand) jcallahan@0: end jcallahan@0: jcallahan@0: jcallahan@153: function WDP:EventDispatcher(...) jcallahan@153: local event_name = ... jcallahan@153: MMOSimca@346: if DEBUGGING then jcallahan@154: if event_name == "COMBAT_LOG_EVENT_UNFILTERED" then jcallahan@154: Debug(event_name) jcallahan@154: else jcallahan@154: Debug(...) jcallahan@153: end jcallahan@153: end jcallahan@153: local func = EVENT_MAPPING[event_name] jcallahan@153: jcallahan@153: if _G.type(func) == "boolean" then jcallahan@153: self[event_name](self, ...) jcallahan@153: elseif _G.type(func) == "function" then jcallahan@159: self[func](self, ...) jcallahan@153: end jcallahan@153: end jcallahan@153: jcallahan@153: jcallahan@0: function WDP:OnEnable() jcallahan@300: PLAYER_GUID = _G.UnitGUID("player") jcallahan@300: jcallahan@0: for event_name, mapping in pairs(EVENT_MAPPING) do jcallahan@156: if EVENT_DEBUG then jcallahan@153: self:RegisterEvent(event_name, "EventDispatcher") jcallahan@153: else jcallahan@153: self:RegisterEvent(event_name, (_G.type(mapping) ~= "boolean") and mapping or nil) jcallahan@153: end jcallahan@0: end jcallahan@95: jcallahan@95: for index = 1, _G.GetNumLanguages() do jcallahan@95: languages_known[_G.GetLanguageByIndex(index)] = true jcallahan@95: end MMOSimca@383: mmosimca@485: -- These timers loop indefinitely using Lua's infinity constant mmosimca@485: item_process_timer_handle = C_Timer.NewTicker(DELAY_PROCESS_ITEMS, WDP.ProcessItems, math.huge) mmosimca@485: target_location_timer_handle = C_Timer.NewTicker(DELAY_UPDATE_TARGET_LOCATION, WDP.UpdateTargetLocation, math.huge) mmosimca@485: world_quest_timer_handle = C_Timer.NewTicker(DELAY_PROCESS_WORLD_QUESTS, WDP.ProcessWorldQuests, math.huge) jcallahan@19: jcallahan@19: _G.hooksecurefunc("UseContainerItem", function(bag_index, slot_index, target_unit) jcallahan@19: if target_unit then jcallahan@19: return jcallahan@19: end jcallahan@19: HandleItemUse(_G.GetContainerItemLink(bag_index, slot_index), bag_index, slot_index) jcallahan@19: end) jcallahan@19: jcallahan@19: _G.hooksecurefunc("UseItemByName", function(identifier, target_unit) jcallahan@19: if target_unit then jcallahan@19: return jcallahan@19: end jcallahan@19: local _, item_link = _G.GetItemInfo(identifier) jcallahan@19: HandleItemUse(item_link) jcallahan@19: end) jcallahan@263: jcallahan@290: self:GROUP_ROSTER_UPDATE() jcallahan@0: end jcallahan@0: jcallahan@0: mmosimca@485: -- Record data for a specific quest ID; reward data must be available or nothing will be recorded mmosimca@485: -- When we reach this point, we've already checked for a valid mapID, questID, quest data, and worldQuestType mmosimca@485: local function RecordWorldQuestData(world_map_id, quest_id, api_data_table) mmosimca@485: mmosimca@485: -- Ensure we have location data and rewards (barely readable so putting it on multiple lines) mmosimca@487: -- (Honor is built in to the quest; it is not a sign rewards have been loaded) mmosimca@485: if not api_data_table.x or not api_data_table.y or not api_data_table.floor or not mmosimca@485: (_G.GetQuestLogRewardXP(quest_id) > 0 or _G.GetNumQuestLogRewardCurrencies(quest_id) > 0 mmosimca@485: or _G.GetNumQuestLogRewards(quest_id) > 0 or _G.GetQuestLogRewardMoney(quest_id) > 0 mmosimca@487: or _G.GetQuestLogRewardArtifactXP(quest_id) > 0) then mmosimca@485: return mmosimca@485: end mmosimca@485: mmosimca@485: local entry = DBEntry("world_quests", quest_id) mmosimca@485: mmosimca@485: if entry then mmosimca@485: mmosimca@485: -- Record location mmosimca@485: entry["location"] = {} mmosimca@485: entry["location"]["world_map_id"] = world_map_id mmosimca@485: entry["location"]["x"] = (tonumber(api_data_table.x) or 0) * 100 mmosimca@485: entry["location"]["y"] = (tonumber(api_data_table.y) or 0) * 100 mmosimca@485: entry["location"]["floor"] = tonumber(api_data_table.floor) or 0 mmosimca@485: mmosimca@485: -- Record simple rewards (XP, money, artifact XP, honor) mmosimca@485: entry["rewards"] = {} mmosimca@485: entry["rewards"]["xp"] = tonumber(_G.GetQuestLogRewardXP(quest_id)) or 0 mmosimca@485: entry["rewards"]["money"] = tonumber(_G.GetQuestLogRewardMoney(quest_id)) or 0 mmosimca@485: local actualXP, scaling = _G.GetQuestLogRewardArtifactXP(quest_id) mmosimca@485: entry["rewards"]["artifact_xp"] = ("%d:%d"):format(tonumber(actualXP) or 0, tonumber(scaling) or 0) mmosimca@485: entry["rewards"]["honor"] = tonumber(_G.GetQuestLogRewardHonor(quest_id)) or 0 mmosimca@485: mmosimca@485: -- Record currencies mmosimca@485: entry["rewards"]["currency_count"] = tonumber(_G.GetNumQuestLogRewardCurrencies(quest_id)) or 0 mmosimca@485: mmosimca@485: if entry["rewards"]["currency_count"] > 0 then mmosimca@485: mmosimca@485: -- Create currency rewards sub-table and fill mmosimca@485: entry["rewards"]["currencies"] = {} mmosimca@485: for i = 1, entry["rewards"]["currency_count"] do mmosimca@485: local name, texture_path, quantity = _G.GetQuestLogRewardCurrencyInfo(i, quest_id) mmosimca@485: local currency_texture = texture_path:match("[^\\]+$"):lower() mmosimca@485: table.insert(entry["rewards"]["currencies"], ("%d:%s"):format(quantity, currency_texture)) mmosimca@485: end mmosimca@485: end mmosimca@485: mmosimca@485: -- Record items mmosimca@485: entry["rewards"]["item_count"] = tonumber(_G.GetNumQuestLogRewards(quest_id)) or 0 mmosimca@485: mmosimca@485: if entry["rewards"]["item_count"] > 0 then mmosimca@485: mmosimca@485: -- Create item rewards sub-table and fill mmosimca@485: entry["rewards"]["items"] = {} mmosimca@485: for i = 1, entry["rewards"]["item_count"] do mmosimca@485: local item_name, item_texture, quantity, quality, is_usable, item_id = _G.GetQuestLogRewardInfo(i, quest_id) mmosimca@485: table.insert(entry["rewards"]["items"], ("%d:%d"):format(item_id, quantity)) mmosimca@485: end mmosimca@485: end mmosimca@485: mmosimca@485: -- Record time remaining mmosimca@485: entry["estimated_end_time"] = _G.GetServerTime() + ((_G.C_TaskQuest.GetQuestTimeLeftMinutes(quest_id) or 0) * 60) mmosimca@485: end mmosimca@485: end mmosimca@485: mmosimca@485: mmosimca@485: function WDP:ProcessWorldQuests() mmosimca@485: -- Ignore if player is low level mmosimca@485: if _G.UnitLevel("player") ~= 110 then return end mmosimca@485: mmosimca@485: local current_world_map_id = _G.GetCurrentMapAreaID() mmosimca@485: mmosimca@485: -- Iterate over known World Quest maps mmosimca@485: for i = 1, #private.WORLD_QUEST_MAP_IDS do mmosimca@485: local world_map_id = private.WORLD_QUEST_MAP_IDS[i] mmosimca@485: mmosimca@485: -- Only bother checking the API if the world map in question is currently displayed OR its continent is currently displayed mmosimca@485: if current_world_map_id == WORLD_MAP_ID_BROKEN_ISLES or current_world_map_id == world_map_id then mmosimca@485: mmosimca@485: -- Get data for World Quests on map mmosimca@485: local api_data = _G.C_TaskQuest.GetQuestsForPlayerByMapID(world_map_id) mmosimca@485: mmosimca@485: -- Iterate over the questIDs for each map, doing preload reward requests and creating SavedVariables entries mmosimca@485: if api_data and type(api_data) == "table" and #api_data > 0 then mmosimca@485: for j = 1, #api_data do mmosimca@485: local current_data = api_data[j] mmosimca@485: mmosimca@485: -- Check if we had a valid API table returned to us mmosimca@485: if current_data and type(current_data) == "table" and current_data["questId"] then mmosimca@485: local quest_id = tonumber(current_data["questId"]) or 0 mmosimca@485: mmosimca@485: -- Check if we have quest data mmosimca@485: if _G.HaveQuestData(quest_id) then mmosimca@485: local tag_id, tag_name, world_quest_type, rarity, is_elite, tradeskill_line_index = _G.GetQuestTagInfo(quest_id) mmosimca@485: mmosimca@485: -- Check for valid questID and whether or not it is a World Quest mmosimca@485: if quest_id > 0 and world_quest_type ~= nil then mmosimca@485: _G.C_TaskQuest.RequestPreloadRewardData(quest_id) mmosimca@485: RecordWorldQuestData(world_map_id, quest_id, current_data) mmosimca@485: end mmosimca@485: end mmosimca@485: end mmosimca@485: end mmosimca@485: end mmosimca@485: end mmosimca@485: end mmosimca@485: end mmosimca@485: mmosimca@485: MMOSimca@340: local function RecordItemData(item_id, item_link, process_bonus_ids, durability) jcallahan@331: local _, _, item_string = item_link:find("^|%x+|H(.+)|h%[.+%]") jcallahan@219: local item jcallahan@0: jcallahan@191: if item_string then MMOSimca@338: local item_results = { (":"):split(item_string) } MMOSimca@338: MMOSimca@460: local suffix_id = tonumber(item_results[8]) or 0 MMOSimca@462: local unique_id = tonumber(item_results[9]) or 0 MMOSimca@447: --local level = tonumber(item_results[10]) MMOSimca@460: --local specialization_id = tonumber(item_results[11]) catherton@469: --local upgrade_type_id = tonumber(item_results[12]) MMOSimca@460: local instance_difficulty_id = tonumber(item_results[13]) or 0 MMOSimca@460: local num_bonus_ids = tonumber(item_results[14]) or 0 catherton@471: -- upgrade_value is optional in 6.2! can be detected using upgrade_type_id, but it's just as easy to check like this catherton@469: local upgrade_value = tonumber(item_results[15 + num_bonus_ids]) or 0 catherton@465: mmosimca@484: local unk_item_field_1 = tonumber(item_results[16 + num_bonus_ids]) or 0 mmosimca@484: local unk_item_field_2 = tonumber(item_results[17 + num_bonus_ids]) or 0 mmosimca@484: --if unk_item_field_1 > 0 then Debug("unk_item_field_1 for %s is non-zero, specifically %d.", item_link, unk_item_field_1) end mmosimca@484: --if unk_item_field_2 > 0 then Debug("unk_item_field_2 for %s is non-zero, specifically %d.", item_link, unk_item_field_2) end MMOSimca@460: MMOSimca@460: -- If there is anything special (non-zero) for this item then we need to make note of everything catherton@471: if math.max(suffix_id, instance_difficulty_id, num_bonus_ids, upgrade_value) ~= 0 then MMOSimca@460: item = DBEntry("items", item_id) MMOSimca@460: item.suffix_id = suffix_id MMOSimca@460: item.unique_id = bit.band(unique_id, 0xFFFF) MMOSimca@460: item.instance_difficulty_id = instance_difficulty_id catherton@471: item.upgrade_value = upgrade_value MMOSimca@460: MMOSimca@460: if process_bonus_ids then MMOSimca@460: MMOSimca@460: -- Get ready for bonus IDs MMOSimca@384: if not item.seen_bonuses then MMOSimca@384: item.seen_bonuses = {} MMOSimca@372: end catherton@465: MMOSimca@460: if num_bonus_ids > 0 then MMOSimca@460: -- We want the bonus ID combo output to be in the form ["bonusID1:bonusID2:bonusID3"] = true MMOSimca@460: -- And sorted numerically with the smallest bonusID first MMOSimca@460: local sorted_bonus_string = "" MMOSimca@460: local min_bonus_id_array = {} MMOSimca@460: for iterations = 1, num_bonus_ids do MMOSimca@460: -- Find minimum of this iteration MMOSimca@460: local min_bonus_id = 100000 MMOSimca@460: for bonus_index = 1, num_bonus_ids do MMOSimca@460: local temp_bonus_id = tonumber(item_results[14 + bonus_index]) MMOSimca@460: if temp_bonus_id and (not min_bonus_id_array[temp_bonus_id]) and (temp_bonus_id < min_bonus_id) then MMOSimca@460: min_bonus_id = temp_bonus_id MMOSimca@460: end MMOSimca@460: end MMOSimca@460: MMOSimca@460: -- Keep track of already processed IDs MMOSimca@460: min_bonus_id_array[min_bonus_id] = true MMOSimca@460: MMOSimca@460: -- Build string MMOSimca@460: if iterations == 1 then MMOSimca@460: sorted_bonus_string = sorted_bonus_string .. tostring(min_bonus_id) MMOSimca@460: else MMOSimca@460: sorted_bonus_string = sorted_bonus_string .. ":" .. tostring(min_bonus_id) MMOSimca@460: end MMOSimca@384: end MMOSimca@460: MMOSimca@460: item.seen_bonuses[sorted_bonus_string] = true MMOSimca@460: Debug("RecordItemData: Recorded bonus IDs %s for item %d.", sorted_bonus_string, item_id) MMOSimca@384: else MMOSimca@460: item.seen_bonuses["0"] = true MMOSimca@384: end MMOSimca@329: end jcallahan@191: end jcallahan@0: end jcallahan@212: jcallahan@212: if durability and durability > 0 then jcallahan@219: item = item or DBEntry("items", item_id) jcallahan@212: item.durability = durability jcallahan@212: end jcallahan@0: end jcallahan@0: jcallahan@0: jcallahan@187: function WDP:ProcessItems() jcallahan@187: for slot_index = _G.INVSLOT_FIRST_EQUIPPED, _G.INVSLOT_LAST_EQUIPPED do jcallahan@1: local item_id = _G.GetInventoryItemID("player", slot_index) jcallahan@0: jcallahan@0: if item_id and item_id > 0 then jcallahan@1: local _, max_durability = _G.GetInventoryItemDurability(slot_index) MMOSimca@340: RecordItemData(item_id, _G.GetInventoryItemLink("player", slot_index), false, max_durability) jcallahan@0: end jcallahan@0: end jcallahan@0: jcallahan@0: for bag_index = 0, _G.NUM_BAG_SLOTS do jcallahan@0: for slot_index = 1, _G.GetContainerNumSlots(bag_index) do jcallahan@1: local item_id = _G.GetContainerItemID(bag_index, slot_index) jcallahan@0: jcallahan@0: if item_id and item_id > 0 then jcallahan@1: local _, max_durability = _G.GetContainerItemDurability(bag_index, slot_index) MMOSimca@340: RecordItemData(item_id, _G.GetContainerItemLink(bag_index, slot_index), false, max_durability) jcallahan@0: end jcallahan@0: end jcallahan@0: end jcallahan@0: end jcallahan@0: jcallahan@118: jcallahan@215: local TargetedNPC jcallahan@118: do jcallahan@118: local GENDER_NAMES = { jcallahan@118: "UNKNOWN", jcallahan@118: "MALE", jcallahan@118: "FEMALE", jcallahan@118: } jcallahan@118: jcallahan@118: jcallahan@118: local REACTION_NAMES = { jcallahan@118: "HATED", jcallahan@118: "HOSTILE", jcallahan@118: "UNFRIENDLY", jcallahan@118: "NEUTRAL", jcallahan@118: "FRIENDLY", jcallahan@118: "HONORED", jcallahan@118: "REVERED", jcallahan@118: "EXALTED", jcallahan@118: } jcallahan@118: jcallahan@118: jcallahan@118: local POWER_TYPE_NAMES = { jcallahan@118: ["0"] = "MANA", jcallahan@118: ["1"] = "RAGE", jcallahan@118: ["2"] = "FOCUS", jcallahan@118: ["3"] = "ENERGY", jcallahan@118: ["6"] = "RUNIC_POWER", jcallahan@118: } jcallahan@118: jcallahan@118: jcallahan@215: function TargetedNPC() jcallahan@118: if not _G.UnitExists("target") or _G.UnitPlayerControlled("target") or currently_drunk then jcallahan@118: current_target_id = nil jcallahan@118: return jcallahan@118: end jcallahan@118: local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("target")) jcallahan@118: jcallahan@171: if not unit_idnum or not UnitTypeIsNPC(unit_type) then jcallahan@118: return jcallahan@118: end jcallahan@118: current_target_id = unit_idnum jcallahan@118: jcallahan@118: local npc = NPCEntry(unit_idnum) jcallahan@118: local _, class_token = _G.UnitClass("target") jcallahan@118: npc.class = class_token jcallahan@118: npc.faction = UnitFactionStanding("target") jcallahan@118: npc.genders = npc.genders or {} jcallahan@118: npc.genders[GENDER_NAMES[_G.UnitSex("target")] or "UNDEFINED"] = true jcallahan@118: npc.is_pvp = _G.UnitIsPVP("target") and true or nil jcallahan@118: npc.reaction = ("%s:%s:%s"):format(_G.UnitLevel("player"), _G.UnitFactionGroup("player"), REACTION_NAMES[_G.UnitReaction("player", "target")]) jcallahan@118: jcallahan@248: local encounter_data = npc:EncounterData(InstanceDifficultyToken()).stats jcallahan@118: local npc_level = ("level_%d"):format(_G.UnitLevel("target")) jcallahan@250: local level_data = encounter_data[npc_level] jcallahan@250: jcallahan@250: if not level_data then jcallahan@250: level_data = {} jcallahan@250: encounter_data[npc_level] = level_data jcallahan@250: end jcallahan@257: level_data.max_health = level_data.max_health or _G.UnitHealthMax("target") jcallahan@257: jcallahan@257: if not level_data.power then MMOSimca@337: local max_power = _G.UnitPowerMax("target") jcallahan@257: jcallahan@257: if max_power > 0 then jcallahan@257: local power_type = _G.UnitPowerType("target") jcallahan@312: level_data.power = ("%s:%d"):format(POWER_TYPE_NAMES[tostring(power_type)] or power_type, max_power) jcallahan@257: end jcallahan@118: end jcallahan@118: name_to_id_map[_G.UnitName("target")] = unit_idnum jcallahan@118: return npc, unit_idnum jcallahan@118: end jcallahan@118: end -- do-block jcallahan@118: jcallahan@118: jcallahan@113: do jcallahan@113: local COORD_MAX = 5 jcallahan@0: jcallahan@113: function WDP:UpdateTargetLocation() catherton@480: if currently_drunk or not _G.UnitExists("target") or _G.UnitPlayerControlled("target") or (_G.UnitIsTapDenied("target") and not _G.UnitIsDead("target")) then jcallahan@2: return jcallahan@2: end jcallahan@113: jcallahan@113: for index = 1, 4 do jcallahan@113: if not _G.CheckInteractDistance("target", index) then jcallahan@113: return jcallahan@113: end jcallahan@113: end jcallahan@215: local npc = TargetedNPC() jcallahan@113: jcallahan@113: if not npc then jcallahan@113: return jcallahan@113: end jcallahan@113: local zone_name, area_id, x, y, map_level, difficulty_token = CurrentLocationData() MMOSimca@328: if not (zone_name and area_id and x and y and map_level) then catherton@468: Debug("UpdateTargetLocation: Missing current location data - %s, %s, %s, %s, %s.", tostring(zone_name), tostring(area_id), tostring(x), tostring(y), tostring(map_level)) MMOSimca@328: return MMOSimca@328: end jcallahan@248: local npc_data = npc:EncounterData(difficulty_token).stats[("level_%d"):format(_G.UnitLevel("target"))] jcallahan@113: local zone_token = ("%s:%d"):format(zone_name, area_id) jcallahan@118: npc_data.locations = npc_data.locations or {} -- TODO: Fix this. It is broken. Possibly something to do with the timed updates. jcallahan@113: jcallahan@113: local zone_data = npc_data.locations[zone_token] jcallahan@113: jcallahan@113: if not zone_data then jcallahan@113: zone_data = {} jcallahan@113: npc_data.locations[zone_token] = zone_data jcallahan@113: end jcallahan@113: jcallahan@113: for location_token in pairs(zone_data) do jcallahan@113: local loc_level, loc_x, loc_y = (":"):split(location_token) jcallahan@113: loc_level = tonumber(loc_level) jcallahan@113: jcallahan@113: if map_level == loc_level and math.abs(x - loc_x) <= COORD_MAX and math.abs(y - loc_y) <= COORD_MAX then jcallahan@113: return jcallahan@113: end jcallahan@113: end jcallahan@141: zone_data[("%d:%d:%d"):format(map_level, x, y)] = true jcallahan@2: end jcallahan@113: end -- do-block jcallahan@2: jcallahan@118: MMOSimca@412: function WDP:HandleBadChatLootData(...) MMOSimca@398: ClearChatLootData() MMOSimca@398: end MMOSimca@398: MMOSimca@398: MMOSimca@420: -- EVENT HANDLERS ----------------------------------------------------- MMOSimca@420: MMOSimca@436: -- This function (and the following function) are to stop 'HandleItemUse' from triggering when you put an item that would normally be opened into the bank, guild bank, etc. MMOSimca@436: function WDP:StopChatLootRecording(event_name) MMOSimca@436: if not block_chat_loot_data then MMOSimca@439: Debug("%s: Pausing chat-based loot recording.", event_name) MMOSimca@436: ClearChatLootData() MMOSimca@436: block_chat_loot_data = true MMOSimca@436: end MMOSimca@436: end MMOSimca@436: MMOSimca@436: MMOSimca@436: function WDP:ResumeChatLootRecording(event_name) MMOSimca@436: if block_chat_loot_data then MMOSimca@439: Debug("%s: Resuming chat-based loot recording.", event_name) MMOSimca@436: block_chat_loot_data = false MMOSimca@436: end MMOSimca@436: end MMOSimca@436: MMOSimca@436: mmosimca@485: -- Process world quests if the map is moved to the Broken Isles continent world map (this provides us an opportunity to get data for all zones on the continent without moving the map) mmosimca@485: function WDP:WORLD_MAP_UPATE(event_name) mmosimca@485: if _G.GetCurrentMapAreaID() == WORLD_MAP_ID_BROKEN_ISLES and _G.GetServerTime() > (world_quest_event_timestamp + DELAY_PROCESS_WORLD_QUESTS) then mmosimca@485: world_quest_event_timestamp = _G.GetServerTime() mmosimca@485: WDP:ProcessWorldQuests() mmosimca@485: end mmosimca@485: end mmosimca@485: mmosimca@485: MMOSimca@408: -- For now, bonus roll data only pollutes the true drop percentages. We still want to capture the data from SPELL_CONFIRMATION_PROMPT because of legendary quest items though. MMOSimca@408: function WDP:BONUS_ROLL_RESULT(event_name) MMOSimca@408: Debug("%s: Bonus roll detected; stopping loot recording for this boss to avoid recording bonus loot.", event_name) MMOSimca@408: ClearKilledBossID() MMOSimca@408: ClearLootToastContainerID() MMOSimca@408: end MMOSimca@408: MMOSimca@408: jcallahan@90: function WDP:BLACK_MARKET_ITEM_UPDATE(event_name) jcallahan@243: if not ALLOWED_LOCALES[CLIENT_LOCALE] then jcallahan@243: return jcallahan@243: end jcallahan@282: local num_items = _G.C_BlackMarket.GetNumItems() or 0 jcallahan@56: jcallahan@56: for index = 1, num_items do jcallahan@56: local name, texture, quantity, item_type, is_usable, level, level_type, seller_name, min_bid, min_increment, current_bid, has_high_bid, num_bids, time_left, item_link, market_id = _G.C_BlackMarket.GetItemInfoByIndex(index); jcallahan@56: jcallahan@56: if item_link then jcallahan@56: DBEntry("items", ItemLinkToID(item_link)).black_market = seller_name or "UNKNOWN" jcallahan@56: end jcallahan@56: end jcallahan@56: end jcallahan@56: jcallahan@56: jcallahan@298: local function UpdateUnitPet(unit_guid, unit_id) jcallahan@246: local current_pet_guid = group_owner_guids_to_pet_guids[unit_guid] jcallahan@246: jcallahan@246: if current_pet_guid then jcallahan@246: group_owner_guids_to_pet_guids[unit_guid] = nil jcallahan@246: group_pet_guids[current_pet_guid] = nil jcallahan@246: end jcallahan@246: local pet_guid = _G.UnitGUID(unit_id .. "pet") jcallahan@246: jcallahan@246: if pet_guid then jcallahan@296: group_owner_guids_to_pet_guids[unit_guid] = pet_guid jcallahan@246: group_pet_guids[pet_guid] = true jcallahan@246: end jcallahan@246: end jcallahan@246: jcallahan@246: jcallahan@298: function WDP:GROUP_ROSTER_UPDATE(event_name) jcallahan@298: local is_raid = _G.IsInRaid() jcallahan@298: local unit_type = is_raid and "raid" or "party" jcallahan@298: local group_size = is_raid and _G.GetNumGroupMembers() or _G.GetNumSubgroupMembers() jcallahan@298: jcallahan@299: table.wipe(group_member_guids) jcallahan@298: jcallahan@298: for index = 1, group_size do jcallahan@298: local unit_id = unit_type .. index jcallahan@298: local unit_guid = _G.UnitGUID(unit_id) jcallahan@298: jcallahan@299: group_member_guids[unit_guid] = true jcallahan@298: UpdateUnitPet(unit_guid, unit_id) jcallahan@298: end jcallahan@299: group_member_guids[PLAYER_GUID] = true jcallahan@298: end jcallahan@298: jcallahan@298: jcallahan@298: function WDP:UNIT_PET(event_name, unit_id) jcallahan@298: UpdateUnitPet(_G.UnitGUID(unit_id), unit_id) jcallahan@298: end jcallahan@298: jcallahan@298: MMOSimca@375: function WDP:SHOW_LOOT_TOAST(event_name, loot_type, item_link, quantity, spec_ID, sex_ID, is_personal, loot_source) jcallahan@312: if not loot_type or (loot_type ~= "item" and loot_type ~= "money" and loot_type ~= "currency") then jcallahan@306: Debug("%s: loot_type is %s. Item link is %s, and quantity is %d.", event_name, loot_type, item_link, quantity) jcallahan@306: return jcallahan@306: end MMOSimca@372: MMOSimca@372: -- Need information on the most recent args, so using this complete debug statement for now MMOSimca@375: Debug("%s: loot_type: %s, item_link: %s, quantity: %s, spec_ID: %s, sex_ID: %s, is_personal: %s, loot_source: %s", event_name, loot_type, item_link, quantity, spec_ID, sex_ID, is_personal, loot_source) MMOSimca@372: MMOSimca@355: -- Handle Garrison cache specially MMOSimca@422: if loot_source and (loot_source == LOOT_SOURCE_ID_GARRISON_CACHE) and last_garrison_cache_object_id then MMOSimca@355: -- Record location data for cache MMOSimca@355: UpdateDBEntryLocation("objects", ("OPENING:%d"):format(last_garrison_cache_object_id)) MMOSimca@355: MMOSimca@355: -- Add drop data MMOSimca@355: local currency_texture = CurrencyLinkToTexture(item_link) MMOSimca@355: if currency_texture and currency_texture ~= "" then MMOSimca@355: -- Check for top level object data MMOSimca@355: local object_entry = DBEntry("objects", ("OPENING:%d"):format(last_garrison_cache_object_id)) MMOSimca@355: local difficulty_token = InstanceDifficultyToken() MMOSimca@355: if object_entry[difficulty_token] then MMOSimca@355: -- Increment loot count MMOSimca@355: object_entry[difficulty_token]["opening_count"] = (object_entry[difficulty_token]["opening_count"] or 0) + 1 MMOSimca@355: MMOSimca@355: Debug("%s: %s X %d", event_name, currency_texture, quantity) MMOSimca@355: object_entry[difficulty_token]["opening"] = object_entry[difficulty_token]["opening"] or {} MMOSimca@355: table.insert(object_entry[difficulty_token]["opening"], ("currency:%d:%s"):format(quantity, currency_texture)) MMOSimca@355: else MMOSimca@355: Debug("%s: When handling the Garrison cache, the top level loot data was missing for objectID %d.", event_name, last_garrison_cache_object_id) MMOSimca@355: end MMOSimca@355: else MMOSimca@355: Debug("%s: Currency texture is nil, from currency link %s", event_name, item_link) MMOSimca@355: end catherton@465: MMOSimca@431: -- Wipe object ID until future mouseover MMOSimca@431: last_garrison_cache_object_id = nil MMOSimca@387: elseif raid_boss_id then MMOSimca@427: local npc = NPCEntry(raid_boss_id) MMOSimca@427: if npc then MMOSimca@427: local loot_label = "drops" MMOSimca@427: local encounter_data = npc:EncounterData(InstanceDifficultyToken()) MMOSimca@427: encounter_data[loot_label] = encounter_data[loot_label] or {} MMOSimca@427: encounter_data.loot_counts = encounter_data.loot_counts or {} MMOSimca@427: MMOSimca@427: if loot_type == "item" then MMOSimca@427: local item_id = ItemLinkToID(item_link) MMOSimca@427: if item_id then MMOSimca@427: Debug("%s: %s X %d (%d)", event_name, item_link, quantity, item_id) MMOSimca@427: RecordItemData(item_id, item_link, true) MMOSimca@427: table.insert(encounter_data[loot_label], ("%d:%d"):format(item_id, quantity)) MMOSimca@427: else MMOSimca@427: Debug("%s: ItemID is nil, from item link %s", event_name, item_link) MMOSimca@427: return MMOSimca@427: end MMOSimca@427: elseif loot_type == "money" then MMOSimca@427: Debug("%s: money X %d", event_name, quantity) MMOSimca@427: table.insert(encounter_data[loot_label], ("money:%d"):format(quantity)) MMOSimca@427: elseif loot_type == "currency" then MMOSimca@427: local currency_texture = CurrencyLinkToTexture(item_link) MMOSimca@427: if currency_texture and currency_texture ~= "" then MMOSimca@427: Debug("%s: %s X %d", event_name, currency_texture, quantity) MMOSimca@427: table.insert(encounter_data[loot_label], ("currency:%d:%s"):format(quantity, currency_texture)) MMOSimca@427: else MMOSimca@427: Debug("%s: Currency texture is nil, from currency link %s", event_name, item_link) MMOSimca@427: return MMOSimca@427: end jcallahan@312: end jcallahan@317: MMOSimca@427: if not boss_loot_toasting[raid_boss_id] then MMOSimca@427: encounter_data.loot_counts[loot_label] = (encounter_data.loot_counts[loot_label] or 0) + 1 MMOSimca@427: boss_loot_toasting[raid_boss_id] = true -- Do not count further loots until timer expires or another boss is killed jcallahan@312: end jcallahan@312: end MMOSimca@387: elseif loot_toast_container_id then jcallahan@305: InitializeCurrentLoot() jcallahan@305: jcallahan@306: -- Fake the loot characteristics to match that of an actual container item MMOSimca@387: current_loot.identifier = loot_toast_container_id jcallahan@306: current_loot.label = "contains" jcallahan@306: current_loot.target_type = AF.ITEM jcallahan@306: MMOSimca@387: current_loot.sources[loot_toast_container_id] = current_loot.sources[loot_toast_container_id] or {} jcallahan@312: jcallahan@301: if loot_type == "item" then jcallahan@312: local item_id = ItemLinkToID(item_link) jcallahan@312: if item_id then jcallahan@312: Debug("%s: %s X %d (%d)", event_name, item_link, quantity, item_id) MMOSimca@340: RecordItemData(item_id, item_link, true) MMOSimca@387: current_loot.sources[loot_toast_container_id][item_id] = (current_loot.sources[loot_toast_container_id][item_id] or 0) + quantity jcallahan@312: else jcallahan@301: Debug("%s: ItemID is nil, from item link %s", event_name, item_link) jcallahan@312: current_loot = nil jcallahan@301: return jcallahan@301: end jcallahan@301: elseif loot_type == "money" then jcallahan@312: Debug("%s: money X %d", event_name, quantity) MMOSimca@387: current_loot.sources[loot_toast_container_id]["money"] = (current_loot.sources[loot_toast_container_id]["money"] or 0) + quantity jcallahan@312: elseif loot_type == "currency" then jcallahan@312: local currency_texture = CurrencyLinkToTexture(item_link) jcallahan@312: if currency_texture and currency_texture ~= "" then jcallahan@312: Debug("%s: %s X %d", event_name, currency_texture, quantity) jcallahan@312: local currency_token = ("currency:%s"):format(currency_texture) MMOSimca@387: current_loot.sources[loot_toast_container_id][currency_token] = (current_loot.sources[loot_toast_container_id][currency_token] or 0) + quantity jcallahan@312: else jcallahan@312: Debug("%s: Currency texture is nil, from currency link %s", event_name, item_link) jcallahan@312: current_loot = nil jcallahan@312: return jcallahan@312: end jcallahan@301: end jcallahan@312: jcallahan@301: GenericLootUpdate("items") jcallahan@301: current_loot = nil MMOSimca@387: container_loot_toasting = true -- Do not count further loots until timer expires or another container is opened MMOSimca@444: elseif loot_source and (loot_source == LOOT_SOURCE_ID_REDUNDANT) and chat_loot_timer_handle then MMOSimca@444: -- Handle currency loot toasts for chat-based loot (we do this instead of reading currency chat messages because the chat messages are very delayed) MMOSimca@444: if loot_type == "currency" then MMOSimca@444: local currency_texture = CurrencyLinkToTexture(item_link) MMOSimca@444: if currency_texture and currency_texture ~= "" then MMOSimca@444: -- Verify that we're still assigning data to the right items MMOSimca@444: if chat_loot_data.identifier and (private.CONTAINER_ITEM_ID_LIST[chat_loot_data.identifier] ~= nil) then MMOSimca@444: local currency_token = ("currency:%s"):format(currency_texture) MMOSimca@444: Debug("%s: Captured currency for chat-based loot recording. %s X %d", event_name, currency_token, quantity) MMOSimca@444: chat_loot_data.loot = chat_loot_data.loot or {} MMOSimca@444: chat_loot_data.loot[currency_token] = (chat_loot_data.loot[currency_token] or 0) + quantity MMOSimca@444: else -- If not, cancel the timer and wipe the loot table early MMOSimca@444: Debug("%s: Canceled chat-based loot recording because we would have assigned the wrong loot!", event_name) MMOSimca@444: ClearChatLootData() MMOSimca@444: end MMOSimca@444: else MMOSimca@444: Debug("%s: Currency texture is nil, from currency link %s", event_name, item_link) MMOSimca@444: end MMOSimca@444: -- Handle money loot toasts for chat-based loot (we do this instead of reading money chat messages because the chat messages are very delayed) MMOSimca@444: elseif loot_type == "money" then MMOSimca@424: -- Verify that we're still assigning data to the right items MMOSimca@435: if chat_loot_data.identifier and (private.CONTAINER_ITEM_ID_LIST[chat_loot_data.identifier] ~= nil) then MMOSimca@444: Debug("%s: Captured money for chat-based loot recording. money X %d", event_name, quantity) MMOSimca@435: chat_loot_data.loot = chat_loot_data.loot or {} MMOSimca@444: chat_loot_data.loot["money"] = (chat_loot_data.loot["money"] or 0) + quantity MMOSimca@424: else -- If not, cancel the timer and wipe the loot table early MMOSimca@424: Debug("%s: Canceled chat-based loot recording because we would have assigned the wrong loot!", event_name) MMOSimca@424: ClearChatLootData() MMOSimca@424: end MMOSimca@424: end jcallahan@301: else jcallahan@307: Debug("%s: NPC and Container are nil, storing loot toast data for 5 seconds.", event_name) jcallahan@307: jcallahan@307: loot_toast_data = loot_toast_data or {} jcallahan@312: loot_toast_data[#loot_toast_data + 1] = { loot_type, item_link, quantity } jcallahan@307: MMOSimca@340: local item_id = ItemLinkToID(item_link) MMOSimca@340: if item_id then MMOSimca@340: RecordItemData(item_id, item_link, true) MMOSimca@340: end MMOSimca@340: MMOSimca@383: loot_toast_data_timer_handle = C_Timer.NewTimer(5, ClearLootToastData) jcallahan@178: end jcallahan@178: end jcallahan@178: jcallahan@178: jcallahan@179: do MMOSimca@388: local CHAT_MSG_CURRENCY_UPDATE_FUNCS = { MMOSimca@388: [AF.NPC] = function(currency_texture, quantity) MMOSimca@388: Debug("CHAT_MSG_CURRENCY: AF.NPC currency:%s (%d)", currency_texture, quantity) MMOSimca@388: end, MMOSimca@388: [AF.ZONE] = function(currency_texture, quantity) MMOSimca@388: local currency_token = ("currency:%s"):format(currency_texture) MMOSimca@388: Debug("CHAT_MSG_CURRENCY: AF.ZONE %s (%d)", currency_token, quantity) MMOSimca@388: InitializeCurrentLoot() MMOSimca@388: current_loot.list[1] = ("%s:%d"):format(currency_token, quantity) MMOSimca@388: GenericLootUpdate("zones") MMOSimca@388: current_loot = nil MMOSimca@388: end, MMOSimca@388: } MMOSimca@388: MMOSimca@388: MMOSimca@388: function WDP:CHAT_MSG_CURRENCY(event_name, message) MMOSimca@388: local category MMOSimca@388: MMOSimca@388: local currency_link, quantity = deformat(message, _G.CURRENCY_GAINED_MULTIPLE) MMOSimca@388: if not currency_link then MMOSimca@388: quantity, currency_link = 1, deformat(message, _G.CURRENCY_GAINED) MMOSimca@388: end MMOSimca@388: local currency_texture = CurrencyLinkToTexture(currency_link) MMOSimca@388: MMOSimca@388: if not currency_texture or currency_texture == "" then MMOSimca@388: return MMOSimca@388: end MMOSimca@388: MMOSimca@388: -- Set update category MMOSimca@388: if current_action.spell_label == "FISHING" then MMOSimca@388: category = AF.ZONE MMOSimca@388: elseif raid_boss_id then MMOSimca@388: category = AF.NPC MMOSimca@388: end MMOSimca@388: MMOSimca@388: -- Take action based on update category MMOSimca@388: local update_func = CHAT_MSG_CURRENCY_UPDATE_FUNCS[category] MMOSimca@388: if not category or not update_func then MMOSimca@388: return MMOSimca@388: end MMOSimca@388: update_func(currency_texture, quantity) MMOSimca@388: end MMOSimca@388: MMOSimca@388: MMOSimca@412: local BLACKLISTED_ITEMS = { MMOSimca@412: [114116] = true, MMOSimca@412: [114119] = true, MMOSimca@412: [114120] = true, MMOSimca@412: [116980] = true, MMOSimca@412: [120319] = true, MMOSimca@412: [120320] = true, catherton@475: [139593] = true, catherton@475: [139594] = true, catherton@475: [140590] = true, MMOSimca@412: } MMOSimca@412: MMOSimca@412: jcallahan@179: local CHAT_MSG_LOOT_UPDATE_FUNCS = { MMOSimca@347: [AF.ITEM] = function(item_id, quantity) MMOSimca@347: -- Verify that we're still assigning data to the right items MMOSimca@435: if chat_loot_data.identifier and (private.CONTAINER_ITEM_ID_LIST[chat_loot_data.identifier] ~= nil) then MMOSimca@347: Debug("CHAT_MSG_LOOT: AF.ITEM %d (%d)", item_id, quantity) MMOSimca@435: chat_loot_data.loot = chat_loot_data.loot or {} MMOSimca@435: chat_loot_data.loot[item_id] = (chat_loot_data.loot[item_id] or 0) + quantity MMOSimca@347: else -- If not, cancel the timer and wipe the loot table early MMOSimca@387: Debug("CHAT_MSG_LOOT: We would have assigned the wrong loot!") MMOSimca@387: ClearChatLootData() MMOSimca@347: end MMOSimca@347: end, jcallahan@179: [AF.NPC] = function(item_id, quantity) MMOSimca@345: Debug("CHAT_MSG_LOOT: AF.NPC %d (%d)", item_id, quantity) MMOSimca@345: end, MMOSimca@345: [AF.OBJECT] = function(item_id, quantity) MMOSimca@345: Debug("CHAT_MSG_LOOT: AF.OBJECT %d (%d)", item_id, quantity) MMOSimca@381: -- Check for top level object data MMOSimca@381: local object_entry = DBEntry("objects", ("OPENING:%s"):format(private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[last_timber_spell_id])) MMOSimca@381: local difficulty_token = InstanceDifficultyToken() MMOSimca@381: if object_entry[difficulty_token] then MMOSimca@381: -- Increment loot count MMOSimca@381: object_entry[difficulty_token]["opening_count"] = (object_entry[difficulty_token]["opening_count"] or 0) + 1 MMOSimca@381: MMOSimca@381: -- Add drop data MMOSimca@381: object_entry[difficulty_token]["opening"] = object_entry[difficulty_token]["opening"] or {} MMOSimca@381: table.insert(object_entry[difficulty_token]["opening"], ("%d:%d"):format(item_id, quantity)) MMOSimca@381: else MMOSimca@381: Debug("CHAT_MSG_LOOT: When handling timber, the top level data was missing for objectID %s.", private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[last_timber_spell_id]) MMOSimca@381: end jcallahan@179: end, jcallahan@179: [AF.ZONE] = function(item_id, quantity) MMOSimca@345: Debug("CHAT_MSG_LOOT: AF.ZONE %d (%d)", item_id, quantity) jcallahan@312: InitializeCurrentLoot() jcallahan@312: current_loot.list[1] = ("%d:%d"):format(item_id, quantity) jcallahan@179: GenericLootUpdate("zones") jcallahan@312: current_loot = nil jcallahan@179: end, jcallahan@179: } jcallahan@177: jcallahan@177: MMOSimca@388: function WDP:CHAT_MSG_LOOT(event_name, message) jcallahan@179: local category jcallahan@177: MMOSimca@345: local item_link, quantity = deformat(message, _G.LOOT_ITEM_PUSHED_SELF_MULTIPLE) MMOSimca@345: if not item_link then MMOSimca@345: quantity, item_link = 1, deformat(message, _G.LOOT_ITEM_PUSHED_SELF) MMOSimca@345: end MMOSimca@345: local item_id = ItemLinkToID(item_link) MMOSimca@345: MMOSimca@345: if not item_id then MMOSimca@345: return MMOSimca@345: end MMOSimca@345: MMOSimca@345: -- Set update category MMOSimca@405: if last_timber_spell_id and item_id == ITEM_ID_TIMBER then MMOSimca@345: category = AF.OBJECT MMOSimca@345: -- Recently changed from ~= "EXTRACT_GAS" because of some occassional bad data, and, as far as I know, no benefit. MMOSimca@345: elseif current_action.spell_label == "FISHING" then jcallahan@179: category = AF.ZONE MMOSimca@388: elseif raid_boss_id then jcallahan@179: category = AF.NPC MMOSimca@347: elseif chat_loot_timer_handle then MMOSimca@347: category = AF.ITEM jcallahan@179: end MMOSimca@345: MMOSimca@395: -- We still want to record the item's data, even if it doesn't need its drop location recorded MMOSimca@395: RecordItemData(item_id, item_link, true) MMOSimca@395: MMOSimca@345: -- Take action based on update category jcallahan@179: local update_func = CHAT_MSG_LOOT_UPDATE_FUNCS[category] MMOSimca@412: if not category or not update_func or BLACKLISTED_ITEMS[item_id] then MMOSimca@340: return MMOSimca@340: end jcallahan@179: update_func(item_id, quantity) jcallahan@177: end MMOSimca@388: end MMOSimca@388: MMOSimca@388: jcallahan@97: function WDP:RecordQuote(event_name, message, source_name, language_name) jcallahan@112: if not ALLOWED_LOCALES[CLIENT_LOCALE] or not source_name or not name_to_id_map[source_name] or (language_name ~= "" and not languages_known[language_name]) then jcallahan@97: return jcallahan@95: end jcallahan@97: local npc = NPCEntry(name_to_id_map[source_name]) jcallahan@97: npc.quotes = npc.quotes or {} jcallahan@97: npc.quotes[event_name] = npc.quotes[event_name] or {} jcallahan@97: npc.quotes[event_name][ReplaceKeywords(message)] = true jcallahan@97: end jcallahan@95: jcallahan@95: jcallahan@95: do jcallahan@40: local SOBER_MATCH = _G.DRUNK_MESSAGE_ITEM_SELF1:gsub("%%s", ".+") jcallahan@40: jcallahan@40: local DRUNK_COMPARES = { jcallahan@40: _G.DRUNK_MESSAGE_SELF2, jcallahan@40: _G.DRUNK_MESSAGE_SELF3, jcallahan@40: _G.DRUNK_MESSAGE_SELF4, jcallahan@40: } jcallahan@40: jcallahan@40: local DRUNK_MATCHES = { jcallahan@254: (_G.DRUNK_MESSAGE_SELF2:gsub("%%s", ".+")), jcallahan@254: (_G.DRUNK_MESSAGE_SELF3:gsub("%%s", ".+")), jcallahan@254: (_G.DRUNK_MESSAGE_SELF4:gsub("%%s", ".+")), jcallahan@40: } jcallahan@40: jcallahan@167: local RECIPE_MATCH = _G.ERR_LEARN_RECIPE_S:gsub("%%s", "(.*)") jcallahan@167: jcallahan@167: jcallahan@167: local function RecordDiscovery(tradeskill_name, tradeskill_index) jcallahan@167: if tradeskill_name == private.discovered_recipe_name then catherton@479: DBEntry("spells", tonumber(_G.C_TradeSkillUI.GetRecipeLink(tradeskill_index):match("^|c%x%x%x%x%x%x%x%x|H%w+:(%d+)"))).discovery = ("%d:%d"):format(private.previous_spell_id, private.profession_level) jcallahan@167: jcallahan@167: private.discovered_recipe_name = nil jcallahan@167: private.profession_level = nil jcallahan@167: private.previous_spell_id = nil jcallahan@169: jcallahan@169: return true jcallahan@167: end jcallahan@167: end jcallahan@167: jcallahan@167: jcallahan@167: local function IterativeRecordDiscovery() jcallahan@167: TradeSkillExecutePer(RecordDiscovery) jcallahan@167: end jcallahan@167: jcallahan@167: jcallahan@92: function WDP:CHAT_MSG_SYSTEM(event_name, message) catherton@472: local item_link, quantity = deformat(message, ERR_QUEST_REWARD_ITEM_MULT_IS) MMOSimca@400: if not item_link then catherton@472: quantity, item_link = 1, deformat(message, ERR_QUEST_REWARD_ITEM_S) MMOSimca@400: end MMOSimca@400: local item_id = ItemLinkToID(item_link) MMOSimca@400: MMOSimca@400: -- If it isn't a quest message, check the other uses of system messages MMOSimca@400: if not item_id then MMOSimca@400: if not private.trainer_shown then MMOSimca@400: local recipe_name = message:match(RECIPE_MATCH) MMOSimca@400: MMOSimca@400: if recipe_name and private.previous_spell_id then catherton@479: local profession_name, prof_level = _G.C_TradeSkillUI.GetTradeSkillLine() MMOSimca@400: MMOSimca@400: if profession_name == _G.UNKNOWN then MMOSimca@400: return MMOSimca@400: end MMOSimca@400: private.discovered_recipe_name = recipe_name MMOSimca@400: private.profession_level = prof_level MMOSimca@400: MMOSimca@400: C_Timer.After(0.2, IterativeRecordDiscovery) jcallahan@167: end jcallahan@167: end MMOSimca@400: MMOSimca@400: if currently_drunk then MMOSimca@400: if message == _G.DRUNK_MESSAGE_SELF1 or message:match(SOBER_MATCH) then MMOSimca@400: currently_drunk = nil MMOSimca@400: end MMOSimca@400: return MMOSimca@400: end MMOSimca@400: MMOSimca@400: for index = 1, #DRUNK_MATCHES do MMOSimca@400: if message == DRUNK_COMPARES[index] or message:match(DRUNK_MATCHES[index]) then MMOSimca@400: currently_drunk = true MMOSimca@400: break MMOSimca@400: end jcallahan@40: end jcallahan@40: return jcallahan@40: end jcallahan@40: MMOSimca@411: -- If it is an item, parse its link MMOSimca@400: RecordItemData(item_id, item_link, true) jcallahan@40: end jcallahan@40: end jcallahan@40: jcallahan@307: jcallahan@331: do jcallahan@23: local FLAGS_NPC = bit.bor(_G.COMBATLOG_OBJECT_TYPE_GUARDIAN, _G.COMBATLOG_OBJECT_CONTROL_NPC) jcallahan@23: local FLAGS_NPC_CONTROL = bit.bor(_G.COMBATLOG_OBJECT_AFFILIATION_OUTSIDER, _G.COMBATLOG_OBJECT_CONTROL_NPC) jcallahan@23: MMOSimca@458: -- Spells that are cast by players/NPCs that are mistakely assigned as being cast by the target; must be blacklisted MMOSimca@405: local BLACKLISTED_SPELLS = { MMOSimca@458: [117526] = true, -- Binding Shot (cast by Hunters) MMOSimca@458: [121308] = true, -- Disguise (cast by Rogues) MMOSimca@458: [132464] = true, -- Chi Wave (cast by Monks) MMOSimca@458: [132467] = true, -- Chi Wave (cast by Monks) MMOSimca@460: [167432] = true, -- Savagery (cast by Warsong Commander) MMOSimca@460: [175077] = true, -- Fearsome Battle Standard (cast by Fearsome Battle Standard item) MMOSimca@458: [176813] = true, -- Itchy Spores (cast by Marsh Creatures in Ashran) MMOSimca@458: [183901] = true, -- Stolen Strength (cast by Felblood NPCs in Tanaan Jungle) MMOSimca@458: [183904] = true, -- Stolen Speed (cast by Felblood NPCs in Tanaan Jungle) MMOSimca@458: [183907] = true, -- Stolen Fervor (cast by Felblood NPCs in Tanaan Jungle) catherton@474: [213738] = true, -- Taste of Blood (applied by Fate and Fortune, Combat Rogue artifacts) mmosimca@483: [213877] = true, -- Vampiric Aura (used by Nathrezim Invasion bosses and transformed players) catherton@474: [215377] = true, -- The Maw Must Feed (applied by Maw of the Damned, Blood Death Knight artifact) catherton@470: [224762] = true, -- Leyline Rift (summoned by players with Leyline Mastery in Suramar) catherton@474: [225832] = true, -- Nightglow Wisp (cast by players using Wisp in a Bottle toy) mmosimca@493: MMOSimca@405: } MMOSimca@405: jcallahan@23: local function RecordNPCSpell(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id, spell_name) MMOSimca@405: if not spell_id or BLACKLISTED_SPELLS[spell_id] then jcallahan@23: return jcallahan@23: end jcallahan@34: local source_type, source_id = ParseGUID(source_guid) jcallahan@23: jcallahan@171: if not source_id or not UnitTypeIsNPC(source_type) then jcallahan@23: return jcallahan@23: end jcallahan@23: jcallahan@23: if bit.band(FLAGS_NPC_CONTROL, source_flags) == FLAGS_NPC_CONTROL and bit.band(FLAGS_NPC, source_flags) ~= 0 then jcallahan@248: local encounter_data = NPCEntry(source_id):EncounterData(InstanceDifficultyToken()) jcallahan@28: encounter_data.spells = encounter_data.spells or {} jcallahan@28: encounter_data.spells[spell_id] = (encounter_data.spells[spell_id] or 0) + 1 jcallahan@23: end jcallahan@23: end jcallahan@23: jcallahan@115: local HEAL_BATTLE_PETS_SPELL_ID = 125801 jcallahan@115: jcallahan@246: local previous_combat_event = {} jcallahan@246: jcallahan@23: local COMBAT_LOG_FUNCS = { jcallahan@23: SPELL_AURA_APPLIED = RecordNPCSpell, jcallahan@23: SPELL_CAST_START = RecordNPCSpell, jcallahan@115: SPELL_CAST_SUCCESS = function(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id, spell_name) jcallahan@115: if spell_id == HEAL_BATTLE_PETS_SPELL_ID then jcallahan@115: local unit_type, unit_idnum = ParseGUID(source_guid) jcallahan@115: jcallahan@171: if unit_idnum and UnitTypeIsNPC(unit_type) then jcallahan@115: NPCEntry(unit_idnum).stable_master = true jcallahan@115: end jcallahan@115: end jcallahan@115: RecordNPCSpell(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id, spell_name) jcallahan@115: end, jcallahan@65: UNIT_DIED = function(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id, spell_name) jcallahan@65: local unit_type, unit_idnum = ParseGUID(dest_guid) jcallahan@65: jcallahan@171: if not unit_idnum or not UnitTypeIsNPC(unit_type) then jcallahan@245: Debug("%s: %s is not an NPC, or has no ID.", sub_event, dest_name or _G.UNKNOWN) jcallahan@177: ClearKilledNPC() jcallahan@98: private.harvesting = nil jcallahan@65: return jcallahan@65: end jcallahan@177: jcallahan@246: if source_guid == "" then jcallahan@246: source_guid = nil jcallahan@246: end jcallahan@246: local killer_guid = source_guid or previous_combat_event.source_guid jcallahan@246: local killer_name = source_name or previous_combat_event.source_name jcallahan@246: jcallahan@299: if not previous_combat_event.party_damage then jcallahan@312: --Debug("%s: %s was killed by %s (not group member or pet).", sub_event, dest_name or _G.UNKNOWN, killer_name or _G.UNKNOWN) -- broken in Patch 5.4 jcallahan@299: table.wipe(previous_combat_event) jcallahan@217: ClearKilledNPC() jcallahan@306: else jcallahan@317: --Debug("%s: %s was killed by %s.", sub_event, dest_name or _G.UNKNOWN, killer_name or _G.UNKNOWN) -- broken in Patch 5.4 jcallahan@177: end jcallahan@177: killed_npc_id = unit_idnum MMOSimca@383: C_Timer.After(0.1, ClearKilledNPC) jcallahan@65: end, jcallahan@23: } jcallahan@23: jcallahan@23: jcallahan@246: local NON_DAMAGE_EVENTS = { jcallahan@246: SPELL_AURA_APPLIED = true, jcallahan@246: SPELL_AURA_REMOVED = true, jcallahan@246: SPELL_AURA_REMOVED_DOSE = true, jcallahan@246: SPELL_CAST_FAILED = true, jcallahan@246: SWING_MISSED = true, jcallahan@246: } jcallahan@246: jcallahan@299: local DAMAGE_EVENTS = { jcallahan@299: RANGE_DAMAGE = true, jcallahan@299: SPELL_BUILDING_DAMAGE = true, jcallahan@299: SPELL_DAMAGE = true, jcallahan@299: SPELL_PERIODIC_DAMAGE = true, jcallahan@299: SWING_DAMAGE = true, jcallahan@299: } jcallahan@299: jcallahan@246: jcallahan@92: function WDP:COMBAT_LOG_EVENT_UNFILTERED(event_name, time_stamp, sub_event, hide_caster, source_guid, source_name, source_flags, source_raid_flags, dest_guid, dest_name, dest_flags, dest_raid_flags, ...) jcallahan@23: local combat_log_func = COMBAT_LOG_FUNCS[sub_event] jcallahan@23: jcallahan@23: if not combat_log_func then jcallahan@299: if DAMAGE_EVENTS[sub_event] then jcallahan@299: table.wipe(previous_combat_event) jcallahan@246: previous_combat_event.source_name = source_name jcallahan@299: jcallahan@299: if source_guid ~= dest_guid and (in_instance or group_member_guids[source_guid] or group_pet_guids[source_guid]) then jcallahan@299: previous_combat_event.party_damage = true jcallahan@299: end jcallahan@246: end jcallahan@23: return jcallahan@23: end jcallahan@23: combat_log_func(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, ...) jcallahan@297: jcallahan@297: if NON_DAMAGE_EVENTS[sub_event] then jcallahan@297: table.wipe(previous_combat_event) jcallahan@297: end jcallahan@23: end jcallahan@23: catherton@465: jcallahan@44: local DIPLOMACY_SPELL_ID = 20599 jcallahan@44: local MR_POP_RANK1_SPELL_ID = 78634 jcallahan@44: local MR_POP_RANK2_SPELL_ID = 78635 MMOSimca@418: local TRADING_PACT_SPELL_ID = 170200 jcallahan@44: jcallahan@44: jcallahan@92: function WDP:COMBAT_TEXT_UPDATE(event_name, message_type, faction_name, amount) jcallahan@177: if message_type ~= "FACTION" or not killed_npc_id then jcallahan@44: return jcallahan@44: end jcallahan@44: UpdateFactionData() jcallahan@44: jcallahan@46: if not faction_name or not faction_standings[faction_name] then jcallahan@46: return jcallahan@46: end jcallahan@177: local npc = NPCEntry(killed_npc_id) jcallahan@177: ClearKilledNPC() jcallahan@46: jcallahan@44: if not npc then jcallahan@98: private.harvesting = nil jcallahan@44: return jcallahan@44: end jcallahan@98: npc.harvested = private.harvesting jcallahan@98: private.harvesting = nil jcallahan@98: jcallahan@44: local modifier = 1 jcallahan@44: MMOSimca@340: -- Check for modifiers from known spells jcallahan@44: if _G.IsSpellKnown(DIPLOMACY_SPELL_ID) then jcallahan@44: modifier = modifier + 0.1 jcallahan@44: end jcallahan@44: if _G.IsSpellKnown(MR_POP_RANK2_SPELL_ID) then jcallahan@44: modifier = modifier + 0.1 jcallahan@44: elseif _G.IsSpellKnown(MR_POP_RANK1_SPELL_ID) then jcallahan@44: modifier = modifier + 0.05 jcallahan@44: end MMOSimca@418: if _G.IsSpellKnown(TRADING_PACT_SPELL_ID) then MMOSimca@418: modifier = modifier + 0.2 MMOSimca@418: end jcallahan@44: MMOSimca@340: -- Determine faction ID MMOSimca@340: local faction_ID MMOSimca@418: for pseudo_faction_name, faction_data_table in pairs(private.FACTION_DATA) do MMOSimca@418: if faction_name == faction_data_table[3] then MMOSimca@418: -- Check ignore flag MMOSimca@418: if faction_data_table[2] then MMOSimca@418: return MMOSimca@418: end MMOSimca@340: faction_ID = faction_data_table[1] MMOSimca@418: break MMOSimca@340: end MMOSimca@340: end MMOSimca@340: if faction_ID and faction_ID > 0 then MMOSimca@340: -- Check for modifiers from Commendations (applied directly to the faction, account-wide) MMOSimca@340: local _, _, _, _, _, _, _, _, _, _, _, _, _, _, has_bonus_rep_gain = GetFactionInfoByID(faction_ID) MMOSimca@340: if has_bonus_rep_gain then MMOSimca@340: modifier = modifier + 1 MMOSimca@340: end MMOSimca@340: end MMOSimca@340: MMOSimca@340: -- Check for modifiers from buffs MMOSimca@418: for buff_name, buff_data_table in pairs(private.REP_BUFFS) do jcallahan@44: if _G.UnitBuff("player", buff_name) then MMOSimca@340: local modded_faction = buff_data_table.faction jcallahan@44: jcallahan@44: if not modded_faction or faction_name == modded_faction then MMOSimca@340: if buff_data_table.ignore then MMOSimca@340: -- Some buffs from tabards convert all rep of other factions into rep for a specific faction. MMOSimca@340: -- We can't know what faction the rep was orginally from, so we must ignore the data entirely in these cases. MMOSimca@340: return MMOSimca@340: else MMOSimca@340: modifier = modifier + buff_data_table.modifier MMOSimca@340: end jcallahan@44: end jcallahan@44: end jcallahan@44: end catherton@465: jcallahan@65: npc.reputations = npc.reputations or {} jcallahan@65: npc.reputations[("%s:%s"):format(faction_name, faction_standings[faction_name])] = math.floor(amount / modifier) jcallahan@32: end jcallahan@44: end -- do-block jcallahan@18: jcallahan@18: jcallahan@140: function WDP:CURSOR_UPDATE(event_name) MMOSimca@355: if current_action.fishing_target or _G.Minimap:IsMouseOver() then jcallahan@140: return jcallahan@140: end jcallahan@140: local text = _G["GameTooltipTextLeft1"]:GetText() jcallahan@140: MMOSimca@355: -- Handle Fishing MMOSimca@355: if (current_action.spell_label == "FISHING") then MMOSimca@355: if not text or text == "Fishing Bobber" then MMOSimca@355: text = "NONE" MMOSimca@355: else MMOSimca@355: current_action.fishing_target = true MMOSimca@355: end MMOSimca@355: current_action.identifier = ("%s:%s"):format(current_action.spell_label, text) MMOSimca@355: -- Handle Garrison Cache MMOSimca@355: elseif private.GARRISON_CACHE_OBJECT_NAME_TO_OBJECT_ID_MAP[text] then MMOSimca@355: last_garrison_cache_object_id = private.GARRISON_CACHE_OBJECT_NAME_TO_OBJECT_ID_MAP[text] jcallahan@140: end jcallahan@140: end jcallahan@140: jcallahan@140: jcallahan@92: function WDP:ITEM_TEXT_BEGIN(event_name) jcallahan@42: local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc")) jcallahan@42: jcallahan@42: if not unit_idnum or unit_type ~= private.UNIT_TYPES.OBJECT or _G.UnitName("npc") ~= _G.ItemTextGetItem() then jcallahan@42: return jcallahan@42: end jcallahan@42: UpdateDBEntryLocation("objects", unit_idnum) jcallahan@42: end jcallahan@42: jcallahan@42: jcallahan@13: do MMOSimca@343: local LOOT_OPENED_VERIFY_FUNCS = { jcallahan@324: -- Item containers can be AOE-looted in Patch 5.4.2 if the user clicks fast enough, but this verification still works as long as they both have loot. jcallahan@16: [AF.ITEM] = function() jcallahan@16: local locked_item_id jcallahan@16: jcallahan@16: for bag_index = 0, _G.NUM_BAG_FRAMES do jcallahan@16: for slot_index = 1, _G.GetContainerNumSlots(bag_index) do jcallahan@324: local _, _, is_locked, _, _, is_lootable = _G.GetContainerItemInfo(bag_index, slot_index) jcallahan@324: jcallahan@324: if is_locked and is_lootable then jcallahan@16: locked_item_id = ItemLinkToID(_G.GetContainerItemLink(bag_index, slot_index)) jcallahan@165: break jcallahan@16: end jcallahan@16: end jcallahan@165: jcallahan@165: if locked_item_id then jcallahan@165: break jcallahan@165: end jcallahan@16: end jcallahan@16: MMOSimca@367: if (not current_action.spell_label == "DISENCHANT") and (not locked_item_id or (current_action.identifier and current_action.identifier ~= locked_item_id)) then jcallahan@16: return false jcallahan@16: end jcallahan@122: current_action.identifier = locked_item_id jcallahan@16: return true jcallahan@16: end, MMOSimca@401: [AF.NPC] = function() MMOSimca@401: return not _G.IsFishingLoot() MMOSimca@401: end, MMOSimca@401: [AF.OBJECT] = function() MMOSimca@401: return not _G.IsFishingLoot() MMOSimca@401: end, jcallahan@17: [AF.ZONE] = function() jcallahan@140: current_action.zone_data = UpdateDBEntryLocation("zones", current_action.identifier) jcallahan@210: return _G.IsFishingLoot() jcallahan@17: end, jcallahan@13: } jcallahan@13: jcallahan@13: MMOSimca@343: local LOOT_OPENED_UPDATE_FUNCS = { jcallahan@16: [AF.ITEM] = function() jcallahan@28: GenericLootUpdate("items") jcallahan@28: end, jcallahan@28: [AF.NPC] = function() jcallahan@75: local difficulty_token = InstanceDifficultyToken() jcallahan@312: local loot_label = current_loot.label jcallahan@77: local source_list = {} jcallahan@75: jcallahan@131: for source_guid, loot_data in pairs(current_loot.sources) do jcallahan@331: local _, source_id = ParseGUID(source_guid) jcallahan@75: local npc = NPCEntry(source_id) jcallahan@75: jcallahan@75: if npc then jcallahan@248: local encounter_data = npc:EncounterData(difficulty_token) jcallahan@312: encounter_data[loot_label] = encounter_data[loot_label] or {} jcallahan@75: jcallahan@78: if not source_list[source_guid] then jcallahan@77: encounter_data.loot_counts = encounter_data.loot_counts or {} MMOSimca@426: encounter_data.loot_counts[loot_label] = (encounter_data.loot_counts[loot_label] or 0) + 1 jcallahan@312: source_list[source_guid] = true jcallahan@77: end jcallahan@77: jcallahan@309: for loot_token, quantity in pairs(loot_data) do jcallahan@312: local loot_type, currency_texture = (":"):split(loot_token) jcallahan@312: jcallahan@312: if loot_type == "currency" and currency_texture then jcallahan@312: table.insert(encounter_data[loot_label], ("currency:%d:%s"):format(quantity, currency_texture)) jcallahan@309: elseif loot_token == "money" then jcallahan@312: table.insert(encounter_data[loot_label], ("money:%d"):format(quantity)) jcallahan@309: else jcallahan@312: table.insert(encounter_data[loot_label], ("%d:%d"):format(loot_token, quantity)) jcallahan@309: end jcallahan@75: end jcallahan@75: end jcallahan@75: end jcallahan@16: end, jcallahan@13: [AF.OBJECT] = function() jcallahan@28: GenericLootUpdate("objects", InstanceDifficultyToken()) jcallahan@17: end, jcallahan@17: [AF.ZONE] = function() MMOSimca@328: if not (current_loot.map_level and current_loot.x and current_loot.y and current_loot.zone_data) then MMOSimca@328: return MMOSimca@328: end jcallahan@141: local location_token = ("%d:%d:%d"):format(current_loot.map_level, current_loot.x, current_loot.y) jcallahan@41: jcallahan@41: -- This will start life as a boolean true. jcallahan@131: if _G.type(current_loot.zone_data[location_token]) ~= "table" then jcallahan@131: current_loot.zone_data[location_token] = { jcallahan@41: drops = {} jcallahan@41: } jcallahan@41: end jcallahan@132: local loot_count = ("%s_count"):format(current_loot.label) jcallahan@131: current_loot.zone_data[location_token][loot_count] = (current_loot.zone_data[location_token][loot_count] or 0) + 1 jcallahan@41: jcallahan@131: if current_loot.sources then jcallahan@131: for source_guid, loot_data in pairs(current_loot.sources) do jcallahan@131: for item_id, quantity in pairs(loot_data) do jcallahan@131: table.insert(current_loot.zone_data[location_token].drops, ("%d:%d"):format(item_id, quantity)) jcallahan@131: end jcallahan@131: end jcallahan@131: end jcallahan@131: jcallahan@131: if #current_loot.list <= 0 then jcallahan@131: return jcallahan@131: end jcallahan@131: jcallahan@131: for index = 1, #current_loot.list do MMOSimca@443: table.insert(current_loot.zone_data[location_token].drops, current_loot.list[index]) jcallahan@41: end jcallahan@13: end, jcallahan@13: } jcallahan@13: jcallahan@79: -- Prevent opening the same loot window multiple times from recording data multiple times. jcallahan@79: local loot_guid_registry = {} jcallahan@124: jcallahan@124: jcallahan@124: function WDP:LOOT_CLOSED(event_name) MMOSimca@398: ClearChatLootData() jcallahan@131: current_loot = nil jcallahan@131: table.wipe(current_action) jcallahan@124: end jcallahan@124: jcallahan@13: jcallahan@322: local function ExtrapolatedCurrentActionFromLootData(event_name) MMOSimca@402: local log_source = event_name .. "- ExtrapolatedCurrentActionFromLootData" MMOSimca@402: local previous_spell_label = current_action.spell_label jcallahan@322: local extrapolated_guid_registry = {} jcallahan@322: local num_guids = 0 MMOSimca@402: table.wipe(current_action) MMOSimca@402: MMOSimca@402: if _G.IsFishingLoot() then MMOSimca@402: -- Set up a proper 'fishing' current_action table MMOSimca@402: local zone_name, area_id, x, y, map_level, instance_token = CurrentLocationData() MMOSimca@402: if not (zone_name and area_id and x and y and map_level) then catherton@468: Debug("%s: Missing current location data - %s, %s, %s, %s, %s.", log_source, tostring(zone_name), tostring(area_id), tostring(x), tostring(y), tostring(map_level)) MMOSimca@402: return MMOSimca@402: end MMOSimca@402: current_action.instance_token = instance_token MMOSimca@402: current_action.map_level = map_level MMOSimca@402: current_action.x = x MMOSimca@402: current_action.y = y MMOSimca@402: current_action.zone_data = ("%s:%d"):format(zone_name, area_id) MMOSimca@402: current_action.spell_label = "FISHING" MMOSimca@402: current_action.loot_label = "fishing" MMOSimca@402: current_action.identifier = "FISHING:NONE" MMOSimca@402: current_action.target_type = AF.ZONE MMOSimca@402: MMOSimca@402: Debug("%s: Fishing loot detected.", log_source) MMOSimca@402: return true MMOSimca@402: end jcallahan@322: MMOSimca@344: -- Loot extrapolation cannot handle objects that need special spell labels (like HERBALISM or MINING) (MIND_CONTROL is okay) MMOSimca@402: if previous_spell_label and private.SPELL_FLAGS_BY_LABEL[previous_spell_label] and not private.NON_LOOT_SPELL_LABELS[previous_spell_label] then MMOSimca@344: 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) MMOSimca@344: return false MMOSimca@344: end MMOSimca@344: jcallahan@322: for loot_slot = 1, _G.GetNumLootItems() do jcallahan@322: local loot_info = { jcallahan@322: _G.GetLootSourceInfo(loot_slot) jcallahan@322: } jcallahan@322: jcallahan@322: for loot_index = 1, #loot_info, 2 do jcallahan@322: local source_guid = loot_info[loot_index] jcallahan@322: jcallahan@322: if not extrapolated_guid_registry[source_guid] then jcallahan@322: local unit_type, unit_idnum = ParseGUID(source_guid) jcallahan@322: jcallahan@322: if unit_type and unit_idnum then jcallahan@322: extrapolated_guid_registry[source_guid] = { jcallahan@322: unit_type, jcallahan@322: unit_idnum jcallahan@322: } jcallahan@322: jcallahan@322: num_guids = num_guids + 1 jcallahan@322: end jcallahan@322: end jcallahan@322: end jcallahan@322: end jcallahan@322: jcallahan@322: if num_guids == 0 then jcallahan@322: Debug("%s: No GUIDs found in loot. Blank loot window?", log_source) jcallahan@322: return false jcallahan@322: end jcallahan@326: jcallahan@322: local num_npcs = 0 jcallahan@322: local num_objects = 0 jcallahan@324: local num_itemcontainers = 0 jcallahan@322: jcallahan@322: for source_guid, guid_data in pairs(extrapolated_guid_registry) do jcallahan@322: local unit_type = guid_data[1] jcallahan@324: local loot_label = (unit_type == private.UNIT_TYPES.OBJECT) and "opening" or (UnitTypeIsNPC(unit_type) and "drops") or ((unit_type == private.UNIT_TYPES.PLAYER) and "contains") jcallahan@322: jcallahan@322: if loot_label then jcallahan@322: local unit_idnum = guid_data[2] jcallahan@322: jcallahan@322: if loot_guid_registry[loot_label] and loot_guid_registry[loot_label][source_guid] then jcallahan@322: Debug("%s: Previously scanned loot for unit with GUID %s and identifier %s.", log_source, source_guid, unit_idnum) jcallahan@322: elseif unit_type == private.UNIT_TYPES.OBJECT and unit_idnum ~= OBJECT_ID_FISHING_BOBBER then jcallahan@322: current_action.loot_label = loot_label jcallahan@322: current_action.spell_label = "OPENING" jcallahan@322: current_action.target_type = AF.OBJECT jcallahan@322: current_action.identifier = unit_idnum jcallahan@322: num_objects = num_objects + 1 jcallahan@322: elseif UnitTypeIsNPC(unit_type) then jcallahan@322: current_action.loot_label = loot_label jcallahan@322: current_action.target_type = AF.NPC jcallahan@322: current_action.identifier = unit_idnum jcallahan@322: num_npcs = num_npcs + 1 jcallahan@324: elseif unit_type == private.UNIT_TYPES.PLAYER then jcallahan@331: -- Item container GUIDs are currently of the 'PLAYER' type; this may be unintended and could change in the future. jcallahan@324: current_action.loot_label = loot_label jcallahan@324: current_action.target_type = AF.ITEM jcallahan@324: -- current_action.identifier assigned during loot verification. jcallahan@324: num_itemcontainers = num_itemcontainers + 1 jcallahan@322: end jcallahan@322: else jcallahan@322: -- Bail here; not only do we not know what this unit is, but we don't want to attribute loot to something that doesn't actually drop it. jcallahan@322: Debug("%s: Unit with GUID %s has unsupported type for looting.", log_source, source_guid) jcallahan@322: return false jcallahan@322: end jcallahan@322: end jcallahan@322: jcallahan@322: if not current_action.target_type then jcallahan@322: Debug("%s: Failure to obtain target_type.", log_source) jcallahan@322: return false jcallahan@322: end jcallahan@322: jcallahan@322: -- We can't figure out what dropped the loot. This shouldn't ever happen, but hey - Blizzard does some awesome stuff on occasion. jcallahan@324: if (num_npcs > 0 and num_objects + num_itemcontainers > 0) or (num_objects > 0 and num_npcs + num_itemcontainers > 0) or (num_itemcontainers > 0 and num_npcs + num_objects > 0) then jcallahan@324: Debug("%s: Mixed target types are not supported. NPCs - %d, Objects - %d, Item Containers - %d.", log_source, num_npcs, num_objects, num_itemcontainers) jcallahan@322: return false jcallahan@322: end jcallahan@322: jcallahan@322: return true jcallahan@322: end jcallahan@322: jcallahan@322: MMOSimca@343: function WDP:LOOT_OPENED(event_name) MMOSimca@398: ClearChatLootData() MMOSimca@387: jcallahan@132: if current_loot then jcallahan@322: current_loot = nil jcallahan@322: Debug("%s: Previous loot did not process in time for this event. Attempting to extrapolate current_action from loot data.", event_name) jcallahan@322: jcallahan@322: if not ExtrapolatedCurrentActionFromLootData(event_name) then jcallahan@322: Debug("%s: Unable to extrapolate current_action. Aborting attempts to handle loot for now.", event_name) jcallahan@322: return jcallahan@322: end jcallahan@18: end jcallahan@151: jcallahan@151: if not current_action.target_type then jcallahan@322: if not ExtrapolatedCurrentActionFromLootData(event_name) then jcallahan@322: Debug("%s: Unable to extrapolate current_action. Aborting attempts to handle loot for now.", event_name) jcallahan@322: return jcallahan@322: end jcallahan@151: end MMOSimca@343: local verify_func = LOOT_OPENED_VERIFY_FUNCS[current_action.target_type] MMOSimca@343: local update_func = LOOT_OPENED_UPDATE_FUNCS[current_action.target_type] jcallahan@13: jcallahan@14: if not verify_func or not update_func then jcallahan@322: Debug("%s: The current action's target type is unsupported or nil.", event_name) jcallahan@13: return jcallahan@13: end jcallahan@13: jcallahan@14: if _G.type(verify_func) == "function" and not verify_func() then jcallahan@324: Debug("%s: The current action type, %s, is supported but has failed loot verification.", event_name, private.ACTION_TYPE_NAMES[current_action.target_type]) jcallahan@14: return jcallahan@14: end jcallahan@80: local guids_used = {} jcallahan@132: jcallahan@301: InitializeCurrentLoot() jcallahan@217: loot_guid_registry[current_loot.label] = loot_guid_registry[current_loot.label] or {} jcallahan@217: jcallahan@55: for loot_slot = 1, _G.GetNumLootItems() do catherton@463: local texturefiledataID, item_text, slot_quantity, quality, locked = _G.GetLootSlotInfo(loot_slot) jcallahan@55: local slot_type = _G.GetLootSlotType(loot_slot) catherton@463: local loot_info = { _G.GetLootSourceInfo(loot_slot) } catherton@464: local loot_link = _G.GetLootSlotLink(loot_slot) jcallahan@13: jcallahan@237: -- Odd index is GUID, even is count. jcallahan@237: for loot_index = 1, #loot_info, 2 do jcallahan@237: local source_guid = loot_info[loot_index] jcallahan@75: jcallahan@237: if not loot_guid_registry[current_loot.label][source_guid] then jcallahan@237: local loot_quantity = loot_info[loot_index + 1] jcallahan@324: -- There is a new bug in 5.4.0 that causes GetLootSlotInfo() to (rarely) return nil values for slot_quantity. jcallahan@324: if slot_quantity then jcallahan@324: -- We need slot_quantity to account for an old bug where loot_quantity is sometimes '1' for stacks of items, such as cloth. jcallahan@324: if slot_quantity > loot_quantity then jcallahan@324: loot_quantity = slot_quantity jcallahan@324: end jcallahan@324: local source_type, source_id = ParseGUID(source_guid) MMOSimca@329: local source_key = ("%s:%d"):format(source_type, source_id) jcallahan@324: MMOSimca@366: if slot_type == LOOT_SLOT_ITEM then catherton@464: if loot_link then catherton@464: local item_id = ItemLinkToID(loot_link) catherton@464: 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) catherton@464: current_loot.sources[source_guid] = current_loot.sources[source_guid] or {} catherton@464: current_loot.sources[source_guid][item_id] = (current_loot.sources[source_guid][item_id] or 0) + loot_quantity catherton@464: guids_used[source_guid] = true catherton@463: else catherton@463: Debug("%s: Loot link 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) catherton@463: end MMOSimca@366: elseif slot_type == LOOT_SLOT_MONEY then jcallahan@324: Debug("GUID: %s - Type:ID: %s - Money - Amount: %d (%d)", loot_info[loot_index], source_key, loot_info[loot_index + 1], slot_quantity) jcallahan@324: if current_loot.target_type == AF.ZONE then jcallahan@324: table.insert(current_loot.list, ("money:%d"):format(loot_quantity)) jcallahan@324: else jcallahan@324: current_loot.sources[source_guid] = current_loot.sources[source_guid] or {} MMOSimca@367: current_loot.sources[source_guid]["money"] = (current_loot.sources[source_guid]["money"] or 0) + loot_quantity jcallahan@324: guids_used[source_guid] = true jcallahan@324: end MMOSimca@366: elseif slot_type == LOOT_SLOT_CURRENCY then jcallahan@324: -- Same bug with GetLootSlotInfo() will screw up currency when it happens, so we won't process this slot's loot. catherton@463: if loot_link then catherton@464: local icon_texture = CurrencyLinkToTexture(loot_link) jcallahan@324: Debug("GUID: %s - Type:ID: %s - Currency: %s - Amount: %d (%d)", loot_info[loot_index], source_key, icon_texture, loot_info[loot_index + 1], slot_quantity) jcallahan@324: if current_loot.target_type == AF.ZONE then catherton@463: table.insert(current_loot.list, ("currency:%d:%s"):format(loot_quantity, icon_texture)) jcallahan@324: else catherton@463: local currency_token = ("currency:%s"):format(icon_texture) jcallahan@324: jcallahan@324: current_loot.sources[source_guid] = current_loot.sources[source_guid] or {} MMOSimca@367: current_loot.sources[source_guid][currency_token] = (current_loot.sources[source_guid][currency_token] or 0) + loot_quantity jcallahan@324: guids_used[source_guid] = true jcallahan@324: end jcallahan@324: else catherton@463: Debug("%s: Loot link 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) jcallahan@324: end jcallahan@308: end jcallahan@324: else jcallahan@324: -- 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. catherton@463: 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) jcallahan@79: end jcallahan@75: end jcallahan@13: end jcallahan@13: end jcallahan@80: jcallahan@81: for guid in pairs(guids_used) do jcallahan@217: loot_guid_registry[current_loot.label][guid] = true jcallahan@80: end jcallahan@13: update_func() jcallahan@1: end jcallahan@13: end -- do-block jcallahan@0: jcallahan@0: jcallahan@89: function WDP:MAIL_SHOW(event_name) MMOSimca@436: WDP:StopChatLootRecording(event_name) jcallahan@89: local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc")) jcallahan@89: jcallahan@89: if not unit_idnum or unit_type ~= private.UNIT_TYPES.OBJECT then jcallahan@89: return jcallahan@89: end jcallahan@89: UpdateDBEntryLocation("objects", unit_idnum) jcallahan@89: end jcallahan@89: jcallahan@89: jcallahan@44: do jcallahan@44: local POINT_MATCH_PATTERNS = { jcallahan@44: ("^%s$"):format(_G.ITEM_REQ_ARENA_RATING:gsub("%%d", "(%%d+)")), -- May no longer be necessary jcallahan@44: ("^%s$"):format(_G.ITEM_REQ_ARENA_RATING_3V3:gsub("%%d", "(%%d+)")), -- May no longer be necessary jcallahan@44: ("^%s$"):format(_G.ITEM_REQ_ARENA_RATING_5V5:gsub("%%d", "(%%d+)")), -- May no longer be necessary jcallahan@44: ("^%s$"):format(_G.ITEM_REQ_ARENA_RATING_BG:gsub("%%d", "(%%d+)")), jcallahan@44: ("^%s$"):format(_G.ITEM_REQ_ARENA_RATING_3V3_BG:gsub("%%d", "(%%d+)")), jcallahan@44: } jcallahan@5: jcallahan@68: local ITEM_REQ_REPUTATION_MATCH = "Requires (.-) %- (.*)" jcallahan@87: local ITEM_REQ_QUEST_MATCH1 = "Requires: .*" jcallahan@87: local ITEM_REQ_QUEST_MATCH2 = "Must have completed: .*" jcallahan@68: jcallahan@133: local current_merchant jcallahan@133: local merchant_standing jcallahan@133: jcallahan@133: function WDP:MERCHANT_CLOSED(event_name) MMOSimca@436: WDP:ResumeChatLootRecording(event_name) jcallahan@133: current_merchant = nil jcallahan@133: merchant_standing = nil jcallahan@133: end jcallahan@133: jcallahan@133: jcallahan@89: function WDP:UpdateMerchantItems(event_name) jcallahan@144: if not current_merchant or event_name == "MERCHANT_SHOW" then MMOSimca@436: WDP:StopChatLootRecording(event_name) jcallahan@195: local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc")) jcallahan@4: jcallahan@171: if not unit_idnum or not UnitTypeIsNPC(unit_type) then jcallahan@133: return jcallahan@133: end jcallahan@331: local _, faction_standing = UnitFactionStanding("npc") jcallahan@331: merchant_standing = faction_standing jcallahan@133: current_merchant = NPCEntry(unit_idnum) jcallahan@133: current_merchant.sells = current_merchant.sells or {} jcallahan@44: end jcallahan@55: local current_filters = _G.GetMerchantFilter() jcallahan@57: _G.SetMerchantFilter(_G.LE_LOOT_FILTER_ALL) jcallahan@57: _G.MerchantFrame_Update() jcallahan@57: jcallahan@150: local num_items = _G.GetMerchantNumItems() jcallahan@150: jcallahan@44: for item_index = 1, num_items do jcallahan@44: local _, _, copper_price, stack_size, num_available, _, extended_cost = _G.GetMerchantItemInfo(item_index) jcallahan@44: local item_id = ItemLinkToID(_G.GetMerchantItemLink(item_index)) jcallahan@5: jcallahan@324: DatamineTT:ClearLines() jcallahan@324: DatamineTT:SetMerchantItem(item_index) jcallahan@324: jcallahan@324: if not item_id then jcallahan@324: local item_name, item_link = DatamineTT:GetItem() jcallahan@324: item_id = ItemLinkToID(item_link) MMOSimca@354: -- GetMerchantItemLink() still ocassionally fails as of Patch 6.0.2. It fails so badly that debug functions cause considerable slowdown. jcallahan@324: end jcallahan@324: jcallahan@44: if item_id and item_id > 0 then jcallahan@44: local price_string = ActualCopperCost(copper_price, merchant_standing) jcallahan@5: jcallahan@68: local num_lines = DatamineTT:NumLines() jcallahan@68: jcallahan@68: for line_index = 1, num_lines do jcallahan@68: local current_line = _G["WDPDatamineTTTextLeft" .. line_index] jcallahan@68: jcallahan@68: if not current_line then jcallahan@68: break jcallahan@68: end jcallahan@68: local faction, reputation = current_line:GetText():match(ITEM_REQ_REPUTATION_MATCH) jcallahan@68: jcallahan@68: if faction or reputation then jcallahan@68: DBEntry("items", item_id).req_reputation = ("%s:%s"):format(faction:gsub("-", ""), reputation:upper()) jcallahan@68: break jcallahan@68: end jcallahan@68: end jcallahan@68: jcallahan@87: for line_index = 1, num_lines do jcallahan@87: local current_line = _G["WDPDatamineTTTextLeft" .. line_index] jcallahan@87: jcallahan@87: if not current_line then jcallahan@87: break jcallahan@87: end jcallahan@87: local line_text = current_line:GetText() jcallahan@87: local quest_name = line_text:match(ITEM_REQ_QUEST_MATCH1) or line_text:match(ITEM_REQ_QUEST_MATCH2) jcallahan@87: jcallahan@87: if quest_name then jcallahan@87: DBEntry("items", item_id).req_quest = ("%s"):format(quest_name:gsub("(.+): ", ""), quest_name) jcallahan@87: break jcallahan@87: end jcallahan@87: end jcallahan@87: jcallahan@44: if extended_cost then jcallahan@53: local battleground_rating = 0 jcallahan@53: local personal_rating = 0 jcallahan@53: local required_season_amount jcallahan@5: jcallahan@68: for line_index = 1, num_lines do jcallahan@44: local current_line = _G["WDPDatamineTTTextLeft" .. line_index] jcallahan@5: jcallahan@44: if not current_line then jcallahan@44: break jcallahan@44: end jcallahan@53: required_season_amount = current_line:GetText():match("Requires earning a total of (%d+)\n(.-) for the season.") jcallahan@5: jcallahan@44: for match_index = 1, #POINT_MATCH_PATTERNS do jcallahan@44: local match1, match2 = current_line:GetText():match(POINT_MATCH_PATTERNS[match_index]) jcallahan@53: personal_rating = personal_rating + (match1 or 0) jcallahan@53: battleground_rating = battleground_rating + (match2 or 0) jcallahan@5: jcallahan@44: if match1 or match2 then jcallahan@44: break jcallahan@44: end jcallahan@44: end jcallahan@5: end jcallahan@44: local currency_list = {} jcallahan@44: local item_count = _G.GetMerchantItemCostInfo(item_index) jcallahan@50: jcallahan@50: -- Keeping this around in case Blizzard makes the two points diverge at some point. jcallahan@53: -- price_string = ("%s:%s:%s:%s"):format(price_string, battleground_rating, personal_rating, required_season_amount or 0) jcallahan@53: price_string = ("%s:%s:%s"):format(price_string, personal_rating, required_season_amount or 0) jcallahan@5: jcallahan@44: for cost_index = 1, item_count do jcallahan@324: -- The third return (Blizz calls "currency_link") of GetMerchantItemCostItem only returns item links as of Patch 5.3.0. jcallahan@317: local icon_texture, amount_required, item_link = _G.GetMerchantItemCostItem(item_index, cost_index) jcallahan@317: local currency_identifier = item_link and ItemLinkToID(item_link) or nil jcallahan@317: jcallahan@317: if (not currency_identifier or currency_identifier < 1) and icon_texture then jcallahan@317: currency_identifier = icon_texture:match("[^\\]+$"):lower() jcallahan@317: end jcallahan@317: jcallahan@317: if currency_identifier then jcallahan@317: currency_list[#currency_list + 1] = ("(%s:%s)"):format(amount_required, currency_identifier) jcallahan@44: end jcallahan@44: end jcallahan@44: jcallahan@44: for currency_index = 1, #currency_list do jcallahan@44: price_string = ("%s:%s"):format(price_string, currency_list[currency_index]) jcallahan@5: end jcallahan@5: end jcallahan@133: current_merchant.sells[item_id] = ("%s:%s:[%s]"):format(num_available, stack_size, price_string) jcallahan@44: end jcallahan@44: end jcallahan@5: jcallahan@44: if _G.CanMerchantRepair() then jcallahan@133: current_merchant.can_repair = true jcallahan@5: end jcallahan@57: _G.SetMerchantFilter(current_filters) jcallahan@57: _G.MerchantFrame_Update() jcallahan@4: end jcallahan@44: end -- do-block jcallahan@4: jcallahan@89: jcallahan@92: function WDP:PET_BAR_UPDATE(event_name) MMOSimca@336: if not private.NON_LOOT_SPELL_LABELS[current_action.spell_label] then jcallahan@25: return jcallahan@25: end jcallahan@34: local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("pet")) jcallahan@25: jcallahan@171: if not unit_idnum or not UnitTypeIsNPC(unit_type) then jcallahan@25: return jcallahan@25: end jcallahan@29: NPCEntry(unit_idnum).mind_control = true jcallahan@122: table.wipe(current_action) jcallahan@25: end jcallahan@25: jcallahan@25: MMOSimca@368: -- This function produces data currently unused by wowdb.com, and it causes unneeded bloat in the raw lua DB. MMOSimca@442: --[[local LPJ = LibStub("LibPetJournal-2.0") MMOSimca@442: function WDP:PET_JOURNAL_LIST_UPDATE(event_name) MMOSimca@346: if DEBUGGING then jcallahan@309: return jcallahan@309: end jcallahan@309: jcallahan@115: local num_pets = LPJ:NumPets() jcallahan@115: jcallahan@115: for index, pet_id in LPJ:IteratePetIDs() do jcallahan@115: local _, _, is_owned, _, level, _, _, name, icon, pet_type, npc_id, _, _, is_wild = _G.C_PetJournal.GetPetInfoByIndex(index) jcallahan@115: jcallahan@115: if is_owned then jcallahan@115: local health, max_health, attack, speed, rarity = _G.C_PetJournal.GetPetStats(pet_id) jcallahan@115: jcallahan@139: if rarity then jcallahan@139: local rarity_name = _G["BATTLE_PET_BREED_QUALITY" .. rarity] jcallahan@139: local npc = NPCEntry(npc_id) jcallahan@139: npc.wild_pet = is_wild or nil jcallahan@139: npc.battle_pet_data = npc.battle_pet_data or {} jcallahan@139: npc.battle_pet_data[rarity_name] = npc.battle_pet_data[rarity_name] or {} jcallahan@139: npc.battle_pet_data[rarity_name]["level_" .. level] = npc.battle_pet_data[rarity_name]["level_" .. level] or {} jcallahan@139: jcallahan@139: local data = npc.battle_pet_data[rarity_name]["level_" .. level] jcallahan@139: data.max_health = max_health jcallahan@139: data.attack = attack jcallahan@139: data.speed = speed jcallahan@139: end jcallahan@115: end jcallahan@115: end MMOSimca@368: end]]-- jcallahan@115: jcallahan@115: jcallahan@156: function WDP:PLAYER_REGEN_DISABLED(event_name) jcallahan@156: private.in_combat = true jcallahan@156: end jcallahan@156: jcallahan@156: jcallahan@156: function WDP:PLAYER_REGEN_ENABLED(event_name) jcallahan@156: private.in_combat = nil jcallahan@156: end jcallahan@156: jcallahan@156: jcallahan@118: function WDP:PLAYER_TARGET_CHANGED(event_name) jcallahan@215: if not TargetedNPC() then jcallahan@118: return jcallahan@2: end jcallahan@151: current_action.target_type = AF.NPC jcallahan@118: self:UpdateTargetLocation() jcallahan@118: end jcallahan@2: jcallahan@89: jcallahan@12: do jcallahan@12: local function UpdateQuestJuncture(point) jcallahan@12: local unit_name = _G.UnitName("questnpc") jcallahan@9: jcallahan@12: if not unit_name then jcallahan@12: return jcallahan@12: end jcallahan@34: local unit_type, unit_id = ParseGUID(_G.UnitGUID("questnpc")) MMOSimca@351: Debug("UpdateQuestJuncture: Updating quest juncture for %s.", ("%s:%d"):format(private.UNIT_TYPE_NAMES[unit_type], unit_id)) jcallahan@12: if unit_type == private.UNIT_TYPES.OBJECT then jcallahan@38: UpdateDBEntryLocation("objects", unit_id) jcallahan@12: end jcallahan@19: local quest = DBEntry("quests", _G.GetQuestID()) jcallahan@12: quest[point] = quest[point] or {} MMOSimca@329: quest[point][("%s:%d"):format(private.UNIT_TYPE_NAMES[unit_type], unit_id)] = true jcallahan@24: jcallahan@24: return quest jcallahan@12: end jcallahan@10: jcallahan@12: jcallahan@92: function WDP:QUEST_COMPLETE(event_name) jcallahan@97: local quest = UpdateQuestJuncture("end") catherton@465: MMOSimca@446: if not quest then MMOSimca@446: return MMOSimca@446: end jcallahan@97: jcallahan@112: if ALLOWED_LOCALES[CLIENT_LOCALE] then jcallahan@112: quest.reward_text = ReplaceKeywords(_G.GetRewardText()) jcallahan@112: end jcallahan@67: -- Make sure the quest NPC isn't erroneously recorded as giving reputation for quests which award it. jcallahan@177: ClearKilledNPC() jcallahan@10: end jcallahan@10: jcallahan@12: jcallahan@92: function WDP:QUEST_DETAIL(event_name) jcallahan@24: local quest = UpdateQuestJuncture("begin") jcallahan@24: jcallahan@46: if not quest then jcallahan@46: return jcallahan@46: end jcallahan@24: quest.classes = quest.classes or {} jcallahan@27: quest.classes[PLAYER_CLASS] = true jcallahan@24: jcallahan@24: quest.races = quest.races or {} jcallahan@100: quest.races[(PLAYER_RACE == "Pandaren") and ("%s_%s"):format(PLAYER_RACE, PLAYER_FACTION or "Neutral") or PLAYER_RACE] = true jcallahan@10: end jcallahan@12: end -- do-block jcallahan@9: jcallahan@9: jcallahan@92: function WDP:QUEST_LOG_UPDATE(event_name) jcallahan@38: local selected_quest = _G.GetQuestLogSelection() -- Save current selection to be restored when we're done. jcallahan@36: local entry_index, processed_quests = 1, 0 jcallahan@36: local _, num_quests = _G.GetNumQuestLogEntries() jcallahan@36: jcallahan@36: while processed_quests <= num_quests do MMOSimca@329: local _, _, _, is_header, _, _, _, quest_id = _G.GetQuestLogTitle(entry_index) jcallahan@36: jcallahan@84: if quest_id == 0 then jcallahan@84: processed_quests = processed_quests + 1 jcallahan@84: elseif not is_header then jcallahan@36: _G.SelectQuestLogEntry(entry_index); jcallahan@36: jcallahan@36: local quest = DBEntry("quests", quest_id) jcallahan@36: quest.timer = _G.GetQuestLogTimeLeft() jcallahan@37: quest.can_share = _G.GetQuestLogPushable() and true or nil jcallahan@36: processed_quests = processed_quests + 1 jcallahan@36: end jcallahan@36: entry_index = entry_index + 1 jcallahan@36: end jcallahan@36: _G.SelectQuestLogEntry(selected_quest) jcallahan@4: self:UnregisterEvent("QUEST_LOG_UPDATE") jcallahan@4: end jcallahan@4: jcallahan@4: jcallahan@97: function WDP:QUEST_PROGRESS(event_name) jcallahan@112: if not ALLOWED_LOCALES[CLIENT_LOCALE] then jcallahan@112: return jcallahan@112: end jcallahan@97: DBEntry("quests", _G.GetQuestID()).progress_text = ReplaceKeywords(_G.GetProgressText()) jcallahan@97: end jcallahan@97: jcallahan@97: jcallahan@92: function WDP:UNIT_QUEST_LOG_CHANGED(event_name, unit_id) jcallahan@4: if unit_id ~= "player" then jcallahan@4: return jcallahan@4: end jcallahan@4: self:RegisterEvent("QUEST_LOG_UPDATE") jcallahan@4: end jcallahan@4: jcallahan@4: jcallahan@92: do jcallahan@92: local TRADESKILL_TOOLS = { jcallahan@92: Anvil = anvil_spell_ids, jcallahan@92: Forge = forge_spell_ids, jcallahan@92: } jcallahan@92: jcallahan@92: jcallahan@167: local function RegisterTools(tradeskill_name, tradeskill_index) catherton@479: local link = _G.C_TradeSkillUI.GetRecipeLink(tradeskill_index) catherton@465: MMOSimca@352: if link then MMOSimca@352: local spell_id = tonumber(link:match("^|c%x%x%x%x%x%x%x%x|H%w+:(%d+)")) catherton@479: local required_tool = _G.C_TradeSkillUI.GetRecipeTools(tradeskill_index) MMOSimca@352: MMOSimca@352: if required_tool then MMOSimca@352: for tool_name, registry in pairs(TRADESKILL_TOOLS) do MMOSimca@352: if required_tool:find(tool_name) then MMOSimca@352: registry[spell_id] = true MMOSimca@352: end jcallahan@167: end jcallahan@167: end jcallahan@167: end jcallahan@167: end jcallahan@167: jcallahan@167: jcallahan@92: function WDP:TRADE_SKILL_SHOW(event_name) catherton@479: local profession_name, prof_level = _G.C_TradeSkillUI.GetTradeSkillLine() jcallahan@92: jcallahan@92: if profession_name == _G.UNKNOWN then jcallahan@92: return jcallahan@92: end jcallahan@167: TradeSkillExecutePer(RegisterTools) jcallahan@92: end jcallahan@92: end -- do-block jcallahan@92: jcallahan@92: jcallahan@167: function WDP:TRAINER_CLOSED(event_name) jcallahan@167: private.trainer_shown = nil jcallahan@167: end jcallahan@167: jcallahan@167: jcallahan@92: function WDP:TRAINER_SHOW(event_name) jcallahan@232: local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc")) jcallahan@164: local trainer = NPCEntry(unit_idnum) jcallahan@58: jcallahan@164: if not trainer then jcallahan@58: return jcallahan@58: end jcallahan@331: local _, trainer_standing = UnitFactionStanding("npc") jcallahan@164: trainer.teaches = trainer.teaches or {} jcallahan@27: jcallahan@167: private.trainer_shown = true jcallahan@167: jcallahan@27: -- Get the initial trainer filters MMOSimca@344: local available = _G.GetTrainerServiceTypeFilter("available") and 1 or 0 MMOSimca@344: local unavailable = _G.GetTrainerServiceTypeFilter("unavailable") and 1 or 0 MMOSimca@344: local used = _G.GetTrainerServiceTypeFilter("used") and 1 or 0 jcallahan@27: jcallahan@27: -- Clear the trainer filters MMOSimca@344: _G.SetTrainerServiceTypeFilter("available", 1) MMOSimca@344: _G.SetTrainerServiceTypeFilter("unavailable", 1) MMOSimca@344: _G.SetTrainerServiceTypeFilter("used", 1) jcallahan@27: jcallahan@27: for index = 1, _G.GetNumTrainerServices(), 1 do jcallahan@27: local spell_name, rank_name, _, _, required_level = _G.GetTrainerServiceInfo(index) jcallahan@27: jcallahan@27: if spell_name then jcallahan@27: DatamineTT:ClearLines() jcallahan@27: DatamineTT:SetTrainerService(index) jcallahan@27: jcallahan@27: local _, _, spell_id = DatamineTT:GetSpell() jcallahan@27: jcallahan@43: if spell_id then jcallahan@164: local class_professions = trainer.teaches[PLAYER_CLASS] jcallahan@164: jcallahan@164: if not class_professions then jcallahan@164: trainer.teaches[PLAYER_CLASS] = {} jcallahan@164: class_professions = trainer.teaches[PLAYER_CLASS] jcallahan@164: end jcallahan@43: local profession, min_skill = _G.GetTrainerServiceSkillReq(index) jcallahan@43: profession = profession or "General" jcallahan@43: jcallahan@164: local profession_skills = class_professions[profession] jcallahan@43: jcallahan@43: if not profession_skills then jcallahan@43: class_professions[profession] = {} jcallahan@43: profession_skills = class_professions[profession] jcallahan@43: end jcallahan@173: profession_skills[spell_id] = ("%d:%d:%d"):format(required_level, min_skill, _G.GetTrainerServiceCost(index)) jcallahan@27: end jcallahan@27: end jcallahan@27: end jcallahan@27: -- Reset the filters to what they were before MMOSimca@344: _G.SetTrainerServiceTypeFilter("available", available or 0) MMOSimca@344: _G.SetTrainerServiceTypeFilter("unavailable", unavailable or 0) MMOSimca@344: _G.SetTrainerServiceTypeFilter("used", used or 0) jcallahan@27: end jcallahan@27: jcallahan@27: jcallahan@1: function WDP:UNIT_SPELLCAST_SENT(event_name, unit_id, spell_name, spell_rank, target_name, spell_line) jcallahan@1: if private.tracked_line or unit_id ~= "player" then jcallahan@1: return jcallahan@1: end jcallahan@1: local spell_label = private.SPELL_LABELS_BY_NAME[spell_name] jcallahan@1: jcallahan@1: if not spell_label then jcallahan@1: return jcallahan@1: end jcallahan@306: MMOSimca@343: Debug("UNIT_SPELLCAST_SENT: %s was cast.", spell_name) jcallahan@150: local item_name, item_link = _G.GameTooltip:GetItem() jcallahan@150: local unit_name, unit_id = _G.GameTooltip:GetUnit() jcallahan@1: jcallahan@150: if not unit_name and _G.UnitName("target") == target_name then jcallahan@150: unit_name = target_name jcallahan@150: unit_id = "target" jcallahan@1: end jcallahan@1: local spell_flags = private.SPELL_FLAGS_BY_LABEL[spell_label] jcallahan@28: local zone_name, area_id, x, y, map_level, instance_token = CurrentLocationData() MMOSimca@328: if not (zone_name and area_id and x and y and map_level) then catherton@468: Debug("%s: Missing current location data - %s, %s, %s, %s, %s.", event_name, tostring(zone_name), tostring(area_id), tostring(x), tostring(y), tostring(map_level)) MMOSimca@328: return MMOSimca@328: end jcallahan@28: jcallahan@205: table.wipe(current_action) jcallahan@122: current_action.instance_token = instance_token jcallahan@122: current_action.map_level = map_level jcallahan@122: current_action.x = x jcallahan@122: current_action.y = y jcallahan@122: current_action.zone_data = ("%s:%d"):format(zone_name, area_id) jcallahan@122: current_action.spell_label = spell_label jcallahan@105: jcallahan@105: if not private.NON_LOOT_SPELL_LABELS[spell_label] then jcallahan@122: current_action.loot_label = spell_label:lower() jcallahan@105: end jcallahan@1: jcallahan@151: if unit_name and unit_name == target_name and not item_name then jcallahan@16: if bit.band(spell_flags, AF.NPC) == AF.NPC then jcallahan@150: if not unit_id or unit_name ~= target_name then jcallahan@16: return jcallahan@16: end jcallahan@123: current_action.target_type = AF.NPC jcallahan@16: end jcallahan@16: elseif bit.band(spell_flags, AF.ITEM) == AF.ITEM then jcallahan@123: current_action.target_type = AF.ITEM jcallahan@16: jcallahan@150: if item_name and item_name == target_name then jcallahan@150: current_action.identifier = ItemLinkToID(item_link) jcallahan@16: elseif target_name and target_name ~= "" then jcallahan@331: local _, item_link = _G.GetItemInfo(target_name) jcallahan@331: current_action.identifier = ItemLinkToID(item_link) jcallahan@16: end jcallahan@150: elseif not item_name and not unit_name then jcallahan@1: if bit.band(spell_flags, AF.OBJECT) == AF.OBJECT then jcallahan@17: if target_name == "" then jcallahan@17: return jcallahan@17: end jcallahan@122: current_action.object_name = target_name jcallahan@123: current_action.target_type = AF.OBJECT jcallahan@1: elseif bit.band(spell_flags, AF.ZONE) == AF.ZONE then jcallahan@123: current_action.target_type = AF.ZONE jcallahan@1: end jcallahan@1: end jcallahan@1: private.tracked_line = spell_line jcallahan@0: end jcallahan@0: jcallahan@94: MMOSimca@393: -- Triggered by bonus roll prompts, disenchant prompts, and in a few other rare circumstances jcallahan@312: function WDP:SPELL_CONFIRMATION_PROMPT(event_name, spell_id, confirm_type, text, duration, currency_id_cost) jcallahan@306: if private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id] then jcallahan@306: ClearKilledBossID() jcallahan@306: ClearLootToastContainerID() MMOSimca@387: raid_boss_id = private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id] jcallahan@306: else MMOSimca@336: Debug("%s: Spell ID %d is not a known raid boss 'Bonus' spell.", event_name, spell_id) jcallahan@306: return jcallahan@1: end jcallahan@306: jcallahan@324: -- Assign existing loot data to boss if it exists jcallahan@307: if loot_toast_data then MMOSimca@427: local npc = NPCEntry(raid_boss_id) MMOSimca@427: if npc then MMOSimca@427: -- Create needed npc fields if required MMOSimca@427: local loot_label = "drops" MMOSimca@427: local encounter_data = npc:EncounterData(InstanceDifficultyToken()) MMOSimca@427: encounter_data[loot_label] = encounter_data[loot_label] or {} MMOSimca@427: encounter_data.loot_counts = encounter_data.loot_counts or {} MMOSimca@427: MMOSimca@427: for index = 1, #loot_toast_data do MMOSimca@427: local data = loot_toast_data[index] MMOSimca@427: local loot_type = data[1] MMOSimca@427: local hyperlink = data[2] MMOSimca@427: local quantity = data[3] MMOSimca@427: MMOSimca@427: if loot_type == "item" then MMOSimca@427: local item_id = ItemLinkToID(hyperlink) MMOSimca@427: Debug("%s: Assigned stored item loot data - %s - %d:%d", event_name, hyperlink, item_id, quantity) MMOSimca@427: table.insert(encounter_data[loot_label], ("%d:%d"):format(item_id, quantity)) MMOSimca@427: elseif loot_type == "money" then MMOSimca@427: Debug("%s: Assigned stored money loot data - money:%d", event_name, quantity) MMOSimca@427: table.insert(encounter_data[loot_label], ("money:%d"):format(quantity)) MMOSimca@427: elseif loot_type == "currency" then MMOSimca@427: local currency_texture = CurrencyLinkToTexture(hyperlink) MMOSimca@427: Debug("%s: Assigned stored currency loot data - %s - currency:%d:%s", event_name, hyperlink, currency_texture, quantity) MMOSimca@427: table.insert(encounter_data[loot_label], ("currency:%d:%s"):format(quantity, currency_texture)) MMOSimca@427: end jcallahan@317: end jcallahan@317: MMOSimca@427: if not boss_loot_toasting[raid_boss_id] then MMOSimca@427: encounter_data.loot_counts[loot_label] = (encounter_data.loot_counts[loot_label] or 0) + 1 MMOSimca@427: boss_loot_toasting[raid_boss_id] = true -- Do not count further loots until timer expires or another boss is killed jcallahan@307: end MMOSimca@427: else MMOSimca@427: Debug("%s: NPC is nil, but we have stored loot data...", event_name) jcallahan@307: end jcallahan@307: end jcallahan@307: jcallahan@307: ClearLootToastData() MMOSimca@427: killed_boss_id_timer_handle = C_Timer.NewTimer(5, ClearKilledBossID) jcallahan@306: end jcallahan@306: jcallahan@306: jcallahan@306: function WDP:UNIT_SPELLCAST_SUCCEEDED(event_name, unit_id, spell_name, spell_rank, spell_line, spell_id) jcallahan@306: if unit_id ~= "player" then jcallahan@306: return jcallahan@306: end jcallahan@306: private.tracked_line = nil jcallahan@306: private.previous_spell_id = spell_id jcallahan@306: MMOSimca@393: -- For spells cast when Logging MMOSimca@345: if private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[spell_id] then MMOSimca@345: last_timber_spell_id = spell_id MMOSimca@351: UpdateDBEntryLocation("objects", ("OPENING:%s"):format(private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[spell_id])) MMOSimca@345: return MMOSimca@345: end MMOSimca@345: MMOSimca@393: -- For spells cast by items that always trigger loot toasts MMOSimca@381: if private.LOOT_TOAST_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] then jcallahan@306: ClearKilledBossID() jcallahan@306: ClearLootToastContainerID() jcallahan@307: ClearLootToastData() jcallahan@306: MMOSimca@387: loot_toast_container_id = private.LOOT_TOAST_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] MMOSimca@383: loot_toast_container_timer_handle = C_Timer.NewTimer(1, ClearLootToastContainerID) -- we need to assign a handle here to cancel it later MMOSimca@345: return jcallahan@306: end jcallahan@306: MMOSimca@393: -- For spells cast by items that don't usually trigger loot toasts catherton@473: if not block_chat_loot_data and (private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] or (private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_BY_CLASS_ID_MAP[spell_id] and private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_BY_CLASS_ID_MAP[spell_id][PLAYER_CLASS_ID])) then MMOSimca@347: -- Set up timer MMOSimca@393: ClearChatLootData() MMOSimca@393: Debug("%s: Beginning chat-based loot timer for spellID %d", event_name, spell_id) MMOSimca@411: chat_loot_timer_handle = C_Timer.NewTimer(1.5, ClearChatLootData) catherton@473: if (private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_BY_CLASS_ID_MAP[spell_id] and private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_BY_CLASS_ID_MAP[spell_id][PLAYER_CLASS_ID]) then catherton@473: chat_loot_data.identifier = private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_BY_CLASS_ID_MAP[spell_id][PLAYER_CLASS_ID] MMOSimca@454: else MMOSimca@454: chat_loot_data.identifier = private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] MMOSimca@454: end MMOSimca@347: return MMOSimca@347: end MMOSimca@347: jcallahan@306: if anvil_spell_ids[spell_id] then jcallahan@306: UpdateDBEntryLocation("objects", OBJECT_ID_ANVIL) jcallahan@306: elseif forge_spell_ids[spell_id] then jcallahan@306: UpdateDBEntryLocation("objects", OBJECT_ID_FORGE) jcallahan@306: elseif spell_name:match("^Harvest.+") then jcallahan@306: killed_npc_id = current_target_id MMOSimca@343: private.harvesting = true -- Used to track which NPCs can be harvested (can we get this from CreatureCache instead?) jcallahan@306: end jcallahan@306: end jcallahan@0: jcallahan@90: jcallahan@1: function WDP:HandleSpellFailure(event_name, unit_id, spell_name, spell_rank, spell_line, spell_id) jcallahan@1: if unit_id ~= "player" then jcallahan@1: return jcallahan@1: end jcallahan@0: jcallahan@1: if private.tracked_line == spell_line then jcallahan@1: private.tracked_line = nil jcallahan@1: end jcallahan@147: table.wipe(current_action) jcallahan@0: end jcallahan@90: jcallahan@90: jcallahan@90: do jcallahan@90: local function SetUnitField(field, required_type) jcallahan@90: local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc")) jcallahan@90: jcallahan@90: if not unit_idnum or (required_type and unit_type ~= required_type) then jcallahan@90: return jcallahan@90: end jcallahan@90: jcallahan@171: if UnitTypeIsNPC(unit_type) then jcallahan@90: NPCEntry(unit_idnum)[field] = true jcallahan@90: elseif unit_type == private.UNIT_TYPES.OBJECT then jcallahan@90: DBEntry("objects", unit_idnum)[field] = true jcallahan@93: UpdateDBEntryLocation("objects", unit_idnum) jcallahan@90: end jcallahan@90: end jcallahan@90: jcallahan@90: jcallahan@90: function WDP:AUCTION_HOUSE_SHOW(event_name) MMOSimca@436: WDP:StopChatLootRecording(event_name) jcallahan@90: SetUnitField("auctioneer", private.UNIT_TYPES.NPC) jcallahan@90: end jcallahan@90: jcallahan@90: jcallahan@90: function WDP:BANKFRAME_OPENED(event_name) MMOSimca@436: WDP:StopChatLootRecording(event_name) jcallahan@90: SetUnitField("banker", private.UNIT_TYPES.NPC) jcallahan@90: end jcallahan@90: jcallahan@90: jcallahan@90: function WDP:BATTLEFIELDS_SHOW(event_name) jcallahan@90: SetUnitField("battlemaster", private.UNIT_TYPES.NPC) jcallahan@90: end jcallahan@90: jcallahan@90: jcallahan@92: function WDP:FORGE_MASTER_OPENED(event_name) jcallahan@90: SetUnitField("arcane_reforger", private.UNIT_TYPES.NPC) jcallahan@90: end jcallahan@90: jcallahan@90: jcallahan@323: local GOSSIP_SHOW_FUNCS = { jcallahan@323: [private.UNIT_TYPES.NPC] = function(unit_idnum) jcallahan@323: local gossip_options = { _G.GetGossipOptions() } jcallahan@323: jcallahan@323: for index = 2, #gossip_options, 2 do jcallahan@323: if gossip_options[index] == "binder" then jcallahan@323: SetUnitField("innkeeper", private.UNIT_TYPES.NPC) jcallahan@323: return jcallahan@323: end jcallahan@323: end jcallahan@323: end, jcallahan@323: [private.UNIT_TYPES.OBJECT] = function(unit_idnum) jcallahan@323: UpdateDBEntryLocation("objects", unit_idnum) jcallahan@323: end, jcallahan@323: } jcallahan@323: jcallahan@323: jcallahan@92: function WDP:GOSSIP_SHOW(event_name) MMOSimca@436: WDP:StopChatLootRecording(event_name) jcallahan@323: local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc")) jcallahan@323: if not unit_idnum then jcallahan@323: return jcallahan@323: end jcallahan@323: jcallahan@323: if GOSSIP_SHOW_FUNCS[unit_type] then jcallahan@323: GOSSIP_SHOW_FUNCS[unit_type](unit_idnum) jcallahan@90: end jcallahan@90: end jcallahan@90: jcallahan@90: jcallahan@93: function WDP:GUILDBANKFRAME_OPENED(event_name) MMOSimca@436: WDP:StopChatLootRecording(event_name) jcallahan@93: SetUnitField("guild_bank", private.UNIT_TYPES.OBJECT) jcallahan@93: end jcallahan@93: jcallahan@93: jcallahan@189: function WDP:ITEM_UPGRADE_MASTER_OPENED(event_name) jcallahan@189: SetUnitField("item_upgrade_master", private.UNIT_TYPES.NPC) jcallahan@189: end jcallahan@189: jcallahan@189: jcallahan@90: function WDP:TAXIMAP_OPENED(event_name) jcallahan@90: SetUnitField("flight_master", private.UNIT_TYPES.NPC) jcallahan@90: end jcallahan@90: jcallahan@90: jcallahan@90: function WDP:TRANSMOGRIFY_OPEN(event_name) jcallahan@90: SetUnitField("transmogrifier", private.UNIT_TYPES.NPC) jcallahan@90: end jcallahan@90: jcallahan@90: jcallahan@90: function WDP:VOID_STORAGE_OPEN(event_name) jcallahan@90: SetUnitField("void_storage", private.UNIT_TYPES.NPC) jcallahan@90: end jcallahan@90: end -- do-block