Page 51 of 111 FirstFirst ... 41495051525361101 ... LastLast
Results 501 to 510 of 1109

Thread: WeakAuras Tutoring Thread

  1. #501

    Default

    Quote Originally Posted by Xyliah View Post
    Time has passed since my first posting here. Since then my lua knowledge increased by quite a bit I would say, but still I haven’t really a clue about efficieny and efficient use of events.

    Lets take a scenario from my mind (COMBAT_LOG_EVENT_UNFILTERED compared to UNIT_AURA)

    Basically my logic is this:
    • less UNIT_AURA events mean less execution of my script mean less usage of memory
    • whereas CLEU is fired way more often, so it should use more memory
    • my conclusion: if I could use UNIT_AURA events to accomplish my aims, it would be more efficient then CLEU
    Some interesting topics for discussion to be sure and I'll provide what information I can on the subjects to hopefully put you in the right direction.

    First and foremost, it is very important to distinguish between the two core concepts of efficiency when discussing addons: Memory and CPU cycles. Most of the time (I'd wager virtually all of the time), when players complain about their addons "using a lot of memory" or wanting to make sure their addons "use low memory", what players really mean is they want to ensure solid performance, and the measurement to indicate performance is almost always (incorrectly) assumed to be memory.

    The reality is, memory and CPU cycles in WoW in regards to Lua are dramatically divergent concepts and generally speaking, the more memory an addon or script takes up, the less CPU cycles it will use (and vice versa). The issue most players face is the notion that addon memory footprints -- or the actual memory in KB or MB that their addon(s) are using at any given time based on the in-game profiler -- is somehow a significant factor on their performance, when very rarely is this the case. In fact, memory usage of addons is almost never a good indicator of the performance impact that addon is having on the game, and instead the real impact comes from the CPU usage. Consider that memory is simply a temporary storage for some form of data, so in the case of addons, this is everything from tables to frames to variables with values. However, Lua is an exceptionally fast scripting language, and additionally, since World of Warcraft 2.0+ which added Lua 5.1 support, it uses an incremental garbage collector for all Lua script. Effectively, this means that memory that was briefly used by Lua for a period is constantly being collected and recycled, and as such, even spikes in memory from an addon or script will be quickly reclaimed by the garbage collector in very short order.

    Therefore, the actual memory usage of an addon is only relevant if it is at risk of causing your memory usage for your entire computer to cap out, which is very unlikely these days with most people usually on 4+ gigs or more. The impact of a single addon, even a very expensive and highly inefficient addon, is rarely going to be over 10-20 megs, which is a pittance compared to everything else that is happening on your computer. For example, if you play wow with a single browser window open in Chrome or Firefox, chances are that single tab will be using more memory that even the worst case WoW addon during it's poor memory spikes prior to garbage collection.

    The far more important impact that addons have on performance is CPU usage. The most blatant case of this is when an addon performs a massive loop or series of functions without throttling of some kind. This is noticed usually when an addon is designed to use an OnUpdate() script assigned to multiple frames, which as you know means the script will run once per OnUpdate per frame on which it attached. To alleviate this, all good addons use some form of throttling to prevent execution of the main script until a certain period of time has elapsed, like so:

    Code:
    function MyFrame:OnUpdate(elapsed)
      self.update.timeSince = (self.update.timeSince or 0) + elapsed
      if (self.update.timeSince > self.db.profile.settings.update.interval) then
        -- Main code
      end
    end

    In this example, a setting of "interval" dictates the timespan after which the next OnUpdate call should proceed with processing, otherwise each call is ignored. While there are a handful of calls made every time OnUpdate runs, they are few enough that performance is not affected, since effectively all that is being done is assigning our new timeSince value to hold the time since we've last processed, and then checking that value against our interval threshold to allow further processing or not.

    Or an even better example that is rarely seen in-game because it would be noticed by an author right away but is a good illustration, is to create a never-ending loop. Just like the OnUpdate example that doesn't include a timeSince throttle gate, a perpetual loop will freeze the CPU and hijack nearly all cycles until the game crashes (very quickly thereafter):

    Code:
    function()
      while true do end
    end

    That said, the majority of addons will have a CPU usage profile far below these examples, even those that utilize OnUpdate frequently. This is again because Lua is so efficient and fast, that only a process so lengthy or complicated as to take longer than full OnUpdate cycle (i.e. frame refresh) will have a negative impact on performance when tied to said OnUpdate script. Now that is certainly possible with a poorly written addon and therefore OnUpdate function, but generally speaking most OnUpdate calls related to an existing addon (such as WeakAuras) will have a throttle of some kind in effect.

    What this all ultimately means is that worrying too much about optimization for WoW addons/Lua is unnecessary.



    Having said all that, there are certainly some tools available to you to help analyze your code if you want to truly focus on optimization, just keep in mind this is rarely worth the effort except in extreme cases.

    Profiling

    You can profile both the CPU and memory usage within WoW by toggling it with the command: /console set scriptProfile "1" (then reload your UI for it to take effect).

    Now you can use a variety of functions for CPU & memory, such as:

    ResetCPUUsage()

    UpdateAddOnCPUUsage()

    GetAddOnCPUUsage(addon)

    GetFunctionCPUUsage(func, subs)

    GetFrameCPUUsage(frame, children)

    GetEventCPUUsage(event)

    UpdateAddOnMemoryUsage()

    GetAddOnMemoryUsage(addon)

    They're all pretty self-explanatory by the names and parameters, but the basic usage is once you enable profiling, you then must make a call to the UpdateAddOnXXXUsage() function first, then you can call any of the other GetXXXUsage functions afterward to retrieve that data.

    As for your specific examples of using CLEU vs. UNIT_AURA, they are a little confusing. You make the original (correct) claim that UNIT_AURA will be executed less-frequently than CLEU, since by its very nature, CLEU will include all events that would trigger UNIT_AURA. However, later in you example of your new function, you mentioned changing it to utilize CLEU due to an inability to track aura refresh. However, I am quite certain that UNIT_AURA does in fact track all forms of aura events (gain, fade, refresh, etc) since that event is even what the default Blizzard buff frames utilize.

    Putting that aside for a moment, while you are correct that the initial example of a double-loop does cause a lot of repetition, it isn't as inefficient as you might think. Remember, while this code you included isn't a complete function, as you say I'll assume it is just run in a WeakAura display somewhere and is based on running "every frame", which is tied to the WeakAuras OnUpdate script. I don't know what the default throttle is for OnUpdate in WeakAuras, but you can just start arbitrarily picking values to compare to and see what the results are. If we assume a very fast throttle of 0.05 seconds, this means that we're getting 20 refreshes per second, so the first function above would run 20 times a second in this scenario.

    How does that compare to CLEU though? You might be surprised at how often the combat log is called in a typical raid environment. Below I picked a random log from the middle of the pack in World of Logs, and picked a random page from it showing all combat log events. Starting at the beginning of Page 50 you can see the timestamp and the number of combat log events just piling up. To see the total events fired within a single random second started on Page 50, we have to actually look on the next page, Page 51, to see everything within that 1-second period. All told, between the timestamps 21:53:56.713 and 21:53:57.683, there were a total of 252 CLEU events fired. This means that, on the base of it, even a very fast assumed OnUpdate throttle of 0.05 seconds at 20 times a second is only 8% of the processes per second that we'd get from a CLEU parse in this random sample.

    Therefore, the question then becomes: Is running the first function 8% as often as the second function more or less efficient? That's very difficult to answer by looking at the code and not testing it yourself, but my gut feeling is that the first example will take fewer CPU cycles over time given the infrequency of being called compared to the CLEU example. You could also look through the number of executed lines on average to get a sense as well. The first example, while not as efficient as it could be by a long shot, is very simple.

    Code:
    for i=1,numPlayers do
    A simple loop, very fast to process.

    Code:
    local unit = prefix..i
    A poor use of concatenation instead of StringBuilder/string.format, but using a local variable is always good.

    Code:
    for a=1, 40 do
    Another simple loop call.

    Code:
    local _, _, _, _, _, duration, expirationTime, unitCaster, _, _, spellId = UnitAura(unit, a)
    Function call and local variable assignments.

    Code:
    if spellId and spellId == ReMistsId then
    The first big gate in the code; this is where the majority of loops will fail. So for each of our 1000 iterations in the entire double-loop, the only calls ever made are effectively UnitAura(), the returned variable assignments, and then two value comparisons. Again this code isn't as efficient as it could be (more on that later), but that's a pretty quick-and-dirty way to analyze the efficiency of something, and while a lot of looping seems inefficient, because it's tied to a throttled process of OnUpdate, we're only talking about 20,000 calls a second (sounds high, but isn't).

    The second function is a lot more complicated and thus more difficult to analyze step-by-step, but we start at the top:

    Code:
     if not WA then WA = {} end
        if not WA.HoT then WA.HoT = {} end
        
        local player_guid = UnitGUID('PLAYER')
        local spell = GetSpellInfo(774)
        local now = GetTime()
        
        WA.HoT.targets = WA.HoT.targets or {}
        local HoT_targets = WA.HoT.targets
        
        WA.HoT.ordered_targets = WA.HoT.ordered_targets or {} 
        local HoT_ordered_targets = WA.HoT.ordered_targets
        
        local function SortTable(sourceTable, destTable)
            if type(sourceTable) == 'table' and type(destTable) == 'table' then
                wipe(destTable) 
                for k,v in pairs(sourceTable) do
                    if v >= now then
                        destTable[#destTable + 1] = { k = k, v = v }
                    else
                        sourceTable[k] = nil
                    end
                end
                table.sort(destTable, function(v1, v2) return v1.v < v2.v end)
                for i =1, #destTable do
                    destTable[i] = destTable[i].k
                end
                local count = table.getn(HoT_ordered_targets)
                WA.HoT.count = count
            end
        end
    So early on there are a lot of variable assignments and checks. It should also be noted that WA is a global function and thus inherently is less efficient to use than a local (though often that's unavoidable especially working with WeakAuras, and as mentioned in the beginning, rarely worth worrying over if it cannot be helped). There I count about 14 comparisons and assignments in total.

    Code:
    if select(14, ...) == spell and select(5, ...) == player_guid then
    Next is our first gate, and as with the previous example, the vast majority will not pass this comparison check, so we'll move on.

    Code:
        local count = WA.HoT.count 
        
        if count == 1 then
            WA.HoT.shortest = HoT_targets[HoT_ordered_targets[1]]
        elseif count >= 2 then
            -- smooth animation (some stuttering caused by 'hide on expire' in the main WeakAuras.lua)
            local adjusted = nil
            if time_left(HoT_targets[HoT_ordered_targets[1]]) < 0.1 then
                for i=2, count do -- start loop with 2nd entry
                    if time_left(HoT_targets[HoT_ordered_targets[i]]) > 0.1 then
                        adjusted = i
                        break 
                    end
                end
                if adjusted then
                    WA.HoT.shortest = HoT_targets[HoT_ordered_targets[adjusted]] 
                else
                    WA.HoT.shortest = HoT_targets[HoT_ordered_targets[1]]
                end
            end
        end
        
        if count and count > 0 then
            return true
        end
    Another 5 or so comparisons/assignments to end it out. So if we assume none of the comparison gates ever allow further processing, it's still a safe bet that you're making approximately 20 calls/comparisons in total baseline just by executing this code. Using the random 252 CLEU calls per second mentioned above, this gives a baseline average of about 5,040 calls per second. So that's good right? That's 25% of the calls per second of the first function. However, you must keep in mind this is the baseline and assumes no buffs are ever found. Now your function is clearly more complicated (and hopefully for good reason) than the first function, but this comes at a cost. You are making a lot more expensive calls in your function (again, many calls to globals throughout, sort functions, table creations, etc) especially once a bit of data is actually gathered. For example, once buffs are detected, there are a lot more calls being made (15+) each iteration until that count drops again. Suddenly your call count is 8000-9000+ per second, and again that's baseline from a random log section. What happens when A LOT of stuff is going on and the number of CLEU events for a given period jumps up 4-5 times? If you've ever been in a raid and had a sudden drop in FPS when a lot of stuff is going on, oftentimes that is due to this very issue, where some processing is trying to keep up with the huge number of CLEU events occurring in a short timespan.

    The bottom line is, as mentioned early on, optimization is rarely worth over thinking, as more often than not, you'll simply over complicate your code and make it worse than it was. Even if that's not the case, the extra effort is likely not worth the performance gain, whether that gain is even real or simply perceived. In the case of both your example functions, as I said I would imagine neither is really vastly superior to the other, but that really isn't the point: The point is the calls that fire these functions, which is the real determining factor in how much processing is going on. Almost always, a throttled method is better than a lower-average but higher-spike method. While clearly your custom function is meant to do more than just check for the existence of a particular buff, it does seem overly complex, so again I would caution going too far down that road and causing more harm than good.

    My solution in this case? Use a modified version of the first function, firing from either the UNIT_AURA or OnUpdate event. The biggest change is simply to make a UnitAura call using the spellName instead of the spellId, which then completely eliminates the need for aura looping:

    Code:
    local spellName = GetSpellInfo(spellId)
    for i=1,numPlayers do
        local unit = ('%s%s'):format(prefix, i)
        local _, _, _, _, _, duration, expirationTime, unitCaster = UnitAura(unit, spellName)
        if expirationTime then
            -- Blah blah
        end
    end

    Now we've cleaned up the code a great deal, so our base loops drop from 1000 for our double-loop down to 25 to simply iterate over all the raid members and evaluate if they have the aura in question. With about 5 calls being made each loop iteration plus the extra assignment before the loop, that's 25 * 5 + 1 = 76 * 20 = about 2,520 calls per second if we're using an OnUpdate at 0.05 seconds threshold. Or, we can also use UNIT_AURA which again is risky with high variance depending how many event calls occur in a given period, but it would also dramatically reduce the number of calls made since we can extract the UnitId from the event and therefore not iterate over the 25 raiders:

    Code:
    function(unit)
        local spellName = GetSpellInfo(spellId)
        local unit = ('%s%s'):format(prefix, i)
        local _, _, _, _, _, duration, expirationTime, unitCaster = UnitAura(unit, spellName)
        if expirationTime then
            -- Blah blah
        end
    end

    Now we're down to 5-6 calls per execution in total, which is just however many UNIT_AURA events are in a given second. For just UNIT_AURA events using that same timestamp and log from the previous example, we can see a total of 75 UNIT_AURA events in that same second period. So we're looking at around 375 total calls per second baseline with the above example using UNIT_AURA.

    Anyway, I hope that helps a bit. If you want some more information, I strongly suggest finding a copy of Beginning Lua with World of Warcraft Addons, which is a great (albeit old) book with a ton of useful information about Lua and how it can be utilized in WoW. Unfortunately I wouldn't have the first idea of where you might look for such a thing, but it's a good read if you can manage it.

  2. #502

    Default

    Thanks a lot (!!!) for the quick reply and especially for your detailed post(s).
    Always n1 to see your in-depth knowledge formatted in a good understandable way. Don't wanna butter you up, but you're doing a great job here!

    To your reply: I'm gonna read it carefully and will probably come up with some additional questions. ^.^

  3. #503

    Default

    I'm back again! Do you have any quick triggers that will cause something only to show when a certain number of enemies are within a certain range? I know that's strangely specific and I doubt there is anything, but I thought it was worth an ask.

    Thanks.

  4. #504

    Default

    Quote Originally Posted by ihanken View Post
    I'm back again! Do you have any quick triggers that will cause something only to show when a certain number of enemies are within a certain range? I know that's strangely specific and I doubt there is anything, but I thought it was worth an ask.

    Thanks.
    Unfortunately detecting mob locations, at least beyond the current target, is next to impossible within the WoW API allowed by Blizzard, let alone within WeakAuras itself. Effectively, there is no API function or command that can detect creatures in the vicinity, which I presume was purposely omitted in the API to prevent advanced "play-by-play" instruction coming from addons (imagine being able to detect when 3+ creatures are within the radius of your AoE spell or what not).

    Due to this limitation, the only way to detect creature distance is through some form of targeting or "interacting" with said creature. Thus if you attack, cast a spell on, target, focus, or mouseover a creature, you can then use various API functions to detect the distance from the player, but outside of those circumstances, it cannot be done. Therefore, to track multiple creatures, it would require a system of multiple players, each with a constant target and/or focus of a creature at all times, which could then detect the range from that particular player to that particular creature. However, the current method of determining relative distance from a player to a target/focus/mouseover/etc literally only indicates distance, it does not also indicate direction. Without the ability to determine both the distance AND direction of the creature, our "system" would then further require that each individual creature being tracked is targeted/focused by at least three players at all times, which would allow us to triangulate the position of the creature based on the distance from each of the 3 players, as well as the players' distances from each other.

    Now, extrapolate that idea ever further to track multiple creatures and you can quickly see that ignoring the near-impossible logistics of the players involved to target/track the appropriate creatures for each encounter or pull, it would still require just a massive number of players at a minimum to track most large packs of mobs (3 players for every potential creature).

    So even if you could find a group that could do all that effectively and had a raid large enough to effectively track all creatures, purely creating the system/addon to perform those tasks would be an extremely difficult project (at least for someone like myself).

    TLDR version; Sadly such a vicinity tracker for creatures is close to impossible due to the WoW API limitations. ><

  5. #505

    Default

    I was assuming this was the case due to my very basic knowledge of WoW API capabilities. I have another question that isn't super important but would still be useful on my quest to make a priority rotation group for each class/spec I play. My question: is there a trigger/animation that will cause an entire groups icons to become red from range limitations due to being out of melee/cast range, or does this fall under the same fate as implementing GCD into the icons?

    Thanks.

  6. #506

    Default

    Quote Originally Posted by ihanken View Post
    I was assuming this was the case due to my very basic knowledge of WoW API capabilities. I have another question that isn't super important but would still be useful on my quest to make a priority rotation group for each class/spec I play. My question: is there a trigger/animation that will cause an entire groups icons to become red from range limitations due to being out of melee/cast range, or does this fall under the same fate as implementing GCD into the icons?

    Thanks.
    There is no simple "flag" or option within WeakAuras to easily toggle the look of a Display based on the range to target, so your only real option is this:

    1. Duplicate all Displays.
    2. For each Display Group, Part A would be the "normal" version to show when a target does not exist, or when a target exists but is within X valid range.
    3. Part B would be the "red" version to show when a target exists but is out of range.

    Beyond that, you'd be getting outside of the realm of WeakAuras and thus if range-display is important to you (and if that's the case it's safe to assume these WeakAuras Displays are for abilities anyway), then you might be better served using a hotbar addon such as Dominos or Bartender for those particular abilities and just skinning/sizing/moving them to fit in with your other WeakAuras Displays as necessary. Good luck!

  7. #507

    Default

    I apologize for taking advantage of your WA knowledge so much. I've been able to figure out a couple things based on the stuff you've told me recently, but this one is really stumping me.

    So I'm working on a feral druid rotation much like my monk rotation. The way rip works currently is that you can't refresh it if it was applied when you had greater attack power. So for example, I apply it with Dancing Steel procced. Then, when it has less than 2 seconds, I don't have a proc, so the game literally tells me "a more powerful spell is already active." So since I'm a sucker for Simcraft, I am attempting to emulate the Simcraft rotation within WeakAuras...the super complex advanced one. As such, if anything is just so complicated that it will take you an obscene amount of time to help me with, don't do it unless you have a desire to, because I can deal with the ones I know how to use. I just want to make this as min-max as possible.

    The first step that is stumping me:
    actions.basic+=/rip,if=combo_points>=5&target.health.pct<=25&action.rip.tick_damage%dot.rip.tick_dmg>=1.15.

    It essentially says "Apply Rip when you have 5 CPs, Target has <= 25% health, and the ticks from the new rip will do 15% more damage than your last rip. I'm already realizing that this would require the trigger to read the current debuff for the tick damage and THEN calculate the new tick damage. Is there anyway to make this happen? This one seems like a pretty complex, and possibly impractical, trigger.

    The next problem step: actions.advanced+=/rip,if=combo_points>=5&target.time_to_die>=6&dot.rip.remains<2&(buff.berserk.up|dot.rip.remains+1.9<=cooldown.tigers_fury.remains)

    It says "Rip if combo points >= 5 and rip has less than two seconds remaining and either berserk is up or the time remaining of rip + 1.9 seconds < the cooldown of tiger's fury. Ignore target.time_to_die, unless you know of some super cool way for WeakAuras to actually calculate the target time to die. :P

    The next steps are very similar:

    actions.advanced+=/savage_roar,if=buff.savage_roar.remains<=3&combo_points>0&buff.savage_roar.remains+2>dot.rip.remains

    actions.advanced+=/savage_roar,if=buff.savage_roar.remains<=6&combo_points>=5&buff.savage_roar.remains+2<=dot.rip.remains&dot.rip.ticking

    actions.advanced+=/pool_resource,if=combo_points>=5&!(energy>=50|(buff.berserk.up&energy>=25))&dot.rip.ticking&!(dot.rip.remains-2<=energy.time_to_max-1)&!(buff.savage_roar.remains-3<=energy.time_to_max-1)

    actions.advanced+=/ferocious_bite,if=combo_points>=5&dot.rip.ticking&!(dot.rip.remains-2<=energy.time_to_max-1)&!(buff.savage_roar.remains-3<=energy.time_to_max-1)

    The only problems I have are calculations such as !(dot.rip.remains-2<=energy.time_to_max-1). by the way, a "!" in the front means that this is NOT true.

    Now I have re-referenced the solution you gave me during the creation of my monk rotation:

    Code:
    function() -- Custom Trigger function
         local GetSpellCooldown = function(spell, getDuration)
             if not spell then return end local start, duration, enabled = GetSpellCooldown(spell)
             if not start then return start end if getDuration then return duration end if start == 0 then return start end
             return (start + duration - GetTime())
         end
         -- UPDATED CODE BELOW
         return ((UnitPower('player') + (GetPowerRegen() * GetSpellCooldown('Rising Sun Kick'))) >= 40)
    end
    So I see using something like GetAuraDuration (if that is the right custom trigger) being used in conjunction with your TimetoEnergy snippet. However, I'm not fully sure how to get this done

    The next problem step: actions.basic+=/rake,if=action.rake.tick_damage>action.mangle_cat.hit_damage&action.rake.tick_damage>=dot.rake.tick_dmg

    I think I can handle this one after you tell me how to handle the first problem I posed, unless you see a glaring difference. It just says "Rake if a rake tick has greater damage than a mangle hit and a rake tick has greater or equal damage to a current rake dot tick."

    The next one: actions.basic+=/rake,if=dot.rake.remains<3|action.rake.tick_damage>dot.rake.tick_dmg

    Same trigger used in the one directly above this one, so that should be good.

    You can leave me with as little information as you want, as I would also like to help myself. I just have nearly no idea where to start on these. Because of it's complexity, I think I will be releasing this WeakAuras grouping to the public if I can successfully implement the advanced rotation. This is a large request, so just help out at your convenience, if you so desire to in the first place.

    Thanks so much for all the help.
    Last edited by ihanken; 08-09-2013 at 04:09 PM.

  8. #508

    Default

    Quote Originally Posted by Kulldam View Post
    Alright, I believe I've come up with it quite accurately and it leaves room for you to customize as you see fit.

    Duo Vengeance Bars

    I've elected to split it into a common setup I like to use for multiple displays: A (hidden) Config where all the data is gathered and assigned, and the actual Display bars that trigger and show what is what. You'll see that consequently, most of the logic takes place in the Config > Trigger > Custom Trigger function. The top of the function is the basic fields for customization:
    Hey Kulldam ! First of all, I really wanted to thank you for all your precious help for those like me who want to get the most of this awesome addon.

    I wanted to see the "skeleton" of your multiple display to inspire myself (and as matter of fact i'm currently tanking, so this display would have been useful). But the link redirect to an empty page on your wiki

    Am i the only one with this issue (not having rights to see the actual page maybe) ?

    Thanks

  9. #509

    Default

    Quote Originally Posted by Ninjouz View Post
    Hey Kulldam ! First of all, I really wanted to thank you for all your precious help for those like me who want to get the most of this awesome addon.

    I wanted to see the "skeleton" of your multiple display to inspire myself (and as matter of fact i'm currently tanking, so this display would have been useful). But the link redirect to an empty page on your wiki

    Am i the only one with this issue (not having rights to see the actual page maybe) ?

    Thanks
    Oops, my fault there! I recently migrated all our sites to a new host and it seems I had an old copy of the wiki database for the migration, so that page was empty as you saw. I've now updated the database to the live version so you should see the page as expected on refresh. Good luck and enjoy! =)

  10. #510

    Default

    Quote Originally Posted by ihanken View Post
    I apologize for taking advantage of your WA knowledge so much. I've been able to figure out a couple things based on the stuff you've told me recently, but this one is really stumping me.

    So I'm working on a feral druid rotation much like my monk rotation. The way rip works currently is that you can't refresh it if it was applied when you had greater attack power. So for example, I apply it with Dancing Steel procced. Then, when it has less than 2 seconds, I don't have a proc, so the game literally tells me "a more powerful spell is already active." So since I'm a sucker for Simcraft, I am attempting to emulate the Simcraft rotation within WeakAuras...the super complex advanced one. As such, if anything is just so complicated that it will take you an obscene amount of time to help me with, don't do it unless you have a desire to, because I can deal with the ones I know how to use. I just want to make this as min-max as possible.

    The first step that is stumping me:
    actions.basic+=/rip,if=combo_points>=5&target.health.pct<=25&action.rip.tick_damage%dot.rip.tick_dmg>=1.15.

    It essentially says "Apply Rip when you have 5 CPs, Target has <= 25% health, and the ticks from the new rip will do 15% more damage than your last rip. I'm already realizing that this would require the trigger to read the current debuff for the tick damage and THEN calculate the new tick damage. Is there anyway to make this happen? This one seems like a pretty complex, and possibly impractical, trigger.
    As with anything, you can greatly simplify the problem by breaking it down into smaller chunks. So for the above, you'd use a Display with multiple Triggers for each part of the requirement:

    1. Combo Points >= 5 -- Easy enough with the basic WeakAuras options
    2. Target Health % <= 0.25 -- Again, a simple task
    3. Refresh Rip damage >= Previous Rip damage * 1.15 -- This is the tricky part of course.

    The way I see it there, is only one way to go about handling the last section. Obviously it will take a few Custom Functions, but your goal is really the split the logic even further into 3 basic steps:

    1. If no Rip (from player) exists, return true.
    2. If Rip (from player) exists, calculate the total damage of that Rip (aka RIP_CURRENT_DAMAGE)
    3. Calculate RIP_NEW_DAMAGE and compare the values, of course returning true to your Trigger if RIP_NEW_DAMAGE is 15% or greater.

    Now this is far too specific a request for me to figure out all the code myself, but I can give you some pointers where to start if I were trying to solve it. First, find out exactly how Rip total and/or tick damage is calculated from the theorycrafting community or what not. As I recall from my Druid days, unless things have dramatically changed in MoP, Rip is a DoT that "snapshots" stats for damage purposes, and if that's the case, this means you can effectively calculate what the expected damage would be total or per tick based on your stats when you cast any given Rip application; the key stats being Attack Power & Mastery I assume.

    Once you have that formula and you stick it into a nice custom little function such as GetRipDamage() or what not, then you just need to "store" that data so you know what the RIP_CURRENT_DAMAGE value was and compare that at any given moment to what your Rip damage would be based on your current stats using GetRipDamage(). Storing this is likely best done with a global variable, for which I'd suggest using the GetValue and SetValue functions which make the process quite easy.

    So you're looking at two Displays for this purpose. The "Rip Data Store" Display is just a hidden display used to track when you cast Rip and then store the RIP_CURRENT_DAMAGE values for later reference. I'd use a Text Display with no text value, and you'll need the Trigger to be Custom > Event > COMBAT_LOG_EVENT_UNFILTERED. Here's the code I used with comments mixed in to explain for the Custom Trigger function:

    Code:
    function(...) -- Rip Data Store: Custom Trigger -- BEGIN EDITS
        local ID = 'RipTracker'
        local SPELL = 'Rip'
        -- END EDITS
        local SetValue = function(value, ...)
           if not WeakAuras.CustomValues then WeakAuras.CustomValues = {} end
           if not ... then return end
           local count, data = #{...}
           for i,v in pairs({...}) do
              if i==1 then if count and count == 1 then WeakAuras.CustomValues[v] = value else
                    WeakAuras.CustomValues[v] = WeakAuras.CustomValues[v] or {}
                    data = WeakAuras.CustomValues[v] end
              else if i ~= count then if not data[v] then data[v] = {} end data = data[v] else data[v] = value end end
           end
        end
        local IsSource = function(value, ...)
           if not select(1, ...) or select(1, ...) ~= 'COMBAT_LOG_EVENT_UNFILTERED' or not value then return end
           local comparison
           local GUID = select(5, ...)  
           if type(value) == 'string' then comparison = select(6, ...)
                if value == GUID then return true end
              for i,v in pairs({'player', 'target', 'focus', 'raid'}) do
                 if string.find(value, v) then value = UnitExists(value) and UnitName(value) end
              end
           elseif type(value) == 'number' then
              if GUID then comparison = tonumber(GUID:sub(6,10), 16) end
           else return end
           if not comparison then return end
           return (value == comparison)
        end
        local GetDestination = function(returnType, ...)
            if not select(1, ...) or select(1, ...) ~= 'COMBAT_LOG_EVENT_UNFILTERED' then return end
            local GetUnitId = function(unit)
                local id        
                if not unit then return end
                if type(unit) == 'string' then -- name or unitId
                    if UnitInRaid(unit) then return 'raid'..UnitInRaid(unit) end -- Player in raid
                    for i=1,10 do id = 'boss'..i if id == unit then return unit end if UnitExists(id) and UnitName(id) == unit then return id end end
                    if UnitExists('player') and UnitName('player') == unit then return 'player' end
                    if UnitExists('pet') and UnitName('pet') == unit then return 'pet' end
                    if UnitExists('target') and UnitName('target') == unit then return 'target' end
                    if UnitExists('focus') and UnitName('focus') == unit then return 'focus' end
                    if UnitExists('mouseover') and UnitName('mouseover') == unit then return 'mouseover' end            
                end
            end
            returnType = returnType or 'name'
            local GUID = select(9, ...)
            if not GUID then return end
            if returnType == 'guid' then return GUID end
            local name = select(10, ...)
            local unitId = GetUnitId(name)
            local maskedTypeBit = tonumber(GUID:sub(5,5), 16) % 8        
            local id = tonumber(GUID:sub(6,10), 16)
            -- 0 player, 1 object, 3 npc, 4 pet, 5 vehicle
            if maskedTypeBit == 0 then -- Player
                if returnType == 'name' then return (name ~= 'Unknown') and name
                elseif returnType == 'unit-id' or returnType == 'unitId' then return unitId end
            else
                if returnType == 'name' then return (name ~= 'Unknown') and name
                elseif returnType == 'unit-id' or returnType == 'unitId' then return unitId
                elseif returnType == 'id' then return id end
            end
        end
        local IsEventType = function(value, ...)
            if not select(1, ...) or select(1, ...) ~= 'COMBAT_LOG_EVENT_UNFILTERED' or not value or type(value) ~= 'string' then return end
            local event = select(3, ...)
            if not event then return end
            if event == value then return true end
        end
        local IsSpell = function(value, ...)
            if not select(1, ...) or select(1, ...) ~= 'COMBAT_LOG_EVENT_UNFILTERED' or not value then return end
            local comparison
            if type(value) == 'string' then comparison = select(14, ...)
            elseif type(value) == 'number' then local n = select(13, ...) comparison = tonumber(n)
            else return end
            if not comparison then return end
            return (value == comparison)
        end
        local GetRipDamage = function(totalDamage)
            if totalDamage then
                -- CODE HERE TO RETURN TOTAL RIP DAMAGE
            else
                -- CODE HERE TO RETURN PER-TICK RIP DAMAGE
            end
        end
       
        -- First ensure this is the proper spell to track
        if not IsSpell(SPELL, ...) then return end
        -- Only track from Source == 'player'
        if not IsSource('player', ...) then return end
        -- Get the GUID as a unique tracker of target unit
        local GUID = GetDestination('guid', ...)
        if not GUID then return end        
        -- Next ensure we only track appropriate event_types
        if IsEventType('SPELL_AURA_APPLIED', ...) or IsEventType('SPELL_AURA_REFRESH', ...) then
            -- If APPLIED or REFRESH, we need to recalculate and update the stored data
            -- Calculate the Rip damage value of the currently applied rip based on Attack Power/Mastery/etc
            local damage = GetRipDamage() or 0 
            -- Finally, store this data using the parent-child data structure of SetValue()
            SetValue(damage, ID, GUID)
        elseif IsEventType('SPELL_AURA_REMOVED', ...) then
            -- If REMOVED, set the value to nil to remove data from the table and keep it clean
            SetValue(nil, ID, GUID)
        end
    end


    So at the top in the edits we simply have the ID we'll use to store our data across multiple Displays, as well as the spell we're tracking (such as Rip). Following that are the copy-pasted snippets we're using in this Trigger to get everything we need. You can find them all as usual in the WeakAuras_Snippets section on the wiki. I strongly recommend checking it out for usage tips on each function, as the code in the wiki includes comments prior to each snippet that show how the function will typically be used/called. The exception is the GetRipDamage snippet which I've just inserted as pseudo-code there, but you'll have to of course insert the proper calculations (remember to use the WoW API if you need to find functions that return information about your stats like attack power or what not).

    At the bottom you can see the actual validation code. Basically first we use IsSpell to weed out the majority of combat_log calls that have nothing to do with the spell in question (Rip). Next we further filter by checking with IsSource that the source of the combat_log event was us ('player'). Next we need to track the unit that we applied Rip to, because obviously our target will change frequently and we need a way to distinguish between Rip targets in the future so we know the damage value that was applied to each unique target. Luckily, WoW uses just such a unique identifier known as a GUID. We grab it using the GetDestination function call.

    Then the last filter is the event type, of which we only care about SPELL_AURA_APPLIED and SPELL_AURA_REFRESH since those are the two that mean we just applied a fresh/new application of our spell and should recalculate the damage. Now we get the damage that our recently applied Rip will deal using the GetRipDamage function.

    Finally, having verified this combat_log event is valid to indicate Rip was just cast by us, and with our Rip damage value calculated at the time and the GUID of the target unit we cast upon in hand, we use the SetValue function to store that damage value globally for reference later on. SetValue uses a child-parent structure with infinite parameters, so the first parameter is the value we're storing (the damage of Rip in this case), then each parameter after is a key value, stored in an ever-deeper tree structure. So our call of "SetValue(damage, ID, GUID)" is basically storing the following for us:

    Code:
    WeakAuras.CustomValues[ID][GUID] = damage


    There's also a secondary IsEventType check if the aura is removed, which is just a cleanup way to destroy an existing value in the "database" so our table doesn't become massive over time.

    NOW, with that out of the way, you can set the Untrigger to just a "Timed" 0 second duration and be set. However, next you'll need your second Display, which is the main Display to be shown for this Rip setup and what will indicate whether everything is valid and you should cast Rip or not.

    Going back to earlier in the post a bit, here are the basic criteria we're using:

    1. Combo Points >= 5 -- Easy enough with the basic WeakAuras options
    2. Target Health % <= 0.25 -- Again, a simple task
    3. Refresh Rip damage >= Previous Rip damage * 1.15 -- This is the tricky part of course.

    So the first two are easy enough to setup as Triggers with WeakAuras basic options, but now that we've got our secondary Display storing our active Rip damage values, we can use that stored data to actually calculate whether our NEW Rip damage if we applied it right now would be >= the previous rip damage * 1.15. In addition to the Triggers you've added before to check that your Rip already exists on the target, your combo points, and target health, I'd also add a Trigger > Custom > Status > check on... > Every Frame as well, and again, this is the code I would use for the Custom Trigger function:

    Code:
    function() -- Rip Validation: Custom Trigger
        -- BEGIN EDITS
        local ID = 'RipTracker'
        -- END EDITS
        local GetValue = function(...)
            if not WeakAuras.CustomValues then WeakAuras.CustomValues = {} end
            if not ... then return end
            local count, data = #{...}
            if count and (count > 1) then
                for i,v in pairs({...}) do
                    if i==1 then WeakAuras.CustomValues[v] = WeakAuras.CustomValues[v] or {} data = WeakAuras.CustomValues[v]
                    else if i ~= count then if not data[v] then data[v] = {} else data = data[v] end else data = data[v] end end
                end
            else data = WeakAuras.CustomValues[select(1, ...)] end
            return data
        end
        local GetRipDamage = function(totalDamage)
            if totalDamage then
                -- CODE HERE TO RETURN TOTAL RIP DAMAGE
            else
                -- CODE HERE TO RETURN PER-TICK RIP DAMAGE
            end
        end

        -- First, get the 'target' GUID for lookup in our data table
        local GUID = UnitGUID('target')
        if not GUID then return end
        -- Find the stored Rip damage value for the matching GUID if applicable
        local currentDamage = GetValue(ID, GUID) or 0
        -- Calculate the NEW rip damage (at present)
        local newDamage = GetRipDamage() or 0
        -- Compare and return the result
        return (newDamage >= (currentDamage * 1.15))
    end


    Here things are much simpler, since we don't need to do much validation and instead we're simply comparing values if applicable. So we use the GetValue snippet as well as the GetRipDamage function from before. The code at the bottom we're grabbing the GUID of our target for lookup, then getting two RipDamage values: One for the saved data value (if it exists) using GetValue and one using the actual GetRipDamage function to calculate the NEW damage of Rip cast right now. Remember that SetValue and GetValue use the same parent-child data structure, so to retrieve the value we assigned in the first Display using SetValue(damage, ID, GUID) we simply use GetValue(ID, GUID) as seen here. By assigning local currentDamage = GetValue(ID, GUID) or 0, we're skipping a bit of "if-then-else" code and telling currentDamage to be equal to zero if our function finds no stored data. The same "else zero" rule is used to assign our newDamage value as well.

    Finally, our return for the Trigger function should always be boolean, so we enclose our comparison function of our damage value calculation in parentheses before returning it. Thus, if newDamage is 15% or greater than currentDamage, a "true" value is returned and the Trigger fires, otherwise no firing occurs.

    All told, in this secondary Display, if all Triggers return true regarding combo points, active Rip, target health, and our custom function above that ensures our new Rip is sufficiently strong enough, then the entire Display is Triggered and you'll see the alert as expected!

    Anyway, I think that's plenty for you to chew on for now. If you want to keep going with your conversion process as you mentioned, I strongly suggest looking into stuff to help you experiment more with the WoW API and Lua in WoW as well. The most useful thing you can do when trying to figure out custom stuff is trial and error and debugging. Basically, find a way to test and see what it is you are trying to do or get and make adjustments until it fits. I would check out the WoWLua addon, which is a great in-game tool for running Lua script. Also the print() function is extremely useful: Use it early and often throughout your code to output values and see what is what. Lastly, /dump is another vital command, this one typed in the chatbox followed by any Lua script or variable. This will "dump" out the value or result of that variable or script for display, so you can see what your code has changed or what the result of a particular call might be.

    If you have anymore questions about Lua, API, or coding let me know, but otherwise I'd encourage you to experiment, and try to come up with the functions/snippets that you might need to solve the issues you're having and see where you can go from there. Good luck!

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •