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 jcallahan@306: local unpack = _G.unpack jcallahan@78: jcallahan@0: jcallahan@246: -- ADDON NAMESPACE ---------------------------------------------------- jcallahan@246: jcallahan@0: local ADDON_NAME, private = ... jcallahan@0: jcallahan@0: local LibStub = _G.LibStub jcallahan@249: local WDP = LibStub("AceAddon-3.0"):NewAddon(ADDON_NAME, "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0") jcallahan@0: jcallahan@48: local deformat = LibStub("LibDeformat-3.0") jcallahan@115: local LPJ = LibStub("LibPetJournal-2.0") jcallahan@141: local MapData = LibStub("LibMapData-1.0") 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@342: WOWDB_DEBUGGING = false jcallahan@156: local EVENT_DEBUG = false jcallahan@322: jcallahan@246: local OBJECT_ID_ANVIL = 192628 jcallahan@322: local OBJECT_ID_FISHING_BOBBER = 35591 jcallahan@246: local OBJECT_ID_FORGE = 1685 jcallahan@322: jcallahan@246: local PLAYER_CLASS = _G.select(2, _G.UnitClass("player")) jcallahan@246: local PLAYER_FACTION = _G.UnitFactionGroup("player") jcallahan@300: local PLAYER_GUID jcallahan@246: local PLAYER_NAME = _G.UnitName("player") jcallahan@246: local PLAYER_RACE = _G.select(2, _G.UnitRace("player")) jcallahan@246: MMOSimca@337: -- Ignoring NPC casts of the following spells MMOSimca@336: local CHI_WAVE_SPELL_ID = 132464 MMOSimca@336: local DISGUISE_SPELL_ID = 121308 MMOSimca@336: MMOSimca@336: -- Constant for duplicate boss data; a dirty hack to get around world bosses that cannot be identified individually and cannot be linked on wowdb because they are not in a raid MMOSimca@336: local DUPLICATE_WORLD_BOSS_IDS = { MMOSimca@336: [71952] = { 71953, 71954, 71955, }, MMOSimca@336: } jcallahan@326: 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 = { jcallahan@90: AUCTION_HOUSE_SHOW = true, jcallahan@90: BANKFRAME_OPENED = true, jcallahan@90: BATTLEFIELDS_SHOW = true, jcallahan@56: BLACK_MARKET_ITEM_UPDATE = 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, jcallahan@90: GOSSIP_SHOW = true, jcallahan@290: GROUP_ROSTER_UPDATE = true, jcallahan@93: GUILDBANKFRAME_OPENED = true, jcallahan@42: ITEM_TEXT_BEGIN = true, jcallahan@189: ITEM_UPGRADE_MASTER_OPENED = true, jcallahan@124: LOOT_CLOSED = true, MMOSimca@343: LOOT_OPENED = true, jcallahan@89: MAIL_SHOW = true, jcallahan@133: MERCHANT_CLOSED = true, jcallahan@7: MERCHANT_SHOW = "UpdateMerchantItems", jcallahan@61: MERCHANT_UPDATE = "UpdateMerchantItems", jcallahan@25: PET_BAR_UPDATE = true, jcallahan@115: 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, 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@299: ZONE_CHANGED = "HandleZoneChange", jcallahan@299: ZONE_CHANGED_INDOORS = "HandleZoneChange", jcallahan@299: ZONE_CHANGED_NEW_AREA = "HandleZoneChange", 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 = {} 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 jcallahan@86: local current_target_id jcallahan@126: local current_area_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: jcallahan@246: -- HELPERS ------------------------------------------------------------ jcallahan@246: jcallahan@245: local function Debug(message, ...) MMOSimca@342: if not WOWDB_DEBUGGING or not message or not ... then jcallahan@151: return jcallahan@151: end jcallahan@306: local args = { ... } jcallahan@306: jcallahan@306: for index = 1, #args do jcallahan@306: if args[index] == nil then jcallahan@306: args[index] = "nil" jcallahan@306: end jcallahan@306: end jcallahan@306: _G.print(message:format(unpack(args))) jcallahan@151: end jcallahan@151: jcallahan@151: 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 jcallahan@167: -- Clear the search box focus so the scan will have correct results. jcallahan@167: local search_box = _G.TradeSkillFrameSearchBox jcallahan@167: search_box:SetText("") jcallahan@169: jcallahan@167: _G.TradeSkillSearch_OnTextChanged(search_box) jcallahan@167: search_box:ClearFocus() jcallahan@167: search_box:GetScript("OnEditFocusLost")(search_box) jcallahan@169: jcallahan@169: table.wipe(header_list) jcallahan@169: jcallahan@169: -- Save the current state of the TradeSkillFrame so it can be restored after we muck with it. jcallahan@169: local have_materials = _G.TradeSkillFrame.filterTbl.hasMaterials jcallahan@169: local have_skillup = _G.TradeSkillFrame.filterTbl.hasSkillUp jcallahan@169: jcallahan@169: if have_materials then jcallahan@169: _G.TradeSkillFrame.filterTbl.hasMaterials = false jcallahan@169: _G.TradeSkillOnlyShowMakeable(false) jcallahan@169: end jcallahan@169: jcallahan@169: if have_skillup then jcallahan@169: _G.TradeSkillFrame.filterTbl.hasSkillUp = false jcallahan@169: _G.TradeSkillOnlyShowSkillUps(false) jcallahan@169: end MMOSimca@330: _G.SetTradeSkillInvSlotFilter(0, true, true) jcallahan@169: _G.TradeSkillUpdateFilterBar() jcallahan@169: _G.TradeSkillFrame_Update() jcallahan@169: jcallahan@169: -- Expand all headers so we can see all the recipes there are jcallahan@169: for tradeskill_index = 1, _G.GetNumTradeSkills() do jcallahan@169: local name, tradeskill_type, _, is_expanded = _G.GetTradeSkillInfo(tradeskill_index) jcallahan@169: jcallahan@169: if tradeskill_type == "header" or tradeskill_type == "subheader" then jcallahan@169: if not is_expanded then jcallahan@169: header_list[name] = true jcallahan@169: _G.ExpandTradeSkillSubClass(tradeskill_index) jcallahan@169: end jcallahan@169: elseif iter_func(name, tradeskill_index) then jcallahan@169: break jcallahan@169: end jcallahan@169: end jcallahan@169: jcallahan@169: -- Restore the state of the things we changed. jcallahan@169: for tradeskill_index = 1, _G.GetNumTradeSkills() do jcallahan@169: local name, tradeskill_type, _, is_expanded = _G.GetTradeSkillInfo(tradeskill_index) jcallahan@169: jcallahan@169: if header_list[name] then jcallahan@169: _G.CollapseTradeSkillSubClass(tradeskill_index) jcallahan@169: end jcallahan@169: end jcallahan@169: _G.TradeSkillFrame.filterTbl.hasMaterials = have_materials jcallahan@169: _G.TradeSkillOnlyShowMakeable(have_materials) jcallahan@169: _G.TradeSkillFrame.filterTbl.hasSkillUp = have_skillup jcallahan@169: _G.TradeSkillOnlyShowSkillUps(have_skillup) jcallahan@169: jcallahan@169: _G.TradeSkillUpdateFilterBar() jcallahan@169: _G.TradeSkillFrame_Update() 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@153: -- Called on a timer jcallahan@177: local function ClearKilledNPC() jcallahan@177: killed_npc_id = nil jcallahan@177: end jcallahan@177: jcallahan@177: jcallahan@203: local function ClearKilledBossID() jcallahan@306: if killed_boss_id_timer_handle then jcallahan@306: WDP:CancelTimer(killed_boss_id_timer_handle) jcallahan@320: killed_boss_id_timer_handle = nil jcallahan@306: end jcallahan@324: jcallahan@317: table.wipe(boss_loot_toasting) MMOSimca@336: private.raid_boss_id = nil jcallahan@306: end jcallahan@306: jcallahan@306: jcallahan@306: local function ClearLootToastContainerID() jcallahan@306: if loot_toast_container_timer_handle then jcallahan@306: WDP:CancelTimer(loot_toast_container_timer_handle) jcallahan@324: loot_toast_container_timer_handle = nil jcallahan@306: end jcallahan@324: jcallahan@306: private.container_loot_toasting = false jcallahan@306: private.loot_toast_container_id = nil jcallahan@203: end jcallahan@203: jcallahan@203: jcallahan@307: local function ClearLootToastData() jcallahan@307: -- cancel existing timer if found jcallahan@307: if loot_toast_data_timer_handle then jcallahan@307: WDP:CancelTimer(loot_toast_data_timer_handle) jcallahan@320: loot_toast_data_timer_handle = nil jcallahan@307: end jcallahan@307: jcallahan@320: if loot_toast_data then jcallahan@320: table.wipe(loot_toast_data) jcallahan@320: end jcallahan@307: end jcallahan@307: jcallahan@307: jcallahan@29: local function InstanceDifficultyToken() jcallahan@233: local _, instance_type, instance_difficulty, _, _, _, is_dynamic = _G.GetInstanceInfo() jcallahan@59: jcallahan@59: if not instance_type or instance_type == "" then jcallahan@59: instance_type = "NONE" jcallahan@59: end jcallahan@312: return ("%s:%d:%s"):format(instance_type:upper(), instance_difficulty, tostring(is_dynamic)) jcallahan@29: end jcallahan@29: jcallahan@29: jcallahan@19: local function DBEntry(data_type, unit_id) jcallahan@19: if not data_type or not unit_id then jcallahan@6: return jcallahan@6: end jcallahan@289: local category = global_db[data_type] jcallahan@289: jcallahan@289: if not category then jcallahan@289: category = {} jcallahan@289: global_db[data_type] = category jcallahan@289: end jcallahan@289: local unit = category[unit_id] jcallahan@6: jcallahan@10: if not unit then jcallahan@187: unit = {} jcallahan@289: category[unit_id] = unit jcallahan@6: end jcallahan@10: return unit jcallahan@6: end jcallahan@270: jcallahan@263: private.DBEntry = DBEntry jcallahan@6: jcallahan@214: local NPCEntry jcallahan@214: do jcallahan@214: local npc_prototype = {} jcallahan@214: local npc_meta = { jcallahan@214: __index = npc_prototype jcallahan@214: } jcallahan@6: jcallahan@214: function NPCEntry(identifier) jcallahan@227: local npc = DBEntry("npcs", identifier) jcallahan@331: return npc and _G.setmetatable(npc, npc_meta) or nil jcallahan@22: end jcallahan@214: jcallahan@248: function npc_prototype:EncounterData(difficulty_token) jcallahan@214: self.encounter_data = self.encounter_data or {} jcallahan@248: self.encounter_data[difficulty_token] = self.encounter_data[difficulty_token] or {} jcallahan@248: self.encounter_data[difficulty_token].stats = self.encounter_data[difficulty_token].stats or {} jcallahan@248: jcallahan@248: return self.encounter_data[difficulty_token] jcallahan@214: end jcallahan@22: end jcallahan@22: jcallahan@22: jcallahan@1: local function CurrentLocationData() jcallahan@161: if _G.GetCurrentMapAreaID() ~= current_area_id then jcallahan@145: return _G.GetRealZoneText(), current_area_id, 0, 0, 0, InstanceDifficultyToken() jcallahan@145: end jcallahan@1: local map_level = _G.GetCurrentMapDungeonLevel() or 0 jcallahan@1: local x, y = _G.GetPlayerMapPosition("player") jcallahan@1: jcallahan@1: x = x or 0 jcallahan@1: y = y or 0 jcallahan@1: jcallahan@1: if x == 0 and y == 0 then jcallahan@1: for level_index = 1, _G.GetNumDungeonMapLevels() do jcallahan@1: _G.SetDungeonMapLevel(level_index) jcallahan@1: x, y = _G.GetPlayerMapPosition("player") jcallahan@1: jcallahan@1: if x and y and (x > 0 or y > 0) then jcallahan@1: _G.SetDungeonMapLevel(map_level) jcallahan@1: map_level = level_index jcallahan@1: break jcallahan@1: end jcallahan@1: end jcallahan@1: end jcallahan@1: jcallahan@1: if _G.DungeonUsesTerrainMap() then jcallahan@1: map_level = map_level - 1 jcallahan@1: end jcallahan@31: local x = _G.floor(x * 1000) jcallahan@31: local y = _G.floor(y * 1000) jcallahan@28: jcallahan@31: if x % 2 ~= 0 then jcallahan@31: x = x + 1 jcallahan@28: end jcallahan@28: jcallahan@31: if y % 2 ~= 0 then jcallahan@31: y = y + 1 jcallahan@28: end jcallahan@126: return _G.GetRealZoneText(), current_area_id, x, y, map_level, InstanceDifficultyToken() jcallahan@1: end jcallahan@1: jcallahan@1: jcallahan@312: local function CurrencyLinkToTexture(currency_link) jcallahan@312: if not currency_link then jcallahan@312: return jcallahan@312: end jcallahan@312: local _, _, texture_path = _G.GetCurrencyInfo(tonumber(currency_link:match("currency:(%d+)"))) jcallahan@312: return texture_path:match("[^\\]+$"):lower() jcallahan@312: end jcallahan@312: jcallahan@312: jcallahan@1: local function ItemLinkToID(item_link) jcallahan@1: if not item_link then jcallahan@1: return jcallahan@1: end jcallahan@7: return tonumber(item_link:match("item:(%d+)")) jcallahan@1: end jcallahan@270: jcallahan@260: private.ItemLinkToID = ItemLinkToID jcallahan@4: jcallahan@171: local function UnitTypeIsNPC(unit_type) jcallahan@171: return unit_type == private.UNIT_TYPES.NPC or unit_type == private.UNIT_TYPES.VEHICLE jcallahan@171: end jcallahan@171: jcallahan@171: jcallahan@34: local ParseGUID jcallahan@4: do jcallahan@229: local UNIT_TYPES = private.UNIT_TYPES jcallahan@4: jcallahan@281: local NPC_ID_MAPPING = { jcallahan@281: [62164] = 63191, -- Garalon jcallahan@281: } jcallahan@281: jcallahan@281: jcallahan@334: local function MatchUnitTypes(unit_type_name) MMOSimca@329: if not unit_type_name then MMOSimca@329: return UNIT_TYPES.UNKNOWN MMOSimca@329: end MMOSimca@329: MMOSimca@329: for def, text in next, UNIT_TYPES do MMOSimca@329: if unit_type_name == text then MMOSimca@329: return UNIT_TYPES[def] MMOSimca@329: end MMOSimca@329: end MMOSimca@329: return UNIT_TYPES.UNKNOWN MMOSimca@329: end MMOSimca@329: MMOSimca@329: jcallahan@34: function ParseGUID(guid) jcallahan@5: if not guid then jcallahan@5: return jcallahan@5: end MMOSimca@329: MMOSimca@329: -- We might want to use some of this new information later, but leaving the returns alone for now MMOSimca@339: local unit_type_name, unk_id1, server_id, instance_id, unk_id2, unit_idnum, spawn_id = ("-"):split(guid) MMOSimca@329: jcallahan@334: local unit_type = MatchUnitTypes(unit_type_name) MMOSimca@329: if unit_type ~= UNIT_TYPES.PLAYER and unit_type ~= UNIT_TYPES.PET and unit_type ~= UNIT_TYPES.ITEM then MMOSimca@329: jcallahan@281: local id_mapping = NPC_ID_MAPPING[unit_idnum] jcallahan@281: jcallahan@281: if id_mapping and UnitTypeIsNPC(unit_type) then jcallahan@281: unit_idnum = id_mapping jcallahan@281: end jcallahan@281: return unit_type, unit_idnum jcallahan@4: end jcallahan@4: return unit_type jcallahan@4: end jcallahan@249: jcallahan@249: private.ParseGUID = ParseGUID jcallahan@4: end -- do-block jcallahan@4: 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 MMOSimca@328: Debug("UpdateDBEntryLocation: Missing current location data - %s, %d, %d, %d, %d.", zone_name, area_id, x, y, 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 jcallahan@141: local yard_width, yard_height = MapData:MapArea(area_id, map_level) 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: 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: jcallahan@19: if not bag_index or not slot_index then jcallahan@19: return jcallahan@19: end jcallahan@19: local _, _, _, _, _, is_lootable = _G.GetContainerItemInfo(bag_index, slot_index) jcallahan@19: jcallahan@19: if not is_lootable then jcallahan@19: return jcallahan@19: end jcallahan@19: DatamineTT:ClearLines() jcallahan@19: DatamineTT:SetBagItem(bag_index, slot_index) jcallahan@19: jcallahan@19: for line_index = 1, DatamineTT:NumLines() do jcallahan@19: local current_line = _G["WDPDatamineTTTextLeft" .. line_index] jcallahan@19: jcallahan@19: if not current_line then MMOSimca@336: Debug("HandleItemUse: Item with ID %d and link %s had an invalid tooltip.", item_id, item_link) jcallahan@324: return jcallahan@19: end jcallahan@306: jcallahan@306: if current_line:GetText() == _G.ITEM_OPENABLE then jcallahan@122: table.wipe(current_action) jcallahan@312: current_loot = nil jcallahan@312: jcallahan@123: current_action.target_type = AF.ITEM jcallahan@122: current_action.identifier = item_id jcallahan@122: current_action.loot_label = "contains" jcallahan@324: return jcallahan@19: end jcallahan@19: end jcallahan@324: Debug("HandleItemUse: Item with ID %d and link %s did not have a tooltip that contained the string %s.", item_id, item_link, _G.ITEM_OPENABLE) 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: jcallahan@75: local GenericLootUpdate jcallahan@75: do jcallahan@77: local 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 jcallahan@306: elseif not private.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. 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: jcallahan@154: -- Contains a dirty hack due to Blizzard's strange handling of Micro Dungeons; GetMapInfo() will not return correct information jcallahan@154: -- unless the WorldMapFrame is shown. jcallahan@143: do jcallahan@143: -- MapFileName = MapAreaID jcallahan@143: local MICRO_DUNGEON_IDS = { jcallahan@143: ShrineofTwoMoons = 903, jcallahan@143: ShrineofSevenStars = 905, jcallahan@143: } jcallahan@126: jcallahan@299: local function SetCurrentAreaID() jcallahan@156: if private.in_combat then jcallahan@156: private.set_area_id = true jcallahan@156: return jcallahan@156: end jcallahan@155: local map_area_id = _G.GetCurrentMapAreaID() jcallahan@155: jcallahan@155: if map_area_id == current_area_id then jcallahan@155: return jcallahan@155: end jcallahan@143: local world_map = _G.WorldMapFrame jcallahan@143: local map_visible = world_map:IsVisible() jcallahan@312: local sfx_value = tonumber(_G.GetCVar("Sound_EnableSFX")) jcallahan@143: jcallahan@143: if not map_visible then jcallahan@143: _G.SetCVar("Sound_EnableSFX", 0) jcallahan@143: world_map:Show() jcallahan@143: end jcallahan@331: local _, _, _, _, micro_dungeon_map_name = _G.GetMapInfo() jcallahan@331: local micro_dungeon_id = MICRO_DUNGEON_IDS[micro_dungeon_map_name] jcallahan@143: jcallahan@154: _G.SetMapToCurrentZone() jcallahan@154: jcallahan@143: if micro_dungeon_id then jcallahan@143: current_area_id = micro_dungeon_id jcallahan@143: else jcallahan@143: current_area_id = _G.GetCurrentMapAreaID() jcallahan@143: end jcallahan@143: jcallahan@154: if map_visible then jcallahan@154: _G.SetMapByID(map_area_id) jcallahan@154: else jcallahan@143: world_map:Hide() jcallahan@143: _G.SetCVar("Sound_EnableSFX", sfx_value) jcallahan@143: end jcallahan@143: end jcallahan@299: jcallahan@299: function WDP:HandleZoneChange(event_name) jcallahan@299: in_instance = _G.IsInInstance() jcallahan@299: SetCurrentAreaID() jcallahan@299: end jcallahan@154: end jcallahan@126: jcallahan@301: local function InitializeCurrentLoot() jcallahan@301: current_loot = { jcallahan@301: list = {}, jcallahan@301: sources = {}, jcallahan@301: identifier = current_action.identifier, jcallahan@301: label = current_action.loot_label or "drops", jcallahan@301: map_level = current_action.map_level, jcallahan@301: object_name = current_action.object_name, jcallahan@301: spell_label = current_action.spell_label, jcallahan@301: target_type = current_action.target_type, jcallahan@301: x = current_action.x, jcallahan@301: y = current_action.y, jcallahan@301: zone_data = current_action.zone_data, jcallahan@301: } jcallahan@301: jcallahan@301: table.wipe(current_action) jcallahan@301: end jcallahan@246: 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) jcallahan@14: 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 jcallahan@35: raw_db.build_num = build_num MMOSimca@330: raw_db.region = private.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@342: if WOWDB_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 jcallahan@187: item_process_timer_handle = self:ScheduleRepeatingTimer("ProcessItems", 30) jcallahan@31: target_location_timer_handle = self:ScheduleRepeatingTimer("UpdateTargetLocation", 0.5) 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@299: self:HandleZoneChange("OnEnable") jcallahan@290: self:GROUP_ROSTER_UPDATE() jcallahan@0: end jcallahan@0: jcallahan@0: jcallahan@219: local ScrapeItemUpgradeStats jcallahan@219: do jcallahan@219: local intermediary = {} jcallahan@219: jcallahan@220: function ScrapeItemUpgradeStats(item_id, upgrade_id, reforge_id) jcallahan@220: if not ALLOWED_LOCALES[CLIENT_LOCALE] then jcallahan@220: return jcallahan@220: end jcallahan@219: local create_entry jcallahan@219: jcallahan@219: table.wipe(intermediary) jcallahan@219: jcallahan@219: for line_index = 1, DatamineTT:NumLines() do jcallahan@293: local left_text = _G["WDPDatamineTTTextLeft" .. line_index]:GetText():trim() jcallahan@293: jcallahan@293: if not left_text or left_text == "" or left_text:find("Socket") or left_text:find("Set:") then jcallahan@293: break jcallahan@219: end jcallahan@219: local amount, stat = left_text:match("+(.-) (.*)") jcallahan@219: jcallahan@219: if amount and stat then jcallahan@219: create_entry = true jcallahan@295: intermediary[stat:lower():gsub(" ", "_"):gsub("|r", "")] = tonumber((amount:gsub(",", ""))) jcallahan@219: end jcallahan@219: end jcallahan@219: jcallahan@219: if not create_entry then jcallahan@219: return jcallahan@219: end jcallahan@219: local item = DBEntry("items", item_id) jcallahan@225: item.upgrade_id = upgrade_id jcallahan@219: item.upgrades = item.upgrades or {} jcallahan@219: item.upgrades[upgrade_id] = item.upgrades[upgrade_id] or {} jcallahan@219: jcallahan@219: for stat, amount in pairs(intermediary) do jcallahan@219: item.upgrades[upgrade_id][stat] = amount jcallahan@219: end jcallahan@219: end MMOSimca@336: end -- do-block jcallahan@219: jcallahan@219: 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@338: local suffix_id = tonumber(item_results[8]) MMOSimca@338: local unique_id = item_results[9] MMOSimca@338: local upgrade_id = tonumber(item_results[11]) MMOSimca@338: local instance_difficulty_id = tonumber(item_results[12]) MMOSimca@338: local num_bonus_ids = tonumber(item_results[13]) jcallahan@333: MMOSimca@340: if not num_bonus_ids or num_bonus_ids == 0 or not process_bonus_ids then MMOSimca@329: if (suffix_id and suffix_id ~= 0) or (instance_difficulty_id and instance_difficulty_id ~= 0) then MMOSimca@329: item = DBEntry("items", item_id) MMOSimca@329: item.unique_id = bit.band(unique_id, 0xFFFF) jcallahan@331: jcallahan@331: if suffix_id and suffix_id ~= 0 then MMOSimca@329: item.suffix_id = suffix_id MMOSimca@329: end jcallahan@331: jcallahan@331: if instance_difficulty_id and instance_difficulty_id ~= 0 then MMOSimca@329: item.instance_difficulty_id = instance_difficulty_id MMOSimca@329: end MMOSimca@329: end MMOSimca@329: elseif num_bonus_ids > 0 then jcallahan@219: item = DBEntry("items", item_id) MMOSimca@329: jcallahan@201: item.unique_id = bit.band(unique_id, 0xFFFF) MMOSimca@329: item.instance_difficulty_id = instance_difficulty_id jcallahan@331: MMOSimca@329: if not item.bonus_ids then MMOSimca@329: item.bonus_ids = {} MMOSimca@329: end MMOSimca@338: MMOSimca@329: for bonus_index = 1, num_bonus_ids do MMOSimca@338: item.bonus_ids[tonumber(item_results[13 + bonus_index])] = true MMOSimca@329: end MMOSimca@329: else MMOSimca@329: Debug("RecordItemData: Item_system is supposed to be 0 or positive, instead it was %s.", item_system) jcallahan@201: end jcallahan@219: if upgrade_id and upgrade_id ~= 0 then jcallahan@220: DatamineTT:SetHyperlink(item_link) MMOSimca@329: ScrapeItemUpgradeStats(item_id, upgrade_id) 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() jcallahan@113: if currently_drunk or not _G.UnitExists("target") or _G.UnitPlayerControlled("target") or (_G.UnitIsTapped("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 MMOSimca@328: Debug("UpdateTargetLocation: Missing current location data - %s, %d, %d, %d, %d.", zone_name, area_id, x, y, 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: jcallahan@246: -- EVENT HANDLERS ----------------------------------------------------- jcallahan@246: 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: jcallahan@178: function WDP:SHOW_LOOT_TOAST(event_name, loot_type, item_link, quantity) 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 jcallahan@301: local container_id = private.loot_toast_container_id MMOSimca@336: local npc_id = private.raid_boss_id jcallahan@317: jcallahan@317: if npc_id then MMOSimca@336: -- Slightly messy hack to workaround duplicate world bosses jcallahan@317: local upper_limit = 0 jcallahan@317: if DUPLICATE_WORLD_BOSS_IDS[npc_id] then jcallahan@317: upper_limit = #DUPLICATE_WORLD_BOSS_IDS[npc_id] jcallahan@317: end jcallahan@317: for i = 0, upper_limit do jcallahan@317: local temp_npc_id = npc_id jcallahan@317: jcallahan@317: if i > 0 then jcallahan@317: temp_npc_id = DUPLICATE_WORLD_BOSS_IDS[npc_id][i] jcallahan@312: end jcallahan@317: jcallahan@317: local npc = NPCEntry(temp_npc_id) jcallahan@317: if npc then jcallahan@317: local loot_label = "drops" jcallahan@317: local encounter_data = npc:EncounterData(InstanceDifficultyToken()) jcallahan@317: encounter_data[loot_label] = encounter_data[loot_label] or {} jcallahan@317: encounter_data.loot_counts = encounter_data.loot_counts or {} jcallahan@317: jcallahan@317: if loot_type == "item" then jcallahan@317: local item_id = ItemLinkToID(item_link) jcallahan@317: if item_id then jcallahan@317: Debug("%s: %s X %d (%d)", event_name, item_link, quantity, item_id) MMOSimca@340: RecordItemData(item_id, item_link, true) jcallahan@317: table.insert(encounter_data[loot_label], ("%d:%d"):format(item_id, quantity)) jcallahan@317: else jcallahan@317: Debug("%s: ItemID is nil, from item link %s", event_name, item_link) jcallahan@317: return jcallahan@317: end jcallahan@317: elseif loot_type == "money" then jcallahan@317: Debug("%s: money X %d", event_name, quantity) jcallahan@317: table.insert(encounter_data[loot_label], ("money:%d"):format(quantity)) jcallahan@317: elseif loot_type == "currency" then jcallahan@317: local currency_texture = CurrencyLinkToTexture(item_link) jcallahan@317: if currency_texture and currency_texture ~= "" then jcallahan@317: Debug("%s: %s X %d", event_name, currency_texture, quantity) MMOSimca@337: -- Workaround for Patch 5.4.0 bug with Flexible raid Siege of Orgrimmar bosses and Valor Points jcallahan@317: if quantity > 1000 and currency_texture == "pvecurrency-valor" then jcallahan@317: quantity = math.floor(quantity / 100) jcallahan@317: end jcallahan@317: table.insert(encounter_data[loot_label], ("currency:%d:%s"):format(quantity, currency_texture)) jcallahan@317: else jcallahan@317: Debug("%s: Currency texture is nil, from currency link %s", event_name, item_link) jcallahan@317: return jcallahan@317: end jcallahan@317: end jcallahan@317: jcallahan@317: if not boss_loot_toasting[temp_npc_id] then jcallahan@317: encounter_data.loot_counts[loot_label] = (encounter_data.loot_counts[loot_label] or 0) + 1 jcallahan@317: boss_loot_toasting[temp_npc_id] = true -- Do not count further loots until timer expires or another boss is killed jcallahan@317: end jcallahan@312: end jcallahan@312: end jcallahan@301: elseif container_id then jcallahan@305: InitializeCurrentLoot() jcallahan@305: jcallahan@306: -- Fake the loot characteristics to match that of an actual container item jcallahan@306: current_loot.identifier = container_id jcallahan@306: current_loot.label = "contains" jcallahan@306: current_loot.target_type = AF.ITEM jcallahan@306: jcallahan@312: current_loot.sources[container_id] = current_loot.sources[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) jcallahan@312: current_loot.sources[container_id][item_id] = current_loot.sources[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) jcallahan@312: current_loot.sources[container_id]["money"] = current_loot.sources[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) jcallahan@312: current_loot.sources[container_id][currency_token] = current_loot.sources[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 jcallahan@307: private.container_loot_toasting = true -- Do not count further loots until timer expires or another container is opened 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: jcallahan@307: loot_toast_data_timer_handle = WDP:ScheduleTimer(ClearLootToastData, 5) jcallahan@178: end jcallahan@178: end jcallahan@178: jcallahan@178: jcallahan@179: do jcallahan@179: local CHAT_MSG_LOOT_UPDATE_FUNCS = { jcallahan@179: [AF.NPC] = function(item_id, quantity) jcallahan@245: Debug("CHAT_MSG_LOOT: %d (%d)", item_id, quantity) jcallahan@179: end, jcallahan@179: [AF.ZONE] = function(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: jcallahan@179: function WDP:CHAT_MSG_LOOT(event_name, message) jcallahan@179: local category jcallahan@177: jcallahan@179: if current_action.spell_label ~= "EXTRACT_GAS" then jcallahan@179: category = AF.ZONE MMOSimca@336: elseif private.raid_boss_id then jcallahan@179: category = AF.NPC jcallahan@179: end jcallahan@179: local update_func = CHAT_MSG_LOOT_UPDATE_FUNCS[category] jcallahan@177: jcallahan@179: local item_link, quantity = deformat(message, _G.LOOT_ITEM_PUSHED_SELF_MULTIPLE) jcallahan@179: jcallahan@179: if not item_link then jcallahan@179: quantity, item_link = 1, deformat(message, _G.LOOT_ITEM_PUSHED_SELF) jcallahan@179: end jcallahan@179: local item_id = ItemLinkToID(item_link) jcallahan@179: jcallahan@179: if not item_id then jcallahan@179: return jcallahan@179: end MMOSimca@340: MMOSimca@340: if not category or not update_func then MMOSimca@340: -- We still want to record the item's data, even if it doesn't need its drop location recorded MMOSimca@340: RecordItemData(item_id, item_link, true) MMOSimca@340: return MMOSimca@340: end jcallahan@179: update_func(item_id, quantity) jcallahan@177: end jcallahan@48: end jcallahan@48: jcallahan@312: 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 jcallahan@167: DBEntry("spells", tonumber(_G.GetTradeSkillRecipeLink(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) jcallahan@167: if not private.trainer_shown then jcallahan@167: local recipe_name = message:match(RECIPE_MATCH) jcallahan@167: jcallahan@167: if recipe_name and private.previous_spell_id then jcallahan@167: local profession_name, prof_level = _G.GetTradeSkillLine() jcallahan@167: jcallahan@167: if profession_name == _G.UNKNOWN then jcallahan@167: return jcallahan@167: end jcallahan@167: private.discovered_recipe_name = recipe_name jcallahan@167: private.profession_level = prof_level jcallahan@167: jcallahan@167: self:ScheduleTimer(IterativeRecordDiscovery, 0.2) jcallahan@167: end jcallahan@167: end jcallahan@167: jcallahan@40: if currently_drunk then jcallahan@40: if message == _G.DRUNK_MESSAGE_SELF1 or message:match(SOBER_MATCH) then jcallahan@40: currently_drunk = nil jcallahan@40: end jcallahan@40: return jcallahan@40: end jcallahan@40: jcallahan@40: for index = 1, #DRUNK_MATCHES do jcallahan@40: if message == DRUNK_COMPARES[index] or message:match(DRUNK_MATCHES[index]) then jcallahan@40: currently_drunk = true jcallahan@40: break jcallahan@40: end jcallahan@40: end 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: jcallahan@23: local function RecordNPCSpell(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id, spell_name) MMOSimca@336: if not spell_id or spell_id == CHI_WAVE_SPELL_ID or spell_id == DISGUISE_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 jcallahan@177: WDP:ScheduleTimer(ClearKilledNPC, 0.1) 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: MMOSimca@337: 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@340: local FACTION_DATA = private.FACTION_DATA MMOSimca@337: local REP_BUFFS = private.REP_BUFFS 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 jcallahan@44: MMOSimca@340: -- Determine faction ID MMOSimca@340: local faction_ID MMOSimca@340: for pseudo_faction_name, faction_data_table in pairs(FACTION_DATA) do MMOSimca@340: if faction_name == faction_data_table[2] then MMOSimca@340: faction_ID = faction_data_table[1] 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@340: for buff_name, buff_data_table in pairs(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 MMOSimca@340: 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) jcallahan@141: if current_action.fishing_target or _G.Minimap:IsMouseOver() or current_action.spell_label ~= "FISHING" then jcallahan@140: return jcallahan@140: end jcallahan@140: local text = _G["GameTooltipTextLeft1"]:GetText() jcallahan@140: jcallahan@140: if not text or text == "Fishing Bobber" then jcallahan@140: text = "NONE" jcallahan@140: else jcallahan@140: current_action.fishing_target = true jcallahan@140: end jcallahan@140: current_action.identifier = ("%s:%s"):format(current_action.spell_label, text) 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: jcallahan@122: if 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, jcallahan@322: [AF.NPC] = true, jcallahan@14: [AF.OBJECT] = true, 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 {} jcallahan@312: 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 jcallahan@131: table.insert(current_loot.zone_data[location_token].drops, current_loot.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) 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) jcallahan@322: local extrapolated_guid_registry = {} jcallahan@322: local num_guids = 0 jcallahan@322: jcallahan@322: table.wipe(current_action) jcallahan@322: 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: local log_source = event_name .. "- ExtrapolatedCurrentActionFromLootData" 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: if private.previous_spell_id and private.EXTRAPOLATION_BANNED_SPELL_IDS[private.previous_spell_id] then jcallahan@326: Debug("%s: Problematic spell id %s found. Loot extrapolation for this set of loot would have run an increased risk of introducing bad data into the system.", log_source, private.previous_spell_id) jcallahan@326: return false jcallahan@326: 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: Debug("%s: Successfully extrapolated information for current_action.", log_source) jcallahan@322: return true jcallahan@322: end jcallahan@322: jcallahan@322: MMOSimca@343: function WDP:LOOT_OPENED(event_name) 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: Debug("%s: No target type found. 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@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 jcallahan@302: local icon_texture, item_text, slot_quantity, quality, locked = _G.GetLootSlotInfo(loot_slot) jcallahan@55: local slot_type = _G.GetLootSlotType(loot_slot) jcallahan@237: local loot_info = { jcallahan@237: _G.GetLootSourceInfo(loot_slot) jcallahan@237: } 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: jcallahan@324: if slot_type == _G.LOOT_SLOT_ITEM then jcallahan@324: local item_id = ItemLinkToID(_G.GetLootSlotLink(loot_slot)) jcallahan@324: 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) jcallahan@308: current_loot.sources[source_guid] = current_loot.sources[source_guid] or {} jcallahan@324: current_loot.sources[source_guid][item_id] = current_loot.sources[source_guid][item_id] or 0 + loot_quantity jcallahan@309: guids_used[source_guid] = true jcallahan@324: elseif slot_type == _G.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 {} jcallahan@324: 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 jcallahan@324: elseif slot_type == _G.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. jcallahan@324: if icon_texture then 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 jcallahan@324: table.insert(current_loot.list, ("currency:%d:%s"):format(loot_quantity, icon_texture:match("[^\\]+$"):lower())) jcallahan@324: else jcallahan@324: local currency_token = ("currency:%s"):format(icon_texture:match("[^\\]+$"):lower()) jcallahan@324: jcallahan@324: current_loot.sources[source_guid] = current_loot.sources[source_guid] or {} jcallahan@324: 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 jcallahan@324: 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@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. jcallahan@324: 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) 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) 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 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@328: if item_id then MMOSimca@328: Debug("%s: GetMerchantItemLink() still ocassionally fails, apparently. Failed item's ID - %s", event_name, item_id) MMOSimca@328: else MMOSimca@328: Debug("%s: GetMerchantItemLink() still ocassionally fails, apparently. Failed item's ID - nil", event_name) MMOSimca@328: end 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: jcallahan@115: function WDP:PET_JOURNAL_LIST_UPDATE(event_name) jcallahan@324: -- This function produces data currently unused by wowdb.com and it makes debugging errors in the .lua output nearly impossible due to the massive bloat. MMOSimca@342: if WOWDB_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 jcallahan@115: 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: jcallahan@156: if private.set_area_id then jcallahan@299: self:HandleZoneChange(event_name) jcallahan@156: private.set_area_id = nil jcallahan@156: end 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")) jcallahan@9: 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") 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) jcallahan@167: local spell_id = tonumber(_G.GetTradeSkillRecipeLink(tradeskill_index):match("^|c%x%x%x%x%x%x%x%x|H%w+:(%d+)")) jcallahan@167: local required_tool = _G.GetTradeSkillTools(tradeskill_index) jcallahan@167: jcallahan@167: if required_tool then jcallahan@167: for tool_name, registry in pairs(TRADESKILL_TOOLS) do jcallahan@167: if required_tool:find(tool_name) then jcallahan@167: registry[spell_id] = true 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) jcallahan@92: local profession_name, prof_level = _G.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 jcallahan@27: local available = _G.GetTrainerServiceTypeFilter("available") jcallahan@27: local unavailable = _G.GetTrainerServiceTypeFilter("unavailable") jcallahan@27: local used = _G.GetTrainerServiceTypeFilter("used") jcallahan@27: jcallahan@27: -- Clear the trainer filters MMOSimca@330: _G.SetTrainerServiceTypeFilter("available", true) MMOSimca@330: _G.SetTrainerServiceTypeFilter("unavailable", true) MMOSimca@330: _G.SetTrainerServiceTypeFilter("used", true) 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@330: _G.SetTrainerServiceTypeFilter("available", available or false) MMOSimca@330: _G.SetTrainerServiceTypeFilter("unavailable", unavailable or false) MMOSimca@330: _G.SetTrainerServiceTypeFilter("used", used or false) 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 MMOSimca@328: Debug("%s: Missing current location data - %s, %d, %d, %d, %d.", event_name, zone_name, area_id, x, y, 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: 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@336: private.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@336: local npc_id = private.raid_boss_id jcallahan@317: jcallahan@324: -- Slightly messy hack to workaround duplicate world bosses jcallahan@317: local upper_limit = 0 jcallahan@317: if DUPLICATE_WORLD_BOSS_IDS[npc_id] then jcallahan@317: upper_limit = #DUPLICATE_WORLD_BOSS_IDS[npc_id] jcallahan@317: end jcallahan@317: jcallahan@317: for i = 0, upper_limit do jcallahan@317: local temp_npc_id = npc_id jcallahan@317: jcallahan@317: if i > 0 then jcallahan@317: temp_npc_id = DUPLICATE_WORLD_BOSS_IDS[npc_id][i] jcallahan@317: end jcallahan@317: jcallahan@317: local npc = NPCEntry(temp_npc_id) jcallahan@317: if npc then jcallahan@324: -- Create needed npc fields if required jcallahan@317: local loot_label = "drops" jcallahan@317: local encounter_data = npc:EncounterData(InstanceDifficultyToken()) jcallahan@317: encounter_data[loot_label] = encounter_data[loot_label] or {} jcallahan@317: encounter_data.loot_counts = encounter_data.loot_counts or {} jcallahan@317: jcallahan@317: for index = 1, #loot_toast_data do jcallahan@317: local data = loot_toast_data[index] jcallahan@317: local loot_type = data[1] jcallahan@317: local hyperlink = data[2] jcallahan@317: local quantity = data[3] jcallahan@317: jcallahan@317: if loot_type == "item" then jcallahan@317: local item_id = ItemLinkToID(hyperlink) jcallahan@317: Debug("%s: Assigned stored item loot data - %s - %d:%d", event_name, hyperlink, item_id, quantity) jcallahan@317: table.insert(encounter_data[loot_label], ("%d:%d"):format(item_id, quantity)) jcallahan@317: elseif loot_type == "money" then jcallahan@317: Debug("%s: Assigned stored money loot data - money:%d", event_name, quantity) jcallahan@317: table.insert(encounter_data[loot_label], ("money:%d"):format(quantity)) jcallahan@317: elseif loot_type == "currency" then jcallahan@317: local currency_texture = CurrencyLinkToTexture(hyperlink) jcallahan@317: Debug("%s: Assigned stored currency loot data - %s - currency:%d:%s", event_name, hyperlink, currency_texture, quantity) jcallahan@324: -- Workaround for Patch 5.4.0 bug with Flexible raid Siege of Orgrimmar bosses and Valor Points jcallahan@317: if quantity > 1000 and currency_texture == "pvecurrency-valor" then jcallahan@317: quantity = math.floor(quantity / 100) jcallahan@317: end jcallahan@317: table.insert(encounter_data[loot_label], ("currency:%d:%s"):format(quantity, currency_texture)) jcallahan@317: end jcallahan@307: end jcallahan@317: jcallahan@317: if not boss_loot_toasting[temp_npc_id] then jcallahan@317: encounter_data.loot_counts[loot_label] = (encounter_data.loot_counts[loot_label] or 0) + 1 jcallahan@317: boss_loot_toasting[temp_npc_id] = true -- Do not count further loots until timer expires or another boss is killed jcallahan@317: end jcallahan@317: else jcallahan@317: Debug("%s: NPC is nil, but we have stored loot data...", event_name) jcallahan@307: end jcallahan@307: end jcallahan@307: end jcallahan@307: jcallahan@307: ClearLootToastData() jcallahan@307: jcallahan@307: killed_boss_id_timer_handle = WDP:ScheduleTimer(ClearKilledBossID, 5) -- we need to assign a handle here to cancel it later 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: jcallahan@306: if private.LOOT_SPELL_ID_TO_ITEM_ID_MAP[spell_id] then jcallahan@306: ClearKilledBossID() jcallahan@306: ClearLootToastContainerID() jcallahan@307: ClearLootToastData() jcallahan@306: jcallahan@306: private.loot_toast_container_id = private.LOOT_SPELL_ID_TO_ITEM_ID_MAP[spell_id] jcallahan@306: loot_toast_container_timer_handle = WDP:ScheduleTimer(ClearLootToastContainerID, 1) -- we need to assign a handle here to cancel it later jcallahan@306: end jcallahan@306: 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) jcallahan@90: SetUnitField("auctioneer", private.UNIT_TYPES.NPC) jcallahan@90: end jcallahan@90: jcallahan@90: jcallahan@90: function WDP:BANKFRAME_OPENED(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) 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) 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