HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

First Time doing anything in vJass; need help with a spell in vJass

10-31-2009, 06:24 AM#1
Newuser
I am trying to recreate "Spectral Dagger" ability from DotA. Anyway I decided to attempt to do this in vJass(first time I am attempting anything in vJass).

Instead of using global variables, I want to use structs(just so I can practice using them).

Here's what I am trying to do with the trigger:
1. Once a unit casts the spell at a target point, a dagger projectile (unit) is created.
2. Using timers, I want the dagger to move towards that target point(by moving the dagger using triggers).
3. After the dagger moves a total of <x> distance(I set the variable to 2000 for now), the timer stops and everything ends.

That's it, well at least for what I am trying to do for the trigger.

I am trying to use TimerUtils for Timer usage.

Note:

1. I'm not even good with regular Jass.

2. A lot of the stuff written here is just stuff I tried to replicate by looking at triggers here. Again, I don't really know or understand much about vJass. Still trying to learn.

3. The code is a bit messy but I tried to add comments to show you what I am trying to do.

4. Also note, the spell doesn't work, it's just what I have currently.

So basically, if anyone can help me get the code "to work", I will appreciate it.

Here's the code:
Collapse JASS:
scope SpectralDagger

private function Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A0GY' ) ) then
        return false
    endif
    return true
endfunction

private struct dagger_data
    unit Dagger_Caster = GetTriggerUnit() // Caster
    unit Dagger_Target = GetEventTargetUnit() // Target of Spell, the spell can either be casted at a point or a unit.
    unit Dagger_SpellUnit = null // The dagger projectile
    location CasterPoint = GetUnitLoc(GetTriggerUnit()) // Location of Caster
    location DaggerTarget = GetSpellTargetLoc() // Location of Target
    real Dagger_Distance = 2000.00
    real Dagger_Counter = 0.00 // (This is used as a counter. Moves at 32 units per 0.04 seconds. Fires 25 times per second. Translates to 800 units per second.
    integer Shadow_Counter = 0 // This value goes up every 0.04 seconds, at 3 it creates a shadow path and resets back to 0
endstruct

// The below function should happen once the spell(event) happens.
// I want the below function to create and start the timer
private function Actions takes nothing returns nothing
    local damagedata dd  = 0 // I have no idea what I am doing, I simply attempted to replicate what was done in other triggers approved at wc3c.net
    local timer      t  = NewTimer() // No ideal what I am doing
    call CreateNUnitsAtLoc( 1, 'h026', GetOwningPlayer(GetTriggerUnit()), CasterPoint, bj_UNIT_FACING )
    call UnitApplyTimedLifeBJ( 20.00, 'BTLF', GetLastCreatedUnit() ) // In case for some reason the projectile isn't removed later on, it has 20 second expiration time
    // Assigning stuff to structs?
    set dd         = dagger_data.create() // No idea 
    set dd.Dagger_SpellUnit = GetLastCreatedUnit() // No idea (again)
    call SetTimerData(d.t, d) // No idea again
    call TimerStart(d.t, 0.04, true, function Timer_Action) // No Idea
endfunction

// I want the below function to happen every 0.04 seconds
private function Timer_Action takes nothing returns nothing 
    local dagger_data dd  = GetTimerData(GetExpiredTimer())
    local location DaggerPoint
    local location DaggerOffset
    set dd         = dagger_data.create()
    set dd.Shadow_Counter = ( dd.Shadow_Counter + 1 )
    set dd.Dagger_Counter = ( dd.Dagger_Counter + 32.00 ) // Causes the counter to go up, once the counter is 2000+, the timer should stop
    set DaggerPoint = GetUnitLoc(dd.Dagger_SpellUnit)
    set DaggerOffset = PolarProjectionBJ(DaggerPoint, 32.00, AngleBetweenPoints(dd.Dagger_Caster, dd.Dagger_Target))
    call SetUnitPositionLocFacingLocBJ( dd-Dagger_SpellUnit, DaggerOffset, dd.Dagger_Target )
    if ( dd.Shadow_Counter >= 3 ) then // I want the shadows, left by the dagger to be made only once every 3 intervals
        call CreateNUnitsAtLoc( 1, 'h027', GetOwningPlayer(dd.Dagger_SpellUnit), dd.DaggerPoint, bj_UNIT_FACING )
        call UnitApplyTimedLifeBJ( 9.00, 'BTLF', GetLastCreatedUnit() )
        set dd.Shadow_Counter = 0
    else
    endif
    call RemoveLocation(DaggerPoint)
    call RemoveLocation(DaggerOffset)
    set DaggerPoint = null
    set DaggerOffset = null
    if ( dd.Dagger_Counter >= dd.Dagger_Distance) // dd.Dagger_Distance is set to 2000.00
        call ReleaseTimer(GetExpiredTimer())
        call dd.destroy()
        call RemoveUnit( dd.Dagger_SpellUnit ) // Removes projectile from the game
        else
    endif
