From f2e1a0928d24b7de4d4f86619bf5b6fecbc380f4 Mon Sep 17 00:00:00 2001 From: Bunny67 Date: Tue, 2 Jun 2020 23:40:06 +0300 Subject: [PATCH] init --- WeakAuras/ArchiveTypes/Repository.lua | 118 + WeakAuras/AuraEnvironment.lua | 270 + WeakAuras/Bindings.xml | 11 + WeakAuras/BuffTrigger.lua | 1949 + WeakAuras/BuffTrigger2.lua | 3121 + WeakAuras/CHANGELOG.md | 16 + WeakAuras/DefaultOptions.lua | 4 + WeakAuras/GenericTrigger.lua | 3765 + WeakAuras/History.lua | 76 + WeakAuras/Init.lua | 78 + WeakAuras/LICENSE | 339 + WeakAuras/Libs/AceComm-3.0/AceComm-3.0.lua | 308 + WeakAuras/Libs/AceComm-3.0/AceComm-3.0.xml | 5 + .../Libs/AceComm-3.0/ChatThrottleLib.lua | 523 + .../Libs/AceConfig-3.0/AceConfig-3.0.lua | 58 + .../Libs/AceConfig-3.0/AceConfig-3.0.xml | 8 + .../AceConfigCmd-3.0/AceConfigCmd-3.0.lua | 794 + .../AceConfigCmd-3.0/AceConfigCmd-3.0.xml | 4 + .../AceConfigDialog-3.0.lua | 1962 + .../AceConfigDialog-3.0.xml | 4 + .../AceConfigRegistry-3.0.lua | 371 + .../AceConfigRegistry-3.0.xml | 4 + .../Libs/AceConsole-3.0/AceConsole-3.0.lua | 250 + .../Libs/AceConsole-3.0/AceConsole-3.0.xml | 4 + WeakAuras/Libs/AceEvent-3.0/AceEvent-3.0.lua | 126 + WeakAuras/Libs/AceEvent-3.0/AceEvent-3.0.xml | 4 + .../BackgroundWidget.lua | 235 + .../BorderWidget.lua | 230 + .../FontWidget.lua | 216 + .../SoundWidget.lua | 264 + .../StatusbarWidget.lua | 233 + .../prototypes.lua | 266 + .../AceGUI-3.0-SharedMediaWidgets/widget.xml | 9 + WeakAuras/Libs/AceGUI-3.0/AceGUI-3.0.lua | 1034 + WeakAuras/Libs/AceGUI-3.0/AceGUI-3.0.xml | 28 + .../AceGUIContainer-BlizOptionsGroup.lua | 138 + .../widgets/AceGUIContainer-DropDownGroup.lua | 157 + .../widgets/AceGUIContainer-Frame.lua | 316 + .../widgets/AceGUIContainer-InlineGroup.lua | 103 + .../widgets/AceGUIContainer-ScrollFrame.lua | 215 + .../widgets/AceGUIContainer-SimpleGroup.lua | 69 + .../widgets/AceGUIContainer-TabGroup.lua | 349 + .../widgets/AceGUIContainer-TreeGroup.lua | 705 + .../widgets/AceGUIContainer-Window.lua | 336 + .../widgets/AceGUIWidget-Button.lua | 103 + .../widgets/AceGUIWidget-CheckBox.lua | 296 + .../widgets/AceGUIWidget-ColorPicker.lua | 190 + .../widgets/AceGUIWidget-DropDown-Items.lua | 471 + .../widgets/AceGUIWidget-DropDown.lua | 745 + .../widgets/AceGUIWidget-EditBox.lua | 263 + .../widgets/AceGUIWidget-Heading.lua | 78 + .../AceGUI-3.0/widgets/AceGUIWidget-Icon.lua | 140 + .../widgets/AceGUIWidget-InteractiveLabel.lua | 94 + .../widgets/AceGUIWidget-Keybinding.lua | 249 + .../AceGUI-3.0/widgets/AceGUIWidget-Label.lua | 178 + .../widgets/AceGUIWidget-MultiLineEditBox.lua | 366 + .../widgets/AceGUIWidget-Slider.lua | 280 + .../AceSerializer-3.0/AceSerializer-3.0.lua | 281 + .../AceSerializer-3.0/AceSerializer-3.0.xml | 4 + WeakAuras/Libs/AceTimer-3.0/AceTimer-3.0.lua | 274 + WeakAuras/Libs/AceTimer-3.0/AceTimer-3.0.xml | 4 + WeakAuras/Libs/Archivist/Archivist.lua | 578 + WeakAuras/Libs/Archivist/Archivist.toc | 17 + WeakAuras/Libs/Archivist/Archivist.xml | 7 + WeakAuras/Libs/Archivist/LICENSE.md | 41 + WeakAuras/Libs/Archivist/README.md | 300 + .../Archivist/libs/LibDeflate/LICENSE.txt | 165 + .../Archivist/libs/LibDeflate/LibDeflate.lua | 3509 + .../Libs/Archivist/libs/LibStub/LibStub.lua | 30 + WeakAuras/Libs/Archivist/stores/RawData.lua | 28 + WeakAuras/Libs/Archivist/stores/ReadOnly.lua | 43 + .../CallbackHandler-1.0.lua | 238 + .../CallbackHandler-1.0.xml | 4 + WeakAuras/Libs/LibCompress/LibCompress.lua | 1255 + WeakAuras/Libs/LibCompress/LibCompress.toc | 14 + WeakAuras/Libs/LibCompress/lib.xml | 4 + .../LibCustomGlow-1.0/LibCustomGlow-1.0.lua | 929 + .../LibCustomGlow-1.0/LibCustomGlow-1.0.toc | 12 + .../LibCustomGlow-1.0/LibCustomGlow-1.0.xml | 4 + WeakAuras/Libs/LibCustomGlow-1.0/README.md | 52 + WeakAuras/Libs/LibCustomGlow-1.0/am_29.blp | Bin 0 -> 88580 bytes .../Libs/LibCustomGlow-1.0/artifacts.blp | Bin 0 -> 1049748 bytes .../Libs/LibDBIcon-1.0/LibDBIcon-1.0.lua | 300 + WeakAuras/Libs/LibDBIcon-1.0/lib.xml | 7 + .../LibDataBroker-1.1/LibDataBroker-1.1.lua | 90 + .../Libs/LibDataBroker-1.1/README.textile | 13 + WeakAuras/Libs/LibDeflate/LibDeflate.lua | 3509 + .../CallbackHandler-1.0.lua | 238 + .../CallbackHandler-1.0.xml | 4 + .../Libs/LibGetFrame-1.0/LibGetFrame-1.0.lua | 221 + .../Libs/LibGetFrame-1.0/LibGetFrame-1.0.toc | 13 + .../Libs/LibGetFrame-1.0/LibGetFrame-1.0.xml | 6 + .../Libs/LibGetFrame-1.0/LibStub/LibStub.lua | 51 + .../Libs/LibGetFrame-1.0/LibStub/LibStub.toc | 9 + .../LibGetFrame-1.0/LibStub/tests/test.lua | 41 + .../LibGetFrame-1.0/LibStub/tests/test2.lua | 27 + .../LibGetFrame-1.0/LibStub/tests/test3.lua | 14 + .../LibGetFrame-1.0/LibStub/tests/test4.lua | 41 + WeakAuras/Libs/LibGetFrame-1.0/README.md | 138 + .../LibRangeCheck-2.0/LibRangeCheck-2.0.lua | 1033 + .../LibSharedMedia-3.0/LibSharedMedia-3.0.lua | 245 + WeakAuras/Libs/LibSharedMedia-3.0/lib.xml | 4 + .../LibSpellRange-1.0/LibSpellRange-1.0.lua | 235 + .../LibSpellRange-1.0/LibSpellRange-1.0.toc | 11 + WeakAuras/Libs/LibSpellRange-1.0/README.md | 61 + WeakAuras/Libs/LibSpellRange-1.0/lib.xml | 3 + WeakAuras/Libs/LibStub/LibStub.lua | 51 + WeakAuras/Libs/LibStub/LibStub.toc | 9 + WeakAuras/Libs/LibStub/tests/test.lua | 41 + WeakAuras/Libs/LibStub/tests/test2.lua | 27 + WeakAuras/Libs/LibStub/tests/test3.lua | 14 + WeakAuras/Libs/LibStub/tests/test4.lua | 41 + WeakAuras/Locales/deDE.lua | 1297 + WeakAuras/Locales/enUS.lua | 899 + WeakAuras/Locales/esES.lua | 1416 + WeakAuras/Locales/esMX.lua | 1322 + WeakAuras/Locales/frFR.lua | 1069 + WeakAuras/Locales/itIT.lua | 1615 + WeakAuras/Locales/koKR.lua | 1107 + WeakAuras/Locales/ptBR.lua | 1489 + WeakAuras/Locales/ruRU.lua | 954 + WeakAuras/Locales/zhCN.lua | 985 + WeakAuras/Locales/zhTW.lua | 890 + .../Creative Commons - Attribution 3.0.txt | 74 + ...ommons - Attribution-NonCommercial 3.0.txt | 75 + .../Creative Commons - Sampling Plus 1.0.txt | 1 + WeakAuras/Media/Fira LICENSE | 94 + WeakAuras/Media/Fonts/FiraMono-Medium.ttf | Bin 0 -> 173516 bytes WeakAuras/Media/Provided by.txt | 34 + WeakAuras/Media/Sounds/Adds.ogg | Bin 0 -> 22667 bytes WeakAuras/Media/Sounds/AirHorn.ogg | Bin 0 -> 15159 bytes WeakAuras/Media/Sounds/Applause.ogg | Bin 0 -> 19626 bytes WeakAuras/Media/Sounds/BananaPeelSlip.ogg | Bin 0 -> 6418 bytes WeakAuras/Media/Sounds/BatmanPunch.ogg | Bin 0 -> 7100 bytes WeakAuras/Media/Sounds/BikeHorn.ogg | Bin 0 -> 8643 bytes WeakAuras/Media/Sounds/Blast.ogg | Bin 0 -> 14745 bytes WeakAuras/Media/Sounds/Bleat.ogg | Bin 0 -> 13083 bytes WeakAuras/Media/Sounds/Boss.ogg | Bin 0 -> 19542 bytes WeakAuras/Media/Sounds/BoxingArenaSound.ogg | Bin 0 -> 18602 bytes .../Media/Sounds/CartoonVoiceBaritone.ogg | Bin 0 -> 16098 bytes WeakAuras/Media/Sounds/CartoonWalking.ogg | Bin 0 -> 17362 bytes WeakAuras/Media/Sounds/CatMeow2.ogg | Bin 0 -> 10488 bytes WeakAuras/Media/Sounds/Circle.ogg | Bin 0 -> 19981 bytes WeakAuras/Media/Sounds/CowMooing.ogg | Bin 0 -> 13453 bytes WeakAuras/Media/Sounds/Cross.ogg | Bin 0 -> 18975 bytes WeakAuras/Media/Sounds/Diamond.ogg | Bin 0 -> 18424 bytes WeakAuras/Media/Sounds/DontRelease.ogg | Bin 0 -> 22360 bytes WeakAuras/Media/Sounds/Empowered.ogg | Bin 0 -> 25291 bytes WeakAuras/Media/Sounds/Focus.ogg | Bin 0 -> 22770 bytes WeakAuras/Media/Sounds/Idiot.ogg | Bin 0 -> 19393 bytes WeakAuras/Media/Sounds/KittenMeow.ogg | Bin 0 -> 5695 bytes WeakAuras/Media/Sounds/Left.ogg | Bin 0 -> 14875 bytes WeakAuras/Media/Sounds/Moon.ogg | Bin 0 -> 16132 bytes WeakAuras/Media/Sounds/Next.ogg | Bin 0 -> 18171 bytes WeakAuras/Media/Sounds/Portal.ogg | Bin 0 -> 19488 bytes WeakAuras/Media/Sounds/Protected.ogg | Bin 0 -> 23543 bytes WeakAuras/Media/Sounds/Release.ogg | Bin 0 -> 21329 bytes WeakAuras/Media/Sounds/Right.ogg | Bin 0 -> 18782 bytes WeakAuras/Media/Sounds/RingingPhone.ogg | Bin 0 -> 16459 bytes WeakAuras/Media/Sounds/RoaringLion.ogg | Bin 0 -> 16579 bytes WeakAuras/Media/Sounds/RobotBlip.ogg | Bin 0 -> 7950 bytes WeakAuras/Media/Sounds/RunAway.ogg | Bin 0 -> 22183 bytes WeakAuras/Media/Sounds/SharpPunch.ogg | Bin 0 -> 7317 bytes WeakAuras/Media/Sounds/Shotgun.ogg | Bin 0 -> 10578 bytes WeakAuras/Media/Sounds/Skull.ogg | Bin 0 -> 18908 bytes WeakAuras/Media/Sounds/Spread.ogg | Bin 0 -> 25738 bytes WeakAuras/Media/Sounds/Square.ogg | Bin 0 -> 19381 bytes WeakAuras/Media/Sounds/SquishFart.ogg | Bin 0 -> 5929 bytes WeakAuras/Media/Sounds/Stack.ogg | Bin 0 -> 22432 bytes WeakAuras/Media/Sounds/Star.ogg | Bin 0 -> 20477 bytes WeakAuras/Media/Sounds/Switch.ogg | Bin 0 -> 22920 bytes WeakAuras/Media/Sounds/Taunt.ogg | Bin 0 -> 17303 bytes WeakAuras/Media/Sounds/TempleBellHuge.ogg | Bin 0 -> 31039 bytes WeakAuras/Media/Sounds/Torch.ogg | Bin 0 -> 18719 bytes WeakAuras/Media/Sounds/Triangle.ogg | Bin 0 -> 20518 bytes WeakAuras/Media/Sounds/WarningSiren.ogg | Bin 0 -> 28302 bytes WeakAuras/Media/Sounds/WaterDrop.ogg | Bin 0 -> 5565 bytes WeakAuras/Media/Textures/Circle_Smooth.tga | Bin 0 -> 105004 bytes WeakAuras/Media/Textures/Circle_Smooth2.tga | Bin 0 -> 262188 bytes .../Media/Textures/Circle_Smooth_Border.tga | Bin 0 -> 123576 bytes WeakAuras/Media/Textures/Circle_Squirrel.tga | Bin 0 -> 146826 bytes .../Media/Textures/Circle_Squirrel_Border.tga | Bin 0 -> 158074 bytes WeakAuras/Media/Textures/Circle_White.tga | Bin 0 -> 11050 bytes .../Media/Textures/Circle_White_Border.tga | Bin 0 -> 33598 bytes WeakAuras/Media/Textures/PRDFrame.tga | Bin 0 -> 18552 bytes WeakAuras/Media/Textures/PRDFrameKui.tga | Bin 0 -> 643 bytes WeakAuras/Media/Textures/Square_FullWhite.tga | Bin 0 -> 530 bytes WeakAuras/Media/Textures/Square_Smooth.tga | Bin 0 -> 62159 bytes .../Media/Textures/Square_Smooth_Border.tga | Bin 0 -> 99406 bytes .../Media/Textures/Square_Smooth_Border2.tga | Bin 0 -> 118496 bytes WeakAuras/Media/Textures/Square_Squirrel.tga | Bin 0 -> 136807 bytes .../Media/Textures/Square_Squirrel_Border.tga | Bin 0 -> 136567 bytes WeakAuras/Media/Textures/Square_White.tga | Bin 0 -> 5144 bytes .../Media/Textures/Square_White_Border.tga | Bin 0 -> 27189 bytes WeakAuras/Media/Textures/StripedTexture.tga | Bin 0 -> 18976 bytes WeakAuras/Media/Textures/Trapezoid.tga | Bin 0 -> 262188 bytes WeakAuras/Media/Textures/Triangle45.tga | Bin 0 -> 1293 bytes WeakAuras/Media/Textures/add.tga | Bin 0 -> 4140 bytes WeakAuras/Media/Textures/arrows_target.tga | Bin 0 -> 2998 bytes WeakAuras/Media/Textures/bullet1.tga | Bin 0 -> 892 bytes WeakAuras/Media/Textures/bullet2.tga | Bin 0 -> 2000 bytes WeakAuras/Media/Textures/bullet3.tga | Bin 0 -> 487 bytes WeakAuras/Media/Textures/cancel-icon.tga | Bin 0 -> 16428 bytes WeakAuras/Media/Textures/cancel-mark.tga | Bin 0 -> 65580 bytes WeakAuras/Media/Textures/circle_border5.tga | Bin 0 -> 16402 bytes WeakAuras/Media/Textures/collapse.tga | Bin 0 -> 16428 bytes WeakAuras/Media/Textures/delete.tga | Bin 0 -> 4140 bytes WeakAuras/Media/Textures/downleft.tga | Bin 0 -> 534 bytes WeakAuras/Media/Textures/downright.tga | Bin 0 -> 534 bytes WeakAuras/Media/Textures/duplicate.tga | Bin 0 -> 4140 bytes WeakAuras/Media/Textures/edit.tga | Bin 0 -> 1356 bytes WeakAuras/Media/Textures/editdown.tga | Bin 0 -> 1356 bytes WeakAuras/Media/Textures/emoji.tga | Bin 0 -> 65580 bytes WeakAuras/Media/Textures/exclamation-mark.tga | Bin 0 -> 16428 bytes WeakAuras/Media/Textures/expand.tga | Bin 0 -> 16428 bytes WeakAuras/Media/Textures/eyes.tga | Bin 0 -> 32812 bytes WeakAuras/Media/Textures/icon.blp | Bin 0 -> 2564 bytes WeakAuras/Media/Textures/importsmall.tga | Bin 0 -> 1068 bytes WeakAuras/Media/Textures/interrupt.tga | Bin 0 -> 7903 bytes WeakAuras/Media/Textures/magnetic.tga | Bin 0 -> 1068 bytes WeakAuras/Media/Textures/movedown.tga | Bin 0 -> 4140 bytes WeakAuras/Media/Textures/moveup.tga | Bin 0 -> 4140 bytes WeakAuras/Media/Textures/newaura.tga | Bin 0 -> 1068 bytes WeakAuras/Media/Textures/ok-icon.tga | Bin 0 -> 16428 bytes WeakAuras/Media/Textures/rainbowbar.tga | Bin 0 -> 898 bytes WeakAuras/Media/Textures/reset.tga | Bin 0 -> 4140 bytes WeakAuras/Media/Textures/ring_glow3.tga | Bin 0 -> 65554 bytes WeakAuras/Media/Textures/square_mini.tga | Bin 0 -> 16402 bytes WeakAuras/Media/Textures/stripe-bar.tga | Bin 0 -> 32812 bytes .../Media/Textures/stripe-rainbow-bar.tga | Bin 0 -> 32812 bytes WeakAuras/Media/Textures/target_indicator.tga | Bin 0 -> 1310 bytes .../Media/Textures/target_indicator_glow.tga | Bin 0 -> 3036 bytes WeakAuras/Media/Textures/targeting-mark.tga | Bin 0 -> 65580 bytes WeakAuras/Media/Textures/template.tga | Bin 0 -> 16428 bytes WeakAuras/Media/Textures/triangle-border.tga | Bin 0 -> 65580 bytes WeakAuras/Media/Textures/triangle.tga | Bin 0 -> 65580 bytes WeakAuras/Media/Textures/upleft.tga | Bin 0 -> 534 bytes WeakAuras/Media/Textures/upright.tga | Bin 0 -> 534 bytes WeakAuras/Media/Textures/wagoupdate_logo.tga | Bin 0 -> 4140 bytes .../Media/Textures/wagoupdate_refresh.tga | Bin 0 -> 4140 bytes WeakAuras/Pools.lua | 324 + WeakAuras/PowerAurasMedia/Auras/Aura1.tga | Bin 0 -> 59040 bytes WeakAuras/PowerAurasMedia/Auras/Aura10.tga | Bin 0 -> 20559 bytes WeakAuras/PowerAurasMedia/Auras/Aura100.tga | Bin 0 -> 13653 bytes WeakAuras/PowerAurasMedia/Auras/Aura101.tga | Bin 0 -> 9295 bytes WeakAuras/PowerAurasMedia/Auras/Aura102.tga | Bin 0 -> 14040 bytes WeakAuras/PowerAurasMedia/Auras/Aura103.tga | Bin 0 -> 8736 bytes WeakAuras/PowerAurasMedia/Auras/Aura104.tga | Bin 0 -> 1744 bytes WeakAuras/PowerAurasMedia/Auras/Aura105.tga | Bin 0 -> 10604 bytes WeakAuras/PowerAurasMedia/Auras/Aura106.tga | Bin 0 -> 2568 bytes WeakAuras/PowerAurasMedia/Auras/Aura107.tga | Bin 0 -> 2566 bytes WeakAuras/PowerAurasMedia/Auras/Aura108.tga | Bin 0 -> 3920 bytes WeakAuras/PowerAurasMedia/Auras/Aura109.tga | Bin 0 -> 4338 bytes WeakAuras/PowerAurasMedia/Auras/Aura11.tga | Bin 0 -> 49046 bytes WeakAuras/PowerAurasMedia/Auras/Aura110.tga | Bin 0 -> 6992 bytes WeakAuras/PowerAurasMedia/Auras/Aura111.tga | Bin 0 -> 6526 bytes WeakAuras/PowerAurasMedia/Auras/Aura112.tga | Bin 0 -> 11553 bytes WeakAuras/PowerAurasMedia/Auras/Aura113.tga | Bin 0 -> 13018 bytes WeakAuras/PowerAurasMedia/Auras/Aura114.tga | Bin 0 -> 4338 bytes WeakAuras/PowerAurasMedia/Auras/Aura115.tga | Bin 0 -> 4398 bytes WeakAuras/PowerAurasMedia/Auras/Aura116.tga | Bin 0 -> 1178 bytes WeakAuras/PowerAurasMedia/Auras/Aura117.tga | Bin 0 -> 8756 bytes WeakAuras/PowerAurasMedia/Auras/Aura118.tga | Bin 0 -> 12746 bytes WeakAuras/PowerAurasMedia/Auras/Aura119.tga | Bin 0 -> 6086 bytes WeakAuras/PowerAurasMedia/Auras/Aura12.tga | Bin 0 -> 24220 bytes WeakAuras/PowerAurasMedia/Auras/Aura120.tga | Bin 0 -> 2726 bytes WeakAuras/PowerAurasMedia/Auras/Aura121.tga | Bin 0 -> 3463 bytes WeakAuras/PowerAurasMedia/Auras/Aura122.tga | Bin 0 -> 4941 bytes WeakAuras/PowerAurasMedia/Auras/Aura123.tga | Bin 0 -> 4913 bytes WeakAuras/PowerAurasMedia/Auras/Aura124.tga | Bin 0 -> 5441 bytes WeakAuras/PowerAurasMedia/Auras/Aura125.tga | Bin 0 -> 8670 bytes WeakAuras/PowerAurasMedia/Auras/Aura126.tga | Bin 0 -> 6046 bytes WeakAuras/PowerAurasMedia/Auras/Aura127.tga | Bin 0 -> 7761 bytes WeakAuras/PowerAurasMedia/Auras/Aura128.tga | Bin 0 -> 6714 bytes WeakAuras/PowerAurasMedia/Auras/Aura129.tga | Bin 0 -> 16826 bytes WeakAuras/PowerAurasMedia/Auras/Aura13.tga | Bin 0 -> 19201 bytes WeakAuras/PowerAurasMedia/Auras/Aura130.tga | Bin 0 -> 19979 bytes WeakAuras/PowerAurasMedia/Auras/Aura131.tga | Bin 0 -> 20623 bytes WeakAuras/PowerAurasMedia/Auras/Aura132.tga | Bin 0 -> 9370 bytes WeakAuras/PowerAurasMedia/Auras/Aura133.tga | Bin 0 -> 25244 bytes WeakAuras/PowerAurasMedia/Auras/Aura134.tga | Bin 0 -> 21442 bytes WeakAuras/PowerAurasMedia/Auras/Aura135.tga | Bin 0 -> 24696 bytes WeakAuras/PowerAurasMedia/Auras/Aura136.tga | Bin 0 -> 26964 bytes WeakAuras/PowerAurasMedia/Auras/Aura137.tga | Bin 0 -> 24109 bytes WeakAuras/PowerAurasMedia/Auras/Aura138.tga | Bin 0 -> 24489 bytes WeakAuras/PowerAurasMedia/Auras/Aura139.tga | Bin 0 -> 13946 bytes WeakAuras/PowerAurasMedia/Auras/Aura14.tga | Bin 0 -> 19362 bytes WeakAuras/PowerAurasMedia/Auras/Aura140.tga | Bin 0 -> 10766 bytes WeakAuras/PowerAurasMedia/Auras/Aura141.tga | Bin 0 -> 14909 bytes WeakAuras/PowerAurasMedia/Auras/Aura142.tga | Bin 0 -> 11660 bytes WeakAuras/PowerAurasMedia/Auras/Aura143.tga | Bin 0 -> 108359 bytes WeakAuras/PowerAurasMedia/Auras/Aura144.tga | Bin 0 -> 38126 bytes WeakAuras/PowerAurasMedia/Auras/Aura145.tga | Bin 0 -> 37122 bytes WeakAuras/PowerAurasMedia/Auras/Aura15.tga | Bin 0 -> 15324 bytes WeakAuras/PowerAurasMedia/Auras/Aura16.tga | Bin 0 -> 29859 bytes WeakAuras/PowerAurasMedia/Auras/Aura17.tga | Bin 0 -> 43645 bytes WeakAuras/PowerAurasMedia/Auras/Aura18.tga | Bin 0 -> 60560 bytes WeakAuras/PowerAurasMedia/Auras/Aura19.tga | Bin 0 -> 27562 bytes WeakAuras/PowerAurasMedia/Auras/Aura2.tga | Bin 0 -> 50052 bytes WeakAuras/PowerAurasMedia/Auras/Aura20.tga | Bin 0 -> 10435 bytes WeakAuras/PowerAurasMedia/Auras/Aura21.tga | Bin 0 -> 17494 bytes WeakAuras/PowerAurasMedia/Auras/Aura22.tga | Bin 0 -> 14134 bytes WeakAuras/PowerAurasMedia/Auras/Aura23.tga | Bin 0 -> 51561 bytes WeakAuras/PowerAurasMedia/Auras/Aura24.tga | Bin 0 -> 43559 bytes WeakAuras/PowerAurasMedia/Auras/Aura25.tga | Bin 0 -> 24187 bytes WeakAuras/PowerAurasMedia/Auras/Aura26.tga | Bin 0 -> 14861 bytes WeakAuras/PowerAurasMedia/Auras/Aura27.tga | Bin 0 -> 28243 bytes WeakAuras/PowerAurasMedia/Auras/Aura28.tga | Bin 0 -> 65966 bytes WeakAuras/PowerAurasMedia/Auras/Aura29.tga | Bin 0 -> 19366 bytes WeakAuras/PowerAurasMedia/Auras/Aura3.tga | Bin 0 -> 55847 bytes WeakAuras/PowerAurasMedia/Auras/Aura30.tga | Bin 0 -> 20838 bytes WeakAuras/PowerAurasMedia/Auras/Aura31.tga | Bin 0 -> 20557 bytes WeakAuras/PowerAurasMedia/Auras/Aura32.tga | Bin 0 -> 39911 bytes WeakAuras/PowerAurasMedia/Auras/Aura33.tga | Bin 0 -> 25772 bytes WeakAuras/PowerAurasMedia/Auras/Aura34.tga | Bin 0 -> 31511 bytes WeakAuras/PowerAurasMedia/Auras/Aura35.tga | Bin 0 -> 18731 bytes WeakAuras/PowerAurasMedia/Auras/Aura36.tga | Bin 0 -> 10515 bytes WeakAuras/PowerAurasMedia/Auras/Aura37.tga | Bin 0 -> 14419 bytes WeakAuras/PowerAurasMedia/Auras/Aura38.tga | Bin 0 -> 14049 bytes WeakAuras/PowerAurasMedia/Auras/Aura39.tga | Bin 0 -> 12120 bytes WeakAuras/PowerAurasMedia/Auras/Aura4.tga | Bin 0 -> 61687 bytes WeakAuras/PowerAurasMedia/Auras/Aura40.tga | Bin 0 -> 14033 bytes WeakAuras/PowerAurasMedia/Auras/Aura41.tga | Bin 0 -> 11688 bytes WeakAuras/PowerAurasMedia/Auras/Aura42.tga | Bin 0 -> 12141 bytes WeakAuras/PowerAurasMedia/Auras/Aura43.tga | Bin 0 -> 12272 bytes WeakAuras/PowerAurasMedia/Auras/Aura44.tga | Bin 0 -> 12210 bytes WeakAuras/PowerAurasMedia/Auras/Aura45.tga | Bin 0 -> 37594 bytes WeakAuras/PowerAurasMedia/Auras/Aura46.tga | Bin 0 -> 4531 bytes WeakAuras/PowerAurasMedia/Auras/Aura47.tga | Bin 0 -> 5557 bytes WeakAuras/PowerAurasMedia/Auras/Aura48.tga | Bin 0 -> 14669 bytes WeakAuras/PowerAurasMedia/Auras/Aura49.tga | Bin 0 -> 43432 bytes WeakAuras/PowerAurasMedia/Auras/Aura5.tga | Bin 0 -> 42773 bytes WeakAuras/PowerAurasMedia/Auras/Aura50.tga | Bin 0 -> 86103 bytes WeakAuras/PowerAurasMedia/Auras/Aura51.tga | Bin 0 -> 13630 bytes WeakAuras/PowerAurasMedia/Auras/Aura52.tga | Bin 0 -> 16779 bytes WeakAuras/PowerAurasMedia/Auras/Aura53.tga | Bin 0 -> 14242 bytes WeakAuras/PowerAurasMedia/Auras/Aura54.tga | Bin 0 -> 6144 bytes WeakAuras/PowerAurasMedia/Auras/Aura55.tga | Bin 0 -> 38599 bytes WeakAuras/PowerAurasMedia/Auras/Aura56.tga | Bin 0 -> 45035 bytes WeakAuras/PowerAurasMedia/Auras/Aura57.tga | Bin 0 -> 65888 bytes WeakAuras/PowerAurasMedia/Auras/Aura58.tga | Bin 0 -> 72734 bytes WeakAuras/PowerAurasMedia/Auras/Aura59.tga | Bin 0 -> 42470 bytes WeakAuras/PowerAurasMedia/Auras/Aura6.tga | Bin 0 -> 69737 bytes WeakAuras/PowerAurasMedia/Auras/Aura60.tga | Bin 0 -> 42703 bytes WeakAuras/PowerAurasMedia/Auras/Aura61.tga | Bin 0 -> 6472 bytes WeakAuras/PowerAurasMedia/Auras/Aura62.tga | Bin 0 -> 6253 bytes WeakAuras/PowerAurasMedia/Auras/Aura63.tga | Bin 0 -> 37813 bytes WeakAuras/PowerAurasMedia/Auras/Aura64.tga | Bin 0 -> 37193 bytes WeakAuras/PowerAurasMedia/Auras/Aura65.tga | Bin 0 -> 4742 bytes WeakAuras/PowerAurasMedia/Auras/Aura66.tga | Bin 0 -> 4006 bytes WeakAuras/PowerAurasMedia/Auras/Aura67.tga | Bin 0 -> 4742 bytes WeakAuras/PowerAurasMedia/Auras/Aura68.tga | Bin 0 -> 44126 bytes WeakAuras/PowerAurasMedia/Auras/Aura69.tga | Bin 0 -> 7080 bytes WeakAuras/PowerAurasMedia/Auras/Aura7.tga | Bin 0 -> 27358 bytes WeakAuras/PowerAurasMedia/Auras/Aura70.tga | Bin 0 -> 6898 bytes WeakAuras/PowerAurasMedia/Auras/Aura71.tga | Bin 0 -> 99361 bytes WeakAuras/PowerAurasMedia/Auras/Aura72.tga | Bin 0 -> 22479 bytes WeakAuras/PowerAurasMedia/Auras/Aura73.tga | Bin 0 -> 43920 bytes WeakAuras/PowerAurasMedia/Auras/Aura74.tga | Bin 0 -> 5144 bytes WeakAuras/PowerAurasMedia/Auras/Aura75.tga | Bin 0 -> 14268 bytes WeakAuras/PowerAurasMedia/Auras/Aura76.tga | Bin 0 -> 9593 bytes WeakAuras/PowerAurasMedia/Auras/Aura77.tga | Bin 0 -> 19221 bytes WeakAuras/PowerAurasMedia/Auras/Aura78.tga | Bin 0 -> 10167 bytes WeakAuras/PowerAurasMedia/Auras/Aura79.tga | Bin 0 -> 19760 bytes WeakAuras/PowerAurasMedia/Auras/Aura8.tga | Bin 0 -> 25996 bytes WeakAuras/PowerAurasMedia/Auras/Aura80.tga | Bin 0 -> 18400 bytes WeakAuras/PowerAurasMedia/Auras/Aura81.tga | Bin 0 -> 44980 bytes WeakAuras/PowerAurasMedia/Auras/Aura82.tga | Bin 0 -> 9840 bytes WeakAuras/PowerAurasMedia/Auras/Aura83.tga | Bin 0 -> 11520 bytes WeakAuras/PowerAurasMedia/Auras/Aura84.tga | Bin 0 -> 7734 bytes WeakAuras/PowerAurasMedia/Auras/Aura85.tga | Bin 0 -> 8817 bytes WeakAuras/PowerAurasMedia/Auras/Aura86.tga | Bin 0 -> 8803 bytes WeakAuras/PowerAurasMedia/Auras/Aura87.tga | Bin 0 -> 5854 bytes WeakAuras/PowerAurasMedia/Auras/Aura88.tga | Bin 0 -> 32253 bytes WeakAuras/PowerAurasMedia/Auras/Aura89.tga | Bin 0 -> 10646 bytes WeakAuras/PowerAurasMedia/Auras/Aura9.tga | Bin 0 -> 11346 bytes WeakAuras/PowerAurasMedia/Auras/Aura90.tga | Bin 0 -> 10100 bytes WeakAuras/PowerAurasMedia/Auras/Aura91.tga | Bin 0 -> 9988 bytes WeakAuras/PowerAurasMedia/Auras/Aura92.tga | Bin 0 -> 10745 bytes WeakAuras/PowerAurasMedia/Auras/Aura93.tga | Bin 0 -> 9074 bytes WeakAuras/PowerAurasMedia/Auras/Aura94.tga | Bin 0 -> 17432 bytes WeakAuras/PowerAurasMedia/Auras/Aura95.tga | Bin 0 -> 11235 bytes WeakAuras/PowerAurasMedia/Auras/Aura96.tga | Bin 0 -> 7140 bytes WeakAuras/PowerAurasMedia/Auras/Aura97.tga | Bin 0 -> 21210 bytes WeakAuras/PowerAurasMedia/Auras/Aura98.tga | Bin 0 -> 21116 bytes WeakAuras/PowerAurasMedia/Auras/Aura99.tga | Bin 0 -> 20124 bytes .../PowerAurasMedia/Sounds/Arrow_Swoosh.ogg | Bin 0 -> 8361 bytes WeakAuras/PowerAurasMedia/Sounds/BITE.ogg | Bin 0 -> 6360 bytes WeakAuras/PowerAurasMedia/Sounds/ESPARK1.ogg | Bin 0 -> 24358 bytes WeakAuras/PowerAurasMedia/Sounds/Fireball.ogg | Bin 0 -> 25069 bytes WeakAuras/PowerAurasMedia/Sounds/Gasp.ogg | Bin 0 -> 11062 bytes WeakAuras/PowerAurasMedia/Sounds/PUNCH.ogg | Bin 0 -> 6672 bytes .../PowerAurasMedia/Sounds/Squeakypig.ogg | Bin 0 -> 87931 bytes WeakAuras/PowerAurasMedia/Sounds/aggro.ogg | Bin 0 -> 10323 bytes WeakAuras/PowerAurasMedia/Sounds/bam.ogg | Bin 0 -> 15001 bytes .../PowerAurasMedia/Sounds/bear_polar.ogg | Bin 0 -> 23323 bytes WeakAuras/PowerAurasMedia/Sounds/bigkiss.ogg | Bin 0 -> 23517 bytes WeakAuras/PowerAurasMedia/Sounds/burp4.ogg | Bin 0 -> 38066 bytes WeakAuras/PowerAurasMedia/Sounds/cat2.ogg | Bin 0 -> 16721 bytes WeakAuras/PowerAurasMedia/Sounds/chant2.ogg | Bin 0 -> 53204 bytes WeakAuras/PowerAurasMedia/Sounds/chant4.ogg | Bin 0 -> 63078 bytes WeakAuras/PowerAurasMedia/Sounds/chimes.ogg | Bin 0 -> 59299 bytes WeakAuras/PowerAurasMedia/Sounds/cookie.ogg | Bin 0 -> 80147 bytes .../PowerAurasMedia/Sounds/heartbeat.ogg | Bin 0 -> 35793 bytes WeakAuras/PowerAurasMedia/Sounds/hic3.ogg | Bin 0 -> 7608 bytes WeakAuras/PowerAurasMedia/Sounds/huh_1.ogg | Bin 0 -> 10122 bytes .../PowerAurasMedia/Sounds/hurricane.ogg | Bin 0 -> 41337 bytes WeakAuras/PowerAurasMedia/Sounds/hyena.ogg | Bin 0 -> 20599 bytes WeakAuras/PowerAurasMedia/Sounds/kaching.ogg | Bin 0 -> 12162 bytes WeakAuras/PowerAurasMedia/Sounds/moan.ogg | Bin 0 -> 31262 bytes WeakAuras/PowerAurasMedia/Sounds/panther1.ogg | Bin 0 -> 13248 bytes WeakAuras/PowerAurasMedia/Sounds/phone.ogg | Bin 0 -> 16775 bytes WeakAuras/PowerAurasMedia/Sounds/rainroof.ogg | Bin 0 -> 41242 bytes WeakAuras/PowerAurasMedia/Sounds/rocket.ogg | Bin 0 -> 37521 bytes .../PowerAurasMedia/Sounds/shipswhistle.ogg | Bin 0 -> 32510 bytes WeakAuras/PowerAurasMedia/Sounds/shot.ogg | Bin 0 -> 28108 bytes WeakAuras/PowerAurasMedia/Sounds/snakeatt.ogg | Bin 0 -> 17966 bytes WeakAuras/PowerAurasMedia/Sounds/sneeze.ogg | Bin 0 -> 16376 bytes WeakAuras/PowerAurasMedia/Sounds/sonar.ogg | Bin 0 -> 25876 bytes WeakAuras/PowerAurasMedia/Sounds/splash.ogg | Bin 0 -> 22195 bytes .../PowerAurasMedia/Sounds/swordecho.ogg | Bin 0 -> 12823 bytes .../PowerAurasMedia/Sounds/throwknife.ogg | Bin 0 -> 14696 bytes WeakAuras/PowerAurasMedia/Sounds/thunder.ogg | Bin 0 -> 31765 bytes .../Sounds/wickedmalelaugh1.ogg | Bin 0 -> 28044 bytes WeakAuras/PowerAurasMedia/Sounds/wilhelm.ogg | Bin 0 -> 12524 bytes WeakAuras/PowerAurasMedia/Sounds/wlaugh.ogg | Bin 0 -> 42223 bytes WeakAuras/PowerAurasMedia/Sounds/wolf5.ogg | Bin 0 -> 17620 bytes WeakAuras/PowerAurasMedia/Sounds/yeehaw.ogg | Bin 0 -> 34073 bytes WeakAuras/Profiling.lua | 763 + WeakAuras/Prototypes.lua | 6008 ++ WeakAuras/RegionTypes/AuraBar.lua | 1254 + WeakAuras/RegionTypes/DynamicGroup.lua | 1217 + WeakAuras/RegionTypes/Group.lua | 169 + WeakAuras/RegionTypes/Icon.lua | 538 + WeakAuras/RegionTypes/Model.lua | 278 + WeakAuras/RegionTypes/ProgressTexture.lua | 1396 + WeakAuras/RegionTypes/RegionPrototype.lua | 869 + WeakAuras/RegionTypes/Text.lua | 275 + WeakAuras/RegionTypes/Texture.lua | 239 + WeakAuras/SubRegionTypes/BarModel.lua | 155 + WeakAuras/SubRegionTypes/Border.lua | 84 + WeakAuras/SubRegionTypes/Glow.lua | 390 + WeakAuras/SubRegionTypes/SubText.lua | 473 + WeakAuras/Transmission.lua | 1936 + WeakAuras/Types.lua | 2190 + WeakAuras/WeakAuras.lua | 6793 ++ WeakAuras/WeakAuras.toc | 59 + WeakAuras/compat.lua | 884 + WeakAuras/embeds.xml | 28 + WeakAuras/locales.xml | 16 + WeakAurasArchive/WeakAurasArchive.lua | 12 + WeakAurasArchive/WeakAurasArchive.toc | 7 + WeakAurasModelPaths/ModelPaths.lua | 68903 ++++++++++++++++ WeakAurasModelPaths/WeakAurasModelPaths.toc | 16 + .../AceGUIContainer-WeakAurasTreeGroup.lua | 722 + .../AceGUIWidget-WeakAurasDisplayButton.lua | 2127 + .../AceGUIWidget-WeakAurasExpand.lua | 142 + .../AceGUIWidget-WeakAurasExpandSmall.lua | 156 + .../AceGUIWidget-WeakAurasIcon.lua | 19 + .../AceGUIWidget-WeakAurasIconButton.lua | 97 + .../AceGUIWidget-WeakAurasImportButton.lua | 192 + ...eGUIWidget-WeakAurasLoadedHeaderButton.lua | 190 + ...AceGUIWidget-WeakAurasMultiLineEditBox.lua | 390 + .../AceGUIWidget-WeakAurasNewButton.lua | 148 + .../AceGUIWidget-WeakAurasNewHeaderButton.lua | 85 + .../AceGUIWidget-WeakAurasSnippetButton.lua | 237 + .../AceGUIWidget-WeakAurasSortedDropDown.lua | 42 + .../AceGUIWidget-WeakAurasTextureButton.lua | 128 + .../AceGUIWidget-WeakAurasToolbarButton.lua | 142 + ...ceGUIWidget-WeakAurasTwoColumnDropDown.lua | 270 + WeakAurasOptions/ActionOptions.lua | 855 + WeakAurasOptions/AnimationOptions.lua | 939 + WeakAurasOptions/AuthorOptions.lua | 2611 + WeakAurasOptions/BuffTrigger.lua | 1095 + WeakAurasOptions/BuffTrigger2.lua | 946 + WeakAurasOptions/Cache.lua | 178 + WeakAurasOptions/ConditionOptions.lua | 2334 + WeakAurasOptions/ExternalAddons.lua | 338 + WeakAurasOptions/ForAllIndentsAndPurposes.lua | 1329 + WeakAurasOptions/GenericTrigger.lua | 502 + WeakAurasOptions/Locales/deDE.lua | 1018 + WeakAurasOptions/Locales/enUS.lua | 405 + WeakAurasOptions/Locales/esES.lua | 1132 + WeakAurasOptions/Locales/esMX.lua | 1021 + WeakAurasOptions/Locales/frFR.lua | 817 + WeakAurasOptions/Locales/itIT.lua | 1240 + WeakAurasOptions/Locales/koKR.lua | 861 + WeakAurasOptions/Locales/ptBR.lua | 1139 + WeakAurasOptions/Locales/ruRU.lua | 749 + WeakAurasOptions/Locales/zhCN.lua | 728 + WeakAurasOptions/Locales/zhTW.lua | 705 + WeakAurasOptions/OptionsFrames/CodeReview.lua | 123 + .../OptionsFrames/FrameChooser.lua | 99 + WeakAurasOptions/OptionsFrames/IconPicker.lua | 199 + .../OptionsFrames/ImportExport.lua | 96 + .../OptionsFrames/ModelPicker.lua | 212 + WeakAurasOptions/OptionsFrames/MoverSizer.lua | 975 + .../OptionsFrames/OptionsFrame.lua | 1094 + WeakAurasOptions/OptionsFrames/TextEditor.lua | 788 + .../OptionsFrames/TexturePicker.lua | 229 + WeakAurasOptions/RegionOptions/AuraBar.lua | 772 + .../RegionOptions/DynamicGroup.lua | 535 + WeakAurasOptions/RegionOptions/Group.lua | 647 + WeakAurasOptions/RegionOptions/Icon.lua | 410 + WeakAurasOptions/RegionOptions/Model.lua | 296 + .../RegionOptions/ProgressTexture.lua | 819 + WeakAurasOptions/RegionOptions/Text.lua | 387 + WeakAurasOptions/RegionOptions/Texture.lua | 235 + .../SubRegionOptions/BarModel.lua | 111 + WeakAurasOptions/SubRegionOptions/Border.lua | 84 + WeakAurasOptions/SubRegionOptions/Glow.lua | 253 + .../SubRegionOptions/SubRegionCommon.lua | 97 + WeakAurasOptions/SubRegionOptions/SubText.lua | 484 + WeakAurasOptions/WeakAurasOptions.lua | 4839 ++ WeakAurasOptions/WeakAurasOptions.toc | 76 + WeakAurasOptions/locales.xml | 16 + 516 files changed, 185320 insertions(+) create mode 100644 WeakAuras/ArchiveTypes/Repository.lua create mode 100644 WeakAuras/AuraEnvironment.lua create mode 100644 WeakAuras/Bindings.xml create mode 100644 WeakAuras/BuffTrigger.lua create mode 100644 WeakAuras/BuffTrigger2.lua create mode 100644 WeakAuras/CHANGELOG.md create mode 100644 WeakAuras/DefaultOptions.lua create mode 100644 WeakAuras/GenericTrigger.lua create mode 100644 WeakAuras/History.lua create mode 100644 WeakAuras/Init.lua create mode 100644 WeakAuras/LICENSE create mode 100644 WeakAuras/Libs/AceComm-3.0/AceComm-3.0.lua create mode 100644 WeakAuras/Libs/AceComm-3.0/AceComm-3.0.xml create mode 100644 WeakAuras/Libs/AceComm-3.0/ChatThrottleLib.lua create mode 100644 WeakAuras/Libs/AceConfig-3.0/AceConfig-3.0.lua create mode 100644 WeakAuras/Libs/AceConfig-3.0/AceConfig-3.0.xml create mode 100644 WeakAuras/Libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.lua create mode 100644 WeakAuras/Libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.xml create mode 100644 WeakAuras/Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua create mode 100644 WeakAuras/Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.xml create mode 100644 WeakAuras/Libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.lua create mode 100644 WeakAuras/Libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.xml create mode 100644 WeakAuras/Libs/AceConsole-3.0/AceConsole-3.0.lua create mode 100644 WeakAuras/Libs/AceConsole-3.0/AceConsole-3.0.xml create mode 100644 WeakAuras/Libs/AceEvent-3.0/AceEvent-3.0.lua create mode 100644 WeakAuras/Libs/AceEvent-3.0/AceEvent-3.0.xml create mode 100644 WeakAuras/Libs/AceGUI-3.0-SharedMediaWidgets/BackgroundWidget.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0-SharedMediaWidgets/BorderWidget.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0-SharedMediaWidgets/FontWidget.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0-SharedMediaWidgets/SoundWidget.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0-SharedMediaWidgets/StatusbarWidget.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0-SharedMediaWidgets/prototypes.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0-SharedMediaWidgets/widget.xml create mode 100644 WeakAuras/Libs/AceGUI-3.0/AceGUI-3.0.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/AceGUI-3.0.xml create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIContainer-BlizOptionsGroup.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIContainer-DropDownGroup.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIContainer-InlineGroup.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIContainer-SimpleGroup.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-Heading.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-Icon.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-InteractiveLabel.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-Keybinding.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-Label.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-MultiLineEditBox.lua create mode 100644 WeakAuras/Libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua create mode 100644 WeakAuras/Libs/AceSerializer-3.0/AceSerializer-3.0.lua create mode 100644 WeakAuras/Libs/AceSerializer-3.0/AceSerializer-3.0.xml create mode 100644 WeakAuras/Libs/AceTimer-3.0/AceTimer-3.0.lua create mode 100644 WeakAuras/Libs/AceTimer-3.0/AceTimer-3.0.xml create mode 100644 WeakAuras/Libs/Archivist/Archivist.lua create mode 100644 WeakAuras/Libs/Archivist/Archivist.toc create mode 100644 WeakAuras/Libs/Archivist/Archivist.xml create mode 100644 WeakAuras/Libs/Archivist/LICENSE.md create mode 100644 WeakAuras/Libs/Archivist/README.md create mode 100644 WeakAuras/Libs/Archivist/libs/LibDeflate/LICENSE.txt create mode 100644 WeakAuras/Libs/Archivist/libs/LibDeflate/LibDeflate.lua create mode 100644 WeakAuras/Libs/Archivist/libs/LibStub/LibStub.lua create mode 100644 WeakAuras/Libs/Archivist/stores/RawData.lua create mode 100644 WeakAuras/Libs/Archivist/stores/ReadOnly.lua create mode 100644 WeakAuras/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua create mode 100644 WeakAuras/Libs/CallbackHandler-1.0/CallbackHandler-1.0.xml create mode 100644 WeakAuras/Libs/LibCompress/LibCompress.lua create mode 100644 WeakAuras/Libs/LibCompress/LibCompress.toc create mode 100644 WeakAuras/Libs/LibCompress/lib.xml create mode 100644 WeakAuras/Libs/LibCustomGlow-1.0/LibCustomGlow-1.0.lua create mode 100644 WeakAuras/Libs/LibCustomGlow-1.0/LibCustomGlow-1.0.toc create mode 100644 WeakAuras/Libs/LibCustomGlow-1.0/LibCustomGlow-1.0.xml create mode 100644 WeakAuras/Libs/LibCustomGlow-1.0/README.md create mode 100644 WeakAuras/Libs/LibCustomGlow-1.0/am_29.blp create mode 100644 WeakAuras/Libs/LibCustomGlow-1.0/artifacts.blp create mode 100644 WeakAuras/Libs/LibDBIcon-1.0/LibDBIcon-1.0.lua create mode 100644 WeakAuras/Libs/LibDBIcon-1.0/lib.xml create mode 100644 WeakAuras/Libs/LibDataBroker-1.1/LibDataBroker-1.1.lua create mode 100644 WeakAuras/Libs/LibDataBroker-1.1/README.textile create mode 100644 WeakAuras/Libs/LibDeflate/LibDeflate.lua create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/CallbackHandler-1.0/CallbackHandler-1.0.lua create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/CallbackHandler-1.0/CallbackHandler-1.0.xml create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/LibGetFrame-1.0.lua create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/LibGetFrame-1.0.toc create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/LibGetFrame-1.0.xml create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/LibStub/LibStub.lua create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/LibStub/LibStub.toc create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/LibStub/tests/test.lua create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/LibStub/tests/test2.lua create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/LibStub/tests/test3.lua create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/LibStub/tests/test4.lua create mode 100644 WeakAuras/Libs/LibGetFrame-1.0/README.md create mode 100644 WeakAuras/Libs/LibRangeCheck-2.0/LibRangeCheck-2.0.lua create mode 100644 WeakAuras/Libs/LibSharedMedia-3.0/LibSharedMedia-3.0.lua create mode 100644 WeakAuras/Libs/LibSharedMedia-3.0/lib.xml create mode 100644 WeakAuras/Libs/LibSpellRange-1.0/LibSpellRange-1.0.lua create mode 100644 WeakAuras/Libs/LibSpellRange-1.0/LibSpellRange-1.0.toc create mode 100644 WeakAuras/Libs/LibSpellRange-1.0/README.md create mode 100644 WeakAuras/Libs/LibSpellRange-1.0/lib.xml create mode 100644 WeakAuras/Libs/LibStub/LibStub.lua create mode 100644 WeakAuras/Libs/LibStub/LibStub.toc create mode 100644 WeakAuras/Libs/LibStub/tests/test.lua create mode 100644 WeakAuras/Libs/LibStub/tests/test2.lua create mode 100644 WeakAuras/Libs/LibStub/tests/test3.lua create mode 100644 WeakAuras/Libs/LibStub/tests/test4.lua create mode 100644 WeakAuras/Locales/deDE.lua create mode 100644 WeakAuras/Locales/enUS.lua create mode 100644 WeakAuras/Locales/esES.lua create mode 100644 WeakAuras/Locales/esMX.lua create mode 100644 WeakAuras/Locales/frFR.lua create mode 100644 WeakAuras/Locales/itIT.lua create mode 100644 WeakAuras/Locales/koKR.lua create mode 100644 WeakAuras/Locales/ptBR.lua create mode 100644 WeakAuras/Locales/ruRU.lua create mode 100644 WeakAuras/Locales/zhCN.lua create mode 100644 WeakAuras/Locales/zhTW.lua create mode 100644 WeakAuras/Media/Creative Commons - Attribution 3.0.txt create mode 100644 WeakAuras/Media/Creative Commons - Attribution-NonCommercial 3.0.txt create mode 100644 WeakAuras/Media/Creative Commons - Sampling Plus 1.0.txt create mode 100644 WeakAuras/Media/Fira LICENSE create mode 100644 WeakAuras/Media/Fonts/FiraMono-Medium.ttf create mode 100644 WeakAuras/Media/Provided by.txt create mode 100644 WeakAuras/Media/Sounds/Adds.ogg create mode 100644 WeakAuras/Media/Sounds/AirHorn.ogg create mode 100644 WeakAuras/Media/Sounds/Applause.ogg create mode 100644 WeakAuras/Media/Sounds/BananaPeelSlip.ogg create mode 100644 WeakAuras/Media/Sounds/BatmanPunch.ogg create mode 100644 WeakAuras/Media/Sounds/BikeHorn.ogg create mode 100644 WeakAuras/Media/Sounds/Blast.ogg create mode 100644 WeakAuras/Media/Sounds/Bleat.ogg create mode 100644 WeakAuras/Media/Sounds/Boss.ogg create mode 100644 WeakAuras/Media/Sounds/BoxingArenaSound.ogg create mode 100644 WeakAuras/Media/Sounds/CartoonVoiceBaritone.ogg create mode 100644 WeakAuras/Media/Sounds/CartoonWalking.ogg create mode 100644 WeakAuras/Media/Sounds/CatMeow2.ogg create mode 100644 WeakAuras/Media/Sounds/Circle.ogg create mode 100644 WeakAuras/Media/Sounds/CowMooing.ogg create mode 100644 WeakAuras/Media/Sounds/Cross.ogg create mode 100644 WeakAuras/Media/Sounds/Diamond.ogg create mode 100644 WeakAuras/Media/Sounds/DontRelease.ogg create mode 100644 WeakAuras/Media/Sounds/Empowered.ogg create mode 100644 WeakAuras/Media/Sounds/Focus.ogg create mode 100644 WeakAuras/Media/Sounds/Idiot.ogg create mode 100644 WeakAuras/Media/Sounds/KittenMeow.ogg create mode 100644 WeakAuras/Media/Sounds/Left.ogg create mode 100644 WeakAuras/Media/Sounds/Moon.ogg create mode 100644 WeakAuras/Media/Sounds/Next.ogg create mode 100644 WeakAuras/Media/Sounds/Portal.ogg create mode 100644 WeakAuras/Media/Sounds/Protected.ogg create mode 100644 WeakAuras/Media/Sounds/Release.ogg create mode 100644 WeakAuras/Media/Sounds/Right.ogg create mode 100644 WeakAuras/Media/Sounds/RingingPhone.ogg create mode 100644 WeakAuras/Media/Sounds/RoaringLion.ogg create mode 100644 WeakAuras/Media/Sounds/RobotBlip.ogg create mode 100644 WeakAuras/Media/Sounds/RunAway.ogg create mode 100644 WeakAuras/Media/Sounds/SharpPunch.ogg create mode 100644 WeakAuras/Media/Sounds/Shotgun.ogg create mode 100644 WeakAuras/Media/Sounds/Skull.ogg create mode 100644 WeakAuras/Media/Sounds/Spread.ogg create mode 100644 WeakAuras/Media/Sounds/Square.ogg create mode 100644 WeakAuras/Media/Sounds/SquishFart.ogg create mode 100644 WeakAuras/Media/Sounds/Stack.ogg create mode 100644 WeakAuras/Media/Sounds/Star.ogg create mode 100644 WeakAuras/Media/Sounds/Switch.ogg create mode 100644 WeakAuras/Media/Sounds/Taunt.ogg create mode 100644 WeakAuras/Media/Sounds/TempleBellHuge.ogg create mode 100644 WeakAuras/Media/Sounds/Torch.ogg create mode 100644 WeakAuras/Media/Sounds/Triangle.ogg create mode 100644 WeakAuras/Media/Sounds/WarningSiren.ogg create mode 100644 WeakAuras/Media/Sounds/WaterDrop.ogg create mode 100644 WeakAuras/Media/Textures/Circle_Smooth.tga create mode 100644 WeakAuras/Media/Textures/Circle_Smooth2.tga create mode 100644 WeakAuras/Media/Textures/Circle_Smooth_Border.tga create mode 100644 WeakAuras/Media/Textures/Circle_Squirrel.tga create mode 100644 WeakAuras/Media/Textures/Circle_Squirrel_Border.tga create mode 100644 WeakAuras/Media/Textures/Circle_White.tga create mode 100644 WeakAuras/Media/Textures/Circle_White_Border.tga create mode 100644 WeakAuras/Media/Textures/PRDFrame.tga create mode 100644 WeakAuras/Media/Textures/PRDFrameKui.tga create mode 100644 WeakAuras/Media/Textures/Square_FullWhite.tga create mode 100644 WeakAuras/Media/Textures/Square_Smooth.tga create mode 100644 WeakAuras/Media/Textures/Square_Smooth_Border.tga create mode 100644 WeakAuras/Media/Textures/Square_Smooth_Border2.tga create mode 100644 WeakAuras/Media/Textures/Square_Squirrel.tga create mode 100644 WeakAuras/Media/Textures/Square_Squirrel_Border.tga create mode 100644 WeakAuras/Media/Textures/Square_White.tga create mode 100644 WeakAuras/Media/Textures/Square_White_Border.tga create mode 100644 WeakAuras/Media/Textures/StripedTexture.tga create mode 100644 WeakAuras/Media/Textures/Trapezoid.tga create mode 100644 WeakAuras/Media/Textures/Triangle45.tga create mode 100644 WeakAuras/Media/Textures/add.tga create mode 100644 WeakAuras/Media/Textures/arrows_target.tga create mode 100644 WeakAuras/Media/Textures/bullet1.tga create mode 100644 WeakAuras/Media/Textures/bullet2.tga create mode 100644 WeakAuras/Media/Textures/bullet3.tga create mode 100644 WeakAuras/Media/Textures/cancel-icon.tga create mode 100644 WeakAuras/Media/Textures/cancel-mark.tga create mode 100644 WeakAuras/Media/Textures/circle_border5.tga create mode 100644 WeakAuras/Media/Textures/collapse.tga create mode 100644 WeakAuras/Media/Textures/delete.tga create mode 100644 WeakAuras/Media/Textures/downleft.tga create mode 100644 WeakAuras/Media/Textures/downright.tga create mode 100644 WeakAuras/Media/Textures/duplicate.tga create mode 100644 WeakAuras/Media/Textures/edit.tga create mode 100644 WeakAuras/Media/Textures/editdown.tga create mode 100644 WeakAuras/Media/Textures/emoji.tga create mode 100644 WeakAuras/Media/Textures/exclamation-mark.tga create mode 100644 WeakAuras/Media/Textures/expand.tga create mode 100644 WeakAuras/Media/Textures/eyes.tga create mode 100644 WeakAuras/Media/Textures/icon.blp create mode 100644 WeakAuras/Media/Textures/importsmall.tga create mode 100644 WeakAuras/Media/Textures/interrupt.tga create mode 100644 WeakAuras/Media/Textures/magnetic.tga create mode 100644 WeakAuras/Media/Textures/movedown.tga create mode 100644 WeakAuras/Media/Textures/moveup.tga create mode 100644 WeakAuras/Media/Textures/newaura.tga create mode 100644 WeakAuras/Media/Textures/ok-icon.tga create mode 100644 WeakAuras/Media/Textures/rainbowbar.tga create mode 100644 WeakAuras/Media/Textures/reset.tga create mode 100644 WeakAuras/Media/Textures/ring_glow3.tga create mode 100644 WeakAuras/Media/Textures/square_mini.tga create mode 100644 WeakAuras/Media/Textures/stripe-bar.tga create mode 100644 WeakAuras/Media/Textures/stripe-rainbow-bar.tga create mode 100644 WeakAuras/Media/Textures/target_indicator.tga create mode 100644 WeakAuras/Media/Textures/target_indicator_glow.tga create mode 100644 WeakAuras/Media/Textures/targeting-mark.tga create mode 100644 WeakAuras/Media/Textures/template.tga create mode 100644 WeakAuras/Media/Textures/triangle-border.tga create mode 100644 WeakAuras/Media/Textures/triangle.tga create mode 100644 WeakAuras/Media/Textures/upleft.tga create mode 100644 WeakAuras/Media/Textures/upright.tga create mode 100644 WeakAuras/Media/Textures/wagoupdate_logo.tga create mode 100644 WeakAuras/Media/Textures/wagoupdate_refresh.tga create mode 100644 WeakAuras/Pools.lua create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura1.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura10.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura100.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura101.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura102.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura103.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura104.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura105.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura106.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura107.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura108.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura109.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura11.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura110.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura111.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura112.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura113.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura114.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura115.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura116.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura117.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura118.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura119.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura12.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura120.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura121.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura122.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura123.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura124.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura125.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura126.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura127.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura128.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura129.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura13.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura130.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura131.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura132.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura133.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura134.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura135.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura136.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura137.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura138.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura139.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura14.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura140.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura141.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura142.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura143.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura144.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura145.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura15.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura16.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura17.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura18.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura19.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura2.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura20.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura21.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura22.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura23.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura24.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura25.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura26.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura27.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura28.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura29.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura3.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura30.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura31.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura32.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura33.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura34.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura35.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura36.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura37.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura38.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura39.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura4.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura40.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura41.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura42.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura43.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura44.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura45.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura46.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura47.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura48.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura49.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura5.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura50.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura51.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura52.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura53.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura54.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura55.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura56.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura57.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura58.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura59.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura6.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura60.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura61.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura62.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura63.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura64.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura65.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura66.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura67.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura68.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura69.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura7.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura70.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura71.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura72.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura73.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura74.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura75.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura76.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura77.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura78.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura79.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura8.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura80.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura81.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura82.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura83.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura84.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura85.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura86.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura87.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura88.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura89.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura9.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura90.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura91.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura92.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura93.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura94.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura95.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura96.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura97.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura98.tga create mode 100644 WeakAuras/PowerAurasMedia/Auras/Aura99.tga create mode 100644 WeakAuras/PowerAurasMedia/Sounds/Arrow_Swoosh.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/BITE.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/ESPARK1.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/Fireball.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/Gasp.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/PUNCH.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/Squeakypig.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/aggro.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/bam.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/bear_polar.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/bigkiss.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/burp4.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/cat2.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/chant2.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/chant4.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/chimes.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/cookie.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/heartbeat.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/hic3.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/huh_1.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/hurricane.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/hyena.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/kaching.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/moan.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/panther1.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/phone.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/rainroof.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/rocket.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/shipswhistle.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/shot.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/snakeatt.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/sneeze.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/sonar.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/splash.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/swordecho.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/throwknife.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/thunder.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/wickedmalelaugh1.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/wilhelm.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/wlaugh.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/wolf5.ogg create mode 100644 WeakAuras/PowerAurasMedia/Sounds/yeehaw.ogg create mode 100644 WeakAuras/Profiling.lua create mode 100644 WeakAuras/Prototypes.lua create mode 100644 WeakAuras/RegionTypes/AuraBar.lua create mode 100644 WeakAuras/RegionTypes/DynamicGroup.lua create mode 100644 WeakAuras/RegionTypes/Group.lua create mode 100644 WeakAuras/RegionTypes/Icon.lua create mode 100644 WeakAuras/RegionTypes/Model.lua create mode 100644 WeakAuras/RegionTypes/ProgressTexture.lua create mode 100644 WeakAuras/RegionTypes/RegionPrototype.lua create mode 100644 WeakAuras/RegionTypes/Text.lua create mode 100644 WeakAuras/RegionTypes/Texture.lua create mode 100644 WeakAuras/SubRegionTypes/BarModel.lua create mode 100644 WeakAuras/SubRegionTypes/Border.lua create mode 100644 WeakAuras/SubRegionTypes/Glow.lua create mode 100644 WeakAuras/SubRegionTypes/SubText.lua create mode 100644 WeakAuras/Transmission.lua create mode 100644 WeakAuras/Types.lua create mode 100644 WeakAuras/WeakAuras.lua create mode 100644 WeakAuras/WeakAuras.toc create mode 100644 WeakAuras/compat.lua create mode 100644 WeakAuras/embeds.xml create mode 100644 WeakAuras/locales.xml create mode 100644 WeakAurasArchive/WeakAurasArchive.lua create mode 100644 WeakAurasArchive/WeakAurasArchive.toc create mode 100644 WeakAurasModelPaths/ModelPaths.lua create mode 100644 WeakAurasModelPaths/WeakAurasModelPaths.toc create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIContainer-WeakAurasTreeGroup.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasDisplayButton.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasExpand.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasExpandSmall.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasIcon.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasIconButton.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasImportButton.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasLoadedHeaderButton.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasMultiLineEditBox.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasNewButton.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasNewHeaderButton.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasSnippetButton.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasSortedDropDown.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasTextureButton.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasToolbarButton.lua create mode 100644 WeakAurasOptions/AceGUI-Widgets/AceGUIWidget-WeakAurasTwoColumnDropDown.lua create mode 100644 WeakAurasOptions/ActionOptions.lua create mode 100644 WeakAurasOptions/AnimationOptions.lua create mode 100644 WeakAurasOptions/AuthorOptions.lua create mode 100644 WeakAurasOptions/BuffTrigger.lua create mode 100644 WeakAurasOptions/BuffTrigger2.lua create mode 100644 WeakAurasOptions/Cache.lua create mode 100644 WeakAurasOptions/ConditionOptions.lua create mode 100644 WeakAurasOptions/ExternalAddons.lua create mode 100644 WeakAurasOptions/ForAllIndentsAndPurposes.lua create mode 100644 WeakAurasOptions/GenericTrigger.lua create mode 100644 WeakAurasOptions/Locales/deDE.lua create mode 100644 WeakAurasOptions/Locales/enUS.lua create mode 100644 WeakAurasOptions/Locales/esES.lua create mode 100644 WeakAurasOptions/Locales/esMX.lua create mode 100644 WeakAurasOptions/Locales/frFR.lua create mode 100644 WeakAurasOptions/Locales/itIT.lua create mode 100644 WeakAurasOptions/Locales/koKR.lua create mode 100644 WeakAurasOptions/Locales/ptBR.lua create mode 100644 WeakAurasOptions/Locales/ruRU.lua create mode 100644 WeakAurasOptions/Locales/zhCN.lua create mode 100644 WeakAurasOptions/Locales/zhTW.lua create mode 100644 WeakAurasOptions/OptionsFrames/CodeReview.lua create mode 100644 WeakAurasOptions/OptionsFrames/FrameChooser.lua create mode 100644 WeakAurasOptions/OptionsFrames/IconPicker.lua create mode 100644 WeakAurasOptions/OptionsFrames/ImportExport.lua create mode 100644 WeakAurasOptions/OptionsFrames/ModelPicker.lua create mode 100644 WeakAurasOptions/OptionsFrames/MoverSizer.lua create mode 100644 WeakAurasOptions/OptionsFrames/OptionsFrame.lua create mode 100644 WeakAurasOptions/OptionsFrames/TextEditor.lua create mode 100644 WeakAurasOptions/OptionsFrames/TexturePicker.lua create mode 100644 WeakAurasOptions/RegionOptions/AuraBar.lua create mode 100644 WeakAurasOptions/RegionOptions/DynamicGroup.lua create mode 100644 WeakAurasOptions/RegionOptions/Group.lua create mode 100644 WeakAurasOptions/RegionOptions/Icon.lua create mode 100644 WeakAurasOptions/RegionOptions/Model.lua create mode 100644 WeakAurasOptions/RegionOptions/ProgressTexture.lua create mode 100644 WeakAurasOptions/RegionOptions/Text.lua create mode 100644 WeakAurasOptions/RegionOptions/Texture.lua create mode 100644 WeakAurasOptions/SubRegionOptions/BarModel.lua create mode 100644 WeakAurasOptions/SubRegionOptions/Border.lua create mode 100644 WeakAurasOptions/SubRegionOptions/Glow.lua create mode 100644 WeakAurasOptions/SubRegionOptions/SubRegionCommon.lua create mode 100644 WeakAurasOptions/SubRegionOptions/SubText.lua create mode 100644 WeakAurasOptions/WeakAurasOptions.lua create mode 100644 WeakAurasOptions/WeakAurasOptions.toc create mode 100644 WeakAurasOptions/locales.xml diff --git a/WeakAuras/ArchiveTypes/Repository.lua b/WeakAuras/ArchiveTypes/Repository.lua new file mode 100644 index 0000000..eb9d14f --- /dev/null +++ b/WeakAuras/ArchiveTypes/Repository.lua @@ -0,0 +1,118 @@ +--[[ +Repository store type. This is a meta-archive of sorts. + Store contains 0 or more substores, each of which is essentially + a list of ReadOnly stores, along with a small amount of meta-data + about the sub-stores. Store type is tailored for quick retrieval of + meta-data and only decompressing the data we need right now. In our use case, + the sub stores are all historical aura snapshots. It would be an + error to mutate this data, so we use ReadOnly stores (which don't + return anything on Commit/Close) to minimize performance impact of reading data. +--]] + +local Archivist = select(2, ...).Archivist + +local subStoreMethods = { + Validate = function(self) + if type(self.id) ~= "string" or not Archivist:Check("ReadOnly", self.id) then + self.id = nil + return false + else + return true + end + end, + Set = function(self, data) + if type(self.id) == "string" then + Archivist:Delete("ReadOnly", self.id, true) + end + local store, storeID = Archivist:Create("ReadOnly", nil, data) + self.id = storeID + self.timestamp = time() + end, + Load = function(self) -- convenience method + return Archivist:Load("ReadOnly", self.id) + end, + Close = function(self) -- convenience method + return Archivist:Close("ReadOnly", self.id) + end, + Delete = function(self) + Archivist:Delete("ReadOnly", self.id) + self.id = nil + end, +} + +local storeMethods = { + Validate = function(self) + for id, subStore in pairs(self.stores) do + if not subStore:Validate() then + -- either it's too old, or doesn't exist. Either way we don't need to keep this record + self.stores[id] = nil + end + end + end, + Get = function(self, id, load) + local subStore = self.stores[id] + local data + if subStore and load then + data = subStore:Load() + end + return subStore, data + end, + GetData = function(self, id) + return select(2, self:Get(id, true)) + end, + Set = function(self, id, data) + if data ~= nil and type(id) == "string" then + if not self.stores[id] then + self.stores[id] = WeakAuras:Mixin({}, subStoreMethods) + end + self.stores[id]:Set(data) + return self.stores[id] + end + end, + Clean = function(self, cutoff) + for id, subStore in pairs(self.stores) do + if subStore.timestamp < cutoff then + self:Drop(id) + end + end + end, + Drop = function(self, id) + if self.stores[id] then + self.stores[id]:Delete() + self.stores[id] = nil + end + end, +} + +local prototype = { + id = "Repository", + version = 1, + Init = nil, -- Repositories are entirely self-contained! No need for Init. + Create = function(self, image) + local store = type(image) == "table" and image or {} + if type(store.stores) ~= "table" then + store.stores = {} + end + WeakAuras:Mixin(store, storeMethods) + store:Validate() + return store, store + end, + Update = nil, -- This is the initial version! No need for Update yet. + Open = function(self, image) + local store = image + WeakAuras:Mixin(store, storeMethods) + for _, subStore in pairs(store.stores) do + WeakAuras:Mixin(subStore, subStoreMethods) + end + store:Validate() + return store + end, + Commit = function(self, store) + return store + end, + Close = function(self, store) + return store + end +} + +Archivist:RegisterStoreType(prototype) diff --git a/WeakAuras/AuraEnvironment.lua b/WeakAuras/AuraEnvironment.lua new file mode 100644 index 0000000..61353a3 --- /dev/null +++ b/WeakAuras/AuraEnvironment.lua @@ -0,0 +1,270 @@ +if not WeakAuras.IsCorrectVersion() then return end + +local WeakAuras = WeakAuras +local L = WeakAuras.L +local prettyPrint = WeakAuras.prettyPrint + +local UnitAura = UnitAura +-- Unit Aura functions that return info about the first Aura matching the spellName or spellID given on the unit. +local WA_GetUnitAura = function(unit, spell, filter) + if filter and not filter:upper():find("FUL") then + filter = filter.."|HELPFUL" + end + for i = 1, 255 do + local name, _, _, _, _, _, _, _, _, _, spellId = UnitAura(unit, i, filter) + if not name then return end + if spell == spellId or spell == name then + return UnitAura(unit, i, filter) + end + end +end + +local WA_GetUnitBuff = function(unit, spell, filter) + filter = filter and filter.."|HELPFUL" or "HELPFUL" + return WA_GetUnitAura(unit, spell, filter) +end + +local WA_GetUnitDebuff = function(unit, spell, filter) + filter = filter and filter.."|HARMFUL" or "HARMFUL" + return WA_GetUnitAura(unit, spell, filter) +end + +-- Function to assist iterating group members whether in a party or raid. +local WA_IterateGroupMembers = function(reversed, forceParty) + local unit = (not forceParty and IsInRaid()) and 'raid' or 'party' + local numGroupMembers = unit == 'party' and GetNumSubgroupMembers() or GetNumGroupMembers() + local i = reversed and numGroupMembers or (unit == 'party' and 0 or 1) + return function() + local ret + if i == 0 and unit == 'party' then + ret = 'player' + elseif i <= numGroupMembers and i > 0 then + ret = unit .. i + end + i = i + (reversed and -1 or 1) + return ret + end +end + +-- Wrapping a unit's name in its class colour is very common in custom Auras +local WA_ClassColorName = function(unit) + if unit and UnitExists(unit) then + local name = UnitName(unit) + local _, class = UnitClass(unit) + if not class then + return name + else + local classData = RAID_CLASS_COLORS[class] + local coloredName = ("|c%s%s|r"):format(classData.colorStr, name) + return coloredName + end + else + return "" -- ¯\_(ツ)_/¯ + end +end + +local helperFunctions = { + WA_GetUnitAura = WA_GetUnitAura, + WA_GetUnitBuff = WA_GetUnitBuff, + WA_GetUnitDebuff = WA_GetUnitDebuff, + WA_IterateGroupMembers = WA_IterateGroupMembers, + WA_ClassColorName = WA_ClassColorName, +} + +local LCG = LibStub("LibCustomGlow-1.0") +WeakAuras.ShowOverlayGlow = LCG.ButtonGlow_Start +WeakAuras.HideOverlayGlow = LCG.ButtonGlow_Stop + +local LGF = LibStub("LibGetFrame-1.0") +WeakAuras.GetUnitFrame = LGF.GetUnitFrame + +local function forbidden() + prettyPrint(L["A WeakAura just tried to use a forbidden function but has been blocked from doing so. Please check your auras!"]) +end + +local blockedFunctions = { + -- Lua functions that may allow breaking out of the environment + getfenv = true, + setfenv = true, + loadstring = true, + pcall = true, + xpcall = true, + -- blocked WoW API + SendMail = true, + SetTradeMoney = true, + AddTradeMoney = true, + PickupTradeMoney = true, + PickupPlayerMoney = true, + TradeFrame = true, + MailFrame = true, + EnumerateFrames = true, + RunScript = true, + AcceptTrade = true, + SetSendMailMoney = true, + EditMacro = true, + SlashCmdList = true, + DevTools_DumpCommand = true, + hash_SlashCmdList = true, + CreateMacro = true, + SetBindingMacro = true, + GuildDisband = true, + GuildUninvite = true, + securecall = true, +} + +local overrideFunctions = { + ActionButton_ShowOverlayGlow = WeakAuras.ShowOverlayGlow, + ActionButton_HideOverlayGlow = WeakAuras.HideOverlayGlow, +} + +local aura_environments = {} +-- nil == Not initiliazed +-- 1 == config initialized +-- 2 == fully initialized +local environment_initialized = {} + +function WeakAuras.IsEnvironmentInitialized(id) + return environment_initialized[id] == 2 +end + +function WeakAuras.DeleteAuraEnvironment(id) + aura_environments[id] = nil + environment_initialized[id] = nil +end + +function WeakAuras.RenameAuraEnvironment(oldid, newid) + aura_environments[oldid], aura_environments[newid] = nil, aura_environments[oldid] + environment_initialized[oldid], environment_initialized[newid] = nil, environment_initialized[oldid] +end + +local current_aura_env = nil +local aura_env_stack = {} -- Stack of of aura environments, allows use of recursive aura activations through calls to WeakAuras.ScanEvents(). + +function WeakAuras.ClearAuraEnvironment(id) + environment_initialized[id] = nil; +end + +function WeakAuras.ActivateAuraEnvironment(id, cloneId, state, states, onlyConfig) + local data = WeakAuras.GetData(id) + local region = WeakAuras.GetRegion(id, cloneId) + if not data then + -- Pop the last aura_env from the stack, and update current_aura_env appropriately. + tremove(aura_env_stack) + current_aura_env = aura_env_stack[#aura_env_stack] or nil + else + -- Existing config is initialized to a high enough value + if environment_initialized[id] == 2 or (onlyConfig and environment_initialized[id] == 1) then + -- Point the current environment to the correct table + current_aura_env = aura_environments[id] + current_aura_env.id = id + current_aura_env.cloneId = cloneId + current_aura_env.state = state + current_aura_env.states = states + current_aura_env.region = region + -- Push the new environment onto the stack + tinsert(aura_env_stack, current_aura_env) + elseif onlyConfig then + environment_initialized[id] = 1 + aura_environments[id] = {} + current_aura_env = aura_environments[id] + current_aura_env.id = id + current_aura_env.cloneId = cloneId + current_aura_env.state = state + current_aura_env.states = states + current_aura_env.region = region + tinsert(aura_env_stack, current_aura_env) + + if not data.controlledChildren then + current_aura_env.config = CopyTable(data.config) + end + else + -- Either this aura environment has not yet been initialized, or it was reset via an edit in WeakaurasOptions + environment_initialized[id] = 2 + aura_environments[id] = aura_environments[id] or {} + current_aura_env = aura_environments[id] + current_aura_env.id = id + current_aura_env.cloneId = cloneId + current_aura_env.state = state + current_aura_env.states = states + current_aura_env.region = region + -- push new environment onto the stack + tinsert(aura_env_stack, current_aura_env) + + if data.controlledChildren then + current_aura_env.child_envs = {} + for dataIndex, childID in ipairs(data.controlledChildren) do + local childData = WeakAuras.GetData(childID) + if childData then + if not environment_initialized[childID] then + WeakAuras.ActivateAuraEnvironment(childID) + WeakAuras.ActivateAuraEnvironment() + end + current_aura_env.child_envs[dataIndex] = aura_environments[childID] + end + end + else + if environment_initialized[id] == 1 then + -- Already done + else + current_aura_env.config = CopyTable(data.config) + end + end + -- Finally, run the init function if supplied + local actions = data.actions.init + if(actions and actions.do_custom and actions.custom) then + local func = WeakAuras.customActionsFunctions[id]["init"] + if func then + xpcall(func, geterrorhandler()) + end + end + end + end +end + +local env_getglobal +local exec_env = setmetatable({}, { __index = + function(t, k) + if k == "_G" then + return t + elseif k == "getglobal" then + return env_getglobal + elseif k == "aura_env" then + return current_aura_env + elseif blockedFunctions[k] then + return forbidden + elseif overrideFunctions[k] then + return overrideFunctions[k] + elseif helperFunctions[k] then + return helperFunctions[k] + else + return _G[k] + end + end +}) + +function env_getglobal(k) + return exec_env[k] +end + +local function_cache = {} +function WeakAuras.LoadFunction(string, id, inTrigger) + if function_cache[string] then + return function_cache[string] + else + local loadedFunction, errorString = loadstring("--[==[ Error in '" .. (id or "Unknown") .. (inTrigger and ("':'".. inTrigger) or "") .."' ]==] " .. string) + if errorString then + print(errorString) + else + setfenv(loadedFunction, exec_env) + local success, func = pcall(assert(loadedFunction)) + if success then + function_cache[string] = func + return func + end + end + end +end + +function WeakAuras.GetSanitizedGlobal(key) + return exec_env[key] +end diff --git a/WeakAuras/Bindings.xml b/WeakAuras/Bindings.xml new file mode 100644 index 0000000..eb4d252 --- /dev/null +++ b/WeakAuras/Bindings.xml @@ -0,0 +1,11 @@ + + + WeakAuras.OpenOptions() + + + WeakAuras.RealTimeProfilingWindow:Toggle() + + + WeakAuras.PrintProfile() + + diff --git a/WeakAuras/BuffTrigger.lua b/WeakAuras/BuffTrigger.lua new file mode 100644 index 0000000..066ef34 --- /dev/null +++ b/WeakAuras/BuffTrigger.lua @@ -0,0 +1,1949 @@ +--[[ BuffTrigger.lua +This file contains the "aura" trigger for buffs and debuffs. + +It registers the BuffTrigger table for the trigger type "aura" and has the following API: + +Add(data) +Adds an aura, setting up internal data structures for all buff triggers. + +LoadDisplays(id) +Loads the aura ids, enabling all buff triggers in the aura. + +UnloadDisplays(id) +Unloads the aura ids, disabling all buff triggers in the aura. + +UnloadAll() +Unloads all auras, disabling all buff triggers. + +ScanAll() +Updates all triggers by checking all triggers. + +Delete(id) +Removes all data for aura id. + +Rename(oldid, newid) +Updates all data for aura oldid to use newid. + +Modernize(data) +Updates all buff triggers in data. + +##################################################### +# Helper functions mainly for the WeakAuras Options # +##################################################### + +CanHaveDuration(data, triggernum) +Returns whether the trigger can have a duration. + +GetOverlayInfo(data, triggernum) +Returns a table containing all overlays. Currently there aren't any + +CanHaveAuto(data, triggernum) +Returns whether the icon can be automatically selected. + +CanHaveClones(data, triggernum) +Returns whether the trigger can have clones. + +CanHaveTooltip(data, triggernum) +Returns the type of tooltip to show for the trigger. + +GetNameAndIcon(data, triggernum) +Returns the name and icon to show in the options. + +GetAdditionalProperties(data, triggernum) +Returns the tooltip text for additional properties. + +GetTriggerConditions(data, triggernum) +Returns the potential conditions for a trigger +]]-- + +if not WeakAuras.IsCorrectVersion() then return end + +-- Lua APIs +local tinsert, wipe = table.insert, wipe +local pairs, next, type = pairs, next, type +local BUFF_MAX_DISPLAY = 255 -- Do tell when you find the real value. +local UnitGroupRolesAssigned = function() return "DAMAGER" end + +local WeakAuras = WeakAuras; +local L = WeakAuras.L; +local BuffTrigger = {}; + +local timer = WeakAuras.timer; +local function_strings = WeakAuras.function_strings; +local auras = WeakAuras.auras; +local specificBosses = WeakAuras.specificBosses; +local specificUnits = WeakAuras.specificUnits; +local loaded_auras = WeakAuras.loaded_auras; + +WeakAuras.me = GetUnitName("player", true) +WeakAuras.myGUID = nil + +local aura_cache = {}; +do + aura_cache.max = 0; + aura_cache.watched = {}; + aura_cache.players = {}; + aura_cache.TANK = 0; + aura_cache.HEALER = 0; + aura_cache.DAMAGER = 0; + aura_cache.playerRole = "NONE"; + + --- Tests if aura_cache data is consistent with trigger settings, eg. OwnOnly, RemainingTime, StackCount. + -- Extra check needed because aura_cache can potentially contain data of two different triggers with different settings! + -- @param acEntry + -- @param data + -- @return boolean + local function TestNonUniformSettings(acEntry, data) + if(data.remFunc) then + if not(data.remFunc(acEntry.expirationTime - GetTime())) then + return false + end + end + + -- Test OwnOnly + if ( + data.ownOnly == true and WeakAuras.myGUID ~= acEntry.casterGUID or + data.ownOnly == false and WeakAuras.myGUID == acEntry.casterGUID + ) then + return false; + end + + -- Test StackCount + if (data.count and not data.count(acEntry.count)) then + return false; + end + + -- Success + return true; + end + + function aura_cache.ForceUpdate() + if not(WeakAuras.IsPaused()) then + WeakAuras.ScanAurasGroup() + end + end + + function aura_cache.Watch(self, id, triggernum) + self.watched[id] = self.watched[id] or {}; + self.watched[id][triggernum] = self.watched[id][triggernum] or {}; + self.watched[id][triggernum].players = self.watched[id][triggernum].players or {}; + self:ForceUpdate() + end + + function aura_cache.Rename(self, oldid, newid) + self.watched[newid] = self.watched[oldid]; + self.watched[oldid] = nil; + end + + function aura_cache.Unwatch(self, id, triggernum) + self.watched[id][triggernum] = nil; + end + + function aura_cache.GetMaxNumber(self) + return self.max; + end + + function aura_cache.GetNumber(self, id, triggernum, data) + local num = 0; + for guid, _ in pairs(self.players) do + -- Need to check if cached data conforms to trigger + if(self.watched[id][triggernum].players[guid] and TestNonUniformSettings(self.watched[id][triggernum].players[guid], data)) then + num = num + 1; + end + end + return num; + end + + function aura_cache.GetDynamicInfo(self, id, triggernum, data) + local bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestSpellId, bestUnitCaster = 0, math.huge, "", "", 0, 0, ""; + if(self.watched[id] and self.watched[id][triggernum]) then + for guid, durationInfo in pairs(self.watched[id][triggernum].players) do + -- Need to check if cached data conforms to trigger + if(durationInfo.expirationTime < bestExpirationTime and TestNonUniformSettings(durationInfo, data)) then + bestDuration = durationInfo.duration; + bestExpirationTime = durationInfo.expirationTime; + bestName = durationInfo.name; + bestIcon = durationInfo.icon; + bestCount = durationInfo.count; + bestSpellId = durationInfo.spellId; + bestUnitCaster = durationInfo.unitCaster; + end + end + end + return bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestSpellId, bestUnitCaster; + end + + function aura_cache.GetPlayerDynamicInfo(self, id, triggernum, guid, data) + local bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestSpellId, bestUnitCaster = 0, math.huge, "", "", 0, 0, ""; + if(self.watched[id] and self.watched[id][triggernum]) then + local durationInfo = self.watched[id][triggernum].players[guid] + if(durationInfo) then + -- Need to check if cached data conforms to trigger + if(durationInfo.expirationTime < bestExpirationTime and TestNonUniformSettings(durationInfo, data)) then + bestDuration = durationInfo.duration; + bestExpirationTime = durationInfo.expirationTime; + bestName = durationInfo.name; + bestIcon = durationInfo.icon; + bestCount = durationInfo.count; + bestSpellId = durationInfo.spellId; + bestUnitCaster = durationInfo.unitCaster; + end + end + end + return bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestSpellId, bestUnitCaster; + end + + function aura_cache.GetAffected(self, id, triggernum, data) + local affected = {}; + if(self.watched[id] and self.watched[id][triggernum]) then + for guid, acEntry in pairs(self.watched[id][triggernum].players) do + -- Need to check if cached data conforms to trigger + if (TestNonUniformSettings(acEntry, data)) then + affected[self.players[guid]] = true; + end + end + end + return affected; + end + + function aura_cache.GetUnaffected(self, id, triggernum, data) + local affected = self:GetAffected(id, triggernum, data); + local ret = {}; + for guid, name in pairs(self.players) do + if not(affected[name]) then + ret[name] = true; + end + end + return ret; + end + + function aura_cache.AssertAura(self, id, triggernum, guid, duration, expirationTime, name, icon, count, casterGUID, spellId, unitCaster) + -- Don't watch aura on non watched players + if not self.players[guid] then return end + + if not(self.watched[id][triggernum].players[guid]) then + self.watched[id][triggernum].players[guid] = { + duration = duration, + expirationTime = expirationTime, + name = name, + icon = icon, + count = count, + unitCaster = unitCaster, + spellId = spellId, + casterGUID = casterGUID + }; + else + local auradata = self.watched[id][triggernum].players[guid]; + if(expirationTime ~= auradata.expirationTime) then + auradata.duration = duration; + auradata.expirationTime = expirationTime; + auradata.name = name; + auradata.icon = icon; + auradata.count = count; + auradata.unitCaster = unitCaster; + auradata.spellId = spellId; + auradata.casterGUID = casterGUID; + end + end + end + + function aura_cache.DeassertAura(self, id, triggernum, guid) + if(self.watched[id] and self.watched[id][triggernum] and self.watched[id][triggernum].players[guid]) then + self.watched[id][triggernum].players[guid] = nil; + end + end + + function aura_cache.AssertMember(self, guid, name) + if not(self.players[guid]) then + self.max = self.max + 1; + end + self.players[guid] = name; + end + + function aura_cache.DeassertMember(self, guid) + if(self.players[guid]) then + self.players[guid] = nil; + for id, v in pairs(self.watched) do + for triggernum, _ in pairs(v) do + self:DeassertAura(id, triggernum, guid); + end + end + self.max = self.max - 1; + end + end + + function aura_cache.AssertMemberList(self, guids) + local toDelete = {}; + + for guid, _ in pairs(self.players) do + if not(guids[guid]) then + toDelete[guid] = true; + end + end + + for guid, _ in pairs(toDelete) do + self:DeassertMember(guid); + end + for guid, name in pairs(guids) do + self:AssertMember(guid, name); + end + self:ForceUpdate(); + end +end +WeakAuras.aura_cache = aura_cache; + +function WeakAuras.SetAuraVisibility(id, triggernum, cloneId, buffShowOn, unitExists, active, unit, duration, expirationTime, name, icon, count, index, spellId, unitCaster) + local triggerState = WeakAuras.GetTriggerStateForTrigger(id, triggernum); + + local show; + if (not UnitExists(unit)) then + show = unitExists; + elseif (buffShowOn == "showAlways") then + show = true; + elseif(buffShowOn == "showOnMissing") then + show = not active; + else + show = active; + end + + cloneId = cloneId or ""; + + if (not show and not triggerState[cloneId]) then + return false; + end + + triggerState[cloneId] = triggerState[cloneId] or {}; + local state = triggerState[cloneId]; + if (state.show ~= show) then + state.show = show; + state.changed = true; + end + + if (state.show) then + if (state.active ~= active) then + state.active = active; + state.changed = true; + end + if (state.index ~= index) then + state.index = index; + state.changed = true; + end + + if (state.spellId ~= spellId) then + state.spellId = spellId; + state.changed = true; + end + + if (state.progressType ~= "timed") then + state.progressType = "timed"; + state.changed = true; + end + + if (state.expirationTime ~= expirationTime) then + state.expirationTime = expirationTime; + state.changed = true; + end + + if (state.duration ~= duration) then + state.duration = duration; + state.changed = true; + end + + local autoHide = false; + if (state.autoHide ~= autoHide) then + state.autoHide = autoHide; + state.changed = true; + end + + if (state.name ~= name) then + state.name = name; + state.changed = true; + end + + if (state.icon ~= icon) then + state.icon = icon; + state.changed = true; + end + + if (state.stacks ~= count) then + state.stacks = count; + state.changed = true; + end + + unitCaster = unitCaster and UnitName(unitCaster); + if (state.unitCaster ~= unitCaster) then + state.unitCaster = unitCaster; + state.changed = true; + end + + if (state.GUID ~= UnitGUID(unit)) then + state.GUID = UnitGUID(unit); + state.changed = true; + end + end + + if (state.changed) then + return true; + end + return false; +end + +--- Calls GetSpellInfo on trigger data to return aura name and icon. +-- @param trigger +-- @return name and icon +local function GetNameAndIconFromTrigger(trigger) + if (trigger.fullscan) then + if (trigger.spellId) then + local name, _, icon = GetSpellInfo(trigger.spellId); + return name, icon; + end + if (trigger.name) then + return trigger.name, WeakAuras.GetDynamicIconCache(trigger.name); + end + else + if (trigger.spellIds and trigger.spellIds[1]) then + local name, _, icon = GetSpellInfo(trigger.spellIds[1]); + return name, icon; + end + if (trigger.names and trigger.names[1]) then + return trigger.names[1], WeakAuras.GetDynamicIconCache(trigger.names[1]); + end + end +end + +local aura_scan_cache = {}; +local aura_lists = {}; +function WeakAuras.ScanAuras(unit) + local time = GetTime(); + + -- Reset scan cache for this unit + aura_scan_cache[unit] = aura_scan_cache[unit] or {}; + for i,v in pairs(aura_scan_cache[unit]) do + v.up_to_date = 0; + end + + -- Make unit available outside + local old_unit = WeakAuras.CurrentUnit; + WeakAuras.CurrentUnit = unit; + + local fixedUnit = unit == "party0" and "player" or unit + local uGUID = UnitGUID(fixedUnit) or fixedUnit; + + -- Link corresponding display (and aura cache) + local aura_object; + wipe(aura_lists); + if(unit:sub(1, 4) == "raid") then + if(aura_cache.players[uGUID]) then + aura_lists[1] = loaded_auras["group"]; + aura_object = aura_cache; + end + elseif(unit:sub(1, 5) == "party") then + aura_lists[1] = loaded_auras["group"]; + aura_object = aura_cache; + elseif(specificBosses[unit]) then + aura_lists[1] = loaded_auras["boss"]; + elseif(unit:sub(1,5) == "arena") then + aura_lists[1] = loaded_auras["arena"]; + else + if(unit == "player" and loaded_auras["group"]) then + WeakAuras.ScanAuras("party0"); + end + aura_lists[1] = loaded_auras[unit]; + end + + -- Add group auras for specific units -- XXX: why? + if(specificUnits[unit] and not aura_object) then + tinsert(aura_lists, loaded_auras["group"]); + end + + -- Locals + local cloneIdList; + local groupcloneToUpdate; + + -- Units GUID + unit = fixedUnit; + + -- Iterate over all displays (list of display lists) + for _, aura_list in pairs(aura_lists) do + -- Locals + local name, rank, icon, count, duration, expirationTime, unitCaster, isStealable, spellId = true; + local tooltip, debuffClass, tooltipSize; + local remaining, checkPassed; + + -- Iterate over all displays (display lists) + for id,triggers in pairs(aura_list) do + WeakAuras.StartProfileAura(id); + -- Iterate over all triggers + local updateTriggerState = false; + for triggernum, data in pairs(triggers) do + if(not data.specificUnit or UnitIsUnit(data.unit, unit)) then + -- Filters + local filter = data.debuffType..(data.ownOnly and "|PLAYER" or ""); + local active = false; + + -- Full aura scan works differently + if(data.fullscan) then + -- Make sure scan cache exists + aura_scan_cache[unit][filter] = aura_scan_cache[unit][filter] or {up_to_date = 0}; + + -- Reset clone list + if cloneIdList then wipe(cloneIdList); end + if(data.autoclone and not cloneIdList) then + cloneIdList = {}; + end + + -- Iterate over all units auras + local index = 0; name = true; + while(name) do + -- Get nexted! + index = index + 1; + + -- Update scan cache + if(aura_scan_cache[unit][filter].up_to_date < index) then + -- Query aura data + name, rank, icon, count, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId = UnitAura(unit, index, filter); + if (debuffClass == nil) then + debuffClass = "none"; + elseif (debuffClass == "") then + debuffClass = "enrage" + else + debuffClass = string.lower(debuffClass); + end + local tooltipSize1, tooltipSize2, tooltipSize3; + tooltip, _, tooltipSize1, tooltipSize2, tooltipSize3 = WeakAuras.GetAuraTooltipInfo(unit, index, filter); + tooltipSize = {tooltipSize1, tooltipSize2, tooltipSize3} + aura_scan_cache[unit][filter][index] = aura_scan_cache[unit][filter][index] or {}; + + -- Save aura data to cache + local current_aura = aura_scan_cache[unit][filter][index]; + current_aura.name = name; + current_aura.icon = icon; + current_aura.count = count; + current_aura.duration = duration; + current_aura.expirationTime = expirationTime; + current_aura.isStealable = isStealable; + current_aura.spellId = spellId; + current_aura.tooltip = tooltip; + current_aura.debuffClass = debuffClass; + current_aura.tooltipSize = tooltipSize; + current_aura.unitCaster = unitCaster; + + -- Updated + aura_scan_cache[unit][filter].up_to_date = index; + + -- Use cached data instead + else + -- Fetch cached aura data + local current_aura = aura_scan_cache[unit][filter][index]; + name = current_aura.name; + icon = current_aura.icon; + count = current_aura.count; + duration = current_aura.duration; + expirationTime = current_aura.expirationTime; + isStealable = current_aura.isStealable; + spellId = current_aura.spellId; + tooltip = current_aura.tooltip; + debuffClass = current_aura.debuffClass; + tooltipSize = current_aura.tooltipSize; + if unitCaster ~= nil then + unitCaster = current_aura.unitCaster + else + unitCaster = "Unknown" + end + end + + local casGUID = unitCaster and UnitGUID(unitCaster); + + -- Aura conforms to trigger options? + if(data.subcount) then + local index = data.subcountCount or 1; + count = tooltipSize[index]; + end + if(name and ((not data.count) or count and data.count(count)) and (data.ownOnly ~= false or not UnitIsUnit("player", unitCaster or "")) and data.scanFunc(name, tooltip, isStealable, spellId, debuffClass)) then + remaining = expirationTime - time; + checkPassed = true; + if(data.remFunc) then + if not(data.remFunc(remaining)) then + checkPassed = false; + end + + -- Schedule remaining time, re-scan later + if(remaining > data.rem) then + WeakAuras.ScheduleAuraScan(unit, time + (remaining - data.rem)); + end + end + + if checkPassed then + -- Show display and handle clones + WeakAuras.SetDynamicIconCache(name, spellId, icon); + if(data.autoclone) then + local cloneId = name .. spellId .."-"..(casGUID or "unknown"); + if (WeakAuras.SetAuraVisibility(id, triggernum, cloneId, data.buffShowOn, data.unitExists, true, unit, duration, expirationTime, name, icon, count, index, spellId, unitCaster)) then + updateTriggerState = true; + end + active = true; + cloneIdList[cloneId] = true; + -- Simply show display (show) + else + if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, true, unit, duration, expirationTime, name, icon, count, index, spellId, unitCaster)) then + updateTriggerState = true; + end + active = true; + break; + end + end + end + end + + -- Update display visibility and clones visibility (hide) + if not(active) then + local nameFromTrigger, iconFromTrigger; + nameFromTrigger, iconFromTrigger = GetNameAndIconFromTrigger(data); + if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, nil, unit, 0, math.huge, nameFromTrigger, iconFromTrigger)) then + updateTriggerState = true; + end + end + if(data.autoclone) then + WeakAuras.SetAllStatesHiddenExcept(id, triggernum, cloneIdList); + updateTriggerState = true; + end + + -- Not using full aura scan + else + -- Reset clone list + if groupcloneToUpdate then wipe(groupcloneToUpdate); end + if(aura_object and data.groupclone and not data.specificUnit and not groupcloneToUpdate) then + groupcloneToUpdate = {}; + end + + -- Check all selected auras (for one trigger) + + local bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestCasGUID, bestSpellId, bestUnitCaster; + + for index, checkname in pairs(data.names) do + -- Fetch aura data + local detected + for i = 1, BUFF_MAX_DISPLAY do + name, rank, icon, count, _, duration, expirationTime, unitCaster, isStealable, _, spellId = UnitAura(unit, i, filter); + if not name then break end + if name == checkname then + detected = true + break + end + end + + if (detected) then + WeakAuras.SetDynamicIconCache(name, spellId, icon); + end + checkPassed = false; + + -- Aura conforms to trigger options? + if(name and ((not data.count) or data.count(count)) and (data.ownOnly ~= false or not UnitIsUnit("player", unitCaster or ""))) then + remaining = expirationTime - time; + checkPassed = true; + if(data.remFunc) then + if not(data.remFunc(remaining)) then + checkPassed = false; + end + + -- Schedule remaining time, re-scan later + if(remaining > data.rem) then + WeakAuras.ScheduleAuraScan(unit, time + (remaining - data.rem)); + end + end + end + + local casGUID = unitCaster and UnitGUID(unitCaster); + + -- Aura conforms to trigger + if(checkPassed) then + active = true; + + if (not bestExpirationTime or expirationTime > bestExpirationTime) then + bestDuration = duration; + bestExpirationTime = expirationTime; + bestName = name; + bestIcon = icon; + bestCount = count; + bestCasGUID = casGUID; + bestSpellId = spellId; + bestUnitCaster = unitCaster; + end + end + end + + local satisfies_role = true + if data.group_role then + satisfies_role = data.group_role == "ANY" or UnitGroupRolesAssigned(unit) == data.group_role + end + local satisfies_ignoreSelf = not data.ignoreSelf or not UnitIsUnit(unit, "player") + if not satisfies_role or not satisfies_ignoreSelf then + active = false + end + + -- Update aura cache (and clones) + if (active) then + if(aura_object and not data.specificUnit) then + aura_object:AssertAura(id, triggernum, uGUID, bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, bestCasGUID, bestSpellId, bestUnitCaster); + if(data.groupclone) then + groupcloneToUpdate[uGUID] = GetUnitName(unit, true); + end + else + if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, true, unit, bestDuration, bestExpirationTime, bestName, bestIcon, bestCount, nil, bestSpellId, bestUnitCaster)) then + updateTriggerState = true; + end + end + else + if(aura_object and not data.specificUnit) then + -- Update aura cache (and clones) + aura_object:DeassertAura(id, triggernum, uGUID); + if(data.groupclone) then + groupcloneToUpdate[uGUID] = GetUnitName(unit, true); + end + else + local nameFromTrigger, iconFromTrigger; + nameFromTrigger, iconFromTrigger = GetNameAndIconFromTrigger(data); + if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, nil, unit, 0, math.huge, nameFromTrigger, iconFromTrigger)) then + updateTriggerState = true; + end + end + end + + -- Processing a unit=group related unit + if(aura_object and not data.specificUnit) then + -- unit=group require valid count function + if(data.group_count) then + -- Query count from aura cache + local aura_count = aura_object:GetNumber(id, triggernum, data) + local max + if (data.group_role) then + max = aura_cache[data.group_role] + if (data.ignoreSelf and aura_cache.playerRole == data.group_role) then + max = max - 1; + end + else + max = aura_object:GetMaxNumber(); + if (data.ignoreSelf) then + max = max - 1; + end + end + local satisfies_count = data.group_count(aura_count, max); + + if(data.hideAlone and not IsInGroup()) then + satisfies_count = false; + end + + -- Satisfying count condition + if(satisfies_count) then + -- Update clones (show) + if(data.groupclone) then + for guid, playerName in pairs(aura_cache.players) do + local duration, expirationTime, name, icon, count, spellId, unitCaster = aura_object:GetPlayerDynamicInfo(id, triggernum, guid, data); + if(name ~= "") then + if (WeakAuras.SetAuraVisibility(id, triggernum, playerName, data.buffShowOn, data.unitExists, true, unit, duration, expirationTime, playerName, icon, count, nil, spellId, unitCaster)) then + updateTriggerState = true; + end + else + if (WeakAuras.SetAuraVisibility(id, triggernum, playerName, data.buffShowOn, data.unitExists, nil, unit, duration, expirationTime, playerName, icon, count, nil, spellId, unitCaster)) then + updateTriggerState = true; + end + end + end + + -- Update display information + else + -- Get display related information + local duration, expirationTime, name, icon, count, spellId, unitCaster = aura_object:GetDynamicInfo(id, triggernum, data); + + -- Process affected players + if(data.name_info == "players") then + local affected = aura_object:GetAffected(id, triggernum, data); + local num = 0; + name = ""; + for affected_name, _ in pairs(affected) do + local space = affected_name:find(" "); + name = name..(space and affected_name:sub(1, space - 1).."*" or affected_name)..", "; + num = num + 1; + end + if(num == 0) then + name = WeakAuras.L["None"]; + else + name = name:sub(1, -3); + end + -- Process unaffected players + elseif(data.name_info == "nonplayers") then + local unaffected = aura_object:GetUnaffected(id, triggernum, data); + local num = 0; + name = ""; + for unaffected_name, _ in pairs(unaffected) do + local space = unaffected_name:find(" "); + name = name..(space and unaffected_name:sub(1, space - 1).."*" or unaffected_name)..", "; + num = num + 1; + end + if(num == 0) then + name = WeakAuras.L["None"]; + else + name = name:sub(1, -3); + end + end + + -- Process stacks/aura count + if(data.stack_info == "count") then + count = aura_count; + end + + -- Update display visibility (show) + if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, true, unit, duration, expirationTime, name, icon, count, nil, spellId, unitCaster)) then + updateTriggerState = true; + end + end + + -- Not satisfying count + else + -- Update clones + if(data.groupclone) then + WeakAuras.SetAllStatesHidden(id, triggernum); + updateTriggerState = true; + -- Update display visibility (hide) + else + local nameFromTrigger, iconFromTrigger; + nameFromTrigger, iconFromTrigger = GetNameAndIconFromTrigger(data); + if (WeakAuras.SetAuraVisibility(id, triggernum, nil, data.buffShowOn, data.unitExists, nil, unit, 0, math.huge, nameFromTrigger, iconFromTrigger)) then + updateTriggerState = true; + end + end + end + end + end + end + end + end + if (updateTriggerState) then + WeakAuras.UpdatedTriggerState(id); + end + WeakAuras.StopProfileAura(id); + end + end + + -- Update current unit once again + WeakAuras.CurrentUnit = old_unit; +end + +function WeakAuras.ScanAurasGroup() + if IsInRaid() then + for i=1, GetNumGroupMembers() do + WeakAuras.ScanAuras(WeakAuras.raidUnits[i]) + end + elseif IsInGroup() then + for i=1, GetNumSubgroupMembers() do + WeakAuras.ScanAuras(WeakAuras.partyUnits[i]) + end + WeakAuras.ScanAuras("player") + else + WeakAuras.ScanAuras("player") + end +end + +local function GroupRosterUpdate(event) + WeakAuras.StartProfileSystem("bufftrigger"); + aura_cache.TANK = 0; + aura_cache.HEALER = 0; + aura_cache.DAMAGER = 0; + aura_cache.playerRole = "NONE"; + local recheck = false; + local groupMembers,playerName,uid,guid, role = {}; + if IsInRaid() then + for i=1, GetNumGroupMembers() do + uid = WeakAuras.raidUnits[i]; + role = UnitGroupRolesAssigned(uid) + if aura_cache[role] then + aura_cache[role] = aura_cache[role] + 1; + end + playerName = GetUnitName(uid,true); + if playerName then + playerName = playerName:gsub("-", " - "); + else + recheck = true; + end + if (playerName == UNKNOWNOBJECT) then + recheck = true; + end + guid = UnitGUID(uid); + if (guid) then + groupMembers[guid] = playerName; + end + end + role = UnitGroupRolesAssigned("player") + aura_cache.playerRole = role; + elseif IsInGroup() then + for i=1, GetNumSubgroupMembers() do + uid = WeakAuras.partyUnits[i]; + role = UnitGroupRolesAssigned(uid) + if (aura_cache[role]) then + aura_cache[role] = aura_cache[role] + 1; + end + guid = UnitGUID(uid); + local playerName = GetUnitName(uid,true); + if (playerName == UNKNOWNOBJECT) then + recheck = true; + end + if (guid) then + groupMembers[guid] = playerName; + end + end + role = UnitGroupRolesAssigned("player") + if (aura_cache[role]) then + aura_cache[role] = aura_cache[role] + 1; + aura_cache.playerRole = role; + end + end + + if (not WeakAuras.myGUID) then + WeakAuras.myGUID = UnitGUID("player") + end + groupMembers[WeakAuras.myGUID] = WeakAuras.me; + aura_cache:AssertMemberList(groupMembers); + if (recheck) then + timer:ScheduleTimer(GroupRosterUpdate, 0.5); + end + WeakAuras.StopProfileSystem("bufftrigger"); +end + +local groupFrame = CreateFrame("FRAME"); +WeakAuras.frames["Group Makeup Handler"] = groupFrame; +groupFrame:RegisterEvent("PARTY_MEMBERS_CHANGED"); +groupFrame:RegisterEvent("RAID_ROSTER_UPDATE"); +groupFrame:RegisterEvent("PLAYER_ENTERING_WORLD"); +groupFrame:SetScript("OnEvent", function(self, event) + GroupRosterUpdate(); +end); + +do + local pendingTracks = {}; + local UIDsfromGUID = {}; + local GUIDfromUID = {}; + + function WeakAuras.ReleaseUID(UID) + if(GUIDfromUID[UID]) then + if(UIDsfromGUID[GUIDfromUID[UID]] and UIDsfromGUID[GUIDfromUID[UID]][UID]) then + UIDsfromGUID[GUIDfromUID[UID]][UID] = nil; + else + -- If this code is reached, it means there was some kind of coordination error between the two lists + -- This shouldn't ever happen, but it is recoverable + -- Search through the whole UIDsfromGUID table and remove all instances of UID + for GUID,UIDs in pairs(UIDsfromGUID) do + for iUID,v in pairs(UIDs) do + if(iUID == UID or iUID == UID) then + UIDs[iUID] = nil; + end + end + end + end + end + GUIDfromUID[UID] = nil; + end + + function WeakAuras.SetUID(GUID, UID) + WeakAuras.ReleaseUID(UID); + if not(UIDsfromGUID[GUID]) then + UIDsfromGUID[GUID] = {}; + end + UIDsfromGUID[GUID][UID] = true; + GUIDfromUID[UID] = GUID; + end + + function WeakAuras.GetUID(GUID) + if not(UIDsfromGUID[GUID]) then + return nil; + end + -- iterate through key/value pairs from the table of UIDs that are registered for this GUID, until a *confirmed* match is found + -- confirming is necessary in case UIDs are not always released correctly (which may actually be close to impossible) + for returnUID,v in pairs(UIDsfromGUID[GUID]) do + -- check the validity of this entry + if(UnitGUID(returnUID) == GUID) then + return returnUID; + else + WeakAuras.ReleaseUID(returnUID); + end + end + return nil; + end + + --- Updates region data to see if states changed. + -- @param id + -- @param data + -- @param triggernum + -- @param GUID + -- @return boolean + local function updateRegion(id, data, triggernum, GUID) + local auradata = data.GUIDs[GUID]; + local triggerState = WeakAuras.GetTriggerStateForTrigger(id, triggernum); + triggerState[GUID] = triggerState[GUID] or {}; + local state = triggerState[GUID]; + if (state.progressType ~= "timed") then + state.progressType = "timed"; + state.changed = true; + end + + if(auradata and auradata.unitName) then + if (state.show ~= true) then + state.show = true; + state.changed = true; + end + + if (state.expirationTime ~= auradata.expirationTime) then + state.expirationTime = auradata.expirationTime; + state.changed = true; + end + + if (state.duration ~= auradata.duration) then + state.duration = auradata.duration; + state.changed = true; + end + + if (state.autoHide ~= true) then + state.autoHide = true; + state.changed = true; + end + + if (state.name ~= auradata.unitName) then + state.name = auradata.unitName; + state.changed = true; + end + + local icon = auradata.icon or WeakAuras.GetDynamicIconCache(auradata.name) or "Interface\\Icons\\INV_Misc_QuestionMark"; + if (state.icon ~= icon) then + state.icon = icon; + state.changed = true; + end + + if (state.stacks ~= auradata.count) then + state.stacks = auradata.count; + state.changed = true; + end + + if (state.unitCaster ~= auradata.unitCaster) then + state.unitCaster = auradata.unitCaster; + state.changed = true; + end + + if (state.GUID ~= GUID) then + state.GUID = GUID; + state.changed = true; + end + else + if (state.show ~= false) then + state.show = false; + state.changed = true; + end + end + + if (state.changed) then + return true; + end + return false; + end + + local function updateSpell(spellName, unit, destGUID) + if (not loaded_auras[spellName]) then return end; + for id, triggers in pairs(loaded_auras[spellName]) do + WeakAuras.StartProfileAura(id); + local updateTriggerState = false; + for triggernum, data in pairs(triggers) do + local filter = data.debuffType..(data.ownOnly and "|PLAYER" or ""); + local detected + local name, rank, icon, count, duration, expirationTime, unitCaster, spellId, _ + for i = 1, BUFF_MAX_DISPLAY do + name, rank, icon, count, _, duration, expirationTime, unitCaster, _, _, spellId = UnitAura(unit, i, filter); + if not name then break end + if name == spellName then + detected = true + break + end + end + + if(detected and (data.spellId == nil or data.spellId == spellId)) then + data.GUIDs = data.GUIDs or {}; + data.GUIDs[destGUID] = data.GUIDs[destGUID] or {}; + data.GUIDs[destGUID].name = spellName; + data.GUIDs[destGUID].unitName = GetUnitName(unit, true); + data.GUIDs[destGUID].duration = duration; + data.GUIDs[destGUID].expirationTime = expirationTime; + data.GUIDs[destGUID].icon = icon; + data.GUIDs[destGUID].count = count; + data.GUIDs[destGUID].unitCaster = unitCaster and UnitName(unitCaster); + data.GUIDs[destGUID].spellId = spellId; + updateTriggerState = updateRegion(id, data, triggernum, destGUID) or updateTriggerState; + end + end + if (updateTriggerState) then + WeakAuras.UpdatedTriggerState(id); + end + WeakAuras.StopProfileAura(id); + end + end + + local function combatLog(_, event, _, sourceName, _, destGUID, destName, _, spellId, spellName, _, auraType, amount) + if(loaded_auras[spellName]) then + if(message == "SPELL_AURA_APPLIED" or message == "SPELL_AURA_REFRESH" or message == "SPELL_AURA_APPLIED_DOSE" or message == "SPELL_AURA_REMOVED_DOSE") then + local unit = WeakAuras.GetUID(destGUID); + if(unit) then + updateSpell(spellName, unit, destGUID); + else + for id, triggers in pairs(loaded_auras[spellName]) do + WeakAuras.StartProfileAura(id); + local updateTriggerState = false; + for triggernum, data in pairs(triggers) do + if((not data.ownOnly) or UnitIsUnit(sourceName or "", "player")) then + pendingTracks[destGUID] = pendingTracks[destGUID] or {}; + pendingTracks[destGUID][spellName] = true; + + data.GUIDs = data.GUIDs or {}; + data.GUIDs[destGUID] = data.GUIDs[destGUID] or {}; + data.GUIDs[destGUID].name = spellName; + data.GUIDs[destGUID].unitName = destName; + local icon = spellId and select(3, GetSpellInfo(spellId)); + if (message == "SPELL_AURA_APPLIED_DOSE" or message == "SPELL_AURA_REMOVED_DOSE") then + -- Shouldn't affect duration/expirationTime nor icon + data.GUIDs[destGUID].duration = data.GUIDs[destGUID].duration or 0; + data.GUIDs[destGUID].expirationTime = data.GUIDs[destGUID].expirationTime or math.huge; + data.GUIDs[destGUID].icon = data.GUIDs[destGUID].icon or icon; + else + data.GUIDs[destGUID].duration = 0; + data.GUIDs[destGUID].expirationTime = math.huge; + data.GUIDs[destGUID].icon = icon; + end + data.GUIDs[destGUID].count = amount or 0; + data.GUIDs[destGUID].spellId = spellId; + data.GUIDs[destGUID].unitCaster = sourceName and UnitName(sourceName); + + updateTriggerState = updateRegion(id, data, triggernum, destGUID) or updateTriggerState; + end + end + if (updateTriggerState) then + WeakAuras.UpdatedTriggerState(id); + end + WeakAuras.StopProfileAura(id); + end + end + elseif(message == "SPELL_AURA_REMOVED") then + for id, triggers in pairs(loaded_auras[spellName]) do + local updateTriggerState = false; + for triggernum, data in pairs(triggers) do + if((not data.ownOnly) or UnitIsUnit(sourceName or "", "player")) then + -- WeakAuras.debug("Removed "..spellName.." from "..destGUID.." ("..(data.GUIDs and data.GUIDs[destGUID] and data.GUIDs[destGUID].unitName or "error")..") - "..(data.ownOnly and "own only" or "not own only")..", "..sourceName, 3); + data.GUIDs = data.GUIDs or {}; + data.GUIDs[destGUID] = nil; + + updateTriggerState = updateRegion(id, data, triggernum, destGUID) or updateTriggerState; + end + end + if (updateTriggerState) then + WeakAuras.UpdatedTriggerState(id); + end + end + end + end + end + + local function uidTrack(unit) + local GUID = UnitGUID(unit); + if(GUID) then + WeakAuras.SetUID(GUID, unit); + if(pendingTracks[GUID]) then + for spellName,_ in pairs(pendingTracks[GUID]) do + updateSpell(spellName, unit, GUID); + pendingTracks[GUID][spellName] = nil; + end + end + else + WeakAuras.ReleaseUID(unit); + end + unit = unit.."target"; + GUID = UnitGUID(unit); + if(GUID) then + WeakAuras.SetUID(GUID, unit); + if(pendingTracks[GUID]) then + for spellName,_ in pairs(pendingTracks[GUID]) do + updateSpell(spellName, unit, GUID); + pendingTracks[GUID][spellName] = nil; + end + end + else + WeakAuras.ReleaseUID(unit); + end + end + + local function checkExists() + WeakAuras.StartProfileSystem("bufftrigger - multi"); + for unit, auras in pairs(loaded_auras) do + if not(WeakAuras.unit_types[unit]) then + for id, triggers in pairs(auras) do + WeakAuras.StartProfileAura(id); + local updateTriggerState = false; + for triggernum, data in pairs(triggers) do + if(data.GUIDs) then + for GUID, GUIDData in pairs(data.GUIDs) do + if(GUIDData.expirationTime and GUIDData.expirationTime + 2 < GetTime()) then + data.GUIDs[GUID] = nil; + updateTriggerState = updateRegion(id, data, triggernum, GUID) or updateTriggerState; + end + end + end + end + if (updateTriggerState) then + WeakAuras.UpdatedTriggerState(id); + end + WeakAuras.StopProfileAura(id); + end + end + end + WeakAuras.StopProfileSystem("bufftrigger - multi"); + end + + local function handleEvent(frame, event, ...) + WeakAuras.StartProfileSystem("bufftrigger - multi"); + if(event == "COMBAT_LOG_EVENT_UNFILTERED") then + combatLog(...); + elseif(event == "UNIT_TARGET") then + uidTrack(...); + elseif(event == "PLAYER_FOCUS_CHANGED") then + uidTrack("focus"); + elseif(event == "NAME_PLATE_UNIT_ADDED") then + uidTrack(...); + elseif(event == "NAME_PLATE_UNIT_REMOVED") then + local unit = ... + WeakAuras.ReleaseUID(unit); + unit = unit.."target"; + WeakAuras.ReleaseUID(unit); + elseif(event == "UNIT_AURA") then + -- Note: Using UNIT_AURA in addition to COMBAT_LOG_EVENT_UNFILTERED, + -- because the combat log event does not contain duration information + local uid = ...; + local guid = UnitGUID(uid); + + for spellName, auras in pairs(loaded_auras) do + if not(WeakAuras.unit_types[spellName]) then + for id, triggers in pairs(auras) do + WeakAuras.StartProfileAura(id); + local updateTriggerState = false; + for triggernum, data in pairs(triggers) do + local filter = data.debuffType..(data.ownOnly and "|PLAYER" or ""); + + local detected + local name, rank, icon, count, duration, expirationTime, unitCaster, _ + for i = 1, BUFF_MAX_DISPLAY do + name, rank, icon, count, _, duration, expirationTime, unitCaster = UnitAura(uid, i, filter); + if not name then break end + if name == spellName then + detected = true + break + end + end + + if(detected) then + data.GUIDs = data.GUIDs or {}; + data.GUIDs[guid] = data.GUIDs[guid] or {}; + data.GUIDs[guid].name = spellName; + data.GUIDs[guid].unitName = GetUnitName(uid, true); + data.GUIDs[guid].duration = duration; + data.GUIDs[guid].expirationTime = expirationTime; + data.GUIDs[guid].icon = icon; + data.GUIDs[guid].count = count; + data.GUIDs[guid].unitCaster = unitCaster and UnitName(unitCaster); + updateTriggerState = updateRegion(id, data, triggernum, guid) or updateTriggerState; + end + end + if (updateTriggerState) then + WeakAuras.UpdatedTriggerState(id); + end + WeakAuras.StopProfileAura(id); + end + end + end + end + WeakAuras.StopProfileSystem("bufftrigger - multi"); + end + + local combatAuraFrame; + function WeakAuras.InitMultiAura() + if not(combatAuraFrame) then + combatAuraFrame = CreateFrame("frame"); + combatAuraFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED"); + combatAuraFrame:RegisterEvent("UNIT_TARGET"); + combatAuraFrame:RegisterEvent("UNIT_AURA"); + combatAuraFrame:RegisterEvent("PLAYER_FOCUS_CHANGED"); + combatAuraFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED"); + combatAuraFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED"); + combatAuraFrame:RegisterEvent("PLAYER_LEAVING_WORLD"); + combatAuraFrame:SetScript("OnEvent", handleEvent); + WeakAuras.frames["Multi-target Aura Trigger Handler"] = combatAuraFrame; + timer:ScheduleRepeatingTimer(checkExists, 10) + end + end +end + +do + local scheduled_scans = {}; + + function WeakAuras.ScheduleAuraScan(unit, fireTime) + scheduled_scans[unit] = scheduled_scans[unit] or {}; + if not(scheduled_scans[unit][fireTime]) then + WeakAuras.debug("Scheduled aura scan for "..unit.." at "..fireTime); + local doScan = function() + WeakAuras.debug("Performing aura scan for "..unit.." at "..fireTime.." ("..GetTime()..")"); + scheduled_scans[unit][fireTime] = nil; + WeakAuras.ScanAuras(unit); + end + scheduled_scans[unit][fireTime] = timer:ScheduleTimerFixed(doScan, fireTime - GetTime() + 0.1); + end + end +end + +--- Adds auras to the loaded_auras table +-- @param id +-- @param triggernum +-- @param data +local function LoadAura(id, triggernum, data) + local unit; + if(data.specificUnit) then + if(data.unit:lower():sub(1,4) == "boss") then + specificBosses[data.unit] = true; + unit = "boss"; + elseif(data.unit:lower():sub(1,5) == "arena") then + unit = "arena"; + else + specificUnits[data.unit] = true; + unit = "group"; + end + elseif(data.unit == "multi") then + unit = data.name + else + unit = data.unit; + end + if(unit) then + loaded_auras[unit] = loaded_auras[unit] or {}; + loaded_auras[unit][id] = loaded_auras[unit][id] or {}; + loaded_auras[unit][id][triggernum] = data; + end +end + +function BuffTrigger.ScanAll() + local unitIdstoScan = {}; + local groupScan = false; + for unit, auras in pairs(loaded_auras) do + if(unit == "group") then + groupScan = true; + elseif(WeakAuras.unit_types[unit]) then + unitIdstoScan[unit] = true; + end + end + + if (groupScan) then + WeakAuras.ScanAurasGroup(); + end + + for unit, _ in pairs(unitIdstoScan) do + WeakAuras.ScanAuras(unit); + end +end + +local aura_scan_cooldowns = {}; +local checkingScanCooldowns; +local scanCooldownFrame = CreateFrame("frame"); +WeakAuras.frames["Aura Scan Cooldown"] = scanCooldownFrame; + +local checkScanCooldownsFunc = function() + WeakAuras.StartProfileSystem("bufftrigger") + for unit,_ in pairs(aura_scan_cooldowns) do + aura_scan_cooldowns[unit] = nil; + WeakAuras.ScanAuras(unit); + end + checkingScanCooldowns = nil; + scanCooldownFrame:SetScript("OnUpdate", nil); + WeakAuras.StopProfileSystem("bufftrigger") +end + +local frame = CreateFrame("FRAME"); +WeakAuras.frames["WeakAuras Buff Frame"] = frame; +frame:RegisterEvent("PLAYER_ENTERING_WORLD"); +frame:RegisterEvent("PLAYER_FOCUS_CHANGED"); +frame:RegisterEvent("PLAYER_TARGET_CHANGED"); +frame:RegisterEvent("INSTANCE_ENCOUNTER_ENGAGE_UNIT"); +frame:RegisterEvent("UNIT_AURA"); +frame:RegisterEvent("UNIT_PET") +frame:SetScript("OnEvent", function (frame, event, arg1, arg2, ...) + if (WeakAuras.IsPaused()) then return end; + WeakAuras.StartProfileSystem("bufftrigger"); + if (event == "PLAYER_ENTERING_WORLD") then + BuffTrigger.ScanAll(); + elseif(event == "PLAYER_TARGET_CHANGED") then + WeakAuras.ScanAuras("target"); + elseif(event == "PLAYER_FOCUS_CHANGED") then + WeakAuras.ScanAuras("focus"); + elseif(event == "UNIT_PET" and arg1 == "player") then + WeakAuras.ScanAuras("pet"); + elseif(event == "INSTANCE_ENCOUNTER_ENGAGE_UNIT") then + for unit,_ in pairs(specificBosses) do + WeakAuras.ScanAuras(unit); + end + elseif(event == "UNIT_AURA") then + if( + loaded_auras[arg1] + or ( + loaded_auras["group"] + and ( + arg1:sub(1, 4) == "raid" + or arg1:sub(1, 5) == "party" + or arg1 == "player" + ) + ) + or ( + loaded_auras["boss"] + and arg1:sub(1,4) == "boss" + ) + or ( + loaded_auras["arena"] + and arg1:sub(1,5) == "arena" + ) + ) then + -- This throttles aura scans to only happen at most once per frame per unit + if not(aura_scan_cooldowns[arg1]) then + aura_scan_cooldowns[arg1] = true; + if not(checkingScanCooldowns) then + checkingScanCooldowns = true; + scanCooldownFrame:SetScript("OnUpdate", checkScanCooldownsFunc); + end + end + end + end + WeakAuras.StopProfileSystem("bufftrigger"); +end); + +function BuffTrigger.UnloadAll() + wipe(loaded_auras); +end + +function BuffTrigger.LoadDisplays(toLoad) + for id in pairs(toLoad) do + if(auras[id]) then + for triggernum, data in pairs(auras[id]) do + if(auras[id] and auras[id][triggernum]) then + LoadAura(id, triggernum, data); + end + end + end + end +end + +function BuffTrigger.UnloadDisplays(toUnload) + for id in pairs(toUnload) do + for unitname, auras in pairs(loaded_auras) do + auras[id] = nil; + end + end +end + +function BuffTrigger.FinishLoadUnload() + BuffTrigger.ScanAll(); +end + +--- Removes all data for an aura id +-- @param id +function BuffTrigger.Delete(id) + auras[id] = nil; + for i,v in pairs(loaded_auras) do + v[id] = nil; + end +end + +--- Updates all data for aura oldid to use newid +-- @param oldid +-- @param newid +function BuffTrigger.Rename(oldid, newid) + auras[newid] = auras[oldid]; + auras[oldid] = nil; + + aura_cache:Rename(oldid, newid); + + for i,v in pairs(loaded_auras) do + v[newid] = v[oldid]; + v[newid] = nil; + end +end + +--- Adds an aura, setting up internal data structures for all buff triggers. +-- @param data +function BuffTrigger.Add(data) + local id = data.id; + auras[id] = nil; + + for triggernum, triggerData in ipairs(data.triggers) do + local trigger, untrigger = triggerData.trigger, triggerData.untrigger + local triggerType; + if(type(trigger) == "table") then + triggerType = trigger.type; + if(triggerType == "aura") then + trigger.names = trigger.names or {}; + trigger.spellIds = trigger.spellIds or {} + trigger.unit = trigger.unit or "player"; + trigger.debuffType = trigger.debuffType or "HELPFUL"; + + local countFunc, countFuncStr; + if(trigger.useCount) then + countFuncStr = function_strings.count:format(trigger.countOperator or ">=", tonumber(trigger.count) or 0); + countFunc = WeakAuras.LoadFunction(countFuncStr); + end + + local remFunc, remFuncStr; + if(trigger.useRem) then + remFuncStr = function_strings.count:format(trigger.remOperator or ">=", tonumber(trigger.rem) or 0); + remFunc = WeakAuras.LoadFunction(remFuncStr); + end + + local group_countFunc, group_countFuncStr; + if(trigger.unit == "group") then + local count, countType = WeakAuras.ParseNumber(trigger.group_count); + if(trigger.group_countOperator and count and countType) then + if(countType == "whole") then + group_countFuncStr = function_strings.count:format(trigger.group_countOperator, count); + else + group_countFuncStr = function_strings.count_fraction:format(trigger.group_countOperator, count); + end + else + group_countFuncStr = function_strings.count:format(">", 0); + end + group_countFunc = WeakAuras.LoadFunction(group_countFuncStr); + WeakAuras.aura_cache:Watch(id, triggernum); + end + + local scanFunc; + if(trigger.fullscan) then + scanFunc = function(name, tooltip, isStealable, spellId, debuffClass) + if ( + ( + (not trigger.use_name) or ( + trigger.name and trigger.name ~= "" and ( + trigger.name_operator == "==" and name == trigger.name + or trigger.name_operator == "find('%s')" and name:find(trigger.name) + or trigger.name_operator == "match('%s')" and name:match(trigger.name) + ) + ) + ) + and ( + (not trigger.use_tooltip) or ( + trigger.tooltip and trigger.tooltip ~= "" and ( + trigger.tooltip_operator == "==" and tooltip == trigger.tooltip + or trigger.tooltip_operator == "find('%s')" and tooltip:find(trigger.tooltip) + or trigger.tooltip_operator == "match('%s')" and tooltip:match(trigger.tooltip) + ) + ) + ) + and ((not trigger.use_stealable) or isStealable) + and ((not trigger.use_spellId) or spellId == tonumber(trigger.spellId)) + and ((not trigger.use_debuffClass) or debuffClass == trigger.debuffClass) + ) then + return true; + else + return false; + end + end -- end scanFunc + end + + if(trigger.unit == "multi") then + WeakAuras.InitMultiAura(); + end + + if (trigger.buffShowOn == nil) then + trigger.buffShowOn = "showOnActive"; + end + + local buffShowOn = "showOnActive"; + local unitExists = true; + + if (not(trigger.unit ~= "group" and trigger.autoclone) and trigger.unit ~= "multi" and trigger.unit ~= "group" and trigger.unit ~= "player") then + unitExists = trigger.unitExists; + end + + if (trigger.type == "aura" and not(trigger.unit ~= "group" and trigger.autoclone) + and trigger.unit ~= "multi" and not(trigger.unit == "group" and not trigger.groupclone)) then + buffShowOn = trigger.buffShowOn; + end + + auras[id] = auras[id] or {}; + auras[id][triggernum] = { + count = countFunc, + remFunc = remFunc, + rem = tonumber(trigger.rem) or 0, + group_count = group_countFunc, + fullscan = trigger.fullscan, + autoclone = trigger.autoclone, + groupclone = trigger.groupclone, + subcount = trigger.subcount, + subcountCount = trigger.subcountCount, + scanFunc = scanFunc, + debuffType = trigger.debuffType, + names = trigger.names, + spellIds = trigger.spellIds, + name = trigger.name, + spellId = trigger.spellId, + unit = trigger.unit == "member" and trigger.specificUnit or trigger.unit, + specificUnit = trigger.unit == "member", + useCount = trigger.useCount, + ownOnly = trigger.ownOnly, + buffShowOn = buffShowOn, + unitExists = unitExists, + numAdditionalTriggers = max(#data.triggers - 1, 0), + hideAlone = trigger.hideAlone, + stack_info = trigger.stack_info, + name_info = trigger.name_info, + group_role = trigger.useGroupRole and trigger.group_role, + ignoreSelf = trigger.ignoreSelf + }; + end + end + end +end + +--- Updates old data to the new format. +-- @param data +function BuffTrigger.Modernize(data) + for triggernum, triggerData in ipairs(data.triggers) do + local trigger = triggerData.trigger; + + if (data.internalVersion < 2) then + if (trigger and trigger.type == "aura") then + if (trigger.showOn == nil or trigger.showOn == "showOnCooldown" or trigger.showOn == "showOnReady" or trigger.showOn == "showAlways") then + trigger.showOn = trigger.inverse and "showOnMissing" or "showOnActive"; + trigger.inverse = nil; + end + end + end + + if data.internalVersion < 6 then + if trigger and trigger.type == "aura" then + if trigger.showOn == "showOnMissing" then + trigger.buffShowOn = "showOnMissing" + elseif trigger.showOn == "showActiveOrMissing" then + trigger.buffShowOn = "showAlways" + else + trigger.buffShowOn = "showOnActive" + end + trigger.showOn = nil + end + end + end +end + +--- Returns whether the first trigger could be shown without any affected group members. +-- @param data +-- @param triggernum +-- @return boolean +local function CanGroupShowWithZero(data, triggernum) + local trigger = data.triggers[triggernum].trigger + local group_countFunc, group_countFuncStr; + if(trigger.unit == "group") then + local count, countType = WeakAuras.ParseNumber(trigger.group_count); + if(trigger.group_countOperator and count and countType) then + if(countType == "whole") then + group_countFuncStr = function_strings.count:format(trigger.group_countOperator, count); + else + group_countFuncStr = function_strings.count_fraction:format(trigger.group_countOperator, count); + end + else + group_countFuncStr = function_strings.count:format(">", 0); + end + group_countFunc = WeakAuras.LoadFunction(group_countFuncStr); + return group_countFunc(0, 1); + else + return false; + end +end + +--- Returns whether the trigger can have a duration. +-- @param data +-- @param triggernum +function BuffTrigger.CanHaveDuration(data, triggernum) + local trigger = data.triggers[triggernum].trigger + if (trigger.type == "aura" and not(trigger.unit ~= "group" and trigger.autoclone) and trigger.unit ~= "multi" and not(trigger.unit == "group" and not trigger.groupclone)) then + if (trigger.buffShowOn ~= "showOnMissing") then + return "timed"; + else + return false; + end + end + return "timed"; +end + +--- Returns a table containing the names of all overlays +-- @param data +-- @param triggernum +function BuffTrigger.GetOverlayInfo(data, triggernum) + return {}; +end + +--- Returns whether the icon can be automatically selected. +-- @param data +-- @param triggernum +-- @return boolean +function BuffTrigger.CanHaveAuto(data, triggernum) + return true; +end + +--- Returns whether the trigger can have clones. +-- @param data +-- @param triggernum +-- @return +function BuffTrigger.CanHaveClones(data, triggernum) + local trigger = data.triggers[triggernum].trigger + return (trigger.fullscan and trigger.autoclone) + or (trigger.unit == "group" and trigger.groupclone) + or (trigger.unit == "multi"); +end + +---Returns the type of tooltip to show for the trigger. +-- @param data +-- @param triggernum +-- @return string +function BuffTrigger.CanHaveTooltip(data, triggernum) + local trigger = data.triggers[triggernum].trigger + if(trigger.unit == "group" and trigger.name_info ~= "aura" and not trigger.groupclone) then + return "playerlist"; + elseif(trigger.fullscan and trigger.unit ~= "group") then + return "auraindex"; + else + return "aura"; + end +end + +function BuffTrigger.SetToolTip(trigger, state) + local data = auras[state.id][state.triggernum]; + if(trigger.unit == "group" and trigger.name_info ~= "aura" and not trigger.groupclone) then + local name = ""; + local playerList; + if(trigger.name_info == "players") then + playerList = WeakAuras.aura_cache:GetAffected(state.id, state.triggernum, data); + name = L["Affected"]..":"; + elseif(trigger.name_info == "nonplayers") then + playerList = WeakAuras.aura_cache:GetUnaffected(state.id, state.triggernum, data); + name = L["Missing"]..":"; + else + playerList = {}; + end + + local numPlayers = 0; + for playerName, _ in pairs(playerList) do + numPlayers = numPlayers + 1; + end + + if(numPlayers > 0) then + GameTooltip:AddLine(name); + local numRaid = IsInRaid() and GetNumGroupMembers() or 0; + local groupMembers,playersString = {}; + + if(numRaid > 0) then + local playerName, _, subgroup + for i = 1,numRaid do + -- Battleground-name, given by GetRaidRosterInfo (name-server) to GetUnitName(...) (name - server) transition + playerName, _, subgroup = GetRaidRosterInfo(i); + + if(playerName) then + playerName = playerName:gsub("-", " - ") + if (playerList[playerName]) then + groupMembers[subgroup] = groupMembers[subgroup] or {}; + groupMembers[subgroup][playerName] = true + end + end + end + for subgroup, players in pairs(groupMembers) do + playersString = L["Group %s"]:format(subgroup)..": "; + local _,space,class,classColor; + for playerName, _ in pairs(players) do + space = playerName:find(" "); + _, class = UnitClass((space and playerName:sub(1, space - 1) or playerName)); + classColor = WeakAuras.class_color_types[class]; + playersString = playersString..(classColor or "")..(space and playerName:sub(1, space - 1).."*" or playerName)..(classColor and "|r" or "")..(next(players, playerName) and ", " or ""); + end + GameTooltip:AddLine(playersString); + end + else + local num = 0; + playersString = ""; + local _,space,class,classColor; + for playerName, _ in pairs(playerList) do + space = playerName:find(" "); + _, class = UnitClass((space and playerName:sub(1, space - 1) or playerName)); + classColor = WeakAuras.class_color_types[class]; + playersString = playersString..(classColor or "")..(space and playerName:sub(1, space - 1).."*" or playerName)..(classColor and "|r" or "")..(next(playerList, playerName) and (", "..(num % 5 == 4 and "\n" or "")) or ""); + num = num + 1; + end + GameTooltip:AddLine(playersString); + end + else + GameTooltip:AddLine(name.." "..L["None"]); + end + return true + elseif(trigger.fullscan and trigger.unit ~= "group" and state.index) then + local unit = trigger.unit == "member" and trigger.specificUnit or trigger.unit; + if(trigger.debuffType == "HELPFUL") then + GameTooltip:SetUnitBuff(unit, state.index); + elseif(trigger.debuffType == "HARMFUL") then + GameTooltip:SetUnitDebuff(unit, state.index); + end + return true + else + if (state.spellId) then + GameTooltip:SetSpellByID(state.spellId); + return true + end + end + return false +end + +--- Returns the name and icon to show in the options. +-- @param data +-- @param triggernum +-- @return name and icon +function BuffTrigger.GetNameAndIcon(data, triggernum) + local _, name, icon + local trigger = data.triggers[triggernum].trigger + if (trigger.fullscan) then + if (trigger.spellId) then + name, _, icon = GetSpellInfo(trigger.spellId); + else + name = trigger.name; + icon = WeakAuras.spellCache.GetIcon(trigger.name); + end + else + if (trigger.spellIds and trigger.spellIds[1]) then + name, _, icon = GetSpellInfo(trigger.spellIds[1]) + elseif(not (trigger.buffShowOn == "showOnMissing" or CanGroupShowWithZero(data, triggernum)) and trigger.names) then + -- Try to get an icon from the icon cache + for index, checkname in pairs(trigger.names) do + local iconFromSpellCache = WeakAuras.spellCache.GetIcon(checkname); + if(iconFromSpellCache) then + name, icon = checkname, iconFromSpellCache; + break; + end + end + end + end + + return name, icon; +end + +--- Returns the tooltip text for additional properties. +-- @param data +-- @param triggernum +-- @return string of additional properties +function BuffTrigger.GetAdditionalProperties(data, triggernum) + local ret = "|cFFFF0000%".. triggernum .. ".spellId|r -" .. L["Spell ID"] .. "\n"; + ret = ret .. "|cFFFF0000%".. triggernum .. ".unitCaster|r -" .. L["Caster"] .. "\n"; + + return ret; +end + +function BuffTrigger.GetTriggerConditions(data, triggernum) + local result = {}; + local trigger = data.triggers[triggernum].trigger + + result["unitCaster"] = { + display = L["Caster"], + type = "string", + } + + result["expirationTime"] = { + display = L["Remaining Duration"], + type = "timer", + } + result["duration"] = { + display = L["Total Duration"], + type = "number", + } + + result["stacks"] = { + display = L["Stacks"], + type = "number" + } + + result["name"] = { + display = L["Name"], + type = "string" + } + + if (trigger.type == "aura" and not(trigger.unit ~= "group" and trigger.fullscan and trigger.autoclone) and trigger.unit ~= "multi" and not(trigger.unit == "group" and not trigger.groupclone)) then + if (trigger.buffShowOn == "showAlways") then + result["buffed"] = { + display = L["Buffed/Debuffed"], + type = "bool", + test = function(state, needle) + return state and state.show and ((state.active and true or false) == (needle == 1)); + end + } + end + end + + return result; +end + +function BuffTrigger.CreateFallbackState(data, triggernum, state) + state.show = true; + state.changed = true; + state.progressType = "timed"; + state.duration = 0; + state.expirationTime = math.huge; + local name, icon = GetNameAndIconFromTrigger(data, triggernum) + state.name = name + state.icon = icon +end + +function BuffTrigger.GetName(triggerType) + if (triggerType == "aura") then + return L["Legacy Aura"]; + end +end + +function BuffTrigger.GetTriggerDescription(data, triggernum, namestable) + local trigger = data.triggers[triggernum].trigger; + if(trigger.fullscan) then + tinsert(namestable, {L["Aura:"], L["Full Scan"]}); + else + for index, name in pairs(trigger.names) do + local left = " "; + if(index == 1) then + if(#trigger.names > 0) then + if(#trigger.names > 1) then + left = L["Auras:"]; + else + left = L["Aura:"]; + end + end + end + local icon = WeakAuras.spellCache.GetIcon(name) or "Interface\\Icons\\INV_Misc_QuestionMark"; + tinsert(namestable, {left, name, icon}); + end + end +end + +function BuffTrigger.CreateFakeStates(id, triggernum) + local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum); + local data = WeakAuras.GetData(id) + local state = {} + BuffTrigger.CreateFallbackState(data, triggernum, state) + state.expirationTime = GetTime() + 60 + state.duration = 65 + state.progressType = "timed" + allStates[""] = state + if BuffTrigger.CanHaveClones(data, triggernum) then + for i = 1, 2 do + local state = {} + BuffTrigger.CreateFallbackState(data, triggernum, state) + state.expirationTime = GetTime() + 60 + i * 20 + state.duration = 100 + state.progressType = "timed" + allStates[i] = state + end + end +end + +WeakAuras.RegisterTriggerSystem({"aura"}, BuffTrigger); diff --git a/WeakAuras/BuffTrigger2.lua b/WeakAuras/BuffTrigger2.lua new file mode 100644 index 0000000..9010008 --- /dev/null +++ b/WeakAuras/BuffTrigger2.lua @@ -0,0 +1,3121 @@ +--[[ BuffTrigger2.lua +This file contains the "aura2" trigger for buffs and debuffs. It is intended to replace +the buff trigger old BuffTrigger at some future point + +It registers the BuffTrigger table for the trigger type "aura2" and has the following API: + +Add(data) +Adds an aura, setting up internal data structures for all buff triggers. + +LoadDisplays(id) +Loads the aura ids, enabling all buff triggers in the aura. + +UnloadDisplays(id) +Unloads the aura ids, disabling all buff triggers in the aura. + +UnloadAll() +Unloads all auras, disabling all buff triggers. + +ScanAll() +Updates all triggers by checking all triggers. + +Delete(id) +Removes all data for aura id. + +Rename(oldid, newid) +Updates all data for aura oldid to use newid. + +Modernize(data) +Updates all buff triggers in data. + +##################################################### +# Helper functions mainly for the WeakAuras Options # +##################################################### + +CanHaveDuration(data, triggernum) +Returns whether the trigger can have a duration. + +GetOverlayInfo(data, triggernum) +Returns a table containing all overlays. Currently there aren't any + +CanHaveAuto(data, triggernum) +Returns whether the icon can be automatically selected. + +CanHaveClones(data, triggernum) +Returns whether the trigger can have clones. + +CanHaveTooltip(data, triggernum) +Returns the type of tooltip to show for the trigger. + +GetNameAndIcon(data, triggernum) +Returns the name and icon to show in the options. + +GetAdditionalProperties(data, triggernum) +Returns the tooltip text for additional properties. + +GetTriggerConditions(data, triggernum) +Returns the potential conditions for a trigger +]]-- +if not WeakAuras.IsCorrectVersion() then return end + +-- Lua APIs +local tinsert, wipe = table.insert, wipe +local pairs, next, type = pairs, next, type +local UnitAura = UnitAura + +local WeakAuras = WeakAuras +local L = WeakAuras.L +local timer = WeakAuras.timer +local BuffTrigger = {} +local triggerInfos = {} + +local UnitGroupRolesAssigned = function() return "DAMAGER" end + +-- keyed on unit, debuffType, spellname, with a scan object value +-- scan object: id, triggernum, scanFunc +local scanFuncName = {} +local scanFuncSpellId = {} +local scanFuncGeneral = {} + +-- same as above but for group triggers +local scanFuncNameGroup = {} +local scanFuncSpellIdGroup = {} +local scanFuncGeneralGroup = {} + +-- Contains all scanFuncs that should be check if the exitance of a unit changed +local unitExistScanFunc = {} +-- Which units exist +local existingUnits = {} + +-- Loaded ScanFuncs per unit type +local groupScanFuncs = {} +--Active ScanFuncs per actual unit id +local activeGroupScanFuncs = {} + + +-- Mutli Target tracking +local scanFuncNameMulti = {} +local scanFuncSpellIdMulti = {} +local cleanupTimerMulti = {} + +-- Auras that matched, unit, index +local matchData = {} + +local matchDataUpToDate = {} + +local matchDataMulti = {} +-- Auras that matched, keyed on id, triggernum, kept in sync with matchData +local matchDataByTrigger = {} + +local matchDataChanged = {} + +local function UnitInSubgroupOrPlayer(unit) + return UnitInSubgroup(unit) or UnitIsUnit("player", unit) +end + +local function GetOrCreateSubTable(base, next, ...) + if not next then + return base + end + + base[next] = base[next] or {} + return GetOrCreateSubTable(base[next], ...) +end + +local function GetSubTable(base, next, ...) + if not base then + return nil + end + + if not next then + return base + end + + return GetSubTable(base[next], ...) +end + +local function IsGroupTrigger(trigger) + return trigger.unit == "group" or trigger.unit == "party" or trigger.unit == "raid" + or trigger.unit == "boss" or trigger.unit == "arena" or trigger.unit == "multi" +end + +local function IsSingleMissing(trigger) + return not IsGroupTrigger(trigger) and trigger.matchesShowOn == "showOnMissing" +end + +local function HasMatchCount(trigger) + if IsGroupTrigger(trigger) then + return trigger.useMatch_count + else + return trigger.matchesShowOn == "showOnMatches" + end +end + +local function ReferenceMatchData(id, triggernum, unit, filter, index) + local match = matchData[unit][filter][index] + local base = GetOrCreateSubTable(matchDataByTrigger, id, triggernum, unit) + + base[index] = match + + match.auras[id] = match.auras[id] or {} + match.auras[id][triggernum] = true +end + +local function ScanMatchData(time, triggerInfo, unit, filter) + if matchData[unit] and matchData[unit][filter] then + for index, match in pairs(matchData[unit][filter]) do + if (not triggerInfo.auranames and not triggerInfo.auraspellids) + or (triggerInfo.auranames and tContains(triggerInfo.auranames, match.name)) + or (triggerInfo.auraspellids and tContains(triggerInfo.auraspellids, match.spellId)) then + if triggerInfo.fetchTooltip then + matchData[unit][filter][index]:UpdateTooltip(time) + end + if not triggerInfo.scanFunc or triggerInfo.scanFunc(time, matchData[unit][filter][index]) then + local id = triggerInfo.id + local triggernum = triggerInfo.triggernum + ReferenceMatchData(id, triggernum, unit, filter, index) + matchDataChanged[id] = matchDataChanged[id] or {} + matchDataChanged[id][triggernum] = true + end + end + end + end +end + +local function ReferenceMatchDataMulti(matchData, id, triggernum, destGUID) + local needToInsert = false + + matchData.auras[id] = matchData.auras[id] or {} + needToInsert = not matchData.auras[id][triggernum] + matchData.auras[id][triggernum] = true + + if needToInsert then + local matchDataByTriggerBase = GetOrCreateSubTable(matchDataByTrigger, id, triggernum, destGUID) + tinsert(matchDataByTriggerBase, matchData) + end + + matchDataChanged[id] = matchDataChanged[id] or {} + matchDataChanged[id][triggernum] = true +end + +local function MatchesTriggerInfoMulti(triggerInfo, sourceGUID) + if triggerInfo.ownOnly then + return sourceGUID == UnitGUID("player") or sourceGUID == UnitGUID("pet") + elseif triggerInfo.ownOnly == false then + return sourceGUID ~= UnitGUID("player") and sourceGUID ~= UnitGUID("pet") + else + return true + end +end + +local function UpdateToolTipDataInMatchData(matchData, time) + if matchData.tooltipUpdated == time then + return + end + local changed = false + + if matchData.unit and matchData.index and matchData.filter then + local tooltip, _, tooltip1, tooltip2, tooltip3 = WeakAuras.GetAuraTooltipInfo(matchData.unit, matchData.index, matchData.filter) + + changed = matchData.tooltip ~= tooltip or matchData.tooltip1 ~= tooltip1 + or matchData.tooltip2 ~= tooltip2 or matchData.tooltip3 ~= tooltip3 + matchData.tooltip, matchData.tooltip1, matchData.tooltip2, matchData.tooltip3 = tooltip, tooltip1, tooltip2, tooltip3 + end + + matchData.tooltipUpdated = time + return changed +end + +local function UpdateMatchData(time, matchDataChanged, unit, index, filter, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId) + if not matchData[unit] then + matchData[unit] = {} + end + if not matchData[unit][filter] then + matchData[unit][filter] = {} + end + local debuffClassIcon = WeakAuras.EJIcons[debuffClass] + if not matchData[unit][filter][index] then + matchData[unit][filter][index] = { + name = name, + icon = icon, + stacks = stacks, + debuffClass = debuffClass, + debuffClassIcon = debuffClassIcon, + duration = duration, + expirationTime = expirationTime, + unitCaster = unitCaster, + casterName = unitCaster and GetUnitName(unitCaster, false) or "", + spellId = spellId, + unit = unit, + unitName = GetUnitName(unit, false) or "", + isStealable = isStealable, + time = time, + lastChanged = time, + filter = filter, + index = index, + UpdateTooltip = UpdateToolTipDataInMatchData, + auras = {} + } + return true + end + + local data = matchData[unit][filter][index] + local changed = false + + if data.name ~= name then + data.name = name + changed = true + end + + if data.icon ~= icon then + data.icon = icon + changed = true + end + + if data.stacks ~= stacks then + data.stacks = stacks + changed = true + end + + if data.debuffClass ~= debuffClass then + data.debuffClass = debuffClass + changed = true + end + + if data.debuffClassIcon ~= debuffClassIcon then + data.debuffClassIcon = debuffClassIcon + changed = true + end + + if data.duration ~= duration then + data.duration = duration + changed = true + end + + if data.expirationTime ~= expirationTime then + data.expirationTime = expirationTime + changed = true + end + + if data.unitCaster ~= unitCaster then + data.unitCaster = unitCaster + changed = true + end + + local casterName = unitCaster and GetUnitName(unitCaster, false) or "" + if data.casterName ~= casterName then + data.casterName = casterName + changed = true + end + + if data.spellId ~= spellId then + data.spellId = spellId + changed = true + end + + if data.isStealable ~= isStealable then + data.isStealable = isStealable + changed = true + end + + local unitName = GetUnitName(unit, false) or "" + if data.unitName ~= unitName then + data.unitName = unitName + changed = true + end + + if data.tooltipUpdated and data.tooltipUpdated < time then + changed = data:UpdateTooltip(time) or changed + end + + if changed then + data.lastChanged = time + end + + if changed then + -- Tell old auras that used this match data + for id, triggerData in pairs(data.auras) do + for triggernum in pairs(triggerData) do + if matchDataByTrigger[id] and matchDataByTrigger[id][triggernum] and matchDataByTrigger[id][triggernum][unit] and matchDataByTrigger[id][triggernum][unit][index] then + matchDataByTrigger[id][triggernum][unit][index] = nil + matchDataChanged[id] = matchDataChanged[id] or {} + matchDataChanged[id][triggernum] = true + end + end + end + wipe(data.auras) + end + + data.index = index + data.time = time + data.unit = unit + + return changed or data.lastChanged == time +end + +local function calculateNextCheck(triggerInfoRemaing, auraDataRemaing, auraDataExpirationTime, nextCheck) + if auraDataRemaing > 0 and auraDataRemaing >= triggerInfoRemaing then + if not nextCheck then + return auraDataExpirationTime - triggerInfoRemaing + else + return min(auraDataExpirationTime - triggerInfoRemaing, nextCheck) + end + end + return nextCheck +end + +local function FindBestMatchData(time, id, triggernum, triggerInfo, matchedUnits) + -- Find best match + local bestMatch = nil + local matchCount = 0 + local unitCount = 0 + local stackCount = 0 + local nextCheck + + if not matchDataByTrigger[id] or not matchDataByTrigger[id][triggernum] then + return nil, 0, 0 + end + + for unit, unitData in pairs(matchDataByTrigger[id][triggernum]) do + local unitCounted = false + for index, auraData in pairs(unitData) do + local remCheck = true + if triggerInfo.remainingFunc and auraData.expirationTime then + local remaining = auraData.expirationTime - time + remCheck = triggerInfo.remainingFunc(remaining) + nextCheck = calculateNextCheck(triggerInfo.remainingCheck, remaining, auraData.expirationTime, nextCheck) + end + + if remCheck then + matchCount = matchCount + 1 + stackCount = stackCount + (auraData.stacks or 0) + matchedUnits[unit] = true + if not unitCounted then + unitCount = unitCount + 1 + unitCounted = true + end + if not bestMatch or triggerInfo.compareFunc(bestMatch, auraData) then + bestMatch = auraData + end + end + end + end + return bestMatch, matchCount, unitCount, stackCount, nextCheck +end + +local function FindBestMatchDataForUnit(time, id, triggernum, triggerInfo, unit) + -- Find best match + local bestMatch = nil + local matchCount = 0 + local stackCount = 0 + local nextCheck + + if not matchDataByTrigger[id] or not matchDataByTrigger[id][triggernum] or not matchDataByTrigger[id][triggernum][unit] then + return nil, 0 + end + + for index, auraData in pairs(matchDataByTrigger[id][triggernum][unit]) do + local remCheck = true + if triggerInfo.remainingFunc and auraData.expirationTime then + local remaining = auraData.expirationTime - time + remCheck = triggerInfo.remainingFunc(remaining) + nextCheck = calculateNextCheck(triggerInfo.remainingCheck, remaining, auraData.expirationTime, nextCheck) + end + + if remCheck then + matchCount = matchCount + 1 + stackCount = stackCount + auraData.stacks + if not bestMatch or triggerInfo.compareFunc(bestMatch, auraData) then + bestMatch = auraData + end + end + end + return bestMatch, matchCount, nextCheck +end + +local function UpdateStateWithMatch(time, bestMatch, triggerStates, cloneId, matchCount, unitCount, maxUnitCount, matchCountPerUnit, totalStacks, affected, unaffected) + local debuffClassIcon = WeakAuras.EJIcons[bestMatch.debuffClass] + if not triggerStates[cloneId] then + triggerStates[cloneId] = { + show = true, + changed = true, + name = bestMatch.name, + icon = bestMatch.icon, + stacks = bestMatch.stacks, + debuffClass = bestMatch.debuffClass, + debuffClassIcon = debuffClassIcon, + progressType = "timed", + duration = bestMatch.duration, + expirationTime = bestMatch.expirationTime, + unitCaster = bestMatch.unitCaster, + casterName = bestMatch.casterName, + spellId = bestMatch.spellId, + index = bestMatch.index, + unit = bestMatch.unit, + unitName = bestMatch.unitName, + GUID = bestMatch.unit and UnitGUID(bestMatch.unit) or bestMatch.GUID, + matchCount = matchCount, + unitCount = unitCount, + maxUnitCount = maxUnitCount, + matchCountPerUnit = matchCountPerUnit, + tooltip = bestMatch.tooltip, + tooltip1 = bestMatch.tooltip1, + tooltip2 = bestMatch.tooltip2, + tooltip3 = bestMatch.tooltip3, + affected = affected, + unaffected = unaffected, + totalStacks = totalStacks, + active = true, + time = time, + } + return true + else + local state = triggerStates[cloneId] + local changed = false + state.time = time + + if state.unit ~= bestMatch.unit then + state.unit = bestMatch.unit + changed = true + end + + local GUID = bestMatch.unit and UnitGUID(bestMatch.unit) or bestMatch.GUID + if state.GUID ~= GUID then + state.GUID = GUID + changed = true + end + + if state.unitName ~= bestMatch.unitName then + state.unitName = bestMatch.unitName + changed = true + end + + if state.show ~= true then + state.show = true + changed = true + end + + if state.name ~= bestMatch.name then + state.name = bestMatch.name + changed = true + end + + if state.icon ~= bestMatch.icon then + state.icon = bestMatch.icon + changed = true + end + + if state.stacks ~= bestMatch.stacks then + state.stacks = bestMatch.stacks + changed = true + end + + if state.debuffClass ~= bestMatch.debuffClass then + state.debuffClass = bestMatch.debuffClass + changed = true + end + + if state.debuffClassIcon ~= debuffClassIcon then + state.debuffClassIcon = debuffClassIcon + changed = true + end + + if state.duration ~= bestMatch.duration then + state.duration = bestMatch.duration + changed = true + end + + if state.expirationTime ~= bestMatch.expirationTime then + state.expirationTime = bestMatch.expirationTime + changed = true + end + + if state.progressType ~= "timed" then + state.progressType = "timed" + changed = true + end + + if state.unitCaster ~= bestMatch.unitCaster then + state.unitCaster = bestMatch.unitCaster + state.casterName = bestMatch.casterName + changed = true + end + + if state.spellId ~= bestMatch.spellId then + state.spellId = bestMatch.spellId + changed = true + end + + if state.index ~= bestMatch.index then + state.index = bestMatch.index + changed = true + end + + if state.tooltip ~= bestMatch.tooltip then + state.tooltip = bestMatch.tooltip + changed = true + end + + if state.tooltip1 ~= bestMatch.tooltip1 then + state.tooltip1 = bestMatch.tooltip1 + changed = true + end + + if state.tooltip2 ~= bestMatch.tooltip2 then + state.tooltip2 = bestMatch.tooltip2 + changed = true + end + + if state.tooltip3 ~= bestMatch.tooltip3 then + state.tooltip3 = bestMatch.tooltip3 + changed = true + end + + if state.matchCount ~= matchCount then + state.matchCount = matchCount + changed = true + end + + if state.unitCount ~= unitCount then + state.unitCount = unitCount + changed = true + end + + if state.maxUnitCount ~= maxUnitCount then + state.maxUnitCount = maxUnitCount + changed = true + end + + if state.matchCountPerUnit ~= matchCountPerUnit then + state.matchCountPerUnit = matchCountPerUnit + changed = true + end + + if state.affected ~= affected then + state.affected = affected + changed = true + end + + if state.unaffected ~= unaffected then + state.unaffected = unaffected + changed = true + end + + if state.active ~= true then + state.active = true + changed = true + end + + if state.totalStacks ~= totalStacks then + state.totalStacks = totalStacks + changed = true + end + + if changed then + state.changed = true + return true + end + end +end + +local function UpdateStateWithNoMatch(time, triggerStates, triggerInfo, cloneId, unit, matchCount, unitCount, maxUnitCount, matchCountPerUnit, totalStacks, affected, unaffected) + local fallbackName, fallbackIcon = BuffTrigger.GetNameAndIconSimple(WeakAuras.GetData(triggerInfo.id), triggerInfo.triggernum) + if not triggerStates[cloneId] then + triggerStates[cloneId] = { + show = true, + changed = true, + progressType = 'timed', + duration = 0, + expirationTime = math.huge, + matchCount = matchCount, + unitCount = unitCount, + maxUnitCount = maxUnitCount, + matchCountPerUnit = matchCountPerUnit, + active = false, + time = time, + affected = affected, + unaffected = unaffected, + unit = unit, + unitName = unit and GetUnitName(unit, false) or "", + destName = "", + name = fallbackName, + icon = fallbackIcon, + totalStacks = totalStacks + } + return true + else + local state = triggerStates[cloneId] + state.time = time + local changed = false + + if state.show ~= true then + state.show = true + changed = true + end + + if state.name ~= fallbackName then + state.name = fallbackName + changed = true + end + + if state.icon ~= fallbackIcon then + state.icon = fallbackIcon + changed = true + end + + if state.stacks then + state.stacks = nil + changed = true + end + + if state.duration then + state.duration = nil + changed = true + end + + if state.expirationTime ~= math.huge then + state.expirationTime = math.huge + changed = true + end + + if state.progressType then + state.progressType = nil + changed = true + end + + if state.unit ~= unit then + state.unit = unit + changed = true + end + + local unitName = unit and GetUnitName(unit, false) or "" + if state.unitName ~= unitName then + state.unitName = unitName + changed = true + end + + if state.unitCaster then + state.unitCaster = nil + changed = true + end + + if state.casterName ~= "" then + state.casterName = "" + changed = true + end + + if state.spellId then + state.spellId = nil + changed = true + end + + if state.index then + state.index = nil + changed = true + end + + if state.tooltip or state.tooltip1 or state.tooltip2 or state.tooltip3 then + state.tooltip, state.tooltip1, state.tooltip2, state.tooltip3 = nil, nil, nil, nil + changed = true + end + + if state.matchCount ~= matchCount then + state.matchCount = matchCount + changed = true + end + + if state.unitCount ~= unitCount then + state.unitCount = unitCount + changed = true + end + + if state.maxUnitCount ~= maxUnitCount then + state.maxUnitCount = maxUnitCount + changed = true + end + + if state.matchCountPerUnit ~= matchCountPerUnit then + state.matchCountPerUnit = matchCountPerUnit + changed = true + end + + if state.active then + state.active = false + changed = true + end + + if state.affected ~= affected then + state.affected = affected + changed = true + end + + if state.unaffected ~= unaffected then + state.unaffected = unaffected + changed = true + end + + if state.totalStacks ~= totalStacks then + state.totalStacks = totalStacks + changed = true + end + + if changed then + state.changed = true + return true + end + end +end + +local function RemoveState(triggerStates, cloneId) + local state = triggerStates[cloneId] + if state then + if state.show then + state.show = false + state.changed = true + return true + end + end +end + +local function GetAllUnits(unit, allUnits) + if unit == "group" then + if allUnits then + local i = 1 + local raid = true + return function() + if raid then + if i <= 40 then + local ret = WeakAuras.raidUnits[i] + i = i + 1 + return ret + end + raid = false + i = 1 + return "player" + end + + if i <= 4 then + local ret = WeakAuras.partyUnits[i] + i = i + 1 + return ret + end + + i = 1 + raid = true + end + end + + -- allunits == false + if IsInRaid() then + local i = 1 + local max = GetNumGroupMembers() + return function() + if i <= max then + local ret = WeakAuras.raidUnits[i] + i = i + 1 + return ret + end + i = 1 + end + else + local i = 0 + local max = GetNumSubgroupMembers() + return function() + if i == 0 then + i = 1 + return "player" + else + if i <= max then + local ret = WeakAuras.partyUnits[i] + i = i + 1 + return ret + end + end + i = 0 + end + end + elseif unit == "boss" or unit == "arena" then + local i = 1 + local max + if unit == "boss" then + max = 4 + elseif unit == "arena" then + max = 5 + else + return function() end + end + return function() + local ret = unit .. i + while not allUnits and not UnitExistsFixed(ret) do + i = i + 1 + if i > max then + i = 1 + return nil + end + ret = unit .. i + end + i = i + 1 + if i > max then + i = 1 + return nil + end + return ret + end + else + local toggle = false + return function() + toggle = not toggle + if toggle then + return unit + end + end + end +end + +local function MaxUnitCount(triggerInfo) + if triggerInfo.groupTrigger then + return triggerInfo.maxUnitCount + else + return UnitExists(triggerInfo.unit) and 1 or 0 + end +end + +local function TriggerInfoApplies(triggerInfo, unit) + if triggerInfo.ignoreSelf and UnitIsUnit("player", unit) then + return false + end + + if triggerInfo.groupRole and triggerInfo.groupRole ~= UnitGroupRolesAssigned(unit) then + return false + end + + if triggerInfo.unit == "group" and triggerInfo.groupSubType == "party" then + if IsInRaid() then + -- Filter our player/party# while in raid and keep only raid units that are correct + if not WeakAuras.multiUnitUnits.raid[unit] or not UnitInSubgroupOrPlayer(unit) then + return false + end + else + if not UnitInSubgroupOrPlayer(unit) then + return false + end + end + end + + -- Filter our player/party# while in raid + if (triggerInfo.unit == "group" and triggerInfo.groupSubType == "group" and IsInRaid() and not WeakAuras.multiUnitUnits.raid[unit]) then + return false + end + + if triggerInfo.unit == "group" and triggerInfo.groupSubType == "raid" and not WeakAuras.multiUnitUnits.raid[unit] then + return false + end + + if triggerInfo.class and not triggerInfo.class[select(2, UnitClass(unit))] then + return false + end + return true +end + +local function FormatAffectedUnaffected(triggerInfo, matchedUnits) + local affected = "" + local unaffected = "" + for unit in GetAllUnits(triggerInfo.unit) do + if activeGroupScanFuncs[unit] and activeGroupScanFuncs[unit][triggerInfo] then + if matchedUnits[unit] then + affected = affected .. (GetUnitName(unit, false) or unit) .. ", " + else + unaffected = unaffected .. (GetUnitName(unit, false) or unit) .. ", " + end + end + end + unaffected = unaffected == "" and L["None"] or unaffected:sub(1, -3) + affected = affected == "" and L["None"] or affected:sub(1, -3) + + return affected, unaffected +end + +local recheckTriggerInfo + +local function SatisfiesGroupMatchCount(triggerInfo, unitCount, maxUnitCount, matchCount) + if triggerInfo.groupCountFunc and not triggerInfo.groupCountFunc(unitCount, maxUnitCount) then + return false + end + + if triggerInfo.matchCountFunc and not triggerInfo.matchCountFunc(matchCount) then + return false + end + return true +end + +local function SortMatchDataByUnitIndex(a, b) + if a.unit and b.unit and a.unit ~= b.unit then + return a.unit < b.unit + end + if a.index and b.index and a.index ~= b.index then + return a.index < b.index + end + return a.expirationTime < b.expirationTime +end + +local function UpdateTriggerState(time, id, triggernum) + local triggerStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum) + local triggerInfo = triggerInfos[id][triggernum] + local updated + local nextCheck + local matchCount = 0 + local totalStacks = 0 + local unitCount = 0 + local auraDatas = {} + local maxUnitCount = MaxUnitCount(triggerInfo) + local matchedUnits = {} + local matchCountPerUnit = {} + + if triggerInfo.combineMode == "showOne" then + local bestMatch + bestMatch, matchCount, unitCount, totalStacks, nextCheck = FindBestMatchData(time, id, triggernum, triggerInfo, matchedUnits) + local cloneId = "" + + local useMatch = true + if triggerInfo.unitExists ~= nil and not existingUnits[triggerInfo.unit] then + useMatch = triggerInfo.unitExists + else + useMatch = SatisfiesGroupMatchCount(triggerInfo, unitCount, maxUnitCount, matchCount) + end + + if useMatch then + local affected, unaffected + if triggerInfo.useAffected then + affected, unaffected = FormatAffectedUnaffected(triggerInfo, matchedUnits) + end + + if bestMatch then + updated = UpdateStateWithMatch(time, bestMatch, triggerStates, cloneId, matchCount, unitCount, maxUnitCount, matchCount, totalStacks, affected, unaffected) + else + updated = UpdateStateWithNoMatch(time, triggerStates, triggerInfo, cloneId, not triggerInfo.groupTrigger and triggerInfo.unit or nil, 0, 0, maxUnitCount, 0, 0, affected, unaffected) + end + else + updated = RemoveState(triggerStates, cloneId) + end + elseif triggerInfo.combineMode == "showClones" then + if matchDataByTrigger[id] and matchDataByTrigger[id][triggernum] then + for unit, unitData in pairs(matchDataByTrigger[id][triggernum]) do + local unitCounted = false + for index, auraData in pairs(unitData) do + local remCheck = true + if triggerInfo.remainingFunc and auraData.expirationTime then + local remaining = auraData.expirationTime - time + remCheck = triggerInfo.remainingFunc(remaining) + nextCheck = calculateNextCheck(triggerInfo.remainingCheck, remaining, auraData.expirationTime, nextCheck) + end + + if remCheck then + tinsert(auraDatas, auraData) + matchCount = matchCount + 1 + totalStacks = totalStacks + (auraData.stacks or 0) + matchedUnits[unit] = true + matchCountPerUnit[unit] = (matchCountPerUnit[unit] or 0) + 1 + if not unitCounted then + unitCount = unitCount + 1 + unitCounted = true + end + end + end + end + end + + local useMatches = true + if triggerInfo.unitExists ~= nil and not existingUnits[triggerInfo.unit] then + useMatches = triggerInfo.unitExists + else + useMatches = SatisfiesGroupMatchCount(triggerInfo, unitCount, maxUnitCount, matchCount) + end + + if useMatches then + + table.sort(auraDatas, SortMatchDataByUnitIndex) + + local affected, unaffected + if triggerInfo.useAffected then + affected, unaffected = FormatAffectedUnaffected(triggerInfo, matchedUnits) + end + + local usedCloneIds = {}; + + for index, auraData in ipairs(auraDatas) do + local cloneId = (auraData.GUID or auraData.unit or "unknown") .. " " .. auraData.spellId + if usedCloneIds[cloneId] then + usedCloneIds[cloneId] = usedCloneIds[cloneId] + 1 + cloneId = cloneId .. usedCloneIds[cloneId] + else + usedCloneIds[cloneId] = 1 + end + + updated = UpdateStateWithMatch(time, auraData, triggerStates, cloneId, matchCount, unitCount, maxUnitCount, matchCountPerUnit[auraData.unit], totalStacks, affected, unaffected) or updated + end + + if matchCount == 0 then + updated = UpdateStateWithNoMatch(time, triggerStates, triggerInfo, "", nil, 0, 0, maxUnitCount, 0, totalStacks, affected, unaffected) or updated + end + end + + for cloneId, state in pairs(triggerStates) do + if state.show and state.time < time then + updated = RemoveState(triggerStates, cloneId) or updated + end + end + elseif triggerInfo.combineMode == "showPerUnit" then + local matches = {} + if matchDataByTrigger[id] and matchDataByTrigger[id][triggernum] then + for unit, unitData in pairs(matchDataByTrigger[id][triggernum]) do + local bestMatch, countPerUnit, stacks, nextCheckForMatch = FindBestMatchDataForUnit(time, id, triggernum, triggerInfo, unit) + matchCount = matchCount + countPerUnit + if bestMatch then + totalStacks = totalStacks + (bestMatch.stacks or 0) + unitCount = unitCount + 1 + matchedUnits[unit] = true + end + + if not nextCheck then + nextCheck = nextCheckForMatch + elseif nextCheckForMatch then + nextCheck = min(nextCheck, nextCheckForMatch) + end + matches[unit] = bestMatch + matchCountPerUnit[unit] = countPerUnit + end + end + + local useMatches = SatisfiesGroupMatchCount(triggerInfo, unitCount, maxUnitCount, matchCount) + + if useMatches then + local affected, unaffected + if triggerInfo.useAffected then + affected, unaffected = FormatAffectedUnaffected(triggerInfo, matchedUnits) + end + + if triggerInfo.perUnitMode == "affected" then + for unit, bestMatch in pairs(matches) do + if bestMatch then + updated = UpdateStateWithMatch(time, bestMatch, triggerStates, unit, matchCount, unitCount, maxUnitCount, matchCountPerUnit[unit], totalStacks, affected, unaffected) or updated + end + end + else + -- state per unaffected unit + for unit in GetAllUnits(triggerInfo.unit) do + if activeGroupScanFuncs[unit] and activeGroupScanFuncs[unit][triggerInfo] then + local bestMatch = matches[unit] + if bestMatch then + if triggerInfo.perUnitMode == "all" then + updated = UpdateStateWithMatch(time, bestMatch, triggerStates, unit, matchCount, unitCount, maxUnitCount, matchCountPerUnit[unit], totalStacks, affected, unaffected) or updated + end + else + updated = UpdateStateWithNoMatch(time, triggerStates, triggerInfo, unit, unit, matchCount, unitCount, maxUnitCount, matchCountPerUnit[unit], totalStacks, affected, unaffected) or updated + end + end + end + end + end + + for cloneId, state in pairs(triggerStates) do + if state.show and state.time < time then + updated = RemoveState(triggerStates, cloneId) or updated + end + end + end + + if nextCheck then + if triggerInfo.nextScheduledCheck ~= nextCheck then + if triggerInfo.nextScheduledCheckHandle then + timer:CancelTimer(triggerInfo.nextScheduledCheckHandle) + end + triggerInfo.nextScheduledCheckHandle = timer:ScheduleTimerFixed(recheckTriggerInfo, nextCheck - time, triggerInfo) + triggerInfo.nextScheduledCheck = nextCheck + end + elseif triggerInfo.nextScheduledCheckHandle then + timer:CancelTimer(triggerInfo.nextScheduledCheckHandle) + triggerInfo.nextScheduledCheckHandle = nil + triggerInfo.nextScheduledCheck = nil + end + + return updated +end + +recheckTriggerInfo = function(triggerInfo) + matchDataChanged[triggerInfo.id] = matchDataChanged[triggerInfo.id] or {} + matchDataChanged[triggerInfo.id][triggerInfo.triggernum] = true + triggerInfo.nextScheduledCheckHandle = nil + triggerInfo.nextScheduledCheck = nil +end + +local function PrepareMatchData(unit, filter) + if not matchDataUpToDate[unit] or not matchDataUpToDate[unit][filter] then + local time = GetTime() + local index = 1 + while true do + local name, rank, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId = UnitAura(unit, index, filter) + if not name then + break + end + + if debuffClass == nil then + debuffClass = "none" + elseif debuffClass == "" then + debuffClass = "enrage" + else + debuffClass = string.lower(debuffClass) + end + + local updatedMatchData = UpdateMatchData(time, matchDataChanged, unit, index, filter, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId) + index = index + 1 + end + + matchDataUpToDate[unit] = matchDataUpToDate[unit] or {} + matchDataUpToDate[unit][filter] = true + end +end + +local function CleanUpOutdatedMatchData(time, unit, filter) + -- Figure out if any matchData is outdated + if matchData[unit] and matchData[unit][filter] then + for index, data in pairs(matchData[unit][filter]) do + if data.time < time or not UnitExists(unit) then + matchData[unit][filter][index] = nil + for id, triggerData in pairs(data.auras) do + for triggernum in pairs(triggerData) do + matchDataByTrigger[id][triggernum][unit][index] = nil + matchDataChanged[id] = matchDataChanged[id] or {} + matchDataChanged[id][triggernum] = true + end + end + end + end + end +end + +local function CleanUpMatchDataForUnit(unit, filter) + -- Figure out if any matchData is outdated + if matchData[unit] and matchData[unit][filter] then + for index, data in pairs(matchData[unit][filter]) do + matchData[unit][filter][index] = nil + for id, triggerData in pairs(data.auras) do + for triggernum in pairs(triggerData) do + matchDataByTrigger[id][triggernum][unit][index] = nil + matchDataChanged[id] = matchDataChanged[id] or {} + matchDataChanged[id][triggernum] = true + end + end + end + end +end + +local function DeactivateScanFuncs(toDeactivate) + for unit, triggerInfosPerUnit in pairs(toDeactivate) do + for triggerInfo in pairs(triggerInfosPerUnit) do + local id = triggerInfo.id + local triggernum = triggerInfo.triggernum + local matches = GetSubTable(matchDataByTrigger, id, triggernum, unit) + if (matches) then + for index, match in pairs(matches) do + match.auras[id][triggernum] = nil + end + matchDataByTrigger[id][triggernum][unit] = nil + matchDataChanged[id] = matchDataChanged[id] or {} + matchDataChanged[id][triggernum] = true + end + end + end +end + +local function CheckScanFuncs(scanFuncs, unit, filter, index) + if scanFuncs then + for triggerInfo in pairs(scanFuncs) do + if triggerInfo.fetchTooltip then + matchData[unit][filter][index]:UpdateTooltip(GetTime()) + end + if not triggerInfo.scanFunc or triggerInfo.scanFunc(time, matchData[unit][filter][index]) then + local id = triggerInfo.id + local triggernum = triggerInfo.triggernum + ReferenceMatchData(id, triggernum, unit, filter, index) + matchDataChanged[id] = matchDataChanged[id] or {} + matchDataChanged[id][triggernum] = true + end + end + end +end + +local function ScanUnitWithFilter(matchDataChanged, time, unit, filter, + scanFuncNameGroup, scanFuncSpellIdGroup, scanFuncGeneralGroup, + scanFuncName, scanFuncSpellId, scanFuncGeneral) + + if not scanFuncName and not scanFuncSpellId and not scanFuncGeneral and not scanFuncNameGroup and not scanFuncSpellIdGroup and not scanFuncGeneralGroup then + if matchDataUpToDate[unit] then + matchDataUpToDate[unit][filter] = nil + end + CleanUpOutdatedMatchData(time, unit, filter) + return + end + + local index = 1 + + if UnitExists(unit) then + while true do + local name, rank, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId = UnitAura(unit, index, filter) + if not name then + break + end + + if debuffClass == nil then + debuffClass = "none" + elseif debuffClass == "" then + debuffClass = "enrage" + else + debuffClass = string.lower(debuffClass) + end + + local updatedMatchData = UpdateMatchData(time, matchDataChanged, unit, index, filter, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId) + + if updatedMatchData then -- Aura data changed, check against triggerInfos + CheckScanFuncs(scanFuncName and scanFuncName[name], unit, filter, index) + CheckScanFuncs(scanFuncNameGroup and scanFuncNameGroup[name], unit, filter, index) + CheckScanFuncs(scanFuncSpellId and scanFuncSpellId[spellId], unit, filter, index) + CheckScanFuncs(scanFuncSpellIdGroup and scanFuncSpellIdGroup[spellId], unit, filter, index) + CheckScanFuncs(scanFuncGeneral, unit, filter, index) + CheckScanFuncs(scanFuncGeneralGroup, unit, filter, index) + end + index = index + 1 + end + end + + CleanUpOutdatedMatchData(time, unit, filter) + + matchDataUpToDate[unit] = matchDataUpToDate[unit] or {} + matchDataUpToDate[unit][filter] = true +end + +local function UpdateStates(matchDataChanged, time) + for id, auraData in pairs(matchDataChanged) do + WeakAuras.StartProfileAura(id) + local updated = false + for triggernum in pairs(auraData) do + updated = UpdateTriggerState(time, id, triggernum) or updated + end + if updated then + WeakAuras.UpdatedTriggerState(id) + end + WeakAuras.StopProfileAura(id) + end +end + +local function ScanGroupUnit(time, matchDataChanged, unitType, unit) + local unitExists = UnitExists(unit) + if existingUnits[unit] ~= unitExists then + existingUnits[unit] = unitExists + + if unitExistScanFunc[unit] then + for id, idData in pairs(unitExistScanFunc[unit]) do + matchDataChanged[id] = matchDataChanged[id] or {} + for _, triggerInfo in ipairs(idData) do + matchDataChanged[id][triggerInfo.triggernum] = true + end + end + end + end + + scanFuncName[unit] = scanFuncName[unit] or {} + scanFuncSpellId[unit] = scanFuncSpellId[unit] or {} + scanFuncGeneral[unit] = scanFuncGeneral[unit] or {} + + if unitType then + scanFuncNameGroup[unit] = scanFuncNameGroup[unit] or {} + scanFuncSpellIdGroup[unit] = scanFuncSpellIdGroup[unit] or {} + scanFuncGeneralGroup[unit] = scanFuncGeneralGroup[unit] or {} + + ScanUnitWithFilter(matchDataChanged, time, unit, "HELPFUL", + scanFuncNameGroup[unit]["HELPFUL"], + scanFuncSpellIdGroup[unit]["HELPFUL"], + scanFuncGeneralGroup[unit]["HELPFUL"], + scanFuncName[unit]["HELPFUL"], + scanFuncSpellId[unit]["HELPFUL"], + scanFuncGeneral[unit]["HELPFUL"]) + + ScanUnitWithFilter(matchDataChanged, time, unit, "HARMFUL", + scanFuncNameGroup[unit]["HARMFUL"], + scanFuncSpellIdGroup[unit]["HARMFUL"], + scanFuncGeneralGroup[unit]["HARMFUL"], + scanFuncName[unit]["HARMFUL"], + scanFuncSpellId[unit]["HARMFUL"], + scanFuncGeneral[unit]["HARMFUL"]) + else + ScanUnitWithFilter(matchDataChanged, time, unit, "HELPFUL", nil, nil, nil, + scanFuncName[unit]["HELPFUL"], + scanFuncSpellId[unit]["HELPFUL"], + scanFuncGeneral[unit]["HELPFUL"]) + + ScanUnitWithFilter(matchDataChanged, time, unit, "HARMFUL", nil, nil, nil, + scanFuncName[unit]["HARMFUL"], + scanFuncSpellId[unit]["HARMFUL"], + scanFuncGeneral[unit]["HARMFUL"]) + end +end + +local function ScanAllGroup(time, matchDataChanged) + -- We iterate over all raid/player unit ids here because ScanGroupUnit also + -- handles the cases where a unit existance changes. + for unit in GetAllUnits("group") do + ScanGroupUnit(time, matchDataChanged, "group", unit) + end +end + +local function ScanAllBoss(time, matchDataChanged) + for unit in GetAllUnits("boss") do + ScanGroupUnit(time, matchDataChanged, "boss", unit) + end +end + +local function ScanUnit(time, arg1) + if not arg1 then return end + if (WeakAuras.multiUnitUnits.raid[arg1] and IsInRaid()) then + ScanGroupUnit(time, matchDataChanged, "group", arg1) + elseif (WeakAuras.multiUnitUnits.party[arg1] and not IsInRaid()) then + ScanGroupUnit(time, matchDataChanged, "group", arg1) + elseif WeakAuras.multiUnitUnits.boss[arg1] then + ScanGroupUnit(time, matchDataChanged, "boss", arg1) + elseif WeakAuras.multiUnitUnits.arena[arg1] then + ScanGroupUnit(time, matchDataChanged, "arena", arg1) + else + ScanGroupUnit(time, matchDataChanged, nil, arg1) + end +end + +local function AddScanFuncs(triggerInfo, unit, scanFuncName, scanFuncSpellId, scanFuncGeneral) + local filter = triggerInfo.debuffType + if triggerInfo.auranames then + for _, name in ipairs(triggerInfo.auranames) do + if name ~= "" then + local base = unit and GetOrCreateSubTable(scanFuncName, unit, filter, name) or GetOrCreateSubTable(scanFuncName, filter, name) + base[triggerInfo] = true + end + end + end + + if triggerInfo.auraspellids then + for _, spellId in ipairs(triggerInfo.auraspellids) do + local base = unit and GetOrCreateSubTable(scanFuncSpellId, unit, filter, spellId) or GetOrCreateSubTable(scanFuncSpellId, filter, spellId) + base[triggerInfo] = true + end + end + + if not triggerInfo.auranames and not triggerInfo.auraspellids and scanFuncGeneral then + local base = GetOrCreateSubTable(scanFuncGeneral, unit, filter) + base[triggerInfo] = true + end + + if unit then + PrepareMatchData(unit, filter) + ScanMatchData(GetTime(), triggerInfo, unit, filter) + end +end + +local function RemoveScanFuncs(triggerInfo, unit, scanFuncName, scanFuncSpellId, scanFuncGeneral) + local filter = triggerInfo.debuffType + if triggerInfo.auranames then + for _, name in ipairs(triggerInfo.auranames) do + if name ~= "" then + local base = unit and GetSubTable(scanFuncName, unit, filter, name) or GetSubTable(scanFuncName, filter, name) + if base then + base[triggerInfo] = nil + end + end + end + end + + if triggerInfo.auraspellids then + for _, spellId in ipairs(triggerInfo.auraspellids) do + local base = unit and GetSubTable(scanFuncSpellId, unit, filter, spellId) or GetSubTable(scanFuncSpellId, filter, spellId) + if base then + base[triggerInfo] = nil + end + end + end + + if not triggerInfo.auranames and not triggerInfo.auraspellids and scanFuncGeneral then + local base = GetSubTable(scanFuncGeneral, unit, filter) + if base then + base[triggerInfo] = nil + end + end +end + +local function RecheckActive(triggerInfo, unit, unitsToRemoveScan) + local isSelf, role, inParty, class + local unitExists = UnitExists(unit) + if unitExists and TriggerInfoApplies(triggerInfo, unit) then + if (not activeGroupScanFuncs[unit] or not activeGroupScanFuncs[unit][triggerInfo]) then + triggerInfo.maxUnitCount = triggerInfo.maxUnitCount + 1 + AddScanFuncs(triggerInfo, unit, scanFuncNameGroup, scanFuncSpellIdGroup, scanFuncGeneralGroup) + activeGroupScanFuncs[unit] = activeGroupScanFuncs[unit] or {} + activeGroupScanFuncs[unit][triggerInfo] = true + matchDataChanged[triggerInfo.id] = matchDataChanged[triggerInfo.id] or {} + matchDataChanged[triggerInfo.id][triggerInfo.triggernum] = true + end + else + -- Either the unit doesn't exist or the TriggerInfo no longer applies + if activeGroupScanFuncs[unit] and activeGroupScanFuncs[unit][triggerInfo] then + triggerInfo.maxUnitCount = triggerInfo.maxUnitCount - 1 + RemoveScanFuncs(triggerInfo, unit, scanFuncNameGroup, scanFuncSpellIdGroup, scanFuncGeneralGroup) + if unitsToRemoveScan then + unitsToRemoveScan[unit] = unitsToRemoveScan[unit] or {} + unitsToRemoveScan[unit][triggerInfo] = true + end + activeGroupScanFuncs[unit][triggerInfo] = nil + matchDataChanged[triggerInfo.id] = matchDataChanged[triggerInfo.id] or {} + matchDataChanged[triggerInfo.id][triggerInfo.triggernum] = true + end + end +end + +local function RecheckActiveForUnitType(unitType, unit, unitsToRemoveScan) + if groupScanFuncs[unitType] then + for i, triggerInfo in ipairs(groupScanFuncs[unitType]) do + RecheckActive(triggerInfo, unit, unitsToRemoveScan) + end + end +end + +local frame = CreateFrame("FRAME") +WeakAuras.frames["WeakAuras Buff2 Frame"] = frame + +local function EventHandler(frame, event, arg1, arg2, ...) + + WeakAuras.StartProfileSystem("bufftrigger2") + + local deactivatedTriggerInfos = {} + local unitsToRemove = {} + + local time = GetTime() + if event == "PLAYER_TARGET_CHANGED" then + ScanGroupUnit(time, matchDataChanged, nil, "target") + if not UnitExists("target") then + tinsert(unitsToRemove, "target") + end + elseif event == "PLAYER_FOCUS_CHANGED" then + ScanGroupUnit(time, matchDataChanged, nil, "focus") + if not UnitExists("focus") then + tinsert(unitsToRemove, "focus") + end + elseif event == "UNIT_PET" and arg1 == "player" then + ScanGroupUnit(time, matchDataChanged, nil, "pet") + if not UnitExists("pet") then + tinsert(unitsToRemove, "pet") + end + elseif event =="ARENA_OPPONENT_UPDATE" then + local unitsToCheck = {} + for unit in GetAllUnits("arena", true) do + RecheckActiveForUnitType("arena", unit, deactivatedTriggerInfos) + if not UnitExists(unit) then + tinsert(unitsToRemove, unit) + end + end + elseif event == "PARTY_MEMBERS_CHANGED" or event == "RAID_ROSTER_UPDATE" then + local unitsToCheck = {} + for unit in GetAllUnits("group", true) do + RecheckActiveForUnitType("group", unit, deactivatedTriggerInfos) + if not UnitExists(unit) then + tinsert(unitsToRemove, unit) + end + end + elseif event == "UNIT_ENTERED_VEHICLE" or event == "UNIT_EXITED_VEHICLE" then + if arg1 == "player" then + ScanGroupUnit(time, matchDataChanged, nil, "vehicle") + end + elseif event == "UNIT_AURA" then + ScanUnit(time, arg1) + elseif event == "PLAYER_ENTERING_WORLD" then + for unit in pairs(matchData) do + ScanUnit(time, unit) + if not UnitExists(unit) then + tinsert(unitsToRemove, unit) + end + end + end + + DeactivateScanFuncs(deactivatedTriggerInfos) + + for i, unit in ipairs(unitsToRemove) do + CleanUpMatchDataForUnit(unit, "HELPFUL") + CleanUpMatchDataForUnit(unit, "HARMFUL") + matchDataUpToDate[unit] = nil + end + + WeakAuras.StopProfileSystem("bufftrigger2") +end + +frame:RegisterEvent("UNIT_AURA") +frame:RegisterEvent("UNIT_PET") +frame:RegisterEvent("PLAYER_FOCUS_CHANGED") +frame:RegisterEvent("ARENA_OPPONENT_UPDATE") +frame:RegisterEvent("UNIT_ENTERED_VEHICLE") +frame:RegisterEvent("UNIT_EXITED_VEHICLE") +frame:RegisterEvent("PLAYER_TARGET_CHANGED") +frame:RegisterEvent("PARTY_MEMBERS_CHANGED") +frame:RegisterEvent("RAID_ROSTER_UPDATE") +frame:RegisterEvent("PLAYER_ENTERING_WORLD") +frame:SetScript("OnEvent", EventHandler) + +frame:SetScript("OnUpdate", function() + if WeakAuras.IsPaused() then + return + end + WeakAuras.StartProfileSystem("bufftrigger2") + if next(matchDataChanged) then + local time = GetTime() + UpdateStates(matchDataChanged, time) + wipe(matchDataChanged) + end + WeakAuras.StopProfileSystem("bufftrigger2") +end) + +function BuffTrigger.ScanAll() + local units = {} + local time = GetTime() + + for unit in pairs(scanFuncName) do + units[unit] = true + end + + for unit in pairs(scanFuncSpellId) do + units[unit] = true + end + + for unit in pairs(scanFuncGeneral) do + units[unit] = true + end + + for unit in pairs(units) do + if unit == "group" then + ScanAllGroup(time, matchDataChanged) + elseif unit == "boss" then + ScanAllBoss(time, matchDataChanged) + else + ScanGroupUnit(time, matchDataChanged, nil, unit) + end + end +end + +local function UnloadAura(scanFuncName, id) + for unit, unitData in pairs(scanFuncName) do + for debuffType, debuffData in pairs(unitData) do + for name, nameData in pairs(debuffData) do + for triggerInfo in pairs(nameData) do + if triggerInfo.id == id or not id then + if triggerInfo.nextScheduledCheckHandle then + timer:CancelTimer(triggerInfo.nextScheduledCheckHandle) + end + nameData[triggerInfo] = nil + end + end + if not next(nameData) then + debuffData[name] = nil + end + end + + if not next(debuffData) then + unitData[debuffType] = nil + end + end + if not next(unitData) then + scanFuncName[unit] = nil + end + end +end + +local function UnloadGeneral(scanFuncGeneral, id) + for unit, unitData in pairs(scanFuncGeneral) do + for debuffType, debuffData in pairs(unitData) do + for triggerInfo in pairs(debuffData) do + if triggerInfo.id == id or not id then + if triggerInfo.nextScheduledCheckHandle then + timer:CancelTimer(triggerInfo.nextScheduledCheckHandle) + end + debuffData[triggerInfo] = nil + end + end + if not next(debuffData) then + unitData[debuffType] = nil + end + end + if not next(unitData) then + scanFuncGeneral[unit] = nil + end + end +end + +function BuffTrigger.UnloadAll() + UnloadAura(scanFuncName, nil) + UnloadAura(scanFuncSpellId, nil) + UnloadGeneral(scanFuncGeneral, nil) + + for unit, unitData in pairs(matchData) do + for filter, filterData in pairs(unitData) do + for index, indexData in pairs(filterData) do + wipe(indexData.auras) + end + end + end + + wipe(scanFuncName) + wipe(scanFuncSpellId) + wipe(scanFuncGeneral) + wipe(scanFuncNameGroup) + wipe(scanFuncSpellIdGroup) + wipe(scanFuncGeneralGroup) + wipe(scanFuncNameMulti) + wipe(scanFuncSpellIdMulti) + wipe(unitExistScanFunc) + wipe(groupScanFuncs) + wipe(matchDataByTrigger) + wipe(matchDataMulti) + wipe(matchDataChanged) + wipe(activeGroupScanFuncs) +end + + +local function LoadAura(id, triggernum, triggerInfo) + if not triggerInfo.unit then + return + end + local filter = triggerInfo.debuffType + local time = GetTime(); + + local unitsToCheck = {} + + if triggerInfo.unit == "multi" then + AddScanFuncs(triggerInfo, nil, scanFuncNameMulti, scanFuncSpellIdMulti, nil) + elseif triggerInfo.groupTrigger then + triggerInfo.maxUnitCount = 0 + for unit in GetAllUnits(triggerInfo.unit) do + RecheckActive(triggerInfo, unit, unitsToCheck) + end + else + AddScanFuncs(triggerInfo, triggerInfo.unit, scanFuncName, scanFuncSpellId, scanFuncGeneral) + unitsToCheck[triggerInfo.unit] = true + end + + if triggerInfo.unitExists ~= nil then + unitExistScanFunc[triggerInfo.unit] = unitExistScanFunc[triggerInfo.unit] or {} + unitExistScanFunc[triggerInfo.unit][id] = unitExistScanFunc[triggerInfo.unit][id] or {} + tinsert(unitExistScanFunc[triggerInfo.unit][id], triggerInfo) + + if existingUnits[triggerInfo.unit] == nil then + existingUnits[triggerInfo.unit] = UnitExists(triggerInfo.unit) + end + end + + if triggerInfo.groupTrigger then + groupScanFuncs[triggerInfo.unit] = groupScanFuncs[triggerInfo.unit] or {} + tinsert(groupScanFuncs[triggerInfo.unit], triggerInfo) + end + + matchDataChanged[id] = matchDataChanged[id] or {} + matchDataChanged[id][triggernum] = true +end + +function BuffTrigger.LoadDisplays(toLoad) + for id in pairs(toLoad) do + if triggerInfos[id] then + for triggernum, triggerInfo in pairs(triggerInfos[id]) do + LoadAura(id, triggernum, triggerInfo) + end + end + end +end + + +function BuffTrigger.UnloadDisplays(toUnload) + local updateGroupScanFuncs = false + for id in pairs(toUnload) do + UnloadAura(scanFuncName, id) + UnloadAura(scanFuncSpellId, id) + UnloadGeneral(scanFuncGeneral, id) + + UnloadAura(scanFuncNameGroup, id) + UnloadAura(scanFuncSpellIdGroup, id) + UnloadGeneral(scanFuncGeneralGroup, id) + + UnloadGeneral(scanFuncNameMulti, id) + UnloadGeneral(scanFuncSpellIdMulti, id) + + for unit, unitData in pairs(unitExistScanFunc) do + unitData[id] = nil + end + + for unit, unitData in pairs(matchData) do + for filter, filterData in pairs(unitData) do + for index, indexData in pairs(filterData) do + indexData.auras[id] = nil + end + end + end + matchDataByTrigger[id] = nil + + for guid, guidData in pairs(matchDataMulti) do + for key, data in pairs(matchDataMulti[guid]) do + for source, sourceData in pairs(data) do + sourceData.auras[id] = nil + end + end + end + matchDataChanged[id] = nil + end + + + for unitType, funcs in pairs(groupScanFuncs) do + for i = #funcs, 1, -1 do + if toUnload[funcs[i].id] then + tremove(funcs, i) + end + end + end + + for unit, unitData in pairs(activeGroupScanFuncs) do + for triggerInfo in pairs(unitData) do + if toUnload[triggerInfo.id] then + unitData[triggerInfo] = nil + end + end + end +end + +function BuffTrigger.FinishLoadUnload() + -- Nothing! +end + +--- Removes all data for an aura id +-- @param id +function BuffTrigger.Delete(id) + BuffTrigger.UnloadDisplays({[id] = true}) + triggerInfos[id] = nil +end + +--- Updates all data for aura oldid to use newid +-- @param oldid +-- @param newid +function BuffTrigger.Rename(oldid, newid) + triggerInfos[newid] = triggerInfos[oldid] + triggerInfos[oldid] = nil + + if triggerInfos[newid] then + for triggernum, triggerData in pairs(triggerInfos[newid]) do + triggerData.id = newid + end + end + + matchDataByTrigger[newid] = matchDataByTrigger[oldid] + matchDataByTrigger[oldid] = nil + + for unit, unitData in pairs(matchData) do + for filter, filterData in pairs(unitData) do + for index, indexData in pairs(filterData) do + indexData.auras[newid] = indexData.auras[oldid] + indexData.auras[oldid] = nil + end + end + end + + for unit, unitData in pairs(unitExistScanFunc) do + unitData[newid] = unitData[oldid] + unitData[oldid] = nil + end + matchDataChanged[newid] = matchDataChanged[oldid] + matchDataChanged[oldid] = nil +end + +local function createScanFunc(trigger) + local isSingleMissing = IsSingleMissing(trigger) + local isMulti = trigger.unit == "multi" + local useStacks = not isSingleMissing and not isMulti and trigger.useStacks + local use_stealable = not isSingleMissing and not isMulti and trigger.use_stealable + local use_debuffClass = not isSingleMissing and not isMulti and trigger.use_debuffClass + local use_tooltip = not isSingleMissing and not isMulti and trigger.fetchTooltip and trigger.use_tooltip + local use_tooltipValue = not isSingleMissing and not isMulti and trigger.fetchTooltip and trigger.use_tooltipValue + local use_total = not isSingleMissing and not isMulti and trigger.useTotal and trigger.total + local use_blacklist_name = not isSingleMissing and not isMulti and trigger.useBlackName and trigger.blackauranames + local use_blacklist_spellId = not isSingleMissing and not isMulti and trigger.useBlackExactSpellId and trigger.blackauraspellids + + if not useStacks and use_stealable == nil and not use_debuffClass and trigger.ownOnly == nil + and not use_tooltip and not use_tooltipValue and not trigger.useNamePattern and not use_total + and not use_blacklist_name and not use_blacklist_spellId then + return nil + end + + local preamble = "" + + local ret = [[ + return function(time, matchData) + ]] + + if use_total then + local ret2 = [[ + if not(matchData.duration %s %s) then + return false + end + ]] + ret = ret .. ret2:format(trigger.totalOperator or ">=", tonumber(trigger.total) or 0) + end + + if useStacks then + local ret2 = [[ + if not(matchData.stacks %s %s) then + return false + end + ]] + ret = ret .. ret2:format(trigger.stacksOperator or ">=", tonumber(trigger.stacks) or 0) + end + + if use_stealable then + ret = ret .. [[ + if not matchData.isStealable then + return false + end + ]] + elseif use_stealable == false then + ret = ret .. [[ + if matchData.isStealable then + return false + end + ]] + end + if use_debuffClass then + local ret2 = [[ + local tDebuffClass = %s; + if not tDebuffClass[matchData.debuffClass] then + return false + end + ]] + ret = ret .. ret2:format(trigger.debuffClass and type(trigger.debuffClass) == "table" and WeakAuras.SerializeTable(trigger.debuffClass) or "{}") + end + + if trigger.ownOnly then + ret = ret .. [[ + if matchData.unitCaster ~= 'player' and matchData.unitCaster ~= 'pet' then + return false + end + ]] + elseif trigger.ownOnly == false then + ret = ret .. [[ + if matchData.unitCaster == 'player' or matchData.unitCaster == 'pet' then + return false + end + ]] + end + + if use_tooltip and trigger.tooltip_operator and trigger.tooltip then + if trigger.tooltip_operator == "==" then + local ret2 = [[ + if not matchData.tooltip or not matchData.tooltip == %q then + return false + end + ]] + ret = ret .. ret2:format(trigger.tooltip) + elseif trigger.tooltip_operator == "find('%s')" then + local ret2 = [[ + if not matchData.tooltip or not matchData.tooltip:find(%q) then + return false + end + ]] + ret = ret .. ret2:format(trigger.tooltip) + elseif trigger.tooltip_operator == "match('%s')" then + local ret2 = [[ + if not matchData.tooltip or not matchData.tooltip:match(%q) then + return false + end + ]] + ret = ret .. ret2:format(trigger.tooltip) + end + end + + if use_tooltipValue and trigger.tooltipValueNumber and trigger.tooltipValue_operator and trigger.tooltipValue then + local property = "tooltip" .. tonumber(trigger.tooltipValueNumber) + local ret2 = [[ + if not matchData.%s or not (matchData.%s %s %s) then + return false + end + ]] + ret = ret .. ret2:format(property, property, trigger.tooltipValue_operator, trigger.tooltipValue) + end + + if trigger.useNamePattern and trigger.namePattern_operator and trigger.namePattern_name then + if trigger.namePattern_operator == "==" then + local ret2 = [[ + if not matchData.name == %q then + return false + end + ]] + ret = ret .. ret2:format(trigger.namePattern_name) + elseif trigger.namePattern_operator == "find('%s')" then + local ret2 = [[ + if not matchData.name:find(%q) then + return false + end + ]] + ret = ret .. ret2:format(trigger.namePattern_name) + elseif trigger.namePattern_operator == "match('%s')" then + local ret2 = [[ + if not matchData.name:match(%q) then + return false + end + ]] + ret = ret .. ret2:format(trigger.namePattern_name) + end + end + + if use_blacklist_name then + local names = {} + for index, spellName in ipairs(trigger.blackauranames) do + local spellId = WeakAuras.SafeToNumber(spellName) + local name = GetSpellInfo(spellId or 0) or spellName + tinsert(names, name) + end + + preamble = preamble .. "local blacklistNames = {\n" + for index, name in ipairs(names) do + preamble = preamble .. string.format(" [%q] = true,\n", name) + end + preamble = preamble .. "}\n" + ret = ret .. [[ + if blacklistNames[matchData.name] then + return false + end + ]] + end + + if use_blacklist_spellId then + preamble = preamble .. "local blacklistSpellId = {\n" + for index, spellId in ipairs(trigger.blackauraspellids) do + local spell = WeakAuras.SafeToNumber(spellId) + if spell then + preamble = preamble .. string.format(" [%s] = true,\n", spell) + end + end + preamble = preamble .. "}\n" + ret = ret .. [[ + if blacklistSpellId[matchData.spellId] then + return false + end + ]] + end + + ret = ret .. [[ + return true + end + ]] + + local func, err = loadstring(preamble .. ret) + + if func then + return func() + end +end + +local function highestExpirationTime(bestMatch, auraMatch) + if bestMatch.expirationTime and auraMatch.expirationTime then + return auraMatch.expirationTime > bestMatch.expirationTime + end + return true +end + +local function lowestExpirationTime(bestMatch, auraMatch) + if bestMatch.expirationTime and auraMatch.expirationTime then + return auraMatch.expirationTime < bestMatch.expirationTime + end + return false +end + +local function GreaterEqualOne(x) + return x >= 1 +end + +local function EqualZero(x) + return x == 0 +end + +--- Adds an aura, setting up internal data structures for all buff triggers. +-- @param data +function BuffTrigger.Add(data) + local id = data.id + + triggerInfos[id] = nil + for triggernum, triggerData in ipairs(data.triggers) do + local trigger, untrigger = triggerData.trigger, triggerData.untrigger + if trigger.type == "aura2" then + + trigger.unit = trigger.unit or "player" + trigger.debuffType = trigger.debuffType or "HELPFUL" + + local combineMode = "showOne" + local perUnitMode + if not IsSingleMissing(trigger) and trigger.showClones then + if IsGroupTrigger(trigger) and trigger.combinePerUnit then + combineMode = "showPerUnit" + if trigger.unit == "multi" then + perUnitMode = "affected" + else + perUnitMode = trigger.perUnitMode or "affected" + end + else + combineMode = "showClones" + end + end + + local scanFunc = createScanFunc(trigger) + + local remFunc + if trigger.unit ~= "multi" and not IsSingleMissing(trigger) and trigger.useRem then + local remFuncStr = WeakAuras.function_strings.count:format(trigger.remOperator or ">=", tonumber(trigger.rem) or 0) + remFunc = WeakAuras.LoadFunction(remFuncStr) + end + + local names + if trigger.useName and trigger.auranames then + names = {} + for index, spellName in ipairs(trigger.auranames) do + local spellId = WeakAuras.SafeToNumber(spellName) + names[index] = GetSpellInfo(spellId or 0) or spellName + end + end + + local showIfInvalidUnit + if trigger.unit ~= "player" and not IsGroupTrigger(trigger) then + showIfInvalidUnit = trigger.unitExists or false + end + local effectiveUseGroupCount = IsGroupTrigger(trigger) and trigger.useGroup_count + local groupCountFunc + if effectiveUseGroupCount then + local group_countFuncStr + local count, countType = WeakAuras.ParseNumber(trigger.group_count) + if trigger.group_countOperator and count and countType then + if countType == "whole" then + group_countFuncStr = WeakAuras.function_strings.count:format(trigger.group_countOperator, count) + else + group_countFuncStr = WeakAuras.function_strings.count_fraction:format(trigger.group_countOperator, count) + end + else + group_countFuncStr = WeakAuras.function_strings.count:format(">", 0) + end + groupCountFunc = WeakAuras.LoadFunction(group_countFuncStr) + end + + local matchCountFunc + if HasMatchCount(trigger) and trigger.match_countOperator and trigger.match_count then + local count = tonumber(trigger.match_count) + local match_countFuncStr = WeakAuras.function_strings.count:format(trigger.match_countOperator, count) + matchCountFunc = WeakAuras.LoadFunction(match_countFuncStr) + elseif IsGroupTrigger(trigger) then + if trigger.showClones and not trigger.combinePerUnit then + matchCountFunc = GreaterEqualOne + end + elseif not IsGroupTrigger(trigger) then + if trigger.matchesShowOn == "showOnMissing" then + matchCountFunc = EqualZero + elseif trigger.matchesShowOn == "showOnActive" or not trigger.matchesShowOn then + matchCountFunc = GreaterEqualOne + end + end + + local groupTrigger = trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party" + local effectiveIgnoreSelf = groupTrigger and trigger.ignoreSelf + local effectiveGroupRole = groupTrigger and trigger.useGroupRole and trigger.group_role + local effectiveClass = groupTrigger and trigger.useClass and trigger.class + + if trigger.unit == "multi" then + BuffTrigger.InitMultiAura() + end + + local auraspellids + if trigger.useExactSpellId and trigger.auraspellids then + auraspellids = {} + for _, spellIdString in ipairs(trigger.auraspellids) do + if spellIdString ~= "" then + local spellId = tonumber(spellIdString) + if spellId then + tinsert(auraspellids, spellId) + end + end + end + end + + local unit + local groupSubType = "group" + if trigger.unit == "member" then + unit = trigger.specificUnit + elseif trigger.unit == "raid" then + unit = "group" + groupSubType = "raid" + elseif trigger.unit == "party" then + unit = "group" + groupSubType = "party" + else + unit = trigger.unit + end + + local triggerInformation = { + auranames = names, + auraspellids = auraspellids, + unit = unit, + debuffType = trigger.debuffType, + ownOnly = trigger.ownOnly, + combineMode = combineMode, + perUnitMode = perUnitMode, + scanFunc = scanFunc, + remainingFunc = remFunc, + remainingCheck = trigger.unit ~= "multi" and not IsSingleMissing(trigger) and trigger.useRem and tonumber(trigger.rem) or 0, + id = id, + triggernum = triggernum, + compareFunc = trigger.combineMode == "showHighest" and highestExpirationTime or lowestExpirationTime, + unitExists = showIfInvalidUnit, + fetchTooltip = not IsSingleMissing(trigger) and trigger.unit ~= "multi" and trigger.fetchTooltip, + groupTrigger = IsGroupTrigger(trigger), + ignoreSelf = effectiveIgnoreSelf, + groupRole = effectiveGroupRole, + groupSubType = groupSubType, + groupCountFunc = groupCountFunc, + class = effectiveClass, + matchCountFunc = matchCountFunc, + useAffected = unit == "group" and trigger.useAffected, + isMulti = trigger.unit == "multi", + } + triggerInfos[id] = triggerInfos[id] or {} + triggerInfos[id][triggernum] = triggerInformation + end + end +end + +--- Updates old data to the new format. +-- @param data +function BuffTrigger.Modernize(data) + -- Does nothing yet! +end + +--- Returns whether the trigger can have a duration. +-- @param data +-- @param triggernum +function BuffTrigger.CanHaveDuration(data, triggernum) + return "timed" +end + +--- Returns a table containing the names of all overlays +-- @param data +-- @param triggernum +function BuffTrigger.GetOverlayInfo(data, triggernum) + return {} +end + +--- Returns whether the icon can be automatically selected. +-- @param data +-- @param triggernum +-- @return boolean +function BuffTrigger.CanHaveAuto(data, triggernum) + return true +end + +--- Returns whether the trigger can have clones. +-- @param data +-- @param triggernum +-- @return +function BuffTrigger.CanHaveClones(data, triggernum) + local trigger = data.triggers[triggernum].trigger + if not IsSingleMissing(trigger) and trigger.showClones then + return true + end + return false +end + +---Returns the type of tooltip to show for the trigger. +-- @param data +-- @param triggernum +-- @return string +function BuffTrigger.CanHaveTooltip(data, triggernum) + return "aura" +end + +function BuffTrigger.SetToolTip(trigger, state) + if not state.unit or not state.index then + return false + end + if trigger.debuffType == "HELPFUL" then + GameTooltip:SetUnitBuff(state.unit, state.index) + elseif trigger.debuffType == "HARMFUL" then + GameTooltip:SetUnitDebuff(state.unit, state.index) + end + return true +end + + +function BuffTrigger.GetNameAndIconSimple(data, triggernum) + if not data then + return + end + local _, name, icon + local trigger = data.triggers[triggernum].trigger + + if trigger.useName and trigger.auranames then + for index, spellName in ipairs(trigger.auranames) do + local spellId = WeakAuras.SafeToNumber(spellName) + if spellId then + name, _, icon = GetSpellInfo(spellName) + if name and icon then + return name, icon + end + elseif not tonumber(spellName) then + name, _, icon = GetSpellInfo(spellName) + if (name and icon) then + return name, icon + end + end + end + end + + if trigger.useExactSpellId and trigger.auraspellids then + for index, spellIdString in ipairs(trigger.auraspellids) do + local spellId = spellIdString ~= "" and tonumber(spellIdString) + if spellId then + name, _, icon = GetSpellInfo(spellIdString) + if name and icon then + return name, icon + end + end + end + end +end + +--- Returns the name and icon to show in the options. +-- @param data +-- @param triggernum +-- @return name and icon +function BuffTrigger.GetNameAndIcon(data, triggernum) + local name, icon = BuffTrigger.GetNameAndIconSimple(data, triggernum) + if (not name or not icon and WeakAuras.spellCache) then + local trigger = data.triggers[triggernum].trigger + if trigger.useName and trigger.auranames then + for index, spellName in ipairs(trigger.auranames) do + icon = WeakAuras.spellCache.GetIcon(spellName) + if icon then + return spellName, icon + end + end + end + end + return name, icon +end + +--- Returns the tooltip text for additional properties. +-- @param data +-- @param triggernum +-- @return string of additional properties +function BuffTrigger.GetAdditionalProperties(data, triggernum) + local trigger = data.triggers[triggernum].trigger + + local ret = "|cFFFF0000%".. triggernum .. ".spellId|r - " .. L["Spell ID"] .. "\n" + ret = ret .. "|cFFFF0000%".. triggernum .. ".debuffClass|r - " .. L["Debuff Class"] .. "\n" + ret = ret .. "|cFFFF0000%".. triggernum .. ".debuffClassIcon|r - " .. L["Debuff Class Icon"] .. "\n" + ret = ret .. "|cFFFF0000%".. triggernum .. ".unitCaster|r - " .. L["Caster Unit"] .. "\n" + ret = ret .. "|cFFFF0000%".. triggernum .. ".casterName|r - " .. L["Caster Name"] .. "\n" + ret = ret .. "|cFFFF0000%".. triggernum .. ".unitName|r - " .. L["Unit Name"] .. "\n" + ret = ret .. "|cFFFF0000%".. triggernum .. ".matchCount|r - " .. L["Match Count"] .. "\n" + ret = ret .. "|cFFFF0000%".. triggernum .. ".matchCountPerUnit|r - " .. L["Match Count per Unit"] .. "\n" + ret = ret .. "|cFFFF0000%".. triggernum .. ".unitCount|r - " .. L["Units Affected"] .. "\n" + ret = ret .. "|cFFFF0000%".. triggernum .. ".totalStacks|r - " .. L["Total stacks over all matches"] .. "\n" + + if trigger.unit ~= "multi" then + ret = ret .. "|cFFFF0000%".. triggernum .. ".maxUnitCount|r - " .. L["Total Units"] .. "\n" + end + + if not IsSingleMissing(trigger) and trigger.unit ~= "multi" and trigger.fetchTooltip then + ret = ret .. "|cFFFF0000%tooltip|r - " .. L["Tooltip"] .. "\n" + ret = ret .. "|cFFFF0000%tooltip1|r - " .. L["First Value of Tooltip Text"] .. "\n" + ret = ret .. "|cFFFF0000%tooltip2|r - " .. L["Second Value of Tooltip Text"] .. "\n" + ret = ret .. "|cFFFF0000%tooltip3|r - " .. L["Third Value of Tooltip Text"] .. "\n" + end + + if (trigger.unit == "group" or trigger.unit == "raid" or trigger.unit == "party") and trigger.useAffected then + ret = ret .. "|cFFFF0000%affected|r - " .. L["Names of affected Players"] .. "\n" + ret = ret .. "|cFFFF0000%unaffected|r - " .. L["Names of unaffected Players"] .. "\n" + end + + return ret +end + +function BuffTrigger.GetTriggerConditions(data, triggernum) + local trigger = data.triggers[triggernum].trigger + local result = {} + + result["debuffClass"] = { + display = L["Debuff Type"], + type = "select", + values = WeakAuras.debuff_class_types + } + + result["unitCaster"] = { + display = L["Caster"], + type = "string" + } + + result["expirationTime"] = { + display = L["Remaining Duration"], + type = "timer" + } + + result["duration"] = { + display = L["Total Duration"], + type = "number" + } + + result["stacks"] = { + display = L["Stacks"], + type = "number" + } + + result["name"] = { + display = L["Name"], + type = "string" + } + + result["spellId"] = { + display = L["Spell Id"], + type = "number", + operator_types_only_equal = true + } + + result["matchCount"] = { + display = L["Total Match Count"], + type = "number" + } + + result["matchCountPerUnit"] = { + display = L["Match Count per Unit"], + type = "number" + } + + result["unitCount"] = { + display = L["Affected Unit Count"], + type = "number" + } + + result["totalStacks"] = { + display = L["Total Stacks"], + type = "number" + } + + if trigger.unit ~= "multi" then + result["maxUnitCount"] = { + display = L["Total Unit Count"], + type = "number" + } + end + + if not IsGroupTrigger(trigger) and trigger.matchesShowOn == "showAlways" then + result["buffed"] = { + display = L["Aura(s) Found"], + type = "bool", + test = function(state, needle) + return state and state.show and ((state.active and true or false) == (needle == 1)) + end + } + end + + if not IsSingleMissing(trigger) and trigger.unit ~= "multi" and trigger.fetchTooltip then + result["tooltip1"] = { + display = L["Tooltip Value 1"], + type = "number" + } + result["tooltip2"] = { + display = L["Tooltip Value 2"], + type = "number" + } + result["tooltip3"] = { + display = L["Tooltip Value 3"], + type = "number" + } + end + + return result +end + +function BuffTrigger.CreateFallbackState(data, triggernum, state) + state.show = true + state.changed = true + state.progressType = "timed" + state.duration = 0 + state.expirationTime = math.huge + local name, icon = BuffTrigger.GetNameAndIconSimple(data, triggernum) + state.name = name + state.icon = icon +end + +function BuffTrigger.GetName(triggerType) + if triggerType == "aura2" then + return L["Aura"] + end +end + +function WeakAuras.CanConvertBuffTrigger2(trigger) + if trigger.type ~= "aura" then + return false + end + + if trigger.unit == "multi" then + return true, L["Note: The available text replacements for multi triggers match the normal triggers now."] + end + + if trigger.unit and trigger.hideAlone then + return false, L["Note: 'Hide Alone' is not available in the new aura tracking system. A load option can be used instead."] + end + + if trigger.unit == "group" then + return true, L["Warning: Name info is now available via %affected, %unaffected. Number of affected group members via %unitCount. Some options behave differently now. This is not automatically adjusted."] + end + + if trigger.fullscan then + if trigger.subcount then + return true, L["Warning: Tooltip values are now available via %tooltip1, %tooltip2, %tooltip3 instead of %s. This is not automatically adjusted."] + end + + if trigger.use_name and trigger.use_spellId then + return false, L["Warning: Full Scan auras checking for both name and spell id can't be converted."] + end + end + + return true +end + +function WeakAuras.ConvertBuffTrigger2(trigger) + if not WeakAuras.CanConvertBuffTrigger2(trigger) then + return + end + trigger.type = "aura2" + + if trigger.fullscan and trigger.autoclone then + trigger.combineMatches = "showClones" + else + trigger.combineMatches = "showLowest" + end + + if trigger.fullscan and trigger.use_stealable then + trigger.use_stealable = true + else + trigger.use_stealable = nil + end + + if trigger.fullscan and trigger.use_debuffClass and trigger.debuffClass then + else + trigger.use_debuffClass = false + end + + if trigger.fullscan and trigger.use_tooltip then + trigger.fetchTooltip = true + else + trigger.use_tooltip = false + end + + if trigger.fullscan and trigger.subcount then + trigger.fetchTooltip = true + end + + if trigger.fullscan and trigger.use_name then + trigger.useNamePattern = true + trigger.namePattern_operator = trigger.name_operator + trigger.namePattern_name = trigger.name + end + + if trigger.fullscan then + -- Use name from fullscan + if trigger.use_name then + if trigger.name_operator == "==" then + -- Convert to normal name check + trigger.useName = true + trigger.auranames = {} + trigger.auranames[1] = trigger.name + end + end + if trigger.use_spellId then + trigger.useExactSpellId = true + trigger.auraspellids = {} + trigger.auraspellids[1] = trigger.spellId + end + else + trigger.useName = true + end + + if not trigger.fullscan and trigger.unit ~= "multi" then + trigger.auranames = {} + for i = 1, 9 do + trigger.auranames[i] = trigger.spellIds[i] and tostring(trigger.spellIds[i]) or trigger.names[i] + end + end + + if trigger.unit == "multi" then + -- Closest to the old behavior + trigger.showClones = true + trigger.useName = true + trigger.auranames = {} + trigger.auranames[1] = tostring(trigger.spellId) or trigger.name + end + + -- debuffType is exactly the same, no need to touch it + -- remaining is exactly the same for now + -- ownOnly is exactly the same + -- unitExists is exactly the same + + if trigger.useCount then + trigger.useStacks = trigger.useCount + trigger.stacksOperator = trigger.countOperator + trigger.stacks = trigger.count + end + + if trigger.fullscan and trigger.autoclone then + trigger.matchesShowOn = "showOnActive" + else + trigger.matchesShowOn = trigger.buffShowOn + end + + if trigger.unit == "group" then + trigger.matchesShowOn = nil + trigger.showClones = trigger.groupclone + end + + if trigger.unit == "group" and not trigger.groupclone then + if trigger.name_info == "players" or trigger.name_info == "nonplayers" then + trigger.useAffected = true + end + end + + if trigger.unit == "group" and trigger.group_countOperator and trigger.group_count then + trigger.useGroup_count = true + else + trigger.useGroup_count = false + end +end + +-- Multi Target trigger code +local multiAuraFrame +local pendingTracks = {} + +local unitToGuid = {} +local guidToUnit = {} + +local function ReleaseUID(unit) + local guid = unitToGuid[unit] + if guid then + guidToUnit[guid][unit] = nil + end +end + +local function SetUID(guid, unit) + ReleaseUID(unit) + + unitToGuid[unit] = guid + guidToUnit[guid] = guidToUnit[guid] or {} + guidToUnit[guid][unit] = true +end + +local function GetUnit(guid) + if not guidToUnit[guid] then + return nil + end + for unit in pairs(guidToUnit[guid]) do + if UnitGUID(unit) == guid then + return unit + else + guidToUnit[guid][unit] = nil + end + end +end + +local function TrackUid(unit) + local GUID = UnitGUID(unit) + if GUID then + SetUID(GUID, unit) + BuffTrigger.HandlePendingTracks(unit, GUID) + else + ReleaseUID(unit) + end + unit = unit.."target" + GUID = UnitGUID(unit) + if GUID then + WeakAuras.SetUID(GUID, unit) + BuffTrigger.HandlePendingTracks(unit, GUID) + else + ReleaseUID(unit) + end +end + +local function RemoveMatchDataMulti(base, destGUID, key, sourceGUID) + if base[key] and base[key][sourceGUID] then + for id, idData in pairs(base[key][sourceGUID].auras) do + for triggernum, triggerData in pairs(idData) do + tDeleteItem(matchDataByTrigger[id][triggernum][destGUID], base[key][sourceGUID]) + if not next(matchDataByTrigger[id][triggernum][destGUID]) then + matchDataByTrigger[id][triggernum][destGUID] = nil + end + matchDataChanged[id] = matchDataChanged[id] or {} + matchDataChanged[id][triggernum] = true + end + end + base[key][sourceGUID] = nil + end +end + +local function CleanUpMulti(guid) + cleanupTimerMulti[guid].handle = nil + cleanupTimerMulti[guid].nextTime = nil + local nextCheck + if matchDataMulti[guid] then + local time = GetTime() + for key, data in pairs(matchDataMulti[guid]) do + for source, sourceData in pairs(data) do + local removeAt = sourceData.expirationTime or (sourceData.time + 60) + if removeAt <= time then + RemoveMatchDataMulti(matchDataMulti[guid], guid, key, source) + else + if not nextCheck then + nextCheck = removeAt + elseif (removeAt < nextCheck) then + nextCheck = removeAt + end + end + end + end + end + + if nextCheck then + local timeUntilNext = nextCheck - GetTime() + if timeUntilNext > 0 then + cleanupTimerMulti[guid].handle = timer:ScheduleTimerFixed(CleanUpMulti, timeUntilNext, guid) + cleanupTimerMulti[guid].nextTime = nextCheck + end + end +end + +local function ScheduleMultiCleanUp(guid, time) + cleanupTimerMulti[guid] = cleanupTimerMulti[guid] or {} + if not cleanupTimerMulti[guid].nextTime or time < cleanupTimerMulti[guid].nextTime then + if cleanupTimerMulti[guid].handle then + timer:CancelTimer(cleanupTimerMulti[guid].handle) + end + cleanupTimerMulti[guid].handle = timer:ScheduleTimerFixed(CleanUpMulti, time - GetTime(), guid) + cleanupTimerMulti[guid].nextTime = time + end +end + +local function UpdateMatchDataMulti(time, base, key, event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount) + local updated = false + local icon = spellId and select(3, GetSpellInfo(spellId)) + ScheduleMultiCleanUp(destGUID, time + 60) + if not base[key] or not base[key][sourceGUID] then + updated = true + base[key] = base[key] or {} + base[key][sourceGUID] = { + name = spellName, + icon = icon, + duration = 0, + expirationTime = math.huge, + spellId = spellId, + GUID = destGUID, + sourceGUID = sourceGUID, + unitName = destName, + casterName = sourceName, + time = time, + auras = {} + } + else + base[key][sourceGUID] = base[key][sourceGUID] or {} + local match = base[key][sourceGUID] + match.time = time + + if match.name ~= spellName then + match.name = spellName + updated = true + end + + if match.unitName ~= destName then + match.unitName = destName + updated = true + end + + local duration, expirationTime + if event == "SPELL_AURA_APPLIED_DOSE" or event == "SPELL_AURA_REMOVED_DOSE" then + -- Shouldn't affect duration/expirationTime nor icon + duration = match.duration or 0 + expirationTime = match.expirationTime or math.huge + icon = match.icon or icon + else + duration = 0 + expirationTime = math.huge + end + + if match.duration ~= duration then + match.duration = duration + updated = true + end + + if match.expirationTime ~= expirationTime then + match.expirationTime = expirationTime + updated = true + end + + if match.icon ~= icon then + match.icon = icon + updated = true + end + + if match.count ~= amount then + match.count = amount + updated = true + end + + if match.spellId ~= spellId then + match.spellId = spellId + updated = true + end + + if match.casterName ~= sourceName then + match.casterName = sourceName + updated = true + end + end + + return updated +end + +local function AugmentMatchDataMultiWith(matchData, unit, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId) + ScheduleMultiCleanUp(matchData.GUID, expirationTime) + local changed = false + if matchData.name ~= name then + matchData.name = name + changed = true + end + + if matchData.icon ~= icon then + matchData.icon = icon + changed = true + end + + if matchData.stacks ~= stacks then + matchData.stacks = stacks + changed = true + end + + if matchData.debuffClass ~= debuffClass then + matchData.debuffClass = debuffClass + changed = true + end + + local debuffClassIcon = WeakAuras.EJIcons[debuffClass] + if matchData.debuffClassIcon ~= debuffClassIcon then + matchData.debuffClassIcon = debuffClassIcon + changed = true + end + + if matchData.duration ~= duration then + matchData.duration = duration + changed = true + end + + if matchData.expirationTime ~= expirationTime then + matchData.expirationTime = expirationTime + changed = true + end + + if matchData.unitCaster ~= unitCaster then + matchData.unitCaster = unitCaster + changed = true + end + + local casterName = GetUnitName(unitCaster, false) or "" + if matchData.casterName ~= casterName then + matchData.casterName = casterName + changed = true + end + + if (matchData.unit ~= unit) then + matchData.unit = unit + changed = true + end + + local unitName = GetUnitName(unit, false) or "" + if matchData.unitName ~= unitName then + matchData.unitName = unitName + changed = true + end + + + if matchData.spellId ~= spellId then + matchData.spellId = name + changed = true + end + return changed +end + +local function AugmentMatchDataMulti(matchData, unit, filter, sourceGUID, nameKey, spellKey) + local index = 1 + while true do + local name, rank, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId = UnitAura(unit, index, filter) + if not name then + return false + end + + if debuffClass == nil then + debuffClass = "none" + elseif debuffClass == "" then + debuffClass = "enrage" + else + debuffClass = string.lower(debuffClass) + end + local auraSourceGuid = unitCaster and UnitGUID(unitCaster) + if (name == nameKey or spellId == spellKey) and sourceGUID == auraSourceGuid then + local changed = AugmentMatchDataMultiWith(matchData, unit, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId) + return changed + end + index = index + 1 + end +end + +local function HandleCombatLog(scanFuncsName, scanFuncsSpellId, filter, event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount) + local time = GetTime() + local unit = GetUnit(destGUID) + if scanFuncsName and scanFuncsName[spellName] or scanFuncsSpellId and scanFuncsSpellId[spellId] then + ScheduleMultiCleanUp(destGUID, time + 60) + matchDataMulti[destGUID] = matchDataMulti[destGUID] or {} + + if scanFuncsSpellId and scanFuncsSpellId[spellId] then + local updatedSpellId = UpdateMatchDataMulti(time, matchDataMulti[destGUID], spellId, event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount) + if unit then + updatedSpellId = AugmentMatchDataMulti(matchDataMulti[destGUID][spellId][sourceGUID], unit, filter, sourceGUID, nil, spellId) or updatedSpellId + else + pendingTracks[destGUID] = true + end + if updatedSpellId then + for triggerInfo in pairs(scanFuncsSpellId[spellId]) do + if MatchesTriggerInfoMulti(triggerInfo, sourceGUID) then + ReferenceMatchDataMulti(matchDataMulti[destGUID][spellId][sourceGUID], triggerInfo.id, triggerInfo.triggernum, destGUID) + end + end + end + end + + if scanFuncsName and scanFuncsName[spellName] then + local updatedName = UpdateMatchDataMulti(time, matchDataMulti[destGUID], spellName, event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount) + if unit then + updatedName = AugmentMatchDataMulti(matchDataMulti[destGUID][spellName][sourceGUID], unit, filter, sourceGUID, spellName, nil) or updatedName + else + pendingTracks[destGUID] = true + end + if updatedName then + for triggerInfo in pairs(scanFuncsName[spellName]) do + if MatchesTriggerInfoMulti(triggerInfo, sourceGUID) then + ReferenceMatchDataMulti(matchDataMulti[destGUID][spellName][sourceGUID], triggerInfo.id, triggerInfo.triggernum, destGUID) + end + end + end + end + end +end + +local function HandleCombatLogRemove(scanFuncsName, scanFuncsSpellId, sourceGUID, destGUID, spellId, spellName) + if scanFuncsName and scanFuncsName[spellName] or scanFuncsSpellId and scanFuncsSpellId[spellId] then + if matchDataMulti[destGUID] then + RemoveMatchDataMulti(matchDataMulti[destGUID], destGUID, spellId, sourceGUID) + RemoveMatchDataMulti(matchDataMulti[destGUID], destGUID, spellName, sourceGUID) + end + end +end + +local function CombatLog(_, event, sourceGUID, sourceName, _, destGUID, destName, _, spellId, spellName, _, auraType, amount) + if event == "SPELL_AURA_APPLIED" or event == "SPELL_AURA_REFRESH" or event == "SPELL_AURA_APPLIED_DOSE" or event == "SPELL_AURA_REMOVED_DOSE" then + if auraType == "BUFF" then + HandleCombatLog(scanFuncNameMulti["HELPFUL"], scanFuncSpellIdMulti["HELPFUL"], "HELPFUL", event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount) + elseif auraType == "DEBUFF" then + HandleCombatLog(scanFuncNameMulti["HARMFUL"], scanFuncSpellIdMulti["HARMFUL"], "HARMFUL", event, sourceGUID, sourceName, destGUID, destName, spellId, spellName, amount) + end + elseif event == "SPELL_AURA_REMOVED" then + if auraType == "BUFF" then + HandleCombatLogRemove(scanFuncNameMulti["HELPFUL"], scanFuncSpellIdMulti["HELPFUL"], sourceGUID, destGUID, spellId, spellName) + elseif auraType == "DEBUFF" then + HandleCombatLogRemove(scanFuncNameMulti["HARMFUL"], scanFuncSpellIdMulti["HARMFUL"], sourceGUID, destGUID, spellId, spellName) + end + end +end + +local function CheckAurasMulti(base, unit, filter) + local index = 1 + while true do + local name, rank, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId = UnitAura(unit, index, filter) + if not name then + return false + end + + if debuffClass == nil then + debuffClass = "none" + elseif debuffClass == "" then + debuffClass = "enrage" + else + debuffClass = string.lower(debuffClass) + end + local auraCasterGUID = unitCaster and UnitGUID(unitCaster) + if base[name] and base[name][auraCasterGUID] then + local changed = AugmentMatchDataMultiWith(base[name][auraCasterGUID], unit, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId) + if changed then + for id, idData in pairs(base[name][auraCasterGUID].auras) do + for triggernum in pairs(idData) do + matchDataChanged[id] = matchDataChanged[id] or {} + matchDataChanged[id][triggernum] = true + end + end + end + end + if base[spellId] and base[spellId][auraCasterGUID] then + local changed = AugmentMatchDataMultiWith(base[spellId][auraCasterGUID], unit, name, icon, stacks, debuffClass, duration, expirationTime, unitCaster, isStealable, _, spellId) + if changed then + for id, idData in pairs(base[spellId][auraCasterGUID].auras) do + for triggernum in pairs(idData) do + matchDataChanged[id] = matchDataChanged[id] or {} + matchDataChanged[id][triggernum] = true + end + end + end + end + index = index + 1 + end +end + +function BuffTrigger.HandlePendingTracks(unit, GUID) + if pendingTracks[GUID] then + if matchDataMulti[GUID] then + CheckAurasMulti(matchDataMulti[GUID], unit, "HELPFUL") + CheckAurasMulti(matchDataMulti[GUID], unit, "HARMFUL") + end + end +end + +function BuffTrigger.InitMultiAura() + if not multiAuraFrame then + multiAuraFrame = CreateFrame("frame") + multiAuraFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") + multiAuraFrame:RegisterEvent("UNIT_TARGET") + multiAuraFrame:RegisterEvent("UNIT_AURA") + multiAuraFrame:RegisterEvent("PLAYER_TARGET_CHANGED") + multiAuraFrame:RegisterEvent("PLAYER_FOCUS_CHANGED") + multiAuraFrame:RegisterEvent("NAME_PLATE_UNIT_ADDED") + multiAuraFrame:RegisterEvent("NAME_PLATE_UNIT_REMOVED") + multiAuraFrame:RegisterEvent("PLAYER_LEAVING_WORLD") + multiAuraFrame:SetScript("OnEvent", BuffTrigger.HandleMultiEvent) + WeakAuras.frames["Multi-target 2 Aura Trigger Handler"] = multiAuraFrame + end +end + +function BuffTrigger.HandleMultiEvent(frame, event, ...) + WeakAuras.StartProfileSystem("bufftrigger2 - multi") + if event == "COMBAT_LOG_EVENT_UNFILTERED" then + CombatLog(...) + elseif event == "UNIT_TARGET" then + TrackUid(...) + elseif event == "PLAYER_TARGET_CHANGED" then + TrackUid("target") + elseif event == "PLAYER_FOCUS_CHANGED" then + TrackUid("focus") + elseif event == "NAME_PLATE_UNIT_ADDED" then + TrackUid(...) + elseif event == "NAME_PLATE_UNIT_REMOVED" then + local unit = ... + ReleaseUID(unit) + unit = unit.."target" + ReleaseUID(unit) + elseif event == "UNIT_AURA" then + local unit = ... + local guid = UnitGUID(unit) + if matchDataMulti[guid] then + CheckAurasMulti(matchDataMulti[guid], unit, "HELPFUL") + CheckAurasMulti(matchDataMulti[guid], unit, "HARMFUL") + end + elseif event == "PLAYER_LEAVING_WORLD" then + -- Remove everything.. + for GUID, GUIDData in pairs(matchDataMulti) do + for key in pairs(GUIDData) do + RemoveMatchDataMulti(GUIDData, GUID, key) + end + end + wipe(matchDataMulti) + end + WeakAuras.StopProfileSystem("bufftrigger2 - multi") +end + +function BuffTrigger.GetTriggerDescription(data, triggernum, namestable) + local trigger = data.triggers[triggernum].trigger + if trigger.auranames then + for index, name in pairs(trigger.auranames) do + local left = " " + if(index == 1) then + if(#trigger.auranames > 0) then + if(#trigger.auranames > 1) then + left = L["Auras:"] + else + left = L["Aura:"] + end + end + end + local icon + local spellId = WeakAuras.SafeToNumber(name) + if spellId then + icon = select(3, GetSpellInfo(spellId)) + else + icon = WeakAuras.spellCache.GetIcon(name) + end + icon = icon or "Interface\\Icons\\INV_Misc_QuestionMark" + tinsert(namestable, {left, name, icon}) + end + end + + if trigger.auraspellids then + for index, spellId in pairs(trigger.auraspellids) do + local left = " " + if index == 1 then + if #trigger.auraspellids > 0 then + if #trigger.auraspellids > 1 then + left = L["Spell IDs:"] + else + left = L["Spell ID:"] + end + end + end + + local icon = select(3, GetSpellInfo(spellId or 0)) or "Interface\\Icons\\INV_Misc_QuestionMark" + tinsert(namestable, {left, spellId, icon}) + end + end +end + +function BuffTrigger.CreateFakeStates(id, triggernum) + local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum); + local data = WeakAuras.GetData(id) + local state = {} + BuffTrigger.CreateFallbackState(data, triggernum, state) + state.expirationTime = GetTime() + 60 + state.duration = 65 + state.progressType = "timed" + state.stacks = 1 + allStates[""] = state + if BuffTrigger.CanHaveClones(data, triggernum) then + for i = 1, 2 do + local state = {} + BuffTrigger.CreateFallbackState(data, triggernum, state) + state.expirationTime = GetTime() + 60 + i * 20 + state.duration = 100 + state.progressType = "timed" + state.stacks = 1 + allStates[i] = state + end + end +end + +WeakAuras.RegisterTriggerSystem({"aura2"}, BuffTrigger) diff --git a/WeakAuras/CHANGELOG.md b/WeakAuras/CHANGELOG.md new file mode 100644 index 0000000..ce88325 --- /dev/null +++ b/WeakAuras/CHANGELOG.md @@ -0,0 +1,16 @@ +# [2.17.4](https://github.com/WeakAuras/WeakAuras2/tree/2.17.4) (2020-04-22) + +[Full Changelog](https://github.com/WeakAuras/WeakAuras2/compare/2.17.3...2.17.4) + +## Highlights + + - fix a buff tracking and nameplates regression + +## Commits + +InfusOnWoW (3): + +- BT2 Fix Multi by adjusting it to recent changes (#2139) +- Clean up match data if a unit ceases to exists +- Fix nameplates auras sometimes not being applied if in a raid group + diff --git a/WeakAuras/DefaultOptions.lua b/WeakAuras/DefaultOptions.lua new file mode 100644 index 0000000..9008654 --- /dev/null +++ b/WeakAuras/DefaultOptions.lua @@ -0,0 +1,4 @@ +--[[ Manual override for the default font and font size until proper options are built ]] + +WeakAuras.defaultFont = "Friz Quadrata TT" +WeakAuras.defaultFontSize = 12 \ No newline at end of file diff --git a/WeakAuras/GenericTrigger.lua b/WeakAuras/GenericTrigger.lua new file mode 100644 index 0000000..bde7cd3 --- /dev/null +++ b/WeakAuras/GenericTrigger.lua @@ -0,0 +1,3765 @@ +--[[ GenericTrigger.lua +This file contains the generic trigger system. That is every trigger except the aura triggers. + +It registers the GenericTrigger table for the trigger types "status", "event" and "custom" and has the following API: + +Add(data) +Adds a display, creating all internal data structures for all triggers. + +Delete(id) +Deletes all triggers for display id. + +Rename(oldid, newid) +Updates all trigger information from oldid to newid. + +LoadDisplay(id) +Loads all triggers of display id. + +UnloadAll +Unloads all triggers. + +UnloadDisplays(id) +Unloads all triggers of the display ids. + +ScanAll +Resets the trigger state for all triggers. + +Modernize(data) +Modernizes all generic triggers in data. + +##################################################### +# Helper functions mainly for the WeakAuras Options # +##################################################### + +CanHaveDuration(data, triggernum) +Returns whether the trigger can have a duration. + +GetOverlayInfo(data, triggernum) +Returns a table containing the names of all overlays + +CanHaveAuto(data, triggernum) +Returns whether the icon can be automatically selected. + +CanHaveClones(data) +Returns whether the trigger can have clones. + +CanHaveTooltip(data, triggernum) +Returns the type of tooltip to show for the trigger. + +GetNameAndIcon(data, triggernum) +Returns the name and icon to show in the options. + +GetAdditionalProperties(data, triggernum) +Returns the a tooltip for the additional properties. + +GetTriggerConditions(data, triggernum) +Returns potential conditions that this trigger provides. +]]-- +if not WeakAuras.IsCorrectVersion() then return end + +-- Lua APIs +local tinsert, tconcat, wipe = table.insert, table.concat, wipe +local tostring, pairs, type = tostring, pairs, type +local error, setmetatable = error, setmetatable + +-- WoW APIs +local IsPlayerMoving = IsPlayerMoving + +WeakAurasAceEvents = setmetatable({}, {__tostring=function() return "WeakAuras" end}); +LibStub("AceEvent-3.0"):Embed(WeakAurasAceEvents); +local aceEvents = WeakAurasAceEvents + +local WeakAuras = WeakAuras; +local L = WeakAuras.L; +local GenericTrigger = {}; + +local event_prototypes = WeakAuras.event_prototypes; + +local timer = WeakAuras.timer; +local debug = WeakAuras.debug; + +local events = WeakAuras.events; +local loaded_events = WeakAuras.loaded_events; +local loaded_unit_events = {}; +local loaded_auras = {}; -- id to bool map +local timers = WeakAuras.timers; +local specificBosses = WeakAuras.specificBosses; + +-- Local functions +local LoadEvent, HandleEvent, HandleUnitEvent, TestForTriState, TestForToggle, TestForLongString, TestForMultiSelect +local ConstructTest, ConstructFunction + +function WeakAuras.UnitExistsFixed(unit, smart) + if smart and IsInRaid() then + if unit:sub(1, 5) == "party" or unit == "player" then + return false + end + end + return UnitExists(unit) +end + +function WeakAuras.split(input) + input = input or ""; + local ret = {}; + local split, element = true; + split = input:find("[,%s]"); + while(split) do + element, input = input:sub(1, split-1), input:sub(split+1); + if(element ~= "") then + tinsert(ret, element); + end + split = input:find("[,%s]"); + end + if(input ~= "") then + tinsert(ret, input); + end + return ret; +end + +function TestForTriState(trigger, arg) + local name = arg.name; + local test; + if(trigger["use_"..name] == false) then + test = "(not "..name..")"; + elseif(trigger["use_"..name]) then + if(arg.test) then + test = "("..arg.test:format(trigger[name])..")"; + else + test = name; + end + end + return test; +end + +function TestForToggle(trigger, arg) + local name = arg.name; + local test; + if(trigger["use_"..name]) then + if(arg.test) then + test = "("..arg.test:format(trigger[name])..")"; + else + test = name; + end + end + return test; +end + +function TestForLongString(trigger, arg) + local name = arg.name; + local test; + if(trigger[name.."_operator"] == "==") then + test = "("..name.."==\""..trigger[name].."\")"; + else + test = "("..name.." and "..name..":"..trigger[name.."_operator"]:format(trigger[name])..")"; + end + return test; +end + +function TestForMultiSelect(trigger, arg) + local name = arg.name; + local test; + if(trigger["use_"..name] == false) then -- multi selection + test = "("; + local any = false; + if trigger[name] and trigger[name].multi then + for value, _ in pairs(trigger[name].multi) do + if not arg.test then + test = test..name.."=="..(tonumber(value) or "[["..value.."]]").." or "; + else + test = test..arg.test:format(tonumber(value) or "[["..value.."]]").." or "; + end + any = true; + end + end + if(any) then + test = test:sub(1, -5); + else + test = "(false"; + end + test = test..")"; + elseif(trigger["use_"..name]) then -- single selection + local value = trigger[name] and trigger[name].single; + if (not value) then + test = "false"; + return test; + end + if not arg.test then + test = trigger[name].single and "("..name.."=="..(tonumber(value) or "[["..value.."]]")..")"; + else + test = trigger[name].single and "("..arg.test:format(tonumber(value) or "[["..value.."]]")..")"; + end + end + return test; +end + +function ConstructTest(trigger, arg) + local test; + local name = arg.name; + if(arg.hidden or arg.type == "tristate" or arg.type == "toggle" or (arg.type == "multiselect" and trigger["use_"..name] ~= nil) or ((trigger["use_"..name] or arg.required) and trigger[name])) then + local number = tonumber(trigger[name]); + if(arg.type == "tristate") then + test = TestForTriState(trigger, arg); + elseif(arg.type == "multiselect") then + test = TestForMultiSelect(trigger, arg); + elseif(arg.type == "toggle") then + test = TestForToggle(trigger, arg); + elseif(arg.test) then + test = "("..arg.test:format(trigger[name])..")"; + elseif(arg.type == "longstring" and trigger[name.."_operator"]) then + test = TestForLongString(trigger, arg); + elseif (arg.type == "string" or arg.type == "select" or arg.type == "spell" or arg.type == "item") then + test = "(".. name .." and "..name.."==" ..(number or "\""..(trigger[name] or "").."\"")..")"; + else + if(type(trigger[name]) == "table") then + trigger[name] = "error"; + end + -- number + test = "(".. name .." and "..name..(trigger[name.."_operator"] or "==")..(number or "\""..(trigger[name] or "").."\"")..")"; + end + end + + if (test == "(true)") then + return nil; + end + + return test; +end + +function ConstructFunction(prototype, trigger, inverse) + if (prototype.triggerFunction) then + return prototype.triggerFunction(trigger); + end + + if (inverse and prototype.automaticrequired) then + return "return function() return true end" + end + + local input; + if (prototype.statesParameter) then + input = {"state", "event"}; + else + input = {"event"}; + end + + local required = {}; + local tests = {}; + local debug = {}; + local store = {}; + local init; + if(prototype.init) then + init = prototype.init(trigger); + else + init = ""; + end + for index, arg in pairs(prototype.args) do + local enable = arg.type ~= "description"; + if(type(arg.enable) == "function") then + enable = arg.enable(trigger); + elseif type(arg.enable) == "boolean" then + enable = arg.enable + end + if(enable) then + local name = arg.name; + if not(arg.name or arg.hidden) then + tinsert(input, "_"); + else + if(arg.init == "arg") then + tinsert(input, name); + elseif(arg.init) then + init = init.."local "..name.." = "..arg.init.."\n"; + end + if (arg.store) then + tinsert(store, name); + end + local test = ConstructTest(trigger, arg); + if (test) then + if(arg.required) then + tinsert(required, test); + else + tinsert(tests, test); + end + if(arg.debug) then + tinsert(debug, arg.debug:format(trigger[name])); + end + end + end + end + end + local ret = "return function("..tconcat(input, ", ")..")\n"; + ret = ret..(init or ""); + + ret = ret..(#debug > 0 and tconcat(debug, "\n") or ""); + + ret = ret.."if("; + ret = ret..((#required > 0) and tconcat(required, " and ").." and " or ""); + if(inverse) then + ret = ret.."not ("..(#tests > 0 and tconcat(tests, " and ") or "true")..")"; + else + ret = ret..(#tests > 0 and tconcat(tests, " and ") or "true"); + end + ret = ret..") then\n"; + if(#debug > 0) then + ret = ret.."print('ret: true');\n"; + end + + if (not inverse) then + if (prototype.statesParameter == "all") then + ret = ret .. " state[cloneId] = state[cloneId] or {}\n" + ret = ret .. " state = state[cloneId]\n" + ret = ret .. " state.changed = true\n" + end + + for _, v in ipairs(store) do + ret = ret .. " if (state." .. v .. " ~= " .. v .. ") then\n" + ret = ret .. " state." .. v .. " = " .. v .. "\n" + ret = ret .. " state.changed = true\n" + ret = ret .. " end\n" + end + end + ret = ret.."return true else return false end end"; + + return ret; +end + +function WeakAuras.EndEvent(id, triggernum, force, state) + if state then + if (state.show ~= false and state.show ~= nil) then + state.show = false; + state.changed = true; + end + return state.changed; + else + return false + end +end + +local function RunOverlayFuncs(event, state) + state.additionalProgress = state.additionalProgress or {}; + local changed = false; + for i, overlayFunc in ipairs(event.overlayFuncs) do + state.additionalProgress[i] = state.additionalProgress[i] or {}; + local additionalProgress = state.additionalProgress[i]; + local a, b, c = overlayFunc(event.trigger, state); + if (not a) then + additionalProgress.min = nil; + additionalProgress.max = nil; + additionalProgress.direction = nil; + additionalProgress.width = nil; + additionalProgress.offset = nil; + elseif (type(a) == "string") then + if (additionalProgress.direction ~= a) then + additionalProgress.direction = a; + changed = true; + end + if (additionalProgress.width ~= b) then + additionalProgress.width = b; + changed = true; + end + if (additionalProgress.offset ~= c) then + additionalProgress.offset = c; + changed = true; + end + additionalProgress.min = nil; + additionalProgress.max = nil; + else + if (additionalProgress.min ~= a) then + additionalProgress.min = a; + changed = true; + end + if (additionalProgress.max ~= b) then + additionalProgress.max = b; + changed = true; + end + if additionalProgress.direction then + changed = true + end + additionalProgress.direction = nil; + additionalProgress.width = nil; + additionalProgress.offset = nil; + end + + end + state.changed = changed or state.changed; +end + +local function callFunctionForActivateEvent(func, trigger, fallback, errorHandler) + if not func then + return fallback + end + local value = func(trigger) + return value or fallback +end + +function WeakAuras.ActivateEvent(id, triggernum, data, state, errorHandler) + local changed = state.changed or false; + if (state.show ~= true) then + state.show = true; + changed = true; + end + if (data.duration) then + local expirationTime = GetTime() + data.duration; + if (state.expirationTime ~= expirationTime) then + state.expirationTime = expirationTime; + changed = true; + end + if (state.duration ~= data.duration) then + state.duration = data.duration; + changed = true; + end + if (state.progressType ~= "timed") then + state.progressType = "timed"; + changed = true; + end + local autoHide = data.automaticAutoHide; + if (state.value or state.total or state.inverse or state.autoHide ~= autoHide) then + changed = true; + end + state.value = nil; + state.total = nil; + state.inverse = nil; + state.autoHide = autoHide; + elseif (data.durationFunc) then + local arg1, arg2, arg3, inverse = data.durationFunc(data.trigger); + arg1 = type(arg1) == "number" and arg1 or 0; + arg2 = type(arg2) == "number" and arg2 or 0; + + if (state.inverse ~= inverse) then + state.inverse = inverse; + changed = true; + end + + if (arg3) then + if (state.progressType ~= "static") then + state.progressType = "static"; + changed = true; + end + if (state.duration) then + state.duration = nil; + changed = true; + end + if (state.expirationTime) then + state.expirationTime = nil; + changed = true; + end + + local autoHide = nil; + if (state.autoHide ~= autoHide) then + changed = true; + state.autoHide = autoHide; + end + + if (state.value ~= arg1) then + state.value = arg1; + changed = true; + end + if (state.total ~= arg2) then + state.total = arg2; + changed = true; + end + else + if (state.progressType ~= "timed") then + state.progressType = "timed"; + changed = true; + end + if (state.duration ~= arg1) then + state.duration = arg1; + end + if (state.expirationTime ~= arg2) then + state.expirationTime = arg2; + changed = true; + end + local autoHide = data.automaticAutoHide and arg1 > 0.01; + if (state.autoHide ~= autoHide) then + changed = true; + state.autoHide = autoHide; + end + if (state.value or state.total) then + changed = true; + end + state.value = nil; + state.total = nil; + end + end + + local name = callFunctionForActivateEvent(data.nameFunc, data.trigger, state.name, errorHandler) + local icon = callFunctionForActivateEvent(data.iconFunc, data.trigger, state.icon, errorHandler) + local texture = callFunctionForActivateEvent(data.textureFunc, data.trigger, state.texture, errorHandler) + local stacks = callFunctionForActivateEvent(data.stacksFunc, data.trigger, state.stacks, errorHandler) + + if (state.name ~= name) then + state.name = name; + changed = true; + end + if (state.icon ~= icon) then + state.icon = icon; + changed = true; + end + if (state.texture ~= texture) then + state.texture = texture; + changed = true; + end + if (state.stacks ~= stacks) then + state.stacks = stacks; + changed = true; + end + + if (data.overlayFuncs) then + RunOverlayFuncs(data, state); + else + state.additionalProgress = nil; + end + + state.changed = state.changed or changed; + + return state.changed; +end + +local function ignoreErrorHandler() + +end + +local function RunTriggerFunc(allStates, data, id, triggernum, event, arg1, arg2, ...) + local optionsEvent = event == "OPTIONS"; + local errorHandler = optionsEvent and ignoreErrorHandler or geterrorhandler() + local updateTriggerState = false; + if(data.triggerFunc) then + local untriggerCheck = false; + if (data.statesParameter == "full") then + local returnValue = data.triggerFunc(allStates, event, arg1, arg2, ...); + if (returnValue) then + updateTriggerState = true; + end + elseif (data.statesParameter == "all") then + local returnValue = data.triggerFunc(allStates, event, arg1, arg2, ...); + if( (returnValue) or optionsEvent) then + for id, state in pairs(allStates) do + if (state.changed) then + if (WeakAuras.ActivateEvent(id, triggernum, data, state, errorHandler)) then + updateTriggerState = true; + end + end + end + else + untriggerCheck = true; + end + elseif (data.statesParameter == "unit") then + if arg1 then + local unit, cloneId + if WeakAuras.multiUnitUnits[data.trigger.unit] then + unit = arg1 + cloneId = arg1 + else + unit = data.trigger.unit + cloneId = "" + end + allStates[cloneId] = allStates[cloneId] or {}; + local state = allStates[cloneId]; + local returnValue = data.triggerFunc(state, event, unit, arg1, arg2, ...); + if returnValue or optionsEvent then + if(WeakAuras.ActivateEvent(id, triggernum, data, state, errorHandler)) then + updateTriggerState = true; + end + else + untriggerCheck = true; + end + end + elseif (data.statesParameter == "one") then + allStates[""] = allStates[""] or {}; + local state = allStates[""]; + local returnValue = data.triggerFunc(state, event, arg1, arg2, ...); + if returnValue or optionsEvent then + if(WeakAuras.ActivateEvent(id, triggernum, data, state, errorHandler)) then + updateTriggerState = true; + end + else + untriggerCheck = true; + end + else + local returnValue = data.triggerFunc(event, arg1, arg2, ...); + if returnValue or optionsEvent then + allStates[""] = allStates[""] or {}; + local state = allStates[""]; + if(WeakAuras.ActivateEvent(id, triggernum, data, state, errorHandler)) then + updateTriggerState = true; + end + else + untriggerCheck = true; + end + end + if (untriggerCheck and not optionsEvent) then + if (data.statesParameter == "all") then + if data.untriggerFunc then + local returnValue = data.untriggerFunc(allStates, event, arg1, arg2, ...); + if returnValue then + for id, state in pairs(allStates) do + if (state.changed) then + if (WeakAuras.EndEvent(id, triggernum, nil, state)) then + updateTriggerState = true; + end + end + end + end + end + elseif data.statesParameter == "unit" then + if data.untriggerFunc then + if arg1 then + local cloneId = WeakAuras.multiUnitUnits[data.trigger.unit] and arg1 or "" + local state = allStates[cloneId] + if state then + local returnValue = data.untriggerFunc(state, event, arg1, arg2, ...); + if returnValue then + if (WeakAuras.EndEvent(id, triggernum, nil, state)) then + updateTriggerState = true; + end + end + end + end + end + elseif (data.statesParameter == "one") then + allStates[""] = allStates[""] or {}; + local state = allStates[""]; + if data.untriggerFunc then + local returnValue = data.untriggerFunc(state, event, arg1, arg2, ...); + if returnValue then + if (WeakAuras.EndEvent(id, triggernum, nil, state)) then + updateTriggerState = true; + end + end + end + else + if data.untriggerFunc then + local returnValue = data.untriggerFunc(event, arg1, arg2, ...); + if returnValue then + allStates[""] = allStates[""] or {}; + local state = allStates[""]; + if(WeakAuras.EndEvent(id, triggernum, nil, state)) then + updateTriggerState = true; + end + end + end + end + end + end + return updateTriggerState; +end + +function WeakAuras.ScanEvents(event, arg1, arg2, ...) + local orgEvent = event; + WeakAuras.StartProfileSystem("generictrigger " .. orgEvent ) + local event_list = loaded_events[event]; + if (not event_list) then + WeakAuras.StopProfileSystem("generictrigger " .. orgEvent ) + return + end + if(event == "COMBAT_LOG_EVENT_UNFILTERED") then + event_list = event_list[arg2]; + if (not event_list) then + WeakAuras.StopProfileSystem("generictrigger " .. orgEvent ) + return; + end + WeakAuras.ScanEventsInternal(event_list, event, arg1, arg2, ...); + + elseif (event == "COMBAT_LOG_EVENT_UNFILTERED_CUSTOM") then + -- This reverts the COMBAT_LOG_EVENT_UNFILTERED_CUSTOM workaround so that custom triggers that check the event argument will work as expected + if(event == "COMBAT_LOG_EVENT_UNFILTERED_CUSTOM") then + event = "COMBAT_LOG_EVENT_UNFILTERED"; + end + WeakAuras.ScanEventsInternal(event_list, event, arg1, arg2, ...); + else + WeakAuras.ScanEventsInternal(event_list, event, arg1, arg2, ...); + end + WeakAuras.StopProfileSystem("generictrigger " .. orgEvent ) +end + +function WeakAuras.ScanUnitEvents(event, unit, ...) + WeakAuras.StartProfileSystem("generictrigger " .. event .. " " .. unit) + local unit_list = loaded_unit_events[unit] + if unit_list then + local event_list = unit_list[event] + if event_list then + for id, triggers in pairs(event_list) do + WeakAuras.StartProfileAura(id); + WeakAuras.ActivateAuraEnvironment(id); + local updateTriggerState = false; + for triggernum, data in pairs(triggers) do + local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum); + if (RunTriggerFunc(allStates, data, id, triggernum, event, unit, ...)) then + updateTriggerState = true; + end + end + if (updateTriggerState) then + WeakAuras.UpdatedTriggerState(id); + end + WeakAuras.StopProfileAura(id); + WeakAuras.ActivateAuraEnvironment(nil); + end + end + end + WeakAuras.StopProfileSystem("generictrigger " .. event .. " " .. unit) +end + +function WeakAuras.ScanEventsInternal(event_list, event, arg1, arg2, ... ) + for id, triggers in pairs(event_list) do + WeakAuras.StartProfileAura(id); + WeakAuras.ActivateAuraEnvironment(id); + local updateTriggerState = false; + for triggernum, data in pairs(triggers) do + local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum); + if (RunTriggerFunc(allStates, data, id, triggernum, event, arg1, arg2, ...)) then + updateTriggerState = true; + end + end + if (updateTriggerState) then + WeakAuras.UpdatedTriggerState(id); + end + WeakAuras.StopProfileAura(id); + WeakAuras.ActivateAuraEnvironment(nil); + end +end + +local function AddFakeTime(state) + if state.progressType == "timed" then + if state.expirationTime and state.expirationTime ~= math.huge and state.expirationTime > GetTime() then + return + end + state.progressType = "timed" + state.expirationTime = GetTime() + 7 + state.duration = 7 + end +end + +function GenericTrigger.CreateFakeStates(id, triggernum) + local data = WeakAuras.GetData(id) + + WeakAuras.ActivateAuraEnvironment(id); + local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum); + RunTriggerFunc(allStates, events[id][triggernum], id, triggernum, "OPTIONS") + + local shown = 0 + for id, state in pairs(allStates) do + if state.show then + shown = shown + 1 + end + state.autoHide = false + AddFakeTime(state) + end + + if shown == 0 then + local state = {} + GenericTrigger.CreateFallbackState(data, triggernum, state) + allStates[""] = state + state.autoHide = false + AddFakeTime(state) + end + + WeakAuras.ActivateAuraEnvironment(nil); +end + +function GenericTrigger.ScanWithFakeEvent(id, fake) + local updateTriggerState = false; + WeakAuras.ActivateAuraEnvironment(id); + for triggernum, event in pairs(events[id] or {}) do + local allStates = WeakAuras.GetTriggerStateForTrigger(id, triggernum); + if (event.force_events) then + if (type(event.force_events) == "string") then + updateTriggerState = RunTriggerFunc(allStates, events[id][triggernum], id, triggernum, event.force_events) or updateTriggerState; + elseif (type(event.force_events) == "table") then + for index, event_args in pairs(event.force_events) do + updateTriggerState = RunTriggerFunc(allStates, events[id][triggernum], id, triggernum, unpack(event_args)) or updateTriggerState; + end + elseif (type(event.force_events) == "boolean" and event.force_events) then + for i, eventName in pairs(event.events) do + if eventName == "COMBAT_LOG_EVENT_UNFILTERED_CUSTOM" then + eventName = "COMBAT_LOG_EVENT_UNFILTERED" + end + updateTriggerState = RunTriggerFunc(allStates, events[id][triggernum], id, triggernum, eventName) or updateTriggerState; + end + end + end + end + + if (updateTriggerState) then + WeakAuras.UpdatedTriggerState(id); + end + WeakAuras.ActivateAuraEnvironment(nil); +end + +function GenericTrigger.ScanAll() + for id, _ in pairs(loaded_auras) do + GenericTrigger.ScanWithFakeEvent(id); + end +end + +function HandleEvent(frame, event, arg1, arg2, ...) + WeakAuras.StartProfileSystem("generictrigger " .. event); + if not(WeakAuras.IsPaused()) then + if(event == "COMBAT_LOG_EVENT_UNFILTERED") then + WeakAuras.ScanEvents(event, arg1, arg2, ...); + -- This triggers the scanning of "hacked" COMBAT_LOG_EVENT_UNFILTERED events that were renamed in order to circumvent + -- the "proper" COMBAT_LOG_EVENT_UNFILTERED checks + WeakAuras.ScanEvents("COMBAT_LOG_EVENT_UNFILTERED_CUSTOM", arg1, arg2, ...); + else + WeakAuras.ScanEvents(event, arg1, arg2, ...); + end + end + if (event == "PLAYER_ENTERING_WORLD") then + timer:ScheduleTimer(function() + WeakAuras.StartProfileSystem("generictrigger WA_DELAYED_PLAYER_ENTERING_WORLD"); + HandleEvent(frame, "WA_DELAYED_PLAYER_ENTERING_WORLD"); + WeakAuras.CheckCooldownReady(); + WeakAuras.StopProfileSystem("generictrigger WA_DELAYED_PLAYER_ENTERING_WORLD"); + WeakAuras.PreShowModels() + end, + 0.8); -- Data not available + + timer:ScheduleTimer(function() + WeakAuras.PreShowModels() + end, + 4); -- Data not available + end + WeakAuras.StopProfileSystem("generictrigger " .. event); +end + +function HandleUnitEvent(frame, event, unit, ...) + if frame.unit ~= unit then return end + WeakAuras.StartProfileSystem("generictrigger " .. event .. " " .. unit); + if not(WeakAuras.IsPaused()) then + if (UnitIsUnit(unit, frame.unit)) then + WeakAuras.ScanUnitEvents(event, unit, ...); + end + end + WeakAuras.StopProfileSystem("generictrigger " .. event .. " " .. unit); +end + +function GenericTrigger.UnloadAll() + wipe(loaded_auras); + wipe(loaded_events); + wipe(loaded_unit_events); + WeakAuras.UnregisterAllEveryFrameUpdate(); +end + +function GenericTrigger.UnloadDisplays(toUnload) + for id in pairs(toUnload) do + loaded_auras[id] = false; + for eventname, events in pairs(loaded_events) do + if(eventname == "COMBAT_LOG_EVENT_UNFILTERED") then + for subeventname, subevents in pairs(events) do + subevents[id] = nil; + end + else + events[id] = nil; + end + end + for unit, events in pairs(loaded_unit_events) do + for eventname, auras in pairs(events) do + auras[id] = nil; + end + end + WeakAuras.UnregisterEveryFrameUpdate(id); + end +end + +local genericTriggerRegisteredEvents = {}; +local genericTriggerRegisteredUnitEvents = {}; +local frame = CreateFrame("FRAME"); +frame.unitFrames = {}; +WeakAuras.frames["WeakAuras Generic Trigger Frame"] = frame; +frame:RegisterEvent("PLAYER_ENTERING_WORLD"); +genericTriggerRegisteredEvents["PLAYER_ENTERING_WORLD"] = true; +frame:SetScript("OnEvent", HandleEvent); + +function GenericTrigger.Delete(id) + GenericTrigger.UnloadDisplays({[id] = true}); +end + +function GenericTrigger.Rename(oldid, newid) + events[newid] = events[oldid]; + events[oldid] = nil; + + for eventname, events in pairs(loaded_events) do + if(eventname == "COMBAT_LOG_EVENT_UNFILTERED") then + for subeventname, subevents in pairs(events) do + subevents[oldid] = subevents[newid]; + subevents[oldid] = nil; + end + else + events[newid] = events[oldid]; + events[oldid] = nil; + end + end + + for unit, events in pairs(loaded_unit_events) do + for eventname, auras in pairs(events) do + auras[newid] = auras[oldid] + auras[oldid] = nil + end + end + + WeakAuras.EveryFrameUpdateRename(oldid, newid) +end + +local function MultiUnitLoop(Func, unit, ...) + unit = string.lower(unit) + if unit == "boss" or unit == "arena" then + for i = 1, 5 do + Func(unit..i, ...) + end + elseif unit == "group" then + Func("player", ...) + for i = 1, 4 do + Func("party"..i, ...) + end + for i = 1, 40 do + Func("raid"..i, ...) + end + elseif unit == "party" then + Func("player", ...) + for i = 1, 4 do + Func("party"..i, ...) + end + elseif unit == "raid" then + for i = 1, 40 do + Func("raid"..i, ...) + end + else + Func(unit, ...) + end +end + +function LoadEvent(id, triggernum, data) + if data.events then + for index, event in pairs(data.events) do + loaded_events[event] = loaded_events[event] or {}; + if(event == "COMBAT_LOG_EVENT_UNFILTERED" and data.subevents) then + for i, subevent in pairs(data.subevents) do + loaded_events[event][subevent] = loaded_events[event][subevent] or {}; + loaded_events[event][subevent][id] = loaded_events[event][subevent][id] or {} + loaded_events[event][subevent][id][triggernum] = data; + end + else + loaded_events[event][id] = loaded_events[event][id] or {}; + loaded_events[event][id][triggernum] = data; + end + end + end + if (data.internal_events) then + for index, event in pairs(data.internal_events) do + loaded_events[event] = loaded_events[event] or {}; + loaded_events[event][id] = loaded_events[event][id] or {}; + loaded_events[event][id][triggernum] = data; + end + end + if data.unit_events then + for unit, events in pairs(data.unit_events) do + unit = string.lower(unit) + for index, event in pairs(events) do + MultiUnitLoop( + function(unit) + loaded_unit_events[unit] = loaded_unit_events[unit] or {}; + loaded_unit_events[unit][event] = loaded_unit_events[unit][event] or {}; + loaded_unit_events[unit][event][id] = loaded_unit_events[unit][event][id] or {} + loaded_unit_events[unit][event][id][triggernum] = data; + end, unit + ) + end + end + end + + if (data.loadFunc) then + data.loadFunc(data.trigger); + end +end + +local function trueFunction() + return true; +end + +local eventsToRegister = {}; +local unitEventsToRegister = {}; +function GenericTrigger.LoadDisplays(toLoad, loadEvent, ...) + for id in pairs(toLoad) do + local register_for_frame_updates = false; + if(events[id]) then + loaded_auras[id] = true; + for triggernum, data in pairs(events[id]) do + if data.events then + for index, event in pairs(data.events) do + if (event == "COMBAT_LOG_EVENT_UNFILTERED_CUSTOM") then + if not genericTriggerRegisteredEvents["COMBAT_LOG_EVENT_UNFILTERED"] then + eventsToRegister["COMBAT_LOG_EVENT_UNFILTERED"] = true; + end + elseif (event == "FRAME_UPDATE") then + register_for_frame_updates = true; + else + if (genericTriggerRegisteredEvents[event]) then + -- Already registered event + else + eventsToRegister[event] = true; + end + end + end + end + if data.unit_events then + for unit, events in pairs(data.unit_events) do + for index, event in pairs(events) do + MultiUnitLoop( + function (unit) + if not (genericTriggerRegisteredUnitEvents[unit] and genericTriggerRegisteredUnitEvents[unit][event]) then + unitEventsToRegister[unit] = unitEventsToRegister[unit] or {} + unitEventsToRegister[unit][event] = true + end + end, unit + ) + end + end + end + + LoadEvent(id, triggernum, data); + end + end + + if(register_for_frame_updates) then + WeakAuras.RegisterEveryFrameUpdate(id); + else + WeakAuras.UnregisterEveryFrameUpdate(id); + end + end + + for event in pairs(eventsToRegister) do + frame:RegisterEvent(event) + genericTriggerRegisteredEvents[event] = true; + end + + for unit, events in pairs(unitEventsToRegister) do + for event in pairs(events) do + if not frame.unitFrames[unit] then + frame.unitFrames[unit] = CreateFrame("FRAME") + frame.unitFrames[unit].unit = unit + frame.unitFrames[unit]:SetScript("OnEvent", HandleUnitEvent); + end + frame.unitFrames[unit]:RegisterEvent(event) + genericTriggerRegisteredUnitEvents[unit] = genericTriggerRegisteredUnitEvents[unit] or {}; + genericTriggerRegisteredUnitEvents[unit][event] = true; + end + end + + for id in pairs(toLoad) do + GenericTrigger.ScanWithFakeEvent(id); + end + + -- Replay events that lead to loading, if we weren't already registered for them + if (eventsToRegister[loadEvent]) then + WeakAuras.ScanEvents(loadEvent, ...); + end + local loadUnit = ... + if loadUnit and unitEventsToRegister[loadUnit] and unitEventsToRegister[loadUnit][loadEvent] then + WeakAuras.ScanUnitEvents(loadEvent, ...); + end + + wipe(eventsToRegister); + wipe(unitEventsToRegister); +end + +function GenericTrigger.FinishLoadUnload() +end + +--- Adds a display, creating all internal data structures for all triggers. +-- @param data +-- @param region +function GenericTrigger.Add(data, region) + local id = data.id; + events[id] = nil; + WeakAuras.forceable_events[id] = {}; + + for triggernum, triggerData in ipairs(data.triggers) do + local trigger, untrigger = triggerData.trigger, triggerData.untrigger + local triggerType; + if(trigger and type(trigger) == "table") then + triggerType = trigger.type; + if(triggerType == "status" or triggerType == "event" or triggerType == "custom") then + local triggerFuncStr, triggerFunc, untriggerFuncStr, untriggerFunc, statesParameter; + local trigger_events = {}; + local internal_events = {}; + local trigger_unit_events = {}; + local trigger_subevents = {}; + local force_events = false; + local durationFunc, overlayFuncs, nameFunc, iconFunc, textureFunc, stacksFunc, loadFunc; + local tsuConditionVariables; + if(triggerType == "status" or triggerType == "event") then + if not(trigger.event) then + error("Improper arguments to WeakAuras.Add - trigger type is \"event\" but event is not defined"); + elseif not(event_prototypes[trigger.event]) then + if(event_prototypes["Health"]) then + trigger.event = "Health"; + else + error("Improper arguments to WeakAuras.Add - no event prototype can be found for event type \""..trigger.event.."\" and default prototype reset failed."); + end + else + if (trigger.event == "Combat Log") then + if (not trigger.subeventPrefix) then + trigger.subeventPrefix = "" + end + if (not trigger.subeventSuffix) then + trigger.subeventSuffix = ""; + end + if not(WeakAuras.subevent_actual_prefix_types[trigger.subeventPrefix]) then + trigger.subeventSuffix = ""; + end + end + + triggerFuncStr = ConstructFunction(event_prototypes[trigger.event], trigger); + + statesParameter = event_prototypes[trigger.event].statesParameter; + WeakAuras.debug(id.." - "..triggernum.." - Trigger", 1); + WeakAuras.debug(triggerFuncStr); + triggerFunc = WeakAuras.LoadFunction(triggerFuncStr, id); + + durationFunc = event_prototypes[trigger.event].durationFunc; + nameFunc = event_prototypes[trigger.event].nameFunc; + iconFunc = event_prototypes[trigger.event].iconFunc; + textureFunc = event_prototypes[trigger.event].textureFunc; + stacksFunc = event_prototypes[trigger.event].stacksFunc; + loadFunc = event_prototypes[trigger.event].loadFunc; + + if (event_prototypes[trigger.event].overlayFuncs) then + overlayFuncs = {}; + local dest = 1; + for i, v in ipairs(event_prototypes[trigger.event].overlayFuncs) do + if (v.enable(trigger)) then + overlayFuncs[dest] = v.func; + dest = dest + 1; + end + end + end + + if (event_prototypes[trigger.event].automaticrequired) then + trigger.unevent = "auto"; + elseif event_prototypes[trigger.event].timedrequired then + if type(event_prototypes[trigger.event].timedrequired) == "function" then + if event_prototypes[trigger.event].timedrequired(trigger) then + trigger.unevent = "timed" + else + if not(WeakAuras.eventend_types[trigger.unevent]) then + trigger.unevent = "timed" + end + end + else + trigger.unevent = "timed" + end + elseif event_prototypes[trigger.event].automatic then + if not(WeakAuras.autoeventend_types[trigger.unevent]) then + trigger.unevent = "auto" + end + else + if not(WeakAuras.eventend_types[trigger.unevent]) then + trigger.unevent = "timed" + end + end + trigger.duration = trigger.duration or "1" + + if(trigger.unevent == "custom") then + untriggerFuncStr = ConstructFunction(event_prototypes[trigger.event], untrigger); + elseif(trigger.unevent == "auto") then + untriggerFuncStr = ConstructFunction(event_prototypes[trigger.event], trigger, true); + end + + if(untriggerFuncStr) then + WeakAuras.debug(id.." - "..triggernum.." - Untrigger", 1) + WeakAuras.debug(untriggerFuncStr); + untriggerFunc = WeakAuras.LoadFunction(untriggerFuncStr, id); + end + + local prototype = event_prototypes[trigger.event]; + if(prototype) then + local trigger_all_events = prototype.events; + internal_events = prototype.internal_events; + force_events = prototype.force_events; + if prototype.subevents then + trigger_subevents = prototype.subevents + if trigger_subevents and type(trigger_subevents) == "function" then + trigger_subevents = trigger_subevents(trigger, untrigger) + end + end + if (type(trigger_all_events) == "function") then + trigger_all_events = trigger_all_events(trigger, untrigger); + end + trigger_events = trigger_all_events.events + trigger_unit_events = trigger_all_events.unit_events + if (type(internal_events) == "function") then + internal_events = internal_events(trigger, untrigger); + end + if (type(force_events) == "function") then + force_events = force_events(trigger, untrigger) + end + end + end + else + triggerFunc = WeakAuras.LoadFunction("return "..(trigger.custom or ""), id); + if (trigger.custom_type == "stateupdate") then + tsuConditionVariables = WeakAuras.LoadFunction("return function() return \n" .. (trigger.customVariables or "") .. "\n end"); + if not tsuConditionVariables then + tsuConditionVariables = function() return end + end + end + + if(trigger.custom_type == "status" or trigger.custom_type == "event" and trigger.custom_hide == "custom") then + untriggerFunc = WeakAuras.LoadFunction("return "..(untrigger.custom or ""), id); + if (not untriggerFunc) then + untriggerFunc = trueFunction; + end + end + + if(trigger.custom_type ~= "stateupdate" and trigger.customDuration and trigger.customDuration ~= "") then + durationFunc = WeakAuras.LoadFunction("return "..trigger.customDuration, id); + end + if(trigger.custom_type ~= "stateupdate") then + overlayFuncs = {}; + for i = 1, 7 do + local property = "customOverlay" .. i; + if (trigger[property] and trigger[property] ~= "") then + overlayFuncs[i] = WeakAuras.LoadFunction("return ".. trigger[property], id); + end + end + end + if(trigger.custom_type ~= "stateupdate" and trigger.customName and trigger.customName ~= "") then + nameFunc = WeakAuras.LoadFunction("return "..trigger.customName, id); + end + if(trigger.custom_type ~= "stateupdate" and trigger.customIcon and trigger.customIcon ~= "") then + iconFunc = WeakAuras.LoadFunction("return "..trigger.customIcon, id); + end + if(trigger.custom_type ~= "stateupdate" and trigger.customTexture and trigger.customTexture ~= "") then + textureFunc = WeakAuras.LoadFunction("return "..trigger.customTexture, id); + end + if(trigger.custom_type ~= "stateupdate" and trigger.customStacks and trigger.customStacks ~= "") then + stacksFunc = WeakAuras.LoadFunction("return "..trigger.customStacks, id); + end + + if((trigger.custom_type == "status" or trigger.custom_type == "stateupdate") and trigger.check == "update") then + trigger_events = {"FRAME_UPDATE"}; + else + local rawEvents = WeakAuras.split(trigger.events); + for index, event in pairs(rawEvents) do + -- custom events in the form of event:unit1:unit2:unitX are registered with RegisterUnitEvent + local trueEvent + local hasParam = false + local isCLEU = false + local isUnitEvent = false + for i in event:gmatch("[^:]+") do + if not trueEvent then + trueEvent = string.upper(i) + isCLEU = trueEvent == "CLEU" or trueEvent == "COMBAT_LOG_EVENT_UNFILTERED" + elseif isCLEU then + local subevent = string.upper(i) + if WeakAuras.IsCLEUSubevent(subevent) then + tinsert(trigger_subevents, subevent) + hasParam = true + end + elseif trueEvent:match("^UNIT_") then + MultiUnitLoop( + function(unit) + trigger_unit_events[unit] = trigger_unit_events[unit] or {} + tinsert(trigger_unit_events[unit], trueEvent) + isUnitEvent = true + end, i + ) + end + end + if isCLEU then + if hasParam then + tinsert(trigger_events, "COMBAT_LOG_EVENT_UNFILTERED") + else + -- This is a dirty, lazy, dirty hack. "Proper" COMBAT_LOG_EVENT_UNFILTERED events are indexed by their sub-event types (e.g. SPELL_PERIODIC_DAMAGE), + -- but custom COMBAT_LOG_EVENT_UNFILTERED events are not guaranteed to have sub-event types. Thus, if the user specifies that they want to use + -- COMBAT_LOG_EVENT_UNFILTERED, this hack renames the event to COMBAT_LOG_EVENT_UNFILTERED_CUSTOM to circumvent the COMBAT_LOG_EVENT_UNFILTERED checks + -- that are already in place. Replacing all those checks would be a pain in the ass. + tinsert(trigger_events, "COMBAT_LOG_EVENT_UNFILTERED_CUSTOM") + end + elseif isUnitEvent then + -- not added to trigger_events + else + tinsert(trigger_events, event) + end + force_events = trigger.custom_type == "status" or trigger.custom_type == "stateupdate"; + end + end + if (trigger.custom_type == "stateupdate") then + statesParameter = "full"; + end + end + + local automaticAutoHide; + local duration; + if(triggerType == "custom" + and trigger.custom_type == "event" + and trigger.custom_hide == "timed") then + automaticAutoHide = true; + if (not trigger.dynamicDuration) then + duration = tonumber(trigger.duration); + end + end + + if (triggerType == "event" and trigger.unevent == "timed") then + duration = tonumber(trigger.duration); + automaticAutoHide = true; + end + + if trigger.event == "Combat Log" and trigger.subeventPrefix and trigger.subeventSuffix then + tinsert(trigger_subevents, trigger.subeventPrefix .. trigger.subeventSuffix) + end + + events[id] = events[id] or {}; + events[id][triggernum] = { + trigger = trigger, + triggerFunc = triggerFunc, + untriggerFunc = untriggerFunc, + statesParameter = statesParameter, + event = trigger.event, + events = trigger_events, + internal_events = internal_events, + force_events = force_events, + unit_events = trigger_unit_events, + inverse = trigger.use_inverse, + subevents = trigger_subevents, + unevent = trigger.unevent, + durationFunc = durationFunc, + overlayFuncs = overlayFuncs, + nameFunc = nameFunc, + iconFunc = iconFunc, + textureFunc = textureFunc, + stacksFunc = stacksFunc, + loadFunc = loadFunc, + duration = duration, + automaticAutoHide = automaticAutoHide, + tsuConditionVariables = tsuConditionVariables + }; + end + end + end + + +end + +do + local update_clients = {}; + local update_clients_num = 0; + local update_frame = nil + WeakAuras.frames["Custom Trigger Every Frame Updater"] = update_frame; + local updating = false; + + function WeakAuras.RegisterEveryFrameUpdate(id) + if not(update_clients[id]) then + update_clients[id] = true; + update_clients_num = update_clients_num + 1; + end + if not(update_frame) then + update_frame = CreateFrame("FRAME"); + end + if not(updating) then + update_frame:SetScript("OnUpdate", function() + if not(WeakAuras.IsPaused()) then + WeakAuras.ScanEvents("FRAME_UPDATE"); + end + end); + updating = true; + end + end + + function WeakAuras.EveryFrameUpdateRename(oldid, newid) + update_clients[newid] = update_clients[oldid]; + update_clients[oldid] = nil; + end + + function WeakAuras.UnregisterEveryFrameUpdate(id) + if(update_clients[id]) then + update_clients[id] = nil; + update_clients_num = update_clients_num - 1; + end + if(update_clients_num == 0 and update_frame and updating) then + update_frame:SetScript("OnUpdate", nil); + updating = false; + end + end + + function WeakAuras.UnregisterAllEveryFrameUpdate() + if (not update_frame) then + return; + end + wipe(update_clients); + update_clients_num = 0; + update_frame:SetScript("OnUpdate", nil); + updating = false; + end +end + +local combatLogUpgrade = { + ["sourceunit"] = "sourceUnit", + ["source"] = "sourceName", + ["destunit"] = "destUnit", + ["dest"] = "destName" +} + +local oldPowerTriggers = { + ["Combo Points"] = 4, + ["Holy Power"] = 9, + ["Insanity"] = 13, + ["Chi Power"] = 12, + ["Astral Power"] = 8, + ["Maelstrom"] = 11, + ["Arcane Charges"] = 16, + ["Fury"] = 17, + ["Pain"] = 18, + ["Shards"] = 7, +} + +function GenericTrigger.Modernize(data) + for triggernum, triggerData in ipairs(data.triggers) do + local trigger, untrigger = triggerData.trigger, triggerData.untrigger + + if (data.internalVersion < 2) then + -- Convert any references to "COMBAT_LOG_EVENT_UNFILTERED_CUSTOM" to "COMBAT_LOG_EVENT_UNFILTERED" + if(trigger and trigger.custom) then + trigger.custom = trigger.custom:gsub("COMBAT_LOG_EVENT_UNFILTERED_CUSTOM", "COMBAT_LOG_EVENT_UNFILTERED"); + end + + if(untrigger and untrigger.custom) then + untrigger.custom = untrigger.custom:gsub("COMBAT_LOG_EVENT_UNFILTERED_CUSTOM", "COMBAT_LOG_EVENT_UNFILTERED"); + end + + if trigger and trigger["event"] and trigger["event"] == "DBM Timer" then + if (type(trigger.spellId) == "number") then + trigger.spellId = tostring(trigger.spellId); + end + end + + if trigger and trigger["event"] and trigger["event"] == "Item Set Equipped" then + trigger.event = "Equipment Set"; + end + + -- Convert ember trigger + local fixEmberTrigger = function(trigger) + if (trigger.power and not trigger.ember) then + trigger.ember = tostring(tonumber(trigger.power) * 10); + trigger.use_ember = trigger.use_power + trigger.ember_operator = trigger.power_operator; + trigger.power = nil; + trigger.use_power = nil; + trigger.power_operator = nil; + end + end + + if (trigger and trigger.type and trigger.event and trigger.type == "status" and trigger.event == "Burning Embers") then + fixEmberTrigger(trigger); + fixEmberTrigger(untrigger); + end + + if (trigger and trigger.type and trigger.event and trigger.type == "status" + and (trigger.event == "Cooldown Progress (Spell)" + or trigger.event == "Cooldown Progress (Item)" + or trigger.event == "Death Knight Rune")) then + + if (not trigger.showOn) then + if (trigger.use_inverse) then + trigger.showOn = "showOnReady" + else + trigger.showOn = "showOnCooldown" + end + + if (trigger.event == "Death Knight Rune") then + trigger.use_genericShowOn = true; + end + trigger.use_inverse = nil + end + end + + for old, new in pairs(combatLogUpgrade) do + if (trigger and trigger[old]) then + local useOld = "use_" .. old; + local useNew = "use_" .. new; + trigger[useNew] = trigger[useOld]; + trigger[new] = trigger[old]; + + trigger[old] = nil; + trigger[useOld] = nil; + end + end + + -- Convert separated Power Triggers to sub options of the Power trigger + if (trigger and trigger.type and trigger.event and trigger.type == "status" and oldPowerTriggers[trigger.event]) then + trigger.powertype = oldPowerTriggers[trigger.event] + trigger.use_powertype = true; + trigger.use_percentpower = false; + if (trigger.event == "Combo Points") then + trigger.power = trigger.combopoints; + trigger.power_operator = trigger.combopoints_operator + trigger.use_power = trigger.use_combopoints; + end + trigger.event = "Power"; + trigger.unit = "player"; + end + end + + if data.internalVersion < 6 then + if trigger and trigger.type ~= "aura" then + trigger.genericShowOn = trigger.showOn or "showOnActive" + trigger.showOn = nil + trigger.use_genericShowOn = trigger.use_showOn + end + end + end +end + +function GenericTrigger.AllAdded() + -- Remove GTFO options if GTFO isn't enabled and there are no saved GTFO auras + local hideGTFO = true; + local hideDBM = true; + + if (GTFO) then + hideGTFO = false; + end + + if (DBM) then + hideDBM = false; + end + + for id, event in pairs(events) do + for triggernum, data in pairs(event) do + if (data.trigger.event == "GTFO") then + hideGTFO = false; + end + if (data.trigger.event == "DBM Announce" or data.trigger.event == "DBM Timer") then + hideDBM = false; + end + end + end + if (hideGTFO) then + WeakAuras.event_types["GTFO"] = nil; + end + if (hideDBM) then + WeakAuras.event_types["DBM Announce"] = nil; + WeakAuras.status_types["DBM Timer"] = nil; + end +end + +--############################# +--# Support code for triggers # +--############################# + +-- Swing timer support code +do + local mh = GetInventorySlotInfo("MainHandSlot") + local oh = GetInventorySlotInfo("SecondaryHandSlot") + local ranged = WeakAuras.IsClassic() and GetInventorySlotInfo("RangedSlot") + + local swingTimerFrame; + local lastSwingMain, lastSwingOff, lastSwingRange; + local swingDurationMain, swingDurationOff, swingDurationRange, mainSwingOffset; + local mainTimer, offTimer, rangeTimer; + local selfGUID; + local mainSpeed, offSpeed = UnitAttackSpeed("player") + local casting = false + + function WeakAuras.GetSwingTimerInfo(hand) + if(hand == "main") then + local itemId = GetInventoryItemID("player", mh); + local name, _, _, _, _, _, _, _, _, icon = GetItemInfo(itemId or 0); + if(lastSwingMain) then + return swingDurationMain, lastSwingMain + swingDurationMain - mainSwingOffset, name, icon; + elseif not WeakAuras.IsClassic() and lastSwingRange then + return swingDurationRange, lastSwingRange + swingDurationRange, name, icon; + else + return 0, math.huge, name, icon; + end + elseif(hand == "off") then + local itemId = GetInventoryItemID("player", oh); + local name, _, _, _, _, _, _, _, _, icon = GetItemInfo(itemId or 0); + if(lastSwingOff) then + return swingDurationOff, lastSwingOff + swingDurationOff, name, icon; + else + return 0, math.huge, name, icon; + end + elseif(hand == "ranged") then + local itemId = GetInventoryItemID("player", ranged); + local name, _, _, _, _, _, _, _, _, icon = GetItemInfo(itemId or 0); + if (lastSwingRange) then + return swingDurationRange, lastSwingRange + swingDurationRange, name, icon; + else + return 0, math.huge, name, icon; + end + end + + return 0, math.huge; + end + + local function swingEnd(hand) + if(hand == "main") then + lastSwingMain, swingDurationMain, mainSwingOffset = nil, nil, nil; + elseif(hand == "off") then + lastSwingOff, swingDurationOff = nil, nil; + elseif(hand == "ranged") then + lastSwingRange, swingDurationRange = nil, nil; + end + WeakAuras.ScanEvents("SWING_TIMER_END"); + end + + local function swingTimerCLEUCheck(ts, event, sourceGUID, _, _, destGUID, _, _, ...) + WeakAuras.StartProfileSystem("generictrigger swing"); + if(sourceGUID == selfGUID) then + if(event == "SWING_DAMAGE" or event == "SWING_MISSED") then + local isOffHand = select(event == "SWING_DAMAGE" and 10 or 2, ...); + + local event; + local currentTime = GetTime(); + mainSpeed, offSpeed = UnitAttackSpeed("player"); + offSpeed = offSpeed or 0; + if not(isOffHand) then + lastSwingMain = currentTime; + swingDurationMain = mainSpeed; + mainSwingOffset = 0; + event = "SWING_TIMER_START"; + timer:CancelTimer(mainTimer); + mainTimer = timer:ScheduleTimerFixed(swingEnd, mainSpeed, "main"); + elseif(isOffHand) then + lastSwingOff = currentTime; + swingDurationOff = offSpeed; + event = "SWING_TIMER_START"; + timer:CancelTimer(offTimer); + offTimer = timer:ScheduleTimerFixed(swingEnd, offSpeed, "off"); + end + WeakAuras.ScanEvents(event); + end + elseif (destGUID == selfGUID and (select(1, ...) == "PARRY" or select(4, ...) == "PARRY")) then + if (lastSwingMain) then + local timeLeft = lastSwingMain + swingDurationMain - GetTime(); + if (timeLeft > 0.6 * swingDurationMain) then + timer:CancelTimer(mainTimer); + mainTimer = timer:ScheduleTimerFixed(swingEnd, timeLeft - 0.4 * swingDurationMain, "main"); + mainSwingOffset = 0.4 * swingDurationMain + WeakAuras.ScanEvents("SWING_TIMER_CHANGE"); + elseif (timeLeft > 0.2 * swingDurationMain) then + timer:CancelTimer(mainTimer); + mainTimer = timer:ScheduleTimerFixed(swingEnd, timeLeft - 0.2 * swingDurationMain, "main"); + mainSwingOffset = 0.2 * swingDurationMain + WeakAuras.ScanEvents("SWING_TIMER_CHANGE"); + end + end + end + WeakAuras.StopProfileSystem("generictrigger swing"); + end + + local function swingTimerCheck(event, unit, guid, spell) + if unit ~= "player" then return end + WeakAuras.StartProfileSystem("generictrigger swing"); + if event == "UNIT_ATTACK_SPEED" then + local mainSpeedNew, offSpeedNew = UnitAttackSpeed("player") + offSpeedNew = offSpeedNew or 0 + if lastSwingMain then + if mainSpeedNew ~= mainSpeed then + timer:CancelTimer(mainTimer) + local multiplier = mainSpeedNew / mainSpeed + local timeLeft = (lastSwingMain + swingDurationMain - GetTime()) * multiplier + swingDurationMain = mainSpeedNew + mainTimer = timer:ScheduleTimerFixed(swingEnd, timeLeft, "main") + WeakAuras.ScanEvents("SWING_TIMER_CHANGE") + end + end + if lastSwingOff then + if offSpeedNew ~= offSpeed then + timer:CancelTimer(offTimer) + local multiplier = offSpeedNew / mainSpeed + local timeLeft = (lastSwingOff + swingDurationOff - GetTime()) * multiplier + swingDurationOff = offSpeedNew + offTimer = timer:ScheduleTimerFixed(swingEnd, timeLeft, "off") + WeakAuras.ScanEvents("SWING_TIMER_CHANGE") + end + end + mainSpeed, offSpeed = mainSpeedNew, offSpeedNew + elseif casting and (event == "UNIT_SPELLCAST_INTERRUPTED" or event == "UNIT_SPELLCAST_FAILED") then + casting = false + elseif event == "UNIT_SPELLCAST_SUCCEEDED" then + if WeakAuras.reset_swing_spells[spell] or casting then + if casting then + casting = false + end + local event; + mainSpeed, offSpeed = UnitAttackSpeed("player"); + lastSwingMain = GetTime(); + swingDurationMain = mainSpeed; + mainSwingOffset = 0; + if (lastSwingMain) then + timer:CancelTimer(mainTimer); + event = "SWING_TIMER_CHANGE"; + else + event = "SWING_TIMER_START"; + end + mainTimer = timer:ScheduleTimerFixed(swingEnd, mainSpeed, "main"); + WeakAuras.ScanEvents(event); + elseif WeakAuras.reset_ranged_swing_spells[spell] then + local event; + local currentTime = GetTime(); + local speed = UnitRangedDamage("player"); + if(lastSwingRange) then + if WeakAuras.IsClassic() then + timer:CancelTimer(rangeTimer, true) + else + timer:CancelTimer(mainTimer, true) + end + event = "SWING_TIMER_CHANGE"; + else + event = "SWING_TIMER_START"; + end + lastSwingRange = currentTime; + swingDurationRange = speed; + if WeakAuras.IsClassic() then + rangeTimer = timer:ScheduleTimerFixed(swingEnd, speed, "ranged"); + else + mainTimer = timer:ScheduleTimerFixed(swingEnd, speed, "main"); + end + WeakAuras.ScanEvents(event); + end + elseif event == "UNIT_SPELLCAST_START" then + -- pause swing timer + casting = true + lastSwingMain, swingDurationMain, mainSwingOffset = nil, nil, nil + lastSwingOff, swingDurationOff = nil, nil + WeakAuras.ScanEvents("SWING_TIMER_END") + end + WeakAuras.StopProfileSystem("generictrigger swing"); + end + + function WeakAuras.InitSwingTimer() + if not(swingTimerFrame) then + swingTimerFrame = CreateFrame("frame"); + swingTimerFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED"); + swingTimerFrame:RegisterEvent("PLAYER_ENTER_COMBAT"); + swingTimerFrame:RegisterEvent("UNIT_ATTACK_SPEED"); + swingTimerFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED"); + if WeakAuras.IsClassic() then + swingTimerFrame:RegisterEvent("UNIT_SPELLCAST_START") + swingTimerFrame:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") + swingTimerFrame:RegisterEvent("UNIT_SPELLCAST_FAILED") + end + swingTimerFrame:SetScript("OnEvent", + function(_, event, ...) + if event == "COMBAT_LOG_EVENT_UNFILTERED" then + swingTimerCLEUCheck(...) + else + swingTimerCheck(event, ...) + end + end); + selfGUID = UnitGUID("player"); + end + end +end + +-- CD/Rune/GCD support code +do + local cdReadyFrame; + + local spells = {}; + local spellKnown = {}; + + local spellCounts = {} + + local items = {}; + local itemCdDurs = {}; + local itemCdExps = {}; + local itemCdHandles = {}; + local itemCdEnabled = {}; + + local itemSlots = {}; + local itemSlotsCdDurs = {}; + local itemSlotsCdExps = {}; + local itemSlotsCdHandles = {}; + local itemSlotsEnable = {}; + + local runes = {}; + local runeCdDurs = {}; + local runeCdExps = {}; + local runeCdHandles = {}; + + local gcdStart; + local gcdDuration; + local gcdSpellName; + local gcdSpellIcon; + local gcdEndCheck; + + local shootStart + local shootDuration + + local function GetRuneDuration() + local runeDuration = -100; + for id, _ in pairs(runes) do + local startTime, duration = GetRuneCooldown(id); + duration = duration or 0; + runeDuration = duration > 0 and duration or runeDuration + end + return runeDuration + end + + local function CheckGCD() + local event; + local startTime, duration + if WeakAuras.IsClassic() then + startTime, duration = GetSpellCooldown(29515); + shootStart, shootDuration = GetSpellCooldown(5019) + else + startTime, duration = GetSpellCooldown(61304); + end + if(duration and duration > 0) then + if not(gcdStart) then + event = "GCD_START"; + elseif(gcdStart ~= startTime) then + event = "GCD_CHANGE"; + end + gcdStart, gcdDuration = startTime, duration; + local endCheck = startTime + duration + 0.1; + if(gcdEndCheck ~= endCheck) then + gcdEndCheck = endCheck; + timer:ScheduleTimerFixed(CheckGCD, duration + 0.1); + end + else + if(gcdStart) then + event = "GCD_END" + end + gcdStart, gcdDuration = nil, nil; + gcdSpellName, gcdSpellIcon = nil, nil; + gcdEndCheck = 0; + end + if(event) then + WeakAuras.ScanEvents(event); + end + end + + local RecheckHandles = { + expirationTime = {}, + handles = {}, + Recheck = function(self, id) + self.handles[id] = nil + self.expirationTime[id] = nil + CheckGCD(); + WeakAuras.CheckSpellCooldown(id, GetRuneDuration()) + end, + Schedule = function(self, expirationTime, id) + if (not self.expirationTime[id] or expirationTime < self.expirationTime[id]) and expirationTime > 0 then + if self.handles[id] then + timer:CancelTimer(self.handles[id]) + self.handles[id] = nil + self.expirationTime[id] = nil + end + + local duration = expirationTime - GetTime() + if duration > 0 then + self.handles[id] = timer:ScheduleTimerFixed(self.Recheck, duration, self, id) + self.expirationTime[id] = expirationTime + end + end + end + } + + local function FetchSpellCooldown(self, id) + if self.duration[id] and self.expirationTime[id] then + return self.expirationTime[id] - self.duration[id], self.duration[id] + end + return 0, 0 + end + + local function HandleSpell(self, id, startTime, duration) + local changed = false + local nowReady = false + local time = GetTime() + if self.expirationTime[id] and self.expirationTime[id] <= time and self.expirationTime[id] ~= 0 then + self.duration[id] = 0 + self.expirationTime[id] = 0 + changed = true + nowReady = true + end + local endTime = startTime + duration; + if endTime <= time then + startTime = 0 + duration = 0 + endTime = 0 + end + + if duration > 0 then + if (startTime == gcdStart and duration == gcdDuration) + or (WeakAuras.IsClassic() and duration == shootDuration and startTime == shootStart) + then + -- GCD cooldown, this could mean that the spell reset! + if self.expirationTime[id] and self.expirationTime[id] > endTime and self.expirationTime[id] ~= 0 then + self.duration[id] = 0 + self.expirationTime[id] = 0 + changed = true + nowReady = true + end + RecheckHandles:Schedule(endTime, id) + return changed, nowReady + end + end + + if self.duration[id] ~= duration then + self.duration[id] = duration + changed = true + end + + if self.expirationTime[id] ~= endTime then + self.expirationTime[id] = endTime + changed = true + nowReady = endTime == 0 + end + + RecheckHandles:Schedule(endTime, id) + return changed, nowReady + end + + local function CreateSpellCDHandler() + local cd = { + duration = {}, + expirationTime = {}, + handles = {}, -- Share handles, and use lowest time to schedule + HandleSpell = HandleSpell, + FetchSpellCooldown = FetchSpellCooldown + } + return cd + end + + local spellCds = CreateSpellCDHandler(); + local spellCdsRune = CreateSpellCDHandler(); + local spellCdsOnlyCooldown = CreateSpellCDHandler(); + local spellCdsOnlyCooldownRune = CreateSpellCDHandler(); + local spellCdsCharges = CreateSpellCDHandler(); + + local spellIds = {} + + function WeakAuras.InitCooldownReady() + cdReadyFrame = CreateFrame("FRAME"); + WeakAuras.frames["Cooldown Trigger Handler"] = cdReadyFrame + cdReadyFrame:RegisterEvent("RUNE_POWER_UPDATE"); + cdReadyFrame:RegisterEvent("PLAYER_TALENT_UPDATE"); + cdReadyFrame:RegisterEvent("CHARACTER_POINTS_CHANGED"); + cdReadyFrame:RegisterEvent("SPELL_UPDATE_COOLDOWN"); + cdReadyFrame:RegisterEvent("UNIT_SPELLCAST_SENT"); + cdReadyFrame:RegisterEvent("BAG_UPDATE_COOLDOWN"); + cdReadyFrame:RegisterEvent("UNIT_INVENTORY_CHANGED") + cdReadyFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED"); + cdReadyFrame:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN"); + cdReadyFrame:RegisterEvent("SPELLS_CHANGED"); + cdReadyFrame:RegisterEvent("PLAYER_ENTERING_WORLD"); + cdReadyFrame:SetScript("OnEvent", function(self, event, ...) + WeakAuras.StartProfileSystem("generictrigger cd tracking"); + if not WeakAuras.IsPaused() then + if(event == "SPELL_UPDATE_COOLDOWN" + or event == "RUNE_POWER_UPDATE" or event == "ACTIONBAR_UPDATE_COOLDOWN" + or event == "PLAYER_TALENT_UPDATE" + or event == "CHARACTER_POINTS_CHANGED") then + WeakAuras.CheckCooldownReady(); + elseif(event == "SPELLS_CHANGED") then + WeakAuras.CheckSpellKnown(); + WeakAuras.CheckCooldownReady(); + elseif(event == "UNIT_SPELLCAST_SENT") then + local unit, guid, castGUID, name = ...; + if(unit == "player") then + name = GetSpellInfo(name); + if(gcdSpellName ~= name) then + local icon = GetSpellTexture(name); + gcdSpellName = name; + gcdSpellIcon = icon; + WeakAuras.ScanEvents("GCD_UPDATE"); + end + end + elseif(event == "UNIT_INVENTORY_CHANGED" or event == "BAG_UPDATE_COOLDOWN" or event == "PLAYER_EQUIPMENT_CHANGED") then + WeakAuras.CheckItemSlotCooldowns(); + end + end + WeakAuras.StopProfileSystem("generictrigger cd tracking"); + end); + end + + function WeakAuras.GetRuneCooldown(id) + if(runes[id] and runeCdExps[id] and runeCdDurs[id]) then + return runeCdExps[id] - runeCdDurs[id], runeCdDurs[id]; + else + return 0, 0; + end + end + + function WeakAuras.GetSpellCooldown(id, ignoreRuneCD, showgcd, ignoreSpellKnown, track) + if (not spellKnown[id] and not ignoreSpellKnown) then + return; + end + local startTime, duration, gcdCooldown; + if track == "charges" then + startTime, duration = spellCdsCharges:FetchSpellCooldown(id) + elseif track == "cooldown" then + if ignoreRuneCD then + startTime, duration = spellCdsOnlyCooldownRune:FetchSpellCooldown(id) + else + startTime, duration = spellCdsOnlyCooldown:FetchSpellCooldown(id) + end + elseif (ignoreRuneCD) then + startTime, duration = spellCdsRune:FetchSpellCooldown(id) + else + startTime, duration = spellCds:FetchSpellCooldown(id) + end + + if (showgcd) then + if ((gcdStart or 0) + (gcdDuration or 0) > startTime + duration) then + startTime = gcdStart; + duration = gcdDuration; + gcdCooldown = true; + end + end + + return startTime, duration, gcdCooldown; + end + + function WeakAuras.GetSpellCharges(id, ignoreSpellKnown) + if (not spellKnown[id] and not ignoreSpellKnown) then + return; + end + return spellCounts[id]; + end + + function WeakAuras.GetItemCooldown(id, showgcd) + local startTime, duration, enabled, gcdCooldown; + if(items[id] and itemCdExps[id] and itemCdDurs[id]) then + startTime, duration, enabled = itemCdExps[id] - itemCdDurs[id], itemCdDurs[id], itemCdEnabled[id]; + else + startTime, duration, enabled = 0, 0, itemCdEnabled[id] or 1; + end + if (showgcd) then + if ((gcdStart or 0) + (gcdDuration or 0) > startTime + duration) then + startTime = gcdStart; + duration = gcdDuration; + gcdCooldown = true; + end + end + return startTime, duration, enabled, gcdCooldown; + end + + function WeakAuras.GetGCDInfo() + if(gcdStart) then + return gcdDuration, gcdStart + gcdDuration, gcdSpellName or "Invalid", gcdSpellIcon or "Interface\\Icons\\INV_Misc_QuestionMark"; + else + return 0, math.huge, gcdSpellName or "Invalid", gcdSpellIcon or "Interface\\Icons\\INV_Misc_QuestionMark"; + end + end + + function WeakAuras.gcdDuration() + return gcdDuration or 0; + end + + function WeakAuras.GcdSpellName() + return gcdSpellName; + end + + function WeakAuras.GetItemSlotCooldown(id, showgcd) + local startTime, duration, enabled, gcdCooldown; + if(itemSlots[id] and itemSlotsCdExps[id] and itemSlotsCdDurs[id]) then + startTime, duration, enabled = itemSlotsCdExps[id] - itemSlotsCdDurs[id], itemSlotsCdDurs[id], itemSlotsEnable[id]; + else + startTime, duration, enabled = 0, 0, itemSlotsEnable[id]; + end + + if (showgcd) then + if ((gcdStart or 0) + (gcdDuration or 0) > startTime + duration) then + startTime = gcdStart; + duration = gcdDuration; + gcdCooldown = true; + end + end + return startTime, duration, enabled, gcdCooldown; + end + + local function RuneCooldownFinished(id) + runeCdHandles[id] = nil; + runeCdDurs[id] = nil; + runeCdExps[id] = nil; + WeakAuras.ScanEvents("RUNE_COOLDOWN_READY", id); + end + + local function ItemCooldownFinished(id) + itemCdHandles[id] = nil; + itemCdDurs[id] = nil; + itemCdExps[id] = nil; + itemCdEnabled[id] = 1; + WeakAuras.ScanEvents("ITEM_COOLDOWN_READY", id); + end + + local function ItemSlotCooldownFinished(id) + itemSlotsCdHandles[id] = nil; + itemSlotsCdDurs[id] = nil; + itemSlotsCdExps[id] = nil; + WeakAuras.ScanEvents("ITEM_SLOT_COOLDOWN_READY", id); + end + + function WeakAuras.CheckRuneCooldown() + local runeDuration = -100; + for id, _ in pairs(runes) do + local startTime, duration = GetRuneCooldown(id); + startTime = startTime or 0; + duration = duration or 0; + runeDuration = duration > 0 and duration or runeDuration + local time = GetTime(); + + if(not startTime or startTime == 0) then + startTime = 0 + duration = 0 + end + + if(duration > 0 and duration ~= WeakAuras.gcdDuration()) then + -- On non-GCD cooldown + local endTime = startTime + duration; + + if not(runeCdExps[id]) then + -- New cooldown + runeCdDurs[id] = duration; + runeCdExps[id] = endTime; + runeCdHandles[id] = timer:ScheduleTimerFixed(RuneCooldownFinished, endTime - time, id); + WeakAuras.ScanEvents("RUNE_COOLDOWN_STARTED", id); + elseif(runeCdExps[id] ~= endTime) then + -- Cooldown is now different + if(runeCdHandles[id]) then + timer:CancelTimer(runeCdHandles[id]); + end + runeCdDurs[id] = duration; + runeCdExps[id] = endTime; + runeCdHandles[id] = timer:ScheduleTimerFixed(RuneCooldownFinished, endTime - time, id); + WeakAuras.ScanEvents("RUNE_COOLDOWN_CHANGED", id); + end + elseif(duration > 0) then + -- GCD, do nothing + else + if(runeCdExps[id]) then + -- Somehow CheckCooldownReady caught the rune cooldown before the timer callback + -- This shouldn't happen, but if it does, no problem + if(runeCdHandles[id]) then + timer:CancelTimer(runeCdHandles[id]); + end + RuneCooldownFinished(id); + end + end + end + return runeDuration; + end + + function WeakAuras.GetSpellCooldownUnified(id, runeDuration) + local startTimeCooldown, durationCooldown, enabled = GetSpellCooldown(id) + + startTimeCooldown = startTimeCooldown or 0; + durationCooldown = durationCooldown or 0; + + -- WORKAROUND Sometimes the API returns very high bogus numbers causing client freeezes, discard them here. WowAce issue #1008 + if (durationCooldown > 604800) then + durationCooldown = 0; + startTimeCooldown = 0; + end + + if (startTimeCooldown > GetTime() + 2^31 / 1000) then + -- WORKAROUND WoW wraps around negative values with 2^32/1000 + -- So if we find a cooldown in the far future, then undo the wrapping + startTimeCooldown = startTimeCooldown - 2^32 / 1000 + end + + -- Default to GetSpellCharges + local unifiedCooldownBecauseRune, cooldownBecauseRune = false, false; + if (enabled == 0) then + startTimeCooldown, durationCooldown = 0, 0 + end + + local onNonGCDCD = durationCooldown and startTimeCooldown and durationCooldown > 0 and (durationCooldown ~= gcdDuration or startTimeCooldown ~= gcdStart); + if (onNonGCDCD) then + cooldownBecauseRune = runeDuration and durationCooldown and abs(durationCooldown - runeDuration) < 0.001; + unifiedCooldownBecauseRune = cooldownBecauseRune + end + + return startTimeCooldown, durationCooldown, unifiedCooldownBecauseRune, cooldownBecauseRune, GetSpellCount(id); + end + + function WeakAuras.CheckSpellKnown() + for id, _ in pairs(spells) do + local known = WeakAuras.IsSpellKnownIncludingPet(id); + if (known ~= spellKnown[id]) then + spellKnown[id] = known; + WeakAuras.ScanEvents("SPELL_COOLDOWN_CHANGED", id); + end + end + end + + function WeakAuras.CheckSpellCooldown(id, runeDuration) + local startTimeCooldown, durationCooldown, unifiedCooldownBecauseRune, cooldownBecauseRune, spellCount = WeakAuras.GetSpellCooldownUnified(id, runeDuration); + + local time = GetTime(); + local remaining = startTimeCooldown + durationCooldown - time; + + local chargesChanged = spellCounts[id] ~= spellCount; + local chargesDifference = (spellCount or 0) - (spellCount or 0) + spellCounts[id] = spellCount + + local changed = false + if spellIds[id] ~= id then + spellIds[id] = id + changed = true + chargesChanged = true + end + + changed = spellCds:HandleSpell(id, startTimeCooldown, durationCooldown) or changed + if not unifiedCooldownBecauseRune then + changed = spellCdsRune:HandleSpell(id, startTimeCooldown, durationCooldown) or changed + end + local cdChanged, nowReady = spellCdsOnlyCooldown:HandleSpell(id, startTimeCooldown, durationCooldown) + changed = cdChanged or changed + if not cooldownBecauseRune then + changed = spellCdsOnlyCooldownRune:HandleSpell(id, startTimeCooldown, durationCooldown) or changed + end + + if nowReady then + WeakAuras.ScanEvents("SPELL_COOLDOWN_READY", id); + end + + if changed or chargesChanged then + WeakAuras.ScanEvents("SPELL_COOLDOWN_CHANGED", id); + end + + if (chargesDifference ~= 0 ) then + WeakAuras.ScanEvents("SPELL_CHARGES_CHANGED", id, chargesDifference, spellCount or 0); + end + end + + function WeakAuras.CheckSpellCooldows(runeDuration) + for id, _ in pairs(spells) do + WeakAuras.CheckSpellCooldown(id, runeDuration) + end + end + + function WeakAuras.CheckItemCooldowns() + for id, _ in pairs(items) do + local startTime, duration, enabled = GetItemCooldown(id); + if (duration == 0) then + enabled = 1; + end + if (enabled == 0) then + startTime, duration = 0, 0 + end + + local itemCdEnabledChanged = (itemCdEnabled[id] ~= enabled); + itemCdEnabled[id] = enabled; + startTime = startTime or 0; + duration = duration or 0; + local time = GetTime(); + + -- We check against 1.5 and gcdDuration, as apparently the durations might not match exactly. + -- But there shouldn't be any trinket with a actual cd of less than 1.5 anyway + if(duration > 0 and duration > 1.5 and duration ~= WeakAuras.gcdDuration()) then + -- On non-GCD cooldown + local endTime = startTime + duration; + + if not(itemCdExps[id]) then + -- New cooldown + itemCdDurs[id] = duration; + itemCdExps[id] = endTime; + itemCdHandles[id] = timer:ScheduleTimerFixed(ItemCooldownFinished, endTime - time, id); + WeakAuras.ScanEvents("ITEM_COOLDOWN_STARTED", id); + itemCdEnabledChanged = false; + elseif(itemCdExps[id] ~= endTime) then + -- Cooldown is now different + if(itemCdHandles[id]) then + timer:CancelTimer(itemCdHandles[id]); + end + itemCdDurs[id] = duration; + itemCdExps[id] = endTime; + itemCdHandles[id] = timer:ScheduleTimerFixed(ItemCooldownFinished, endTime - time, id); + WeakAuras.ScanEvents("ITEM_COOLDOWN_CHANGED", id); + itemCdEnabledChanged = false; + end + elseif(duration > 0) then + -- GCD, do nothing + else + if(itemCdExps[id]) then + -- Somehow CheckCooldownReady caught the item cooldown before the timer callback + -- This shouldn't happen, but if it does, no problem + if(itemCdHandles[id]) then + timer:CancelTimer(itemCdHandles[id]); + end + ItemCooldownFinished(id); + itemCdEnabledChanged = false; + end + end + if (itemCdEnabledChanged) then + WeakAuras.ScanEvents("ITEM_COOLDOWN_CHANGED", id); + end + end + end + + function WeakAuras.CheckItemSlotCooldowns() + for id, itemId in pairs(itemSlots) do + local startTime, duration, enable = GetInventoryItemCooldown("player", id); + itemSlotsEnable[id] = enable; + startTime = startTime or 0; + duration = duration or 0; + local time = GetTime(); + + -- We check against 1.5 and gcdDuration, as apparently the durations might not match exactly. + -- But there shouldn't be any trinket with a actual cd of less than 1.5 anyway + if(duration > 0 and duration > 1.5 and duration ~= WeakAuras.gcdDuration()) then + -- On non-GCD cooldown + local endTime = startTime + duration; + + if not(itemSlotsCdExps[id]) then + -- New cooldown + itemSlotsCdDurs[id] = duration; + itemSlotsCdExps[id] = endTime; + itemSlotsCdHandles[id] = timer:ScheduleTimerFixed(ItemSlotCooldownFinished, endTime - time, id); + WeakAuras.ScanEvents("ITEM_SLOT_COOLDOWN_STARTED", id); + elseif(itemSlotsCdExps[id] ~= endTime) then + -- Cooldown is now different + if(itemSlotsCdHandles[id]) then + timer:CancelTimer(itemSlotsCdHandles[id]); + end + itemSlotsCdDurs[id] = duration; + itemSlotsCdExps[id] = endTime; + itemSlotsCdHandles[id] = timer:ScheduleTimerFixed(ItemSlotCooldownFinished, endTime - time, id); + WeakAuras.ScanEvents("ITEM_SLOT_COOLDOWN_CHANGED", id); + end + elseif(duration > 0) then + -- GCD, do nothing + else + if(itemSlotsCdExps[id]) then + -- Somehow CheckCooldownReady caught the item cooldown before the timer callback + -- This shouldn't happen, but if it does, no problem + if(itemSlotsCdHandles[id]) then + timer:CancelTimer(itemSlotsCdHandles[id]); + end + ItemSlotCooldownFinished(id); + end + end + + local newItemId = GetInventoryItemID("player", id); + if (itemId ~= newItemId) then + WeakAuras.ScanEvents("ITEM_SLOT_COOLDOWN_ITEM_CHANGED"); + itemSlots[id] = newItemId or 0; + end + end + end + + function WeakAuras.CheckCooldownReady() + CheckGCD(); + local runeDuration = WeakAuras.CheckRuneCooldown(); + WeakAuras.CheckSpellCooldows(runeDuration); + WeakAuras.CheckItemCooldowns(); + WeakAuras.CheckItemSlotCooldowns(); + end + + function WeakAuras.WatchGCD() + if not(cdReadyFrame) then + WeakAuras.InitCooldownReady(); + end + end + + function WeakAuras.WatchRuneCooldown(id) + if not(cdReadyFrame) then + WeakAuras.InitCooldownReady(); + end + + if not id or id == 0 then return end + + if not(runes[id]) then + runes[id] = true; + local startTime, duration = GetRuneCooldown(id); + + if(not startTime or startTime == 0) then + startTime = 0 + duration = 0 + end + + if(duration > 0 and duration ~= WeakAuras.gcdDuration()) then + local time = GetTime(); + local endTime = startTime + duration; + runeCdDurs[id] = duration; + runeCdExps[id] = endTime; + if not(runeCdHandles[id]) then + runeCdHandles[id] = timer:ScheduleTimerFixed(RuneCooldownFinished, endTime - time, id); + end + end + end + end + + function WeakAuras.WatchSpellCooldown(id, ignoreRunes) + if not(cdReadyFrame) then + WeakAuras.InitCooldownReady(); + end + + if not id or id == 0 then return end + + if ignoreRunes then + for i = 1, 6 do + WeakAuras.WatchRuneCooldown(i); + end + end + + if (spells[id]) then + return; + end + spells[id] = true; + spellIds[id] = id + spellKnown[id] = WeakAuras.IsSpellKnownIncludingPet(id); + + local startTimeCooldown, durationCooldown, unifiedCooldownBecauseRune, cooldownBecauseRune, spellCount = WeakAuras.GetSpellCooldownUnified(id, GetRuneDuration()); + spellCounts[id] = spellCount + spellCds:HandleSpell(id, startTimeCooldown, durationCooldown) + if not unifiedCooldownBecauseRune then + spellCdsRune:HandleSpell(id, startTimeCooldown, durationCooldown) + end + spellCdsOnlyCooldown:HandleSpell(id, startTimeCooldown, durationCooldown) + if not cooldownBecauseRune then + spellCdsOnlyCooldownRune:HandleSpell(id, startTimeCooldown, durationCooldown) + end + end + + function WeakAuras.WatchItemCooldown(id) + if not(cdReadyFrame) then + WeakAuras.InitCooldownReady(); + end + + if not id or id == 0 then return end + + if not(items[id]) then + items[id] = true; + local startTime, duration, enabled = GetItemCooldown(id); + if (duration == 0) then + enabled = 1; + end + if (enabled == 0) then + startTime, duration = 0, 0 + end + itemCdEnabled[id] = enabled; + if(duration > 0 and duration > 1.5 and duration ~= WeakAuras.gcdDuration()) then + local time = GetTime(); + local endTime = startTime + duration; + itemCdDurs[id] = duration; + itemCdExps[id] = endTime; + if not(itemCdHandles[id]) then + itemCdHandles[id] = timer:ScheduleTimerFixed(ItemCooldownFinished, endTime - time, id); + end + end + end + end + + function WeakAuras.WatchItemSlotCooldown(id) + if not(cdReadyFrame) then + WeakAuras.InitCooldownReady(); + end + + if not id or id == 0 then return end + + if not(itemSlots[id]) then + itemSlots[id] = GetInventoryItemID("player", id); + local startTime, duration, enable = GetInventoryItemCooldown("player", id); + itemSlotsEnable[id] = enable; + if(duration > 0 and duration > 1.5 and duration ~= WeakAuras.gcdDuration()) then + local time = GetTime(); + local endTime = startTime + duration; + itemSlotsCdDurs[id] = duration; + itemSlotsCdExps[id] = endTime; + if not(itemSlotsCdHandles[id]) then + itemSlotsCdHandles[id] = timer:ScheduleTimerFixed(ItemSlotCooldownFinished, endTime - time, id); + end + end + end + end +end + +do + local spellActivationSpells = {}; + local spellActivationSpellsCurrent = {}; + local spellActivationFrame; + local function InitSpellActivation() + spellActivationFrame = CreateFrame("FRAME"); + WeakAuras.frames["Spell Activation"] = spellActivationFrame; + spellActivationFrame:RegisterEvent("SPELL_ACTIVATION_OVERLAY_GLOW_SHOW"); + spellActivationFrame:RegisterEvent("SPELL_ACTIVATION_OVERLAY_GLOW_HIDE"); + spellActivationFrame:SetScript("OnEvent", function(self, event, spell) + WeakAuras.StartProfileSystem("generictrigger"); + if (spellActivationSpells[spell]) then + spellActivationSpellsCurrent[spell] = (event == "SPELL_ACTIVATION_OVERLAY_GLOW_SHOW"); + WeakAuras.ScanEvents("WA_UPDATE_OVERLAY_GLOW", spell); + end + WeakAuras.StopProfileSystem("generictrigger"); + end); + end + + function WeakAuras.WatchSpellActivation(id) + if (not id) then + return; + end + if (not spellActivationFrame) then + InitSpellActivation(); + end + spellActivationSpells[id] = true; + end + + function WeakAuras.SpellActivationActive(id) + return spellActivationSpellsCurrent[id]; + end +end + +local watchUnitChange + +-- Nameplates only distinguish between friends and everyone else +function WeakAuras.GetPlayerReaciton(unit) + return UnitIsEnemy('player', unit) and 'hostile' or 'friendly' +end + +function WeakAuras.WatchUnitChange(unit) + unit = string.lower(unit) + if not watchUnitChange then + watchUnitChange = CreateFrame("FRAME"); + watchUnitChange.unitChangeGUIDS = {} + watchUnitChange.unitRoles = {} + watchUnitChange.inRaid = IsInRaid() + + WeakAuras.frames["Unit Change Frame"] = watchUnitChange; + watchUnitChange:RegisterEvent("PLAYER_TARGET_CHANGED") + watchUnitChange:RegisterEvent("PLAYER_FOCUS_CHANGED"); + watchUnitChange:RegisterEvent("UNIT_TARGET"); + watchUnitChange:RegisterEvent("PARTY_MEMBERS_CHANGED"); + watchUnitChange:RegisterEvent("RAID_ROSTER_UPDATE"); + watchUnitChange:RegisterEvent("PLAYER_ENTERING_WORLD") + + watchUnitChange:SetScript("OnEvent", function(self, event, unit) + WeakAuras.StartProfileSystem("generictrigger unit change"); + local inRaid = IsInRaid() + local inRaidChanged = inRaid ~= watchUnitChange.inRaid + local UnitGroupRolesAssigned = not WeakAuras.IsClassic() and UnitGroupRolesAssigned or function() return "DAMAGER" end + + for unit, guid in pairs(watchUnitChange.unitChangeGUIDS) do + local newGuid = WeakAuras.UnitExistsFixed(unit) and UnitGUID(unit) or "" + if guid ~= newGuid or event == "PLAYER_ENTERING_WORLD" then + WeakAuras.ScanEvents("UNIT_CHANGED_" .. unit, unit) + watchUnitChange.unitChangeGUIDS[unit] = newGuid + elseif WeakAuras.multiUnitUnits.group[unit] then + -- If in raid changed we send a UNIT_CHANGED for the group units + if inRaidChanged then + WeakAuras.ScanEvents("UNIT_CHANGED_" .. unit, unit) + else + local newRole = UnitGroupRolesAssigned(unit) + if watchUnitChange.unitRoles[unit] ~= newRole then + watchUnitChange.unitRoles[unit] = newRole + WeakAuras.ScanEvents("UNIT_ROLE_CHANGED_" .. unit, unit) + end + end + end + end + watchUnitChange.inRaid = inRaid + WeakAuras.StopProfileSystem("generictrigger unit change"); + end) + end + watchUnitChange.unitChangeGUIDS = watchUnitChange.unitChangeGUIDS or {} + watchUnitChange.unitChangeGUIDS[unit] = UnitGUID(unit) or "" +end + +function WeakAuras.GetEquipmentSetInfo(itemSetName, partial) + local bestMatchNumItems = 0; + local bestMatchNumEquipped = 0; + local bestMatchName = nil; + local bestMatchIcon = nil; + + local equipmentSetIds = C_EquipmentSet.GetEquipmentSetIDs(); + for index, id in pairs(equipmentSetIds) do + local name, icon, _, _, numItems, numEquipped = C_EquipmentSet.GetEquipmentSetInfo(id); + if (itemSetName == nil or (name and itemSetName == name)) then + if (name ~= nil) then + local match = (not partial and numItems == numEquipped) + or (partial and (numEquipped or 0) > bestMatchNumEquipped); + if (match) then + bestMatchNumEquipped = numEquipped; + bestMatchNumItems = numItems; + bestMatchName = name; + bestMatchIcon = icon; + end + end + end + end + return bestMatchName, bestMatchIcon, bestMatchNumEquipped, bestMatchNumItems; +end + +-- DBM +do + local registeredDBMEvents = {} + local bars = {} + local nextExpire -- time of next expiring timer + local recheckTimer -- handle of timer + + local function dbmRecheckTimers() + local now = GetTime() + nextExpire = nil + for id, bar in pairs(bars) do + if bar.expirationTime < now then + bars[id] = nil + WeakAuras.ScanEvents("DBM_TimerStop", id) + elseif nextExpire == nil then + nextExpire = bar.expirationTime + elseif bar.expirationTime < nextExpire then + nextExpire = bar.expirationTime + end + end + + if nextExpire then + recheckTimer = timer:ScheduleTimerFixed(dbmRecheckTimers, nextExpire - now) + end + end + + local function dbmEventCallback(event, ...) + if event == "DBM_TimerStart" then + local id, msg, duration, icon, timerType, spellId, dbmType = ... + local now = GetTime() + local expirationTime = now + duration + bars[id] = bars[id] or {} + local bar = bars[id] + bar.message = msg + bar.expirationTime = expirationTime + bar.duration = duration + bar.icon = icon + bar.timerType = timerType + bar.spellId = tostring(spellId) + bar.count = msg:match("(%d+)") or "0" + bar.dbmType = dbmType + + local barOptions = DBM.Bars.options + local r, g, b = 0, 0, 0 + if dbmType == 1 then + r, g, b = barOptions.StartColorAR, barOptions.StartColorAG, barOptions.StartColorAB + elseif dbmType == 2 then + r, g, b = barOptions.StartColorAER, barOptions.StartColorAEG, barOptions.StartColorAEB + elseif dbmType == 3 then + r, g, b = barOptions.StartColorDR, barOptions.StartColorDG, barOptions.StartColorDB + elseif dbmType == 4 then + r, g, b = barOptions.StartColorIR, barOptions.StartColorIG, barOptions.StartColorIB + elseif dbmType == 5 then + r, g, b = barOptions.StartColorRR, barOptions.StartColorRG, barOptions.StartColorRB + elseif dbmType == 6 then + r, g, b = barOptions.StartColorPR, barOptions.StartColorPG, barOptions.StartColorPB + elseif dbmType == 7 then + r, g, b = barOptions.StartColorUIR, barOptions.StartColorUIG, barOptions.StartColorUIB + else + r, g, b = barOptions.StartColorR, barOptions.StartColorG, barOptions.StartColorB + end + bar.dbmColor = {r, g, b} + + WeakAuras.ScanEvents("DBM_TimerStart", id) + if nextExpire == nil then + recheckTimer = timer:ScheduleTimerFixed(dbmRecheckTimers, expirationTime - now) + nextExpire = expirationTime + elseif expirationTime < nextExpire then + timer:CancelTimer(recheckTimer) + recheckTimer = timer:ScheduleTimerFixed(dbmRecheckTimers, expirationTime - now) + nextExpire = expirationTime + end + elseif event == "DBM_TimerStop" then + local id = ... + bars[id] = nil + WeakAuras.ScanEvents("DBM_TimerStop", id) + elseif event == "kill" or event == "wipe" then -- Wipe or kill, removing all timers + local id = ... + bars = {} + WeakAuras.ScanEvents("DBM_TimerStopAll", id) + elseif event == "DBM_TimerUpdate" then + local id, elapsed, duration = ... + local now = GetTime() + local expirationTime = now + duration - elapsed + local bar = bars[id] + if bar then + bar.duration = duration + bar.expirationTime = expirationTime + if expirationTime < nextExpire then + timer:CancelTimer(recheckTimer) + recheckTimer = timer:ScheduleTimerFixed(dbmRecheckTimers, duration - elapsed) + nextExpire = expirationTime + end + end + WeakAuras.ScanEvents("DBM_TimerUpdate", id) + else -- DBM_Announce + WeakAuras.ScanEvents(event, ...) + end + end + + function WeakAuras.DBMTimerMatches(timerId, id, message, operator, spellId, dbmType, count) + if not bars[timerId] then + return false + end + + local v = bars[timerId] + if id and id ~= "" and id ~= timerId then + return false + end + if spellId and spellId ~= "" and spellId ~= v.spellId then + return false + end + if message and message ~= "" and operator then + if operator == "==" then + if v.message ~= message then + return false + end + elseif operator == "find('%s')" then + if v.message == nil or not v.message:find(message, 1, true) then + return false + end + elseif operator == "match('%s')" then + if v.message == nil or not v.message:match(message) then + return false + end + end + end + if count and count ~= "" and count ~= v.count then + return false + end + if dbmType and dbmType ~= v.dbmType then + return false + end + return true + end + + function WeakAuras.GetDBMTimerById(id) + return bars[id] + end + + function WeakAuras.GetAllDBMTimers() + return bars + end + + function WeakAuras.GetDBMTimer(id, message, operator, spellId, extendTimer, dbmType, count) + local bestMatch + for timerId, bar in pairs(bars) do + if WeakAuras.DBMTimerMatches(timerId, id, message, operator, spellId, dbmType, count) + and (bestMatch == nil or bar.expirationTime < bestMatch.expirationTime) + and bar.expirationTime + extendTimer > GetTime() + then + bestMatch = bar + end + end + return bestMatch + end + + function WeakAuras.CopyBarToState(bar, states, id, extendTimer) + extendTimer = extendTimer or 0 + if extendTimer + bar.duration < 0 then return end + states[id] = states[id] or {} + local state = states[id] + state.show = true + state.changed = true + state.icon = bar.icon + state.message = bar.message + state.name = bar.message + state.expirationTime = bar.expirationTime + extendTimer + state.progressType = 'timed' + state.duration = bar.duration + extendTimer + state.timerType = bar.timerType + state.spellId = bar.spellId + state.count = bar.count + state.dbmType = bar.dbmType + state.dbmColor = bar.dbmColor + state.extend = extendTimer + if extendTimer ~= 0 then + state.autoHide = true + end + end + + function WeakAuras.RegisterDBMCallback(event) + if registeredDBMEvents[event] then + return + end + if DBM then + DBM:RegisterCallback(event, dbmEventCallback) + registeredDBMEvents[event] = true + end + end + + function WeakAuras.GetDBMTimers() + return bars + end + + local scheduled_scans = {} + + local function doDbmScan(fireTime) + WeakAuras.debug("Performing dbm scan at "..fireTime.." ("..GetTime()..")") + scheduled_scans[fireTime] = nil + WeakAuras.ScanEvents("DBM_TimerUpdate") + end + function WeakAuras.ScheduleDbmCheck(fireTime) + if not scheduled_scans[fireTime] then + scheduled_scans[fireTime] = timer:ScheduleTimerFixed(doDbmScan, fireTime - GetTime() + 0.1, fireTime) + WeakAuras.debug("Scheduled dbm scan at "..fireTime) + end + end +end + +-- BigWigs +do + local registeredBigWigsEvents = {} + local bars = {} + local nextExpire -- time of next expiring timer + local recheckTimer -- handle of timer + + local function recheckTimers() + local now = GetTime() + nextExpire = nil + for id, bar in pairs(bars) do + if bar.expirationTime < now then + bars[id] = nil + WeakAuras.ScanEvents("BigWigs_StopBar", id) + elseif nextExpire == nil then + nextExpire = bar.expirationTime + elseif bar.expirationTime < nextExpire then + nextExpire = bar.expirationTime + end + end + + if nextExpire then + recheckTimer = timer:ScheduleTimerFixed(recheckTimers, nextExpire - now) + end + end + + local function bigWigsEventCallback(event, ...) + if event == "BigWigs_Message" then + WeakAuras.ScanEvents("BigWigs_Message", ...) + elseif event == "BigWigs_StartBar" then + local addon, spellId, text, duration, icon = ... + local now = GetTime() + local expirationTime = now + duration + + local newBar + bars[text] = bars[text] or {} + local bar = bars[text] + bar.addon = addon + bar.spellId = tostring(spellId) + bar.text = text + bar.duration = duration + bar.expirationTime = expirationTime + bar.icon = icon + local BWColorModule = BigWigs:GetPlugin("Colors") + bar.bwBarColor = BWColorModule:GetColorTable("barColor", addon, spellId) + bar.bwTextColor = BWColorModule:GetColorTable("barText", addon, spellId) + bar.bwBackgroundColor = BWColorModule:GetColorTable("barBackground", addon, spellId) + local BWEmphasizedModule = BigWigs:GetPlugin("Super Emphasize") + bar.emphasized = BWEmphasizedModule:IsSuperEmphasized(addon, spellId) and true or false + bar.count = text:match("(%d+)") or "0" + bar.cast = not(text:match("^[^<]") and true) + + WeakAuras.ScanEvents("BigWigs_StartBar", text) + if nextExpire == nil then + recheckTimer = timer:ScheduleTimerFixed(recheckTimers, expirationTime - now) + nextExpire = expirationTime + elseif expirationTime < nextExpire then + timer:CancelTimer(recheckTimer) + recheckTimer = timer:ScheduleTimerFixed(recheckTimers, expirationTime - now) + nextExpire = expirationTime + end + elseif event == "BigWigs_StopBar" then + local addon, text = ... + if bars[text] then + bars[text] = nil + WeakAuras.ScanEvents("BigWigs_StopBar", text) + end + elseif event == "BigWigs_StopBars" + or event == "BigWigs_OnBossDisable" + or event == "BigWigs_OnPluginDisable" + then + local addon = ... + for id, bar in pairs(bars) do + if bar.addon == addon then + bars[id] = nil + WeakAuras.ScanEvents("BigWigs_StopBar", id) + end + end + end + end + + function WeakAuras.RegisterBigWigsCallback(event) + if registeredBigWigsEvents[event] then + return + end + if BigWigsLoader then + BigWigsLoader.RegisterMessage(WeakAuras, event, bigWigsEventCallback) + registeredBigWigsEvents[event] = true + end + end + + function WeakAuras.RegisterBigWigsTimer() + WeakAuras.RegisterBigWigsCallback("BigWigs_StartBar") + WeakAuras.RegisterBigWigsCallback("BigWigs_StopBar") + WeakAuras.RegisterBigWigsCallback("BigWigs_StopBars") + WeakAuras.RegisterBigWigsCallback("BigWigs_OnBossDisable") + end + + function WeakAuras.CopyBigWigsTimerToState(bar, states, id, extendTimer) + extendTimer = extendTimer or 0 + if extendTimer + bar.duration < 0 then return end + states[id] = states[id] or {} + local state = states[id] + state.show = true + state.changed = true + state.addon = bar.addon + state.spellId = bar.spellId + state.text = bar.text + state.name = bar.text + state.duration = bar.duration + extendTimer + state.expirationTime = bar.expirationTime + extendTimer + state.bwBarColor = bar.bwBarColor + state.bwTextColor = bar.bwTextColor + state.bwBackgroundColor = bar.bwBackgroundColor + state.emphasized = bar.emphasized + state.count = bar.count + state.cast = bar.cast + state.progressType = "timed" + state.icon = bar.icon + state.extend = extendTimer + if extendTimer ~= 0 then + state.autoHide = true + end + end + + function WeakAuras.BigWigsTimerMatches(id, message, operator, spellId, emphasized, count, cast) + if not bars[id] then + return false + end + + local v = bars[id] + local bestMatch + if spellId and spellId ~= "" and spellId ~= v.spellId then + return false + end + if message and message ~= "" and operator then + if operator == "==" then + if v.text ~= message then + return false + end + elseif operator == "find('%s')" then + if v.text == nil or not v.text:find(message, 1, true) then + return false + end + elseif operator == "match('%s')" then + if v.text == nil or not v.text:match(message) then + return false + end + end + end + if emphasized ~= nil and v.emphasized ~= emphasized then + return false + end + if count and count ~= "" and count ~= v.count then + return false + end + if cast ~= nil and v.cast ~= cast then + return false + end + return true + end + + function WeakAuras.GetAllBigWigsTimers() + return bars + end + + function WeakAuras.GetBigWigsTimerById(id) + return bars[id] + end + + function WeakAuras.GetBigWigsTimer(text, operator, spellId, extendTimer, emphasized, count, cast) + local bestMatch + for id, bar in pairs(bars) do + if WeakAuras.BigWigsTimerMatches(id, text, operator, spellId, emphasized, count, cast) + and (bestMatch == nil or bar.expirationTime < bestMatch.expirationTime) + and bar.expirationTime + extendTimer > GetTime() + then + bestMatch = bar + end + end + return bestMatch + end + + local scheduled_scans = {} + + local function doBigWigsScan(fireTime) + WeakAuras.debug("Performing BigWigs scan at "..fireTime.." ("..GetTime()..")") + scheduled_scans[fireTime] = nil + WeakAuras.ScanEvents("BigWigs_Timer_Update") + end + + function WeakAuras.ScheduleBigWigsCheck(fireTime) + if not scheduled_scans[fireTime] then + scheduled_scans[fireTime] = timer:ScheduleTimerFixed(doBigWigsScan, fireTime - GetTime() + 0.1, fireTime) + WeakAuras.debug("Scheduled BigWigs scan at "..fireTime) + end + end +end + +function WeakAuras.CheckTotemName(totemName, triggerTotemName, triggerTotemPattern, triggerTotemOperator) + if not totemName or totemName == "" then + return false + end + + if triggerTotemName and #triggerTotemName > 0 and triggerTotemName ~= totemName then + return false + end + + if triggerTotemPattern and #triggerTotemPattern > 0 then + if triggerTotemOperator == "==" then + if totemName ~= triggerTotemPattern then + return false + end + elseif triggerTotemOperator == "find('%s')" then + if not totemName:find(triggerTotemPattern, 1, true) then + return false + end + elseif triggerTotemOperator == "match('%s')" then + if not totemName:match(triggerTotemPattern) then + return false + end + end + end + + return true +end + +-- Weapon Enchants +do + local mh = GetInventorySlotInfo("MainHandSlot") + local oh = GetInventorySlotInfo("SecondaryHandSlot") + + local mh_name, mh_shortenedName, mh_exp, mh_dur, mh_charges, mh_EnchantID; + local mh_icon = GetInventoryItemTexture("player", mh); + + local oh_name, oh_shortenedName, oh_exp, oh_dur, oh_charges, oh_EnchantID; + local oh_icon = GetInventoryItemTexture("player", oh); + + local tenchFrame = nil + WeakAuras.frames["Temporary Enchant Handler"] = tenchFrame; + local tenchTip; + + function WeakAuras.TenchInit() + if not(tenchFrame) then + tenchFrame = CreateFrame("Frame"); + tenchFrame:RegisterEvent("UNIT_INVENTORY_CHANGED"); + + tenchTip = WeakAuras.GetHiddenTooltip(); + + local function getTenchName(id) + tenchTip:SetInventoryItem("player", id); + local lines = { tenchTip:GetRegions() }; + for i,v in ipairs(lines) do + if(v:GetObjectType() == "FontString") then + local text = v:GetText(); + if(text) then + local _, _, name = text:find("^(.+) %(%d+ [^%)]+%)$"); + if(name) then + local _, _, shortenedName = name:find("^(.+) [VI%d]+$") + return name, shortenedName or name; + end + end + end + end + + return "Unknown", "Unknown"; + end + + local function tenchUpdate() + WeakAuras.StartProfileSystem("generictrigger"); + local _, mh_rem, oh_rem + _, mh_rem, mh_charges, mh_EnchantID, _, oh_rem, oh_charges, oh_EnchantID = GetWeaponEnchantInfo(); + local time = GetTime(); + local mh_exp_new = mh_rem and (time + (mh_rem / 1000)); + local oh_exp_new = oh_rem and (time + (oh_rem / 1000)); + if(math.abs((mh_exp or 0) - (mh_exp_new or 0)) > 1) then + mh_exp = mh_exp_new; + mh_dur = mh_rem and mh_rem / 1000; + if mh_exp then + mh_name, mh_shortenedName = getTenchName(mh) + else + mh_name, mh_shortenedName = "None", "None" + end + mh_icon = GetInventoryItemTexture("player", mh) + end + if(math.abs((oh_exp or 0) - (oh_exp_new or 0)) > 1) then + oh_exp = oh_exp_new; + oh_dur = oh_rem and oh_rem / 1000; + if oh_exp then + oh_name, oh_shortenedName = getTenchName(oh) + else + oh_name, oh_shortenedName = "None", "None" + end + oh_icon = GetInventoryItemTexture("player", oh) + end + WeakAuras.ScanEvents("TENCH_UPDATE"); + WeakAuras.StopProfileSystem("generictrigger"); + end + + tenchFrame:SetScript("OnEvent", function(self, event, arg1) + WeakAuras.StartProfileSystem("generictrigger"); + if (event == "UNIT_INVENTORY_CHANGED" and arg1 == "player") then + timer:ScheduleTimer(tenchUpdate, 0.1); + end + WeakAuras.StopProfileSystem("generictrigger"); + end); + + tenchUpdate(); + end + end + + function WeakAuras.GetMHTenchInfo() + return mh_exp, mh_dur, mh_name, mh_shortenedName, mh_icon, mh_charges, mh_EnchantID; + end + + function WeakAuras.GetOHTenchInfo() + return oh_exp, oh_dur, oh_name, oh_shortenedName, oh_icon, oh_charges, oh_EnchantID; + end +end + +-- Pets +do + local petFrame = nil + WeakAuras.frames["Pet Use Handler"] = petFrame; + function WeakAuras.WatchForPetDeath() + if not(petFrame) then + petFrame = CreateFrame("frame"); + petFrame:RegisterEvent("UNIT_PET") + petFrame:SetScript("OnEvent", function(_, event, unit) + if unit ~= "player" then return end + WeakAuras.StartProfileSystem("generictrigger") + WeakAuras.ScanEvents("PET_UPDATE", "pet") + WeakAuras.StopProfileSystem("generictrigger") + end) + end + end +end + +-- Player Moving +do + local playerMovingFrame = nil + local moving; + + local function PlayerMoveUpdate(self, event) + WeakAuras.StartProfileSystem("generictrigger"); + -- channeling e.g. Mind Flay results in lots of PLAYER_STARTED_MOVING, PLAYER_STOPPED_MOVING + -- for each frame + -- So check after 0.01 s if IsPlayerMoving() actually returns something different. + timer:ScheduleTimer(function() + WeakAuras.StartProfileSystem("generictrigger"); + if (moving ~= IsPlayerMoving() or moving == nil) then + moving = IsPlayerMoving(); + WeakAuras.ScanEvents("PLAYER_MOVING_UPDATE") + end + WeakAuras.StopProfileSystem("generictrigger"); + end, 0.01); + WeakAuras.StopProfileSystem("generictrigger"); + end + + local function PlayerMoveSpeedUpdate() + WeakAuras.StartProfileSystem("generictrigger"); + local speed = GetUnitSpeed("player") + if speed ~= playerMovingFrame.speed then + playerMovingFrame.speed = speed + WeakAuras.ScanEvents("PLAYER_MOVE_SPEED_UPDATE") + end + WeakAuras.StopProfileSystem("generictrigger"); + end + + function WeakAuras.WatchForPlayerMoving() + if not(playerMovingFrame) then + playerMovingFrame = CreateFrame("frame"); + WeakAuras.frames["Player Moving Frame"] = playerMovingFrame; + end + playerMovingFrame:RegisterEvent("PLAYER_STARTED_MOVING"); + playerMovingFrame:RegisterEvent("PLAYER_STOPPED_MOVING"); + playerMovingFrame:SetScript("OnEvent", PlayerMoveUpdate) + end + + function WeakAuras.WatchPlayerMoveSpeed() + if not(playerMovingFrame) then + playerMovingFrame = CreateFrame("frame"); + WeakAuras.frames["Player Moving Frame"] = playerMovingFrame; + end + playerMovingFrame.speed = GetUnitSpeed("player") + playerMovingFrame:SetScript("OnUpdate", PlayerMoveSpeedUpdate) + end +end + +-- Item Count +local itemCountWatchFrame; +function WeakAuras.RegisterItemCountWatch() + if not(itemCountWatchFrame) then + itemCountWatchFrame = CreateFrame("frame"); + itemCountWatchFrame:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player"); + itemCountWatchFrame:SetScript("OnEvent", function() + WeakAuras.StartProfileSystem("generictrigger"); + timer:ScheduleTimer(WeakAuras.ScanEvents, 0.2, "ITEM_COUNT_UPDATE"); + timer:ScheduleTimer(WeakAuras.ScanEvents, 0.5, "ITEM_COUNT_UPDATE"); + WeakAuras.StopProfileSystem("generictrigger"); + end); + end +end + +do + local scheduled_scans = {}; + + local function doScan(fireTime, event) + WeakAuras.debug("Performing scan at "..fireTime.." ("..GetTime()..") " .. event); + scheduled_scans[event][fireTime] = nil; + WeakAuras.ScanEvents(event); + end + function WeakAuras.ScheduleScan(fireTime, event) + event = event or "COOLDOWN_REMAINING_CHECK" + scheduled_scans[event] = scheduled_scans[event] or {} + if not(scheduled_scans[event][fireTime]) then + WeakAuras.debug("Scheduled scan at " .. fireTime .. " for event " .. event); + scheduled_scans[event][fireTime] = timer:ScheduleTimerFixed(doScan, fireTime - GetTime() + 0.1, fireTime, event); + end + end +end + +do + local scheduled_scans = {}; + + local function doCastScan(firetime, unit) + WeakAuras.debug("Performing cast scan at "..firetime.." ("..GetTime()..")"); + scheduled_scans[unit][firetime] = nil; + WeakAuras.ScanEvents("CAST_REMAINING_CHECK", unit); + end + function WeakAuras.ScheduleCastCheck(fireTime, unit) + scheduled_scans[unit] = scheduled_scans[unit] or {} + if not(scheduled_scans[unit][fireTime]) then + WeakAuras.debug("Scheduled cast scan at "..fireTime); + scheduled_scans[unit][fireTime] = timer:ScheduleTimerFixed(doCastScan, fireTime - GetTime() + 0.1, fireTime, unit); + end + end +end + +local uniqueId = 0; +function WeakAuras.GetUniqueCloneId() + uniqueId = (uniqueId + 1) % 1000000; + return uniqueId; +end + +function GenericTrigger.CanHaveDuration(data, triggernum) + local trigger = data.triggers[triggernum].trigger + + if (trigger.type == "event" or trigger.type == "status") then + if trigger.event and WeakAuras.event_prototypes[trigger.event] then + if WeakAuras.event_prototypes[trigger.event].durationFunc then + if(type(WeakAuras.event_prototypes[trigger.event].init) == "function") then + WeakAuras.event_prototypes[trigger.event].init(trigger); + end + local current, maximum, custom = WeakAuras.event_prototypes[trigger.event].durationFunc(trigger); + current = type(current) ~= "number" and current or 0 + maximum = type(maximum) ~= "number" and maximum or 0 + if(custom) then + return {current = current, maximum = maximum}; + else + return "timed"; + end + elseif WeakAuras.event_prototypes[trigger.event].canHaveDuration then + return WeakAuras.event_prototypes[trigger.event].canHaveDuration + end + end + if trigger.unevent == "timed" and trigger.duration then + return "timed" + end + elseif (trigger.type == "custom") then + if trigger.custom_type == "event" and trigger.custom_hide == "timed" and trigger.duration then + return "timed"; + elseif (trigger.customDuration and trigger.customDuration ~= "") then + return "timed"; + elseif (trigger.custom_type == "stateupdate") then + return "timed"; + end + end + return false +end + +--- Returns a table containing the names of all overlays +-- @param data +-- @param triggernum +function GenericTrigger.GetOverlayInfo(data, triggernum) + local result; + + local trigger = data.triggers[triggernum].trigger + + if (trigger.type ~= "custom" and trigger.event and WeakAuras.event_prototypes[trigger.event] and WeakAuras.event_prototypes[trigger.event].overlayFuncs) then + result = {}; + local dest = 1; + for i, v in ipairs(WeakAuras.event_prototypes[trigger.event].overlayFuncs) do + if (v.enable(trigger)) then + result[dest] = v.name; + dest = dest + 1; + end + end + end + + if (trigger.type == "custom") then + if (trigger.custom_type == "stateupdate") then + local count = 0; + local variables = events[data.id][triggernum].tsuConditionVariables(); + if (type(variables) == "table") then + if (type(variables.additionalProgress) == "table") then + count = #variables.additionalProgress; + elseif (type(variables.additionalProgress) == "number") then + count = variables.additionalProgress; + end + else + local allStates = {}; + WeakAuras.ActivateAuraEnvironment(data.id); + RunTriggerFunc(allStates, events[data.id][triggernum], data.id, triggernum, "OPTIONS"); + WeakAuras.ActivateAuraEnvironment(nil); + local count = 0; + for id, state in pairs(allStates) do + if (state.additionalProgress) then + count = max(count, #state.additionalProgress); + end + end + end + + count = min(count, 7); + for i = 1, count do + result = result or {}; + result[i] = string.format(L["Overlay %s"], i); + end + else + for i = 1, 7 do + local property = "customOverlay" .. i; + if (trigger[property] and trigger[property] ~= "") then + result = result or {}; + result[i] = string.format(L["Overlay %s"], i); + end + end + end + end + + return result; +end + +function GenericTrigger.CanHaveAuto(data, triggernum) + -- Is also called on importing before conversion, so do a few checks + local trigger = data.triggers[triggernum].trigger + + if (not trigger) then + return false; + end + if( + ( + ( + trigger.type == "event" + or trigger.type == "status" + ) + and trigger.event + and WeakAuras.event_prototypes[trigger.event] + and ( + WeakAuras.event_prototypes[trigger.event].iconFunc + or WeakAuras.event_prototypes[trigger.event].canHaveAuto + ) + ) + or ( + trigger.type == "custom" + and (( + trigger.customIcon + and trigger.customIcon ~= "" + ) or trigger.custom_type == "stateupdate") + ) + ) then + return true; + else + return false; + end +end + +function GenericTrigger.CanHaveClones(data) + return false; +end + +function GenericTrigger.GetNameAndIcon(data, triggernum) + local trigger = data.triggers[triggernum].trigger + local icon, name + if (trigger.type == "event" or trigger.type == "status") then + if(trigger.event and WeakAuras.event_prototypes[trigger.event]) then + if(WeakAuras.event_prototypes[trigger.event].iconFunc) then + icon = WeakAuras.event_prototypes[trigger.event].iconFunc(trigger); + end + if(WeakAuras.event_prototypes[trigger.event].nameFunc) then + name = WeakAuras.event_prototypes[trigger.event].nameFunc(trigger); + end + end + end + + return name, icon +end + +---Returns the type of tooltip to show for the trigger. +-- @param data +-- @param triggernum +-- @return string +function GenericTrigger.CanHaveTooltip(data, triggernum) + local trigger = data.triggers[triggernum].trigger + if (trigger.type == "event" or trigger.type == "status") then + if (trigger.event and WeakAuras.event_prototypes[trigger.event]) then + if(WeakAuras.event_prototypes[trigger.event].hasSpellID) then + return "spell"; + elseif(WeakAuras.event_prototypes[trigger.event].hasItemID) then + return "item"; + end + end + end + + if (trigger.type == "custom") then + if (trigger.custom_type == "stateupdate") then + return true; + end + end + + return false; +end + +function GenericTrigger.SetToolTip(trigger, state) + if (trigger.type == "custom" and trigger.custom_type == "stateupdate") then + if (state.tooltip) then + local lines = { strsplit("\n", state.tooltip) }; + GameTooltip:ClearLines(); + for i, line in ipairs(lines) do + GameTooltip:AddLine(line); + end + return true + elseif (state.spellId) then + GameTooltip:SetSpellByID(state.spellId); + return true + elseif (state.link) then + GameTooltip:SetHyperlink(state.link); + return true + elseif (state.itemId) then + GameTooltip:SetHyperlink("item:"..state.itemId..":0:0:0:0:0:0:0"); + return true + elseif (state.unit and state.unitBuffIndex) then + GameTooltip:SetUnitBuff(state.unit, state.unitBuffIndex); + return true + elseif (state.unit and state.unitDebuffIndex) then + GameTooltip:SetUnitDebuff(state.unit, state.unitDebuffIndex); + return true + end + end + + if (trigger.type == "event" or trigger.type == "status") then + if (trigger.event and WeakAuras.event_prototypes[trigger.event]) then + if(WeakAuras.event_prototypes[trigger.event].hasSpellID) then + GameTooltip:SetSpellByID(trigger.spellName); + return true + elseif(WeakAuras.event_prototypes[trigger.event].hasItemID) then + GameTooltip:SetHyperlink("item:"..trigger.itemName..":0:0:0:0:0:0:0") + return true + end + end + end + return false +end + +function GenericTrigger.GetAdditionalProperties(data, triggernum) + local trigger = data.triggers[triggernum].trigger + local ret = ""; + if (trigger.type == "event" or trigger.type == "status") then + if (trigger.event and WeakAuras.event_prototypes[trigger.event]) then + local found = false; + local additional = "" + for _, v in pairs(WeakAuras.event_prototypes[trigger.event].args) do + local enable = true + if(type(v.enable) == "function") then + enable = v.enable(trigger) + elseif type(v.enable) == "boolean" then + enable = v.enable + end + if (enable and v.store and v.name and v.display) then + found = true; + additional = additional .. "|cFFFF0000%".. triggernum .. "." .. v.name .. "|r - " .. v.display .. "\n"; + end + end + + if (found) then + ret = ret .. additional; + end + end + end + + return ret; +end + +local commonConditions = { + expirationTime = { + display = L["Remaining Duration"], + type = "timer", + }, + duration = { + display = L["Total Duration"], + type = "number", + }, + value = { + display = L["Progress Value"], + type = "number", + }, + total = { + display = L["Progress Total"], + type = "number", + }, + stacks = { + display = L["Stacks"], + type = "number" + }, + name = { + display = L["Name"], + type = "string" + } +} + +function GenericTrigger.GetTriggerConditions(data, triggernum) + local trigger = data.triggers[triggernum].trigger + + if (trigger.type == "event" or trigger.type == "status") then + if (trigger.event and WeakAuras.event_prototypes[trigger.event]) then + local result = {}; + + local canHaveDuration = GenericTrigger.CanHaveDuration(data, triggernum); + local timedDuration = canHaveDuration; + local valueDuration = canHaveDuration; + if (canHaveDuration == "timed") then + valueDuration = false; + elseif (type(canHaveDuration) == "table") then + timedDuration = false; + end + + if (timedDuration) then + result.expirationTime = commonConditions.expirationTime; + result.duration = commonConditions.duration; + end + + if (valueDuration) then + result.value = commonConditions.value; + result.total = commonConditions.total; + end + + if (WeakAuras.event_prototypes[trigger.event].stacksFunc) then + result.stacks = commonConditions.stacks; + end + + if (WeakAuras.event_prototypes[trigger.event].nameFunc) then + result.name = commonConditions.name; + end + + for _, v in pairs(WeakAuras.event_prototypes[trigger.event].args) do + if (v.conditionType and v.name and v.display) then + local enable = true; + if (v.enable) then + if type(v.enable) == "function" then + enable = v.enable(trigger); + elseif type(v.enable) == "boolean" then + enable = v.enable + end + end + + if (enable) then + result[v.name] = { + display = v.display, + type = v.conditionType + } + if (result[v.name].type == "select" or result[v.name].type == "unit") then + if (v.conditionValues) then + result[v.name].values = WeakAuras[v.conditionValues]; + else + if type(v.values) == "function" then + result[v.name].values = v.values() + else + result[v.name].values = WeakAuras[v.values]; + end + end + end + if (v.conditionTest) then + result[v.name].test = v.conditionTest; + end + if (v.conditionEvents) then + result[v.name].events = v.conditionEvents; + end + if (v.operator_types_without_equal) then + result[v.name].operator_types_without_equal = true; + elseif (v.operator_types_only_equal) then + result[v.name].operator_types_only_equal = true; + end + end + end + end + + return result; + end + elseif(trigger.type == "custom") then + if (trigger.custom_type == "status" or trigger.custom_type == "event") then + local result = {}; + + local canHaveDurationFunc = trigger.custom_type == "status" or (trigger.custom_type == "event" and (trigger.custom_hide ~= "timed" or trigger.dynamicDuration)); + + if (canHaveDurationFunc and trigger.customDuration and trigger.customDuration ~= "") then + result.expirationTime = commonConditions.expirationTime; + result.duration = commonConditions.duration; + result.value = commonConditions.value; + result.total = commonConditions.total; + end + + if (trigger.custom_type == "event" and trigger.custom_hide ~= "custom" and trigger.dynamicDuration ~= true) then + -- This is the static duration of a event/timed trigger + result.expirationTime = commonConditions.expirationTime; + result.duration = commonConditions.duration; + end + + if (trigger.customStacks and trigger.customStacks ~= "") then + result.stacks = commonConditions.stacks; + end + + if (trigger.customName and trigger.customName ~= "") then + result.name = commonConditions.name; + end + + return result; + elseif (trigger.custom_type == "stateupdate") then + if (events[data.id][triggernum] and events[data.id][triggernum].tsuConditionVariables) then + WeakAuras.ActivateAuraEnvironment(data.id, nil, nil, nil, true) + local result = events[data.id][triggernum].tsuConditionVariables() + WeakAuras.ActivateAuraEnvironment(nil) + if (type(result)) ~= "table" then + return nil; + end + -- Make the life of tsu authors easier, by automatically filling in the details for + -- expirationTime, duration, value, total, stacks, if those exists but aren't a table value + -- By allowing a short-hand notation of just variable = type + -- In addition to the long form of variable = { type = xyz, display = "desc"} + for k, v in pairs(commonConditions) do + if (result[k] and type(result[k]) ~= "table") then + result[k] = v; + end + end + + for k, v in pairs(result) do + if (type(v) == "string") then + result[k] = { + display = k, + type = v, + }; + end + end + + for k, v in pairs(result) do + if (type(v) ~= "table") then + result[k] = nil; + elseif (v.display == nil or type(v.display) ~= "string") then + if (type(k) == "string") then + v.display = k; + else + result[k] = nil; + end + end + end + + return result; + end + end + end + + return nil; +end + +function GenericTrigger.CreateFallbackState(data, triggernum, state) + state.show = true; + state.changed = true; + local event = events[data.id][triggernum]; + + WeakAuras.ActivateAuraEnvironment(data.id, "", state); + local firstTrigger = data.triggers[1].trigger + if (event.nameFunc) then + local name = event.nameFunc(firstTrigger); + state.name = name or nil; + end + if (event.iconFunc) then + local icon = event.iconFunc(firstTrigger); + state.icon = icon or nil; + end + + if (event.textureFunc ) then + local texture = event.textureFunc(firstTrigger); + state.texture = texture or nil; + end + + if (event.stacksFunc) then + local stacks = event.stacksFunc(firstTrigger); + state.stacks = stacks or nil; + end + + if (event.durationFunc) then + local arg1, arg2, arg3, inverse = event.durationFunc(firstTrigger); + arg1 = type(arg1) == "number" and arg1 or 0; + arg2 = type(arg2) == "number" and arg2 or 0; + + if(type(arg3) == "string") then + state.durationFunc = event.durationFunc; + elseif (type(arg3) == "function") then + state.durationFunc = arg3; + else + state.durationFunc = nil; + end + + if (arg3) then + state.progressType = "static"; + state.duration = nil; + state.expirationTime = nil; + state.value = arg1; + state.total = arg2; + state.inverse = inverse; + else + state.progressType = "timed"; + state.duration = arg1; + state.expirationTime = arg2; + state.autoHide = nil; + state.value = nil; + state.total = nil; + state.inverse = inverse; + end + else + state.progressType = "timed"; + state.duration = 0; + state.expirationTime = math.huge; + state.value = nil; + state.total = nil; + end + if (event.overlayFuncs) then + RunOverlayFuncs(event, state); + end + WeakAuras.ActivateAuraEnvironment(nil); +end + +function GenericTrigger.GetName(triggerType) + if (triggerType == "status") then + return L["Status"]; + end + if (triggerType == "event") then + return L["Event"]; + end + if (triggerType == "custom") then + return L["Custom"]; + end +end + +function GenericTrigger.GetTriggerDescription(data, triggernum, namestable) + local trigger = data.triggers[triggernum].trigger + if(trigger.type == "event" or trigger.type == "status") then + if(trigger.type == "event") then + tinsert(namestable, {L["Trigger:"], (WeakAuras.event_types[trigger.event] or L["Undefined"])}); + else + tinsert(namestable, {L["Trigger:"], (WeakAuras.status_types[trigger.event] or L["Undefined"])}); + end + if(trigger.event == "Combat Log" and trigger.subeventPrefix and trigger.subeventSuffix) then + tinsert(namestable, {L["Message type:"], (WeakAuras.subevent_prefix_types[trigger.subeventPrefix] or L["Undefined"]).." "..(WeakAuras.subevent_suffix_types[trigger.subeventSuffix] or L["Undefined"])}); + end + else + tinsert(namestable, {L["Trigger:"], L["Custom"]}); + end +end + + + +WeakAuras.RegisterTriggerSystem({"event", "status", "custom"}, GenericTrigger); diff --git a/WeakAuras/History.lua b/WeakAuras/History.lua new file mode 100644 index 0000000..07356b0 --- /dev/null +++ b/WeakAuras/History.lua @@ -0,0 +1,76 @@ + + +if not WeakAuras.IsCorrectVersion() then return end + +local WeakAuras = WeakAuras + +local histRepo, migrationRepo +local function loadHistory() + if not histRepo then + histRepo = WeakAuras.LoadFromArchive("Repository", "history") + end + return histRepo +end + +local function loadMigrations() + if not migrationRepo then + migrationRepo = WeakAuras.LoadFromArchive("Repository", "migration") + end + return migrationRepo +end + +function WeakAuras.CleanArchive(historyCutoff, migrationCutoff) + if type(historyCutoff) == "number" then + local repo = loadHistory() + local cutoffTime = time() - (historyCutoff * 86400) + for uid, subStore in pairs(repo.stores) do + -- Ideally we would just use Clean and not access the stores list directly, + -- but that'd mean having Clean take a predicate which seems like overkill for the moment + if not WeakAuras.GetDataByUID(uid) and subStore.timestamp < cutoffTime then + repo:Drop(uid) + end + end + end + + if type(migrationCutoff) == "number" then + local repo = loadMigrations() + repo:Clean(time() - (migrationCutoff * 86400)) + end +end + +function WeakAuras.SetHistory(uid, data, source, addon) + if uid and data then + local repo = loadHistory() + data.source = source + data.addon = source == "addon" and addon or nil + local hist = repo:Set(uid, data, true) + return hist + end +end + +function WeakAuras.GetHistory(uid, load) + return loadHistory():Get(uid, load) +end + +function WeakAuras.RemoveHistory(uid) + return loadHistory():Drop(uid) +end + +function WeakAuras.RestoreFromHistory(uid) + local _, histData = WeakAuras.GetHistory(uid, true) + if histData then + WeakAuras.Add(histData) + end +end + +function WeakAuras.SetMigrationSnapshot(uid, oldData) + if type(oldData) == "table" then + local repo = loadMigrations() + repo:Set(uid, oldData) + end +end + +function WeakAuras.GetMigrationSnapshot(uid) + return loadMigrations():GetData(uid) +end + diff --git a/WeakAuras/Init.lua b/WeakAuras/Init.lua new file mode 100644 index 0000000..b836416 --- /dev/null +++ b/WeakAuras/Init.lua @@ -0,0 +1,78 @@ +WeakAuras = {} +WeakAuras.L = {} +WeakAuras.frames = {} + +WeakAuras.normalWidth = 1.25 +WeakAuras.halfWidth = WeakAuras.normalWidth / 2 +WeakAuras.doubleWidth = WeakAuras.normalWidth * 2 + +local versionStringFromToc = GetAddOnMetadata("WeakAuras", "Version") +local versionString = "2.17.4" +local buildTime = "20200422171414" + +WeakAuras.versionString = versionStringFromToc +WeakAuras.buildTime = buildTime +WeakAuras.printPrefix = "|cff9900ffWeakAuras:|r " +WeakAuras.newFeatureString = "|TInterface\\OptionsFrame\\UI-OptionsFrame-NewFeatureIcon:0|t" +WeakAuras.BuildInfo = select(4, GetBuildInfo()) + +function WeakAuras.IsClassic() + return false +end + +function WeakAuras.IsCorrectVersion() + return true +end + +WeakAuras.prettyPrint = function(msg) + print(WeakAuras.printPrefix .. msg) +end + +WeakAuras.versionMismatchPrint = function() + WeakAuras.prettyPrint("You need to restart your game client to complete the WeakAuras update!") +end + +if versionString ~= versionStringFromToc and versionStringFromToc ~= "Dev" then + C_Timer:After(1, WeakAuras.versionMismatchPrint) +end + +WeakAuras.PowerAurasPath = "Interface\\Addons\\WeakAuras\\PowerAurasMedia\\Auras\\" +WeakAuras.PowerAurasSoundPath = "Interface\\Addons\\WeakAuras\\PowerAurasMedia\\Sounds\\" + +-- force enable WeakAurasCompanion and Archive because some addon managers interfere with it +EnableAddOn("WeakAurasCompanion") +EnableAddOn("WeakAurasArchive") + +--These function stubs are defined here to reduce the number of errors that occur if WeakAuras.lua fails to compile +function WeakAuras.RegisterRegionType() +end + +function WeakAuras.RegisterRegionOptions() +end + +function WeakAuras.StartProfileSystem() +end + +function WeakAuras.StartProfileAura() +end + +function WeakAuras.StopProfileSystem() +end + +function WeakAuras.StopProfileAura() +end + +-- if weakauras shuts down due to being installed on the wrong target, keep the bindings from erroring +function WeakAuras.StartProfile() +end + +function WeakAuras.StopProfile() +end + +function WeakAuras.PrintProfile() +end + +function WeakAuras.CountWagoUpdates() + -- XXX this is to work around the Companion app trying to use our stuff! + return 0 +end diff --git a/WeakAuras/LICENSE b/WeakAuras/LICENSE new file mode 100644 index 0000000..23cb790 --- /dev/null +++ b/WeakAuras/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/WeakAuras/Libs/AceComm-3.0/AceComm-3.0.lua b/WeakAuras/Libs/AceComm-3.0/AceComm-3.0.lua new file mode 100644 index 0000000..00e713b --- /dev/null +++ b/WeakAuras/Libs/AceComm-3.0/AceComm-3.0.lua @@ -0,0 +1,308 @@ +--- **AceComm-3.0** allows you to send messages of unlimited length over the addon comm channels. +-- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\ +-- **ChatThrottleLib** is of course being used to avoid being disconnected by the server. +-- +-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by +-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object +-- and can be accessed directly, without having to explicitly call AceComm itself.\\ +-- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you +-- make into AceComm. +-- @class file +-- @name AceComm-3.0 +-- @release $Id$ + +--[[ AceComm-3.0 + +TODO: Time out old data rotting around from dead senders? Not a HUGE deal since the number of possible sender names is somewhat limited. + +]] + +local CallbackHandler = LibStub("CallbackHandler-1.0") +local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib") + +local MAJOR, MINOR = "AceComm-3.0", 12 +local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceComm then return end + +-- Lua APIs +local type, next, pairs, tostring = type, next, pairs, tostring +local strsub, strfind = string.sub, string.find +local tinsert, tconcat = table.insert, table.concat +local error, assert = error, assert + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler + +AceComm.embeds = AceComm.embeds or {} + +-- for my sanity and yours, let's give the message type bytes some names +local MSG_MULTI_FIRST = "\001" +local MSG_MULTI_NEXT = "\002" +local MSG_MULTI_LAST = "\003" + +AceComm.multipart_origprefixes = AceComm.multipart_origprefixes or {} -- e.g. "Prefix\001"="Prefix", "Prefix\002"="Prefix" +AceComm.multipart_reassemblers = AceComm.multipart_reassemblers or {} -- e.g. "Prefix\001"="OnReceiveMultipartFirst" + +-- the multipart message spool: indexed by a combination of sender+distribution+ +AceComm.multipart_spool = AceComm.multipart_spool or {} + +--- Register for Addon Traffic on a specified prefix +-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent) +-- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived" +function AceComm:RegisterComm(prefix, method) + if method == nil then + method = "OnCommReceived" + end + + return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler +end + +local warnedPrefix=false + +--- Send a message over the Addon Channel +-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent) +-- @param text Data to send, nils (\000) not allowed. Any length. +-- @param distribution Addon channel, e.g. "RAID", "GUILD", etc; see SendAddonMessage API +-- @param target Destination for some distributions; see SendAddonMessage API +-- @param prio OPTIONAL: ChatThrottleLib priority, "BULK", "NORMAL" or "ALERT". Defaults to "NORMAL". +-- @param callbackFn OPTIONAL: callback function to be called as each chunk is sent. receives 3 args: the user supplied arg (see next), the number of bytes sent so far, and the number of bytes total to send. +-- @param callbackArg: OPTIONAL: first arg to the callback function. nil will be passed if not specified. +function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callbackFn, callbackArg) + prio = prio or "NORMAL" -- pasta's reference implementation had different prio for singlepart and multipart, but that's a very bad idea since that can easily lead to out-of-sequence delivery! + if not( type(prefix)=="string" and + type(text)=="string" and + type(distribution)=="string" and + (target==nil or type(target)=="string" or type(target)=="number") and + (prio=="BULK" or prio=="NORMAL" or prio=="ALERT") + ) then + error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2) + end + + if strfind(prefix, "[\001-\009]") then + if strfind(prefix, "[\001-\003]") then + error("SendCommMessage: Characters \\001--\\003 in prefix are reserved for AceComm metadata", 2) + elseif not warnedPrefix then + -- I have some ideas about future extensions that require more control characters /mikk, 20090808 + geterrorhandler()("SendCommMessage: Heads-up developers: Characters \\004--\\009 in prefix are reserved for AceComm future extension") + warnedPrefix = true + end + end + + + local textlen = #text + local maxtextlen = 254 - #prefix -- 254 is the max length of prefix + text that can be sent in one message + local queueName = prefix..distribution..(target or "") + + local ctlCallback = nil + if callbackFn then + ctlCallback = function(sent) + return callbackFn(callbackArg, sent, textlen) + end + end + + if textlen <= maxtextlen then + -- fits all in one message + CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen) + else + maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix + + -- first part + local chunk = strsub(text, 1, maxtextlen) + CTL:SendAddonMessage(prio, prefix..MSG_MULTI_FIRST, chunk, distribution, target, queueName, ctlCallback, maxtextlen) + + -- continuation + local pos = 1+maxtextlen + local prefix2 = prefix..MSG_MULTI_NEXT + + while pos+maxtextlen <= textlen do + chunk = strsub(text, pos, pos+maxtextlen-1) + CTL:SendAddonMessage(prio, prefix2, chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1) + pos = pos + maxtextlen + end + + -- final part + chunk = strsub(text, pos) + CTL:SendAddonMessage(prio, prefix..MSG_MULTI_LAST, chunk, distribution, target, queueName, ctlCallback, textlen) + end +end + + +---------------------------------------- +-- Message receiving +---------------------------------------- + +do + local compost = setmetatable({}, {__mode = "k"}) + local function new() + local t = next(compost) + if t then + compost[t]=nil + for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten + t[i]=nil + end + return t + end + + return {} + end + + local function lostdatawarning(prefix,sender,where) + DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")") + end + + function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender) + local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender + local spool = AceComm.multipart_spool + + --[[ + if spool[key] then + lostdatawarning(prefix,sender,"First") + -- continue and overwrite + end + --]] + + spool[key] = message -- plain string for now + end + + function AceComm:OnReceiveMultipartNext(prefix, message, distribution, sender) + local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender + local spool = AceComm.multipart_spool + local olddata = spool[key] + + if not olddata then + --lostdatawarning(prefix,sender,"Next") + return + end + + if type(olddata)~="table" then + -- ... but what we have is not a table. So make it one. (Pull a composted one if available) + local t = new() + t[1] = olddata -- add old data as first string + t[2] = message -- and new message as second string + spool[key] = t -- and put the table in the spool instead of the old string + else + tinsert(olddata, message) + end + end + + function AceComm:OnReceiveMultipartLast(prefix, message, distribution, sender) + local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender + local spool = AceComm.multipart_spool + local olddata = spool[key] + + if not olddata then + --lostdatawarning(prefix,sender,"End") + return + end + + spool[key] = nil + + if type(olddata) == "table" then + -- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat + tinsert(olddata, message) + AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender) + compost[olddata] = true + else + -- if we've only received a "first", the spooled data will still only be a string + AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender) + end + end +end + + + + + + +---------------------------------------- +-- Embed CallbackHandler +---------------------------------------- + +if not AceComm.callbacks then + -- ensure that 'prefix to watch' table is consistent with registered + -- callbacks + AceComm.__prefixes = {} + + AceComm.callbacks = CallbackHandler:New(AceComm, + "_RegisterComm", + "UnregisterComm", + "UnregisterAllComm") +end + +function AceComm.callbacks:OnUsed(target, prefix) + AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = prefix + AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = "OnReceiveMultipartFirst" + + AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = prefix + AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = "OnReceiveMultipartNext" + + AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = prefix + AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = "OnReceiveMultipartLast" +end + +function AceComm.callbacks:OnUnused(target, prefix) + AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = nil + AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = nil + + AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = nil + AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = nil + + AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = nil + AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = nil +end + +local function OnEvent(this, event, ...) + if event == "CHAT_MSG_ADDON" then + local prefix,message,distribution,sender = ... + local reassemblername = AceComm.multipart_reassemblers[prefix] + if reassemblername then + -- multipart: reassemble + local aceCommReassemblerFunc = AceComm[reassemblername] + local origprefix = AceComm.multipart_origprefixes[prefix] + aceCommReassemblerFunc(AceComm, origprefix, message, distribution, sender) + else + -- single part: fire it off immediately and let CallbackHandler decide if it's registered or not + AceComm.callbacks:Fire(prefix, message, distribution, sender) + end + else + assert(false, "Received "..tostring(event).." event?!") + end +end + +AceComm.frame = AceComm.frame or CreateFrame("Frame", "AceComm30Frame") +AceComm.frame:SetScript("OnEvent", OnEvent) +AceComm.frame:UnregisterAllEvents() +AceComm.frame:RegisterEvent("CHAT_MSG_ADDON") + + +---------------------------------------- +-- Base library stuff +---------------------------------------- + +local mixins = { + "RegisterComm", + "UnregisterComm", + "UnregisterAllComm", + "SendCommMessage", +} + +-- Embeds AceComm-3.0 into the target object making the functions from the mixins list available on target:.. +-- @param target target object to embed AceComm-3.0 in +function AceComm:Embed(target) + for k, v in pairs(mixins) do + target[v] = self[v] + end + self.embeds[target] = true + return target +end + +function AceComm:OnEmbedDisable(target) + target:UnregisterAllComm() +end + +-- Update embeds +for target, v in pairs(AceComm.embeds) do + AceComm:Embed(target) +end diff --git a/WeakAuras/Libs/AceComm-3.0/AceComm-3.0.xml b/WeakAuras/Libs/AceComm-3.0/AceComm-3.0.xml new file mode 100644 index 0000000..09e8d87 --- /dev/null +++ b/WeakAuras/Libs/AceComm-3.0/AceComm-3.0.xml @@ -0,0 +1,5 @@ + +