• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[JASS] Grenade trigger help

Status
Not open for further replies.
I could use some help optimizing a trigger i just made, it is a kind of grenade spell which works roughly like this:

*A unit "squad" is ordered to throw grenades. This sets a boolean flag called "ThrowGrenade" to true and sets the real variable "GrenadeDistance" to the distance between the units and the target.

*A trigger calls the fllowing script to throw the grenades:
JASS:
    call ForGroup(L.units, function GrenadeInterval)
    set L.ThrowGrenade = false

"L" is a struct that controls information about the group and their "leader" etc. This has to do with other systems in the map.

The following script is the process itself; it is a linked chain of functions that trigger eachother through timers (using Timerutils). It works this way because i have to synchronize attack animations, flight time, etc.

In the first step, the animation starts and the first timer is started. By the time this finishes, the anim has reached the point where the grenade is to be spawned as a projectile, which is where the second function fires. It spawns a dummy that casts a dummy spell (with projectile art) towards a polar point in front of the unit. The next timer starts with the interval required until the grenade lands.

Finally, when the last timer expires, it spawns a grenade unit at the target point. This unit will have timed life and explode upon death.

JASS:
function GrenadeSpawn takes nothing returns nothing
    local location l = LoadLocationHandle(LineHash, 0, GetHandleId(GetExpiredTimer()) )
    call CreateUnit(Player(PLAYER_NEUTRAL_AGGRESSIVE), 'h00P', GetLocationX(l), GetLocationY(l), 0.)
    call RemoveLocation(l)
    call RemoveSavedHandle(LineHash, 0, GetHandleId(GetExpiredTimer()))
    call ReleaseTimer(GetExpiredTimer())
    set l = null
endfunction

function GrenadesFly takes nothing returns nothing
    local unit u = LoadUnitHandle(LineHash, 0, GetHandleId(GetExpiredTimer()) )
    local unit dummy = CreateUnit(GetOwningPlayer(u), 'h00G', GetUnitX(u), GetUnitY(u), GetUnitFacing(u))
    local Line L = Lines[GetUnitUserData(u)]
    local timer t = GetExpiredTimer()
    local real X = GetUnitX(u) +GetRandomReal(-5, 5) + L.GrenadeDistance * Cos(GetUnitFacing(L.controller) * bj_DEGTORAD)
    local real Y = GetUnitY(u) +GetRandomReal(-5, 5) + L.GrenadeDistance * Sin(GetUnitFacing(L.controller) * bj_DEGTORAD)
    local unit target = CreateUnit(GetOwningPlayer(u), 'h00G', X, Y, GetUnitFacing(u))
    call UnitAddAbility(dummy, 'A002')
    call IssueTargetOrderById( dummy, OrderId("acidbomb"), target)
    call UnitApplyTimedLife(dummy, 'BTLF', 5.00)
    call UnitApplyTimedLife(target, 'BTLF', 5.00)
    call RemoveSavedHandle(LineHash, 0, GetHandleId(GetExpiredTimer()))
    call SaveLocationHandle(LineHash, 0, GetHandleId(t), Location(X, Y))
    call TimerStart(t, L.GrenadeDistance/700, false, function GrenadeSpawn)
    set u = null
    set t = null
    set dummy = null
endfunction

function ThrowGrenades takes nothing returns nothing
    local unit u = LoadUnitHandle(LineHash, 0, GetHandleId(GetExpiredTimer()) )
    local Line L = Lines[GetUnitUserData(u)]
    local timer t = GetExpiredTimer()
    call SetUnitFacing(u, GetUnitFacing(L.controller))
    call SetUnitAnimation(u, "spell throw")
    call QueueUnitAnimation(u, "stand")
    call TimerStart(t, 2., false, function GrenadesFly)
    set t = null
    set u = null
endfunction

function GrenadeInterval takes nothing returns nothing
    local Line L = Lines[GetUnitUserData(GetEnumUnit())]
    local timer t = NewTimer()
    call SaveUnitHandle(LineHash, 0, GetHandleId(t), GetEnumUnit())
    call TimerStart(t, GetRandomReal(0.1, 1.0), false, function ThrowGrenades)
    set t = null
endfunction

This is the code that fires when the grenade explodes:

JASS:
function Trig_Grenade_Damage_Conditions takes nothing returns boolean
    if ( not ( GetUnitTypeId(GetTriggerUnit()) == 'h00P' ) ) then
        return false
    endif
    return true
