• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Event: unit lost buff

Status
Not open for further replies.
Check each second.

When the unit receives the buff, add it to the unit group, and check the units in the unit group every second. If the unit no longer has the buff, remove it from the group.

it means for each active buff a trigger will be active, it is extreamly annoying when 4 heroes casted a buff and have a critical time , with mass lags up untill someone dies.
 
Buff checking is very light. You can check a few hundred units several times a second and it won't be noticeable.

4 times this:
EACH SECOND
Pick each unit in unit group(say 20 units)
Deal damage to them
set p = their location
create special effect at their location
if picked unit suffered enough{
remove ability from unit
remove it from group
}
if unit group is empty then turn off trigger


^ LIGHTLY? :ogre_icwydt:
 
Level 6
Joined
Nov 24, 2012
Messages
218
I don't understand why you will need 4 triggers if there was 4 heroes?
I was assuming something like this would be done:

If this particular buff is gained by ability cast do this, if it's by custom spell, add to BuffGroup in the custom spell trigger.
If it is buff by orb ability, you will pick every unit in map on periodic trigger (or if you are using a DDS, do damage detection event and run a 0.01 expiring timer (if the system detects damage before buff placement), which will then check the unit for buff X and add damaged target to BuffGroup if true).

Event: Unit starts effect of ability
Condition: Ability = X
Action: Add target of ability being cast to BuffGroup

Event: Every 1 second
Condition: # of units in BuffGroup > 0
Action: Pick every unit in BuffGroup and if picked unit has Buff (X) = false, do actions.

Uh, in one of my maps, there is over 40,000 lines of code and massive AI scripts, picking all units in the map (100+) like every few seconds, along with several heavy systems. Lag is pretty minor in the map. There was more FPS drop in SFX spam/tons of units fighting than triggers/systems. As long as it is leakless, something like this will not lag at all.
 
"There was more FPS drop in SFX spam/tons of units fighting than triggers/systems. As long as it is leakless, something like this will not lag at all."
I know a map in which they used 150 flame strikes, fps kicked all players xD.
But is this the one and only most not reducing FPS there is? I doubt it since it has special treatment to lets say 20 units 4 times a second = 80 units. that sucks!
 
Last edited:
Level 11
Joined
Mar 31, 2009
Messages
732
If you don't want to check every second, here is an alternative:

- Register event unit gains buff
- CONSTANT where you track the duration of the buff (copied from your object data)
- Condition: check it is the buff you want
- Action: create a timer, create a trigger, set timer to the duration of the buff, and register event timer elapsed.
- Condition: assert the unit still has the buff (because it was recast), and if it does then destroy the timer (GetElapsedTimer), destroy the trigger (GetTriggeringTrigger) and return
- Action: Do stuff, then destroy trigger and timer

No need to keep an array of the timers and triggers, just destroy them using the Get functions when the timer elapses.
 
Level 6
Joined
Nov 24, 2012
Messages
218
If you don't want to check every second, here is an alternative:

- Register event unit gains buff
- CONSTANT where you track the duration of the buff (copied from your object data)
- Condition: check it is the buff you want
- Action: create a timer, create a trigger, set timer to the duration of the buff, and register event timer elapsed.
- Condition: assert the unit still has the buff (because it was recast), and if it does then destroy the timer (GetElapsedTimer), destroy the trigger (GetTriggeringTrigger) and return
- Action: Do stuff, then destroy trigger and timer

No need to keep an array of the timers and triggers, just destroy them using the Get functions when the timer elapses.

I didn't know there was an event for unit gains buff.
That's pretty neat. Well, I would have known if I had needed it in the past.
Now that I think about it, it would make sense timers are more efficient than periodic checks.
User just needs to account for possible external debuff sources.
 
Level 11
Joined
Mar 31, 2009
Messages
732
User just needs to account for possible external debuff sources.

Thats true. Maybe another event for ability cast, that checks if it is a purge type spell.

Say a trigger has a timer elapsed event registered, and an ability is cast event registered, what happens if it is triggered by an ability being cast, then you call "GetElapsedTimer"? Does it return null?

Wondering if the timer would leak in this situation...

Trigger1. Timer1.
Trigger1: Registered Timer1 elapsed. Registered ability is cast.
Condition -
if GetElapsedTimer == null {
check if the ability is a purge type...
...
}