endfunction


//====================================================================

public function InitTrig takes nothing returns nothing
    local trigger trigger_dagger = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(trigger_dagger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(trigger_dagger, Condition( function Conditions ))
    call TriggerAddAction(trigger_dagger, function Actions)
endfunction
endscope
10-31-2009, 10:49 AM#2
Komaqtion
Collapse JASS:
private function Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A0GY' ) ) then
        return false
    endif
    return true
endfunction

Can be merged to:
Collapse JASS:
private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A0GY' // Will return true if the ability being cast is 'A0GY' :D
endfunction

Collapse JASS:
private struct dagger_data
    unit Dagger_Caster = GetTriggerUnit() // Caster
    unit Dagger_Target = GetEventTargetUnit() // Target of Spell, the spell can either be casted at a point or a unit.
    unit Dagger_SpellUnit = null // The dagger projectile
    location CasterPoint = GetUnitLoc(GetTriggerUnit()) // Location of Caster
    location DaggerTarget = GetSpellTargetLoc() // Location of Target
    real Dagger_Distance = 2000.00
    real Dagger_Counter = 0.00 // (This is used as a counter. Moves at 32 units per 0.04 seconds. Fires 25 times per second. Translates to 800 units per second.
    integer Shadow_Counter = 0 // This value goes up every 0.04 seconds, at 3 it creates a shadow path and resets back to 0
endstruct

You can do it like this, you should try creating a custom "create" method (Which I'll tell you later when to use) which will take those members as arguments, and you can simply set them ;)

I also strongly suggest you try using reals x and y instead of locations, as locations leak ;)

Like this:
Collapse JASS:
private struct dagger_data
    unit Dagger_Caster
    unit Dagger_Target
    unit Dagger_SpellUnit
    location CasterPoint
    location DaggerTarget
    real Dagger_Distance
    real Dagger_Counter = 0
    integer Shadow_Counter = 0

    static method create takes unit caster, unit target, unit projectile, real distance, location targetloc returns thistype
        local thistype this = thistype.allocate() //Here's we're actually creating the struct, for further use ;)
        
        set this.Dagger_Caster = caster //Set the caster to a struct member
        set this.Dagger_Target = target //Set the target unit of the ability to a struct member
        set this.Dagger_SpellUnit = projectile
        set this.CasterPoint = GetUnitLoc( caster )
        set this.DaggerTarget = targetloc
        set this.DaggerDistance = distance

        call UnitApplyTimedLife( projectile, 'BTLF', 20.0 )
        
        return this
        endmethod
endstruct

That will do exactly what you wanted your struct to do, only this will work ;)

Ok, so on to your Action function...
Here you're using the wrong struct name:
Collapse JASS:
private function Actions takes nothing returns nothing
    local damagedata dd  = 0 // I have no idea what I am doing, I simply attempted to replicate what was done in other triggers approved at wc3c.net
    local timer      t  = NewTimer() // No ideal what I am doing
    call CreateNUnitsAtLoc( 1, 'h026', GetOwningPlayer(GetTriggerUnit()), CasterPoint, bj_UNIT_FACING )
    call UnitApplyTimedLifeBJ( 20.00, 'BTLF', GetLastCreatedUnit() ) // In case for some reason the projectile isn't removed later on, it has 20 second expiration time
    // Assigning stuff to structs?
    set dd         = dagger_data.create() // No idea 
    set dd.Dagger_SpellUnit = GetLastCreatedUnit() // No idea (again)
    call SetTimerData(d.t, d) // No idea again
    call TimerStart(d.t, 0.04, true, function Timer_Action) // No Idea
endfunction

