• 🏆 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] Wow i feel dumb for asking this...

Status
Not open for further replies.
Level 5
Joined
Nov 14, 2007
Messages
161
FIXED/SOLVED THANKS!






ive been wanting a knockback system for some of the skills on my map, and the one that seemed easiest and was explained well enough was this one http://www.hiveworkshop.com/forums/f280/coding-efficient-knockback-vjass-41418/
but im having problems with it and im not sure if i just typed it out wrong, or im calling it incorrectly or just missed where to put it.

currently it sets in the map header (on trigger page where the map name is) and i am using the latest JASS NewGen


EDIT: going to look at an alternative method do doing this, see a few posts later.

JASS:
library Knockback

struct Knockback_Data
    unit u
    real a
    real d1
    real d2

    real sin
    real cos

    real r

    string s = ""
    effect e = null
endstruct

globals
    timer Tim = CreateTimer()
Knockback_Data array Ar
    integer Total = 0

    item I = CreateItem('ciri', 0, 0)

//    boolexpr Filter
endglobals

function CheckPathabilityTrickGet takes nothing returns nothing
    set bj_rescueChangeColorUnit = bj_rescueChangeColorUnit or GetEnumItem() != I
endfunction

function CheckPathabilityTrick takes real x, real y returns boolean
    local integer i = 30
    local real X
    local real Y
    local rect r
    call SetItemPosition(I, x, y)
    set X = GetItemX(I) - x
set Y = GetItemY(I) - y
    if X * X + Y * Y <= 100 then
        return true
    endif
    set r = Rect(x - i, y - i, x + i, y + i)
    set bj_rescueChangeColorUnit = false
call EnumItemsInRect(r, null, function CheckPathabilityTrickGet)
    call RemoveRect(r)
    set r = null
    return bj_rescueChangeColorUnit
endfunction

function CheckPathability takes real x, real y returns boolean
    local boolean b = CheckPathabilityTrick(x, y)
    call SetItemVisible(I, false)
return b
endfunction

function Knockback_TreeFilter takes nothing returns boolean
    local integer d = GetDestructableTypeId(GetFilterDestructable())
    return d == 'ATtr' or d == 'BTtw' or d == 'KTtw' or d == 'YTft' or d == 'JTct' or d == 'YTst' or d == 'YTct' or d == 'YTwt' or d == 'JTwt' or d == 'JTwt' or d == 'FTtw' or d == 'CTtr' or d == 'ITtw' or d == 'NTtw' or d == 'OTtw' or d == 'ZTtw' or d == 'WTst' or d == 'LTlt' or d == 'GTsh' or d == 'Xtlt' or d == 'WTtw' or d == 'Attc' or d == 'BTtc' or d == 'CTtc' or d == 'ITtc' or d == 'NTtc' or d == 'ZTtc'
endfunction

constant function Interval takes nothing returns real
    return 0.04
endfunction

function Knockback_Execute takes nothing returns nothing
    local Knockback_Data kd
    local integer i = 0
    local real x
    local real y
    local rect r
    local boolexpr b

    loop
        exitwhen i >= Total
        set kd = Ar[i]

        if kd.s != null and kd.s != null then
            set x = GetUnitX(kd.u)
            set y = GetUnitY(kd.u)

            call DestroyEffect(AddSpecialEffect(kd.s, x, y))

            set x = x + kd.d1 * kd.cos
            set y = y + kd.d1 * kd.sin
        else
            set x = GetUnitX(kd.u) + kd.d1 * kd.cos
            set y = GetUnitY(kd.u) + kd.d1 * kd.sin
        endif

        if kd.r != 0 then
            set r = Rect(x - kd.r, y - kd.r, x + kd.r, y + kd.r)
            set b = Filter(function Knockback_TreeFilter)
            call EnumDestructablesInRect(r, b, function Knockback_TreeFilter)
            call RemoveRect(r)
            call DestroyBoolExpr(b)
        endif

        if CheckPathability(x, y) then
            call SetUnitX(kd.u, x)
            call SetUnitY(kd.u, y)
        endif

        set kd.d1 = kd.d1 - kd.d2


        if kd.d1 <= 0 or not CheckPathability(x, y) then
            if kd.e != null then
                call DestroyEffect(kd.e)
            endif
            call PauseUnit(kd.u, false)
            set Ar[i] = Ar[Total - 1]