Might be difficult to keep doing it without retaining a table of handles though. Could still be doable depending on your actions.
You won't be able to directly pull the unit in question that lost the buff if it was when the timer expired. You'd have to track it somehow, in a hashtable of trigger and unit.
 
Level 11
Joined
Mar 31, 2009
Messages
732
Ugh, I can't make a code array? I can't call a code variable? I can't store them in hashtables? How can I track a function then?

JASS:
library UnitLosesBuff

    globals 
        private unit array freedUnits
        //private code array eventHandlers
        private trigger array dispelTriggers
    endglobals
    
    private function UnitPurged takes nothing returns nothing
    
    endfunction
    
    private function TimerExpires takes nothing returns nothing
        local integer timerHandleId = GetHandleId(GetExpiredTimer())
        //local code eventHandler = eventHandlers[timerHandleId]
        local unit freedUnit = freedUnits[timerHandleId]
        local trigger dispelTrigger = dispelTriggers[timerHandleId]
        
        // do an assertion on freedUnit here to make sure it doesn't still have the buff        
        
        // call eventHandler
        
        // call RemoveHandles
        call DestroyTrigger(dispelTrigger)
        call DestroyTimer(GetExpiredTimer())
    endfunction
    
    private function Register takes code eventHandler, integer buffDuration, unit unitWithBuff returns nothing
        local timer t = CreateTimer()
        local trigger trig = CreateTrigger()
        
        call TimerStart(t, buffDuration, false, function TimerExpires)
        
        call TriggerRegisterUnitEvent(trig, unitWithBuff, EVENT_UNIT_SPELL_CAST)
        call TriggerAddAction(trig, function UnitPurged)
        
        set freedUnits[GetHandleId(t)] = unitWithBuff
        //set eventHandlers[GetHandleId(t)] = function eventHandler
        set dispelTriggers[GetHandleId(t)] = trig
    endfunction
    

endlibrary
 
I have no idea what you guys wrote there but what I tried to do is to
set a unit critical strike to be more whenever a unit cast a spell on it
when it is purged, it will reset the critical chance
when the caster spell is over, the timer will check go actions{
remove buffs
remove greater critical hit chance
}

That gave me an idea to create "critical increaser" which is based
on that the timer ended, not that the second is passed

I am not yet sure how to make this, but I am very happy! +REP to everyone
:thumbs_up::thumbs_up::thumbs_up::thumbs_up:
 
Scrap what you quoted there, I was doing it wrong with a hashtable.

I have no idea how JASS works nor how to apply them, could you do it with GUI just so I can get the idea of it (dont make it totaly, I will do it in my map, I just need an example)

Might be difficult to keep doing it without retaining a table of handles though. Could still be doable depending on your actions.
You won't be able to directly pull the unit in question that lost the buff if it was when the timer expired. You'd have to track it somehow, in a hashtable of trigger and unit.
Well, considering I just want to know if the unit lost or added a buff, and ofc if it was refreshed:
1) Need timer, but it will need to be timer for each unit that can cast the spell (unit indexer)
2) timer[1000]
3) if the unit has purge on it, stop the timer immidiatly or set bool to ignore its finish
4) if the unit has the buff and was again refreshed with new cast, reset the timer
^ I have never done any of those, but its hotdamn sexy to think there is a lag-less way to do it
 
Level 6
Joined
Nov 24, 2012
Messages
218
I was thinking for maximum efficiency, use AIDS / TimerUtils / T32X / Nes's Combining Timer thing, but it's all vJass.

If not, we have to use Unit Indexer + Normal GUI Timers...
This is going to be killer, adding 8190 events for A timer expires [Custom Value]...
This is why me and Iron suggested other method, simple, easy, shorter.
This method is not viable, you either use vJass with those advanced systems or the simple unit group method.

So, unit indexer is out of the game here. You don't want to create this trigger:
  • For Loop 1-8190
  • Trigger - Add to Buff <gen> the event (Time - BuffRemoveTimer[(Integer A)] expires)
  • Unless 8190 events makes no strain on the system ? I don't really know.