Should at least be:
Collapse JASS:
private function Actions takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local unit t = GetSpellTargetUnit()
    local player p = GetOwningPlayer ( u )
    local location l
    local dagger_data dd
    local timer t  = NewTimer() // Here you're using the TimerUtils system to efficiently create and use timers ;)

    if t == null then // Just so you know wether you're targetting a unit, or point ;)
        set l = GetSpellTargetLoc()
        set dd = dagger_data.create( u, null, CreateUnit( p, 'h026', GetLocationX( l ), GetLocationY( l ), bj_UNIT_FACING ), 2000.0, l )  // Using the right struct name, we create the struct and thereby triggering the custom "create" method ;)
    else
        set l = GetUnitLoc ( t )
        set dd = dagger_data.create( u, t, CreateUnit( p, 'h026', GetLocationX( l ), GetLocationY( l ), bj_UNIT_FACING ), 2000.0, l )
    call CreateNUnitsAtLoc( 1, 'h026', GetOwningPlayer(GetTriggerUnit()), CasterPoint, bj_UNIT_FACING )
    // Assigning stuff to structs?
    //set dd         = dagger_data.create() this isn't needed now ;)
    //set dd.Dagger_SpellUnit = GetLastCreatedUnit() // Not this either ;)
    call SetTimerData(t, dd) // Here's you're attaching the struct you just created to the timer, so you can later on get that value from another function
    call TimerStart(t, 0.04, true, function Timer_Action) // Here's you're simply starting the timer you just attached the struct to :D
endfunction

And now on to the callback function...

This one is actually pretty good, just a few errors ;)

Collapse JASS:
private function Timer_Action takes nothing returns nothing 
    local dagger_data dd  = GetTimerData(GetExpiredTimer())
    //local location DaggerPoint // Isn't needed because you have a structmember which stores this ;)
    local location DaggerOffset
    local unit shadow
    //set dd         = dagger_data.create() You already have one, two lines above ;)
    set dd.Shadow_Counter = ( dd.Shadow_Counter + 1 )
    set dd.Dagger_Counter = ( dd.Dagger_Counter + 32.00 ) // Causes the counter to go up, once the counter is 2000+, the timer should stop
    set DaggerOffset = PolarProjectionBJ(dd.DaggerTarget, 32.00, AngleBetweenPoints(dd.Dagger_Caster, dd.DaggerTarget))
    call SetUnitPositionLocFacingLocBJ( dd.Dagger_SpellUnit, DaggerOffset, dd.Dagger_Target )
    if ( dd.Shadow_Counter >= 3 ) then // I want the shadows, left by the dagger to be made only once every 3 intervals
        set shadow = CreateUnit(GetOwningPlayer(dd.Dagger_Caster), 'h027', GetLocationX( dd.DaggerTarget ), GetLocationY( dd.DaggerTarget ), bj_UNIT_FACING )
        call UnitApplyTimedLife( shadow, 'BTLF', 9.00 )
        set dd.Shadow_Counter = 0
    endif
    call RemoveLocation(DaggerOffset)
    set DaggerOffset = null
    set shadow = null
    if ( dd.Dagger_Counter >= dd.Dagger_Distance) // dd.Dagger_Distance is set to 2000.00
        call RemoveUnit( dd.Dagger_SpellUnit )
        call ReleaseTimer(GetExpiredTimer())
        call RemoveLocation( dd.DaggerTarget )
        call RemoveLocation( dd.CasterPoint )
        call dd.destroy()
    endif
endfunction

There you go, though it's all freehanded, so there could be some compile errors, if so then please post it here, and I can help you ;)

Hope you learned something :D
10-31-2009, 07:59 PM#3
Anitarf
You could also use xecollider, it's perfect for what you are describing.
11-01-2009, 12:42 AM#4
D1000
But not if he wants to practice (v)Jass...
Attached Images
File type: gifSpellSession15.gif (1.2 KB)
11-02-2009, 05:36 PM#5
Vexorian
Quote:
Originally Posted by D1000
But not if he wants to practice (v)Jass...
It is perfect for practice actually.

xemissile gives a great opportunity to use features that are kind of unused but are very useful.
11-09-2009, 12:56 AM#6
Newuser
Thanks for the help Komaqtion and everyone else.

Reason why I took so long to reply was because I haven't really been mapping or anything until recently.

Anyway, I used Komaqtion's suggestions and edited a few things because there were some things that did not work(just the syntax, suggestions worked fine).

Despite how things look, it works but it seems to leak. After a certain amount of uses, an error message stating that there are no more timers available appears.

I'm still a bit confused on how to use structs, timers, etc.

Questions:
1. To use structs in a function, you have to start with something like
Collapse JASS:
local dagger_data dd
right? Then after that, you can start referring to structs by typing "dd.<name of variable here>" right?

2.
Collapse JASS:
        static method create takes unit caster, unit target, unit projectile, real distance, location targetloc returns thistype

        local thistype this = thistype.allocate() //Here's we're actually creating the struct, for further use ;)
        set this.Dagger_Caster = caster //Set the caster to a struct member
        set this.Dagger_Target = target //Set the target unit of the ability to a struct member
        set this.Dagger_SpellUnit = projectile
        set this.CasterPoint = GetUnitLoc( caster )
        set this.DaggerTargetLoc = targetloc
        set this.Dagger_Distance = distance

        call UnitApplyTimedLife( projectile, 'BTLF', 20.0 )
        
        return this
        endmethod