set Total = Total - 1
            call kd.destroy()
        endif

        set i = i + 1
    endloop

    if Total == 0 then
        call PauseTimer(Tim)
    endif
endfunction

function Knockback takes unit u, real d, real a, real w, real r, integer t, string s, string p returns Knockback_Data
    local Knockback_Data kd = Knockback_Data.create()
    local integer q = R2I(w / Interval())

    set kd.u = u
    set kd.a = a
    set kd.d1 = 2 * d / (q + 1)
    set kd.d2 = kd.d1 / q

    set kd.sin = Sin(a)
    set kd.cos = Cos(a)

    set kd.r = r

    if s != "" and s != null then
        if t == 2 then
            if p != "" and p != null then
                set kd.e = AddSpecialEffectTarget(s, u, p)
            else
                set kd.e = AddSpecialEffectTarget(s, u, "chest")
            endif
        elseif t == 1 then
            set kd.s = s
        endif
    endif

    call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
    call PauseUnit(u, true)

    if Total == 0 then
        call TimerStart(Tim, Interval(), true, function Knockback_Execute)
    endif

    set Total = Total + 1
    set Ar[Total - 1] = kd

    return kd
    
    
endfunction

function KnockBEZ takes unit u, real d, real a, real w returns Knockback_Data
    return Knockback(u, d, a, w, 0, 0, "", "")
endfunction
endlibrary
i had to do some slight changing because it wouldnt save correctly without the changes but essentially it's the same.

  • Magic Bullet gui test
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to test knockback yep the test
    • Actions
      • Custom script: local unit Knocked = GetTriggerUnit()
      • Custom script: call Knockback(Knocked, 1000, 5, 100, 0, 0, "", "")

i believe the skill should take the unit and knock him back a distance of 1000 in direction 5 radians, over 100 executions of a .04 timer. yes?

my major questions are the Radians "a" and "w" which should be how long it takes them to get knocked back.

can anyone spot my problem or can explain what im missing?

sorry for asking, i know it's somewhere in the forums but my last few searches didn't effectively answer my questions hope ill get better results this way.

Thanks for reading YO_MA_MA
 
Last edited:
Level 22
Joined
Dec 31, 2006
Messages
2,216
a is the angle in radians and w is the duration in seconds.
To convert from degrees to radians you multiply it with (PI / 180).
So currently you want to knock back the unit "Knocked" 1000 units with the angle 5 radians(286 degrees) for 100 seconds.
 
Level 5
Joined
Nov 14, 2007
Messages
161
actually i was just trying to see if it's working correctly before i come up with a more elaborate trigger that does what i want. i guess w should be a little lower ;)

ok, i just tested with w = 2 thinking that would be more realistic.
  • Custom script: call Knockback(Knocked, 1000, 5, 2, 0, 0, "", "")
nothing seems to happen.
is there some sort of compiling i have to do or anything? or just throw the library into the header?

edit, i put a display text to all at the end of this trigger and it does display it. so it's not my skill/trigger.
 