Since you want GUI, you should go with the original proposal. At least it's simple and easy.
That, or make this MUI with arrays without using Indexer.
Here is my MUI attempt, gosh I almost never bother making anything MUI this approach is gonna fail.
Assuming a 101 unit max with buff in the map at a time:

  • Init
    • Events
      • Map initialization
    • Conditions
    • Actions
      • For each (Integer LoopA) from 0 to 100, do (Actions)
        • Loop - Actions
          • Trigger - Add to RemoveBuff <gen> the event (Time - BuffRemoveTimer[LoopA] expires)
  • AddBuff
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to CriticalMod
    • Actions
      • If (Key Less than 100) then do (Set Key = Key + 1) else do (Set Key = 0)
      • Set BuffUnit[Key] = (Target unit of ability being cast)
      • Custom script: if TimerGetRemaining(udg_BuffRemoveTimer[udg_Key]) > 0.00 then
      • Custom script: call PauseTimer(udg_BuffRemoveTimer[udg_Key])
      • Custom script: call DestroyTimer(udg_BuffRemoveTimer[udg_Key])
      • Custom script: endif
      • For each (Integer LoopA) from 0 to 100, do (Actions)
        • Loop - Actions
          • Custom script: if udg_BuffUnit[udg_LoopA] == GetSpellTargetUnit() then
          • Custom script: set udg_BuffUnit[udg_LoopA] = null
          • Custom script: exitwhen true
          • Custom script: endif
      • Countdown Timer - Start BuffRemoveTimer[Key] as a One-shot timer that will expire in 30.00 seconds
  • RemoveBuff
    • Events
    • Conditions
    • Actions
      • Set ExpiredTimer = (Expiring timer)
      • For each (Integer LoopA) from 0 to 100, do (Actions)
        • Loop - Actions
          • Custom script: if udg_ExpiredTimer == udg_BuffRemoveTimer[udg_LoopA] then
          • Custom script: exitwhen true
          • Custom script: endif
      • -------- LoopA = our index now --------
      • Game - Display to (All players) the text: ((Name of BuffUnit[LoopA]) + lost buff!)
      • Custom script: set udg_BuffUnit[udg_LoopA] = null
  • PurgeBuff
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Purge
    • Actions
      • For each (Integer LoopA) from 0 to 100, do (Actions)
        • Loop - Actions
          • Custom script: if udg_BuffUnit[udg_LoopA] == GetSpellTargetUnit() then
          • Custom script: exitwhen true
          • Custom script: endif
      • -------- LoopA = our index now --------
      • Game - Display to (All players) the text: ((Name of BuffUnit[LoopA]) + got buff purged!)
      • Custom script: set udg_BuffUnit[udg_LoopA] = null
So the question is, which is faster, periodic unit group checks (which I don't see the point in triggering since it takes like 3 minutes), or this?

Edit: The 0-100 loops are starting to feel really inefficient. Is there some way to detect which array timer expired without looping through them all? I think timer systems are needed to make this become more efficient than doing unit group periodic checks for lost buff.
 
Last edited:
Level 11
Joined
Mar 31, 2009
Messages
732
Sif rewrite all that nonsense into GUI. Heres how its done, with a little GUI interface :)

Note, no guarantees it actually works, cause I haven't tested it. Perhaps someone could peer review this?

  • UnitLosesBuffGUI
    • Events
    • Conditions
    • Actions
      • Set spellCode = (Integer(Bblo))
      • Set trackedUnit = No unit
      • Set existingTrigger = Melee Initialization <gen>
      • Set buffDuration = 8
      • Custom script: call UnitLosesBuff_Register(udg_existingTrigger, udg_buffDuration, udg_trackedUnit, udg_spellCode)
