• 🏆 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!

[vJASS] Buggy Knockback Spell

Status
Not open for further replies.
Level 17
Joined
Feb 11, 2011
Messages
1,860
I am making a spell for my map which is as follows:

Forward
Active: The hero charges forward to the targeted unit. Any enemy that he comes into contact with during the charge takes damage and is knocked back 120 distance. The hero gains bonus armour and movement speed during the charge.
The part that I am having trouble with is getting nearby units to get knocked back while charging. The distance is inaccurate and it is laggy while units are being knocked back. Here is the code:
JASS:
scope Forward

    globals
        private constant real KNOCK_DISTANCE = 120
        private constant real KNOCK_DURATION = 1
    endglobals

    public struct KnockbackData
        unit target
        unit caster
        real angle
        real DistLeft = KNOCK_DISTANCE
        
        method destroy takes nothing returns nothing
            set .target = null
            set .caster = null
            call .deallocate()
        endmethod
        
        static method Timer_Actions takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype D = GetTimerData(t)
            local real x
            local real y
            local real x2
            local real y2
            local real distance = KNOCK_DISTANCE / KNOCK_DURATION * 0.03
            if D.DistLeft > 0 then
                set x = GetUnitX(D.target)
                set y = GetUnitY(D.target)
                set x2 = x + distance * Cos(D.angle * bj_DEGTORAD)
                set y2 = y + distance * Sin(D.angle * bj_DEGTORAD)
                if not IsTerrainPathable(x2, y2, PATHING_TYPE_WALKABILITY) then
                    call SetUnitX(D.target, x2)
                    call SetUnitY(D.target, y2)
                    call DestroyEffect(AddSpecialEffectTarget("Abilities\\Weapons\\FlyingMachine\\FlyingMachineImpact.mdl", D.target, "chest"))
                    set D.DistLeft = D.DistLeft - distance
                else
                    call ReleaseTimer(t)
                    call PauseUnit(D.target, false)
                    call SetUnitPathing(D.target, true)
                    call D.destroy()
                endif
            else
                call ReleaseTimer(t)
                call PauseUnit(D.target, false)
                call SetUnitPathing(D.target, true)
                call D.destroy()
            endif
            set t = null
        endmethod
        
        static method Actions takes unit target, unit caster returns thistype
            local thistype D = thistype.allocate()
            local timer t = NewTimer()
            set D.target = target
            set D.caster = caster
            set D.angle = bj_RADTODEG * (Atan2(GetUnitY(D.target) - GetUnitY(D.caster), GetUnitX(D.target) - GetUnitX(D.caster)))
            call PauseUnit(D.target, true)
            call SetUnitPathing(D.target, false)
            call SetTimerData(t, D)
            call TimerStart(t, 0.03, true, function thistype.Timer_Actions)
            set t = null
            return D
        endmethod
        
    endstruct

    public struct ForwardData
        unit caster
        unit target
        real DistLeft
        real angle
        effect overhead
        effect lefthand
        effect righthand
        group KnockedUnits
    
        static method Timer_Actions takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype D = GetTimerData(t)
            local real x
            local real y
            local real x2
            local real y2
            local real xdist
            local real ydist
            local unit dummy
            local unit u
            set x = GetUnitX(D.caster)
            set y = GetUnitY(D.caster)
            if D.DistLeft > 128 then
                set x2 = x + 10 * Cos(D.angle * bj_DEGTORAD)
                set y2 = y + 10 * Sin(D.angle * bj_DEGTORAD)
                call SetUnitX(D.caster, x2)
                call SetUnitY(D.caster, y2)
                set xdist = GetUnitX(D.caster) - GetUnitX(D.target)
                set ydist = GetUnitY(D.caster) - GetUnitY(D.target)
                set D.DistLeft = SquareRoot((xdist * xdist) + (ydist * ydist))
                set D.angle = bj_RADTODEG * (Atan2(GetUnitY(D.target) - GetUnitY(D.caster), GetUnitX(D.target) - GetUnitX(D.caster)))
                call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, 150, null)
                loop
                    set u = FirstOfGroup(bj_lastCreatedGroup)
                    exitwhen u == null
                    if not IsUnitInGroup(u, D.KnockedUnits) and IsUnitEnemy(u, GetOwningPlayer(D.caster)) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(u, UNIT_TYPE_DEAD) and not IsUnit(u, D.target) then
                        call KnockbackData.Actions(u, D.caster)
                        call GroupAddUnit(D.KnockedUnits, u)
                    endif
                    call GroupRemoveUnit(bj_lastCreatedGroup, u)
                endloop
            else
                call ReleaseTimer(t)
                call SetUnitPathing(D.caster, true)
                call PauseUnit(D.caster, false)
                call SetUnitAnimation(D.caster, "stand")
                call D.deallocate()
                set dummy = CreateUnit(GetOwningPlayer(D.caster), 'h001', x, y, 0)
                call RemoveGuardPosition(dummy)
                call UnitAddAbility(dummy, 'A000')
                call IssueTargetOrder(dummy, "thunderbolt", D.target)
                call UnitApplyTimedLife(dummy, 'BTLF', 1)
                call DestroyEffect(D.overhead)
                call DestroyEffect(D.lefthand)
                call DestroyEffect(D.righthand)
                call IssueTargetOrder(D.caster, "attack", D.target)
            endif
            set dummy = null
            set t = null
        endmethod
    
        static method create takes nothing returns thistype
            local thistype D = thistype.allocate()
            local real xdist
            local real ydist
            local timer t = NewTimer()
            set D.caster = GetTriggerUnit()
            set D.target = GetSpellTargetUnit()
            set xdist = GetUnitX(D.caster) - GetUnitX(D.target)
            set ydist = GetUnitY(D.caster) - GetUnitY(D.target)
            set D.DistLeft = SquareRoot((xdist * xdist) + (ydist * ydist))
            call SetUnitPathing(D.caster, false)
            call PauseUnit(D.caster, true)
            call SetUnitAnimationByIndex(D.caster, 6)
            set D.angle = bj_RADTODEG * (Atan2(GetUnitY(D.target) - GetUnitY(D.caster), GetUnitX(D.target) - GetUnitX(D.caster)))
            set D.overhead = AddSpecialEffectTarget("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl", D.caster, "overhead")
            set D.lefthand = AddSpecialEffectTarget("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl", D.caster, "hand,left")
            set D.righthand = AddSpecialEffectTarget("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl", D.caster, "hand,right")
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\NightElf\\BattleRoar\\RoarCaster.mdl", D.caster, "origin"))
            call SetTimerData(t, D)
            call TimerStart(t, 0.03, true, function thistype.Timer_Actions)
            set t = null
            return D
        endmethod
        
        static method Conditions takes nothing returns boolean
            if GetSpellAbilityId() == 'A001' then
                call thistype.create()
            endif
            return false
        endmethod
        
        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.Conditions))
            set t = null
        endmethod
    
    endstruct
    