Level 11
Joined
Feb 22, 2006
Messages
752
Ugh...every day I wish more and more that people would comment their code before posting it (or at least use variable names that actually tell you what they're supposed to represent).

The fact that your original code generated compile-time errors isn't encouraging. But anyway, the first problem I see with this is that you're not handling your array correctly when you start destroying structs. You destroy structs, but you never update what the array indexes hold. This probably ISN'T what's causing your problem (btw, what IS the problem exactly?) since your test only knocks back one unit, and that array index problem would only be an issue if you started having multiple knockbacks running at the same time. However, it is a serious issue that needs fixing if you ever want this to work.

Another problem I noticed: in the Knockback() function, you set q = R2I( w / Interval() ) then you set kd.d2 = kd.d1 / q. And in your test trigger, you specify that w = 0, so essentially you are dividing by 0. I don't know how JASS handles division by 0 with reals, but it's probably not a good thing to have in your code.

I've been staring at your code for the past 30 min, and I can honestly say that I'm getting nowhere with it, but here's the knockback system I use in my map. It doesn't have any of the terrain pathability checks (I just use SetUnitPosition and I'm comfortable with that), but you can easily adapt the code to fit your needs.

Geometry System:

JASS:
library Geometry

struct Point
    real x
    real y
    
    static method create takes real x, real y returns Point
        local Point p = Point.allocate()
        set p.x = x
        set p.y = y
        
        return p
    endmethod
    
    method getDistanceToPoint takes Point p returns real
        local real dx = p.x - .x
        local real dy = p.y - .y
        
        return SquareRoot( dx * dx + dy * dy )
    endmethod
    
    method getAngletoPoint takes Point p returns real
        local real dx = p.x - .x
        local real dy = p.y - .y
        
        return Atan2( dy, dx )
    endmethod
    
    method calculateDisplacement takes real distance, real angle returns Point
        return Point.create( .x + distance * Cos( angle ), .y + distance * Sin( angle ) )
    endmethod
    
    static method getDistanceBetweenPoints takes Point p1, Point p2 returns real
        local real dx = p2.x - p1.x
        local real dy = p2.y - p1.y
        
        return SquareRoot( dx * dx + dy * dy )
    endmethod
    
    static method getAngleBetweenPoints takes Point p1, Point p2 returns real
        local real dx = p2.x - p1.x
        local real dy = p2.y - p1.y
        
        return Atan2( dy, dx )
    endmethod
    
    static method pointFromLoc takes location loc returns Point
        return Point.create( GetLocationX( loc ), GetLocationY( loc ) )
    endmethod
    
    static method pointFromUnit takes unit u returns Point
        return Point.create( GetUnitX( u ), GetUnitY( u ) )
    endmethod
    
endstruct

endlibrary

Knockback System:

JASS:
library Knockback initializer init requires Geometry

globals
    private constant real TIMEOUT = 0.035
    
    private Knockback array knockbackArray
    private integer knockbackArraySize = 0
    private timer t
endglobals

struct Knockback

    unit target                    //unit being knocked back
    real angle                     //angle (equal to theta in 3D polar coords) of movement direction in radians
    real increment                 //distance to move unit with each loop of timer
    real distance                  //total distance to knock back
    real distanceRemaining         //distance remaining to knock back
    boolean toDestroy = false      //whether or not struct needs to be destroyed
    
    //whenever you need to knock back a unit, just call this method
    //unit target: unit being knocked back
    //real angle: angle (theta) in radians of the direction of the knockback
    //real speed: speed (wc3 distance units/second) of knockback
    //real distance: distance to knock back
    static method create takes unit target, real angle, real speed, real distance returns Knockback
        local Knockback temp = Knockback.allocate()
        
        set temp.target = target
        set temp.angle = angle
        set temp.increment = TIMEOUT * speed
        set temp.distance = distance
        set temp.distanceRemaining = distance
        
        set knockbackArray[knockbackArraySize] = temp
        set knockbackArraySize = knockbackArraySize + 1
        
        if ( knockbackArraySize == 1 ) then
            call TimerStart( t, TIMEOUT, true, function Knockback.executeKnockback )
        endif
        
        return temp
    endmethod

    private static method executeKnockback takes nothing returns nothing
        local Knockback data
        local Point p
        local integer i = 0
    
        loop
            exitwhen ( i >= knockbackArraySize )
        
            set data = knockbackArray[i]
        
            if ( data.toDestroy ) then //struct needs to be destroyed
                set knockbackArray[i] = knockbackArray[knockbackArraySize - 1] //we copy the struct at the last index of the array to the current index
                set knockbackArraySize = knockbackArraySize - 1 //decrease size of array by one (technically just decreasing the maximum index of the array that this loop will read
                set i = i - 1 //since we decreased the array size by one, we need to decrease i by one otherwise that struct that we copied would not get read by this loop (loop would stop one short of the index for that struct), but since we COPIED that struct to the current index, by decreasing i by one, the next iteration of this loop would result in the loop reading the same index as this iteration, and now that index contains the copied struct
        
                call data.destroy() //destroy the struct to free up memory
            else
                set p = Point.pointFromUnit( data.target ) //get location of the unit being knocked back
                set p = p.calculateDisplacement( data.angle, data.increment ) //calculate the point with a displacement from p equal to the given angle and distance (think of it as a vector in polar form), and save that point in p
                call SetUnitPosition( data.target, p.x, p.y ) //set unit position
                set data.distanceRemaining = data.distanceRemaining - data.increment //update the distance remaining to knockback
            
                call p.destroy() //destroy the point struct to free up memory
            
                if ( data.distanceRemaining <= 0 ) then //unit has been knocked back far enough
                    set data.toDestroy = true //slate struct for destruction on next iteration of the timer
                endif
            endif
        
            set i = i + 1
        endloop
    
        if ( knockbackArraySize == 0 ) then
            call PauseTimer( t )
        endif
    endmethod

endstruct

private function init takes nothing returns nothing
    set t = CreateTimer()
endfunction

endlibrary
 
Level 5
Joined
Nov 14, 2007
Messages
161
cool, and thanks (ill put credits in my map if i end up using something like yours) though i cant work on it much more tonight, but tomorrow i should have a few hours to check it out.

as i said im new to vjass and most of programming. regular jass makes pretty good sense to me, but many say knockback is easiest with vjass so i went for it. only tutorial i found on a few sites was that one posted above-same tutorial and same person.

im pretty sure the 4th value was the one i was changing which was w, but whatever. ill have a crack at yours tomorrow. thanks and good night.
 
Level 5
Joined
Nov 14, 2007
Messages
161
so, im finnaly able to work on this, been busy with school and stuff. the one thing im having problems with is how do i call yours? i assume its call Knockback(unit, angle, speed, distance) but i cant get it to save correctly to test, compile error on my calling knockback.

how do i call your "method"? im use to calling functions.

  • knockback test
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to test knockback yep the test
    • Actions
      • Custom script: local unit Knocked = GetTriggerUnit()
      • Custom script: call Knockback(Knocked, 2, 500, 500) <--- ERROR
      • Game - Display to (All players) the text: works
simply how do i call it?
 
Level 12
Joined
Apr 27, 2008
Messages
1,228
I'd reckon like this:
  • knockback test
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to test knockback yep the test
    • Actions
      • Custom script: local Knockback data = Knockback.create(GetTriggerUnit(), GetUnitFacing(GetTriggerUnit()), 30, 500)
      • Game - Display to (All players) the text: works
 
Level 5
Joined
Nov 14, 2007
Messages
161
to call it:
  • Custom script: call Knockback.create(Knocked, 2, 500, 500)

thank you thank you, this way works. +rep time (to bad it dont count)

I'd reckon like this:
  • Custom script: local Knockback data = Knockback.create(GetTriggerUnit(), GetUnitFacing(GetTriggerUnit()), 30, 500)

has a syntax error, but i learned from it anyway.

JASS:
local real angle = GetUnitFacing(GetTriggerUnit()) * (3.14 / 180)


EDIT: quick question, is there a better way to get an angle in radians?
 
Last edited:
Level 5
Joined
Nov 14, 2007
Messages
161
ok, i know this is a little offtopic on the whole knockback, but if you know that would be cool.

how do i do a pick every unit in jass without calling alot of functions? etc. ive done something like it, made a unit group and added the units to it, ran them through a filter and looped what i wanted done to each of them. but i lost how i did it.

  • Holy Smash
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Holy Smash
    • Actions
      • Unit Group - Pick every unit in (Units within 300.00 of (Position of (Triggering unit)) matching (((Matching unit) belongs to an enemy of (Owner of (Triggering unit))) Equal to True)) and do (Actions)
        • Loop - Actions
          • Custom script: local unit target = GetEnumUnit()
          • Custom script: local real angle = Deg2Rad( GetUnitFacing(target) - 180)
          • Custom script: call Knockback.create(target, angle ,800,600)
          • Custom script: set target = null
how would i do that in jass? and can it be any more efficient?


THANKS ALOT Reborn Devil!!!!
 
Level 22
Joined
Dec 31, 2006
Messages
2,216
JASS:
function Holy_Smash takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local player p = GetOwningPlayer(u)
    local unit victim
    local real x = GetUnitX(u)
    local real y = GetUnitY(u)
    local group g = CreateGroup()
    call GroupEnumUnitsInRange(g, x, y, 300., null)
    loop
        set victim = FirstOfGroup(g)
        exitwhen victim == null
        if IsUnitEnemy(victim, p) then
            call Knockback.create(victim, (GetUnitFacing(victim) - 180.) * bj_DEGTORAD, 800., 600.)
        endif
        call GroupRemoveUnit(g, victim)
        set victim = null
    endloop
    call DestroyGroup(g)
    set g = null
    set p = null
    set u  = null
endfunction
 
Level 11
Joined
Feb 22, 2006
Messages
752
There's actually a slightly faster way (don't really know how much faster, but still...)

JASS:
function filter takes nothing returns boolean
    //do your stuff here. Remember this will loop through EVERY unit that meets the Enum
    //conditions. To make stuff happen only to the units you want (i.e. only heroes, or only 
    //friendly units, etc, use if statements and stuff. Use GetFilterUnit() to get the unit 
    //currently being selected

    return false
endfunction

function foo takes nothing returns nothing
    local group g = CreateGroup() //would be even better if you used a global so you didn't need to constantly create/destroy groups

    call GroupEnumUnitsInRange( g, 0, 0, 500.0, Condition( function filter ) ) //you can use any of the GroupEnum... native functions here

    call DestroyGroup( g )
    set g = null
endfunction

Basically, you take advantage of the fact that the GroupEnum... functions will essentially loop through a bunch of units anyway if you set a filter condition and just use that instead of bothering with your own loops, FirstOfGroup() and GroupRemoveUnit(), which all take up extra system resources to do.

Additionally, this method is really convenient for using temp global group variables (which I didn't do) because you never need to even clear the group (notice how the filter ALWAYS returns false so no units ever get added to the group).
 
There's one problem with that - you leak a boolexpr. Should be
JASS:
function filter takes nothing returns boolean
    //do your stuff here. Remember this will loop through EVERY unit that meets the Enum
    //conditions. To make stuff happen only to the units you want (i.e. only heroes, or only
    //friendly units, etc, use if statements and stuff. Use GetFilterUnit() to get the unit
    //currently being selected

    return false
endfunction

function foo takes nothing returns nothing
    local group g = CreateGroup() //would be even better if you used a global so you didn't need to constantly create/destroy groups
    local boolexpr e = Filter(function filter)

    call GroupEnumUnitsInRange( g, 0, 0, 500.0, e ) //you can use any of the GroupEnum... native functions here

    call DestroyGroup( g )
    set g = null
    call DestroyBoolexpr(e)
    set e = null
endfunction
 
Level 5
Joined
Nov 14, 2007
Messages
161
That's true, but if he can implement my systems, then he has to have vJASS.

i do ;)



i might actually use your way for another skill of mine. currently it gives units a buff then takes 5% of their current hp every few seconds if they have the buff. i might change it to add them in the group and loop that group depending on the level of the skill.

as u can tell, commented out stuff was where i attempted something but instead made a new trigger that checks every second for a unit with the buff. i like the idea of not having a trigger run every second.

JASS:
function Trig_Stench_Cloud_Actions takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local unit temp
    local player p = GetOwningPlayer(u)
    local unit victim
    local real x = GetUnitX(u)
    local real y = GetUnitY(u)
    local group g = CreateGroup()
    local integer i = 0

    call PlaySoundBJ( gg_snd_common_fart )

    loop
        exitwhen i > 360
        set temp = CreateUnit(p, 'h00B', x, y, bj_UNIT_FACING)
        call UnitApplyTimedLife(temp, 'BTLF', 1.3)
        call Knockback.create(temp, i * bj_DEGTORAD, 400, 600)
        set temp = null
        set i = i + 15        
    endloop
    
    
//    working on the rest of it all in 1 trigger
//
//    set i = 0        (i was basically seconds the skill lasted for these loops)
//    loop
//    exitwhen i > (10 + (3 * GetUnitAbilityLevel(u, 'A00Q')))
//    call GroupEnumUnitsInRange(g, x, y, 300., null)
//        call BJDebugMsg("first loop")        
//    loop
//        set victim = FirstOfGroup(g)
//        exitwhen victim == null
//        call BJDebugMsg("second loop")
//        if GetUnitAbilityLevel(victim, 'B008') > 0 then
//            call BJDebugMsg("in the if and working")
//            call SetUnitState(victim, UNIT_STATE_LIFE, GetUnitState(victim, UNIT_STATE_MAX_LIFE) * 0.95)
//        endif
//        call GroupRemoveUnit(g, victim)
//        set victim = null
//        endloop
//        call TriggerSleepAction(1.0)        
//        set i = i + 1
//    endloop

    call DestroyGroup(g)
    set g = null
    set temp = null
    set victim = null
    set u = null        
        
endfunction

ill be on tomorrow afternoon-got school stuff to do. but would u mind showing me how to combine the 2? i think i got the idea in my head, just not the time right now to try it out. thanks!

PS: if you dont that's cool, ill edit this post tomorrow.
 
Level 11
Joined
Feb 22, 2006
Messages
752
I'm actually kind of confused as to what exactly your spell does.

In the code (including commented out stuff) every second it picks all units within 300.0 range of the location where unit u (I'm guessing the caster) was when the trigger action ran (you never update the x and y coords), then it checks to see if they have a certain buff (which I guess is added using an ability or something). If they do, they will lose 5% of their hp.

However, in your description, it sounds like you want units to take damage if they have the buff no matter where they are on the map (i.e. they could move out of the 300.0 radius area and still take the damage).

Because I need an excuse not to do my writing homework (god I hate writing...), I came up with code for both.

The code for the first version (NOTE: Requires TimerUtils by Vexorian):

JASS:
scope StenchCloud initializer init

globals
    private constant integer SPELL_ABILITY_ID = 'A00Q' //Raw code of the stench cloud spell (I'm taking a guess here)
    private constant integer BUFF_ABILITY_ID = 'B008' //Buff to check for when damaging
    private constant real TIMEOUT = 1.00 //How often timer fires (seconds)
    private constant real AOE_RADIUS = 300.0 //Radius around casting location to check for units with buff
    private constant real LIFE_LOSS_FACTOR = 0.95 //Damaged units get their health set to current health times this
    private constant real DURATION = 13.00 //How long the timer sticks around to damage things - base (meaning this is duration at level 1 of your spell)
    private constant real DURATION_LEVEL = 3.00 //How much to increase duration per level of spell

    private group g = CreateGroup()
    private boolexpr filterExpr
endglobals

private struct TimerData
    
    unit u
    real x
    real y
    integer level //I know with current code you don't need this but in future if you ever decide to change spell you might need this
    real timeRemaining //you shouldn't mess with this, it keeps track of how long the timer has until it has to be released
    
    //unit u is caster (once again, that is a guess), real x and real y are casting coords, integer level
    //is level of the ability, and real duration is how long the timer sticks around to damage stuff
    static method create takes unit u, real x, real y, integer level, real duration returns TimerData
        local TimerData temp = TimerData.allocate()
        
        set temp.u = u
        set temp.x = x
        set temp.y = y
        set temp.level = level
        set temp.timeRemaining = duration
        
        return temp
    endmethod
    
endstruct

private function filter takes nothing returns boolean
    local unit victim = GetFilterUnit()
    
    if ( GetUnitAbilityLevel( victim, BUFF_ABILITY_ID ) > 0 ) then
        call SetWidgetLife( victim, GetWidgetLife( victim ) * LIFE_LOSS_FACTOR )
    endif
    
    set victim = null
    
    return false
endfunction

private function callBack takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local TimerData temp = GetTimerData( t )
    
    call GroupEnumUnitsInRange( g, temp.x, temp.y, AOE_RADIUS, filterExpr )
    
    set temp.timeRemaining = temp.timeRemaining - TIMEOUT
    
    if ( temp.timeRemaining <= 0 ) then
        call temp.destroy()
        call ReleaseTimer( t )
    endif
    
    set t = null
endfunction

private function actions takes nothing returns nothing
    local timer t
    local unit u = GetTriggerUnit()
    local unit temp
    local player owner = GetOwningPlayer( u )
    local real x = GetUnitX( u )
    local real y = GetUnitY( u )
    local integer level = GetUnitAbilityLevel( u, SPELL_ABILITY_ID )
    local integer i = 0
    
    call PlaySoundBJ( gg_snd_common_fart )
    
    loop
        exitwhen ( i > 360 )
        
        set temp = CreateUnit( owner, 'h00B', x, y, bj_UNIT_FACING )
        call UnitApplyTimedLife( temp, 'BTLF', 1.3 )
        call Knockback.create( temp, i * bj_DEGTORAD, 400, 600 )
        set temp = null
        
        set i = i + 15
    endloop
    
    set t = NewTimer()
    call TimerStart( t, TIMEOUT, true, function callBack )
    call SetTimerData( t, TimerData.create( u, x, y, level, DURATION + ( level - 1 ) * DURATION_LEVEL ) )
    
    set t = null
    set u = null
    set owner = null
endfunction

//===========================================================================
private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    
    set filterExpr = Condition( function filter )
    
    //do your trigger init stuff here (I don't actually know what the events are since you didn't post)
    call TriggerAddAction( t, function actions ) //I CAN infer that this is in here though, lol
endfunction

endscope


And the second one (does not require TimerUtils):

JASS:
scope StenchCloud initializer init

globals
    private constant integer SPELL_ABILITY_ID = 'A00Q' //Raw code of the stench cloud spell (I'm taking a guess here)
    private constant integer BUFF_ABILITY_ID = 'B008' //Buff to check for when damaging
    private constant real TIMEOUT = 1.00 //How often timer fires (seconds)
    private constant real AOE_RADIUS = 300.0 //Radius around casting location to check for units with buff
    private constant real LIFE_LOSS_FACTOR = 0.95 //Damaged units get their health set to current health times this
    private constant real DURATION = 13.00 //How long the timer sticks around to damage things - base (meaning this is duration at level 1 of your spell)
    private constant real DURATION_LEVEL = 3.00 //How much to increase duration per level of spell
    
    private timer t = CreateTimer()
    private group g = CreateGroup()
    private boolexpr filterExpr
    
    private BuffData array buffDataArray
    private integer buffDataArraySize = 0
endglobals

struct BuffData //You may need to change the name if it conflicts with another struct type
    
    unit caster
    unit target
    integer level //I know with current code you don't need this but in future if you ever decide to change spell you might need this
    real timeRemaining //you shouldn't mess with this, it keeps track of how long the timer has until it has to be released
    boolean toDestroy = false
    
    //unit caster is caster (once again, that is a guess), unit target is the unit that will get damaged, 
    //integer level is level of the ability, and real duration is how long the target will continue to take damage
    static method create takes unit caster, unit target, integer level, real duration returns BuffData
        local BuffData temp = BuffData.allocate()
        
        set temp.caster = caster
        set temp.target = target
        set temp.level = level
        set temp.timeRemaining = duration
        
        set buffDataArray[buffDataArraySize] = temp
        set buffDataArraySize = buffDataArraySize + 1
        
        if ( buffDataArraySize == 1 ) then
            call TimerStart( t, TIMEOUT, true, function BuffData.doDamage )
        endif
        
        return temp
    endmethod

    private static method doDamage takes nothing returns nothing
        local BuffData data
        local integer i = 0
    
        loop
            exitwhen ( i >= buffDataArraySize )
        
            set data = buffDataArray[i]
        
            if ( data.toDestroy ) then
                set buffDataArray[i] = buffDataArray[buffDataArraySize - 1]
                set buffDataArraySize = buffDataArraySize - 1
                set i = i - 1
        
                call data.destroy()
            else
                if ( GetUnitAbilityLevel( data.target, BUFF_ABILITY_ID ) > 0 ) then
                    call SetWidgetLife( data.target, GetWidgetLife( data.target ) * LIFE_LOSS_FACTOR )
                    set data.timeRemaining = data.timeRemaining - TIMEOUT
                    
                    if ( data.timeRemaining <= 0 ) then
                        set data.toDestroy = true //duration has expired
                    endif
                else
                    set data.toDestroy = true //the unit doesn't even have the buff anymore, so why bother trying to do damage?
                endif
            endif
        
            set i = i + 1
        endloop
    
        if ( buffDataArraySize == 0 ) then
            call PauseTimer( t )
        endif
    endmethod
    
endstruct

private function filter takes nothing returns boolean
    local unit caster = GetTriggerUnit()
    local unit victim = GetFilterUnit()
    local integer level = GetUnitAbilityLevel( caster, SPELL_ABILITY_ID )
    
    if ( true ) then //replace the "true" with your conditions on what units can be affected by the spell
                     //remember you can still use the event response native functions here
        call BuffData.create( GetTriggerUnit(), victim, level, DURATION + ( level - 1 ) * DURATION_LEVEL )
    endif
    
    set caster = null
    set victim = null
    
    return false
endfunction

private function actions takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local unit temp
    local player owner = GetOwningPlayer( u )
    local real x = GetUnitX( u )
    local real y = GetUnitY( u )
    local integer level = GetUnitAbilityLevel( u, SPELL_ABILITY_ID )
    local integer i = 0
    
    call PlaySoundBJ( gg_snd_common_fart )
    
    loop
        exitwhen ( i > 360 )
        
        set temp = CreateUnit( owner, 'h00B', x, y, bj_UNIT_FACING )
        call UnitApplyTimedLife( temp, 'BTLF', 1.3 )
        call Knockback.create( temp, i * bj_DEGTORAD, 400, 600 )
        set temp = null
        
        set i = i + 15
    endloop
    
    call GroupEnumUnitsInRange( g, x, y, AOE_RADIUS, filterExpr )
    
    set u = null
    set owner = null
endfunction

//===========================================================================
private function init takes nothing returns nothing
    local trigger tr = CreateTrigger()
    
    set filterExpr = Condition( function filter )
    
    //do your trigger init stuff here (I don't actually know what the events are since you didn't post)
    call TriggerAddAction( tr, function actions ) //I CAN infer that this is in here though, lol
endfunction

endscope


If the second one looks really similar to knockback, that's because it is. It's based on the same basic concept and template.


If you decide to use the first code, here's the TimerUtils library cuz I don't know where to find it online (it came with another system I use in my map). I just want to make it clear I did not make this library. Vexorian did. So give him credit where appropriate.

JASS:
library TimerUtils
//*********************************************************************
//* TimerUtils
//* ----------
//*
//*  To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//*  To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass)   More scripts: htt://www.wc3campaigns.net
//*
//* For your timer needs:
//*  * Attaching
//*  * Recycling (with double-free protection)
//*
//* set t=NewTimer()      : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t)       : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2)     : Attach value 2 to timer
//* GetTimerData(t)       : Get the timer's value.
//*                         You can assume a timer's value is 0
//*                         after NewTimer.
//*
//********************************************************************

//================================================================
    globals
        private constant integer MAX_HANDLE_ID_COUNT = 408000
        // values lower than 8191: very fast, but very unsafe.
        // values bigger than 8191: not that fast, the bigger the number is the slower the function gets
        // Most maps don't really need a value bigger than 50000 here, but if you are unsure, leave it
        // as the rather inflated value of 408000
    endglobals

    //=================================================================================================
    private function H2I takes handle h returns integer
        return h
        return 0
    endfunction

    //==================================================================================================
    globals
        private integer array data[MAX_HANDLE_ID_COUNT]
        private constant integer MIN_HANDLE_ID=0x100000
    endglobals

    //It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
    function SetTimerData takes timer t, integer value returns nothing
        debug if(H2I(t)-MIN_HANDLE_ID>=MAX_HANDLE_ID_COUNT) then
        debug     call BJDebugMsg("SetTimerData: Handle id too big, increase the max handle id count or use gamecache instead")
        debug endif
        set data[H2I(t)-MIN_HANDLE_ID]=value
    endfunction

    function GetTimerData takes timer t returns integer
        debug if(H2I(t)-MIN_HANDLE_ID>=MAX_HANDLE_ID_COUNT) then
        debug     call BJDebugMsg("GetTimerData: Handle id too big, increase the max handle id count or use gamecache instead")
        debug endif
        return data[H2I(t)-MIN_HANDLE_ID]
    endfunction

    //==========================================================================================
    globals
        private timer array tT
        private integer tN = 0
        private constant integer HELD=0x28829022
        //use a totally random number here, the more improbable someone uses it, the better.
    endglobals

    //==========================================================================================
    function NewTimer takes nothing returns timer
        if (tN==0) then
            set tT[0]=CreateTimer()
        else
            set tN=tN-1
        endif
        call SetTimerData(tT[tN],0)
     return tT[tN]
    endfunction

    //==========================================================================================
    function ReleaseTimer takes timer t returns nothing
        if(t==null) then
            debug call BJDebugMsg("Warning: attempt to release a null timer")
            return
        endif
        if (tN==8191) then
            debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")

            //stack is full, the map already has much more troubles than the chance of bug
            call DestroyTimer(t)
        else
            call PauseTimer(t)
            if(GetTimerData(t)==HELD) then
                debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
                return
            endif
            call SetTimerData(t,HELD)
            set tT[tN]=t
            set tN=tN+1
        endif    
    endfunction

endlibrary
 
Level 5
Joined
Nov 14, 2007
Messages
161
for some reason i envisioned it alot different. i probably didnt explain myself very well...

basically i was just going to take that do to group functions you showed me, and loop them depending on how many seconds i wanted it to work on that group. i guess it will work with your way (though i havent tried) but i envisioned something REALLY simple and adding a system or 2 just for 1 skill... well i just didnt think it was necessary. ill write up something in a little bit on what i had in mind.
 
Status
Not open for further replies.
Top