Impale script bug

Reaction score
91
Okay, as an idea from DotA and waaaks!, I wanted to make my own impale system for personal use due to the minor bugs the normal skill has. It worked perfectly when using it on a single unit but the problem comes when I impale more than 2-3 units from the "line" - as they return back to the ground a huge lag spike is created and my handle counter goes wild. The only things I run when the units get back to the ground are damaging, reseting height to 0 and applying a stun which duration is supposed to get refreshed if multiple impales are casted on the same unit while it's being stunned. I can't find a reason why this lag spike is created... Also, when it ends a random unit gets four-five times damaged instead of only once and then the system totally bugs and instead of tossing units it just makes random units get their height increased by 24 permanently.

JASS:
library ImpaleLib initializer Init uses TimerUtils, Table, xebasic, TimedEffects, GroupUtils

globals
    private constant integer AID_STUN = 'A00E' // Stun spell with 10 sec duration.
    private constant integer BID_STUN = 'B002' // Buff that comes with the spell
    private constant string  OID_STUN = "thunderbolt" // Order string
    private constant real RANGE = 150. // Range between sfx spikes creation
    private constant real TIME = 1.04 // Time in the air (including falling down).
    private constant real PERIOD = 0.03125 // Period for increasing height.
    private constant real PERIOD2 = 2 * PERIOD // Period for creating spikes.
    private constant real HEIGHT = 24. // Height increment per tick.
    // Some sfxs.
    private constant string SFX_IMPALE = "Abilities\\Spells\\Undead\\Impale\\ImpaleHitTarget.mdl"
    private constant string SFX_SPIKES = "Abilities\\Spells\\Undead\\Impale\\ImpaleMissTarget.mdl"
endglobals

globals
    private HandleTable ht
    private Table t
    private Table s
endglobals

private struct Data
    unit targ
    timer tim 
    integer ticks
    
    private static method Callback takes nothing returns nothing
        local Data d = Data(ht[GetExpiredTimer()])
        set d.ticks = d.ticks - 1 
        if d.ticks <= 0 or GetWidgetLife(d.targ) < 0.405 then
            call UnitRemoveAbility(d.targ, BID_STUN)
            call d.destroy() // End the stun.
        endif
    endmethod
    
    static method create takes unit targ, real dur returns Data
        local Data d = Data.allocate()
        local integer id = GetUnitId(targ)
        set d.targ = targ
        set d.ticks = R2I(dur / PERIOD)
        set d.tim = NewTimer()
        set ht[d.tim] = integer(d)
        set s[id] = integer(d)
        
        // Dummy stuff.
        //call UnitRemoveAbility(d.targ, 'Avul')
        call GetAvailableDummy(Player(13))
        call SetUnitX(dum, GetUnitX(d.targ))
        call SetUnitY(dum, GetUnitY(d.targ))
        call AddAndCastTarget(dum, d.targ, AID_STUN, 1, OID_STUN) 
        call ReleaseDummy(dum)
        
        call TimerStart(d.tim, PERIOD, true, function Data.Callback)
        return d
    endmethod
    
    method onDestroy takes nothing returns nothing
        // I think this stuff is never called...
        call ht.flush(.tim)
        call ReleaseTimer(.tim)
        call BJDebugMsg("STUN DATA DESTROYED!")
    endmethod
endstruct

function ImpaleStunTarget takes unit targ, real stun returns nothing
    call Data.create(targ, stun)
endfunction