JASS:
library UnitLosesBuff

    globals 
        // Index: timerHandleId
        private unit array freedUnits
        
        // Index: unitHandleId
        private timer array freedTimers
        private trigger array eventHandlers
        private trigger array dispelTriggers
        private integer array buffCodes
    endglobals
    
    private function UnitPurgedCondition takes nothing returns boolean
        local unit castee = GetSpellTargetUnit()
        local integer buffCode = buffCodes[GetHandleId(castee)]
        
        if (GetUnitAbilityLevel(castee, buffCode) > 0) then
            return false
        endif
        
        return true
    endfunction
    
    private function UnitPurged takes nothing returns nothing
        local unit castee = GetSpellTargetUnit()
        local integer casteeId = GetHandleId(castee)
        local timer casteeTimer = freedTimers[casteeId]
        local integer casteeTimerId = GetHandleId(casteeTimer)
        local trigger eventHandler = eventHandlers[casteeId]
        
        // I am assuming here that you WANT your handler to fire if the unit is purged
        call TriggerExecute(eventHandler)
        
        call DestroyTrigger(eventHandler)
        call DestroyTimer(casteeTimer)
        call DestroyTrigger(GetTriggeringTrigger())
        set freedUnits[casteeTimerId] = null
        set freedTimers[casteeId] = null
    endfunction
    
    private function TimerExpires takes nothing returns nothing
        local integer timerId = GetHandleId(GetExpiredTimer())
        local unit freedUnit = freedUnits[timerId]
        local integer freedUnitId = GetHandleId(freedUnit)
        local trigger eventHandler = eventHandlers[freedUnitId]
        local trigger dispelTrigger = dispelTriggers[freedUnitId]
        
        // As long as the UnitPurged trigger works, this local and the if block won't be needed.
        local integer buffCode = buffCodes[freedUnitId]
        if (GetUnitAbilityLevel(freedUnit, buffCode) == 0) then
            call TriggerExecute(eventHandler)
        endif
        
        call DestroyTrigger(eventHandler)
        call DestroyTrigger(dispelTrigger)
        call DestroyTimer(GetExpiredTimer())
        set freedUnits[timerId] = null
        set freedTimers[freedUnitId] = null
    endfunction
    
    public function Register takes trigger handlerTrigger, integer buffDuration, unit unitWithBuff, integer buffCode returns nothing
        local timer buffExpiresTimer
        local trigger purgeTrigger = CreateTrigger()
        local integer unitId = GetHandleId(unitWithBuff)
        
        if (freedTimers[unitId] == null) then
            set freedTimers[unitId] = CreateTimer()
        endif
        call TimerStart(freedTimers[unitId], buffDuration, false, function TimerExpires)
        set freedUnits[GetHandleId(freedTimers[unitId])] = unitWithBuff
        
        call TriggerRegisterUnitEvent(purgeTrigger, unitWithBuff, EVENT_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(purgeTrigger, Condition(function UnitPurgedCondition))
        call TriggerAddAction(purgeTrigger, function UnitPurged)
                
        set dispelTriggers[unitId] = purgeTrigger
        set eventHandlers[unitId] = handlerTrigger
        set buffCodes[unitId] = buffCode
    endfunction
    

endlibrary


Edit: UnitLosesBuff? UnitBuffFades? UnitFreedFromSlavery? Hmmmmmmmm.
 

Attachments

  • testingunitlosesbuff.w3x
    18.4 KB · Views: 81
Level 37
Joined
Mar 6, 2006
Messages
9,243
I triggered the system also, but in GUI. It uses the looping method.


  • Melee Initialization
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set BUFF_HASH = (Last created hashtable)
      • -------- ---------------------------------------- --------
      • Custom script: call SaveInteger(udg_BUFF_HASH, 'AUfu', 0, 'BUfa')
      • Custom script: call SaveStr(udg_BUFF_HASH, 'BUfa', 0, "Frost Armor")
      • -------- ---------------------------------------- --------
      • Custom script: call SaveInteger(udg_BUFF_HASH, 'Ainf', 0, 'Binf')
      • Custom script: call SaveStr(udg_BUFF_HASH, 'Binf', 0, "Inner Fire")
  • Cast
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
    • Actions
      • Custom script: if HaveSavedInteger(udg_BUFF_HASH, GetSpellAbilityId(), 0) then
      • Set U = (Target unit of ability being cast)
      • Custom script: set udg_ID = GetHandleId(udg_U)
      • Set I1 = (Load 0 of ID from BUFF_HASH)
      • Custom script: call SaveInteger(udg_BUFF_HASH, udg_ID, 100+udg_I1, LoadInteger(udg_BUFF_HASH, GetSpellAbilityId(), 0))
      • Hashtable - Save (I1 + 1) as 0 of ID in BUFF_HASH
      • Unit Group - Add U to BUFF_GRP
      • Trigger - Turn on Loop <gen>
      • Custom script: endif
  • Loop
    • Events
      • Time - Every 0.10 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in BUFF_GRP and do (Actions)
        • Loop - Actions
          • Set U = (Picked unit)
          • Custom script: set udg_ID = GetHandleId(udg_U)
          • Set I1 = (Load 0 of ID from BUFF_HASH)
          • For each (Integer A) from 1 to I1, do (Actions)
            • Loop - Actions
              • Set I2 = (Load ((Integer A) + 99) of ID from BUFF_HASH)
              • Custom script: if GetUnitAbilityLevel(udg_U, udg_I2) == 0 then
              • Game - Display to Player Group - Player 1 (Red) for 5.00 seconds the text: ((Proper name of U) + ( lost + (Load 0 of I2 from BUFF_HASH)))
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • I1 Equal to 1
                • Then - Actions
                  • Hashtable - Clear all child hashtables of child ID in BUFF_HASH
                  • Unit Group - Remove U from BUFF_GRP
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • (BUFF_GRP is empty) Equal to True
                    • Then - Actions
                      • Trigger - Turn off (This trigger)
                    • Else - Actions
                • Else - Actions
                  • Hashtable - Save (I1 - 1) as 0 of ID in BUFF_HASH
                  • Hashtable - Save (Load ((Integer A) + 100) of ID from BUFF_HASH) as ((Integer A) + 99) of ID in BUFF_HASH
              • Custom script: endif


The SaveStr lines can be removed if you don't want to display the buff's name.
 

Attachments

  • UnitHasBuffs.w3x
    14.3 KB · Views: 69
Level 37
Joined
Mar 6, 2006
Messages
9,243

The trigger is very light, mostly just integers. And a max 0.1 second delay isn't that much, though I admit that timers would be more accurate.

I'm not sure how the handle IDs work. Do I really need to use a hashtable for one column entries? Can I not just use an array with the handleID as the index?

Handle ids are huge numbers usually, much greater than the max index of arrays.
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
They used H2I in gameCache.

@onTopic: Can you not just code the whole ability with its lifetime? There is not a direct way to detect any buff gain/loss of the engine but when writing your spells, which is nowadays almost completely done custom via triggers, you handle the display of your buff icon/sfx yourself or have your own buff system whose objects react on certain events and provide you a gain/loss event.
 
Level 7
Joined
Jan 30, 2011
Messages
267
Ugh, I can't make a code array? I can't call a code variable? I can't store them in hashtables? How can I track a function then?

you cant create code arrays but you can create boolexpr arrays (boolexpr are functions that have no parameters and return a boolean)
you can execute them this way:
JASS:
local triggercondition trigcon = TriggerAddCondition(someTrigger, Condition(function myFunction))
call TriggerEvaluate(someTrigger)
call TriggerRemoveCondition(someTrigger, trigcon)
make sure myFunction returns a boolean (no matter if true or false i believe)

you can also use a normal function, save its name as string and call it with ExecuteFunc(String func)
but afaik ExecuteFunc is synced and TriggerEvaluate is not
 
you cant create code arrays but you can create boolexpr arrays (boolexpr are functions that have no parameters and return a boolean)
you can execute them this way:
JASS:
local triggercondition trigcon = TriggerAddCondition(someTrigger, Condition(function myFunction))
call TriggerEvaluate(someTrigger)
call TriggerRemoveCondition(someTrigger, trigcon)
make sure myFunction returns a boolean (no matter if true or false i believe)

you can also use a normal function, save its name as string and call it with ExecuteFunc(String func)
but afaik ExecuteFunc is synced and TriggerEvaluate is not

Considering how a warcraft spell have allways integer seconds of happening, it will be totaly un-needed to check for buffs each 0.1 second, and even if we missed, it shouldnt be such a big deal. I was wondering if this could be done via timer[1000] and unit indexer, while we know that the map never has more than 1000 units alive, I think something like this should work even better than "every 1.00 seconds" trigger. But I am too noob as a map maker and I tried but couldn't make that work.
 

Attachments

  • DetectableBuffs.w3x
    14.1 KB · Views: 60
Last edited:
ok I deleted both my masssaged and I figured out a way to do it without custom scripts!!!
only gui , simple and sound but it took me to use 3 different mephodes to apply a spell
I will post it few days from now, hopefully tommorow, I wanted to say
HesitationSnow, your version is very simple and I think it so far the one which is
most garenteed to be used by people who read this thread.
Hopefully, I will post version who won't use custom scripts, for making it simple
even for most most out of most new map makers out there.
Stay tunned!
 
Last edited:
Status
Not open for further replies.
Top