endscope
What am I doing wrong? Thanks.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
in ForwardData you can store 10 * Cos(D.angle * bj_DEGTORAD) as a struct member instead of calculating it during each loop. Same for the Sin.

You're also using GetUnitX/Y(caster) even though you have the coordinates stored to x2/y2.

Instead of using Squareroot for the dist, leave Squareroot out. Then instead of 128, use 16384 (128^2).

Try not pausing the unit, maybe it bugs the distance for some reason. I wouldn't pause the unit in the first place, I'd use Channel as a base ability for my needs.

You're using x and y when you pick units in range, why not x2 and y2?

I would use a single timer to handle all knockbacks, the interval is so low than you don't need but one timer.

You should precalculate KNOCK_DISTANCE / KNOCK_DURATION * 0.03 into a variable, don't calculate it for all units during every loop.

Creating an effect every 0.03 for each unit knocked back...seriously? Reduce the count by a lot.

You don't need x,y ,x2 and y2 in the knockback loop. Use only x and y for your needs.
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Okay, thanks! I have implemented most of your suggestions. However, the distance is still very wrong. Any ideas? Also, how would I implement one single timer? :D

JASS:
scope Forward

    globals
        private constant real KNOCK_DISTANCE = 120
        private constant real KNOCK_DURATION = 1
        
        private real KNOCK_INVERVAL_DISTANCE = KNOCK_DISTANCE / KNOCK_DURATION * 0.03
    endglobals

    public struct KnockbackData
        unit target
        unit caster
        real angle
        real DistLeft = KNOCK_DISTANCE
        
        method destroy takes nothing returns nothing
            set .target = null
            set .caster = null
            call .deallocate()
        endmethod
        
        static method Timer_Actions takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype D = GetTimerData(t)
            local real x
            local real y
            if D.DistLeft > 0 then
                set x = GetUnitX(D.target)
                set y = GetUnitY(D.target)
                if not IsTerrainPathable(x + KNOCK_INVERVAL_DISTANCE * Cos(D.angle * bj_DEGTORAD), y + KNOCK_INVERVAL_DISTANCE * Sin(D.angle * bj_DEGTORAD), PATHING_TYPE_WALKABILITY) then
                    call SetUnitX(D.target, x + KNOCK_INVERVAL_DISTANCE * Cos(D.angle * bj_DEGTORAD))
                    call SetUnitY(D.target, y + KNOCK_INVERVAL_DISTANCE * Sin(D.angle * bj_DEGTORAD))
                    set D.DistLeft = D.DistLeft - KNOCK_INVERVAL_DISTANCE
                else
                    call ReleaseTimer(t)
                    call PauseUnit(D.target, false)
                    call SetUnitPathing(D.target, true)
                    call D.destroy()
                endif
            else
                call ReleaseTimer(t)
                call PauseUnit(D.target, false)
                call SetUnitPathing(D.target, true)
                call D.destroy()
            endif
            set t = null
        endmethod
        
        static method Actions takes unit target, unit caster returns thistype
            local thistype D = thistype.allocate()
            local timer t = NewTimer()
            set D.target = target
            set D.caster = caster
            set D.angle = bj_RADTODEG * (Atan2(GetUnitY(D.target) - GetUnitY(D.caster), GetUnitX(D.target) - GetUnitX(D.caster)))
            call PauseUnit(D.target, true)
            call SetUnitPathing(D.target, false)
            call SetTimerData(t, D)
            call TimerStart(t, 0.03, true, function thistype.Timer_Actions)
            set t = null
            return D
        endmethod
        
    endstruct

    public struct ForwardData
        unit caster
        unit target
        real DistLeft
        real angle
        real xoffset
        real yoffset
        effect overhead
        effect lefthand
        effect righthand
        group KnockedUnits
    
        static method Timer_Actions takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype D = GetTimerData(t)
            local real x
            local real y
            local real x2
            local real y2
            local real xdist
            local real ydist
            local unit dummy
            local unit u
            set x = GetUnitX(D.caster)
            set y = GetUnitY(D.caster)
            if D.DistLeft > 128 then
                set x2 = x + D.xoffset
                set y2 = y + D.yoffset
                call SetUnitX(D.caster, x2)
                call SetUnitY(D.caster, y2)
                set xdist = GetUnitX(D.caster) - GetUnitX(D.target)
                set ydist = GetUnitY(D.caster) - GetUnitY(D.target)
                set D.DistLeft = SquareRoot((xdist * xdist) + (ydist * ydist))
                set D.angle = bj_RADTODEG * (Atan2(GetUnitY(D.target) - GetUnitY(D.caster), GetUnitX(D.target) - GetUnitX(D.caster)))
                call GroupEnumUnitsInRange(bj_lastCreatedGroup, x2, y2, 150, null)
                loop
                    set u = FirstOfGroup(bj_lastCreatedGroup)
                    exitwhen u == null
                    if not IsUnitInGroup(u, D.KnockedUnits) and IsUnitEnemy(u, GetOwningPlayer(D.caster)) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(u, UNIT_TYPE_DEAD) and not IsUnit(u, D.target) then
                        call GroupAddUnit(D.KnockedUnits, u)
                        call KnockbackData.Actions(u, D.caster)
                    endif
                    call GroupRemoveUnit(bj_lastCreatedGroup, u)
                endloop
            else
                call ReleaseTimer(t)
                call SetUnitPathing(D.caster, true)
                call PauseUnit(D.caster, false)
                call SetUnitAnimation(D.caster, "stand")
                call D.deallocate()
                set dummy = CreateUnit(GetOwningPlayer(D.caster), 'h001', x, y, 0)
                call RemoveGuardPosition(dummy)
                call UnitAddAbility(dummy, 'A000')
                call IssueTargetOrder(dummy, "thunderbolt", D.target)
                call UnitApplyTimedLife(dummy, 'BTLF', 1)
                call DestroyEffect(D.overhead)
                call DestroyEffect(D.lefthand)
                call DestroyEffect(D.righthand)
                call IssueTargetOrder(D.caster, "attack", D.target)
            endif
            set dummy = null
            set t = null
        endmethod
    
        static method create takes nothing returns thistype
            local thistype D = thistype.allocate()
            local real xdist
            local real ydist
            local timer t = NewTimer()
            set D.caster = GetTriggerUnit()
            set D.target = GetSpellTargetUnit()
            set xdist = GetUnitX(D.caster) - GetUnitX(D.target)
            set ydist = GetUnitY(D.caster) - GetUnitY(D.target)
            set D.DistLeft = SquareRoot((xdist * xdist) + (ydist * ydist))
            call SetUnitPathing(D.caster, false)
            call PauseUnit(D.caster, true)
            call SetUnitAnimationByIndex(D.caster, 6)
            set D.angle = bj_RADTODEG * (Atan2(GetUnitY(D.target) - GetUnitY(D.caster), GetUnitX(D.target) - GetUnitX(D.caster)))
            set D.xoffset = 10 * Cos(D.angle * bj_DEGTORAD)
            set D.yoffset = 10 * Sin(D.angle * bj_DEGTORAD)
            set D.overhead = AddSpecialEffectTarget("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl", D.caster, "overhead")
            set D.lefthand = AddSpecialEffectTarget("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl", D.caster, "hand,left")
            set D.righthand = AddSpecialEffectTarget("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl", D.caster, "hand,right")
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\NightElf\\BattleRoar\\RoarCaster.mdl", D.caster, "origin"))
            call SetTimerData(t, D)
            call TimerStart(t, 0.03, true, function thistype.Timer_Actions)
            set t = null
            return D
        endmethod
        
        static method Conditions takes nothing returns boolean
            if GetSpellAbilityId() == 'A001' then
                call thistype.create()
            endif
            return false
        endmethod
        
        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.Conditions))
            set t = null
        endmethod
    
    endstruct
    