private struct Data2
    unit cast
    unit targ
    real dmg
    real stun
    real facing
    integer ticks
    integer halfticks
    timer tim
    boolean flip = false
    
    private static method Callback takes nothing returns nothing
        local Data2 d = Data2(ht[GetExpiredTimer()])
        set d.ticks = d.ticks - 1
        if d.ticks == d.halfticks then
            set d.flip = true // Do a height deduction. 
        endif
        if d.ticks <= 0 then
            // Make it stop since the last order was hold position.
            call IssueImmediateOrder(d.targ, "stop")
            call UnitDamageTarget(d.cast, d.targ, d.dmg, true, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, null)
            call UnitAddAbility(d.targ, XE_HEIGHT_ENABLER)
            call UnitRemoveAbility(d.targ, XE_HEIGHT_ENABLER)
            call SetUnitFlyHeight(d.targ, 0., 0.)
            if d.stun != 0. then
                call ImpaleStunTarget(d.targ, d.stun)
            endif
            call d.destroy()
            return 
        endif
        call UnitAddAbility(d.targ, XE_HEIGHT_ENABLER)
        call UnitRemoveAbility(d.targ, XE_HEIGHT_ENABLER)
        if d.flip == true then // Height deduction? 
            call SetUnitFlyHeight(d.targ, GetUnitFlyHeight(d.targ) - HEIGHT, 0.)
        else // Then it should be increased.
            call SetUnitFlyHeight(d.targ, GetUnitFlyHeight(d.targ) + HEIGHT, 0.)
        endif
        // Simulate a "disabling" process instead of using pause. 
        call IssueImmediateOrder(d.targ, "holdposition")
        call SetUnitFacing(d.targ, d.facing)
    endmethod

    static method create takes unit cast, unit targ, real dmg, real stun returns Data2
        local Data2 d = Data2.allocate()
        local integer id = GetUnitId(targ) 
        set d.cast = cast
        set d.targ = targ
        set d.facing = GetUnitFacing(d.targ)
        set d.dmg = dmg
        set d.stun = stun
        set d.ticks = R2I(TIME / PERIOD)
        set d.halfticks = R2I(d.ticks * 0.5) // To check when the unit has to return back
                                             // to the ground.
        // Timed effect.
        call StartTimedEffect(AddSpecialEffect(SFX_IMPALE, GetUnitX(d.targ), GetUnitY(d.targ)), TIME)
        set d.tim = NewTimer()
        set ht[d.tim] = integer(d)
        set t[id] = integer(d)
        call TimerStart(d.tim, PERIOD, true, function Data2.Callback)
        return d
    endmethod
    
    method onDestroy takes nothing returns nothing
        call ht.flush(.tim)
        call ReleaseTimer(.tim)
        call BJDebugMsg("TARGET DATA DESTROYED!")
    endmethod
endstruct

function ImpaleUnit takes unit cast, unit targ, real dmg, real stun returns nothing
    call Data2.create(cast, targ, dmg, stun)
endfunction

private struct Data3
    unit cast
    real dmg
    integer ticks
    real angle
    real x
    real y
    real cosp
    real sinp
    real aoe
    real stun
    timer tim
    group gr
    
    private static method GetEnemies takes nothing returns boolean
        return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(pass)) and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) == true
    endmethod
    
    private static method Callback takes nothing returns nothing
        local Data3 d = Data3(ht[GetExpiredTimer()])
        set d.x = d.x + d.cosp
        set d.y = d.y + d.sinp
        call StartTimedEffect(AddSpecialEffect(SFX_SPIKES, d.x, d.y), 1.)
        set pass = d.cast
        call GroupClear(ENUM_GROUP)
        call GroupEnumUnitsInRange(ENUM_GROUP, d.x, d.y, d.aoe, Condition(function Data3.GetEnemies))
        loop
            set fir = FirstOfGroup(ENUM_GROUP)
            exitwhen fir == null
            if IsUnitInGroup(fir, d.gr) == false then
                call GroupAddUnit(d.gr, fir)
                call ImpaleUnit(d.cast, fir, d.dmg, d.stun) // Do an impale...
            endif
            call GroupRemoveUnit(ENUM_GROUP, fir)
        endloop
        set d.ticks = d.ticks - 1
        if d.ticks <= 0 then
            call d.destroy()
        endif
    endmethod
    
    static method create takes unit cast, real dmg, real tx, real ty, real aoe, real distance, real stun returns Data3
        local Data3 d = Data3.allocate()
        set d.x = GetUnitX(cast)
        set d.y = GetUnitY(cast)
        set d.angle = Atan2(ty - d.y, tx - d.x)
        set d.cosp = RANGE * Cos(d.angle)
        set d.sinp = RANGE * Sin(d.angle)
        set d.cast = cast
        set d.dmg = dmg
        set d.aoe = aoe
        set d.stun = stun // Stun duration.
        set d.ticks = R2I(distance / RANGE)
        set d.gr = NewGroup() 
        set d.tim = NewTimer()
        set ht[d.tim] = integer(d)
        call TimerStart(d.tim, PERIOD2, true, function Data3.Callback)
        return d
    endmethod
    
    method onDestroy takes nothing returns nothing
        call ht.flush(.tim)
        call ReleaseTimer(.tim)
        call ReleaseGroup(.gr)
        call BJDebugMsg("LINE DATA DESTROYED!")
    endmethod
endstruct

function ImpaleLine takes unit cast, real dmg, real tx, real ty, real aoe, real distance, real stun returns nothing
    call Data3.create(cast, dmg, tx, ty, aoe, distance, stun)
endfunction

private function Init takes nothing returns nothing
    set ht = HandleTable.create()
    set t = Table.create()
    set s = Table.create()