endfunction

function HeroFilter takes nothing returns boolean
    if (IsUnitType(GetEnumUnit(), UNIT_TYPE_HERO) == true) then
        return false
    endif

    if (GetUnitTypeId(GetEnumUnit()) == 'h00P') then
        return false 
    endif
    return true
endfunction

function Trig_Grenade_Damage_Actions takes nothing returns nothing
    local group g = CreateGroup()
    local unit temp
    local real ang
    local real dmg
    local Knockback K

    call GroupEnumUnitsInRange(g, GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), 200., Filter(function HeroFilter))


    loop 

         set temp = FirstOfGroup(g)
         set dmg = GetRandomReal(5., 9.)


         if (GetUnitState(temp, UNIT_STATE_LIFE) < dmg) then
         if (GetUnitState(temp, UNIT_STATE_LIFE) > 0.5) then
           set ang = bj_RADTODEG * Atan2(GetUnitY(GetTriggerUnit()) - GetUnitY(temp), GetUnitX(GetTriggerUnit()) - GetUnitX(temp))
           call SetUnitFacing(temp, ang)
           call SetUnitAnimation(temp, "spell slam")
           set K = Knockback.create(GetTriggerUnit(), temp, GetRandomReal(100., 250.), GetRandomReal(1, 2), ang-180, KbType) 
         endif
         endif


         call SetUnitState(temp, UNIT_STATE_LIFE, GetUnitState(temp, UNIT_STATE_LIFE)-dmg)
         call GroupRemoveUnit(g, temp)
        exitwhen temp == null

    endloop

//Cleanup variables    
call RemoveUnit(GetTriggerUnit())
call DestroyGroup(g)
set g = null
endfunction

//===========================================================================
function InitTrig_Grenade_Damage takes nothing returns nothing
    set gg_trg_Grenade_Damage = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Grenade_Damage, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddCondition( gg_trg_Grenade_Damage, Condition( function Trig_Grenade_Damage_Conditions ) )
    call TriggerAddAction( gg_trg_Grenade_Damage, function Trig_Grenade_Damage_Actions )
endfunction

The knockback struct type is part of a knockback library made by kricz. YOu may find it in the spells section if you want to take a closer look.

The problem is that i'm experiencing some pretty heavy lag when this fires, especially when the grenades themselves spawn, and i want some advice on how i can make this script faster, or if there's something causing near-infinite loops (like for instance when the grenades tries to knockback eachother, which was an issue until i added a condition for it).

Also, i'm having trouble making the projectile art show. I tried giving the ability to a regular unit, and ifor some reason the projectile art does not show. I am basing my ability on "channel" (a generic neutral hostile ability).

+2 rep to whoever manages to help me somehow.
Thanks in advance.
 
Last edited:
48 units throwing simultaneously.
I'm thinking that it might also be the particles in the grenade model, but i have reduced its particle count quite significantly already. It also has some sound events though, so it might be a lot of things stacking up causing the lag.

The worst lag spike happens when you throw the grenades into an enemy group of units; i'm thinking it might be because i haven't yet included the code for discriminating between already dead units and units who are killed by the blast (decaying units will also play the animation and be knockbacked once again). FPS drops to nearly 0 in those occasions. I tried making a test function that spawned grenades wherever a special unit was ordered to move, and whilst i never spawned as many at the same time, i also never reached theese drastic fps drops (in fact, i hardly noticed any at all, despite spawning as fast as i could click).

I will try to make some minor adjustments to make the throw less synchronized (like add a second step with a random timer linking to the next), it looks awkward anyways having them all throw at the exact same time anyway. If you want, i can send you the map so that you can have a look at the code.
 
Last edited:
Well, i figured you wouldn't need all the thousands of lines of code in the map to see if anything was wrong. Just replace the variables related to the struct L with any values you please (should work the same). Timerutils should be something you're familiar with. Here's the code for the knockback struct:


JASS:
library Knockback requires ListModule, GroupUtils, TerrainPathability, optional BoundSentinel

    //Please check the Knockback Info Trigger for more information about this system.

    globals
        public constant real TIMER_INTERVAL             = 0.05
        
        private constant real STANDARD_DEST_RADIUS      = 100.00
        private constant real STANDARD_UNIT_RADIUS      = 75.00
        private constant boolean STOP_WHEN_IMPASSABLE   = false
        private constant boolean USE_KNOCKBACK_EFFECTS  = true
            //will only be used, if USE_KNOCKBACK_EFFECTS is true
            private constant string EARTH_EFFECT                    = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
            private constant string WATER_EFFECT                    = "Abilities\\Spells\\Undead\\AbsorbMana\\AbsorbManaBirthMissile.mdl"
            private constant string KNOCKBACK_EFFECT_ATTACHPOINT    = "origin"
            private constant boolean IGNORE_FLYING_UNITS            = true
    
    endglobals

    //The Action Respones
    private function interface onStart              takes Knockback kb                      returns nothing
    private function interface onLoop               takes Knockback kb                      returns nothing
    private function interface onUnitHit            takes Knockback kb, unit hit            returns nothing
    private function interface onDestructableHit    takes Knockback kb, destructable hit    returns nothing
    private function interface onTargetDeath        takes Knockback kb                      returns nothing
    private function interface onEnd                takes Knockback kb                      returns nothing
    private function interface filterFunction       takes Knockback kb, unit enum           returns boolean
        
    //The KnockbackType Struct
    struct KnockbackType
        onStart             onStartAction = 0
        onLoop              onLoopAction = 0
        onUnitHit           onUnitHitAction = 0
        onDestructableHit   onDestructableHitAction = 0
        onEnd               onEndAction = 0
        onTargetDeath       onTargetDeathAction = 0
        filterFunction      filterFunc = 0
    endstruct

    //The Knockback Struct
    struct Knockback
    
        //public readonly variables
        readonly unit caster = null
        readonly unit target = null
        readonly real timeOver = 0.00
        readonly real currentSpeed = 0.00
        readonly real movedDistance = 0.00
        readonly real duration = 0.00
        readonly real distance = 0.00
        readonly real knockbackAngle = 0.00
        
        //variables you can change while the unit is being knocked back
        public real aoeUnit = STANDARD_UNIT_RADIUS
        public real aoeDest = STANDARD_DEST_RADIUS
        public integer array userData[3]
        
        //private variables
        private real deceleration = 0.00
        private real cos = 0.00
        private real sin = 0.00
        private effect fx = null
        private effect kbEffect = null
        private integer kbEffectMode = 0
        private boolean ranDeathAction = false
        private boolean isGround = false
        
        //the actions
        private onStart onStartAction = 0
        private onLoop onLoopAction = 0
        private onUnitHit onUnitHitAction = 0
        private onDestructableHit onDestructableHitAction = 0
        private onEnd onEndAction = 0
        private onTargetDeath onTargetDeathAction = 0
        private filterFunction filterFunc = 0
        
        //just some static variables to make the system faster/work
        private static timer ticker         = null
        private static boolexpr unitFilter  = null 
        private static boolexpr destFilter  = null
        private static code unitActions     = null
        private static code destActions     = null
        private static thistype temp        = 0
        private static rect destRect        = Rect(0,0,1,1)
        private static real tx              = 0.00
        private static real ty              = 0.00
        
        //ListModule
        implement List
        
        
        //saves the actions in the Knockback Struct (will be used in the create method)
        private method assignActions takes KnockbackType kb returns nothing
            set .onStartAction = kb.onStartAction
            set .onLoopAction = kb.onLoopAction
            set .onUnitHitAction = kb.onUnitHitAction
            set .onDestructableHitAction = kb.onDestructableHitAction
            set .onEndAction = kb.onEndAction
            set .onTargetDeathAction = kb.onTargetDeathAction
            set .filterFunc = kb.filterFunc
        endmethod
        
        //this is the filter for destructables around the target
        private static method destFilterMethod takes nothing returns boolean
            local real x = GetDestructableX(GetFilterDestructable())
            local real y = GetDestructableY(GetFilterDestructable())
            return (.tx-x)*(.tx-x) + (.ty-y)*(.ty-y) <= .temp.aoeDest * .temp.aoeDest
        endmethod
        
        //this method will run the onDestructableHit action for every destructable hit
        private static method runDestActions takes nothing returns nothing
            if .temp.onDestructableHitAction != 0 then
                call .temp.onDestructableHitAction.evaluate(.temp, GetEnumDestructable())
            endif
        endmethod
        
        //this method calls the user defined filter method
        private static method filterMethod takes nothing returns boolean
            if .temp.filterFunc != 0 then
                return GetFilterUnit() != .temp.target and temp.filterFunc.evaluate(.temp, GetFilterUnit())
            else
                return GetFilterUnit() != .temp.target
            endif
        endmethod
        
        //this method will run the onUnitHit action for every unit hit
        private static method runUnitActions takes nothing returns nothing
            if .temp.onUnitHitAction != 0 then
                call .temp.onUnitHitAction.evaluate(.temp, GetEnumUnit())
            endif
        endmethod
        
        //cleans up the struct and runs the onEnd actions
        private method onDestroy takes nothing returns nothing
            if .fx != null then
                call DestroyEffect(.fx)
            endif
            static if USE_KNOCKBACK_EFFECTS then
                call DestroyEffect(.kbEffect)
            endif
            if .onEndAction != 0 then
                call .onEndAction.evaluate(this)
            endif
            call .listRemove()
        endmethod
        
        //this method will be called every TIMER_INTERVAL and update the units position as well as runs the onLoop action
        private static method onExpire takes nothing returns nothing
            local thistype this = .first
            local real x 
            local real y 
            loop
                exitwhen this == 0
                
                //run the onLoop action
                if .onLoopAction != 0 then
                    call .onLoopAction.evaluate(this)
                endif
                
                if IsUnitType(.target, UNIT_TYPE_DEAD) or GetUnitTypeId(.target) == 0 and not .ranDeathAction then
                    if .onTargetDeathAction != 0 then
                        call .onTargetDeathAction.evaluate(this)
                    endif
                    set .ranDeathAction = true
                endif
                
                //change the time which has been gone
                set .timeOver = .timeOver + TIMER_INTERVAL
                set .currentSpeed = .currentSpeed - .deceleration
                set .movedDistance = .movedDistance + .currentSpeed
                
                set x = GetUnitX(.target) + (.currentSpeed) * .cos
                set y = GetUnitY(.target) + (.currentSpeed) * .sin
                
                ///check for destructables arround the target
                set .tx = x
                set .ty = y
                call SetRect(.destRect, -.aoeDest, -.aoeDest, .aoeDest, .aoeDest)
                call MoveRectTo(.destRect, x, y)
                call EnumDestructablesInRect(.destRect, .destFilter, .destActions)
                
                //check pathability and set new unit position if walkable
                if IsTerrainWalkable(x, y) then
                    call SetUnitX(.target, x)
                    call SetUnitY(.target, y)
                else
                    static if STOP_WHEN_UNPASSABLE then
                        call .destroy()
                    endif
                endif
                
                //change the knockback effect if the user want to use it
                static if USE_KNOCKBACK_EFFECTS then
                    if .isGround or not IGNORE_FLYING_UNITS then
                        if .kbEffectMode == 2 and IsTerrainLand(x, y) then
                            call DestroyEffect(.kbEffect)
                            set .kbEffect = AddSpecialEffectTarget(EARTH_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
                        elseif .kbEffectMode == 1 and IsTerrainShallowWater(x, y) then
                            call DestroyEffect(.kbEffect)
                            set .kbEffect = AddSpecialEffectTarget(WATER_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
                        endif
                    endif
                endif
                             
                //refresh group and get units arround target
                call GroupRefresh(ENUM_GROUP)
                set .temp = this
                call GroupEnumUnitsInArea(ENUM_GROUP, x, y, .aoeUnit, .unitFilter)
                call ForGroup(ENUM_GROUP, .unitActions)
                
                //if the knockback ended because the target reached the end or the speed is smaller than 0
                if .currentSpeed <= 0 or .movedDistance >= .distance then
                    call .destroy()
                endif
                
                //pause the timer if no knockback is active anymore
                if .count < 1 then
                    call PauseTimer(.ticker)
                endif
                
                //get next instance
                set this = .next
            endloop
            
        endmethod

        //adds or changes the current effect attached to the target
        public method addSpecialEffect takes string fx, string attachPoint returns nothing
            if .fx != null then
                call DestroyEffect(.fx)
            endif
            set .fx = AddSpecialEffectTarget(fx, .target, attachPoint)
        endmethod
        
        //changes the angle of the knockback
        public method operator angle= takes real newAngle returns nothing
            set .knockbackAngle = newAngle
            set .cos = Cos(.knockbackAngle * bj_DEGTORAD)
            set .sin = Sin(.knockbackAngle * bj_DEGTORAD)
        endmethod
        
        //use this method to check if a unit is being knocked back
        static method isUnitKnockedBack takes unit whichUnit returns boolean
            local thistype this = .first
            if this == 0 then
                return false
            endif
            loop
                exitwhen this == 0
                if .target == whichUnit then
                    return true
                endif
                set this = .next
            endloop
            return false
        endmethod
                
        
        //this method creates a new knockback
        public static method create takes unit caster, unit target, real distance, real time, real angle, KnockbackType kbType returns thistype
            local thistype this = thistype.allocate()
            local real speed = 2 * distance / ((time / TIMER_INTERVAL) + 1)
            local real deceleration = speed / (time / TIMER_INTERVAL)
            local integer i = 0
            
            //reset userData
            loop
                exitwhen i >= 2
                set .userData[i] = 0
                set i = i + 1
            endloop
            
            //set variables
            set .caster = caster
            set .target = target
            set .knockbackAngle = angle
            set .distance = distance
            set .duration = time
            set .currentSpeed = speed
            set .deceleration = deceleration
            set .knockbackAngle = angle
            set .cos = Cos(.knockbackAngle * bj_DEGTORAD)
            set .sin = Sin(.knockbackAngle * bj_DEGTORAD)
            
            set .isGround = not IsUnitType(.target, UNIT_TYPE_FLYING)
            
            //Adds the knockback effect when USE_KNOCKBACK_EFFECTS is set to true
            static if USE_KNOCKBACK_EFFECTS then
                if .isGround or not IGNORE_FLYING_UNITS then
                    if IsTerrainLand(GetUnitX(.target), GetUnitY(.target)) then
                        set .kbEffect = AddSpecialEffectTarget(EARTH_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
                        set .kbEffectMode = 1
                    elseif IsTerrainShallowWater(GetUnitX(.target), GetUnitY(.target)) then
                        set .kbEffect = AddSpecialEffectTarget(WATER_EFFECT, .target, KNOCKBACK_EFFECT_ATTACHPOINT)
                        set .kbEffectMode = 2
                    endif
                endif
            endif
            
            //add the event actions to the struct
            call .assignActions(kbType)
            
            //run the onStart action
            if .onStartAction != 0 then
                call .onStartAction.evaluate(this)
            endif
            
            //add the knockback to the List
            call .listAdd()
            
            //start the timer if the new instance is the only one
            if .count == 1 then
                call TimerStart(.ticker, TIMER_INTERVAL, true, function thistype.onExpire)
            endif
        
            return this
        endmethod
    
        //Initialization
        private static method onInit takes nothing returns nothing
            set .ticker = CreateTimer()
            set .unitFilter  = Condition(function thistype.filterMethod)
            set .destFilter  = Condition(function thistype.destFilterMethod)
            set .unitActions = function thistype.runUnitActions
            set .destActions = function thistype.runDestActions
        endmethod
        
        
    endstruct
    

endlibrary

You can also find it in the spells section if you need to look closer.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
That's a terrible terrible knockback system, but what I mean is to indent the code in your first post.

A function statement should be exactly four spaces unindented from its contents, and subsequently loops and ifs follow the same protocol. Deleting spaces doesn't reduce the KB size of your map and it makes it impossibly unreadable too.
 
Ok, what knockback system should i be using instead?

Updated the script and added indenting. It now includes an extra random interval and grenades are not added to the group in the damage function. I also added an extra terget dummy so that i could use a "target unit" spell instead of a target ground. I think it would be much more efficient to have a target ground ability, but i couldn't find one that properly displays missile art.

Also, i did a new test with the now working projectiles, but i found that it caused massive lag which caused the game to freeze. The grenade has about 125 polygons and i think i could reduce that. I don't know however if it is the polycount, the sound events, or the particles which are causing it (it emmits a mere 25 particles/second), it is hard to say which this large ammount of units. I might reduce the ammount of units throwing to about half to ease the lag.
 
Level 20
Joined
Jul 6, 2009
Messages
1,885
For the dummy spell,you can use acid bomb. Create 2 dummies,at caster and target point and order the first dummy to cast acid bomb on second (i'm not sure if you can cast spells on locust units so you can make the second dummy have no model with invulnerable ability,should be same; acid bomb would need 'invulnerable' as valid target). Then you can add expiration timer to second dummy which expires after grenade falls,detect dummy death and create the grenade effect at dying dummy position.
 
That is pretty much what i'm doing right now, if you read the script. Only difference is i use a timer to count the time it would take for the projectile to travel the distance to the target, and then spawn the grenade at a location attached to the timer.

I don't know if it's faster to use a trigger to check when the dummy dies (please tell me if it is), but i like this method since it allows me to use generic dummies without randomly spawning grenades on other abilities. ;)


EDIT: Tried firing it without a model for the dummy projectile. No difference in lag, which means there is nothing wrong with the model. It is purely the throw trigger and the sheer amount if instances it has to fire.
Please help me find a more efficient way to do it, or i will have to find a way to reduce the ammount of units that have to throw.
 
I just found a fatal bug that had reduced performance significantly:

To make sure each unit is in position, i register everytime a unit reaches it's destination. When 90% of the units have done so, i check if the ThrowGrenade flag is set to true, and then i run the ForGroup action to start the spell. However, i had accidentaly placed a 0.5 second wait between which the trigger fires and the flag is reset to false, so the forGroup would run once per every unit that fires the trigger after the other 90% have stopped. This would cause the trigger to throw 3-4 times the many grenade for each unit causing massive lag (imagine 200 grenades flying at the same time!). It's now fixed, but there is still some lag at the point where the grenades land and the grenade "units" are spawned. I still have to figure out what this is caused by.


EDIT: I just ran some tests and got some seriously interresting results!
I altered my test trigger to spawn 48 grenades at the same time at the target location (spaced 100 inbetween), and i was able to spawn almost infinite ammounts of grenades on the map at the same time without significantly reducing frame rate! I also found that the grenade damage trigger doesn't cause any significant lag either (although it does lag for about half a second when i spawn 48 grenades on 48 units).
This leads to the conclusion that it has to be related to this function:

JASS:
function GrenadesFly takes nothing returns nothing
    local unit u = LoadUnitHandle(LineHash, 0, GetHandleId(GetExpiredTimer()) )
    local unit dummy = CreateUnit(GetOwningPlayer(u), 'h00G', GetUnitX(u), GetUnitY(u), GetUnitFacing(u))
    local Line L = Lines[GetUnitUserData(u)]
    local timer t = GetExpiredTimer()
    local real X = GetUnitX(u) +GetRandomReal(-10, 10) + L.GrenadeDistance * Cos(GetUnitFacing(L.controller) * bj_DEGTORAD)
    local real Y = GetUnitY(u) +GetRandomReal(-10, 10) + L.GrenadeDistance * Sin(GetUnitFacing(L.controller) * bj_DEGTORAD)
    local unit target = CreateUnit(GetOwningPlayer(u), 'h00G', X, Y, GetUnitFacing(u))
    call UnitAddAbility(dummy, 'A002')
    call IssueTargetOrderById( dummy, OrderId("acidbomb"), target)
    call UnitApplyTimedLife(dummy, 'BTLF', 5.00)
    call UnitApplyTimedLife(target, 'BTLF', 5.00)
    call RemoveSavedHandle(LineHash, 0, GetHandleId(GetExpiredTimer()))
    call SaveLocationHandle(LineHash, 0, GetHandleId(t), Location(X, Y))
    call TimerStart(t, L.GrenadeDistance/700, false, function GrenadeSpawn)
    set u = null
    set t = null
    set dummy = null
endfunction

It could either be the fact that 48 timers are expiring at roughly the same time, something related to hashtables and loading handles from them, or the fact that i'm using a location to store the X and Y values of the target location (less likely).
 
Last edited:
What is LHV?

The ability used to order the cast is fine since it worked when i tested it without the ability firing. The dummy ability is based on AcidBomb and i have made similar tests there; it lags equally much without the dummy ability being cast. I understand what you mean though, fiddeling with abilities like "Shockwave" can cause infinite loop errors in the system causing similar bugs.

Nestharus suggested that it might be that hashtables load very slowly, and that i should switch to a unit indexing system and save the timers in an array linked to the units index ID. He also claimed that ForGroup and GetEnumUnit were slow natives that could cause lag in theese occasions. I would like to hear your opinion on this.
 
Status
Not open for further replies.
Top