Mercurial > wow > wowdb-profiler
comparison Main.lua @ 393:7d0ad2573092
Added support for collecting loot from item containers with no loot window and no spells cast.
| author | MMOSimca <MMOSimca@gmail.com> |
|---|---|
| date | Thu, 18 Dec 2014 21:38:11 -0500 |
| parents | b00732fa9352 |
| children | 930da8078de7 |
comparison
equal
deleted
inserted
replaced
| 392:f1952ed33a16 | 393:7d0ad2573092 |
|---|---|
| 191 y = nil, | 191 y = nil, |
| 192 zone_data = nil, | 192 zone_data = nil, |
| 193 } | 193 } |
| 194 | 194 |
| 195 | 195 |
| 196 -- Timer prototypes | |
| 197 local ClearKilledNPC, ClearKilledBossID, ClearLootToastContainerID, ClearLootToastData, ClearChatLootData | |
| 198 | |
| 199 | |
| 196 -- HELPERS ------------------------------------------------------------ | 200 -- HELPERS ------------------------------------------------------------ |
| 197 | 201 |
| 198 local function Debug(message, ...) | 202 local function Debug(message, ...) |
| 199 if not DEBUGGING or not message then | 203 if not DEBUGGING or not message then |
| 200 return | 204 return |
| 210 else | 214 else |
| 211 _G.print(message) | 215 _G.print(message) |
| 212 end | 216 end |
| 213 end | 217 end |
| 214 | 218 |
| 215 | |
| 216 local TradeSkillExecutePer | |
| 217 do | |
| 218 local header_list = {} | |
| 219 | |
| 220 function TradeSkillExecutePer(iter_func) | |
| 221 if not _G.TradeSkillFrame or not _G.TradeSkillFrame:IsVisible() then | |
| 222 return | |
| 223 end | |
| 224 -- Clear the search box focus so the scan will have correct results. | |
| 225 local search_box = _G.TradeSkillFrameSearchBox | |
| 226 search_box:SetText("") | |
| 227 | |
| 228 _G.TradeSkillSearch_OnTextChanged(search_box) | |
| 229 search_box:ClearFocus() | |
| 230 search_box:GetScript("OnEditFocusLost")(search_box) | |
| 231 | |
| 232 table.wipe(header_list) | |
| 233 | |
| 234 -- Save the current state of the TradeSkillFrame so it can be restored after we muck with it. | |
| 235 local have_materials = _G.TradeSkillFrame.filterTbl.hasMaterials | |
| 236 local have_skillup = _G.TradeSkillFrame.filterTbl.hasSkillUp | |
| 237 | |
| 238 if have_materials then | |
| 239 _G.TradeSkillFrame.filterTbl.hasMaterials = false | |
| 240 _G.TradeSkillOnlyShowMakeable(false) | |
| 241 end | |
| 242 | |
| 243 if have_skillup then | |
| 244 _G.TradeSkillFrame.filterTbl.hasSkillUp = false | |
| 245 _G.TradeSkillOnlyShowSkillUps(false) | |
| 246 end | |
| 247 _G.SetTradeSkillInvSlotFilter(0, true, true) | |
| 248 _G.TradeSkillUpdateFilterBar() | |
| 249 _G.TradeSkillFrame_Update() | |
| 250 | |
| 251 -- Expand all headers so we can see all the recipes there are | |
| 252 for tradeskill_index = 1, _G.GetNumTradeSkills() do | |
| 253 local name, tradeskill_type, _, is_expanded = _G.GetTradeSkillInfo(tradeskill_index) | |
| 254 | |
| 255 if tradeskill_type == "header" or tradeskill_type == "subheader" then | |
| 256 if not is_expanded then | |
| 257 header_list[name] = true | |
| 258 _G.ExpandTradeSkillSubClass(tradeskill_index) | |
| 259 end | |
| 260 elseif iter_func(name, tradeskill_index) then | |
| 261 break | |
| 262 end | |
| 263 end | |
| 264 | |
| 265 -- Restore the state of the things we changed. | |
| 266 for tradeskill_index = 1, _G.GetNumTradeSkills() do | |
| 267 local name, tradeskill_type, _, is_expanded = _G.GetTradeSkillInfo(tradeskill_index) | |
| 268 | |
| 269 if header_list[name] then | |
| 270 _G.CollapseTradeSkillSubClass(tradeskill_index) | |
| 271 end | |
| 272 end | |
| 273 _G.TradeSkillFrame.filterTbl.hasMaterials = have_materials | |
| 274 _G.TradeSkillOnlyShowMakeable(have_materials) | |
| 275 _G.TradeSkillFrame.filterTbl.hasSkillUp = have_skillup | |
| 276 _G.TradeSkillOnlyShowSkillUps(have_skillup) | |
| 277 | |
| 278 _G.TradeSkillUpdateFilterBar() | |
| 279 _G.TradeSkillFrame_Update() | |
| 280 end | |
| 281 end -- do-block | |
| 282 | |
| 283 | |
| 284 local ActualCopperCost | |
| 285 do | |
| 286 local BARTERING_SPELL_ID = 83964 | |
| 287 | |
| 288 local STANDING_DISCOUNTS = { | |
| 289 HATED = 0, | |
| 290 HOSTILE = 0, | |
| 291 UNFRIENDLY = 0, | |
| 292 NEUTRAL = 0, | |
| 293 FRIENDLY = 0.05, | |
| 294 HONORED = 0.1, | |
| 295 REVERED = 0.15, | |
| 296 EXALTED = 0.2, | |
| 297 } | |
| 298 | |
| 299 | |
| 300 function ActualCopperCost(copper_cost, rep_standing) | |
| 301 if not copper_cost or copper_cost == 0 then | |
| 302 return 0 | |
| 303 end | |
| 304 local modifier = 1 | |
| 305 | |
| 306 if _G.IsSpellKnown(BARTERING_SPELL_ID) then | |
| 307 modifier = modifier - 0.1 | |
| 308 end | |
| 309 | |
| 310 if rep_standing then | |
| 311 if PLAYER_RACE == "Goblin" then | |
| 312 modifier = modifier - STANDING_DISCOUNTS["EXALTED"] | |
| 313 elseif STANDING_DISCOUNTS[rep_standing] then | |
| 314 modifier = modifier - STANDING_DISCOUNTS[rep_standing] | |
| 315 end | |
| 316 end | |
| 317 return math.floor(copper_cost / modifier) | |
| 318 end | |
| 319 end -- do-block | |
| 320 | |
| 321 | |
| 322 local function InstanceDifficultyToken() | |
| 323 local _, instance_type, instance_difficulty, _, _, _, is_dynamic = _G.GetInstanceInfo() | |
| 324 | |
| 325 if not instance_type or instance_type == "" then | |
| 326 instance_type = "NONE" | |
| 327 end | |
| 328 return ("%s:%d:%s"):format(instance_type:upper(), instance_difficulty, tostring(is_dynamic)) | |
| 329 end | |
| 330 | |
| 331 | |
| 332 local function DBEntry(data_type, unit_id) | |
| 333 if not data_type or not unit_id then | |
| 334 return | |
| 335 end | |
| 336 local category = global_db[data_type] | |
| 337 | |
| 338 if not category then | |
| 339 category = {} | |
| 340 global_db[data_type] = category | |
| 341 end | |
| 342 local unit = category[unit_id] | |
| 343 | |
| 344 if not unit then | |
| 345 unit = {} | |
| 346 category[unit_id] = unit | |
| 347 end | |
| 348 return unit | |
| 349 end | |
| 350 | |
| 351 private.DBEntry = DBEntry | |
| 352 | |
| 353 local NPCEntry | |
| 354 do | |
| 355 local npc_prototype = {} | |
| 356 local npc_meta = { | |
| 357 __index = npc_prototype | |
| 358 } | |
| 359 | |
| 360 function NPCEntry(identifier) | |
| 361 local npc = DBEntry("npcs", identifier) | |
| 362 return npc and _G.setmetatable(npc, npc_meta) or nil | |
| 363 end | |
| 364 | |
| 365 function npc_prototype:EncounterData(difficulty_token) | |
| 366 self.encounter_data = self.encounter_data or {} | |
| 367 self.encounter_data[difficulty_token] = self.encounter_data[difficulty_token] or {} | |
| 368 self.encounter_data[difficulty_token].stats = self.encounter_data[difficulty_token].stats or {} | |
| 369 | |
| 370 return self.encounter_data[difficulty_token] | |
| 371 end | |
| 372 end | |
| 373 | |
| 374 | |
| 375 local function CurrentLocationData() | |
| 376 if _G.GetCurrentMapAreaID() ~= current_area_id then | |
| 377 return _G.GetRealZoneText(), current_area_id, 0, 0, 0, InstanceDifficultyToken() | |
| 378 end | |
| 379 local map_level = _G.GetCurrentMapDungeonLevel() or 0 | |
| 380 local x, y = _G.GetPlayerMapPosition("player") | |
| 381 | |
| 382 x = x or 0 | |
| 383 y = y or 0 | |
| 384 | |
| 385 if x == 0 and y == 0 then | |
| 386 for level_index = 1, _G.GetNumDungeonMapLevels() do | |
| 387 _G.SetDungeonMapLevel(level_index) | |
| 388 x, y = _G.GetPlayerMapPosition("player") | |
| 389 | |
| 390 if x and y and (x > 0 or y > 0) then | |
| 391 _G.SetDungeonMapLevel(map_level) | |
| 392 map_level = level_index | |
| 393 break | |
| 394 end | |
| 395 end | |
| 396 end | |
| 397 | |
| 398 if _G.DungeonUsesTerrainMap() then | |
| 399 map_level = map_level - 1 | |
| 400 end | |
| 401 local x = _G.floor(x * 1000) | |
| 402 local y = _G.floor(y * 1000) | |
| 403 | |
| 404 if x % 2 ~= 0 then | |
| 405 x = x + 1 | |
| 406 end | |
| 407 | |
| 408 if y % 2 ~= 0 then | |
| 409 y = y + 1 | |
| 410 end | |
| 411 return _G.GetRealZoneText(), current_area_id, x, y, map_level, InstanceDifficultyToken() | |
| 412 end | |
| 413 | |
| 414 | |
| 415 local function CurrencyLinkToTexture(currency_link) | |
| 416 if not currency_link then | |
| 417 return | |
| 418 end | |
| 419 local _, _, texture_path = _G.GetCurrencyInfo(tonumber(currency_link:match("currency:(%d+)"))) | |
| 420 return texture_path:match("[^\\]+$"):lower() | |
| 421 end | |
| 422 | |
| 423 | |
| 424 local function ItemLinkToID(item_link) | |
| 425 if not item_link then | |
| 426 return | |
| 427 end | |
| 428 return tonumber(item_link:match("item:(%d+)")) | |
| 429 end | |
| 430 | |
| 431 private.ItemLinkToID = ItemLinkToID | |
| 432 | |
| 433 local function UnitTypeIsNPC(unit_type) | |
| 434 return unit_type == private.UNIT_TYPES.NPC or unit_type == private.UNIT_TYPES.VEHICLE | |
| 435 end | |
| 436 | |
| 437 | |
| 438 local ParseGUID | |
| 439 do | |
| 440 local UNIT_TYPES = private.UNIT_TYPES | |
| 441 | |
| 442 local NPC_ID_MAPPING = { | |
| 443 [62164] = 63191, -- Garalon | |
| 444 } | |
| 445 | |
| 446 | |
| 447 local function MatchUnitTypes(unit_type_name) | |
| 448 if not unit_type_name then | |
| 449 return UNIT_TYPES.UNKNOWN | |
| 450 end | |
| 451 | |
| 452 for def, text in next, UNIT_TYPES do | |
| 453 if unit_type_name == text then | |
| 454 return UNIT_TYPES[def] | |
| 455 end | |
| 456 end | |
| 457 return UNIT_TYPES.UNKNOWN | |
| 458 end | |
| 459 | |
| 460 | |
| 461 function ParseGUID(guid) | |
| 462 if not guid then | |
| 463 return | |
| 464 end | |
| 465 | |
| 466 -- We might want to use some of this new information later, but leaving the returns alone for now | |
| 467 local unit_type_name, unk_id1, server_id, instance_id, unk_id2, unit_idnum, spawn_id = ("-"):split(guid) | |
| 468 | |
| 469 local unit_type = MatchUnitTypes(unit_type_name) | |
| 470 if unit_type ~= UNIT_TYPES.PLAYER and unit_type ~= UNIT_TYPES.PET and unit_type ~= UNIT_TYPES.ITEM then | |
| 471 | |
| 472 local id_mapping = NPC_ID_MAPPING[unit_idnum] | |
| 473 | |
| 474 if id_mapping and UnitTypeIsNPC(unit_type) then | |
| 475 unit_idnum = id_mapping | |
| 476 end | |
| 477 return unit_type, unit_idnum | |
| 478 end | |
| 479 return unit_type | |
| 480 end | |
| 481 | |
| 482 private.ParseGUID = ParseGUID | |
| 483 end -- do-block | |
| 484 | |
| 485 | |
| 486 local UpdateDBEntryLocation | |
| 487 do | |
| 488 -- Fishing node coordinate code based on code in GatherMate2 with permission from Kagaro. | |
| 489 local function FishingCoordinates(x, y, yard_width, yard_height) | |
| 490 local facing = _G.GetPlayerFacing() | |
| 491 | |
| 492 if not facing then | |
| 493 return x, y | |
| 494 end | |
| 495 local rad = facing + math.pi | |
| 496 return x + math.sin(rad) * 15 / yard_width, y + math.cos(rad) * 15 / yard_height | |
| 497 end | |
| 498 | |
| 499 | |
| 500 function UpdateDBEntryLocation(entry_type, identifier) | |
| 501 if not identifier then | |
| 502 return | |
| 503 end | |
| 504 local zone_name, area_id, x, y, map_level, difficulty_token = CurrentLocationData() | |
| 505 if not (zone_name and area_id and x and y and map_level) then | |
| 506 Debug("UpdateDBEntryLocation: Missing current location data - %s, %d, %d, %d, %d.", zone_name, area_id, x, y, map_level) | |
| 507 return | |
| 508 end | |
| 509 local entry = DBEntry(entry_type, identifier) | |
| 510 entry[difficulty_token] = entry[difficulty_token] or {} | |
| 511 entry[difficulty_token].locations = entry[difficulty_token].locations or {} | |
| 512 | |
| 513 local zone_token = ("%s:%d"):format(zone_name, area_id) | |
| 514 local zone_data = entry[difficulty_token].locations[zone_token] | |
| 515 | |
| 516 if not zone_data then | |
| 517 zone_data = {} | |
| 518 entry[difficulty_token].locations[zone_token] = zone_data | |
| 519 end | |
| 520 | |
| 521 -- Special case for Fishing. | |
| 522 if current_action.spell_label == "FISHING" then | |
| 523 local yard_width, yard_height = MapData:MapArea(area_id, map_level) | |
| 524 | |
| 525 if yard_width > 0 and yard_height > 0 then | |
| 526 x, y = FishingCoordinates(x, y, yard_width, yard_height) | |
| 527 current_action.x = x | |
| 528 current_action.y = y | |
| 529 end | |
| 530 end | |
| 531 local location_token = ("%d:%d:%d"):format(map_level, x, y) | |
| 532 | |
| 533 zone_data[location_token] = zone_data[location_token] or true | |
| 534 return zone_data | |
| 535 end | |
| 536 end -- do-block | |
| 537 | |
| 538 | |
| 539 local function HandleItemUse(item_link, bag_index, slot_index) | |
| 540 if not item_link then | |
| 541 return | |
| 542 end | |
| 543 local item_id = ItemLinkToID(item_link) | |
| 544 | |
| 545 if not bag_index or not slot_index then | |
| 546 for new_bag_index = 0, _G.NUM_BAG_FRAMES do | |
| 547 for new_slot_index = 1, _G.GetContainerNumSlots(new_bag_index) do | |
| 548 if item_id == ItemLinkToID(_G.GetContainerItemLink(new_bag_index, new_slot_index)) then | |
| 549 bag_index = new_bag_index | |
| 550 slot_index = new_slot_index | |
| 551 break | |
| 552 end | |
| 553 end | |
| 554 end | |
| 555 end | |
| 556 | |
| 557 if not bag_index or not slot_index then | |
| 558 return | |
| 559 end | |
| 560 local _, _, _, _, _, is_lootable = _G.GetContainerItemInfo(bag_index, slot_index) | |
| 561 | |
| 562 if not is_lootable then | |
| 563 return | |
| 564 end | |
| 565 | |
| 566 table.wipe(current_action) | |
| 567 current_loot = nil | |
| 568 current_action.target_type = AF.ITEM | |
| 569 current_action.identifier = item_id | |
| 570 current_action.loot_label = "contains" | |
| 571 | |
| 572 --[[DatamineTT:ClearLines() | |
| 573 DatamineTT:SetBagItem(bag_index, slot_index) | |
| 574 | |
| 575 for line_index = 1, DatamineTT:NumLines() do | |
| 576 local current_line = _G["WDPDatamineTTTextLeft" .. line_index] | |
| 577 | |
| 578 if not current_line then | |
| 579 Debug("HandleItemUse: Item with ID %d and link %s had an invalid tooltip.", item_id, item_link) | |
| 580 return | |
| 581 end | |
| 582 | |
| 583 if current_line:GetText() == _G.ITEM_OPENABLE then | |
| 584 table.wipe(current_action) | |
| 585 current_loot = nil | |
| 586 | |
| 587 current_action.target_type = AF.ITEM | |
| 588 current_action.identifier = item_id | |
| 589 current_action.loot_label = "contains" | |
| 590 return | |
| 591 end | |
| 592 end | |
| 593 Debug("HandleItemUse: Item with ID %d and link %s did not have a tooltip that contained the string %s.", item_id, item_link, _G.ITEM_OPENABLE)]]-- | |
| 594 end | |
| 595 | |
| 596 | |
| 597 local UnitFactionStanding | |
| 598 local UpdateFactionData | |
| 599 do | |
| 600 local MAX_FACTION_INDEX = 1000 | |
| 601 | |
| 602 local STANDING_NAMES = { | |
| 603 "HATED", | |
| 604 "HOSTILE", | |
| 605 "UNFRIENDLY", | |
| 606 "NEUTRAL", | |
| 607 "FRIENDLY", | |
| 608 "HONORED", | |
| 609 "REVERED", | |
| 610 "EXALTED", | |
| 611 } | |
| 612 | |
| 613 | |
| 614 function UnitFactionStanding(unit) | |
| 615 local unit_name = _G.UnitName(unit) | |
| 616 UpdateFactionData() | |
| 617 DatamineTT:ClearLines() | |
| 618 DatamineTT:SetUnit(unit) | |
| 619 | |
| 620 for line_index = 1, DatamineTT:NumLines() do | |
| 621 local faction_name = _G["WDPDatamineTTTextLeft" .. line_index]:GetText():trim() | |
| 622 | |
| 623 if faction_name and faction_name ~= unit_name and faction_standings[faction_name] then | |
| 624 return faction_name, faction_standings[faction_name] | |
| 625 end | |
| 626 end | |
| 627 end | |
| 628 | |
| 629 | |
| 630 function UpdateFactionData() | |
| 631 for faction_index = 1, MAX_FACTION_INDEX do | |
| 632 local faction_name, _, current_standing, _, _, _, _, _, is_header = _G.GetFactionInfo(faction_index) | |
| 633 | |
| 634 if faction_name then | |
| 635 faction_standings[faction_name] = STANDING_NAMES[current_standing] | |
| 636 elseif not faction_name then | |
| 637 break | |
| 638 end | |
| 639 end | |
| 640 end | |
| 641 end -- do-block | |
| 642 | |
| 643 | |
| 644 local GenericLootUpdate | |
| 645 do | |
| 646 local function LootTable(entry, loot_type, top_field) | |
| 647 if top_field then | |
| 648 entry[top_field] = entry[top_field] or {} | |
| 649 entry[top_field][loot_type] = entry[top_field][loot_type] or {} | |
| 650 return entry[top_field][loot_type] | |
| 651 end | |
| 652 entry[loot_type] = entry[loot_type] or {} | |
| 653 return entry[loot_type] | |
| 654 end | |
| 655 | |
| 656 function GenericLootUpdate(data_type, top_field) | |
| 657 local loot_type = current_loot.label | |
| 658 local loot_count = ("%s_count"):format(loot_type) | |
| 659 local source_list = {} | |
| 660 | |
| 661 if current_loot.sources then | |
| 662 for source_guid, loot_data in pairs(current_loot.sources) do | |
| 663 local source_id | |
| 664 | |
| 665 if current_loot.target_type == AF.ITEM then | |
| 666 -- Items return the player as the source, so we need to use the item's ID (disenchant, milling, etc) | |
| 667 source_id = current_loot.identifier | |
| 668 else | |
| 669 local _, unit_ID = ParseGUID(source_guid) | |
| 670 if unit_ID then | |
| 671 if current_loot.target_type == AF.OBJECT then | |
| 672 source_id = ("%s:%s"):format(current_loot.spell_label, unit_ID) | |
| 673 else | |
| 674 source_id = unit_ID | |
| 675 end | |
| 676 end | |
| 677 end | |
| 678 local entry = DBEntry(data_type, source_id) | |
| 679 | |
| 680 if entry then | |
| 681 local loot_table = LootTable(entry, loot_type, top_field) | |
| 682 | |
| 683 if not source_list[source_id] then | |
| 684 if top_field then | |
| 685 entry[top_field][loot_count] = (entry[top_field][loot_count] or 0) + 1 | |
| 686 elseif not container_loot_toasting then | |
| 687 entry[loot_count] = (entry[loot_count] or 0) + 1 | |
| 688 end | |
| 689 source_list[source_id] = true | |
| 690 end | |
| 691 UpdateDBEntryLocation(data_type, source_id) | |
| 692 | |
| 693 if current_loot.target_type == AF.ZONE then | |
| 694 for item_id, quantity in pairs(loot_data) do | |
| 695 table.insert(loot_table, ("%d:%d"):format(item_id, quantity)) | |
| 696 end | |
| 697 else | |
| 698 for loot_token, quantity in pairs(loot_data) do | |
| 699 local label, currency_texture = (":"):split(loot_token) | |
| 700 | |
| 701 if label == "currency" and currency_texture then | |
| 702 table.insert(loot_table, ("currency:%d:%s"):format(quantity, currency_texture)) | |
| 703 elseif loot_token == "money" then | |
| 704 table.insert(loot_table, ("money:%d"):format(quantity)) | |
| 705 else | |
| 706 table.insert(loot_table, ("%d:%d"):format(loot_token, quantity)) | |
| 707 end | |
| 708 end | |
| 709 end | |
| 710 end | |
| 711 end | |
| 712 end | |
| 713 | |
| 714 -- This is used for Gas Extractions. | |
| 715 if #current_loot.list <= 0 then | |
| 716 return | |
| 717 end | |
| 718 local entry | |
| 719 | |
| 720 -- At this point we only have a name if it's an object. | |
| 721 -- (As of 5.x, the above statement is almost never true, but there are a few cases, like gas extractions.) | |
| 722 if current_loot.target_type == AF.OBJECT then | |
| 723 entry = DBEntry(data_type, ("%s:%s"):format(current_loot.spell_label, current_loot.object_name)) | |
| 724 else | |
| 725 entry = DBEntry(data_type, current_loot.identifier) | |
| 726 end | |
| 727 | |
| 728 if not entry then | |
| 729 return | |
| 730 end | |
| 731 local loot_table = LootTable(entry, loot_type, top_field) | |
| 732 | |
| 733 if current_loot.identifier then | |
| 734 if not source_list[current_loot.identifier] then | |
| 735 if top_field then | |
| 736 entry[top_field][loot_count] = (entry[top_field][loot_count] or 0) + 1 | |
| 737 else | |
| 738 entry[loot_count] = (entry[loot_count] or 0) + 1 | |
| 739 end | |
| 740 source_list[current_loot.identifier] = true | |
| 741 end | |
| 742 end | |
| 743 | |
| 744 for index = 1, #current_loot.list do | |
| 745 table.insert(loot_table, current_loot.list[index]) | |
| 746 end | |
| 747 end | |
| 748 end -- do-block | |
| 749 | |
| 750 | |
| 751 local ReplaceKeywords | |
| 752 do | |
| 753 local KEYWORD_SUBSTITUTIONS = { | |
| 754 class = PLAYER_CLASS, | |
| 755 name = PLAYER_NAME, | |
| 756 race = PLAYER_RACE, | |
| 757 } | |
| 758 | |
| 759 | |
| 760 function ReplaceKeywords(text) | |
| 761 if not text or text == "" then | |
| 762 return "" | |
| 763 end | |
| 764 | |
| 765 for category, lookup in pairs(KEYWORD_SUBSTITUTIONS) do | |
| 766 local category_format = ("<%s>"):format(category) | |
| 767 text = text:gsub(lookup, category_format):gsub(lookup:lower(), category_format) | |
| 768 end | |
| 769 return text | |
| 770 end | |
| 771 end -- do-block | |
| 772 | |
| 773 | |
| 774 -- Contains a dirty hack due to Blizzard's strange handling of Micro Dungeons; GetMapInfo() will not return correct information | |
| 775 -- unless the WorldMapFrame is shown. | |
| 776 do | |
| 777 -- MapFileName = MapAreaID | |
| 778 local MICRO_DUNGEON_IDS = { | |
| 779 ShrineofTwoMoons = 903, | |
| 780 ShrineofSevenStars = 905, | |
| 781 } | |
| 782 | |
| 783 local function SetCurrentAreaID() | |
| 784 if private.in_combat then | |
| 785 private.set_area_id = true | |
| 786 return | |
| 787 end | |
| 788 local map_area_id = _G.GetCurrentMapAreaID() | |
| 789 | |
| 790 if map_area_id == current_area_id then | |
| 791 return | |
| 792 end | |
| 793 local world_map = _G.WorldMapFrame | |
| 794 local map_visible = world_map:IsVisible() | |
| 795 local sfx_value = tonumber(_G.GetCVar("Sound_EnableSFX")) | |
| 796 | |
| 797 if not map_visible then | |
| 798 _G.SetCVar("Sound_EnableSFX", 0) | |
| 799 world_map:Show() | |
| 800 end | |
| 801 local _, _, _, _, micro_dungeon_map_name = _G.GetMapInfo() | |
| 802 local micro_dungeon_id = MICRO_DUNGEON_IDS[micro_dungeon_map_name] | |
| 803 | |
| 804 _G.SetMapToCurrentZone() | |
| 805 | |
| 806 if micro_dungeon_id then | |
| 807 current_area_id = micro_dungeon_id | |
| 808 else | |
| 809 current_area_id = _G.GetCurrentMapAreaID() | |
| 810 end | |
| 811 | |
| 812 if map_visible then | |
| 813 _G.SetMapByID(map_area_id) | |
| 814 else | |
| 815 world_map:Hide() | |
| 816 _G.SetCVar("Sound_EnableSFX", sfx_value) | |
| 817 end | |
| 818 end | |
| 819 | |
| 820 function WDP:HandleZoneChange(event_name) | |
| 821 in_instance = _G.IsInInstance() | |
| 822 SetCurrentAreaID() | |
| 823 end | |
| 824 end | |
| 825 | 219 |
| 826 local function InitializeCurrentLoot() | 220 local function InitializeCurrentLoot() |
| 827 current_loot = { | 221 current_loot = { |
| 828 list = {}, | 222 list = {}, |
| 829 sources = {}, | 223 sources = {}, |
| 840 | 234 |
| 841 table.wipe(current_action) | 235 table.wipe(current_action) |
| 842 end | 236 end |
| 843 | 237 |
| 844 | 238 |
| 239 local TradeSkillExecutePer | |
| 240 do | |
| 241 local header_list = {} | |
| 242 | |
| 243 function TradeSkillExecutePer(iter_func) | |
| 244 if not _G.TradeSkillFrame or not _G.TradeSkillFrame:IsVisible() then | |
| 245 return | |
| 246 end | |
| 247 -- Clear the search box focus so the scan will have correct results. | |
| 248 local search_box = _G.TradeSkillFrameSearchBox | |
| 249 search_box:SetText("") | |
| 250 | |
| 251 _G.TradeSkillSearch_OnTextChanged(search_box) | |
| 252 search_box:ClearFocus() | |
| 253 search_box:GetScript("OnEditFocusLost")(search_box) | |
| 254 | |
| 255 table.wipe(header_list) | |
| 256 | |
| 257 -- Save the current state of the TradeSkillFrame so it can be restored after we muck with it. | |
| 258 local have_materials = _G.TradeSkillFrame.filterTbl.hasMaterials | |
| 259 local have_skillup = _G.TradeSkillFrame.filterTbl.hasSkillUp | |
| 260 | |
| 261 if have_materials then | |
| 262 _G.TradeSkillFrame.filterTbl.hasMaterials = false | |
| 263 _G.TradeSkillOnlyShowMakeable(false) | |
| 264 end | |
| 265 | |
| 266 if have_skillup then | |
| 267 _G.TradeSkillFrame.filterTbl.hasSkillUp = false | |
| 268 _G.TradeSkillOnlyShowSkillUps(false) | |
| 269 end | |
| 270 _G.SetTradeSkillInvSlotFilter(0, true, true) | |
| 271 _G.TradeSkillUpdateFilterBar() | |
| 272 _G.TradeSkillFrame_Update() | |
| 273 | |
| 274 -- Expand all headers so we can see all the recipes there are | |
| 275 for tradeskill_index = 1, _G.GetNumTradeSkills() do | |
| 276 local name, tradeskill_type, _, is_expanded = _G.GetTradeSkillInfo(tradeskill_index) | |
| 277 | |
| 278 if tradeskill_type == "header" or tradeskill_type == "subheader" then | |
| 279 if not is_expanded then | |
| 280 header_list[name] = true | |
| 281 _G.ExpandTradeSkillSubClass(tradeskill_index) | |
| 282 end | |
| 283 elseif iter_func(name, tradeskill_index) then | |
| 284 break | |
| 285 end | |
| 286 end | |
| 287 | |
| 288 -- Restore the state of the things we changed. | |
| 289 for tradeskill_index = 1, _G.GetNumTradeSkills() do | |
| 290 local name, tradeskill_type, _, is_expanded = _G.GetTradeSkillInfo(tradeskill_index) | |
| 291 | |
| 292 if header_list[name] then | |
| 293 _G.CollapseTradeSkillSubClass(tradeskill_index) | |
| 294 end | |
| 295 end | |
| 296 _G.TradeSkillFrame.filterTbl.hasMaterials = have_materials | |
| 297 _G.TradeSkillOnlyShowMakeable(have_materials) | |
| 298 _G.TradeSkillFrame.filterTbl.hasSkillUp = have_skillup | |
| 299 _G.TradeSkillOnlyShowSkillUps(have_skillup) | |
| 300 | |
| 301 _G.TradeSkillUpdateFilterBar() | |
| 302 _G.TradeSkillFrame_Update() | |
| 303 end | |
| 304 end -- do-block | |
| 305 | |
| 306 | |
| 307 local ActualCopperCost | |
| 308 do | |
| 309 local BARTERING_SPELL_ID = 83964 | |
| 310 | |
| 311 local STANDING_DISCOUNTS = { | |
| 312 HATED = 0, | |
| 313 HOSTILE = 0, | |
| 314 UNFRIENDLY = 0, | |
| 315 NEUTRAL = 0, | |
| 316 FRIENDLY = 0.05, | |
| 317 HONORED = 0.1, | |
| 318 REVERED = 0.15, | |
| 319 EXALTED = 0.2, | |
| 320 } | |
| 321 | |
| 322 | |
| 323 function ActualCopperCost(copper_cost, rep_standing) | |
| 324 if not copper_cost or copper_cost == 0 then | |
| 325 return 0 | |
| 326 end | |
| 327 local modifier = 1 | |
| 328 | |
| 329 if _G.IsSpellKnown(BARTERING_SPELL_ID) then | |
| 330 modifier = modifier - 0.1 | |
| 331 end | |
| 332 | |
| 333 if rep_standing then | |
| 334 if PLAYER_RACE == "Goblin" then | |
| 335 modifier = modifier - STANDING_DISCOUNTS["EXALTED"] | |
| 336 elseif STANDING_DISCOUNTS[rep_standing] then | |
| 337 modifier = modifier - STANDING_DISCOUNTS[rep_standing] | |
| 338 end | |
| 339 end | |
| 340 return math.floor(copper_cost / modifier) | |
| 341 end | |
| 342 end -- do-block | |
| 343 | |
| 344 | |
| 345 local function InstanceDifficultyToken() | |
| 346 local _, instance_type, instance_difficulty, _, _, _, is_dynamic = _G.GetInstanceInfo() | |
| 347 | |
| 348 if not instance_type or instance_type == "" then | |
| 349 instance_type = "NONE" | |
| 350 end | |
| 351 return ("%s:%d:%s"):format(instance_type:upper(), instance_difficulty, tostring(is_dynamic)) | |
| 352 end | |
| 353 | |
| 354 | |
| 355 local function DBEntry(data_type, unit_id) | |
| 356 if not data_type or not unit_id then | |
| 357 return | |
| 358 end | |
| 359 local category = global_db[data_type] | |
| 360 | |
| 361 if not category then | |
| 362 category = {} | |
| 363 global_db[data_type] = category | |
| 364 end | |
| 365 local unit = category[unit_id] | |
| 366 | |
| 367 if not unit then | |
| 368 unit = {} | |
| 369 category[unit_id] = unit | |
| 370 end | |
| 371 return unit | |
| 372 end | |
| 373 | |
| 374 private.DBEntry = DBEntry | |
| 375 | |
| 376 local NPCEntry | |
| 377 do | |
| 378 local npc_prototype = {} | |
| 379 local npc_meta = { | |
| 380 __index = npc_prototype | |
| 381 } | |
| 382 | |
| 383 function NPCEntry(identifier) | |
| 384 local npc = DBEntry("npcs", identifier) | |
| 385 return npc and _G.setmetatable(npc, npc_meta) or nil | |
| 386 end | |
| 387 | |
| 388 function npc_prototype:EncounterData(difficulty_token) | |
| 389 self.encounter_data = self.encounter_data or {} | |
| 390 self.encounter_data[difficulty_token] = self.encounter_data[difficulty_token] or {} | |
| 391 self.encounter_data[difficulty_token].stats = self.encounter_data[difficulty_token].stats or {} | |
| 392 | |
| 393 return self.encounter_data[difficulty_token] | |
| 394 end | |
| 395 end | |
| 396 | |
| 397 | |
| 398 local function CurrentLocationData() | |
| 399 if _G.GetCurrentMapAreaID() ~= current_area_id then | |
| 400 return _G.GetRealZoneText(), current_area_id, 0, 0, 0, InstanceDifficultyToken() | |
| 401 end | |
| 402 local map_level = _G.GetCurrentMapDungeonLevel() or 0 | |
| 403 local x, y = _G.GetPlayerMapPosition("player") | |
| 404 | |
| 405 x = x or 0 | |
| 406 y = y or 0 | |
| 407 | |
| 408 if x == 0 and y == 0 then | |
| 409 for level_index = 1, _G.GetNumDungeonMapLevels() do | |
| 410 _G.SetDungeonMapLevel(level_index) | |
| 411 x, y = _G.GetPlayerMapPosition("player") | |
| 412 | |
| 413 if x and y and (x > 0 or y > 0) then | |
| 414 _G.SetDungeonMapLevel(map_level) | |
| 415 map_level = level_index | |
| 416 break | |
| 417 end | |
| 418 end | |
| 419 end | |
| 420 | |
| 421 if _G.DungeonUsesTerrainMap() then | |
| 422 map_level = map_level - 1 | |
| 423 end | |
| 424 local x = _G.floor(x * 1000) | |
| 425 local y = _G.floor(y * 1000) | |
| 426 | |
| 427 if x % 2 ~= 0 then | |
| 428 x = x + 1 | |
| 429 end | |
| 430 | |
| 431 if y % 2 ~= 0 then | |
| 432 y = y + 1 | |
| 433 end | |
| 434 return _G.GetRealZoneText(), current_area_id, x, y, map_level, InstanceDifficultyToken() | |
| 435 end | |
| 436 | |
| 437 | |
| 438 local function CurrencyLinkToTexture(currency_link) | |
| 439 if not currency_link then | |
| 440 return | |
| 441 end | |
| 442 local _, _, texture_path = _G.GetCurrencyInfo(tonumber(currency_link:match("currency:(%d+)"))) | |
| 443 return texture_path:match("[^\\]+$"):lower() | |
| 444 end | |
| 445 | |
| 446 | |
| 447 local function ItemLinkToID(item_link) | |
| 448 if not item_link then | |
| 449 return | |
| 450 end | |
| 451 return tonumber(item_link:match("item:(%d+)")) | |
| 452 end | |
| 453 | |
| 454 private.ItemLinkToID = ItemLinkToID | |
| 455 | |
| 456 local function UnitTypeIsNPC(unit_type) | |
| 457 return unit_type == private.UNIT_TYPES.NPC or unit_type == private.UNIT_TYPES.VEHICLE | |
| 458 end | |
| 459 | |
| 460 | |
| 461 local ParseGUID | |
| 462 do | |
| 463 local UNIT_TYPES = private.UNIT_TYPES | |
| 464 | |
| 465 local NPC_ID_MAPPING = { | |
| 466 [62164] = 63191, -- Garalon | |
| 467 } | |
| 468 | |
| 469 | |
| 470 local function MatchUnitTypes(unit_type_name) | |
| 471 if not unit_type_name then | |
| 472 return UNIT_TYPES.UNKNOWN | |
| 473 end | |
| 474 | |
| 475 for def, text in next, UNIT_TYPES do | |
| 476 if unit_type_name == text then | |
| 477 return UNIT_TYPES[def] | |
| 478 end | |
| 479 end | |
| 480 return UNIT_TYPES.UNKNOWN | |
| 481 end | |
| 482 | |
| 483 | |
| 484 function ParseGUID(guid) | |
| 485 if not guid then | |
| 486 return | |
| 487 end | |
| 488 | |
| 489 -- We might want to use some of this new information later, but leaving the returns alone for now | |
| 490 local unit_type_name, unk_id1, server_id, instance_id, unk_id2, unit_idnum, spawn_id = ("-"):split(guid) | |
| 491 | |
| 492 local unit_type = MatchUnitTypes(unit_type_name) | |
| 493 if unit_type ~= UNIT_TYPES.PLAYER and unit_type ~= UNIT_TYPES.PET and unit_type ~= UNIT_TYPES.ITEM then | |
| 494 | |
| 495 local id_mapping = NPC_ID_MAPPING[unit_idnum] | |
| 496 | |
| 497 if id_mapping and UnitTypeIsNPC(unit_type) then | |
| 498 unit_idnum = id_mapping | |
| 499 end | |
| 500 return unit_type, unit_idnum | |
| 501 end | |
| 502 return unit_type | |
| 503 end | |
| 504 | |
| 505 private.ParseGUID = ParseGUID | |
| 506 end -- do-block | |
| 507 | |
| 508 | |
| 509 local UpdateDBEntryLocation | |
| 510 do | |
| 511 -- Fishing node coordinate code based on code in GatherMate2 with permission from Kagaro. | |
| 512 local function FishingCoordinates(x, y, yard_width, yard_height) | |
| 513 local facing = _G.GetPlayerFacing() | |
| 514 | |
| 515 if not facing then | |
| 516 return x, y | |
| 517 end | |
| 518 local rad = facing + math.pi | |
| 519 return x + math.sin(rad) * 15 / yard_width, y + math.cos(rad) * 15 / yard_height | |
| 520 end | |
| 521 | |
| 522 | |
| 523 function UpdateDBEntryLocation(entry_type, identifier) | |
| 524 if not identifier then | |
| 525 return | |
| 526 end | |
| 527 local zone_name, area_id, x, y, map_level, difficulty_token = CurrentLocationData() | |
| 528 if not (zone_name and area_id and x and y and map_level) then | |
| 529 Debug("UpdateDBEntryLocation: Missing current location data - %s, %d, %d, %d, %d.", zone_name, area_id, x, y, map_level) | |
| 530 return | |
| 531 end | |
| 532 local entry = DBEntry(entry_type, identifier) | |
| 533 entry[difficulty_token] = entry[difficulty_token] or {} | |
| 534 entry[difficulty_token].locations = entry[difficulty_token].locations or {} | |
| 535 | |
| 536 local zone_token = ("%s:%d"):format(zone_name, area_id) | |
| 537 local zone_data = entry[difficulty_token].locations[zone_token] | |
| 538 | |
| 539 if not zone_data then | |
| 540 zone_data = {} | |
| 541 entry[difficulty_token].locations[zone_token] = zone_data | |
| 542 end | |
| 543 | |
| 544 -- Special case for Fishing. | |
| 545 if current_action.spell_label == "FISHING" then | |
| 546 local yard_width, yard_height = MapData:MapArea(area_id, map_level) | |
| 547 | |
| 548 if yard_width > 0 and yard_height > 0 then | |
| 549 x, y = FishingCoordinates(x, y, yard_width, yard_height) | |
| 550 current_action.x = x | |
| 551 current_action.y = y | |
| 552 end | |
| 553 end | |
| 554 local location_token = ("%d:%d:%d"):format(map_level, x, y) | |
| 555 | |
| 556 zone_data[location_token] = zone_data[location_token] or true | |
| 557 return zone_data | |
| 558 end | |
| 559 end -- do-block | |
| 560 | |
| 561 | |
| 562 local function HandleItemUse(item_link, bag_index, slot_index) | |
| 563 if not item_link then | |
| 564 return | |
| 565 end | |
| 566 local item_id = ItemLinkToID(item_link) | |
| 567 | |
| 568 if not bag_index or not slot_index then | |
| 569 for new_bag_index = 0, _G.NUM_BAG_FRAMES do | |
| 570 for new_slot_index = 1, _G.GetContainerNumSlots(new_bag_index) do | |
| 571 if item_id == ItemLinkToID(_G.GetContainerItemLink(new_bag_index, new_slot_index)) then | |
| 572 bag_index = new_bag_index | |
| 573 slot_index = new_slot_index | |
| 574 break | |
| 575 end | |
| 576 end | |
| 577 end | |
| 578 end | |
| 579 | |
| 580 if not bag_index or not slot_index then | |
| 581 return | |
| 582 end | |
| 583 local _, _, _, _, _, is_lootable = _G.GetContainerItemInfo(bag_index, slot_index) | |
| 584 | |
| 585 if not is_lootable then | |
| 586 return | |
| 587 end | |
| 588 | |
| 589 table.wipe(current_action) | |
| 590 current_loot = nil | |
| 591 current_action.target_type = AF.ITEM | |
| 592 current_action.identifier = item_id | |
| 593 current_action.loot_label = "contains" | |
| 594 | |
| 595 -- For items that open instantly with no spell cast | |
| 596 if private.CONTAINER_ITEM_ID_LIST[item_id] == true then | |
| 597 ClearChatLootData() | |
| 598 Debug("HandleItemUse: Beginning chat-based loot timer for item with ID %d.", item_id) | |
| 599 chat_loot_timer_handle = C_Timer.NewTimer(1, ClearChatLootData) | |
| 600 InitializeCurrentLoot() | |
| 601 end | |
| 602 | |
| 603 --[[DatamineTT:ClearLines() | |
| 604 DatamineTT:SetBagItem(bag_index, slot_index) | |
| 605 | |
| 606 for line_index = 1, DatamineTT:NumLines() do | |
| 607 local current_line = _G["WDPDatamineTTTextLeft" .. line_index] | |
| 608 | |
| 609 if not current_line then | |
| 610 Debug("HandleItemUse: Item with ID %d and link %s had an invalid tooltip.", item_id, item_link) | |
| 611 return | |
| 612 end | |
| 613 | |
| 614 if current_line:GetText() == _G.ITEM_OPENABLE then | |
| 615 table.wipe(current_action) | |
| 616 current_loot = nil | |
| 617 | |
| 618 current_action.target_type = AF.ITEM | |
| 619 current_action.identifier = item_id | |
| 620 current_action.loot_label = "contains" | |
| 621 return | |
| 622 end | |
| 623 end | |
| 624 Debug("HandleItemUse: Item with ID %d and link %s did not have a tooltip that contained the string %s.", item_id, item_link, _G.ITEM_OPENABLE)]]-- | |
| 625 end | |
| 626 | |
| 627 | |
| 628 local UnitFactionStanding | |
| 629 local UpdateFactionData | |
| 630 do | |
| 631 local MAX_FACTION_INDEX = 1000 | |
| 632 | |
| 633 local STANDING_NAMES = { | |
| 634 "HATED", | |
| 635 "HOSTILE", | |
| 636 "UNFRIENDLY", | |
| 637 "NEUTRAL", | |
| 638 "FRIENDLY", | |
| 639 "HONORED", | |
| 640 "REVERED", | |
| 641 "EXALTED", | |
| 642 } | |
| 643 | |
| 644 | |
| 645 function UnitFactionStanding(unit) | |
| 646 local unit_name = _G.UnitName(unit) | |
| 647 UpdateFactionData() | |
| 648 DatamineTT:ClearLines() | |
| 649 DatamineTT:SetUnit(unit) | |
| 650 | |
| 651 for line_index = 1, DatamineTT:NumLines() do | |
| 652 local faction_name = _G["WDPDatamineTTTextLeft" .. line_index]:GetText():trim() | |
| 653 | |
| 654 if faction_name and faction_name ~= unit_name and faction_standings[faction_name] then | |
| 655 return faction_name, faction_standings[faction_name] | |
| 656 end | |
| 657 end | |
| 658 end | |
| 659 | |
| 660 | |
| 661 function UpdateFactionData() | |
| 662 for faction_index = 1, MAX_FACTION_INDEX do | |
| 663 local faction_name, _, current_standing, _, _, _, _, _, is_header = _G.GetFactionInfo(faction_index) | |
| 664 | |
| 665 if faction_name then | |
| 666 faction_standings[faction_name] = STANDING_NAMES[current_standing] | |
| 667 elseif not faction_name then | |
| 668 break | |
| 669 end | |
| 670 end | |
| 671 end | |
| 672 end -- do-block | |
| 673 | |
| 674 | |
| 675 local GenericLootUpdate | |
| 676 do | |
| 677 local function LootTable(entry, loot_type, top_field) | |
| 678 if top_field then | |
| 679 entry[top_field] = entry[top_field] or {} | |
| 680 entry[top_field][loot_type] = entry[top_field][loot_type] or {} | |
| 681 return entry[top_field][loot_type] | |
| 682 end | |
| 683 entry[loot_type] = entry[loot_type] or {} | |
| 684 return entry[loot_type] | |
| 685 end | |
| 686 | |
| 687 function GenericLootUpdate(data_type, top_field) | |
| 688 local loot_type = current_loot.label | |
| 689 local loot_count = ("%s_count"):format(loot_type) | |
| 690 local source_list = {} | |
| 691 | |
| 692 if current_loot.sources then | |
| 693 for source_guid, loot_data in pairs(current_loot.sources) do | |
| 694 local source_id | |
| 695 | |
| 696 if current_loot.target_type == AF.ITEM then | |
| 697 -- Items return the player as the source, so we need to use the item's ID (disenchant, milling, etc) | |
| 698 source_id = current_loot.identifier | |
| 699 else | |
| 700 local _, unit_ID = ParseGUID(source_guid) | |
| 701 if unit_ID then | |
| 702 if current_loot.target_type == AF.OBJECT then | |
| 703 source_id = ("%s:%s"):format(current_loot.spell_label, unit_ID) | |
| 704 else | |
| 705 source_id = unit_ID | |
| 706 end | |
| 707 end | |
| 708 end | |
| 709 local entry = DBEntry(data_type, source_id) | |
| 710 | |
| 711 if entry then | |
| 712 local loot_table = LootTable(entry, loot_type, top_field) | |
| 713 | |
| 714 if not source_list[source_id] then | |
| 715 if top_field then | |
| 716 entry[top_field][loot_count] = (entry[top_field][loot_count] or 0) + 1 | |
| 717 elseif not container_loot_toasting then | |
| 718 entry[loot_count] = (entry[loot_count] or 0) + 1 | |
| 719 end | |
| 720 source_list[source_id] = true | |
| 721 end | |
| 722 UpdateDBEntryLocation(data_type, source_id) | |
| 723 | |
| 724 if current_loot.target_type == AF.ZONE then | |
| 725 for item_id, quantity in pairs(loot_data) do | |
| 726 table.insert(loot_table, ("%d:%d"):format(item_id, quantity)) | |
| 727 end | |
| 728 else | |
| 729 for loot_token, quantity in pairs(loot_data) do | |
| 730 local label, currency_texture = (":"):split(loot_token) | |
| 731 | |
| 732 if label == "currency" and currency_texture then | |
| 733 table.insert(loot_table, ("currency:%d:%s"):format(quantity, currency_texture)) | |
| 734 elseif loot_token == "money" then | |
| 735 table.insert(loot_table, ("money:%d"):format(quantity)) | |
| 736 else | |
| 737 table.insert(loot_table, ("%d:%d"):format(loot_token, quantity)) | |
| 738 end | |
| 739 end | |
| 740 end | |
| 741 end | |
| 742 end | |
| 743 end | |
| 744 | |
| 745 -- This is used for Gas Extractions. | |
| 746 if #current_loot.list <= 0 then | |
| 747 return | |
| 748 end | |
| 749 local entry | |
| 750 | |
| 751 -- At this point we only have a name if it's an object. | |
| 752 -- (As of 5.x, the above statement is almost never true, but there are a few cases, like gas extractions.) | |
| 753 if current_loot.target_type == AF.OBJECT then | |
| 754 entry = DBEntry(data_type, ("%s:%s"):format(current_loot.spell_label, current_loot.object_name)) | |
| 755 else | |
| 756 entry = DBEntry(data_type, current_loot.identifier) | |
| 757 end | |
| 758 | |
| 759 if not entry then | |
| 760 return | |
| 761 end | |
| 762 local loot_table = LootTable(entry, loot_type, top_field) | |
| 763 | |
| 764 if current_loot.identifier then | |
| 765 if not source_list[current_loot.identifier] then | |
| 766 if top_field then | |
| 767 entry[top_field][loot_count] = (entry[top_field][loot_count] or 0) + 1 | |
| 768 else | |
| 769 entry[loot_count] = (entry[loot_count] or 0) + 1 | |
| 770 end | |
| 771 source_list[current_loot.identifier] = true | |
| 772 end | |
| 773 end | |
| 774 | |
| 775 for index = 1, #current_loot.list do | |
| 776 table.insert(loot_table, current_loot.list[index]) | |
| 777 end | |
| 778 end | |
| 779 end -- do-block | |
| 780 | |
| 781 | |
| 782 local ReplaceKeywords | |
| 783 do | |
| 784 local KEYWORD_SUBSTITUTIONS = { | |
| 785 class = PLAYER_CLASS, | |
| 786 name = PLAYER_NAME, | |
| 787 race = PLAYER_RACE, | |
| 788 } | |
| 789 | |
| 790 | |
| 791 function ReplaceKeywords(text) | |
| 792 if not text or text == "" then | |
| 793 return "" | |
| 794 end | |
| 795 | |
| 796 for category, lookup in pairs(KEYWORD_SUBSTITUTIONS) do | |
| 797 local category_format = ("<%s>"):format(category) | |
| 798 text = text:gsub(lookup, category_format):gsub(lookup:lower(), category_format) | |
| 799 end | |
| 800 return text | |
| 801 end | |
| 802 end -- do-block | |
| 803 | |
| 804 | |
| 805 -- Contains a dirty hack due to Blizzard's strange handling of Micro Dungeons; GetMapInfo() will not return correct information | |
| 806 -- unless the WorldMapFrame is shown. | |
| 807 do | |
| 808 -- MapFileName = MapAreaID | |
| 809 local MICRO_DUNGEON_IDS = { | |
| 810 ShrineofTwoMoons = 903, | |
| 811 ShrineofSevenStars = 905, | |
| 812 } | |
| 813 | |
| 814 local function SetCurrentAreaID() | |
| 815 if private.in_combat then | |
| 816 private.set_area_id = true | |
| 817 return | |
| 818 end | |
| 819 local map_area_id = _G.GetCurrentMapAreaID() | |
| 820 | |
| 821 if map_area_id == current_area_id then | |
| 822 return | |
| 823 end | |
| 824 local world_map = _G.WorldMapFrame | |
| 825 local map_visible = world_map:IsVisible() | |
| 826 local sfx_value = tonumber(_G.GetCVar("Sound_EnableSFX")) | |
| 827 | |
| 828 if not map_visible then | |
| 829 _G.SetCVar("Sound_EnableSFX", 0) | |
| 830 world_map:Show() | |
| 831 end | |
| 832 local _, _, _, _, micro_dungeon_map_name = _G.GetMapInfo() | |
| 833 local micro_dungeon_id = MICRO_DUNGEON_IDS[micro_dungeon_map_name] | |
| 834 | |
| 835 _G.SetMapToCurrentZone() | |
| 836 | |
| 837 if micro_dungeon_id then | |
| 838 current_area_id = micro_dungeon_id | |
| 839 else | |
| 840 current_area_id = _G.GetCurrentMapAreaID() | |
| 841 end | |
| 842 | |
| 843 if map_visible then | |
| 844 _G.SetMapByID(map_area_id) | |
| 845 else | |
| 846 world_map:Hide() | |
| 847 _G.SetCVar("Sound_EnableSFX", sfx_value) | |
| 848 end | |
| 849 end | |
| 850 | |
| 851 function WDP:HandleZoneChange(event_name) | |
| 852 in_instance = _G.IsInInstance() | |
| 853 SetCurrentAreaID() | |
| 854 end | |
| 855 end | |
| 856 | |
| 857 | |
| 845 -- TIMERS ------------------------------------------------------------- | 858 -- TIMERS ------------------------------------------------------------- |
| 846 | 859 |
| 847 local function ClearKilledNPC() | 860 function ClearKilledNPC() |
| 848 killed_npc_id = nil | 861 killed_npc_id = nil |
| 849 end | 862 end |
| 850 | 863 |
| 851 | 864 |
| 852 local function ClearKilledBossID() | 865 function ClearKilledBossID() |
| 853 if killed_boss_id_timer_handle then | 866 if killed_boss_id_timer_handle then |
| 854 killed_boss_id_timer_handle:Cancel() | 867 killed_boss_id_timer_handle:Cancel() |
| 855 killed_boss_id_timer_handle = nil | 868 killed_boss_id_timer_handle = nil |
| 856 end | 869 end |
| 857 | 870 |
| 858 table.wipe(boss_loot_toasting) | 871 table.wipe(boss_loot_toasting) |
| 859 raid_boss_id = nil | 872 raid_boss_id = nil |
| 860 end | 873 end |
| 861 | 874 |
| 862 | 875 |
| 863 local function ClearLootToastContainerID() | 876 function ClearLootToastContainerID() |
| 864 if loot_toast_container_timer_handle then | 877 if loot_toast_container_timer_handle then |
| 865 loot_toast_container_timer_handle:Cancel() | 878 loot_toast_container_timer_handle:Cancel() |
| 866 loot_toast_container_timer_handle = nil | 879 loot_toast_container_timer_handle = nil |
| 867 end | 880 end |
| 868 | 881 |
| 869 container_loot_toasting = false | 882 container_loot_toasting = false |
| 870 loot_toast_container_id = nil | 883 loot_toast_container_id = nil |
| 871 end | 884 end |
| 872 | 885 |
| 873 | 886 |
| 874 local function ClearLootToastData() | 887 function ClearLootToastData() |
| 875 if loot_toast_data_timer_handle then | 888 if loot_toast_data_timer_handle then |
| 876 loot_toast_data_timer_handle:Cancel() | 889 loot_toast_data_timer_handle:Cancel() |
| 877 loot_toast_data_timer_handle = nil | 890 loot_toast_data_timer_handle = nil |
| 878 end | 891 end |
| 879 | 892 |
| 881 table.wipe(loot_toast_data) | 894 table.wipe(loot_toast_data) |
| 882 end | 895 end |
| 883 end | 896 end |
| 884 | 897 |
| 885 | 898 |
| 886 local function ClearChatLootData() | 899 function ClearChatLootData() |
| 887 Debug("ClearChatLootData: Ending chat-based loot timer.") | |
| 888 if chat_loot_timer_handle then | 900 if chat_loot_timer_handle then |
| 901 Debug("ClearChatLootData: Ending chat-based loot timer.") | |
| 889 chat_loot_timer_handle:Cancel() | 902 chat_loot_timer_handle:Cancel() |
| 890 chat_loot_timer_handle = nil | 903 chat_loot_timer_handle = nil |
| 891 end | 904 |
| 892 | 905 if current_loot and current_loot.identifier and (private.CONTAINER_ITEM_ID_LIST[current_loot.identifier] ~= nil) then |
| 893 if current_loot and current_loot.identifier and (private.CONTAINER_ITEM_ID_LIST[current_loot.identifier] ~= nil) then | 906 GenericLootUpdate("items") |
| 894 GenericLootUpdate("items") | 907 end |
| 895 end | 908 end |
| 896 current_loot = nil | 909 current_loot = nil |
| 897 end | 910 end |
| 898 | 911 |
| 899 | 912 |
| 2705 end | 2718 end |
| 2706 private.tracked_line = spell_line | 2719 private.tracked_line = spell_line |
| 2707 end | 2720 end |
| 2708 | 2721 |
| 2709 | 2722 |
| 2723 -- Triggered by bonus roll prompts, disenchant prompts, and in a few other rare circumstances | |
| 2710 function WDP:SPELL_CONFIRMATION_PROMPT(event_name, spell_id, confirm_type, text, duration, currency_id_cost) | 2724 function WDP:SPELL_CONFIRMATION_PROMPT(event_name, spell_id, confirm_type, text, duration, currency_id_cost) |
| 2711 if private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id] then | 2725 if private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id] then |
| 2712 ClearKilledBossID() | 2726 ClearKilledBossID() |
| 2713 ClearLootToastContainerID() | 2727 ClearLootToastContainerID() |
| 2714 raid_boss_id = private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id] | 2728 raid_boss_id = private.RAID_BOSS_BONUS_SPELL_ID_TO_NPC_ID_MAP[spell_id] |
| 2783 return | 2797 return |
| 2784 end | 2798 end |
| 2785 private.tracked_line = nil | 2799 private.tracked_line = nil |
| 2786 private.previous_spell_id = spell_id | 2800 private.previous_spell_id = spell_id |
| 2787 | 2801 |
| 2788 -- Handle Logging spell casts | 2802 -- For spells cast when Logging |
| 2789 if private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[spell_id] then | 2803 if private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[spell_id] then |
| 2790 last_timber_spell_id = spell_id | 2804 last_timber_spell_id = spell_id |
| 2791 UpdateDBEntryLocation("objects", ("OPENING:%s"):format(private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[spell_id])) | 2805 UpdateDBEntryLocation("objects", ("OPENING:%s"):format(private.LOGGING_SPELL_ID_TO_OBJECT_ID_MAP[spell_id])) |
| 2792 return | 2806 return |
| 2793 end | 2807 end |
| 2794 | 2808 |
| 2795 -- Handle Loot Toast spell casts | 2809 -- For spells cast by items that always trigger loot toasts |
| 2796 if private.LOOT_TOAST_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] then | 2810 if private.LOOT_TOAST_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] then |
| 2797 ClearKilledBossID() | 2811 ClearKilledBossID() |
| 2798 ClearLootToastContainerID() | 2812 ClearLootToastContainerID() |
| 2799 ClearLootToastData() | 2813 ClearLootToastData() |
| 2800 | 2814 |
| 2801 loot_toast_container_id = private.LOOT_TOAST_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] | 2815 loot_toast_container_id = private.LOOT_TOAST_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] |
| 2802 loot_toast_container_timer_handle = C_Timer.NewTimer(1, ClearLootToastContainerID) -- we need to assign a handle here to cancel it later | 2816 loot_toast_container_timer_handle = C_Timer.NewTimer(1, ClearLootToastContainerID) -- we need to assign a handle here to cancel it later |
| 2803 return | 2817 return |
| 2804 end | 2818 end |
| 2805 | 2819 |
| 2806 -- For Crates of Salvage (and potentially other items based on spell casts in the future which need manual handling) | 2820 -- For spells cast by items that don't usually trigger loot toasts |
| 2807 if private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] then | 2821 if private.DELAYED_CONTAINER_SPELL_ID_TO_ITEM_ID_MAP[spell_id] then |
| 2808 -- Set up timer | 2822 -- Set up timer |
| 2809 Debug("%s: Beginning Salvage loot timer for spellID %d", event_name, spell_id) | 2823 ClearChatLootData() |
| 2824 Debug("%s: Beginning chat-based loot timer for spellID %d", event_name, spell_id) | |
| 2810 chat_loot_timer_handle = C_Timer.NewTimer(1, ClearChatLootData) | 2825 chat_loot_timer_handle = C_Timer.NewTimer(1, ClearChatLootData) |
| 2811 | 2826 |
| 2812 -- Standard item handling setup | 2827 -- Standard item handling setup |
| 2813 table.wipe(current_action) | 2828 table.wipe(current_action) |
| 2814 current_loot = nil | 2829 current_loot = nil |