endfunction

endlibrary



EDIT:
Nvm, fixed, if someone wants the code to learn or just use:
JASS:
library ImpaleLib initializer Init uses TimerUtils, Table, DummyLib, xebasic, TimedEffects, GroupUtils, AbilityPreload

globals
    private constant integer AID_STUN = 'A00E' 
    private constant integer BID_STUN = 'B002' 
    private constant string  OID_STUN = "thunderbolt" 
    private constant real RANGE = 150.
    private constant real TIME = 1.04 
    private constant real PERIOD = 0.03125 
    private constant real PERIOD2 = 2 * PERIOD 
    private constant real HEIGHT = 24. 
    private constant string SFX_IMPALE = "Abilities\\Spells\\Undead\\Impale\\ImpaleHitTarget.mdl"
    private constant string SFX_SPIKES = "Abilities\\Spells\\Undead\\Impale\\ImpaleMissTarget.mdl"
endglobals

globals
    private HandleTable ht
    private Table t
    private unit dumi
endglobals

private struct Unit
    unit cast
    unit targ
    real dmg
    real stun
    real facing
    integer ticks
    integer halfticks
    timer tim
    boolean flip = false
    
    private static method Callback takes nothing returns nothing
        local Unit d = ht[GetExpiredTimer()]
        set d.ticks = d.ticks - 1
        if d.ticks == d.halfticks then
            set d.flip = true 
        endif
        if d.ticks <= 0 then
        //==============================================
            call UnitRemoveAbility(d.targ, 'Avul')    // You should remove this line.
        //==============================================
            call IssueImmediateOrder(d.targ, "stop")
            call UnitDamageTarget(d.cast, d.targ, d.dmg, true, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, null)
            call UnitAddAbility(d.targ, XE_HEIGHT_ENABLER)
            call UnitRemoveAbility(d.targ, XE_HEIGHT_ENABLER)
            call SetUnitFlyHeight(d.targ, 0., 0.)
            if d.stun != 0. then
                call SetUnitX(dumi, GetUnitX(d.targ))
                call SetUnitY(dumi, GetUnitY(d.targ))
                call AddAndCastTarget(dumi, d.targ, AID_STUN, R2I(d.stun), OID_STUN) 
            endif
            call UnitRemoveAbility(d.targ, XE_HEIGHT_ENABLER)
            call d.destroy()
        else
            call UnitAddAbility(d.targ, XE_HEIGHT_ENABLER)
            call UnitRemoveAbility(d.targ, XE_HEIGHT_ENABLER)
            if d.flip == true then 
                call SetUnitFlyHeight(d.targ, GetUnitFlyHeight(d.targ) - HEIGHT, 0.)
            else 
                call SetUnitFlyHeight(d.targ, GetUnitFlyHeight(d.targ) + HEIGHT, 0.)
            endif
            call IssueImmediateOrder(d.targ, "holdposition")
            call SetUnitFacing(d.targ, d.facing)
        endif
    endmethod

    static method create takes unit cast, unit targ, real dmg, real stun returns Unit
        local Unit d = Unit.allocate()
        local integer id = GetUnitId(targ) 
        set d.cast = cast
        set d.targ = targ
        set d.facing = GetUnitFacing(d.targ)
        set d.dmg = dmg
        set d.stun = stun
        set d.ticks = R2I(TIME / PERIOD)
        set d.halfticks = R2I(d.ticks * 0.5) 
        call StartTimedEffect(AddSpecialEffect(SFX_IMPALE, GetUnitX(d.targ), GetUnitY(d.targ)), TIME)
        set d.tim = NewTimer()
        set ht[d.tim] = d
        set t[id] = d
        call TimerStart(d.tim, PERIOD, true, function Unit.Callback)
        return d
    endmethod
    
    method onDestroy takes nothing returns nothing
        call ht.flush(.tim)
        call ReleaseTimer(.tim)
    endmethod
endstruct

function ImpaleUnit takes unit cast, unit targ, real dmg, real stun returns nothing
    call Unit.create(cast, targ, dmg, stun)
endfunction