endscope
I tested not pausing the unit, but it makes no difference. I will just remove the effects for now.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
JASS:
scope Forward

    globals
        private constant real KNOCK_DISTANCE = 200
        private constant real KNOCK_DURATION = 1
        
        private real KNOCK_INVERVAL_DISTANCE = KNOCK_DISTANCE / KNOCK_DURATION * 0.03
    endglobals

    public struct KnockbackData
        unit target
        unit caster
        real angle
        real DistLeft
        
        method destroy takes nothing returns nothing
            set .target = null
            set .caster = null
            call .deallocate()
        endmethod
        
        static method Timer_Actions takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype D = GetTimerData(t)
            local real x
            local real y
            if D.DistLeft > 0 then
                set x = GetUnitX(D.target) + KNOCK_INVERVAL_DISTANCE * Cos(D.angle)
                set y = GetUnitY(D.target) + KNOCK_INVERVAL_DISTANCE * Sin(D.angle)
                if not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then
                    call SetUnitX(D.target, x)
                    call SetUnitY(D.target, y)
                    set D.DistLeft = D.DistLeft - KNOCK_INVERVAL_DISTANCE
                else
                    call ReleaseTimer(t)
                    call PauseUnit(D.target, false)
                    call SetUnitPathing(D.target, true)
                    call D.destroy()
                endif
            else
                call ReleaseTimer(t)
                call PauseUnit(D.target, false)
                call SetUnitPathing(D.target, true)
                call D.destroy()
            endif
            set t = null
        endmethod
        
        static method Actions takes unit target, unit caster returns thistype
            local thistype D = thistype.allocate()
            local timer t = NewTimer()
            set D.target = target
            set D.caster = caster
            set D.DistLeft = KNOCK_DISTANCE
            set D.angle = Atan2(GetUnitY(D.target) - GetUnitY(D.caster), GetUnitX(D.target) - GetUnitX(D.caster))
            call PauseUnit(D.target, true)
            call SetUnitPathing(D.target, false)
            call SetTimerData(t, D)
            call TimerStart(t, 0.03, true, function thistype.Timer_Actions)
            set t = null
            return D
        endmethod
        
    endstruct

    public struct ForwardData
        unit caster
        unit target
        real DistLeft
        real angle
        real xoffset
        real yoffset
        effect overhead
        effect lefthand
        effect righthand
        group KnockedUnits
    
        static method Timer_Actions takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype D = GetTimerData(t)
            local real x
            local real y
            local real xdist
            local real ydist
            local unit dummy
            local unit u
            if D.DistLeft > 128 then
                set x = GetUnitX(D.caster) + D.xoffset
                set y = GetUnitY(D.caster) + D.yoffset
                call SetUnitX(D.caster, x)
                call SetUnitY(D.caster, y)
                set xdist = x - GetUnitX(D.target)
                set ydist = y - GetUnitY(D.target)
                set D.DistLeft = SquareRoot(xdist * xdist + ydist * ydist)
                set D.angle = Atan2(GetUnitY(D.target) - y, GetUnitX(D.target) - x)
                call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, 150, null)
                loop
                    set u = FirstOfGroup(bj_lastCreatedGroup)
                    exitwhen u == null
                    if not IsUnitInGroup(u, D.KnockedUnits) /*
                    */and IsUnitEnemy(u, GetOwningPlayer(D.caster))/*
                    */and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)/*
                    */and not IsUnitType(u, UNIT_TYPE_DEAD)/*
                    */and not IsUnit(u, D.target) then
                        call GroupAddUnit(D.KnockedUnits, u)
                        call KnockbackData.Actions(u, D.caster)
                    endif
                    call GroupRemoveUnit(bj_lastCreatedGroup, u)
                endloop
            else
                call ReleaseTimer(t)
                call SetUnitPathing(D.caster, true)
                call PauseUnit(D.caster, false)
                call SetUnitAnimation(D.caster, "stand")
                call D.deallocate()
                set dummy = CreateUnit(GetOwningPlayer(D.caster), 'h001', x, y, 0)
                call RemoveGuardPosition(dummy)
                call UnitAddAbility(dummy, 'A000')
                call IssueTargetOrder(dummy, "thunderbolt", D.target)
                call UnitApplyTimedLife(dummy, 'BTLF', 1)
                call DestroyEffect(D.overhead)
                call DestroyEffect(D.lefthand)
                call DestroyEffect(D.righthand)
                call IssueTargetOrder(D.caster, "attack", D.target)
                set dummy = null
            endif
            set t = null
        endmethod
    
        static method create takes nothing returns thistype
            local thistype D = thistype.allocate()
            local real xdist
            local real ydist
            local timer t = NewTimer()
            set D.caster = GetTriggerUnit()
            set D.target = GetSpellTargetUnit()
            set xdist = GetUnitX(D.caster) - GetUnitX(D.target)
            set ydist = GetUnitY(D.caster) - GetUnitY(D.target)
            set D.DistLeft = SquareRoot(xdist*xdist + ydist*ydist)
            call SetUnitPathing(D.caster, false)
            call PauseUnit(D.caster, true)
            call SetUnitAnimationByIndex(D.caster, 6)
            set D.angle = Atan2(GetUnitY(D.target) - GetUnitY(D.caster), GetUnitX(D.target) - GetUnitX(D.caster))
            set D.xoffset = 10 * Cos(D.angle)
            set D.yoffset = 10 * Sin(D.angle)
            set D.overhead = AddSpecialEffectTarget("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl", D.caster, "overhead")
            set D.lefthand = AddSpecialEffectTarget("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl", D.caster, "hand,left")
            set D.righthand = AddSpecialEffectTarget("Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl", D.caster, "hand,right")
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\NightElf\\BattleRoar\\RoarCaster.mdl", D.caster, "origin"))
            call SetTimerData(t, D)
            call TimerStart(t, 0.03, true, function thistype.Timer_Actions)
            set t = null
            return D
        endmethod
        
        static method Conditions takes nothing returns boolean
            if GetSpellAbilityId() == 'A001' then
                call thistype.create()
            endif
            return false
        endmethod
        
        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.Conditions))
            set t = null
        endmethod
    
    endstruct
    
endscope

I made some changes. I deleted all RADTODEG/DEGTORADs, so you'll see using radians is actually simpler than using angles here.

You should use only one timer, use the same for both structs. You can keep track of instance coun with an integer and then start/stop the time as needed.

EDIT: Oh, and KnockedUnits is a null group. You're not creating it.
 
Status
Not open for further replies.
Top