This is Komaqtion's suggestions he made, which was placed inside the struct data.

So the above is making the structs usable in other functions?

Collapse JASS:
set this.Dagger_Caster = caster

For example, does that make anytime I do something like "set caster", it will set a value to the "caster"(variable name) in the struct?


Sorry for all the questions but if anyone can explain some more stuff, I will appreciate it.



Anyway here's my revised trigger(note that I haven't completely understand how to do things; the trigger works but it leaks it seems).
Collapse JASS:
scope SpectralDagger initializer InitDagger

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A0GY' // Will return true if the ability being cast is 'A0GY' :D
endfunction


private struct dagger_data
    unit Dagger_Caster = GetTriggerUnit() // Caster
    unit Dagger_Target = GetEventTargetUnit() // Target of Spell, the spell can either be casted at a point or a unit.
    unit Dagger_SpellUnit = null // The dagger projectile
    location CasterPoint = GetUnitLoc(GetTriggerUnit()) // Location of Caster
    location DaggerTargetLoc = GetSpellTargetLoc() // Location of Target
    real Dagger_Distance = 2000.00
    real Dagger_Counter = 0.00 // (This is used as a counter. Moves at 32 units per 0.04 seconds. Fires 25 times per second. Translates to 800 units per second.
    integer Shadow_Counter = 0 // This value goes up every 0.04 seconds, at 3 it creates a shadow path and resets back to 0
        static method create takes unit caster, unit target, unit projectile, real distance, location targetloc returns thistype

        local thistype this = thistype.allocate() //Here's we're actually creating the struct, for further use ;)
        set this.Dagger_Caster = caster //Set the caster to a struct member
        set this.Dagger_Target = target //Set the target unit of the ability to a struct member
        set this.Dagger_SpellUnit = projectile
        set this.CasterPoint = GetUnitLoc( caster )
        set this.DaggerTargetLoc = targetloc
        set this.Dagger_Distance = distance

        call UnitApplyTimedLife( projectile, 'BTLF', 20.0 )
        
        return this
        endmethod
endstruct

// I want the below function to happen every 0.04 seconds
private function TimerAction takes nothing returns nothing 
    local dagger_data dd  = GetTimerData(GetExpiredTimer())
    local location DaggerPoint // Isn't needed because you have a structmember which stores this ;)
    local location DaggerOffset
    local unit shadow
    set dd.Shadow_Counter = ( dd.Shadow_Counter + 1 )
    set dd.Dagger_Counter = ( dd.Dagger_Counter + 32.00 ) 
    set DaggerPoint = GetUnitLoc(dd.Dagger_SpellUnit)
    set DaggerOffset = PolarProjectionBJ(DaggerPoint, 32.00, AngleBetweenPoints(dd.CasterPoint, dd.DaggerTargetLoc))
    call SetUnitPositionLocFacingLocBJ( dd.Dagger_SpellUnit, DaggerOffset, dd.DaggerTargetLoc )
    call SetUnitAbilityLevelSwapped( 'A0GW', dd.Dagger_SpellUnit, GetUnitAbilityLevelSwapped('A0GY', dd.Dagger_Caster) )
    call SetUnitAbilityLevelSwapped( 'A0GX', dd.Dagger_SpellUnit, GetUnitAbilityLevelSwapped('A0GY', dd.Dagger_Caster) )
    if ( dd.Shadow_Counter >= 2 ) then 
        set shadow = CreateUnit(GetOwningPlayer(dd.Dagger_Caster), 'h027', GetLocationX( DaggerPoint ), GetLocationY( DaggerPoint ), bj_UNIT_FACING )
        call UnitApplyTimedLife( shadow, 'BTLF', 9.00 )
        set dd.Shadow_Counter = 0
    endif
    call RemoveLocation(DaggerOffset)
    call RemoveLocation(DaggerPoint)
    call RemoveLocation(DaggerOffset)
    set DaggerPoint = null
    set DaggerOffset = null
    set shadow = null
//Below is the part where the timer should be recycled and the struct destroyed 
//so it doesn't leak right? Not sure if I did it correctly.
    if ( dd.Dagger_Counter >= dd.Dagger_Distance ) then // dd.Dagger_Distance is set to 2000.00
        call RemoveUnit( dd.Dagger_SpellUnit )
        call ReleaseTimer(GetExpiredTimer())
        call RemoveLocation( dd.DaggerTargetLoc )
        call RemoveLocation( dd.CasterPoint )
        call dd.destroy()
    endif