private struct Line
    unit cast
    real dmg
    integer ticks
    real angle
    real x
    real y
    real cosp
    real sinp
    real aoe
    real stun
    timer tim
    group gr
    
    private static method GetEnemies takes nothing returns boolean
        return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(pass)) and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) == true
    endmethod
    
    private static method Callback takes nothing returns nothing
        local Line d = ht[GetExpiredTimer()]
        set d.x = d.x + d.cosp
        set d.y = d.y + d.sinp
        call StartTimedEffect(AddSpecialEffect(SFX_SPIKES, d.x, d.y), 1.)
        set pass = d.cast
        call GroupClear(ENUM_GROUP)
        call GroupEnumUnitsInRange(ENUM_GROUP, d.x, d.y, d.aoe, Condition(function Line.GetEnemies))
        loop
            set fir = FirstOfGroup(ENUM_GROUP)
            exitwhen fir == null
            if IsUnitInGroup(fir, d.gr) == false then
                call GroupAddUnit(d.gr, fir)
                call ImpaleUnit(d.cast, fir, d.dmg, d.stun) // Do an impale...
            endif
            call GroupRemoveUnit(ENUM_GROUP, fir)
        endloop
        set d.ticks = d.ticks - 1
        if d.ticks <= 0 then
            call d.destroy()
        endif
    endmethod
    
    static method create takes unit cast, real dmg, real tx, real ty, real aoe, real distance, real stun returns Line
        local Line d = Line.allocate()
        local unit gstu = GetSpellTargetUnit()
        if gstu != null then
            set tx = GetUnitX(gstu)
            set ty = GetUnitY(gstu)
            set gstu = null
        endif
        set d.x = GetUnitX(cast)
        set d.y = GetUnitY(cast)
        set d.angle = Atan2(ty - d.y, tx - d.x)
        set d.cosp = RANGE * Cos(d.angle)
        set d.sinp = RANGE * Sin(d.angle)
        set d.cast = cast
        set d.dmg = dmg
        set d.aoe = aoe
        set d.stun = stun
        set d.ticks = R2I(distance / RANGE)
        set d.gr = NewGroup() 
        set d.tim = NewTimer()
        set ht[d.tim] = d
        call TimerStart(d.tim, PERIOD2, true, function Line.Callback)
        return d
    endmethod
    
    method onDestroy takes nothing returns nothing
        call ht.flush(.tim)
        call ReleaseTimer(.tim)
        call ReleaseGroup(.gr)
    endmethod
endstruct

function ImpaleLine takes unit cast, real dmg, real tx, real ty, real aoe, real distance, real stun returns nothing
    call Line.create(cast, dmg, tx, ty, aoe, distance, stun)
endfunction

private function Init takes nothing returns nothing
    set ht = HandleTable.create()
    set t = Table.create()
    
    call AbilityPreload(AID_STUN)
    
    set dumi = CreateUnit(Player(13), XE_DUMMY_UNITID, 0., 0., 0.)
    call UnitAddAbility(dumi, 'Aloc')
    call UnitAddAbility(dumi, AID_STUN)
endfunction

endlibrary
 

Viikuna

No Marlo no game.
Reaction score
265
I think wraithseeker posted some script I made that does things like that somewhere here. Wait I get you a link.

edit. link

You should just make this system to use AutoIndex and then use it ( Or at least make your own based on this ). But yea, I can try to find that error from your code, gonna take few minutes.
 
Reaction score
91
Well, I'm not searching for height stuff nor improvements on this one... I just want to know why do I get a massive 5-10 seconds spike and suddenly some odd stuff happen after it.
 

Viikuna

No Marlo no game.
Reaction score
265
Your code makes no sense to me at all, I think I either need some sleep or then I need to concentrate more. And Im starting to hate those Data1, Data2 names for structtype...

Anyways, I think its not fully MUI for somereason. Going to investigate further...
 
Reaction score
91
Okay, I found the reason for my epic spike...
It comes from my dummy unit library.

JASS:

private function CreateDummyUnits takes nothing returns nothing
    local integer a = 0
    local integer id 
    loop
        exitwhen a > DUMMY_UNITS_CREATED
        set DUMMY[a] = CreateUnit(Player(13), XE_DUMMY_UNITID, 9999., 9999., 0.)
        call UnitAddAbility(DUMMY[a], 'Aloc')
        set id = GetUnitId(DUMMY[a])
        set IS_SOME_DUMMY[id] = true
        set IS_IN_USE[id] = false
        set a = a + 1
    endloop
endfunction

This function is ran at map initialization. It should create a dummy unit and store a boolean for it (autoindex property) IS_IN_USE to false, which would refer if the unit is currently in use by some script.
Then I have a function that searches for a free dummy unit and if it finds one it assigns it to a global unit variable and makes its property to be in use.
JASS:

function GetAvailableDummy takes player p returns nothing
    local integer id  
    loop
        set dum = DUMMY[GetRandomInt(0, DUMMY_UNITS_CREATED)]
        set id = GetUnitId(dum)
        exitwhen (IS_IN_USE[id] == false)
    endloop
    call SetUnitOwner(dum, p, false)
    set IS_IN_USE[id] = true
