annotate Main.lua @ 583:4a51fdc1e2e4

Began the process of updating for Midnight's changes.
author MMOSimca
date Sun, 05 Oct 2025 22:48:07 -0400
parents 78e9cbbf3d58
children be7931f9225c
rev   line source
jcallahan@246 1 -- LUA API ------------------------------------------------------------
jcallahan@246 2
jcallahan@0 3 local _G = getfenv(0)
jcallahan@0 4
jcallahan@0 5 local pairs = _G.pairs
jcallahan@312 6 local tostring = _G.tostring
jcallahan@1 7 local tonumber = _G.tonumber
jcallahan@1 8
jcallahan@1 9 local bit = _G.bit
jcallahan@1 10 local math = _G.math
jcallahan@1 11 local table = _G.table
jcallahan@1 12
jcallahan@334 13 local next = _G.next
jcallahan@78 14 local select = _G.select
mmosimca@485 15 local type = _G.type
jcallahan@306 16 local unpack = _G.unpack
jcallahan@78 17
MMOSimca@383 18 local C_Timer = _G.C_Timer
MMOSimca@581 19 local C_Spell = _G.C_Spell
mmosimca@496 20 local GetCurrencyInfo = _G.GetCurrencyInfo
MMOSimca@383 21
MMOSimca@583 22 local ICL = InCombatLockdown
MMOSimca@583 23 local III = IsInInstance
MMOSimca@583 24
jcallahan@0 25
jcallahan@246 26 -- ADDON NAMESPACE ----------------------------------------------------
jcallahan@246 27
jcallahan@0 28 local ADDON_NAME, private = ...
jcallahan@0 29
jcallahan@0 30 local LibStub = _G.LibStub
MMOSimca@383 31 local WDP = LibStub("AceAddon-3.0"):NewAddon(ADDON_NAME, "AceConsole-3.0", "AceEvent-3.0")
jcallahan@0 32
jcallahan@48 33 local deformat = LibStub("LibDeformat-3.0")
atcaleb@558 34 local HereBeDragons = LibStub("HereBeDragons-2.0")
jcallahan@48 35
jcallahan@4 36 local DatamineTT = _G.CreateFrame("GameTooltip", "WDPDatamineTT", _G.UIParent, "GameTooltipTemplate")
jcallahan@5 37 DatamineTT:SetOwner(_G.WorldFrame, "ANCHOR_NONE")
jcallahan@5 38
jcallahan@0 39
atcaleb@566 40 -- SIMPLE CONSTANTS ----------------------------------------------------------
jcallahan@246 41
jcallahan@246 42 local AF = private.ACTION_TYPE_FLAGS
jcallahan@246 43 local CLIENT_LOCALE = _G.GetLocale()
atcaleb@565 44 local DB_VERSION = 19
MMOSimca@346 45 local DEBUGGING = false
jcallahan@156 46 local EVENT_DEBUG = false
jcallahan@322 47
mmosimca@485 48 -- Timer durations in seconds
mmosimca@485 49 local DELAY_PROCESS_ITEMS = 30
atcaleb@566 50 local DELAY_PROCESS_WORLD_QUESTS = 90
mmosimca@485 51 local DELAY_UPDATE_TARGET_LOCATION = 0.5
mmosimca@485 52
MMOSimca@405 53 local ITEM_ID_TIMBER = 114781
MMOSimca@405 54
MMOSimca@581 55 local ENUM_LOOTSLOTTYPE_CURRENCY = _G.Enum.LootSlotType.Currency
MMOSimca@581 56 local ENUM_LOOTSLOTTYPE_ITEM = _G.Enum.LootSlotType.Item
MMOSimca@581 57 local ENUM_LOOTSLOTTYPE_MONEY = _G.Enum.LootSlotType.Money
mmosimca@520 58
MMOSimca@539 59 --local LOOT_SOURCE_ID_UNKNOWN = 1 -- Technically unused right now, but has future use potential
MMOSimca@539 60 --local LOOT_SOURCE_ID_REDUNDANT = 3 -- Technically unused right now, but has future use potential
MMOSimca@422 61 local LOOT_SOURCE_ID_GARRISON_CACHE = 10
MMOSimca@422 62
jcallahan@246 63 local OBJECT_ID_ANVIL = 192628
jcallahan@322 64 local OBJECT_ID_FISHING_BOBBER = 35591
jcallahan@246 65 local OBJECT_ID_FORGE = 1685
jcallahan@322 66
MMOSimca@454 67 local PLAYER_CLASS, PLAYER_CLASS_ID = _G.select(2, _G.UnitClass("player"))
jcallahan@246 68 local PLAYER_FACTION = _G.UnitFactionGroup("player")
jcallahan@300 69 local PLAYER_GUID
mmosimca@485 70 local PLAYER_LEVEL = _G.UnitLevel("player")
jcallahan@246 71 local PLAYER_NAME = _G.UnitName("player")
jcallahan@246 72 local PLAYER_RACE = _G.select(2, _G.UnitRace("player"))
jcallahan@246 73
mmosimca@520 74 local SPELL_ID_UPDATE_INTERACTIONS = 161006
MMOSimca@377 75
atcaleb@566 76 local UI_MAP_COSMIC = 946
atcaleb@566 77
jcallahan@246 78 local ALLOWED_LOCALES = {
jcallahan@246 79 enUS = true,
jcallahan@246 80 enGB = true,
MMOSimca@336 81 enTW = true,
MMOSimca@336 82 enCN = true,
jcallahan@246 83 }
jcallahan@157 84
jcallahan@0 85 local DATABASE_DEFAULTS = {
jcallahan@128 86 char = {},
jcallahan@0 87 global = {
jcallahan@270 88 config = {
jcallahan@270 89 minimap_icon = {
jcallahan@270 90 hide = true,
jcallahan@270 91 },
jcallahan@270 92 },
jcallahan@0 93 items = {},
jcallahan@0 94 npcs = {},
jcallahan@0 95 objects = {},
jcallahan@0 96 quests = {},
jcallahan@167 97 spells = {},
MMOSimca@550 98 world_quests = {},
jcallahan@17 99 zones = {},
jcallahan@0 100 }
jcallahan@0 101 }
jcallahan@0 102
jcallahan@1 103 local EVENT_MAPPING = {
MMOSimca@436 104 AUCTION_HOUSE_CLOSED = "ResumeChatLootRecording",
MMOSimca@436 105 AUCTION_HOUSE_SHOW = true, -- also triggers StopChatLootRecording
MMOSimca@436 106 BANKFRAME_CLOSED = "ResumeChatLootRecording",
MMOSimca@436 107 BANKFRAME_OPENED = true, -- also triggers StopChatLootRecording
jcallahan@90 108 BATTLEFIELDS_SHOW = true,
jcallahan@56 109 BLACK_MARKET_ITEM_UPDATE = true,
MMOSimca@408 110 BONUS_ROLL_RESULT = true,
MMOSimca@388 111 CHAT_MSG_CURRENCY = true,
jcallahan@48 112 CHAT_MSG_LOOT = true,
jcallahan@95 113 CHAT_MSG_MONSTER_SAY = "RecordQuote",
jcallahan@95 114 CHAT_MSG_MONSTER_WHISPER = "RecordQuote",
jcallahan@95 115 CHAT_MSG_MONSTER_YELL = "RecordQuote",
jcallahan@40 116 CHAT_MSG_SYSTEM = true,
jcallahan@23 117 COMBAT_LOG_EVENT_UNFILTERED = true,
jcallahan@18 118 COMBAT_TEXT_UPDATE = true,
MMOSimca@581 119 WORLD_CURSOR_TOOLTIP_UPDATE = true,
MMOSimca@436 120 GARRISON_MISSION_NPC_CLOSED = "ResumeChatLootRecording",
MMOSimca@436 121 GARRISON_MISSION_NPC_OPENED = "StopChatLootRecording",
MMOSimca@450 122 GARRISON_SHIPYARD_NPC_CLOSED = "ResumeChatLootRecording",
MMOSimca@450 123 GARRISON_SHIPYARD_NPC_OPENED = "StopChatLootRecording",
MMOSimca@436 124 GOSSIP_CLOSED = "ResumeChatLootRecording",
MMOSimca@436 125 GOSSIP_SHOW = true, -- also triggers StopChatLootRecording
jcallahan@290 126 GROUP_ROSTER_UPDATE = true,
MMOSimca@436 127 GUILDBANKFRAME_CLOSED = "ResumeChatLootRecording",
MMOSimca@436 128 GUILDBANKFRAME_OPENED = true, -- also triggers StopChatLootRecording
atcaleb@573 129 ISLAND_AZERITE_GAIN = true,
atcaleb@573 130 ISLAND_COMPLETED = true,
jcallahan@42 131 ITEM_TEXT_BEGIN = true,
jcallahan@124 132 LOOT_CLOSED = true,
MMOSimca@343 133 LOOT_OPENED = true,
MMOSimca@412 134 LOOT_SLOT_CLEARED = "HandleBadChatLootData",
MMOSimca@436 135 MAIL_CLOSED = "ResumeChatLootRecording",
MMOSimca@436 136 MAIL_SHOW = true, -- also triggers StopChatLootRecording
MMOSimca@436 137 MERCHANT_CLOSED = true, -- also triggers ResumeChatLootRecording
MMOSimca@436 138 MERCHANT_SHOW = "UpdateMerchantItems", -- also triggers StopChatLootRecording
jcallahan@61 139 MERCHANT_UPDATE = "UpdateMerchantItems",
jcallahan@25 140 PET_BAR_UPDATE = true,
MMOSimca@368 141 --PET_JOURNAL_LIST_UPDATE = true,
jcallahan@156 142 PLAYER_REGEN_DISABLED = true,
jcallahan@156 143 PLAYER_REGEN_ENABLED = true,
jcallahan@2 144 PLAYER_TARGET_CHANGED = true,
jcallahan@9 145 QUEST_COMPLETE = true,
jcallahan@9 146 QUEST_DETAIL = true,
jcallahan@9 147 QUEST_LOG_UPDATE = true,
jcallahan@97 148 QUEST_PROGRESS = true,
jcallahan@178 149 SHOW_LOOT_TOAST = true,
jcallahan@306 150 SPELL_CONFIRMATION_PROMPT = true,
jcallahan@88 151 TAXIMAP_OPENED = true,
MMOSimca@437 152 TRADE_CLOSED = "ResumeChatLootRecording",
MMOSimca@437 153 TRADE_SHOW = "StopChatLootRecording",
MMOSimca@581 154 --TRADE_SKILL_SHOW = true,
MMOSimca@581 155 --TRAINER_CLOSED = true,
MMOSimca@581 156 --TRAINER_SHOW = true,
jcallahan@246 157 UNIT_PET = true,
jcallahan@4 158 UNIT_QUEST_LOG_CHANGED = true,
jcallahan@1 159 UNIT_SPELLCAST_FAILED = "HandleSpellFailure",
jcallahan@1 160 UNIT_SPELLCAST_FAILED_QUIET = "HandleSpellFailure",
jcallahan@1 161 UNIT_SPELLCAST_INTERRUPTED = "HandleSpellFailure",
jcallahan@1 162 UNIT_SPELLCAST_SENT = true,
jcallahan@1 163 UNIT_SPELLCAST_SUCCEEDED = true,
MMOSimca@581 164 PLAYER_INTERACTION_MANAGER_FRAME_SHOW = true,
jcallahan@0 165 }
jcallahan@0 166
jcallahan@4 167
jcallahan@246 168 -- VARIABLES ----------------------------------------------------------
jcallahan@246 169
jcallahan@92 170 local anvil_spell_ids = {}
jcallahan@92 171 local currently_drunk
jcallahan@128 172 local char_db
jcallahan@128 173 local global_db
jcallahan@299 174 local group_member_guids = {}
jcallahan@246 175 local group_owner_guids_to_pet_guids = {}
jcallahan@246 176 local group_pet_guids = {}
jcallahan@299 177 local in_instance
jcallahan@187 178 local item_process_timer_handle
jcallahan@92 179 local faction_standings = {}
jcallahan@92 180 local forge_spell_ids = {}
jcallahan@95 181 local languages_known = {}
jcallahan@317 182 local boss_loot_toasting = {}
MMOSimca@387 183 local container_loot_toasting
MMOSimca@387 184 local loot_toast_container_id
MMOSimca@387 185 local raid_boss_id
jcallahan@306 186 local loot_toast_container_timer_handle
jcallahan@307 187 local loot_toast_data
jcallahan@307 188 local loot_toast_data_timer_handle
jcallahan@95 189 local name_to_id_map = {}
jcallahan@306 190 local killed_boss_id_timer_handle
jcallahan@177 191 local killed_npc_id
jcallahan@2 192 local target_location_timer_handle
MMOSimca@345 193 local last_timber_spell_id
MMOSimca@355 194 local last_garrison_cache_object_id
MMOSimca@436 195 local block_chat_loot_data
MMOSimca@435 196 local chat_loot_data = {}
MMOSimca@347 197 local chat_loot_timer_handle
mmosimca@485 198 local world_quest_timer_handle
mmosimca@485 199 local world_quest_event_timestamp = 0
atcaleb@573 200 local killed_npcs_in_island = {}
atcaleb@573 201 local island_difficulty_token
jcallahan@86 202 local current_target_id
jcallahan@131 203 local current_loot
jcallahan@1 204
jcallahan@312 205
jcallahan@121 206 -- Data for our current action. Including possible values as a reference.
jcallahan@122 207 local current_action = {
jcallahan@121 208 identifier = nil,
jcallahan@121 209 loot_label = nil,
jcallahan@121 210 loot_list = nil,
jcallahan@121 211 loot_sources = nil,
jcallahan@121 212 map_level = nil,
jcallahan@121 213 spell_label = nil,
jcallahan@123 214 target_type = nil,
jcallahan@121 215 x = nil,
jcallahan@121 216 y = nil,
jcallahan@121 217 zone_data = nil,
jcallahan@121 218 }
jcallahan@92 219
jcallahan@246 220
MMOSimca@393 221 -- Timer prototypes
MMOSimca@393 222 local ClearKilledNPC, ClearKilledBossID, ClearLootToastContainerID, ClearLootToastData, ClearChatLootData
MMOSimca@393 223
MMOSimca@393 224
jcallahan@246 225 -- HELPERS ------------------------------------------------------------
jcallahan@246 226
jcallahan@245 227 local function Debug(message, ...)
MMOSimca@350 228 if not DEBUGGING or not message then
jcallahan@151 229 return
jcallahan@151 230 end
catherton@465 231
MMOSimca@350 232 if ... then
MMOSimca@350 233 local args = { ... }
MMOSimca@350 234
MMOSimca@350 235 for index = 1, #args do
MMOSimca@377 236 args[index] = tostring(args[index])
jcallahan@306 237 end
MMOSimca@350 238 _G.print(message:format(unpack(args)))
MMOSimca@350 239 else
MMOSimca@350 240 _G.print(message)
jcallahan@306 241 end
jcallahan@151 242 end
jcallahan@151 243
jcallahan@151 244
MMOSimca@393 245 local function InitializeCurrentLoot()
MMOSimca@393 246 current_loot = {
MMOSimca@393 247 list = {},
MMOSimca@393 248 sources = {},
MMOSimca@393 249 identifier = current_action.identifier,
MMOSimca@393 250 label = current_action.loot_label or "drops",
MMOSimca@393 251 map_level = current_action.map_level,
MMOSimca@393 252 object_name = current_action.object_name,
MMOSimca@393 253 spell_label = current_action.spell_label,
MMOSimca@393 254 target_type = current_action.target_type,
MMOSimca@393 255 x = current_action.x,
MMOSimca@393 256 y = current_action.y,
MMOSimca@393 257 zone_data = current_action.zone_data,
MMOSimca@393 258 }
MMOSimca@393 259
MMOSimca@393 260 table.wipe(current_action)
MMOSimca@393 261 end
MMOSimca@393 262
MMOSimca@393 263
jcallahan@169 264 local TradeSkillExecutePer
jcallahan@169 265 do
jcallahan@169 266 local header_list = {}
jcallahan@169 267
jcallahan@169 268 function TradeSkillExecutePer(iter_func)
jcallahan@169 269 if not _G.TradeSkillFrame or not _G.TradeSkillFrame:IsVisible() then
jcallahan@169 270 return
jcallahan@169 271 end
catherton@465 272
catherton@479 273 local recipes = _G.C_TradeSkillUI.GetAllRecipeIDs()
catherton@479 274
catherton@479 275 if recipes and (#recipes > 0) then
catherton@479 276 for i = 1, #recipes do
catherton@479 277 if iter_func(_G.C_TradeSkillUI.GetRecipeInfo(recipes[i]).name, recipes[i]) then
catherton@465 278 break
catherton@465 279 end
catherton@465 280 end
catherton@465 281 end
jcallahan@167 282 end
jcallahan@169 283 end -- do-block
jcallahan@167 284
jcallahan@167 285
jcallahan@39 286 local ActualCopperCost
jcallahan@39 287 do
jcallahan@39 288 local BARTERING_SPELL_ID = 83964
jcallahan@39 289
jcallahan@39 290 function ActualCopperCost(copper_cost, rep_standing)
jcallahan@39 291 if not copper_cost or copper_cost == 0 then
jcallahan@39 292 return 0
jcallahan@39 293 end
jcallahan@39 294 local modifier = 1
jcallahan@39 295
jcallahan@39 296 if _G.IsSpellKnown(BARTERING_SPELL_ID) then
jcallahan@39 297 modifier = modifier - 0.1
jcallahan@39 298 end
jcallahan@39 299
jcallahan@39 300 if rep_standing then
jcallahan@39 301 if PLAYER_RACE == "Goblin" then
atcaleb@558 302 modifier = modifier - private.STANDING_DISCOUNTS["EXALTED"]
atcaleb@558 303 elseif private.STANDING_DISCOUNTS[rep_standing] then
atcaleb@558 304 modifier = modifier - private.STANDING_DISCOUNTS[rep_standing]
jcallahan@39 305 end
jcallahan@39 306 end
jcallahan@39 307 return math.floor(copper_cost / modifier)
jcallahan@39 308 end
jcallahan@39 309 end -- do-block
jcallahan@39 310
jcallahan@39 311
jcallahan@29 312 local function InstanceDifficultyToken()
MMOSimca@440 313 -- Sometimes, instance information is returned when not in an instance. This check protects against that.
MMOSimca@440 314 if _G.IsInInstance() then
MMOSimca@440 315 local _, instance_type, instance_difficulty, _, _, _, is_dynamic = _G.GetInstanceInfo()
MMOSimca@440 316
MMOSimca@440 317 if not instance_type or instance_type == "" then
MMOSimca@440 318 instance_type = "NONE"
MMOSimca@440 319 end
MMOSimca@440 320 return ("%s:%d:%s"):format(instance_type:upper(), instance_difficulty, tostring(is_dynamic))
jcallahan@59 321 end
MMOSimca@440 322 return "NONE:0:false"
jcallahan@29 323 end
jcallahan@29 324
jcallahan@29 325
jcallahan@1 326 local function CurrentLocationData()
mmosimca@488 327 local x, y, current_area_id, map_level, map_file, is_micro_dungeon = HereBeDragons:GetPlayerZonePosition(false)
catherton@468 328 local zone_name = _G.GetRealZoneText()
catherton@465 329
mmosimca@488 330 -- Remove micro-dungeon-ness by translating back to the parent world map (at floor 0) if possible
mmosimca@488 331 if (is_micro_dungeon and x and y and current_area_id and map_level and map_level > 0) then
mmosimca@488 332 x, y = HereBeDragons:TranslateZoneCoordinates(x, y, current_area_id, map_level, current_area_id, 0, false)
mmosimca@488 333 map_level = 0
mmosimca@488 334 end
mmosimca@488 335
catherton@465 336 -- Put coordinates into expected format (as integers, they don't get a billion decimals output in the SavedVariables)
catherton@468 337 local x_int = nil
catherton@468 338 if (x and type(x) == "number") then
catherton@468 339 x_int = _G.floor(x * 1000)
mmosimca@485 340
mmosimca@482 341 -- Limit precision to 0.2
catherton@468 342 if x_int % 2 ~= 0 then
catherton@468 343 x_int = x_int + 1
catherton@468 344 end
mmosimca@485 345
mmosimca@482 346 -- Prevent out of bounds coordinates
mmosimca@482 347 if (x_int < 0 or x_int > 1000) then
mmosimca@482 348 x_int = nil
mmosimca@482 349 end
jcallahan@145 350 end
catherton@468 351 local y_int = nil
catherton@468 352 if (y and type(y) == "number") then
catherton@468 353 y_int = _G.floor(y * 1000)
mmosimca@485 354
mmosimca@482 355 -- Limit precision to 0.2
catherton@468 356 if y_int % 2 ~= 0 then
catherton@468 357 y_int = y_int + 1
catherton@468 358 end
mmosimca@485 359
mmosimca@482 360 -- Prevent out of bounds coordinates
mmosimca@482 361 if (y_int < 0 or y_int > 1000) then
mmosimca@482 362 y_int = nil
mmosimca@482 363 end
jcallahan@1 364 end
jcallahan@1 365
catherton@468 366 return zone_name, current_area_id, x_int, y_int, map_level, InstanceDifficultyToken()
jcallahan@1 367 end
jcallahan@1 368
jcallahan@1 369
MMOSimca@441 370 local function DBEntry(data_type, unit_id)
MMOSimca@441 371 if not data_type or not unit_id then
jcallahan@312 372 return
jcallahan@312 373 end
MMOSimca@441 374 local category = global_db[data_type]
MMOSimca@441 375
MMOSimca@441 376 if not category then
MMOSimca@441 377 category = {}
MMOSimca@441 378 global_db[data_type] = category
MMOSimca@441 379 end
MMOSimca@441 380 local unit = category[unit_id]
MMOSimca@441 381
MMOSimca@441 382 if not unit then
MMOSimca@441 383 unit = {}
MMOSimca@441 384 category[unit_id] = unit
MMOSimca@441 385 end
MMOSimca@441 386 return unit
jcallahan@312 387 end
jcallahan@312 388
MMOSimca@441 389 private.DBEntry = DBEntry
MMOSimca@441 390
MMOSimca@441 391 local NPCEntry
MMOSimca@441 392 do
MMOSimca@441 393 local npc_prototype = {}
MMOSimca@441 394 local npc_meta = {
MMOSimca@441 395 __index = npc_prototype
MMOSimca@441 396 }
MMOSimca@441 397
MMOSimca@441 398 function NPCEntry(identifier)
MMOSimca@441 399 local npc = DBEntry("npcs", identifier)
MMOSimca@441 400 return npc and _G.setmetatable(npc, npc_meta) or nil
jcallahan@1 401 end
MMOSimca@441 402
MMOSimca@441 403 function npc_prototype:EncounterData(difficulty_token)
MMOSimca@441 404 self.encounter_data = self.encounter_data or {}
MMOSimca@441 405 self.encounter_data[difficulty_token] = self.encounter_data[difficulty_token] or {}
MMOSimca@441 406 self.encounter_data[difficulty_token].stats = self.encounter_data[difficulty_token].stats or {}
MMOSimca@441 407
MMOSimca@441 408 return self.encounter_data[difficulty_token]
MMOSimca@441 409 end
jcallahan@1 410 end
jcallahan@270 411
jcallahan@4 412
jcallahan@141 413 local UpdateDBEntryLocation
jcallahan@141 414 do
jcallahan@141 415 -- Fishing node coordinate code based on code in GatherMate2 with permission from Kagaro.
jcallahan@141 416 local function FishingCoordinates(x, y, yard_width, yard_height)
jcallahan@141 417 local facing = _G.GetPlayerFacing()
jcallahan@141 418
jcallahan@141 419 if not facing then
jcallahan@141 420 return x, y
jcallahan@141 421 end
jcallahan@246 422 local rad = facing + math.pi
jcallahan@141 423 return x + math.sin(rad) * 15 / yard_width, y + math.cos(rad) * 15 / yard_height
jcallahan@10 424 end
jcallahan@10 425
jcallahan@24 426
jcallahan@141 427 function UpdateDBEntryLocation(entry_type, identifier)
jcallahan@141 428 if not identifier then
jcallahan@141 429 return
jcallahan@141 430 end
jcallahan@141 431 local zone_name, area_id, x, y, map_level, difficulty_token = CurrentLocationData()
MMOSimca@328 432 if not (zone_name and area_id and x and y and map_level) then
mmosimca@508 433 if not (_G.IsInInstance()) then
mmosimca@508 434 Debug("UpdateDBEntryLocation: Missing current location data - %s, %s, %s, %s, %s.", tostring(zone_name), tostring(area_id), tostring(x), tostring(y), tostring(map_level))
mmosimca@508 435 end
MMOSimca@328 436 return
MMOSimca@328 437 end
jcallahan@141 438 local entry = DBEntry(entry_type, identifier)
jcallahan@141 439 entry[difficulty_token] = entry[difficulty_token] or {}
jcallahan@141 440 entry[difficulty_token].locations = entry[difficulty_token].locations or {}
jcallahan@141 441
jcallahan@141 442 local zone_token = ("%s:%d"):format(zone_name, area_id)
jcallahan@141 443 local zone_data = entry[difficulty_token].locations[zone_token]
jcallahan@141 444
jcallahan@141 445 if not zone_data then
jcallahan@141 446 zone_data = {}
jcallahan@141 447 entry[difficulty_token].locations[zone_token] = zone_data
jcallahan@141 448 end
jcallahan@141 449
jcallahan@141 450 -- Special case for Fishing.
jcallahan@141 451 if current_action.spell_label == "FISHING" then
atcaleb@561 452 local _, q_vector = _G.C_Map.GetWorldPosFromMapPos(area_id, _G.CreateVector2D(0,0))
atcaleb@561 453 local _, w_vector = _G.C_Map.GetWorldPosFromMapPos(area_id, _G.CreateVector2D(1,1))
atcaleb@561 454 local yard_width, yard_height = q_vector.y - w_vector.y, q_vector.x - w_vector.x
jcallahan@141 455
jcallahan@141 456 if yard_width > 0 and yard_height > 0 then
jcallahan@141 457 x, y = FishingCoordinates(x, y, yard_width, yard_height)
jcallahan@141 458 current_action.x = x
jcallahan@141 459 current_action.y = y
jcallahan@141 460 end
jcallahan@141 461 end
jcallahan@141 462 local location_token = ("%d:%d:%d"):format(map_level, x, y)
jcallahan@141 463
jcallahan@141 464 zone_data[location_token] = zone_data[location_token] or true
jcallahan@141 465 return zone_data
jcallahan@10 466 end
jcallahan@141 467 end -- do-block
jcallahan@10 468
jcallahan@10 469
mmosimca@496 470 local function CurrencyLinkToID(currency_link)
MMOSimca@441 471 if not currency_link then
mmosimca@496 472 return nil
MMOSimca@441 473 end
MMOSimca@540 474 return tonumber(tostring(currency_link):match("currency:(%d+)"))
MMOSimca@441 475 end
MMOSimca@441 476
MMOSimca@441 477
MMOSimca@441 478 local function ItemLinkToID(item_link)
MMOSimca@441 479 if not item_link then
MMOSimca@441 480 return
MMOSimca@441 481 end
MMOSimca@441 482 return tonumber(tostring(item_link):match("item:(%d+)"))
MMOSimca@441 483 end
MMOSimca@441 484
MMOSimca@441 485 private.ItemLinkToID = ItemLinkToID
MMOSimca@441 486
MMOSimca@441 487 local function UnitTypeIsNPC(unit_type)
MMOSimca@441 488 return unit_type == private.UNIT_TYPES.NPC or unit_type == private.UNIT_TYPES.VEHICLE
MMOSimca@441 489 end
MMOSimca@441 490
MMOSimca@441 491
MMOSimca@441 492 local ParseGUID
MMOSimca@441 493 do
MMOSimca@441 494 local UNIT_TYPES = private.UNIT_TYPES
MMOSimca@441 495
MMOSimca@441 496 local NPC_ID_MAPPING = {
MMOSimca@441 497 [62164] = 63191, -- Garalon
MMOSimca@441 498 }
MMOSimca@441 499
MMOSimca@441 500
MMOSimca@441 501 local function MatchUnitTypes(unit_type_name)
MMOSimca@441 502 if not unit_type_name then
MMOSimca@441 503 return UNIT_TYPES.UNKNOWN
MMOSimca@441 504 end
MMOSimca@441 505
MMOSimca@441 506 for def, text in next, UNIT_TYPES do
MMOSimca@441 507 if unit_type_name == text then
MMOSimca@441 508 return UNIT_TYPES[def]
MMOSimca@441 509 end
MMOSimca@441 510 end
MMOSimca@441 511 return UNIT_TYPES.UNKNOWN
MMOSimca@441 512 end
MMOSimca@441 513
MMOSimca@441 514
MMOSimca@441 515 function ParseGUID(guid)
MMOSimca@441 516 if not guid then
MMOSimca@441 517 return
MMOSimca@441 518 end
MMOSimca@441 519
MMOSimca@441 520 -- We might want to use some of this new information later, but leaving the returns alone for now
mmosimca@497 521 local guid_pieces = { ("-"):split(guid) }
mmosimca@497 522 local unit_type_name, unk_field, server_id, instance_id, zone_uid, unit_id, spawn_uid, player_uid = guid_pieces[1]
MMOSimca@441 523
MMOSimca@441 524 local unit_type = MatchUnitTypes(unit_type_name)
mmosimca@518 525
mmosimca@497 526 -- Creatures, GameObjects, Vehicles, and Vignettes
MMOSimca@441 527 if unit_type ~= UNIT_TYPES.PLAYER and unit_type ~= UNIT_TYPES.PET and unit_type ~= UNIT_TYPES.ITEM then
mmosimca@497 528 unk_field, server_id, instance_id, zone_uid, unit_id, spawn_uid = guid_pieces[2], guid_pieces[3], guid_pieces[4], guid_pieces[5], guid_pieces[6], guid_pieces[7]
mmosimca@497 529
mmosimca@497 530 local id_mapping = NPC_ID_MAPPING[unit_id]
MMOSimca@441 531
MMOSimca@441 532 if id_mapping and UnitTypeIsNPC(unit_type) then
mmosimca@497 533 unit_id = id_mapping
MMOSimca@441 534 end
mmosimca@497 535
mmosimca@497 536 -- Players
mmosimca@497 537 elseif unit_type == UNIT_TYPES.PLAYER then
mmosimca@497 538 server_id, player_uid = guid_pieces[2], guid_pieces[3]
mmosimca@497 539
mmosimca@497 540 -- Items
mmosimca@497 541 elseif unit_type == UNIT_TYPES.ITEM then
mmosimca@497 542 server_id, unk_field, spawn_uid = guid_pieces[2], guid_pieces[3], guid_pieces[4]
MMOSimca@441 543 end
mmosimca@518 544
mmosimca@497 545 -- Pets and other (i.e. do nothing)
mmosimca@497 546 return unit_type, unit_id
MMOSimca@441 547 end
MMOSimca@441 548
MMOSimca@441 549 private.ParseGUID = ParseGUID
MMOSimca@441 550 end -- do-block
MMOSimca@441 551
MMOSimca@441 552
jcallahan@19 553 local function HandleItemUse(item_link, bag_index, slot_index)
jcallahan@19 554 if not item_link then
jcallahan@19 555 return
jcallahan@19 556 end
jcallahan@19 557 local item_id = ItemLinkToID(item_link)
jcallahan@19 558
jcallahan@19 559 if not bag_index or not slot_index then
jcallahan@19 560 for new_bag_index = 0, _G.NUM_BAG_FRAMES do
MMOSimca@581 561 for new_slot_index = 1, _G.C_Container.GetContainerNumSlots(new_bag_index) do
MMOSimca@581 562 if item_id == ItemLinkToID(_G.C_Container.GetContainerItemLink(new_bag_index, new_slot_index)) then
jcallahan@19 563 bag_index = new_bag_index
jcallahan@19 564 slot_index = new_slot_index
jcallahan@19 565 break
jcallahan@19 566 end
jcallahan@19 567 end
jcallahan@19 568 end
jcallahan@19 569 end
jcallahan@19 570
MMOSimca@410 571 local any_loot = false
MMOSimca@410 572
MMOSimca@411 573 -- Check if Blizzard has marked this item as officially having a chance of containing loot
MMOSimca@410 574 if bag_index and slot_index then
MMOSimca@581 575 local _, _, _, _, _, is_lootable = _G.C_Container.GetContainerItemInfo(bag_index, slot_index)
MMOSimca@410 576 if is_lootable then
MMOSimca@410 577 any_loot = true
MMOSimca@410 578 end
jcallahan@19 579 end
catherton@465 580
MMOSimca@410 581 -- Check if we've marked this item as one Blizzard provides bad is_lootable data for
MMOSimca@410 582 if private.CONTAINER_ITEM_ID_LIST[item_id] ~= nil then
MMOSimca@410 583 any_loot = true
jcallahan@19 584 end
MMOSimca@368 585
MMOSimca@436 586 -- Going to block 'chat-loot data' at this level for now because I don't think we actually want normal item containers being recorded in these scenarios either.
MMOSimca@436 587 if any_loot and not block_chat_loot_data then
MMOSimca@414 588 -- For item containers that open instantly with no spell cast
MMOSimca@410 589 if (private.CONTAINER_ITEM_ID_LIST[item_id] == true) and ((not _G.GetNumLootItems()) or (_G.GetNumLootItems() == 0)) then
MMOSimca@410 590 ClearChatLootData()
MMOSimca@410 591 Debug("HandleItemUse: Beginning chat-based loot timer for item with ID %d.", item_id)
MMOSimca@423 592 chat_loot_timer_handle = C_Timer.NewTimer(1.5, ClearChatLootData)
atcaleb@573 593 chat_loot_data.category = AF.ITEM
MMOSimca@414 594 chat_loot_data.identifier = item_id
MMOSimca@414 595 -- For normal item containers
MMOSimca@414 596 else
MMOSimca@414 597 table.wipe(current_action)
MMOSimca@414 598 current_loot = nil
MMOSimca@414 599 current_action.target_type = AF.ITEM
MMOSimca@414 600 current_action.identifier = item_id
MMOSimca@414 601 current_action.loot_label = "contains"
MMOSimca@410 602 end
MMOSimca@393 603 end
jcallahan@19 604 end
jcallahan@19 605
jcallahan@19 606
jcallahan@39 607 local UnitFactionStanding
jcallahan@32 608 local UpdateFactionData
jcallahan@32 609 do
jcallahan@32 610 local MAX_FACTION_INDEX = 1000
jcallahan@20 611
jcallahan@32 612 local STANDING_NAMES = {
jcallahan@32 613 "HATED",
jcallahan@32 614 "HOSTILE",
jcallahan@32 615 "UNFRIENDLY",
jcallahan@32 616 "NEUTRAL",
jcallahan@32 617 "FRIENDLY",
jcallahan@32 618 "HONORED",
jcallahan@32 619 "REVERED",
jcallahan@32 620 "EXALTED",
jcallahan@32 621 }
jcallahan@32 622
jcallahan@39 623
jcallahan@39 624 function UnitFactionStanding(unit)
jcallahan@135 625 local unit_name = _G.UnitName(unit)
jcallahan@39 626 UpdateFactionData()
jcallahan@39 627 DatamineTT:ClearLines()
jcallahan@39 628 DatamineTT:SetUnit(unit)
jcallahan@39 629
jcallahan@39 630 for line_index = 1, DatamineTT:NumLines() do
jcallahan@64 631 local faction_name = _G["WDPDatamineTTTextLeft" .. line_index]:GetText():trim()
jcallahan@39 632
jcallahan@135 633 if faction_name and faction_name ~= unit_name and faction_standings[faction_name] then
jcallahan@39 634 return faction_name, faction_standings[faction_name]
jcallahan@39 635 end
jcallahan@39 636 end
jcallahan@39 637 end
jcallahan@39 638
jcallahan@39 639
jcallahan@32 640 function UpdateFactionData()
jcallahan@32 641 for faction_index = 1, MAX_FACTION_INDEX do
MMOSimca@581 642 local faction_table = _G.C_Reputation.GetFactionDataByIndex(faction_index)
MMOSimca@581 643
MMOSimca@581 644 if faction_table then
MMOSimca@581 645 if faction_table.name then
MMOSimca@581 646 faction_standings[faction_table.name] = STANDING_NAMES[faction_table.currentStanding]
MMOSimca@581 647 elseif not faction_table.name then
MMOSimca@581 648 break
MMOSimca@581 649 end
MMOSimca@581 650 end
jcallahan@20 651 end
jcallahan@20 652 end
jcallahan@32 653 end -- do-block
jcallahan@20 654
jcallahan@48 655
MMOSimca@429 656 local GenericLootUpdate, LootTable
jcallahan@75 657 do
MMOSimca@429 658 function LootTable(entry, loot_type, top_field)
jcallahan@75 659 if top_field then
jcallahan@75 660 entry[top_field] = entry[top_field] or {}
jcallahan@75 661 entry[top_field][loot_type] = entry[top_field][loot_type] or {}
jcallahan@75 662 return entry[top_field][loot_type]
jcallahan@75 663 end
jcallahan@48 664 entry[loot_type] = entry[loot_type] or {}
jcallahan@75 665 return entry[loot_type]
jcallahan@48 666 end
jcallahan@48 667
jcallahan@75 668 function GenericLootUpdate(data_type, top_field)
jcallahan@132 669 local loot_type = current_loot.label
jcallahan@75 670 local loot_count = ("%s_count"):format(loot_type)
jcallahan@77 671 local source_list = {}
jcallahan@75 672
jcallahan@131 673 if current_loot.sources then
jcallahan@131 674 for source_guid, loot_data in pairs(current_loot.sources) do
jcallahan@304 675 local source_id
jcallahan@78 676
jcallahan@131 677 if current_loot.target_type == AF.ITEM then
jcallahan@119 678 -- Items return the player as the source, so we need to use the item's ID (disenchant, milling, etc)
jcallahan@131 679 source_id = current_loot.identifier
jcallahan@119 680 else
jcallahan@331 681 local _, unit_ID = ParseGUID(source_guid)
MMOSimca@328 682 if unit_ID then
MMOSimca@328 683 if current_loot.target_type == AF.OBJECT then
MMOSimca@328 684 source_id = ("%s:%s"):format(current_loot.spell_label, unit_ID)
MMOSimca@328 685 else
MMOSimca@328 686 source_id = unit_ID
MMOSimca@328 687 end
MMOSimca@328 688 end
jcallahan@119 689 end
jcallahan@304 690 local entry = DBEntry(data_type, source_id)
jcallahan@75 691
jcallahan@119 692 if entry then
jcallahan@119 693 local loot_table = LootTable(entry, loot_type, top_field)
jcallahan@77 694
jcallahan@304 695 if not source_list[source_id] then
jcallahan@119 696 if top_field then
jcallahan@119 697 entry[top_field][loot_count] = (entry[top_field][loot_count] or 0) + 1
MMOSimca@387 698 elseif not container_loot_toasting then
jcallahan@119 699 entry[loot_count] = (entry[loot_count] or 0) + 1
jcallahan@119 700 end
jcallahan@304 701 source_list[source_id] = true
jcallahan@77 702 end
jcallahan@119 703 UpdateDBEntryLocation(data_type, source_id)
jcallahan@75 704
jcallahan@309 705 if current_loot.target_type == AF.ZONE then
jcallahan@309 706 for item_id, quantity in pairs(loot_data) do
jcallahan@309 707 table.insert(loot_table, ("%d:%d"):format(item_id, quantity))
jcallahan@309 708 end
jcallahan@309 709 else
jcallahan@308 710 for loot_token, quantity in pairs(loot_data) do
mmosimca@496 711 local label, currency_id = (":"):split(loot_token)
mmosimca@496 712
mmosimca@496 713 if label == "currency" and currency_id then
mmosimca@496 714 -- Convert currency_id back into number from string
mmosimca@496 715 currency_id = tonumber(currency_id) or 0
mmosimca@496 716 if currency_id ~= 0 then
mmosimca@496 717 table.insert(loot_table, ("currency:%d:%d"):format(quantity, currency_id))
mmosimca@496 718 end
jcallahan@308 719 elseif loot_token == "money" then
jcallahan@308 720 table.insert(loot_table, ("money:%d"):format(quantity))
jcallahan@308 721 else
jcallahan@308 722 table.insert(loot_table, ("%d:%d"):format(loot_token, quantity))
jcallahan@308 723 end
jcallahan@308 724 end
jcallahan@119 725 end
jcallahan@75 726 end
jcallahan@75 727 end
jcallahan@75 728 end
jcallahan@121 729
jcallahan@121 730 -- This is used for Gas Extractions.
jcallahan@131 731 if #current_loot.list <= 0 then
jcallahan@78 732 return
jcallahan@78 733 end
jcallahan@82 734 local entry
jcallahan@82 735
jcallahan@82 736 -- At this point we only have a name if it's an object.
MMOSimca@388 737 -- (As of 5.x, the above statement is almost never true, but there are a few cases, like gas extractions.)
jcallahan@131 738 if current_loot.target_type == AF.OBJECT then
jcallahan@131 739 entry = DBEntry(data_type, ("%s:%s"):format(current_loot.spell_label, current_loot.object_name))
jcallahan@82 740 else
jcallahan@131 741 entry = DBEntry(data_type, current_loot.identifier)
jcallahan@82 742 end
jcallahan@75 743
jcallahan@75 744 if not entry then
jcallahan@75 745 return
jcallahan@75 746 end
jcallahan@77 747 local loot_table = LootTable(entry, loot_type, top_field)
jcallahan@77 748
jcallahan@307 749 if current_loot.identifier then
jcallahan@307 750 if not source_list[current_loot.identifier] then
jcallahan@307 751 if top_field then
jcallahan@307 752 entry[top_field][loot_count] = (entry[top_field][loot_count] or 0) + 1
jcallahan@307 753 else
jcallahan@307 754 entry[loot_count] = (entry[loot_count] or 0) + 1
jcallahan@307 755 end
jcallahan@307 756 source_list[current_loot.identifier] = true
jcallahan@77 757 end
jcallahan@77 758 end
jcallahan@75 759
jcallahan@131 760 for index = 1, #current_loot.list do
jcallahan@131 761 table.insert(loot_table, current_loot.list[index])
jcallahan@75 762 end
jcallahan@48 763 end
jcallahan@75 764 end -- do-block
jcallahan@48 765
jcallahan@97 766
jcallahan@97 767 local ReplaceKeywords
jcallahan@97 768 do
jcallahan@97 769 local KEYWORD_SUBSTITUTIONS = {
jcallahan@97 770 class = PLAYER_CLASS,
jcallahan@97 771 name = PLAYER_NAME,
jcallahan@97 772 race = PLAYER_RACE,
jcallahan@97 773 }
jcallahan@97 774
jcallahan@97 775
jcallahan@97 776 function ReplaceKeywords(text)
jcallahan@97 777 if not text or text == "" then
jcallahan@97 778 return ""
jcallahan@97 779 end
jcallahan@97 780
jcallahan@97 781 for category, lookup in pairs(KEYWORD_SUBSTITUTIONS) do
jcallahan@97 782 local category_format = ("<%s>"):format(category)
jcallahan@97 783 text = text:gsub(lookup, category_format):gsub(lookup:lower(), category_format)
jcallahan@97 784 end
jcallahan@97 785 return text
jcallahan@97 786 end
jcallahan@97 787 end -- do-block
jcallahan@97 788
jcallahan@97 789
MMOSimca@347 790 -- TIMERS -------------------------------------------------------------
MMOSimca@347 791
MMOSimca@393 792 function ClearKilledNPC()
MMOSimca@347 793 killed_npc_id = nil
MMOSimca@347 794 end
MMOSimca@347 795
MMOSimca@347 796
MMOSimca@393 797 function ClearKilledBossID()
MMOSimca@347 798 if killed_boss_id_timer_handle then
MMOSimca@383 799 killed_boss_id_timer_handle:Cancel()
MMOSimca@347 800 killed_boss_id_timer_handle = nil
MMOSimca@347 801 end
MMOSimca@347 802
MMOSimca@347 803 table.wipe(boss_loot_toasting)
MMOSimca@387 804 raid_boss_id = nil
MMOSimca@347 805 end
MMOSimca@347 806
MMOSimca@347 807
MMOSimca@393 808 function ClearLootToastContainerID()
MMOSimca@347 809 if loot_toast_container_timer_handle then
MMOSimca@383 810 loot_toast_container_timer_handle:Cancel()
MMOSimca@347 811 loot_toast_container_timer_handle = nil
MMOSimca@347 812 end
MMOSimca@347 813
MMOSimca@387 814 container_loot_toasting = false
MMOSimca@387 815 loot_toast_container_id = nil
MMOSimca@347 816 end
MMOSimca@347 817
MMOSimca@347 818
MMOSimca@393 819 function ClearLootToastData()
MMOSimca@347 820 if loot_toast_data_timer_handle then
MMOSimca@383 821 loot_toast_data_timer_handle:Cancel()
MMOSimca@347 822 loot_toast_data_timer_handle = nil
MMOSimca@347 823 end
MMOSimca@347 824
MMOSimca@347 825 if loot_toast_data then
MMOSimca@347 826 table.wipe(loot_toast_data)
MMOSimca@347 827 end
MMOSimca@347 828 end
MMOSimca@347 829
MMOSimca@347 830
MMOSimca@393 831 function ClearChatLootData()
MMOSimca@398 832 if not chat_loot_timer_handle then
MMOSimca@435 833 table.wipe(chat_loot_data)
MMOSimca@398 834 return
MMOSimca@398 835 end
MMOSimca@398 836 Debug("ClearChatLootData: Ending chat-based loot timer.")
MMOSimca@398 837 chat_loot_timer_handle:Cancel()
MMOSimca@398 838 chat_loot_timer_handle = nil
atcaleb@573 839
atcaleb@573 840 -- Slimmed down (and more importantly, separate) versions of GenericLootUpdate, specifically for chat_loot_data
atcaleb@573 841 -- First version is for special item containers that 'push' loot without using a loot window
atcaleb@573 842 if chat_loot_data.identifier and chat_loot_data.loot and chat_loot_data.category == AF.ITEM and private.CONTAINER_ITEM_ID_LIST[chat_loot_data.identifier] ~= nil then
MMOSimca@414 843 local entry = DBEntry("items", chat_loot_data.identifier)
MMOSimca@414 844
MMOSimca@414 845 if entry then
MMOSimca@414 846 local loot_table = LootTable(entry, "contains")
MMOSimca@414 847 entry["contains_count"] = (entry["contains_count"] or 0) + 1
MMOSimca@414 848
MMOSimca@435 849 for loot_token, quantity in pairs(chat_loot_data.loot) do
mmosimca@496 850 local label, currency_id = (":"):split(loot_token)
mmosimca@496 851
mmosimca@496 852 if label == "currency" and currency_id then
mmosimca@496 853 -- Convert currency_id back into number from string
mmosimca@496 854 currency_id = tonumber(currency_id) or 0
mmosimca@496 855 if currency_id ~= 0 then
mmosimca@496 856 table.insert(loot_table, ("currency:%d:%d"):format(quantity, currency_id))
mmosimca@496 857 end
MMOSimca@414 858 elseif loot_token == "money" then
MMOSimca@414 859 table.insert(loot_table, ("money:%d"):format(quantity))
MMOSimca@414 860 else
MMOSimca@414 861 table.insert(loot_table, ("%d:%d"):format(loot_token, quantity))
MMOSimca@414 862 end
MMOSimca@414 863 end
MMOSimca@414 864 end
atcaleb@573 865 -- Second version is for Island Expeditions. This code is flawed (by design).
atcaleb@573 866 elseif chat_loot_data.loot and chat_loot_data.category == AF.NPC then
atcaleb@573 867 -- Iterate over all NPCs killed in an island expedition (at least by your team)
atcaleb@573 868 for island_npc_id, kill_count in pairs(killed_npcs_in_island) do
atcaleb@573 869 local npc = NPCEntry(island_npc_id)
atcaleb@573 870 if npc then
atcaleb@573 871 -- Create needed npc fields if required
atcaleb@573 872 local loot_label = "drops"
atcaleb@573 873 local encounter_data = npc:EncounterData(InstanceDifficultyToken())
atcaleb@573 874 encounter_data[loot_label] = encounter_data[loot_label] or {}
atcaleb@573 875 encounter_data.loot_counts = encounter_data.loot_counts or {}
atcaleb@573 876 encounter_data.loot_counts[loot_label] = (encounter_data.loot_counts[loot_label] or 0) + kill_count
atcaleb@573 877
atcaleb@573 878 for loot_token, quantity in pairs(chat_loot_data.loot) do
atcaleb@573 879 local label, currency_id = (":"):split(loot_token)
atcaleb@573 880
atcaleb@573 881 -- Only support items
atcaleb@573 882 if (label ~= "currency" or not currency_id) and (loot_token ~= "money") then
atcaleb@573 883 -- Ignore certain items (dubloon bags)
atcaleb@573 884 if not private.IGNORED_ISLAND_REWARDS[tonumber(loot_token)] then
atcaleb@573 885 table.insert(encounter_data[loot_label], ("%d:%d"):format(loot_token, quantity))
atcaleb@573 886 end
atcaleb@573 887 end
atcaleb@573 888 end
atcaleb@573 889 end
atcaleb@573 890 end
MMOSimca@347 891 end
atcaleb@573 892 table.wipe(killed_npcs_in_island)
MMOSimca@435 893 table.wipe(chat_loot_data)
MMOSimca@347 894 end
MMOSimca@347 895
MMOSimca@347 896
jcallahan@246 897 -- METHODS ------------------------------------------------------------
jcallahan@246 898
jcallahan@0 899 function WDP:OnInitialize()
jcallahan@128 900 local db = LibStub("AceDB-3.0"):New("WoWDBProfilerData", DATABASE_DEFAULTS, "Default")
jcallahan@270 901 private.db = db
jcallahan@128 902 global_db = db.global
jcallahan@128 903 char_db = db.char
jcallahan@14 904
jcallahan@270 905 local raw_db = _G.WoWDBProfilerData
jcallahan@18 906 local build_num = tonumber(private.build_num)
mmosimca@485 907
MMOSimca@533 908 -- Get current region from API (flawed)
MMOSimca@533 909 local current_region = _G.GetCurrentRegionName() or "XX"
mmosimca@484 910
mmosimca@484 911 -- Wipe all data if DB version or build number changed
jcallahan@136 912 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 913 for entry in pairs(DATABASE_DEFAULTS.global) do
jcallahan@128 914 global_db[entry] = {}
jcallahan@74 915 end
jcallahan@74 916 end
mmosimca@485 917 -- Wipe World Quest data if region changed
mmosimca@485 918 if raw_db.region and raw_db.region ~= current_region and global_db["world_quests"] then
mmosimca@485 919 global_db["world_quests"] = {}
mmosimca@485 920 end
mmosimca@485 921
jcallahan@35 922 raw_db.build_num = build_num
mmosimca@484 923 raw_db.region = current_region
jcallahan@63 924 raw_db.version = DB_VERSION
jcallahan@249 925
jcallahan@312 926 private.InitializeCommentSystem()
jcallahan@312 927 self:RegisterChatCommand("comment", private.ProcessCommentCommand)
jcallahan@0 928 end
jcallahan@0 929
jcallahan@0 930
jcallahan@153 931 function WDP:EventDispatcher(...)
jcallahan@153 932 local event_name = ...
jcallahan@153 933
MMOSimca@346 934 if DEBUGGING then
jcallahan@154 935 if event_name == "COMBAT_LOG_EVENT_UNFILTERED" then
jcallahan@154 936 Debug(event_name)
jcallahan@154 937 else
jcallahan@154 938 Debug(...)
jcallahan@153 939 end
jcallahan@153 940 end
jcallahan@153 941 local func = EVENT_MAPPING[event_name]
jcallahan@153 942
mmosimca@522 943 if type(func) == "boolean" then
jcallahan@153 944 self[event_name](self, ...)
mmosimca@522 945 elseif type(func) == "function" then
jcallahan@159 946 self[func](self, ...)
jcallahan@153 947 end
jcallahan@153 948 end
jcallahan@153 949
jcallahan@153 950
jcallahan@0 951 function WDP:OnEnable()
jcallahan@300 952 PLAYER_GUID = _G.UnitGUID("player")
jcallahan@300 953
jcallahan@0 954 for event_name, mapping in pairs(EVENT_MAPPING) do
jcallahan@156 955 if EVENT_DEBUG then
jcallahan@153 956 self:RegisterEvent(event_name, "EventDispatcher")
jcallahan@153 957 else
mmosimca@522 958 self:RegisterEvent(event_name, (type(mapping) ~= "boolean") and mapping or nil)
jcallahan@153 959 end
jcallahan@0 960 end
jcallahan@95 961
mmosimca@496 962 -- Gather known languages
jcallahan@95 963 for index = 1, _G.GetNumLanguages() do
jcallahan@95 964 languages_known[_G.GetLanguageByIndex(index)] = true
jcallahan@95 965 end
mmosimca@518 966
mmosimca@485 967 -- These timers loop indefinitely using Lua's infinity constant
atcaleb@562 968 item_process_timer_handle = C_Timer.NewTicker(DELAY_PROCESS_ITEMS, WDP.ProcessItems, nil)
atcaleb@562 969 target_location_timer_handle = C_Timer.NewTicker(DELAY_UPDATE_TARGET_LOCATION, WDP.UpdateTargetLocation, nil)
atcaleb@562 970 world_quest_timer_handle = C_Timer.NewTicker(DELAY_PROCESS_WORLD_QUESTS, WDP.ProcessWorldQuests, nil)
jcallahan@19 971
MMOSimca@581 972 _G.hooksecurefunc(C_Container, "UseContainerItem", function(bag_index, slot_index, target_unit)
jcallahan@19 973 if target_unit then
jcallahan@19 974 return
jcallahan@19 975 end
MMOSimca@581 976 HandleItemUse(_G.C_Container.GetContainerItemLink(bag_index, slot_index), bag_index, slot_index)
jcallahan@19 977 end)
jcallahan@19 978
jcallahan@19 979 _G.hooksecurefunc("UseItemByName", function(identifier, target_unit)
jcallahan@19 980 if target_unit then
jcallahan@19 981 return
jcallahan@19 982 end
jcallahan@19 983 local _, item_link = _G.GetItemInfo(identifier)
jcallahan@19 984 HandleItemUse(item_link)
jcallahan@19 985 end)
jcallahan@263 986
jcallahan@290 987 self:GROUP_ROSTER_UPDATE()
jcallahan@0 988 end
jcallahan@0 989
jcallahan@0 990
mmosimca@485 991 -- Record data for a specific quest ID; reward data must be available or nothing will be recorded
mmosimca@485 992 -- When we reach this point, we've already checked for a valid mapID, questID, quest data, and worldQuestType
atcaleb@562 993 local function RecordWorldQuestData(quest_id, api_data_table)
atcaleb@575 994 local xp, _ = _G.GetQuestLogRewardXP(quest_id)
atcaleb@575 995
mmosimca@485 996 -- Ensure we have location data and rewards (barely readable so putting it on multiple lines)
mmosimca@487 997 -- (Honor is built in to the quest; it is not a sign rewards have been loaded)
atcaleb@562 998 if not api_data_table or not api_data_table.x or not api_data_table.y or not api_data_table.mapID or not
atcaleb@575 999 (xp > 0 or _G.GetNumQuestLogRewardCurrencies(quest_id) > 0
atcaleb@562 1000 or _G.GetNumQuestLogRewards(quest_id) > 0 or _G.GetQuestLogRewardMoney(quest_id) > 0) then
atcaleb@562 1001 --or _G.GetQuestLogRewardArtifactXP(quest_id) > 0)
mmosimca@485 1002 return
mmosimca@485 1003 end
mmosimca@485 1004
mmosimca@485 1005 local entry = DBEntry("world_quests", quest_id)
mmosimca@485 1006 if entry then
mmosimca@485 1007
mmosimca@485 1008 -- Record location
mmosimca@485 1009 entry["location"] = {}
atcaleb@562 1010 entry["location"]["world_map_id"] = api_data_table.mapID
atcaleb@562 1011 entry["location"]["x"] = api_data_table.x * 100
atcaleb@562 1012 entry["location"]["y"] = api_data_table.y * 100
mmosimca@485 1013
mmosimca@485 1014 -- Record simple rewards (XP, money, artifact XP, honor)
mmosimca@485 1015 entry["rewards"] = {}
atcaleb@575 1016 entry["rewards"]["xp"] = tonumber(xp) or 0
mmosimca@485 1017 entry["rewards"]["money"] = tonumber(_G.GetQuestLogRewardMoney(quest_id)) or 0
atcaleb@562 1018 --local actualXP, scaling = _G.GetQuestLogRewardArtifactXP(quest_id)
atcaleb@562 1019 --entry["rewards"]["artifact_xp"] = ("%d:%d"):format(tonumber(actualXP) or 0, tonumber(scaling) or 0)
mmosimca@485 1020 entry["rewards"]["honor"] = tonumber(_G.GetQuestLogRewardHonor(quest_id)) or 0
mmosimca@485 1021
mmosimca@485 1022 -- Record currencies
mmosimca@485 1023 entry["rewards"]["currency_count"] = tonumber(_G.GetNumQuestLogRewardCurrencies(quest_id)) or 0
mmosimca@485 1024
mmosimca@496 1025 -- Create currency rewards sub-table and fill
mmosimca@485 1026 if entry["rewards"]["currency_count"] > 0 then
mmosimca@485 1027 entry["rewards"]["currencies"] = {}
mmosimca@485 1028 for i = 1, entry["rewards"]["currency_count"] do
mmosimca@503 1029 local name, texture_path, quantity, currency_id = _G.GetQuestLogRewardCurrencyInfo(i, quest_id)
mmosimca@503 1030 table.insert(entry["rewards"]["currencies"], ("%d:%d"):format(quantity, currency_id))
mmosimca@485 1031 end
mmosimca@485 1032 end
mmosimca@485 1033
mmosimca@485 1034 -- Record items
mmosimca@485 1035 entry["rewards"]["item_count"] = tonumber(_G.GetNumQuestLogRewards(quest_id)) or 0
mmosimca@485 1036
mmosimca@496 1037 -- Create item rewards sub-table and fill
mmosimca@485 1038 if entry["rewards"]["item_count"] > 0 then
mmosimca@485 1039 entry["rewards"]["items"] = {}
mmosimca@485 1040 for i = 1, entry["rewards"]["item_count"] do
mmosimca@485 1041 local item_name, item_texture, quantity, quality, is_usable, item_id = _G.GetQuestLogRewardInfo(i, quest_id)
mmosimca@485 1042 table.insert(entry["rewards"]["items"], ("%d:%d"):format(item_id, quantity))
mmosimca@485 1043 end
mmosimca@485 1044 end
mmosimca@485 1045
mmosimca@485 1046 -- Record time remaining
mmosimca@485 1047 entry["estimated_end_time"] = _G.GetServerTime() + ((_G.C_TaskQuest.GetQuestTimeLeftMinutes(quest_id) or 0) * 60)
mmosimca@485 1048 end
mmosimca@485 1049 end
mmosimca@485 1050
mmosimca@485 1051
mmosimca@485 1052 function WDP:ProcessWorldQuests()
MMOSimca@532 1053 -- Ignore if player is low level (there are some world quests before max level now, but we can collect enough data from 110s alone still)
atcaleb@562 1054 if _G.UnitLevel("player") < 110 then return end
atcaleb@559 1055
atcaleb@566 1056 -- Check all first-order continents
atcaleb@566 1057 local continents = C_Map.GetMapChildrenInfo(UI_MAP_COSMIC, Enum.UIMapType.Continent, true);
atcaleb@566 1058 for i, continentInfo in ipairs(continents) do
atcaleb@566 1059
atcaleb@566 1060 -- Get continent data for World Quests
atcaleb@566 1061 local continent_api_data = C_TaskQuest.GetQuestsForPlayerByMapID(continentInfo.mapID)
atcaleb@566 1062
atcaleb@566 1063 -- If the continent has WQ data, continue
atcaleb@566 1064 if continent_api_data and type(continent_api_data) == "table" and #continent_api_data > 0 then
atcaleb@566 1065 -- Iterate over zones on continents
atcaleb@566 1066 local zones = C_Map.GetMapChildrenInfo(continentInfo.mapID, Enum.UIMapType.Zone)
atcaleb@566 1067 for j, zoneInfo in ipairs(zones) do
atcaleb@566 1068
atcaleb@566 1069 -- Get zone data for World Quests
atcaleb@566 1070 local zone_api_data = C_TaskQuest.GetQuestsForPlayerByMapID(zoneInfo.mapID);
atcaleb@566 1071
atcaleb@566 1072 -- Iterate over the questIDs for each zone, doing preload reward requests and creating SavedVariables entries
atcaleb@566 1073 if zone_api_data and type(zone_api_data) == "table" and #zone_api_data > 0 then
atcaleb@566 1074 for k = 1, #zone_api_data do
atcaleb@566 1075
atcaleb@566 1076 -- Check if we had a valid API table returned to us
atcaleb@566 1077 if zone_api_data[k] and type(zone_api_data[k]) == "table" and zone_api_data[k].questId then
atcaleb@566 1078 local quest_id = tonumber(zone_api_data[k].questId) or 0
atcaleb@566 1079
atcaleb@566 1080 -- Check if we have quest data and the quest is within this zone directly
atcaleb@566 1081 if quest_id > 0 and _G.HaveQuestData(quest_id) and QuestUtils_IsQuestWorldQuest(quest_id) and zone_api_data[k].mapID == zoneInfo.mapID then
atcaleb@566 1082 _G.C_TaskQuest.RequestPreloadRewardData(quest_id)
atcaleb@566 1083 RecordWorldQuestData(quest_id, zone_api_data[k])
atcaleb@566 1084 end
atcaleb@566 1085 end
mmosimca@485 1086 end
mmosimca@485 1087 end
mmosimca@485 1088 end
mmosimca@485 1089 end
mmosimca@485 1090 end
mmosimca@485 1091 end
mmosimca@485 1092
mmosimca@485 1093
MMOSimca@340 1094 local function RecordItemData(item_id, item_link, process_bonus_ids, durability)
jcallahan@331 1095 local _, _, item_string = item_link:find("^|%x+|H(.+)|h%[.+%]")
jcallahan@219 1096 local item
jcallahan@0 1097
jcallahan@191 1098 if item_string then
MMOSimca@338 1099 local item_results = { (":"):split(item_string) }
MMOSimca@338 1100
MMOSimca@460 1101 local suffix_id = tonumber(item_results[8]) or 0
MMOSimca@462 1102 local unique_id = tonumber(item_results[9]) or 0
MMOSimca@447 1103 --local level = tonumber(item_results[10])
MMOSimca@460 1104 --local specialization_id = tonumber(item_results[11])
catherton@469 1105 --local upgrade_type_id = tonumber(item_results[12])
MMOSimca@460 1106 local instance_difficulty_id = tonumber(item_results[13]) or 0
MMOSimca@460 1107 local num_bonus_ids = tonumber(item_results[14]) or 0
catherton@471 1108 -- upgrade_value is optional in 6.2! can be detected using upgrade_type_id, but it's just as easy to check like this
catherton@469 1109 local upgrade_value = tonumber(item_results[15 + num_bonus_ids]) or 0
catherton@465 1110
mmosimca@484 1111 local unk_item_field_1 = tonumber(item_results[16 + num_bonus_ids]) or 0
mmosimca@484 1112 local unk_item_field_2 = tonumber(item_results[17 + num_bonus_ids]) or 0
mmosimca@484 1113 --if unk_item_field_1 > 0 then Debug("unk_item_field_1 for %s is non-zero, specifically %d.", item_link, unk_item_field_1) end
mmosimca@484 1114 --if unk_item_field_2 > 0 then Debug("unk_item_field_2 for %s is non-zero, specifically %d.", item_link, unk_item_field_2) end
MMOSimca@460 1115
MMOSimca@460 1116 -- If there is anything special (non-zero) for this item then we need to make note of everything
catherton@471 1117 if math.max(suffix_id, instance_difficulty_id, num_bonus_ids, upgrade_value) ~= 0 then
MMOSimca@460 1118 item = DBEntry("items", item_id)
MMOSimca@460 1119 item.suffix_id = suffix_id
MMOSimca@460 1120 item.unique_id = bit.band(unique_id, 0xFFFF)
MMOSimca@460 1121 item.instance_difficulty_id = instance_difficulty_id
catherton@471 1122 item.upgrade_value = upgrade_value
MMOSimca@460 1123
MMOSimca@460 1124 if process_bonus_ids then
MMOSimca@460 1125
MMOSimca@460 1126 -- Get ready for bonus IDs
MMOSimca@384 1127 if not item.seen_bonuses then
MMOSimca@384 1128 item.seen_bonuses = {}
MMOSimca@372 1129 end
catherton@465 1130
MMOSimca@460 1131 if num_bonus_ids > 0 then
MMOSimca@460 1132 -- We want the bonus ID combo output to be in the form ["bonusID1:bonusID2:bonusID3"] = true
MMOSimca@460 1133 -- And sorted numerically with the smallest bonusID first
MMOSimca@460 1134 local sorted_bonus_string = ""
MMOSimca@460 1135 local min_bonus_id_array = {}
MMOSimca@460 1136 for iterations = 1, num_bonus_ids do
MMOSimca@460 1137 -- Find minimum of this iteration
MMOSimca@460 1138 local min_bonus_id = 100000
MMOSimca@460 1139 for bonus_index = 1, num_bonus_ids do
MMOSimca@460 1140 local temp_bonus_id = tonumber(item_results[14 + bonus_index])
MMOSimca@460 1141 if temp_bonus_id and (not min_bonus_id_array[temp_bonus_id]) and (temp_bonus_id < min_bonus_id) then
MMOSimca@460 1142 min_bonus_id = temp_bonus_id
MMOSimca@460 1143 end
MMOSimca@460 1144 end
MMOSimca@460 1145
MMOSimca@460 1146 -- Keep track of already processed IDs
MMOSimca@460 1147 min_bonus_id_array[min_bonus_id] = true
MMOSimca@460 1148
MMOSimca@460 1149 -- Build string
MMOSimca@460 1150 if iterations == 1 then
MMOSimca@460 1151 sorted_bonus_string = sorted_bonus_string .. tostring(min_bonus_id)
MMOSimca@460 1152 else
MMOSimca@460 1153 sorted_bonus_string = sorted_bonus_string .. ":" .. tostring(min_bonus_id)
MMOSimca@460 1154 end
MMOSimca@384 1155 end
MMOSimca@460 1156
MMOSimca@460 1157 item.seen_bonuses[sorted_bonus_string] = true
MMOSimca@460 1158 Debug("RecordItemData: Recorded bonus IDs %s for item %d.", sorted_bonus_string, item_id)
MMOSimca@384 1159 else
MMOSimca@460 1160 item.seen_bonuses["0"] = true
MMOSimca@384 1161 end
MMOSimca@329 1162 end
jcallahan@191 1163 end
jcallahan@0 1164 end
jcallahan@212 1165
jcallahan@212 1166 if durability and durability > 0 then
jcallahan@219 1167 item = item or DBEntry("items", item_id)
jcallahan@212 1168 item.durability = durability
jcallahan@212 1169 end
jcallahan@0 1170 end
jcallahan@0 1171
jcallahan@0 1172
jcallahan@187 1173 function WDP:ProcessItems()
jcallahan@187 1174 for slot_index = _G.INVSLOT_FIRST_EQUIPPED, _G.INVSLOT_LAST_EQUIPPED do
jcallahan@1 1175 local item_id = _G.GetInventoryItemID("player", slot_index)
jcallahan@0 1176
jcallahan@0 1177 if item_id and item_id > 0 then
jcallahan@1 1178 local _, max_durability = _G.GetInventoryItemDurability(slot_index)
MMOSimca@340 1179 RecordItemData(item_id, _G.GetInventoryItemLink("player", slot_index), false, max_durability)
jcallahan@0 1180 end
jcallahan@0 1181 end
jcallahan@0 1182
jcallahan@0 1183 for bag_index = 0, _G.NUM_BAG_SLOTS do
MMOSimca@581 1184 for slot_index = 1, _G.C_Container.GetContainerNumSlots(bag_index) do
MMOSimca@581 1185 local item_id = _G.C_Container.GetContainerItemID(bag_index, slot_index)
jcallahan@0 1186
jcallahan@0 1187 if item_id and item_id > 0 then
MMOSimca@581 1188 local _, max_durability = _G.C_Container.GetContainerItemDurability(bag_index, slot_index)
MMOSimca@581 1189 RecordItemData(item_id, _G.C_Container.GetContainerItemLink(bag_index, slot_index), false, max_durability)
jcallahan@0 1190 end
jcallahan@0 1191 end
jcallahan@0 1192 end
jcallahan@0 1193 end
jcallahan@0 1194
jcallahan@118 1195
atcaleb@558 1196 local function TargetedNPC()
atcaleb@558 1197 if not _G.UnitExists("target") or _G.UnitPlayerControlled("target") or currently_drunk then
atcaleb@558 1198 current_target_id = nil
atcaleb@558 1199 return
atcaleb@558 1200 end
atcaleb@558 1201 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("target"))
atcaleb@558 1202
atcaleb@558 1203 if not unit_idnum or not UnitTypeIsNPC(unit_type) then
atcaleb@558 1204 return
atcaleb@558 1205 end
atcaleb@558 1206 current_target_id = unit_idnum
atcaleb@558 1207
atcaleb@558 1208 local npc = NPCEntry(unit_idnum)
atcaleb@558 1209 local _, class_token = _G.UnitClass("target")
atcaleb@558 1210 npc.class = class_token
atcaleb@558 1211 npc.faction = UnitFactionStanding("target")
atcaleb@558 1212 npc.genders = npc.genders or {}
atcaleb@558 1213 npc.genders[private.GENDER_NAMES[_G.UnitSex("target")] or "UNDEFINED"] = true
atcaleb@558 1214 npc.is_pvp = _G.UnitIsPVP("target") and true or nil
atcaleb@558 1215 npc.reaction = ("%s:%s:%s"):format(_G.UnitLevel("player"), _G.UnitFactionGroup("player"), private.REACTION_NAMES[_G.UnitReaction("player", "target")])
atcaleb@558 1216
atcaleb@558 1217 local encounter_data = npc:EncounterData(InstanceDifficultyToken()).stats
atcaleb@558 1218 local npc_level = ("level_%d"):format(_G.UnitLevel("target"))
atcaleb@558 1219 local level_data = encounter_data[npc_level]
atcaleb@558 1220
atcaleb@558 1221 if not level_data then
atcaleb@558 1222 level_data = {}
atcaleb@558 1223 encounter_data[npc_level] = level_data
atcaleb@558 1224 end
MMOSimca@583 1225
MMOSimca@583 1226 -- Can't do this in instances now
MMOSimca@583 1227 if not III() then
MMOSimca@583 1228 level_data.max_health = level_data.max_health or _G.UnitHealthMax("target")
MMOSimca@583 1229
MMOSimca@583 1230 -- May not capture as much data as it could, since the API changed in Legion to report multiple types of power
MMOSimca@583 1231 if not level_data.power then
MMOSimca@583 1232 local max_power = _G.UnitPowerMax("target")
MMOSimca@583 1233
MMOSimca@583 1234 if max_power > 0 then
MMOSimca@583 1235 local power_type = _G.UnitPowerType("target")
MMOSimca@583 1236 level_data.power = ("%s:%d"):format(private.POWER_TYPE_NAMES[tostring(power_type)] or power_type, max_power)
MMOSimca@583 1237 end
jcallahan@118 1238 end
jcallahan@118 1239 end
atcaleb@558 1240 name_to_id_map[_G.UnitName("target")] = unit_idnum
atcaleb@558 1241 return npc, unit_idnum
atcaleb@558 1242 end
jcallahan@118 1243
jcallahan@118 1244
jcallahan@113 1245 do
jcallahan@113 1246 local COORD_MAX = 5
jcallahan@0 1247
jcallahan@113 1248 function WDP:UpdateTargetLocation()
catherton@480 1249 if currently_drunk or not _G.UnitExists("target") or _G.UnitPlayerControlled("target") or (_G.UnitIsTapDenied("target") and not _G.UnitIsDead("target")) then
jcallahan@2 1250 return
jcallahan@2 1251 end
jcallahan@113 1252
jcallahan@113 1253 for index = 1, 4 do
jcallahan@113 1254 if not _G.CheckInteractDistance("target", index) then
jcallahan@113 1255 return
jcallahan@113 1256 end
jcallahan@113 1257 end
jcallahan@215 1258 local npc = TargetedNPC()
jcallahan@113 1259
jcallahan@113 1260 if not npc then
jcallahan@113 1261 return
jcallahan@113 1262 end
jcallahan@113 1263 local zone_name, area_id, x, y, map_level, difficulty_token = CurrentLocationData()
MMOSimca@328 1264 if not (zone_name and area_id and x and y and map_level) then
mmosimca@508 1265 if not (_G.IsInInstance()) then
mmosimca@508 1266 Debug("UpdateTargetLocation: Missing current location data - %s, %s, %s, %s, %s.", tostring(zone_name), tostring(area_id), tostring(x), tostring(y), tostring(map_level))
mmosimca@508 1267 end
MMOSimca@328 1268 return
MMOSimca@328 1269 end
jcallahan@248 1270 local npc_data = npc:EncounterData(difficulty_token).stats[("level_%d"):format(_G.UnitLevel("target"))]
jcallahan@113 1271 local zone_token = ("%s:%d"):format(zone_name, area_id)
jcallahan@118 1272 npc_data.locations = npc_data.locations or {} -- TODO: Fix this. It is broken. Possibly something to do with the timed updates.
jcallahan@113 1273
jcallahan@113 1274 local zone_data = npc_data.locations[zone_token]
jcallahan@113 1275
jcallahan@113 1276 if not zone_data then
jcallahan@113 1277 zone_data = {}
jcallahan@113 1278 npc_data.locations[zone_token] = zone_data
jcallahan@113 1279 end
jcallahan@113 1280
jcallahan@113 1281 for location_token in pairs(zone_data) do
jcallahan@113 1282 local loc_level, loc_x, loc_y = (":"):split(location_token)
jcallahan@113 1283 loc_level = tonumber(loc_level)
jcallahan@113 1284
jcallahan@113 1285 if map_level == loc_level and math.abs(x - loc_x) <= COORD_MAX and math.abs(y - loc_y) <= COORD_MAX then
jcallahan@113 1286 return
jcallahan@113 1287 end
jcallahan@113 1288 end
jcallahan@141 1289 zone_data[("%d:%d:%d"):format(map_level, x, y)] = true
jcallahan@2 1290 end
jcallahan@113 1291 end -- do-block
jcallahan@2 1292
jcallahan@118 1293
MMOSimca@412 1294 function WDP:HandleBadChatLootData(...)
MMOSimca@398 1295 ClearChatLootData()
MMOSimca@398 1296 end
MMOSimca@398 1297
MMOSimca@398 1298
MMOSimca@420 1299 -- EVENT HANDLERS -----------------------------------------------------
MMOSimca@420 1300
MMOSimca@436 1301 -- This function (and the following function) are to stop 'HandleItemUse' from triggering when you put an item that would normally be opened into the bank, guild bank, etc.
MMOSimca@436 1302 function WDP:StopChatLootRecording(event_name)
MMOSimca@436 1303 if not block_chat_loot_data then
MMOSimca@439 1304 Debug("%s: Pausing chat-based loot recording.", event_name)
MMOSimca@436 1305 ClearChatLootData()
MMOSimca@436 1306 block_chat_loot_data = true
MMOSimca@436 1307 end
MMOSimca@436 1308 end
MMOSimca@436 1309
MMOSimca@436 1310
MMOSimca@436 1311 function WDP:ResumeChatLootRecording(event_name)
MMOSimca@436 1312 if block_chat_loot_data then
MMOSimca@439 1313 Debug("%s: Resuming chat-based loot recording.", event_name)
MMOSimca@436 1314 block_chat_loot_data = false
MMOSimca@436 1315 end
MMOSimca@436 1316 end
MMOSimca@436 1317
MMOSimca@436 1318
MMOSimca@408 1319 -- For now, bonus roll data only pollutes the true drop percentages. We still want to capture the data from SPELL_CONFIRMATION_PROMPT because of legendary quest items though.
MMOSimca@408 1320 function WDP:BONUS_ROLL_RESULT(event_name)
MMOSimca@408 1321 Debug("%s: Bonus roll detected; stopping loot recording for this boss to avoid recording bonus loot.", event_name)
MMOSimca@408 1322 ClearKilledBossID()
MMOSimca@408 1323 ClearLootToastContainerID()
MMOSimca@408 1324 end
MMOSimca@408 1325
MMOSimca@408 1326
atcaleb@573 1327 -- Store all known killed NPCs during an island in a table
atcaleb@573 1328 function WDP:ISLAND_AZERITE_GAIN(event_name, amount, gained_by_player, faction_index, gained_by, gained_from)
atcaleb@573 1329 -- Exit now if GUID is not for an NPC
atcaleb@573 1330 local unit_type, unit_id = ParseGUID(gained_from)
atcaleb@573 1331 if not UnitTypeIsNPC(unit_type) then
atcaleb@573 1332 return
atcaleb@573 1333 end
atcaleb@573 1334
atcaleb@573 1335 -- Otherwise, store the NPC ID in the island kill table (if it already exists there, increment its count by 1)
atcaleb@573 1336 Debug("%s: Recording killed island NPC with ID #%d.", event_name, unit_id)
atcaleb@573 1337 if killed_npcs_in_island[unit_id] then
atcaleb@573 1338 killed_npcs_in_island[unit_id] = killed_npcs_in_island[unit_id] + 1
atcaleb@573 1339 else
atcaleb@573 1340 killed_npcs_in_island[unit_id] = 1
atcaleb@573 1341 end
atcaleb@573 1342 end
atcaleb@573 1343
atcaleb@573 1344
atcaleb@573 1345 -- Start chat loot timer for NPCs (and store general island information)
atcaleb@573 1346 function WDP:ISLAND_COMPLETED(event_name)
atcaleb@573 1347 island_difficulty_token = InstanceDifficultyToken()
atcaleb@573 1348
atcaleb@573 1349 Debug("%s: Beginning chat-based loot timer for Island Expedition rewards.", event_name, item_id)
atcaleb@573 1350 chat_loot_timer_handle = C_Timer.NewTimer(1.5, ClearChatLootData)
atcaleb@573 1351 chat_loot_data.category = AF.NPC
atcaleb@573 1352 chat_loot_data.identifier = 0
atcaleb@573 1353 end
atcaleb@573 1354
atcaleb@573 1355
jcallahan@90 1356 function WDP:BLACK_MARKET_ITEM_UPDATE(event_name)
jcallahan@243 1357 if not ALLOWED_LOCALES[CLIENT_LOCALE] then
jcallahan@243 1358 return
jcallahan@243 1359 end
jcallahan@282 1360 local num_items = _G.C_BlackMarket.GetNumItems() or 0
jcallahan@56 1361
jcallahan@56 1362 for index = 1, num_items do
jcallahan@56 1363 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 1364
jcallahan@56 1365 if item_link then
jcallahan@56 1366 DBEntry("items", ItemLinkToID(item_link)).black_market = seller_name or "UNKNOWN"
jcallahan@56 1367 end
jcallahan@56 1368 end
jcallahan@56 1369 end
jcallahan@56 1370
jcallahan@56 1371
jcallahan@298 1372 local function UpdateUnitPet(unit_guid, unit_id)
jcallahan@246 1373 local current_pet_guid = group_owner_guids_to_pet_guids[unit_guid]
jcallahan@246 1374
jcallahan@246 1375 if current_pet_guid then
jcallahan@246 1376 group_owner_guids_to_pet_guids[unit_guid] = nil
jcallahan@246 1377 group_pet_guids[current_pet_guid] = nil
jcallahan@246 1378 end
jcallahan@246 1379 local pet_guid = _G.UnitGUID(unit_id .. "pet")
jcallahan@246 1380
jcallahan@246 1381 if pet_guid then
jcallahan@296 1382 group_owner_guids_to_pet_guids[unit_guid] = pet_guid
jcallahan@246 1383 group_pet_guids[pet_guid] = true
jcallahan@246 1384 end
jcallahan@246 1385 end
jcallahan@246 1386
jcallahan@246 1387
jcallahan@298 1388 function WDP:GROUP_ROSTER_UPDATE(event_name)
jcallahan@298 1389 local is_raid = _G.IsInRaid()
jcallahan@298 1390 local unit_type = is_raid and "raid" or "party"
jcallahan@298 1391 local group_size = is_raid and _G.GetNumGroupMembers() or _G.GetNumSubgroupMembers()
jcallahan@298 1392
jcallahan@299 1393 table.wipe(group_member_guids)
jcallahan@298 1394
jcallahan@298 1395 for index = 1, group_size do
jcallahan@298 1396 local unit_id = unit_type .. index
jcallahan@298 1397 local unit_guid = _G.UnitGUID(unit_id)
jcallahan@298 1398
jcallahan@299 1399 group_member_guids[unit_guid] = true
jcallahan@298 1400 UpdateUnitPet(unit_guid, unit_id)
jcallahan@298 1401 end
jcallahan@299 1402 group_member_guids[PLAYER_GUID] = true
jcallahan@298 1403 end
jcallahan@298 1404
jcallahan@298 1405
jcallahan@298 1406 function WDP:UNIT_PET(event_name, unit_id)
jcallahan@298 1407 UpdateUnitPet(_G.UnitGUID(unit_id), unit_id)
jcallahan@298 1408 end
jcallahan@298 1409
jcallahan@298 1410
MMOSimca@375 1411 function WDP:SHOW_LOOT_TOAST(event_name, loot_type, item_link, quantity, spec_ID, sex_ID, is_personal, loot_source)
jcallahan@312 1412 if not loot_type or (loot_type ~= "item" and loot_type ~= "money" and loot_type ~= "currency") then
jcallahan@306 1413 Debug("%s: loot_type is %s. Item link is %s, and quantity is %d.", event_name, loot_type, item_link, quantity)
jcallahan@306 1414 return
jcallahan@306 1415 end
MMOSimca@372 1416
MMOSimca@372 1417 -- Need information on the most recent args, so using this complete debug statement for now
MMOSimca@375 1418 Debug("%s: loot_type: %s, item_link: %s, quantity: %s, spec_ID: %s, sex_ID: %s, is_personal: %s, loot_source: %s", event_name, loot_type, item_link, quantity, spec_ID, sex_ID, is_personal, loot_source)
MMOSimca@372 1419
MMOSimca@355 1420 -- Handle Garrison cache specially
MMOSimca@422 1421 if loot_source and (loot_source == LOOT_SOURCE_ID_GARRISON_CACHE) and last_garrison_cache_object_id then
MMOSimca@355 1422 -- Record location data for cache
MMOSimca@355 1423 UpdateDBEntryLocation("objects", ("OPENING:%d"):format(last_garrison_cache_object_id))
MMOSimca@355 1424
MMOSimca@355 1425 -- Add drop data
mmosimca@496 1426 local currency_id = CurrencyLinkToID(item_link)
mmosimca@496 1427 if currency_id and currency_id ~= 0 then
MMOSimca@355 1428 -- Check for top level object data
MMOSimca@355 1429 local object_entry = DBEntry("objects", ("OPENING:%d"):format(last_garrison_cache_object_id))
MMOSimca@355 1430 local difficulty_token = InstanceDifficultyToken()
MMOSimca@355 1431 if object_entry[difficulty_token] then
MMOSimca@355 1432 -- Increment loot count
MMOSimca@355 1433 object_entry[difficulty_token]["opening_count"] = (object_entry[difficulty_token]["opening_count"] or 0) + 1
MMOSimca@355 1434
mmosimca@496 1435 Debug("%s: %d X %d", event_name, currency_id, quantity)
MMOSimca@355 1436 object_entry[difficulty_token]["opening"] = object_entry[difficulty_token]["opening"] or {}
mmosimca@496 1437 table.insert(object_entry[difficulty_token]["opening"], ("currency:%d:%d"):format(quantity, currency_id))
MMOSimca@355 1438 else
MMOSimca@355 1439 Debug("%s: When handling the Garrison cache, the top level loot data was missing for objectID %d.", event_name, last_garrison_cache_object_id)
MMOSimca@355 1440 end
MMOSimca@355 1441 else
mmosimca@496 1442 Debug("%s: Currency ID is nil or 0, from currency link %s", event_name, item_link)
MMOSimca@355 1443 end
catherton@465 1444
MMOSimca@431 1445 -- Wipe object ID until future mouseover
MMOSimca@431 1446 last_garrison_cache_object_id = nil
MMOSimca@387 1447 elseif raid_boss_id then
MMOSimca@427 1448 local npc = NPCEntry(raid_boss_id)
MMOSimca@427 1449 if npc then
MMOSimca@427 1450 local loot_label = "drops"
MMOSimca@427 1451 local encounter_data = npc:EncounterData(InstanceDifficultyToken())
MMOSimca@427 1452 encounter_data[loot_label] = encounter_data[loot_label] or {}
MMOSimca@427 1453 encounter_data.loot_counts = encounter_data.loot_counts or {}
MMOSimca@427 1454
MMOSimca@427 1455 if loot_type == "item" then
MMOSimca@427 1456 local item_id = ItemLinkToID(item_link)
MMOSimca@427 1457 if item_id then
MMOSimca@427 1458 Debug("%s: %s X %d (%d)", event_name, item_link, quantity, item_id)
MMOSimca@427 1459 RecordItemData(item_id, item_link, true)
MMOSimca@427 1460 table.insert(encounter_data[loot_label], ("%d:%d"):format(item_id, quantity))
MMOSimca@427 1461 else
MMOSimca@427 1462 Debug("%s: ItemID is nil, from item link %s", event_name, item_link)
MMOSimca@427 1463 return
MMOSimca@427 1464 end
MMOSimca@427 1465 elseif loot_type == "money" then
MMOSimca@427 1466 Debug("%s: money X %d", event_name, quantity)
MMOSimca@427 1467 table.insert(encounter_data[loot_label], ("money:%d"):format(quantity))
MMOSimca@427 1468 elseif loot_type == "currency" then
mmosimca@496 1469 local currency_id = CurrencyLinkToID(item_link)
mmosimca@496 1470 if currency_id and currency_id ~= 0 then
mmosimca@496 1471 Debug("%s: %d X %d", event_name, currency_id, quantity)
mmosimca@496 1472 table.insert(encounter_data[loot_label], ("currency:%d:%d"):format(quantity, currency_id))
MMOSimca@427 1473 else
mmosimca@496 1474 Debug("%s: Currency ID is nil or 0, from currency link %s", event_name, item_link)
MMOSimca@427 1475 return
MMOSimca@427 1476 end
jcallahan@312 1477 end
jcallahan@317 1478
MMOSimca@427 1479 if not boss_loot_toasting[raid_boss_id] then
MMOSimca@427 1480 encounter_data.loot_counts[loot_label] = (encounter_data.loot_counts[loot_label] or 0) + 1
MMOSimca@427 1481 boss_loot_toasting[raid_boss_id] = true -- Do not count further loots until timer expires or another boss is killed
jcallahan@312 1482 end
jcallahan@312 1483 end
MMOSimca@387 1484 elseif loot_toast_container_id then
jcallahan@305 1485 InitializeCurrentLoot()
jcallahan@305 1486
jcallahan@306 1487 -- Fake the loot characteristics to match that of an actual container item
MMOSimca@387 1488 current_loot.identifier = loot_toast_container_id
jcallahan@306 1489 current_loot.label = "contains"
jcallahan@306 1490 current_loot.target_type = AF.ITEM
jcallahan@306 1491
MMOSimca@387 1492 current_loot.sources[loot_toast_container_id] = current_loot.sources[loot_toast_container_id] or {}
jcallahan@312 1493
jcallahan@301 1494 if loot_type == "item" then
jcallahan@312 1495 local item_id = ItemLinkToID(item_link)
jcallahan@312 1496 if item_id then
jcallahan@312 1497 Debug("%s: %s X %d (%d)", event_name, item_link, quantity, item_id)
MMOSimca@340 1498 RecordItemData(item_id, item_link, true)
MMOSimca@387 1499 current_loot.sources[loot_toast_container_id][item_id] = (current_loot.sources[loot_toast_container_id][item_id] or 0) + quantity
jcallahan@312 1500 else
jcallahan@301 1501 Debug("%s: ItemID is nil, from item link %s", event_name, item_link)
jcallahan@312 1502 current_loot = nil
jcallahan@301 1503 return
jcallahan@301 1504 end
jcallahan@301 1505 elseif loot_type == "money" then
jcallahan@312 1506 Debug("%s: money X %d", event_name, quantity)
MMOSimca@387 1507 current_loot.sources[loot_toast_container_id]["money"] = (current_loot.sources[loot_toast_container_id]["money"] or 0) + quantity
jcallahan@312 1508 elseif loot_type == "currency" then
mmosimca@496 1509 local currency_id = CurrencyLinkToID(item_link)
mmosimca@496 1510 if currency_id and currency_id ~= 0 then
mmosimca@496 1511 Debug("%s: %d X %d", event_name, currency_id, quantity)
mmosimca@496 1512 local currency_token = ("currency:%d"):format(currency_id)
MMOSimca@387 1513 current_loot.sources[loot_toast_container_id][currency_token] = (current_loot.sources[loot_toast_container_id][currency_token] or 0) + quantity
jcallahan@312 1514 else
mmosimca@496 1515 Debug("%s: Currency ID is nil or 0, from currency link %s", event_name, item_link)
jcallahan@312 1516 current_loot = nil
jcallahan@312 1517 return
jcallahan@312 1518 end
jcallahan@301 1519 end
jcallahan@312 1520
jcallahan@301 1521 GenericLootUpdate("items")
jcallahan@301 1522 current_loot = nil
MMOSimca@387 1523 container_loot_toasting = true -- Do not count further loots until timer expires or another container is opened
atcaleb@573 1524 elseif loot_source and chat_loot_timer_handle and chat_loot_data.category == AF.ITEM then
MMOSimca@444 1525 -- Handle currency loot toasts for chat-based loot (we do this instead of reading currency chat messages because the chat messages are very delayed)
MMOSimca@444 1526 if loot_type == "currency" then
mmosimca@496 1527 local currency_id = CurrencyLinkToID(item_link)
mmosimca@496 1528 if currency_id and currency_id ~= 0 then
MMOSimca@444 1529 -- Verify that we're still assigning data to the right items
atcaleb@573 1530 if chat_loot_data.category == AF.ITEM and chat_loot_data.identifier and (private.CONTAINER_ITEM_ID_LIST[chat_loot_data.identifier] ~= nil) then
mmosimca@496 1531 Debug("%s: Captured currency for chat-based loot recording. %d X %d", event_name, currency_id, quantity)
mmosimca@496 1532 local currency_token = ("currency:%d"):format(currency_id)
MMOSimca@444 1533 chat_loot_data.loot = chat_loot_data.loot or {}
MMOSimca@444 1534 chat_loot_data.loot[currency_token] = (chat_loot_data.loot[currency_token] or 0) + quantity
MMOSimca@444 1535 else -- If not, cancel the timer and wipe the loot table early
MMOSimca@444 1536 Debug("%s: Canceled chat-based loot recording because we would have assigned the wrong loot!", event_name)
MMOSimca@444 1537 ClearChatLootData()
MMOSimca@444 1538 end
MMOSimca@444 1539 else
mmosimca@496 1540 Debug("%s: Currency ID is nil or 0, from currency link %s", event_name, item_link)
MMOSimca@444 1541 end
MMOSimca@444 1542 -- Handle money loot toasts for chat-based loot (we do this instead of reading money chat messages because the chat messages are very delayed)
MMOSimca@444 1543 elseif loot_type == "money" then
MMOSimca@424 1544 -- Verify that we're still assigning data to the right items
atcaleb@573 1545 if chat_loot_data.category == AF.ITEM and chat_loot_data.identifier and (private.CONTAINER_ITEM_ID_LIST[chat_loot_data.identifier] ~= nil) then
MMOSimca@444 1546 Debug("%s: Captured money for chat-based loot recording. money X %d", event_name, quantity)
MMOSimca@435 1547 chat_loot_data.loot = chat_loot_data.loot or {}
MMOSimca@444 1548 chat_loot_data.loot["money"] = (chat_loot_data.loot["money"] or 0) + quantity
MMOSimca@424 1549 else -- If not, cancel the timer and wipe the loot table early
MMOSimca@424 1550 Debug("%s: Canceled chat-based loot recording because we would have assigned the wrong loot!", event_name)
MMOSimca@424 1551 ClearChatLootData()
MMOSimca@424 1552 end
MMOSimca@424 1553 end
jcallahan@301 1554 else
jcallahan@307 1555 Debug("%s: NPC and Container are nil, storing loot toast data for 5 seconds.", event_name)
jcallahan@307 1556
jcallahan@307 1557 loot_toast_data = loot_toast_data or {}
jcallahan@312 1558 loot_toast_data[#loot_toast_data + 1] = { loot_type, item_link, quantity }
jcallahan@307 1559
MMOSimca@340 1560 local item_id = ItemLinkToID(item_link)
MMOSimca@340 1561 if item_id then
MMOSimca@340 1562 RecordItemData(item_id, item_link, true)
MMOSimca@340 1563 end
MMOSimca@340 1564
MMOSimca@383 1565 loot_toast_data_timer_handle = C_Timer.NewTimer(5, ClearLootToastData)
jcallahan@178 1566 end
jcallahan@178 1567 end
jcallahan@178 1568
jcallahan@178 1569
jcallahan@179 1570 do
MMOSimca@388 1571 local CHAT_MSG_CURRENCY_UPDATE_FUNCS = {
mmosimca@496 1572 [AF.NPC] = function(currency_id, quantity)
mmosimca@496 1573 Debug("CHAT_MSG_CURRENCY: AF.NPC currency:%d (%d)", currency_id, quantity)
MMOSimca@388 1574 end,
mmosimca@496 1575 [AF.ZONE] = function(currency_id, quantity)
mmosimca@496 1576 Debug("CHAT_MSG_CURRENCY: AF.ZONE currency:%d (%d)", currency_id, quantity)
MMOSimca@388 1577 InitializeCurrentLoot()
mmosimca@496 1578 current_loot.list[1] = ("currency:%d:%d"):format(quantity, currency_id)
MMOSimca@388 1579 GenericLootUpdate("zones")
MMOSimca@388 1580 current_loot = nil
MMOSimca@388 1581 end,
MMOSimca@388 1582 }
MMOSimca@388 1583
MMOSimca@388 1584
MMOSimca@388 1585 function WDP:CHAT_MSG_CURRENCY(event_name, message)
MMOSimca@388 1586 local category
MMOSimca@388 1587
MMOSimca@388 1588 local currency_link, quantity = deformat(message, _G.CURRENCY_GAINED_MULTIPLE)
MMOSimca@388 1589 if not currency_link then
MMOSimca@388 1590 quantity, currency_link = 1, deformat(message, _G.CURRENCY_GAINED)
MMOSimca@388 1591 end
mmosimca@496 1592 local currency_id = CurrencyLinkToID(currency_link)
mmosimca@496 1593
mmosimca@496 1594 if not currency_id or currency_id == 0 then
MMOSimca@388 1595 return
MMOSimca@388 1596 end
MMOSimca@388 1597
MMOSimca@388 1598 -- Set update category
MMOSimca@388 1599 if current_action.spell_label == "FISHING" then
MMOSimca@388 1600 category = AF.ZONE
MMOSimca@388 1601 elseif raid_boss_id then
MMOSimca@388 1602 category = AF.NPC
MMOSimca@388 1603 end
MMOSimca@388 1604
MMOSimca@388 1605 -- Take action based on update category
MMOSimca@388 1606 local update_func = CHAT_MSG_CURRENCY_UPDATE_FUNCS[category]
MMOSimca@388 1607 if not category or not update_func then
MMOSimca@388 1608 return
MMOSimca@388 1609 end
mmosimca@496 1610 update_func(currency_id, quantity)
MMOSimca@388 1611 end
MMOSimca@388 1612
MMOSimca@388 1613
jcallahan@179 1614 local CHAT_MSG_LOOT_UPDATE_FUNCS = {
atcaleb@573 1615 -- Handle chat loot data from item containers
MMOSimca@347 1616 [AF.ITEM] = function(item_id, quantity)
MMOSimca@347 1617 -- Verify that we're still assigning data to the right items
MMOSimca@435 1618 if chat_loot_data.identifier and (private.CONTAINER_ITEM_ID_LIST[chat_loot_data.identifier] ~= nil) then
MMOSimca@347 1619 Debug("CHAT_MSG_LOOT: AF.ITEM %d (%d)", item_id, quantity)
MMOSimca@435 1620 chat_loot_data.loot = chat_loot_data.loot or {}
MMOSimca@435 1621 chat_loot_data.loot[item_id] = (chat_loot_data.loot[item_id] or 0) + quantity
MMOSimca@347 1622 else -- If not, cancel the timer and wipe the loot table early
MMOSimca@387 1623 Debug("CHAT_MSG_LOOT: We would have assigned the wrong loot!")
MMOSimca@387 1624 ClearChatLootData()
MMOSimca@347 1625 end
MMOSimca@347 1626 end,
atcaleb@573 1627 -- Handle chat loot data from island expedition rewards
jcallahan@179 1628 [AF.NPC] = function(item_id, quantity)
MMOSimca@345 1629 Debug("CHAT_MSG_LOOT: AF.NPC %d (%d)", item_id, quantity)
atcaleb@573 1630 chat_loot_data.loot = chat_loot_data.loot or {}
atcaleb@573 1631 chat_loot_data.loot[item_id] = (chat_loot_data.loot[item_id] or 0) + quantity
MMOSimca@345 1632 end,
atcaleb@573 1633 -- Handle logging spells for objects
MMOSimca@345 1634 [AF.OBJECT] = function(item_id, quantity)
MMOSimca@345 1635 Debug("CHAT_MSG_LOOT: AF.OBJECT %d (%d)", item_id, quantity)
MMOSimca@381 1636 -- Check for top level object data
MMOSimca@381 1637 local object_entry = DBEntry("objects", ("OPENING:%s"):format(private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[last_timber_spell_id]))
MMOSimca@381 1638 local difficulty_token = InstanceDifficultyToken()
MMOSimca@381 1639 if object_entry[difficulty_token] then
MMOSimca@381 1640 -- Increment loot count
MMOSimca@381 1641 object_entry[difficulty_token]["opening_count"] = (object_entry[difficulty_token]["opening_count"] or 0) + 1
MMOSimca@381 1642
MMOSimca@381 1643 -- Add drop data
MMOSimca@381 1644 object_entry[difficulty_token]["opening"] = object_entry[difficulty_token]["opening"] or {}
MMOSimca@381 1645 table.insert(object_entry[difficulty_token]["opening"], ("%d:%d"):format(item_id, quantity))
MMOSimca@381 1646 else
MMOSimca@381 1647 Debug("CHAT_MSG_LOOT: When handling timber, the top level data was missing for objectID %s.", private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[last_timber_spell_id])
MMOSimca@381 1648 end
jcallahan@179 1649 end,
atcaleb@573 1650 -- Handle fishing loot table population
jcallahan@179 1651 [AF.ZONE] = function(item_id, quantity)
MMOSimca@345 1652 Debug("CHAT_MSG_LOOT: AF.ZONE %d (%d)", item_id, quantity)
jcallahan@312 1653 InitializeCurrentLoot()
jcallahan@312 1654 current_loot.list[1] = ("%d:%d"):format(item_id, quantity)
jcallahan@179 1655 GenericLootUpdate("zones")
jcallahan@312 1656 current_loot = nil
jcallahan@179 1657 end,
jcallahan@179 1658 }
jcallahan@177 1659
jcallahan@177 1660
MMOSimca@388 1661 function WDP:CHAT_MSG_LOOT(event_name, message)
jcallahan@179 1662 local category
jcallahan@177 1663
MMOSimca@345 1664 local item_link, quantity = deformat(message, _G.LOOT_ITEM_PUSHED_SELF_MULTIPLE)
MMOSimca@345 1665 if not item_link then
MMOSimca@345 1666 quantity, item_link = 1, deformat(message, _G.LOOT_ITEM_PUSHED_SELF)
MMOSimca@345 1667 end
MMOSimca@345 1668 local item_id = ItemLinkToID(item_link)
MMOSimca@345 1669
MMOSimca@345 1670 if not item_id then
MMOSimca@345 1671 return
MMOSimca@345 1672 end
MMOSimca@345 1673
MMOSimca@345 1674 -- Set update category
MMOSimca@405 1675 if last_timber_spell_id and item_id == ITEM_ID_TIMBER then
MMOSimca@345 1676 category = AF.OBJECT
atcaleb@573 1677 -- Changed from ~= "EXTRACT_GAS" because of some occassional bad data, and, as far as I know, no benefit.
MMOSimca@345 1678 elseif current_action.spell_label == "FISHING" then
jcallahan@179 1679 category = AF.ZONE
atcaleb@573 1680 elseif not raid_boss_id and chat_loot_timer_handle and chat_loot_data.category == AF.NPC then
jcallahan@179 1681 category = AF.NPC
atcaleb@573 1682 elseif not raid_boss_id and chat_loot_timer_handle and chat_loot_data.category == AF.ITEM then
MMOSimca@347 1683 category = AF.ITEM
jcallahan@179 1684 end
MMOSimca@345 1685
MMOSimca@395 1686 -- We still want to record the item's data, even if it doesn't need its drop location recorded
MMOSimca@395 1687 RecordItemData(item_id, item_link, true)
MMOSimca@395 1688
MMOSimca@345 1689 -- Take action based on update category
jcallahan@179 1690 local update_func = CHAT_MSG_LOOT_UPDATE_FUNCS[category]
atcaleb@558 1691 if not category or not update_func or private.BLACKLISTED_ITEMS[item_id] then
MMOSimca@340 1692 return
MMOSimca@340 1693 end
jcallahan@179 1694 update_func(item_id, quantity)
jcallahan@177 1695 end
MMOSimca@388 1696 end
MMOSimca@388 1697
MMOSimca@388 1698
jcallahan@97 1699 function WDP:RecordQuote(event_name, message, source_name, language_name)
jcallahan@112 1700 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 1701 return
jcallahan@95 1702 end
jcallahan@97 1703 local npc = NPCEntry(name_to_id_map[source_name])
jcallahan@97 1704 npc.quotes = npc.quotes or {}
jcallahan@97 1705 npc.quotes[event_name] = npc.quotes[event_name] or {}
jcallahan@97 1706 npc.quotes[event_name][ReplaceKeywords(message)] = true
jcallahan@97 1707 end
jcallahan@95 1708
jcallahan@95 1709
jcallahan@95 1710 do
jcallahan@40 1711 local SOBER_MATCH = _G.DRUNK_MESSAGE_ITEM_SELF1:gsub("%%s", ".+")
jcallahan@40 1712
jcallahan@40 1713 local DRUNK_COMPARES = {
jcallahan@40 1714 _G.DRUNK_MESSAGE_SELF2,
jcallahan@40 1715 _G.DRUNK_MESSAGE_SELF3,
jcallahan@40 1716 _G.DRUNK_MESSAGE_SELF4,
jcallahan@40 1717 }
jcallahan@40 1718
jcallahan@40 1719 local DRUNK_MATCHES = {
jcallahan@254 1720 (_G.DRUNK_MESSAGE_SELF2:gsub("%%s", ".+")),
jcallahan@254 1721 (_G.DRUNK_MESSAGE_SELF3:gsub("%%s", ".+")),
jcallahan@254 1722 (_G.DRUNK_MESSAGE_SELF4:gsub("%%s", ".+")),
jcallahan@40 1723 }
jcallahan@40 1724
jcallahan@167 1725 local RECIPE_MATCH = _G.ERR_LEARN_RECIPE_S:gsub("%%s", "(.*)")
jcallahan@167 1726
jcallahan@167 1727
jcallahan@167 1728 local function RecordDiscovery(tradeskill_name, tradeskill_index)
jcallahan@167 1729 if tradeskill_name == private.discovered_recipe_name then
catherton@479 1730 DBEntry("spells", tonumber(_G.C_TradeSkillUI.GetRecipeLink(tradeskill_index):match("^|c%x%x%x%x%x%x%x%x|H%w+:(%d+)"))).discovery = ("%d:%d"):format(private.previous_spell_id, private.profession_level)
jcallahan@167 1731
jcallahan@167 1732 private.discovered_recipe_name = nil
jcallahan@167 1733 private.profession_level = nil
jcallahan@167 1734 private.previous_spell_id = nil
jcallahan@169 1735
jcallahan@169 1736 return true
jcallahan@167 1737 end
jcallahan@167 1738 end
jcallahan@167 1739
jcallahan@167 1740
jcallahan@167 1741 local function IterativeRecordDiscovery()
jcallahan@167 1742 TradeSkillExecutePer(RecordDiscovery)
jcallahan@167 1743 end
jcallahan@167 1744
jcallahan@167 1745
jcallahan@92 1746 function WDP:CHAT_MSG_SYSTEM(event_name, message)
mmosimca@514 1747 -- This code no longer works, as of Patch 7.0.3, because Blizzard unified the text from quest rewards and loot to match (and now there is no way to distinguish between them)
MMOSimca@532 1748 --[[
MMOSimca@532 1749 -- Removed in Patch 7.0.3; previously used to determine if a system message was a quest reward or not
MMOSimca@532 1750 local ERR_QUEST_REWARD_ITEM_MULT_IS = _G.ERR_QUEST_REWARD_ITEM_MULT_IS or "Received %d of item: %s."
MMOSimca@532 1751 local ERR_QUEST_REWARD_ITEM_S = _G.ERR_QUEST_REWARD_ITEM_S or "Received item: %s."
MMOSimca@532 1752
catherton@472 1753 local item_link, quantity = deformat(message, ERR_QUEST_REWARD_ITEM_MULT_IS)
MMOSimca@400 1754 if not item_link then
catherton@472 1755 quantity, item_link = 1, deformat(message, ERR_QUEST_REWARD_ITEM_S)
MMOSimca@400 1756 end
MMOSimca@400 1757 local item_id = ItemLinkToID(item_link)
MMOSimca@400 1758
mmosimca@514 1759 if item_id then
mmosimca@514 1760 -- If it was a quest message (that we can decode), parse its link
mmosimca@514 1761 RecordItemData(item_id, item_link, true)
mmosimca@514 1762 else
mmosimca@514 1763 -- If it isn't a quest message, check the other uses of system messages
MMOSimca@532 1764 end
MMOSimca@532 1765 ]]--
MMOSimca@532 1766
MMOSimca@532 1767 if not private.trainer_shown then
MMOSimca@532 1768 local recipe_name = message:match(RECIPE_MATCH)
MMOSimca@532 1769
MMOSimca@532 1770 if recipe_name and private.previous_spell_id then
MMOSimca@532 1771 local profession_name, prof_level = _G.C_TradeSkillUI.GetTradeSkillLine()
MMOSimca@532 1772
MMOSimca@532 1773 if profession_name == _G.UNKNOWN then
MMOSimca@532 1774 return
jcallahan@167 1775 end
MMOSimca@532 1776 private.discovered_recipe_name = recipe_name
MMOSimca@532 1777 private.profession_level = prof_level
MMOSimca@532 1778
MMOSimca@532 1779 C_Timer.After(0.2, IterativeRecordDiscovery)
jcallahan@167 1780 end
MMOSimca@532 1781 end
MMOSimca@532 1782
MMOSimca@532 1783 if currently_drunk then
MMOSimca@532 1784 if message == _G.DRUNK_MESSAGE_SELF1 or message:match(SOBER_MATCH) then
MMOSimca@532 1785 currently_drunk = nil
MMOSimca@400 1786 end
MMOSimca@532 1787 return
MMOSimca@532 1788 end
MMOSimca@532 1789
MMOSimca@532 1790 for index = 1, #DRUNK_MATCHES do
MMOSimca@532 1791 if message == DRUNK_COMPARES[index] or message:match(DRUNK_MATCHES[index]) then
MMOSimca@532 1792 currently_drunk = true
MMOSimca@532 1793 break
jcallahan@40 1794 end
jcallahan@40 1795 end
jcallahan@40 1796 end
jcallahan@40 1797 end
jcallahan@40 1798
jcallahan@307 1799
jcallahan@331 1800 do
jcallahan@23 1801 local FLAGS_NPC = bit.bor(_G.COMBATLOG_OBJECT_TYPE_GUARDIAN, _G.COMBATLOG_OBJECT_CONTROL_NPC)
jcallahan@23 1802 local FLAGS_NPC_CONTROL = bit.bor(_G.COMBATLOG_OBJECT_AFFILIATION_OUTSIDER, _G.COMBATLOG_OBJECT_CONTROL_NPC)
jcallahan@23 1803
atcaleb@552 1804 local function RecordNPCSpell(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id)
atcaleb@558 1805 if not spell_id or private.BLACKLISTED_SPELLS[spell_id] then
jcallahan@23 1806 return
jcallahan@23 1807 end
jcallahan@34 1808 local source_type, source_id = ParseGUID(source_guid)
jcallahan@23 1809
jcallahan@171 1810 if not source_id or not UnitTypeIsNPC(source_type) then
jcallahan@23 1811 return
jcallahan@23 1812 end
jcallahan@23 1813
jcallahan@23 1814 if bit.band(FLAGS_NPC_CONTROL, source_flags) == FLAGS_NPC_CONTROL and bit.band(FLAGS_NPC, source_flags) ~= 0 then
jcallahan@248 1815 local encounter_data = NPCEntry(source_id):EncounterData(InstanceDifficultyToken())
jcallahan@28 1816 encounter_data.spells = encounter_data.spells or {}
jcallahan@28 1817 encounter_data.spells[spell_id] = (encounter_data.spells[spell_id] or 0) + 1
jcallahan@23 1818 end
jcallahan@23 1819 end
jcallahan@23 1820
jcallahan@115 1821 local HEAL_BATTLE_PETS_SPELL_ID = 125801
jcallahan@115 1822
jcallahan@246 1823 local previous_combat_event = {}
jcallahan@246 1824
jcallahan@23 1825 local COMBAT_LOG_FUNCS = {
jcallahan@23 1826 SPELL_AURA_APPLIED = RecordNPCSpell,
jcallahan@23 1827 SPELL_CAST_START = RecordNPCSpell,
atcaleb@552 1828 SPELL_CAST_SUCCESS = function(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id)
jcallahan@115 1829 if spell_id == HEAL_BATTLE_PETS_SPELL_ID then
jcallahan@115 1830 local unit_type, unit_idnum = ParseGUID(source_guid)
jcallahan@115 1831
jcallahan@171 1832 if unit_idnum and UnitTypeIsNPC(unit_type) then
jcallahan@115 1833 NPCEntry(unit_idnum).stable_master = true
jcallahan@115 1834 end
jcallahan@115 1835 end
atcaleb@552 1836 RecordNPCSpell(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id)
jcallahan@115 1837 end,
atcaleb@552 1838 UNIT_DIED = function(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id)
jcallahan@65 1839 local unit_type, unit_idnum = ParseGUID(dest_guid)
jcallahan@65 1840
jcallahan@171 1841 if not unit_idnum or not UnitTypeIsNPC(unit_type) then
mmosimca@515 1842 --Debug("%s: %s is not an NPC, or has no ID.", sub_event, dest_name or _G.UNKNOWN) -- we really don't need to know this
jcallahan@177 1843 ClearKilledNPC()
jcallahan@98 1844 private.harvesting = nil
jcallahan@65 1845 return
jcallahan@65 1846 end
jcallahan@177 1847
jcallahan@246 1848 if source_guid == "" then
jcallahan@246 1849 source_guid = nil
jcallahan@246 1850 end
jcallahan@246 1851 local killer_guid = source_guid or previous_combat_event.source_guid
jcallahan@246 1852 local killer_name = source_name or previous_combat_event.source_name
jcallahan@246 1853
jcallahan@299 1854 if not previous_combat_event.party_damage then
jcallahan@312 1855 --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 1856 table.wipe(previous_combat_event)
jcallahan@217 1857 ClearKilledNPC()
jcallahan@306 1858 else
jcallahan@317 1859 --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 1860 end
jcallahan@177 1861 killed_npc_id = unit_idnum
MMOSimca@383 1862 C_Timer.After(0.1, ClearKilledNPC)
jcallahan@65 1863 end,
jcallahan@23 1864 }
jcallahan@23 1865
jcallahan@23 1866
jcallahan@246 1867 local NON_DAMAGE_EVENTS = {
jcallahan@246 1868 SPELL_AURA_APPLIED = true,
jcallahan@246 1869 SPELL_AURA_REMOVED = true,
jcallahan@246 1870 SPELL_AURA_REMOVED_DOSE = true,
jcallahan@246 1871 SPELL_CAST_FAILED = true,
jcallahan@246 1872 SWING_MISSED = true,
jcallahan@246 1873 }
jcallahan@246 1874
jcallahan@299 1875 local DAMAGE_EVENTS = {
jcallahan@299 1876 RANGE_DAMAGE = true,
jcallahan@299 1877 SPELL_BUILDING_DAMAGE = true,
jcallahan@299 1878 SPELL_DAMAGE = true,
jcallahan@299 1879 SPELL_PERIODIC_DAMAGE = true,
jcallahan@299 1880 SWING_DAMAGE = true,
jcallahan@299 1881 }
jcallahan@299 1882
jcallahan@246 1883
atcaleb@552 1884 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, spell_id, ...)
atcaleb@558 1885 time_stamp, sub_event, hide_caster, source_guid, source_name, source_flags, source_raid_flags, dest_guid, dest_name, dest_flags, dest_raid_flags, spell_id = CombatLogGetCurrentEventInfo()
atcaleb@552 1886
jcallahan@23 1887 local combat_log_func = COMBAT_LOG_FUNCS[sub_event]
jcallahan@23 1888
jcallahan@23 1889 if not combat_log_func then
jcallahan@299 1890 if DAMAGE_EVENTS[sub_event] then
jcallahan@299 1891 table.wipe(previous_combat_event)
jcallahan@246 1892 previous_combat_event.source_name = source_name
jcallahan@299 1893
jcallahan@299 1894 if source_guid ~= dest_guid and (in_instance or group_member_guids[source_guid] or group_pet_guids[source_guid]) then
jcallahan@299 1895 previous_combat_event.party_damage = true
jcallahan@299 1896 end
jcallahan@246 1897 end
jcallahan@23 1898 return
jcallahan@23 1899 end
atcaleb@552 1900 combat_log_func(sub_event, source_guid, source_name, source_flags, dest_guid, dest_name, dest_flags, spell_id)
jcallahan@297 1901
jcallahan@297 1902 if NON_DAMAGE_EVENTS[sub_event] then
jcallahan@297 1903 table.wipe(previous_combat_event)
jcallahan@297 1904 end
jcallahan@23 1905 end
jcallahan@23 1906
catherton@465 1907
jcallahan@44 1908 local DIPLOMACY_SPELL_ID = 20599
jcallahan@44 1909 local MR_POP_RANK1_SPELL_ID = 78634
jcallahan@44 1910 local MR_POP_RANK2_SPELL_ID = 78635
MMOSimca@418 1911 local TRADING_PACT_SPELL_ID = 170200
jcallahan@44 1912
jcallahan@44 1913
jcallahan@92 1914 function WDP:COMBAT_TEXT_UPDATE(event_name, message_type, faction_name, amount)
jcallahan@177 1915 if message_type ~= "FACTION" or not killed_npc_id then
jcallahan@44 1916 return
jcallahan@44 1917 end
jcallahan@44 1918 UpdateFactionData()
jcallahan@44 1919
jcallahan@46 1920 if not faction_name or not faction_standings[faction_name] then
jcallahan@46 1921 return
jcallahan@46 1922 end
jcallahan@177 1923 local npc = NPCEntry(killed_npc_id)
jcallahan@177 1924 ClearKilledNPC()
jcallahan@46 1925
jcallahan@44 1926 if not npc then
jcallahan@98 1927 private.harvesting = nil
jcallahan@44 1928 return
jcallahan@44 1929 end
jcallahan@98 1930 npc.harvested = private.harvesting
jcallahan@98 1931 private.harvesting = nil
jcallahan@98 1932
jcallahan@44 1933 local modifier = 1
jcallahan@44 1934
MMOSimca@340 1935 -- Check for modifiers from known spells
jcallahan@44 1936 if _G.IsSpellKnown(DIPLOMACY_SPELL_ID) then
jcallahan@44 1937 modifier = modifier + 0.1
jcallahan@44 1938 end
jcallahan@44 1939 if _G.IsSpellKnown(MR_POP_RANK2_SPELL_ID) then
jcallahan@44 1940 modifier = modifier + 0.1
jcallahan@44 1941 elseif _G.IsSpellKnown(MR_POP_RANK1_SPELL_ID) then
jcallahan@44 1942 modifier = modifier + 0.05
jcallahan@44 1943 end
MMOSimca@418 1944 if _G.IsSpellKnown(TRADING_PACT_SPELL_ID) then
MMOSimca@418 1945 modifier = modifier + 0.2
MMOSimca@418 1946 end
jcallahan@44 1947
MMOSimca@340 1948 -- Determine faction ID
MMOSimca@340 1949 local faction_ID
MMOSimca@418 1950 for pseudo_faction_name, faction_data_table in pairs(private.FACTION_DATA) do
MMOSimca@581 1951 if faction_data_table[3] and faction_data_table[3].currentStanding and faction_name == faction_data_table[3].currentStanding then
MMOSimca@418 1952 -- Check ignore flag
MMOSimca@418 1953 if faction_data_table[2] then
MMOSimca@418 1954 return
MMOSimca@418 1955 end
MMOSimca@340 1956 faction_ID = faction_data_table[1]
MMOSimca@418 1957 break
MMOSimca@340 1958 end
MMOSimca@340 1959 end
MMOSimca@340 1960 if faction_ID and faction_ID > 0 then
MMOSimca@340 1961 -- Check for modifiers from Commendations (applied directly to the faction, account-wide)
MMOSimca@581 1962 local factionReturn = _G.C_Reputation.GetFactionDataByID(faction_ID)
MMOSimca@581 1963 if factionReturn and factionReturn.hasBonusRepGain then
MMOSimca@340 1964 modifier = modifier + 1
MMOSimca@340 1965 end
MMOSimca@340 1966 end
MMOSimca@340 1967
MMOSimca@340 1968 -- Check for modifiers from buffs
MMOSimca@418 1969 for buff_name, buff_data_table in pairs(private.REP_BUFFS) do
jcallahan@44 1970 if _G.UnitBuff("player", buff_name) then
MMOSimca@340 1971 local modded_faction = buff_data_table.faction
jcallahan@44 1972
jcallahan@44 1973 if not modded_faction or faction_name == modded_faction then
MMOSimca@340 1974 if buff_data_table.ignore then
MMOSimca@340 1975 -- Some buffs from tabards convert all rep of other factions into rep for a specific faction.
MMOSimca@340 1976 -- We can't know what faction the rep was orginally from, so we must ignore the data entirely in these cases.
MMOSimca@340 1977 return
MMOSimca@340 1978 else
MMOSimca@340 1979 modifier = modifier + buff_data_table.modifier
MMOSimca@340 1980 end
jcallahan@44 1981 end
jcallahan@44 1982 end
jcallahan@44 1983 end
catherton@465 1984
jcallahan@65 1985 npc.reputations = npc.reputations or {}
jcallahan@65 1986 npc.reputations[("%s:%s"):format(faction_name, faction_standings[faction_name])] = math.floor(amount / modifier)
jcallahan@32 1987 end
jcallahan@44 1988 end -- do-block
jcallahan@18 1989
jcallahan@18 1990
MMOSimca@581 1991 function WDP:WORLD_CURSOR_TOOLTIP_UPDATE(event_name, is_shown)
MMOSimca@355 1992 if current_action.fishing_target or _G.Minimap:IsMouseOver() then
jcallahan@140 1993 return
jcallahan@140 1994 end
jcallahan@140 1995 local text = _G["GameTooltipTextLeft1"]:GetText()
jcallahan@140 1996
MMOSimca@355 1997 -- Handle Fishing
MMOSimca@355 1998 if (current_action.spell_label == "FISHING") then
MMOSimca@355 1999 if not text or text == "Fishing Bobber" then
MMOSimca@355 2000 text = "NONE"
MMOSimca@355 2001 else
MMOSimca@355 2002 current_action.fishing_target = true
MMOSimca@355 2003 end
MMOSimca@355 2004 current_action.identifier = ("%s:%s"):format(current_action.spell_label, text)
MMOSimca@355 2005 -- Handle Garrison Cache
MMOSimca@355 2006 elseif private.GARRISON_CACHE_OBJECT_NAME_TO_OBJECT_ID_MAP[text] then
MMOSimca@355 2007 last_garrison_cache_object_id = private.GARRISON_CACHE_OBJECT_NAME_TO_OBJECT_ID_MAP[text]
jcallahan@140 2008 end
jcallahan@140 2009 end
jcallahan@140 2010
jcallahan@140 2011
jcallahan@92 2012 function WDP:ITEM_TEXT_BEGIN(event_name)
jcallahan@42 2013 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc"))
jcallahan@42 2014
jcallahan@42 2015 if not unit_idnum or unit_type ~= private.UNIT_TYPES.OBJECT or _G.UnitName("npc") ~= _G.ItemTextGetItem() then
jcallahan@42 2016 return
jcallahan@42 2017 end
jcallahan@42 2018 UpdateDBEntryLocation("objects", unit_idnum)
jcallahan@42 2019 end
jcallahan@42 2020
jcallahan@42 2021
jcallahan@13 2022 do
MMOSimca@343 2023 local LOOT_OPENED_VERIFY_FUNCS = {
jcallahan@324 2024 -- 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 2025 [AF.ITEM] = function()
jcallahan@16 2026 local locked_item_id
jcallahan@16 2027
jcallahan@16 2028 for bag_index = 0, _G.NUM_BAG_FRAMES do
MMOSimca@581 2029 for slot_index = 1, _G.C_Container.GetContainerNumSlots(bag_index) do
MMOSimca@581 2030 local _, _, is_locked, _, _, is_lootable = _G.C_Container.GetContainerItemInfo(bag_index, slot_index)
jcallahan@324 2031
jcallahan@324 2032 if is_locked and is_lootable then
MMOSimca@581 2033 locked_item_id = ItemLinkToID(_G.C_Container.GetContainerItemLink(bag_index, slot_index))
jcallahan@165 2034 break
jcallahan@16 2035 end
jcallahan@16 2036 end
jcallahan@165 2037
jcallahan@165 2038 if locked_item_id then
jcallahan@165 2039 break
jcallahan@165 2040 end
jcallahan@16 2041 end
jcallahan@16 2042
MMOSimca@367 2043 if (not current_action.spell_label == "DISENCHANT") and (not locked_item_id or (current_action.identifier and current_action.identifier ~= locked_item_id)) then
jcallahan@16 2044 return false
jcallahan@16 2045 end
jcallahan@122 2046 current_action.identifier = locked_item_id
jcallahan@16 2047 return true
jcallahan@16 2048 end,
MMOSimca@401 2049 [AF.NPC] = function()
MMOSimca@401 2050 return not _G.IsFishingLoot()
MMOSimca@401 2051 end,
MMOSimca@401 2052 [AF.OBJECT] = function()
MMOSimca@401 2053 return not _G.IsFishingLoot()
MMOSimca@401 2054 end,
jcallahan@17 2055 [AF.ZONE] = function()
jcallahan@140 2056 current_action.zone_data = UpdateDBEntryLocation("zones", current_action.identifier)
jcallahan@210 2057 return _G.IsFishingLoot()
jcallahan@17 2058 end,
jcallahan@13 2059 }
jcallahan@13 2060
jcallahan@13 2061
MMOSimca@343 2062 local LOOT_OPENED_UPDATE_FUNCS = {
jcallahan@16 2063 [AF.ITEM] = function()
jcallahan@28 2064 GenericLootUpdate("items")
jcallahan@28 2065 end,
jcallahan@28 2066 [AF.NPC] = function()
jcallahan@75 2067 local difficulty_token = InstanceDifficultyToken()
jcallahan@312 2068 local loot_label = current_loot.label
jcallahan@77 2069 local source_list = {}
jcallahan@75 2070
jcallahan@131 2071 for source_guid, loot_data in pairs(current_loot.sources) do
jcallahan@331 2072 local _, source_id = ParseGUID(source_guid)
jcallahan@75 2073 local npc = NPCEntry(source_id)
jcallahan@75 2074
jcallahan@75 2075 if npc then
jcallahan@248 2076 local encounter_data = npc:EncounterData(difficulty_token)
jcallahan@312 2077 encounter_data[loot_label] = encounter_data[loot_label] or {}
jcallahan@75 2078
jcallahan@78 2079 if not source_list[source_guid] then
jcallahan@77 2080 encounter_data.loot_counts = encounter_data.loot_counts or {}
MMOSimca@426 2081 encounter_data.loot_counts[loot_label] = (encounter_data.loot_counts[loot_label] or 0) + 1
jcallahan@312 2082 source_list[source_guid] = true
jcallahan@77 2083 end
jcallahan@77 2084
jcallahan@309 2085 for loot_token, quantity in pairs(loot_data) do
mmosimca@496 2086 local loot_type, currency_id = (":"):split(loot_token)
mmosimca@496 2087
mmosimca@496 2088 if loot_type == "currency" and currency_id then
mmosimca@496 2089 -- Convert currency_id back into number from string
mmosimca@496 2090 currency_id = tonumber(currency_id) or 0
mmosimca@496 2091 if currency_id ~= 0 then
mmosimca@496 2092 table.insert(encounter_data[loot_label], ("currency:%d:%d"):format(quantity, currency_id))
mmosimca@496 2093 end
jcallahan@309 2094 elseif loot_token == "money" then
jcallahan@312 2095 table.insert(encounter_data[loot_label], ("money:%d"):format(quantity))
jcallahan@309 2096 else
jcallahan@312 2097 table.insert(encounter_data[loot_label], ("%d:%d"):format(loot_token, quantity))
jcallahan@309 2098 end
jcallahan@75 2099 end
jcallahan@75 2100 end
jcallahan@75 2101 end
jcallahan@16 2102 end,
jcallahan@13 2103 [AF.OBJECT] = function()
jcallahan@28 2104 GenericLootUpdate("objects", InstanceDifficultyToken())
jcallahan@17 2105 end,
jcallahan@17 2106 [AF.ZONE] = function()
MMOSimca@328 2107 if not (current_loot.map_level and current_loot.x and current_loot.y and current_loot.zone_data) then
MMOSimca@328 2108 return
MMOSimca@328 2109 end
jcallahan@141 2110 local location_token = ("%d:%d:%d"):format(current_loot.map_level, current_loot.x, current_loot.y)
jcallahan@41 2111
jcallahan@41 2112 -- This will start life as a boolean true.
mmosimca@522 2113 if type(current_loot.zone_data[location_token]) ~= "table" then
jcallahan@131 2114 current_loot.zone_data[location_token] = {
jcallahan@41 2115 drops = {}
jcallahan@41 2116 }
jcallahan@41 2117 end
jcallahan@132 2118 local loot_count = ("%s_count"):format(current_loot.label)
jcallahan@131 2119 current_loot.zone_data[location_token][loot_count] = (current_loot.zone_data[location_token][loot_count] or 0) + 1
jcallahan@41 2120
jcallahan@131 2121 if current_loot.sources then
jcallahan@131 2122 for source_guid, loot_data in pairs(current_loot.sources) do
jcallahan@131 2123 for item_id, quantity in pairs(loot_data) do
jcallahan@131 2124 table.insert(current_loot.zone_data[location_token].drops, ("%d:%d"):format(item_id, quantity))
jcallahan@131 2125 end
jcallahan@131 2126 end
jcallahan@131 2127 end
jcallahan@131 2128
jcallahan@131 2129 if #current_loot.list <= 0 then
jcallahan@131 2130 return
jcallahan@131 2131 end
jcallahan@131 2132
jcallahan@131 2133 for index = 1, #current_loot.list do
MMOSimca@443 2134 table.insert(current_loot.zone_data[location_token].drops, current_loot.list[index])
jcallahan@41 2135 end
jcallahan@13 2136 end,
jcallahan@13 2137 }
jcallahan@13 2138
jcallahan@79 2139 -- Prevent opening the same loot window multiple times from recording data multiple times.
jcallahan@79 2140 local loot_guid_registry = {}
jcallahan@124 2141
jcallahan@124 2142
jcallahan@124 2143 function WDP:LOOT_CLOSED(event_name)
MMOSimca@398 2144 ClearChatLootData()
jcallahan@131 2145 current_loot = nil
jcallahan@131 2146 table.wipe(current_action)
jcallahan@124 2147 end
jcallahan@124 2148
jcallahan@13 2149
jcallahan@322 2150 local function ExtrapolatedCurrentActionFromLootData(event_name)
MMOSimca@402 2151 local log_source = event_name .. "- ExtrapolatedCurrentActionFromLootData"
MMOSimca@402 2152 local previous_spell_label = current_action.spell_label
jcallahan@322 2153 local extrapolated_guid_registry = {}
jcallahan@322 2154 local num_guids = 0
MMOSimca@402 2155 table.wipe(current_action)
MMOSimca@402 2156
MMOSimca@402 2157 if _G.IsFishingLoot() then
MMOSimca@402 2158 -- Set up a proper 'fishing' current_action table
MMOSimca@402 2159 local zone_name, area_id, x, y, map_level, instance_token = CurrentLocationData()
MMOSimca@402 2160 if not (zone_name and area_id and x and y and map_level) then
mmosimca@508 2161 if not (_G.IsInInstance()) then
mmosimca@508 2162 Debug("%s: Missing current location data - %s, %s, %s, %s, %s.", log_source, tostring(zone_name), tostring(area_id), tostring(x), tostring(y), tostring(map_level))
mmosimca@508 2163 end
MMOSimca@402 2164 return
MMOSimca@402 2165 end
MMOSimca@402 2166 current_action.instance_token = instance_token
MMOSimca@402 2167 current_action.map_level = map_level
MMOSimca@402 2168 current_action.x = x
MMOSimca@402 2169 current_action.y = y
MMOSimca@402 2170 current_action.zone_data = ("%s:%d"):format(zone_name, area_id)
MMOSimca@402 2171 current_action.spell_label = "FISHING"
MMOSimca@402 2172 current_action.loot_label = "fishing"
MMOSimca@402 2173 current_action.identifier = "FISHING:NONE"
MMOSimca@402 2174 current_action.target_type = AF.ZONE
MMOSimca@402 2175
MMOSimca@402 2176 Debug("%s: Fishing loot detected.", log_source)
MMOSimca@402 2177 return true
MMOSimca@402 2178 end
jcallahan@322 2179
MMOSimca@344 2180 -- Loot extrapolation cannot handle objects that need special spell labels (like HERBALISM or MINING) (MIND_CONTROL is okay)
MMOSimca@402 2181 if previous_spell_label and private.SPELL_FLAGS_BY_LABEL[previous_spell_label] and not private.NON_LOOT_SPELL_LABELS[previous_spell_label] then
MMOSimca@344 2182 Debug("%s: Problematic spell label %s found. Loot extrapolation for this set of loot would have run an increased risk of introducing bad data into the system.", log_source, private.previous_spell_id)
MMOSimca@344 2183 return false
MMOSimca@344 2184 end
MMOSimca@344 2185
jcallahan@322 2186 for loot_slot = 1, _G.GetNumLootItems() do
jcallahan@322 2187 local loot_info = {
jcallahan@322 2188 _G.GetLootSourceInfo(loot_slot)
jcallahan@322 2189 }
jcallahan@322 2190
jcallahan@322 2191 for loot_index = 1, #loot_info, 2 do
jcallahan@322 2192 local source_guid = loot_info[loot_index]
jcallahan@322 2193
jcallahan@322 2194 if not extrapolated_guid_registry[source_guid] then
jcallahan@322 2195 local unit_type, unit_idnum = ParseGUID(source_guid)
jcallahan@322 2196
jcallahan@322 2197 if unit_type and unit_idnum then
jcallahan@322 2198 extrapolated_guid_registry[source_guid] = {
jcallahan@322 2199 unit_type,
jcallahan@322 2200 unit_idnum
jcallahan@322 2201 }
jcallahan@322 2202
jcallahan@322 2203 num_guids = num_guids + 1
jcallahan@322 2204 end
jcallahan@322 2205 end
jcallahan@322 2206 end
jcallahan@322 2207 end
jcallahan@322 2208
jcallahan@322 2209 if num_guids == 0 then
jcallahan@322 2210 Debug("%s: No GUIDs found in loot. Blank loot window?", log_source)
jcallahan@322 2211 return false
jcallahan@322 2212 end
jcallahan@326 2213
jcallahan@322 2214 local num_npcs = 0
jcallahan@322 2215 local num_objects = 0
jcallahan@324 2216 local num_itemcontainers = 0
jcallahan@322 2217
jcallahan@322 2218 for source_guid, guid_data in pairs(extrapolated_guid_registry) do
jcallahan@322 2219 local unit_type = guid_data[1]
mmosimca@516 2220 local loot_label = (unit_type == private.UNIT_TYPES.OBJECT) and "opening" or (UnitTypeIsNPC(unit_type) and "drops") or ((unit_type == private.UNIT_TYPES.ITEM) and "contains")
jcallahan@322 2221
jcallahan@322 2222 if loot_label then
jcallahan@322 2223 local unit_idnum = guid_data[2]
jcallahan@322 2224
jcallahan@322 2225 if loot_guid_registry[loot_label] and loot_guid_registry[loot_label][source_guid] then
jcallahan@322 2226 Debug("%s: Previously scanned loot for unit with GUID %s and identifier %s.", log_source, source_guid, unit_idnum)
jcallahan@322 2227 elseif unit_type == private.UNIT_TYPES.OBJECT and unit_idnum ~= OBJECT_ID_FISHING_BOBBER then
jcallahan@322 2228 current_action.loot_label = loot_label
jcallahan@322 2229 current_action.spell_label = "OPENING"
jcallahan@322 2230 current_action.target_type = AF.OBJECT
jcallahan@322 2231 current_action.identifier = unit_idnum
jcallahan@322 2232 num_objects = num_objects + 1
jcallahan@322 2233 elseif UnitTypeIsNPC(unit_type) then
jcallahan@322 2234 current_action.loot_label = loot_label
jcallahan@322 2235 current_action.target_type = AF.NPC
jcallahan@322 2236 current_action.identifier = unit_idnum
jcallahan@322 2237 num_npcs = num_npcs + 1
mmosimca@516 2238 elseif unit_type == private.UNIT_TYPES.ITEM then
jcallahan@324 2239 current_action.loot_label = loot_label
jcallahan@324 2240 current_action.target_type = AF.ITEM
jcallahan@324 2241 -- current_action.identifier assigned during loot verification.
jcallahan@324 2242 num_itemcontainers = num_itemcontainers + 1
jcallahan@322 2243 end
jcallahan@322 2244 else
jcallahan@322 2245 -- 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 2246 Debug("%s: Unit with GUID %s has unsupported type for looting.", log_source, source_guid)
jcallahan@322 2247 return false
jcallahan@322 2248 end
jcallahan@322 2249 end
jcallahan@322 2250
jcallahan@322 2251 if not current_action.target_type then
jcallahan@322 2252 Debug("%s: Failure to obtain target_type.", log_source)
jcallahan@322 2253 return false
jcallahan@322 2254 end
jcallahan@322 2255
jcallahan@322 2256 -- 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 2257 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 2258 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 2259 return false
jcallahan@322 2260 end
jcallahan@322 2261
jcallahan@322 2262 return true
jcallahan@322 2263 end
jcallahan@322 2264
jcallahan@322 2265
MMOSimca@343 2266 function WDP:LOOT_OPENED(event_name)
MMOSimca@398 2267 ClearChatLootData()
MMOSimca@387 2268
jcallahan@132 2269 if current_loot then
jcallahan@322 2270 current_loot = nil
jcallahan@322 2271 Debug("%s: Previous loot did not process in time for this event. Attempting to extrapolate current_action from loot data.", event_name)
jcallahan@322 2272
jcallahan@322 2273 if not ExtrapolatedCurrentActionFromLootData(event_name) then
jcallahan@322 2274 Debug("%s: Unable to extrapolate current_action. Aborting attempts to handle loot for now.", event_name)
jcallahan@322 2275 return
jcallahan@322 2276 end
jcallahan@18 2277 end
jcallahan@151 2278
jcallahan@151 2279 if not current_action.target_type then
jcallahan@322 2280 if not ExtrapolatedCurrentActionFromLootData(event_name) then
jcallahan@322 2281 Debug("%s: Unable to extrapolate current_action. Aborting attempts to handle loot for now.", event_name)
jcallahan@322 2282 return
jcallahan@322 2283 end
jcallahan@151 2284 end
MMOSimca@343 2285 local verify_func = LOOT_OPENED_VERIFY_FUNCS[current_action.target_type]
MMOSimca@343 2286 local update_func = LOOT_OPENED_UPDATE_FUNCS[current_action.target_type]
jcallahan@13 2287
jcallahan@14 2288 if not verify_func or not update_func then
jcallahan@322 2289 Debug("%s: The current action's target type is unsupported or nil.", event_name)
jcallahan@13 2290 return
jcallahan@13 2291 end
jcallahan@13 2292
mmosimca@522 2293 if type(verify_func) == "function" and not verify_func() then
jcallahan@324 2294 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 2295 return
jcallahan@14 2296 end
jcallahan@80 2297 local guids_used = {}
jcallahan@132 2298
jcallahan@301 2299 InitializeCurrentLoot()
jcallahan@217 2300 loot_guid_registry[current_loot.label] = loot_guid_registry[current_loot.label] or {}
jcallahan@217 2301
jcallahan@55 2302 for loot_slot = 1, _G.GetNumLootItems() do
MMOSimca@581 2303 local texture_filedata_id, item_text, slot_quantity, _, locked = _G.GetLootSlotInfo(loot_slot)
jcallahan@55 2304 local slot_type = _G.GetLootSlotType(loot_slot)
catherton@463 2305 local loot_info = { _G.GetLootSourceInfo(loot_slot) }
catherton@464 2306 local loot_link = _G.GetLootSlotLink(loot_slot)
jcallahan@13 2307
jcallahan@237 2308 -- Odd index is GUID, even is count.
jcallahan@237 2309 for loot_index = 1, #loot_info, 2 do
jcallahan@237 2310 local source_guid = loot_info[loot_index]
jcallahan@75 2311
jcallahan@237 2312 if not loot_guid_registry[current_loot.label][source_guid] then
jcallahan@237 2313 local loot_quantity = loot_info[loot_index + 1]
jcallahan@324 2314 -- There is a new bug in 5.4.0 that causes GetLootSlotInfo() to (rarely) return nil values for slot_quantity.
jcallahan@324 2315 if slot_quantity then
jcallahan@324 2316 -- 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 2317 if slot_quantity > loot_quantity then
jcallahan@324 2318 loot_quantity = slot_quantity
jcallahan@324 2319 end
jcallahan@324 2320 local source_type, source_id = ParseGUID(source_guid)
MMOSimca@329 2321 local source_key = ("%s:%d"):format(source_type, source_id)
jcallahan@324 2322
MMOSimca@581 2323 if slot_type == ENUM_LOOTSLOTTYPE_ITEM then
catherton@464 2324 if loot_link then
catherton@464 2325 local item_id = ItemLinkToID(loot_link)
catherton@464 2326 Debug("GUID: %s - Type:ID: %s - ItemID: %d - Amount: %d (%d)", loot_info[loot_index], source_key, item_id, loot_info[loot_index + 1], slot_quantity)
catherton@464 2327 current_loot.sources[source_guid] = current_loot.sources[source_guid] or {}
catherton@464 2328 current_loot.sources[source_guid][item_id] = (current_loot.sources[source_guid][item_id] or 0) + loot_quantity
catherton@464 2329 guids_used[source_guid] = true
catherton@463 2330 else
catherton@463 2331 Debug("%s: Loot link is nil for loot slot %d of the entity with GUID %s and Type:ID: %s.", event_name, loot_slot, loot_info[loot_index], source_key)
catherton@463 2332 end
MMOSimca@581 2333 elseif slot_type == ENUM_LOOTSLOTTYPE_MONEY then
jcallahan@324 2334 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 2335 if current_loot.target_type == AF.ZONE then
jcallahan@324 2336 table.insert(current_loot.list, ("money:%d"):format(loot_quantity))
jcallahan@324 2337 else
jcallahan@324 2338 current_loot.sources[source_guid] = current_loot.sources[source_guid] or {}
MMOSimca@367 2339 current_loot.sources[source_guid]["money"] = (current_loot.sources[source_guid]["money"] or 0) + loot_quantity
jcallahan@324 2340 guids_used[source_guid] = true
jcallahan@324 2341 end
MMOSimca@581 2342 elseif slot_type == ENUM_LOOTSLOTTYPE_CURRENCY then
jcallahan@324 2343 -- Same bug with GetLootSlotInfo() will screw up currency when it happens, so we won't process this slot's loot.
catherton@463 2344 if loot_link then
mmosimca@496 2345 local currency_id = CurrencyLinkToID(loot_link)
mmosimca@496 2346 Debug("GUID: %s - Type:ID: %s - Currency: %d - Amount: %d (%d)", loot_info[loot_index], source_key, currency_id, loot_info[loot_index + 1], slot_quantity)
jcallahan@324 2347 if current_loot.target_type == AF.ZONE then
mmosimca@496 2348 table.insert(current_loot.list, ("currency:%d:%d"):format(loot_quantity, currency_id))
jcallahan@324 2349 else
mmosimca@496 2350 local currency_token = ("currency:%d"):format(currency_id)
jcallahan@324 2351
jcallahan@324 2352 current_loot.sources[source_guid] = current_loot.sources[source_guid] or {}
MMOSimca@367 2353 current_loot.sources[source_guid][currency_token] = (current_loot.sources[source_guid][currency_token] or 0) + loot_quantity
jcallahan@324 2354 guids_used[source_guid] = true
jcallahan@324 2355 end
jcallahan@324 2356 else
catherton@463 2357 Debug("%s: Loot link is nil for loot slot %d of the entity with GUID %s and Type:ID: %s.", event_name, loot_slot, loot_info[loot_index], source_key)
jcallahan@324 2358 end
MMOSimca@581 2359 else
MMOSimca@581 2360 Debug("Unknown LootSlotType %d. GUID: %s - Type:ID: %s - Loot Slot: %d - Loot Link: %s", slot_type, loot_info[loot_index], source_key, loot_slot, loot_link or "")
jcallahan@308 2361 end
jcallahan@324 2362 else
jcallahan@324 2363 -- If this is nil, then the item's quantity could be wrong if loot_quantity is wrong, so we won't process this slot's loot.
catherton@463 2364 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 2365 end
jcallahan@75 2366 end
jcallahan@13 2367 end
jcallahan@13 2368 end
jcallahan@80 2369
jcallahan@81 2370 for guid in pairs(guids_used) do
jcallahan@217 2371 loot_guid_registry[current_loot.label][guid] = true
jcallahan@80 2372 end
jcallahan@13 2373 update_func()
jcallahan@1 2374 end
jcallahan@13 2375 end -- do-block
jcallahan@0 2376
jcallahan@0 2377
jcallahan@89 2378 function WDP:MAIL_SHOW(event_name)
MMOSimca@436 2379 WDP:StopChatLootRecording(event_name)
jcallahan@89 2380 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc"))
jcallahan@89 2381
jcallahan@89 2382 if not unit_idnum or unit_type ~= private.UNIT_TYPES.OBJECT then
jcallahan@89 2383 return
jcallahan@89 2384 end
jcallahan@89 2385 UpdateDBEntryLocation("objects", unit_idnum)
jcallahan@89 2386 end
jcallahan@89 2387
jcallahan@89 2388
jcallahan@44 2389 do
jcallahan@44 2390 local POINT_MATCH_PATTERNS = {
jcallahan@44 2391 ("^%s$"):format(_G.ITEM_REQ_ARENA_RATING:gsub("%%d", "(%%d+)")), -- May no longer be necessary
jcallahan@44 2392 ("^%s$"):format(_G.ITEM_REQ_ARENA_RATING_3V3:gsub("%%d", "(%%d+)")), -- May no longer be necessary
jcallahan@44 2393 ("^%s$"):format(_G.ITEM_REQ_ARENA_RATING_5V5:gsub("%%d", "(%%d+)")), -- May no longer be necessary
jcallahan@44 2394 ("^%s$"):format(_G.ITEM_REQ_ARENA_RATING_BG:gsub("%%d", "(%%d+)")),
jcallahan@44 2395 ("^%s$"):format(_G.ITEM_REQ_ARENA_RATING_3V3_BG:gsub("%%d", "(%%d+)")),
jcallahan@44 2396 }
jcallahan@5 2397
jcallahan@68 2398 local ITEM_REQ_REPUTATION_MATCH = "Requires (.-) %- (.*)"
jcallahan@87 2399 local ITEM_REQ_QUEST_MATCH1 = "Requires: .*"
jcallahan@87 2400 local ITEM_REQ_QUEST_MATCH2 = "Must have completed: .*"
jcallahan@68 2401
jcallahan@133 2402 local current_merchant
jcallahan@133 2403 local merchant_standing
jcallahan@133 2404
jcallahan@133 2405 function WDP:MERCHANT_CLOSED(event_name)
MMOSimca@436 2406 WDP:ResumeChatLootRecording(event_name)
jcallahan@133 2407 current_merchant = nil
jcallahan@133 2408 merchant_standing = nil
jcallahan@133 2409 end
jcallahan@133 2410
jcallahan@133 2411
jcallahan@89 2412 function WDP:UpdateMerchantItems(event_name)
jcallahan@144 2413 if not current_merchant or event_name == "MERCHANT_SHOW" then
MMOSimca@436 2414 WDP:StopChatLootRecording(event_name)
jcallahan@195 2415 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc"))
jcallahan@4 2416
jcallahan@171 2417 if not unit_idnum or not UnitTypeIsNPC(unit_type) then
jcallahan@133 2418 return
jcallahan@133 2419 end
jcallahan@331 2420 local _, faction_standing = UnitFactionStanding("npc")
jcallahan@331 2421 merchant_standing = faction_standing
jcallahan@133 2422 current_merchant = NPCEntry(unit_idnum)
jcallahan@133 2423 current_merchant.sells = current_merchant.sells or {}
jcallahan@44 2424 end
jcallahan@55 2425 local current_filters = _G.GetMerchantFilter()
jcallahan@57 2426 _G.SetMerchantFilter(_G.LE_LOOT_FILTER_ALL)
jcallahan@57 2427 _G.MerchantFrame_Update()
jcallahan@57 2428
jcallahan@150 2429 local num_items = _G.GetMerchantNumItems()
jcallahan@150 2430
jcallahan@44 2431 for item_index = 1, num_items do
jcallahan@44 2432 local _, _, copper_price, stack_size, num_available, _, extended_cost = _G.GetMerchantItemInfo(item_index)
jcallahan@44 2433 local item_id = ItemLinkToID(_G.GetMerchantItemLink(item_index))
jcallahan@5 2434
jcallahan@324 2435 DatamineTT:ClearLines()
jcallahan@324 2436 DatamineTT:SetMerchantItem(item_index)
jcallahan@324 2437
jcallahan@324 2438 if not item_id then
jcallahan@324 2439 local item_name, item_link = DatamineTT:GetItem()
jcallahan@324 2440 item_id = ItemLinkToID(item_link)
MMOSimca@354 2441 -- GetMerchantItemLink() still ocassionally fails as of Patch 6.0.2. It fails so badly that debug functions cause considerable slowdown.
jcallahan@324 2442 end
jcallahan@324 2443
jcallahan@44 2444 if item_id and item_id > 0 then
jcallahan@44 2445 local price_string = ActualCopperCost(copper_price, merchant_standing)
jcallahan@5 2446
jcallahan@68 2447 local num_lines = DatamineTT:NumLines()
jcallahan@68 2448
jcallahan@68 2449 for line_index = 1, num_lines do
jcallahan@68 2450 local current_line = _G["WDPDatamineTTTextLeft" .. line_index]
jcallahan@68 2451
jcallahan@68 2452 if not current_line then
jcallahan@68 2453 break
jcallahan@68 2454 end
jcallahan@68 2455 local faction, reputation = current_line:GetText():match(ITEM_REQ_REPUTATION_MATCH)
jcallahan@68 2456
jcallahan@68 2457 if faction or reputation then
jcallahan@68 2458 DBEntry("items", item_id).req_reputation = ("%s:%s"):format(faction:gsub("-", ""), reputation:upper())
jcallahan@68 2459 break
jcallahan@68 2460 end
jcallahan@68 2461 end
jcallahan@68 2462
jcallahan@87 2463 for line_index = 1, num_lines do
jcallahan@87 2464 local current_line = _G["WDPDatamineTTTextLeft" .. line_index]
jcallahan@87 2465
jcallahan@87 2466 if not current_line then
jcallahan@87 2467 break
jcallahan@87 2468 end
jcallahan@87 2469 local line_text = current_line:GetText()
jcallahan@87 2470 local quest_name = line_text:match(ITEM_REQ_QUEST_MATCH1) or line_text:match(ITEM_REQ_QUEST_MATCH2)
jcallahan@87 2471
jcallahan@87 2472 if quest_name then
jcallahan@87 2473 DBEntry("items", item_id).req_quest = ("%s"):format(quest_name:gsub("(.+): ", ""), quest_name)
jcallahan@87 2474 break
jcallahan@87 2475 end
jcallahan@87 2476 end
jcallahan@87 2477
jcallahan@44 2478 if extended_cost then
jcallahan@53 2479 local battleground_rating = 0
jcallahan@53 2480 local personal_rating = 0
jcallahan@53 2481 local required_season_amount
jcallahan@5 2482
jcallahan@68 2483 for line_index = 1, num_lines do
jcallahan@44 2484 local current_line = _G["WDPDatamineTTTextLeft" .. line_index]
jcallahan@5 2485
jcallahan@44 2486 if not current_line then
jcallahan@44 2487 break
jcallahan@44 2488 end
jcallahan@53 2489 required_season_amount = current_line:GetText():match("Requires earning a total of (%d+)\n(.-) for the season.")
jcallahan@5 2490
jcallahan@44 2491 for match_index = 1, #POINT_MATCH_PATTERNS do
jcallahan@44 2492 local match1, match2 = current_line:GetText():match(POINT_MATCH_PATTERNS[match_index])
jcallahan@53 2493 personal_rating = personal_rating + (match1 or 0)
jcallahan@53 2494 battleground_rating = battleground_rating + (match2 or 0)
jcallahan@5 2495
jcallahan@44 2496 if match1 or match2 then
jcallahan@44 2497 break
jcallahan@44 2498 end
jcallahan@44 2499 end
jcallahan@5 2500 end
jcallahan@44 2501 local currency_list = {}
jcallahan@44 2502 local item_count = _G.GetMerchantItemCostInfo(item_index)
jcallahan@50 2503
mmosimca@518 2504 -- Keeping this around in case Blizzard makes the two ratings (personal and battleground) diverge at some point
mmosimca@518 2505 --price_string = ("%s:%s:%s:%s"):format(price_string, battleground_rating, personal_rating, required_season_amount or 0)
jcallahan@53 2506 price_string = ("%s:%s:%s"):format(price_string, personal_rating, required_season_amount or 0)
jcallahan@5 2507
jcallahan@44 2508 for cost_index = 1, item_count do
jcallahan@324 2509 -- The third return (Blizz calls "currency_link") of GetMerchantItemCostItem only returns item links as of Patch 5.3.0.
MMOSimca@540 2510 local texture_id, amount_required, hyperlink, name = _G.GetMerchantItemCostItem(item_index, cost_index)
mmosimca@496 2511
MMOSimca@581 2512 if hyperlink then
MMOSimca@581 2513 -- Try to get item ID
MMOSimca@581 2514 local item_id = ItemLinkToID(hyperlink)
MMOSimca@581 2515
MMOSimca@581 2516 -- FUTURE: At some point, we should make the output from these two cases (item_id vs currency_id) slightly different, so that WoWDB doesn't have to guess if it is a currency or item
MMOSimca@581 2517 -- Handle cases when the additional cost is another item
MMOSimca@581 2518 if item_id and item_id > 0 then
MMOSimca@581 2519 currency_list[#currency_list + 1] = ("(%s:%d)"):format(amount_required, item_id)
MMOSimca@581 2520 else
MMOSimca@581 2521 -- Try to get currency ID
MMOSimca@581 2522 local currency_id = CurrencyLinkToID(hyperlink)
MMOSimca@581 2523 if currency_id and currency_id > 0 then
MMOSimca@581 2524 currency_list[#currency_list + 1] = ("(%s:%d)"):format(amount_required, currency_id)
MMOSimca@581 2525 else
MMOSimca@581 2526 Debug("UpdateMerchantItems: Failed to get item ID and failed to get currency ID for item index %d and cost index %d with hyperlink %s", item_index, cost_index, hyperlink)
MMOSimca@581 2527 end
MMOSimca@581 2528 end
MMOSimca@581 2529 end
jcallahan@44 2530 end
jcallahan@44 2531
jcallahan@44 2532 for currency_index = 1, #currency_list do
jcallahan@44 2533 price_string = ("%s:%s"):format(price_string, currency_list[currency_index])
jcallahan@5 2534 end
jcallahan@5 2535 end
jcallahan@133 2536 current_merchant.sells[item_id] = ("%s:%s:[%s]"):format(num_available, stack_size, price_string)
jcallahan@44 2537 end
jcallahan@44 2538 end
jcallahan@5 2539
jcallahan@44 2540 if _G.CanMerchantRepair() then
jcallahan@133 2541 current_merchant.can_repair = true
jcallahan@5 2542 end
jcallahan@57 2543 _G.SetMerchantFilter(current_filters)
jcallahan@57 2544 _G.MerchantFrame_Update()
jcallahan@4 2545 end
jcallahan@44 2546 end -- do-block
jcallahan@4 2547
jcallahan@89 2548
jcallahan@92 2549 function WDP:PET_BAR_UPDATE(event_name)
MMOSimca@336 2550 if not private.NON_LOOT_SPELL_LABELS[current_action.spell_label] then
jcallahan@25 2551 return
jcallahan@25 2552 end
jcallahan@34 2553 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("pet"))
jcallahan@25 2554
jcallahan@171 2555 if not unit_idnum or not UnitTypeIsNPC(unit_type) then
jcallahan@25 2556 return
jcallahan@25 2557 end
jcallahan@29 2558 NPCEntry(unit_idnum).mind_control = true
jcallahan@122 2559 table.wipe(current_action)
jcallahan@25 2560 end
jcallahan@25 2561
jcallahan@25 2562
MMOSimca@368 2563 -- This function produces data currently unused by wowdb.com, and it causes unneeded bloat in the raw lua DB.
MMOSimca@442 2564 --[[local LPJ = LibStub("LibPetJournal-2.0")
MMOSimca@442 2565 function WDP:PET_JOURNAL_LIST_UPDATE(event_name)
MMOSimca@346 2566 if DEBUGGING then
jcallahan@309 2567 return
jcallahan@309 2568 end
jcallahan@309 2569
jcallahan@115 2570 local num_pets = LPJ:NumPets()
jcallahan@115 2571
jcallahan@115 2572 for index, pet_id in LPJ:IteratePetIDs() do
jcallahan@115 2573 local _, _, is_owned, _, level, _, _, name, icon, pet_type, npc_id, _, _, is_wild = _G.C_PetJournal.GetPetInfoByIndex(index)
jcallahan@115 2574
jcallahan@115 2575 if is_owned then
jcallahan@115 2576 local health, max_health, attack, speed, rarity = _G.C_PetJournal.GetPetStats(pet_id)
jcallahan@115 2577
jcallahan@139 2578 if rarity then
jcallahan@139 2579 local rarity_name = _G["BATTLE_PET_BREED_QUALITY" .. rarity]
jcallahan@139 2580 local npc = NPCEntry(npc_id)
jcallahan@139 2581 npc.wild_pet = is_wild or nil
jcallahan@139 2582 npc.battle_pet_data = npc.battle_pet_data or {}
jcallahan@139 2583 npc.battle_pet_data[rarity_name] = npc.battle_pet_data[rarity_name] or {}
jcallahan@139 2584 npc.battle_pet_data[rarity_name]["level_" .. level] = npc.battle_pet_data[rarity_name]["level_" .. level] or {}
jcallahan@139 2585
jcallahan@139 2586 local data = npc.battle_pet_data[rarity_name]["level_" .. level]
jcallahan@139 2587 data.max_health = max_health
jcallahan@139 2588 data.attack = attack
jcallahan@139 2589 data.speed = speed
jcallahan@139 2590 end
jcallahan@115 2591 end
jcallahan@115 2592 end
MMOSimca@368 2593 end]]--
jcallahan@115 2594
jcallahan@115 2595
jcallahan@156 2596 function WDP:PLAYER_REGEN_DISABLED(event_name)
jcallahan@156 2597 private.in_combat = true
jcallahan@156 2598 end
jcallahan@156 2599
jcallahan@156 2600
jcallahan@156 2601 function WDP:PLAYER_REGEN_ENABLED(event_name)
jcallahan@156 2602 private.in_combat = nil
jcallahan@156 2603 end
jcallahan@156 2604
jcallahan@156 2605
jcallahan@118 2606 function WDP:PLAYER_TARGET_CHANGED(event_name)
jcallahan@215 2607 if not TargetedNPC() then
jcallahan@118 2608 return
jcallahan@2 2609 end
jcallahan@151 2610 current_action.target_type = AF.NPC
jcallahan@118 2611 self:UpdateTargetLocation()
jcallahan@118 2612 end
jcallahan@2 2613
jcallahan@89 2614
jcallahan@12 2615 do
jcallahan@12 2616 local function UpdateQuestJuncture(point)
jcallahan@12 2617 local unit_name = _G.UnitName("questnpc")
jcallahan@9 2618
jcallahan@12 2619 if not unit_name then
jcallahan@12 2620 return
jcallahan@12 2621 end
jcallahan@34 2622 local unit_type, unit_id = ParseGUID(_G.UnitGUID("questnpc"))
MMOSimca@351 2623 Debug("UpdateQuestJuncture: Updating quest juncture for %s.", ("%s:%d"):format(private.UNIT_TYPE_NAMES[unit_type], unit_id))
jcallahan@12 2624 if unit_type == private.UNIT_TYPES.OBJECT then
jcallahan@38 2625 UpdateDBEntryLocation("objects", unit_id)
jcallahan@12 2626 end
jcallahan@19 2627 local quest = DBEntry("quests", _G.GetQuestID())
jcallahan@12 2628 quest[point] = quest[point] or {}
MMOSimca@329 2629 quest[point][("%s:%d"):format(private.UNIT_TYPE_NAMES[unit_type], unit_id)] = true
jcallahan@24 2630
jcallahan@24 2631 return quest
jcallahan@12 2632 end
jcallahan@10 2633
jcallahan@12 2634
jcallahan@92 2635 function WDP:QUEST_COMPLETE(event_name)
jcallahan@97 2636 local quest = UpdateQuestJuncture("end")
catherton@465 2637
MMOSimca@446 2638 if not quest then
MMOSimca@446 2639 return
MMOSimca@446 2640 end
jcallahan@97 2641
jcallahan@112 2642 if ALLOWED_LOCALES[CLIENT_LOCALE] then
jcallahan@112 2643 quest.reward_text = ReplaceKeywords(_G.GetRewardText())
jcallahan@112 2644 end
jcallahan@67 2645 -- Make sure the quest NPC isn't erroneously recorded as giving reputation for quests which award it.
jcallahan@177 2646 ClearKilledNPC()
jcallahan@10 2647 end
jcallahan@10 2648
jcallahan@12 2649
jcallahan@92 2650 function WDP:QUEST_DETAIL(event_name)
jcallahan@24 2651 local quest = UpdateQuestJuncture("begin")
jcallahan@24 2652
jcallahan@46 2653 if not quest then
jcallahan@46 2654 return
jcallahan@46 2655 end
jcallahan@24 2656 quest.classes = quest.classes or {}
jcallahan@27 2657 quest.classes[PLAYER_CLASS] = true
jcallahan@24 2658
jcallahan@24 2659 quest.races = quest.races or {}
jcallahan@100 2660 quest.races[(PLAYER_RACE == "Pandaren") and ("%s_%s"):format(PLAYER_RACE, PLAYER_FACTION or "Neutral") or PLAYER_RACE] = true
jcallahan@10 2661 end
jcallahan@12 2662 end -- do-block
jcallahan@9 2663
jcallahan@9 2664
jcallahan@92 2665 function WDP:QUEST_LOG_UPDATE(event_name)
MMOSimca@581 2666 local selected_quest = _G.C_QuestLog.GetSelectedQuest() -- Save current selection to be restored when we're done.
jcallahan@36 2667 local entry_index, processed_quests = 1, 0
MMOSimca@581 2668 local _, num_quests = _G.C_QuestLog.GetNumQuestLogEntries()
jcallahan@36 2669
jcallahan@36 2670 while processed_quests <= num_quests do
MMOSimca@581 2671 local info = _G.C_QuestLog.GetInfo(entry_index)
MMOSimca@581 2672
MMOSimca@581 2673 if info.questID == 0 then
jcallahan@84 2674 processed_quests = processed_quests + 1
MMOSimca@581 2675 elseif not info.isHeader then
MMOSimca@581 2676 _G.C_QuestLog.SetSelectedQuest(entry_index);
MMOSimca@581 2677
MMOSimca@581 2678 local quest = DBEntry("quests", info.questID)
jcallahan@36 2679 quest.timer = _G.GetQuestLogTimeLeft()
MMOSimca@581 2680 quest.can_share = _G.C_QuestLog.IsPushableQuest(info.questID) and true or nil
jcallahan@36 2681 processed_quests = processed_quests + 1
jcallahan@36 2682 end
jcallahan@36 2683 entry_index = entry_index + 1
jcallahan@36 2684 end
MMOSimca@581 2685 _G.C_QuestLog.SetSelectedQuest(selected_quest)
jcallahan@4 2686 self:UnregisterEvent("QUEST_LOG_UPDATE")
jcallahan@4 2687 end
jcallahan@4 2688
jcallahan@4 2689
jcallahan@97 2690 function WDP:QUEST_PROGRESS(event_name)
jcallahan@112 2691 if not ALLOWED_LOCALES[CLIENT_LOCALE] then
jcallahan@112 2692 return
jcallahan@112 2693 end
jcallahan@97 2694 DBEntry("quests", _G.GetQuestID()).progress_text = ReplaceKeywords(_G.GetProgressText())
jcallahan@97 2695 end
jcallahan@97 2696
jcallahan@97 2697
jcallahan@92 2698 function WDP:UNIT_QUEST_LOG_CHANGED(event_name, unit_id)
jcallahan@4 2699 if unit_id ~= "player" then
jcallahan@4 2700 return
jcallahan@4 2701 end
jcallahan@4 2702 self:RegisterEvent("QUEST_LOG_UPDATE")
jcallahan@4 2703 end
jcallahan@4 2704
jcallahan@4 2705
MMOSimca@581 2706 -- This functionality is broken and should be rethought entirely in the wake of 10.0
MMOSimca@581 2707 --[[
jcallahan@92 2708 do
jcallahan@92 2709 local TRADESKILL_TOOLS = {
jcallahan@92 2710 Anvil = anvil_spell_ids,
jcallahan@92 2711 Forge = forge_spell_ids,
jcallahan@92 2712 }
jcallahan@92 2713
jcallahan@92 2714
jcallahan@167 2715 local function RegisterTools(tradeskill_name, tradeskill_index)
catherton@479 2716 local link = _G.C_TradeSkillUI.GetRecipeLink(tradeskill_index)
catherton@465 2717
MMOSimca@352 2718 if link then
MMOSimca@352 2719 local spell_id = tonumber(link:match("^|c%x%x%x%x%x%x%x%x|H%w+:(%d+)"))
catherton@479 2720 local required_tool = _G.C_TradeSkillUI.GetRecipeTools(tradeskill_index)
MMOSimca@352 2721
MMOSimca@352 2722 if required_tool then
MMOSimca@352 2723 for tool_name, registry in pairs(TRADESKILL_TOOLS) do
MMOSimca@352 2724 if required_tool:find(tool_name) then
MMOSimca@352 2725 registry[spell_id] = true
MMOSimca@352 2726 end
jcallahan@167 2727 end
jcallahan@167 2728 end
jcallahan@167 2729 end
jcallahan@167 2730 end
jcallahan@167 2731
jcallahan@167 2732
jcallahan@92 2733 function WDP:TRADE_SKILL_SHOW(event_name)
catherton@479 2734 local profession_name, prof_level = _G.C_TradeSkillUI.GetTradeSkillLine()
jcallahan@92 2735
jcallahan@92 2736 if profession_name == _G.UNKNOWN then
jcallahan@92 2737 return
jcallahan@92 2738 end
jcallahan@167 2739 TradeSkillExecutePer(RegisterTools)
jcallahan@92 2740 end
jcallahan@92 2741 end -- do-block
jcallahan@92 2742
jcallahan@92 2743
jcallahan@167 2744 function WDP:TRAINER_CLOSED(event_name)
jcallahan@167 2745 private.trainer_shown = nil
jcallahan@167 2746 end
jcallahan@167 2747
jcallahan@167 2748
jcallahan@92 2749 function WDP:TRAINER_SHOW(event_name)
jcallahan@232 2750 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc"))
jcallahan@164 2751 local trainer = NPCEntry(unit_idnum)
jcallahan@58 2752
jcallahan@164 2753 if not trainer then
jcallahan@58 2754 return
jcallahan@58 2755 end
jcallahan@331 2756 local _, trainer_standing = UnitFactionStanding("npc")
jcallahan@164 2757 trainer.teaches = trainer.teaches or {}
jcallahan@27 2758
jcallahan@167 2759 private.trainer_shown = true
jcallahan@167 2760
jcallahan@27 2761 -- Get the initial trainer filters
MMOSimca@344 2762 local available = _G.GetTrainerServiceTypeFilter("available") and 1 or 0
MMOSimca@344 2763 local unavailable = _G.GetTrainerServiceTypeFilter("unavailable") and 1 or 0
MMOSimca@344 2764 local used = _G.GetTrainerServiceTypeFilter("used") and 1 or 0
jcallahan@27 2765
jcallahan@27 2766 -- Clear the trainer filters
MMOSimca@344 2767 _G.SetTrainerServiceTypeFilter("available", 1)
MMOSimca@344 2768 _G.SetTrainerServiceTypeFilter("unavailable", 1)
MMOSimca@344 2769 _G.SetTrainerServiceTypeFilter("used", 1)
jcallahan@27 2770
jcallahan@27 2771 for index = 1, _G.GetNumTrainerServices(), 1 do
jcallahan@27 2772 local spell_name, rank_name, _, _, required_level = _G.GetTrainerServiceInfo(index)
jcallahan@27 2773
jcallahan@27 2774 if spell_name then
jcallahan@27 2775 DatamineTT:ClearLines()
jcallahan@27 2776 DatamineTT:SetTrainerService(index)
jcallahan@27 2777
atcaleb@570 2778 local _, spell_id = DatamineTT:GetSpell()
jcallahan@27 2779
jcallahan@43 2780 if spell_id then
jcallahan@164 2781 local class_professions = trainer.teaches[PLAYER_CLASS]
jcallahan@164 2782
jcallahan@164 2783 if not class_professions then
jcallahan@164 2784 trainer.teaches[PLAYER_CLASS] = {}
jcallahan@164 2785 class_professions = trainer.teaches[PLAYER_CLASS]
jcallahan@164 2786 end
jcallahan@43 2787 local profession, min_skill = _G.GetTrainerServiceSkillReq(index)
jcallahan@43 2788 profession = profession or "General"
jcallahan@43 2789
jcallahan@164 2790 local profession_skills = class_professions[profession]
jcallahan@43 2791
jcallahan@43 2792 if not profession_skills then
jcallahan@43 2793 class_professions[profession] = {}
jcallahan@43 2794 profession_skills = class_professions[profession]
jcallahan@43 2795 end
jcallahan@173 2796 profession_skills[spell_id] = ("%d:%d:%d"):format(required_level, min_skill, _G.GetTrainerServiceCost(index))
jcallahan@27 2797 end
jcallahan@27 2798 end
jcallahan@27 2799 end
jcallahan@27 2800 -- Reset the filters to what they were before
MMOSimca@344 2801 _G.SetTrainerServiceTypeFilter("available", available or 0)
MMOSimca@344 2802 _G.SetTrainerServiceTypeFilter("unavailable", unavailable or 0)
MMOSimca@344 2803 _G.SetTrainerServiceTypeFilter("used", used or 0)
jcallahan@27 2804 end
MMOSimca@581 2805 ]]--
jcallahan@27 2806
jcallahan@27 2807
jcallahan@1 2808 function WDP:UNIT_SPELLCAST_SENT(event_name, unit_id, spell_name, spell_rank, target_name, spell_line)
jcallahan@1 2809 if private.tracked_line or unit_id ~= "player" then
jcallahan@1 2810 return
jcallahan@1 2811 end
jcallahan@1 2812 local spell_label = private.SPELL_LABELS_BY_NAME[spell_name]
jcallahan@1 2813
jcallahan@1 2814 if not spell_label then
jcallahan@1 2815 return
jcallahan@1 2816 end
jcallahan@306 2817
MMOSimca@343 2818 Debug("UNIT_SPELLCAST_SENT: %s was cast.", spell_name)
jcallahan@150 2819 local item_name, item_link = _G.GameTooltip:GetItem()
jcallahan@150 2820 local unit_name, unit_id = _G.GameTooltip:GetUnit()
jcallahan@1 2821
jcallahan@150 2822 if not unit_name and _G.UnitName("target") == target_name then
jcallahan@150 2823 unit_name = target_name
jcallahan@150 2824 unit_id = "target"
jcallahan@1 2825 end
jcallahan@1 2826 local spell_flags = private.SPELL_FLAGS_BY_LABEL[spell_label]
jcallahan@28 2827 local zone_name, area_id, x, y, map_level, instance_token = CurrentLocationData()
MMOSimca@328 2828 if not (zone_name and area_id and x and y and map_level) then
mmosimca@508 2829 if not (_G.IsInInstance()) then
mmosimca@508 2830 Debug("%s: Missing current location data - %s, %s, %s, %s, %s.", event_name, tostring(zone_name), tostring(area_id), tostring(x), tostring(y), tostring(map_level))
mmosimca@508 2831 end
MMOSimca@328 2832 return
MMOSimca@328 2833 end
jcallahan@28 2834
jcallahan@205 2835 table.wipe(current_action)
jcallahan@122 2836 current_action.instance_token = instance_token
jcallahan@122 2837 current_action.map_level = map_level
jcallahan@122 2838 current_action.x = x
jcallahan@122 2839 current_action.y = y
jcallahan@122 2840 current_action.zone_data = ("%s:%d"):format(zone_name, area_id)
jcallahan@122 2841 current_action.spell_label = spell_label
jcallahan@105 2842
jcallahan@105 2843 if not private.NON_LOOT_SPELL_LABELS[spell_label] then
jcallahan@122 2844 current_action.loot_label = spell_label:lower()
jcallahan@105 2845 end
jcallahan@1 2846
jcallahan@151 2847 if unit_name and unit_name == target_name and not item_name then
jcallahan@16 2848 if bit.band(spell_flags, AF.NPC) == AF.NPC then
jcallahan@150 2849 if not unit_id or unit_name ~= target_name then
jcallahan@16 2850 return
jcallahan@16 2851 end
jcallahan@123 2852 current_action.target_type = AF.NPC
jcallahan@16 2853 end
jcallahan@16 2854 elseif bit.band(spell_flags, AF.ITEM) == AF.ITEM then
jcallahan@123 2855 current_action.target_type = AF.ITEM
jcallahan@16 2856
jcallahan@150 2857 if item_name and item_name == target_name then
jcallahan@150 2858 current_action.identifier = ItemLinkToID(item_link)
jcallahan@16 2859 elseif target_name and target_name ~= "" then
jcallahan@331 2860 local _, item_link = _G.GetItemInfo(target_name)
jcallahan@331 2861 current_action.identifier = ItemLinkToID(item_link)
jcallahan@16 2862 end
jcallahan@150 2863 elseif not item_name and not unit_name then
jcallahan@1 2864 if bit.band(spell_flags, AF.OBJECT) == AF.OBJECT then
jcallahan@17 2865 if target_name == "" then
jcallahan@17 2866 return
jcallahan@17 2867 end
jcallahan@122 2868 current_action.object_name = target_name
jcallahan@123 2869 current_action.target_type = AF.OBJECT
jcallahan@1 2870 elseif bit.band(spell_flags, AF.ZONE) == AF.ZONE then
jcallahan@123 2871 current_action.target_type = AF.ZONE
jcallahan@1 2872 end
jcallahan@1 2873 end
jcallahan@1 2874 private.tracked_line = spell_line
jcallahan@0 2875 end
jcallahan@0 2876
jcallahan@94 2877
MMOSimca@393 2878 -- Triggered by bonus roll prompts, disenchant prompts, and in a few other rare circumstances
jcallahan@312 2879 function WDP:SPELL_CONFIRMATION_PROMPT(event_name, spell_id, confirm_type, text, duration, currency_id_cost)
jcallahan@306 2880 if private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id] then
jcallahan@306 2881 ClearKilledBossID()
jcallahan@306 2882 ClearLootToastContainerID()
MMOSimca@387 2883 raid_boss_id = private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id]
jcallahan@306 2884 else
MMOSimca@336 2885 Debug("%s: Spell ID %d is not a known raid boss 'Bonus' spell.", event_name, spell_id)
jcallahan@306 2886 return
jcallahan@1 2887 end
jcallahan@306 2888
jcallahan@324 2889 -- Assign existing loot data to boss if it exists
jcallahan@307 2890 if loot_toast_data then
MMOSimca@427 2891 local npc = NPCEntry(raid_boss_id)
MMOSimca@427 2892 if npc then
MMOSimca@427 2893 -- Create needed npc fields if required
MMOSimca@427 2894 local loot_label = "drops"
MMOSimca@427 2895 local encounter_data = npc:EncounterData(InstanceDifficultyToken())
MMOSimca@427 2896 encounter_data[loot_label] = encounter_data[loot_label] or {}
MMOSimca@427 2897 encounter_data.loot_counts = encounter_data.loot_counts or {}
MMOSimca@427 2898
MMOSimca@427 2899 for index = 1, #loot_toast_data do
MMOSimca@427 2900 local data = loot_toast_data[index]
MMOSimca@427 2901 local loot_type = data[1]
MMOSimca@427 2902 local hyperlink = data[2]
MMOSimca@427 2903 local quantity = data[3]
MMOSimca@427 2904
MMOSimca@427 2905 if loot_type == "item" then
MMOSimca@427 2906 local item_id = ItemLinkToID(hyperlink)
MMOSimca@427 2907 Debug("%s: Assigned stored item loot data - %s - %d:%d", event_name, hyperlink, item_id, quantity)
MMOSimca@427 2908 table.insert(encounter_data[loot_label], ("%d:%d"):format(item_id, quantity))
MMOSimca@427 2909 elseif loot_type == "money" then
MMOSimca@427 2910 Debug("%s: Assigned stored money loot data - money:%d", event_name, quantity)
MMOSimca@427 2911 table.insert(encounter_data[loot_label], ("money:%d"):format(quantity))
MMOSimca@427 2912 elseif loot_type == "currency" then
mmosimca@496 2913 local currency_id = CurrencyLinkToID(hyperlink)
mmosimca@496 2914 Debug("%s: Assigned stored currency loot data - %s - currency:%d (%d)", event_name, hyperlink, currency_id, quantity)
mmosimca@496 2915 table.insert(encounter_data[loot_label], ("currency:%d:%d"):format(quantity, currency_id))
MMOSimca@427 2916 end
jcallahan@317 2917 end
jcallahan@317 2918
MMOSimca@427 2919 if not boss_loot_toasting[raid_boss_id] then
MMOSimca@427 2920 encounter_data.loot_counts[loot_label] = (encounter_data.loot_counts[loot_label] or 0) + 1
MMOSimca@427 2921 boss_loot_toasting[raid_boss_id] = true -- Do not count further loots until timer expires or another boss is killed
jcallahan@307 2922 end
MMOSimca@427 2923 else
MMOSimca@427 2924 Debug("%s: NPC is nil, but we have stored loot data...", event_name)
jcallahan@307 2925 end
jcallahan@307 2926 end
jcallahan@307 2927
jcallahan@307 2928 ClearLootToastData()
MMOSimca@427 2929 killed_boss_id_timer_handle = C_Timer.NewTimer(5, ClearKilledBossID)
jcallahan@306 2930 end
jcallahan@306 2931
jcallahan@306 2932
jcallahan@306 2933 function WDP:UNIT_SPELLCAST_SUCCEEDED(event_name, unit_id, spell_name, spell_rank, spell_line, spell_id)
jcallahan@306 2934 if unit_id ~= "player" then
jcallahan@306 2935 return
jcallahan@306 2936 end
jcallahan@306 2937 private.tracked_line = nil
jcallahan@306 2938 private.previous_spell_id = spell_id
jcallahan@306 2939
MMOSimca@393 2940 -- For spells cast when Logging
MMOSimca@345 2941 if private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[spell_id] then
MMOSimca@345 2942 last_timber_spell_id = spell_id
MMOSimca@351 2943 UpdateDBEntryLocation("objects", ("OPENING:%s"):format(private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[spell_id]))
MMOSimca@345 2944 return
MMOSimca@345 2945 end
MMOSimca@345 2946
MMOSimca@393 2947 -- For spells cast by items that always trigger loot toasts
MMOSimca@381 2948 if private.LOOT_TOAST_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] then
jcallahan@306 2949 ClearKilledBossID()
jcallahan@306 2950 ClearLootToastContainerID()
jcallahan@307 2951 ClearLootToastData()
jcallahan@306 2952
MMOSimca@387 2953 loot_toast_container_id = private.LOOT_TOAST_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id]
MMOSimca@383 2954 loot_toast_container_timer_handle = C_Timer.NewTimer(1, ClearLootToastContainerID) -- we need to assign a handle here to cancel it later
MMOSimca@345 2955 return
jcallahan@306 2956 end
jcallahan@306 2957
MMOSimca@393 2958 -- For spells cast by items that don't usually trigger loot toasts
catherton@473 2959 if not block_chat_loot_data and (private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] or (private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_BY_CLASS_ID_MAP[spell_id] and private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_BY_CLASS_ID_MAP[spell_id][PLAYER_CLASS_ID])) then
MMOSimca@347 2960 -- Set up timer
MMOSimca@393 2961 ClearChatLootData()
MMOSimca@393 2962 Debug("%s: Beginning chat-based loot timer for spellID %d", event_name, spell_id)
MMOSimca@411 2963 chat_loot_timer_handle = C_Timer.NewTimer(1.5, ClearChatLootData)
catherton@473 2964 if (private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_BY_CLASS_ID_MAP[spell_id] and private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_BY_CLASS_ID_MAP[spell_id][PLAYER_CLASS_ID]) then
catherton@473 2965 chat_loot_data.identifier = private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_BY_CLASS_ID_MAP[spell_id][PLAYER_CLASS_ID]
MMOSimca@454 2966 else
MMOSimca@454 2967 chat_loot_data.identifier = private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id]
MMOSimca@454 2968 end
MMOSimca@347 2969 return
MMOSimca@347 2970 end
MMOSimca@347 2971
mmosimca@520 2972 -- For Ephemeral Crystals (uses a combination of mouseover text and a 'Update Interactions' spell cast to detect the object - this is incredibly hacky but there is no alternative)
mmosimca@520 2973 local text = _G["GameTooltipTextLeft1"] and _G["GameTooltipTextLeft1"]:GetText() or nil
mmosimca@520 2974 if spell_id == SPELL_ID_UPDATE_INTERACTIONS and text and text == "Ephemeral Crystal" then
mmosimca@522 2975 Debug("%s: Detected location for an Ephemeral Crystal.", event_name)
mmosimca@520 2976 for index = 1, #private.EPHEMERAL_CRYSTAL_OBJECT_IDS do
mmosimca@520 2977 UpdateDBEntryLocation("objects", private.EPHEMERAL_CRYSTAL_OBJECT_IDS[index])
mmosimca@520 2978 end
mmosimca@520 2979 end
mmosimca@520 2980
jcallahan@306 2981 if anvil_spell_ids[spell_id] then
jcallahan@306 2982 UpdateDBEntryLocation("objects", OBJECT_ID_ANVIL)
jcallahan@306 2983 elseif forge_spell_ids[spell_id] then
jcallahan@306 2984 UpdateDBEntryLocation("objects", OBJECT_ID_FORGE)
jcallahan@306 2985 elseif spell_name:match("^Harvest.+") then
jcallahan@306 2986 killed_npc_id = current_target_id
MMOSimca@343 2987 private.harvesting = true -- Used to track which NPCs can be harvested (can we get this from CreatureCache instead?)
jcallahan@306 2988 end
jcallahan@306 2989 end
jcallahan@0 2990
jcallahan@90 2991
jcallahan@1 2992 function WDP:HandleSpellFailure(event_name, unit_id, spell_name, spell_rank, spell_line, spell_id)
jcallahan@1 2993 if unit_id ~= "player" then
jcallahan@1 2994 return
jcallahan@1 2995 end
jcallahan@0 2996
jcallahan@1 2997 if private.tracked_line == spell_line then
jcallahan@1 2998 private.tracked_line = nil
jcallahan@1 2999 end
jcallahan@147 3000 table.wipe(current_action)
jcallahan@0 3001 end
jcallahan@90 3002
jcallahan@90 3003
jcallahan@90 3004 do
jcallahan@90 3005 local function SetUnitField(field, required_type)
jcallahan@90 3006 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc"))
jcallahan@90 3007
jcallahan@90 3008 if not unit_idnum or (required_type and unit_type ~= required_type) then
jcallahan@90 3009 return
jcallahan@90 3010 end
jcallahan@90 3011
jcallahan@171 3012 if UnitTypeIsNPC(unit_type) then
jcallahan@90 3013 NPCEntry(unit_idnum)[field] = true
jcallahan@90 3014 elseif unit_type == private.UNIT_TYPES.OBJECT then
jcallahan@90 3015 DBEntry("objects", unit_idnum)[field] = true
jcallahan@93 3016 UpdateDBEntryLocation("objects", unit_idnum)
jcallahan@90 3017 end
jcallahan@90 3018 end
jcallahan@90 3019
jcallahan@90 3020
jcallahan@90 3021 function WDP:AUCTION_HOUSE_SHOW(event_name)
MMOSimca@436 3022 WDP:StopChatLootRecording(event_name)
jcallahan@90 3023 SetUnitField("auctioneer", private.UNIT_TYPES.NPC)
jcallahan@90 3024 end
jcallahan@90 3025
jcallahan@90 3026
MMOSimca@581 3027 -- manager_frame_id is 4 in case we need to merge this into that event
jcallahan@90 3028 function WDP:BANKFRAME_OPENED(event_name)
MMOSimca@436 3029 WDP:StopChatLootRecording(event_name)
jcallahan@90 3030 SetUnitField("banker", private.UNIT_TYPES.NPC)
jcallahan@90 3031 end
jcallahan@90 3032
jcallahan@90 3033
jcallahan@90 3034 function WDP:BATTLEFIELDS_SHOW(event_name)
jcallahan@90 3035 SetUnitField("battlemaster", private.UNIT_TYPES.NPC)
jcallahan@90 3036 end
jcallahan@90 3037
jcallahan@90 3038
jcallahan@323 3039 local GOSSIP_SHOW_FUNCS = {
jcallahan@323 3040 [private.UNIT_TYPES.NPC] = function(unit_idnum)
MMOSimca@581 3041 local gossip_options = { _G.C_GossipInfo.GetOptions() }
jcallahan@323 3042
jcallahan@323 3043 for index = 2, #gossip_options, 2 do
jcallahan@323 3044 if gossip_options[index] == "binder" then
jcallahan@323 3045 SetUnitField("innkeeper", private.UNIT_TYPES.NPC)
jcallahan@323 3046 return
jcallahan@323 3047 end
jcallahan@323 3048 end
jcallahan@323 3049 end,
jcallahan@323 3050 [private.UNIT_TYPES.OBJECT] = function(unit_idnum)
jcallahan@323 3051 UpdateDBEntryLocation("objects", unit_idnum)
jcallahan@323 3052 end,
jcallahan@323 3053 }
jcallahan@323 3054
jcallahan@323 3055
MMOSimca@581 3056 -- manager_frame_id is 3 in case we need to merge this into that event
jcallahan@92 3057 function WDP:GOSSIP_SHOW(event_name)
MMOSimca@436 3058 WDP:StopChatLootRecording(event_name)
jcallahan@323 3059 local unit_type, unit_idnum = ParseGUID(_G.UnitGUID("npc"))
jcallahan@323 3060 if not unit_idnum then
jcallahan@323 3061 return
jcallahan@323 3062 end
jcallahan@323 3063
jcallahan@323 3064 if GOSSIP_SHOW_FUNCS[unit_type] then
jcallahan@323 3065 GOSSIP_SHOW_FUNCS[unit_type](unit_idnum)
jcallahan@90 3066 end
jcallahan@90 3067 end
jcallahan@90 3068
jcallahan@90 3069
MMOSimca@581 3070 -- manager_frame_id is 10 in case we need to merge this into that event
jcallahan@93 3071 function WDP:GUILDBANKFRAME_OPENED(event_name)
MMOSimca@436 3072 WDP:StopChatLootRecording(event_name)
jcallahan@93 3073 SetUnitField("guild_bank", private.UNIT_TYPES.OBJECT)
jcallahan@93 3074 end
jcallahan@93 3075
jcallahan@93 3076
MMOSimca@581 3077 -- manager_frame_id is 6 in case we need to merge this into that event
jcallahan@90 3078 function WDP:TAXIMAP_OPENED(event_name)
jcallahan@90 3079 SetUnitField("flight_master", private.UNIT_TYPES.NPC)
jcallahan@90 3080 end
jcallahan@90 3081
jcallahan@90 3082
MMOSimca@581 3083 function WDP:PLAYER_INTERACTION_MANAGER_FRAME_SHOW(event_name, manager_frame_id)
MMOSimca@581 3084 if not manager_frame_id then
MMOSimca@581 3085 return
MMOSimca@581 3086 elseif manager_frame_id == 24 then
MMOSimca@581 3087 SetUnitField("transmogrifier", private.UNIT_TYPES.NPC)
MMOSimca@581 3088 elseif manager_frame_id == 26 then
MMOSimca@581 3089 WDP:StopChatLootRecording(event_name)
MMOSimca@581 3090 SetUnitField("void_storage", private.UNIT_TYPES.NPC)
MMOSimca@581 3091 elseif manager_frame_id == 53 then
MMOSimca@581 3092 WDP:StopChatLootRecording(event_name)
MMOSimca@581 3093 SetUnitField("item_upgrade_master", private.UNIT_TYPES.NPC)
MMOSimca@581 3094 end
jcallahan@90 3095 end
jcallahan@90 3096 end -- do-block