endfunction

// I want the below function to create and start the timer
private function Actions takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local unit t = GetSpellTargetUnit()
    local player p = GetOwningPlayer ( u )
    local location l
    local location Caster
    local location SpellTarget
    local dagger_data dd
    local timer time  = NewTimer() // Here you're using the TimerUtils system to efficiently create and use timers ;)
    
    local unit SomeUnit
        loop
        set SomeUnit = FirstOfGroup(udg_ShadowStopDamageGroup)
        exitwhen SomeUnit == null
        //Do stuff
        call GroupRemoveUnit(udg_ShadowStopDamageGroup, SomeUnit)
    endloop
    set Caster = GetUnitLoc (u)
    set SpellTarget = GetSpellTargetLoc()
    set dd = dagger_data.create( u, null, CreateUnit( p, 'h026', GetLocationX( Caster ), GetLocationY( Caster ), bj_UNIT_FACING ), 2000.0, SpellTarget )
dd.CasterPoint, bj_UNIT_FACING )
    call SetTimerData(time, dd) // Here's you're attaching the struct you just created to the timer, so you can later on get that value from another function
    call TimerStart(time, 0.04, true, function TimerAction) // Here's you're simply starting the timer you just attached the struct to :D
    call TriggerSleepAction( 7.00 ) // I did this because for some reason, my edited
//version of the trigger depends on the two local variables "Caster" and "SpellTarget". 
//If I were to remove the wait on removing the local points, the projectile would move to 
//the center to the map instead of the target point of cast.
    call RemoveLocation(Caster)
    call RemoveLocation(SpellTarget)
    set Caster = null
    set SpellTarget = null
    set u = null
    set t = null
    set p = null
    set l = null
endfunction


//====================================================================

private function InitDagger takes nothing returns nothing
    local trigger trigger_dagger = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(trigger_dagger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(trigger_dagger, Condition( function Conditions ))
    call TriggerAddAction(trigger_dagger, function Actions)
endfunction
endscope
11-09-2009, 11:58 AM#7
Anitarf
It would be useful to know exactly which debug message you're getting from TimerUtils. Check the code in the resource thread to copy the exact text of the debug message (since you can't copy it from WC3).

This spell would be greatly simplified if you used xecollider. The code for what you described in your first post would look something like this:

Collapse JASS:
scope dagger initializer Init

    globals
        private constant integer SPELL_ID = 'A000'

        private constant real DAGGER_SPEED = 1500.0
        private constant real DAGGER_DISTANCE = 2000.0
        private constant real DAGGER_COLLISION_SIZE = 50.0

        private constant string DAGGER_MODEL = "Abilities\\Weapons\\..."
        private constant real DAGGER_MODEL_SCALE = 1.0
        private constant real DAGGER_FLYHEIGHT = 60.0
    endglobals

    private struct dagger extends xecollider
        integer level
        unit caster

    // The "extends" keyword automatically makes our struct an xecollider,
    // meaning it has all of the properties of xecollider and by extension xefx.
    // This includes the following method, which will run whenever a dagger hits a unit:
        method onUnitHit takes unit hitTarget returns nothing
            // You can do what you want with the unit here.
            // If you want to destroy the dagger when it hits a unit, just do:
            // call this.terminate()
        endmethod

        static method create takes real x, real y, real targetx, real targety returns dagger
            // When we allocate a new instance of dagger, we must use
            // the create arguments of xecollider: x, y and direction
            local real angle = Atan2(targety-y, targetx-x)
            local dagger this = dagger.allocate(x,y,angle)
            // Now we set the xefx properties:
            set this.fxpath = DAGGER_MODEL
            set this.scale = DAGGER_MODEL_SCALE
            set this.z = DAGGER_FLYHEIGHT
            // And the xecollider properties:
            set this.speed = DAGGER_SPEED
            set this.expirationTime = DAGGER_DISTANCE/DAGGER_SPEED
            set this.collisionSize = DAGGER_COLLISION_SIZE
        endmethod
    endstruct

    private function SpellCast takes nothing returns nothing
        local unit caster = SpellEvent.CastingUnit
        local dagger d = dagger.create(GetUnitX(caster), GetUnitY(caster), SpellEvent.TargetX, SpellEvent.TargetY)
        set d.level = GetUnitAbilityLevel(caster, SPELL_ID)
        set d.caster = caster
    endfunction
    private function Init takes nothing returns nothing
        call RegisterSpellEffectResponse(SPELL_ID, SpellCast)
    endfunction
    
endscope

Quick note: I'm also using SpellEvent.