endfunction


However, calling GetAvailableDummy seems to cause the massive spike and AutoIndex starts flooding my screen with some debug messages about indexing a unit that hasn't previously received an index. Could there be some reason that the first function that creates the units isn't working ?!

JASS:

library DummyLib initializer Init uses xebasic, Table, TimerUtils, AutoIndex, RegisterAnyUnitEvent

globals
    private constant real TIME = 5.
    private constant real WAIT = 0.28
endglobals

globals
    constant integer DUMMY_UNITS_CREATED = 100
endglobals

globals
    private HandleTable ht
endglobals

globals
    unit dum = null
endglobals

private function CreateDummyUnits takes nothing returns nothing
    local integer a = 0
    local integer id 
    loop
        exitwhen a > DUMMY_UNITS_CREATED
        set DUMMY[a] = CreateUnit(Player(13), XE_DUMMY_UNITID, 9999., 9999., 0.)
        call UnitAddAbility(DUMMY[a], 'Aloc')
        set id = GetUnitId(DUMMY[a])
        set IS_SOME_DUMMY[id] = true
        set IS_IN_USE[id] = false
        set a = a + 1
    endloop
endfunction

private struct Data
    unit dummy
    timer tim 
    
    private static method Callback takes nothing returns nothing
        call Data(ht[GetExpiredTimer()]).destroy()
    endmethod
    
    static method create takes unit dummy returns Data
        local Data d = Data.allocate()
        set d.dummy = dummy
        set d.tim = NewTimer()
        set ht[d.tim] = integer(d)
        call TimerStart(d.tim, TIME, false, function Data.Callback)
        return d
    endmethod
    
    method onDestroy takes nothing returns nothing
        call ht.flush(.tim)
        call ReleaseTimer(.tim)
        call RemoveUnitEx(.dummy)
    endmethod
endstruct

private struct Release
    unit dum
    timer tim 
    
    private static method Callback takes nothing returns nothing
        call Release(ht[GetExpiredTimer()]).destroy()
    endmethod
    
    static method create takes unit dum returns Release
        local Release d = Release.allocate()
        set d.dum = dum
        set d.tim = NewTimer()
        set ht[d.tim] = integer(d)
        call TimerStart(d.tim, WAIT, false, function Release.Callback)
        return d
    endmethod
    
    method onDestroy takes nothing returns nothing
        call ReleaseTimer(.tim)
        call ht.flush(.tim)
        call SetUnitOwner(.dum, Player(13), false)
        set IS_IN_USE[GetUnitId(.dum)] = false
        call SetUnitX(.dum, 999.)
        call SetUnitY(.dum, 999.)
    endmethod
endstruct
    
private function Actions takes nothing returns nothing
    local unit dummy = GetTriggerUnit()
    local integer id = GetUnitId(dummy)
    if IS_SOME_DUMMY[id] == true then
        call Data.create(dummy)
    endif
    set dummy = null
endfunction

function GetAvailableDummy takes player p returns nothing
    local integer id  
    loop
        set dum = DUMMY[GetRandomInt(0, DUMMY_UNITS_CREATED)]
        set id = GetUnitId(dum)
        exitwhen (IS_IN_USE[id] == false)
    endloop
    call SetUnitOwner(dum, p, false)
    set IS_IN_USE[id] = true
endfunction

function ReleaseDummy takes unit dum returns nothing
    call Release.create(dum)
endfunction

function AddAndCastTarget takes unit dum, unit targ, integer aid, integer lvl, string oid returns nothing
    call UnitAddAbility(dum, aid)
    call SetUnitAbilityLevel(dum, aid, lvl)
    call IssueTargetOrder(dum, oid, targ)
endfunction

function AddAndCast takes unit dum, integer aid, integer lvl, string oid returns nothing
    call UnitAddAbility(dum, aid)
    call SetUnitAbilityLevel(dum, aid, lvl)
    call IssueImmediateOrder(dum, oid)
endfunction

private function Init takes nothing returns nothing
    call RegisterAnyUnitEvent(EVENT_PLAYER_UNIT_DEATH, null, function Actions)
    
    call CreateDummyUnits()
        
    set ht = HandleTable.create()
endfunction

endlibrary
 

Viikuna

No Marlo no game.
Reaction score
265
Why such a hassle? Cant you just use one global dummy to cast all stormbolts. ( Like in my AssAssTidies map ( link ) )
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top