Help with first Jass spell

tommerbob

Minecraft. :D
Reaction score
110
Okay, I think it's time to start learning Jass. I read through a couple of the tutorials here, and I want to practice as I learn (obviously), so I came up with this spell.

The caster channels for 3 seconds, pulling the target enemy toward him, dealing damage to it equal to 5% of its max hitpoints each second. The target is disabled for the duration.

I realize it probably totally sucks, but hey, its my first real try at Jass. Here is the code:

JASS:
function Pull takes nothing returns nothing
    local location c = GetUnitLoc(udg_caster)
    local real r = (GetUnitState(udg_target, UNIT_STATE_MAX_LIFE) * 0.05) * 0.03 // Deals damage = 5% max hp per second
    local real d = DistanceBetweenPoints(GetUnitLoc(udg_target), c)
    local location l = PolarProjectionBJ(GetUnitLoc(udg_target), d * 0.03, GetUnitFacing(udg_target)) 
    
    call SetUnitFacing(udg_target, AngleBetweenPoints(GetUnitLoc(udg_target), c))
    call SetUnitX(udg_target, GetLocationX(l))
    call SetUnitY(udg_target, GetLocationY(l))
    
    call UnitDamageTarget(udg_caster, udg_target, r, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
    
    call RemoveLocation(l)
    call RemoveLocation(c)
    set l = null
    set c = null
endfunction

function Cast takes nothing returns nothing
    local timer t = CreateTimer()
    set udg_caster = GetTriggerUnit()
    set udg_target = GetSpellTargetUnit()
    call PauseUnit(udg_target, true)
    call TimerStart(t, 0.03, true, function Pull)
    call TriggerSleepAction(3.00)
    call DestroyTimer(t)
    call PauseUnit(udg_target, false)
    set udg_caster = null
    set udg_target = null
    set t = null
endfunction

function InitTrig_Test takes nothing returns nothing
    set gg_trg_Test = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Test, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddAction(gg_trg_Test, function Cast)
endfunction


I'm wondering how I can make it better. (this is a total practice spell, so I don't care if it totally sucks, I can trash it). I have tons of questions, haha, but for now I'm just curious how I can improve on the basic stuff. Thanks for looking.

EDIT: Talk to me like I'm a total noob. I probably won't understand you anyway. :p
 

Cookiemaster

New Member
Reaction score
36
It looks pretty good to me for a first JASS spell actually.

The "Pull" function can be optimized a bit though. Removing the BJ functions with their math equivalents will give a boost in speed, since JASS function calls are kind of slow. (And you're calling this function 33+ times per second, you need any kind of optimization here really.)

DistanceBetweenPoints(a,b)
to
SquareRoot( (aX-bX)*(aX-bX) , (aY-bY)*(aY-bY) )
(aka pythagoras, which is exactly what the DistanceBetweenPoints function does.)

PolarProjectionBJ(loc,dist,angle)
to
locX+dist*Cos(angle)
and
locY+dist*Sin(angle)
for the x,y.

AngleBetweenPoints(a,b)
to
Atan2(bY-aY,bX-aX)
 

rexpim

Member
Reaction score
8
Don't forget to add condition, if you dont add coondition everytime you cast a spell it will do this trigger

JASS:
function Spellbeingcast takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A000' ) ) then //raw code
        return false
    endif
    return true
endfunction

function Pull takes nothing returns nothing
    local location c = GetUnitLoc(udg_caster)
    local real r = (GetUnitState(udg_target, UNIT_STATE_MAX_LIFE) * 0.05) * 0.03 // Deals damage = 5% max hp per second
    local real d = DistanceBetweenPoints(GetUnitLoc(udg_target), c)
    local location l = PolarProjectionBJ(GetUnitLoc(udg_target), d * 0.03, GetUnitFacing(udg_target)) 
    
    call SetUnitFacing(udg_target, AngleBetweenPoints(GetUnitLoc(udg_target), c))
    call SetUnitX(udg_target, GetLocationX(l))
    call SetUnitY(udg_target, GetLocationY(l))
    
    call UnitDamageTarget(udg_caster, udg_target, r, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
    
    call RemoveLocation(l)
    call RemoveLocation(c)
    set l = null
    set c = null
endfunction

function Cast takes nothing returns nothing
    local timer t = CreateTimer()
    set udg_caster = GetTriggerUnit()
    set udg_target = GetSpellTargetUnit()
    call PauseUnit(udg_target, true)
    call TimerStart(t, 0.03, true, function Pull)
    call TriggerSleepAction(3.00)
    call DestroyTimer(t)
    call PauseUnit(udg_target, false)
    set udg_caster = null
    set udg_target = null
    set t = null
endfunction

function InitTrig_Test takes nothing returns nothing
    set gg_trg_Test = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Test, EVENT_PLAYER_UNIT_SPELL_EFFECT )
 call TriggerAddCondition( gg_trg_teste, Condition( function Spellbeingcast  ) )
    call TriggerAddAction(gg_trg_Test, function Cast)
endfunction
 

XeNiM666

I lurk for pizza
Reaction score
138
remove the trigger sleep action. it bugs everytime you cast it again within 3 seconds of last casting...
try using another udg_ variable to count the duration. and check it in the Pull function.

also, do you use NewGen?
 

luorax

Invasion in Duskwood
Reaction score
67
This should work, if you have JNGP. It's MUI, and as much optimized, as possible (in my opinion - maybe KeyTimers2 would be better. And a "Conditon-Function" without "TriggerAddAction". But this is far enough for a beginner).
But be careful! This is hand/fastwritten!
Oh, and you'll need a TimerUtils.

JASS:
scope TestOne initializer init

    globals
        private constant integer ABILITY_ID = 'A000'
        private constant real PERIOD = .03
        private constant real DURATION = 3.
    endglobals

    private struct Data

        unit caster
        unit target
        real time

        static method create takes unit caster, unit target returns thistype
            local thistype this = .allocate()
            set .caster = caster
            set .target= target
            set .time = 0.
        endmethod

        method onDestroy takes nothing returns nothing
            set .caster = null
            set .target= null
        endmethod

    endstruct

    private function Cond takes nothing returns boolean
        return GetSpellAbilityId()== ABILITY_ID
    endfunction

    private function Pull takes nothing returns nothing
        local timer t = GetExpiredTimer() //Get the Expired timer, to get the attached datas
        local Data d = GetTimerData(t) //Get the actual Data of this instance
        local real cx = GetUnitX(d.caster)
        local real cy = GetUnitY(d.caster)
        local real tx = GetUnitX(d.target)
        local real ty = GetUnitY(d.target)
        local real face = GetUnitFacing(d.target)
        local real r = (GetUnitState(d.target, UNIT_STATE_MAX_LIFE) * 0.05) * 0.03 // Deals damage = 5% max hp per second
        local real d = SquareRoot( (cx-tx)*(cx-tx) , (cy-ty)*(cy-ty) ) //DistanceBetweenXY
    
        call SetUnitFacing(d.target, Atan2(cy-ty,cx-tx) ) //AngleeBetweenXY
        call SetUnitX(d.target, tx + (d * .03)*Cos(face))
        call SetUnitY(d.target, ty + (d * .03)*Sin(face))
        call UnitDamageTarget(d.caster, d.target, r, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
    
        set d.time = d.time + PERIOD
        if d.time >= DURATION then
            call PauseUnit(d.target, false)
            call d.destroy() //Destroying the data holder
            call ReleaseTimer(t) //PauseTimer(t) + DestroyTimer(t) -- But the timer'll be recycled instead
        endif

        //Removeing leaks
        set caster = null
        set target = null
        set t = null
    endfunction

    private function Cast takes nothing returns nothing
        local timer t = NewTimer() //We need the TimerUtils, to store our struct (Data), and to recycle the timer
        local unit caster = GetTriggerUnit()
        local unit target = GetSpellTargetUnit()

        call PauseUnit(target, true)
        call SetTimerData(t, Data.create(caster, target)) //The "Data.create(), is inlined, because we don't need it anywhere in this function
        call TimerStart(t, PERIOD, true, function Pull)

        //Removing leaks
        set t = null
        set caster = null
        set target = null
    endfunction

    private function init takes nothing returns nothing
        local trigger t  = CreateTrigger()

        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(t, Condition(function Cond))
        call TriggerAddAction(t, function Cast)

        set t = null //Because we won't destroy the trigger, it isn't really necessary
    endfunction

endscope


If something isn't clear, feel free to ask! :D

EDIT:
And one more thing: if you use any locations, always store the location in a variable! Or you won't be able to destroy it, and it will leak.
Like in these calls:
JASS:
call SetUnitFacing(udg_target, AngleBetweenPoints(GetUnitLoc(udg_target), c))
local real d = DistanceBetweenPoints(GetUnitLoc(udg_target), c)
local location l = PolarProjectionBJ(GetUnitLoc(udg_target), d * 0.03, GetUnitFacing(udg_target))

GetUnitLoc(udg_target) will leak three times per run, so only one use of the spell will leak 300! locations.
 

tommerbob

Minecraft. :D
Reaction score
110
Thanks for all of the replies. I got lots of questions lol. But for now, @ Luorax when I saved your code in my map, it had a compile error that there are too many arguments for the SquareRoot function...?

(this line)

JASS:
local real d = SquareRoot( (cx-tx)*(cx-tx) , (cy-ty)*(cy-ty) ) //DistanceBetweenXY


@Xenim666: Yeah I use NewGen. I can't imagine writing in Jass with vanilla WE lol.
 

Sgqvur

FullOfUltimateTruthsAndEt ernalPrinciples, i.e shi
Reaction score
62
JASS:
local real d = SquareRoot( (cx-tx)*(cx-tx) , (cy-ty)*(cy-ty) ) //DistanceBetweenXY


The , should be a +

JASS:
local real d = SquareRoot( (cx-tx)*(cx-tx) + (cy-ty)*(cy-ty) ) //DistanceBetweenXY


>I can't imagine writing in Jass with vanilla WE lol.
=) (space space)
 

tommerbob

Minecraft. :D
Reaction score
110
Now it says there is a missing return on this line:

JASS:
set s__TestOne___Data_time[this]=0.


But I can't find that line anywhere... :confused: :confused:
 

luorax

Invasion in Duskwood
Reaction score
67
JASS:
scope TestOne initializer init

    globals
        private constant integer ABILITY_ID = 'A000'
        private constant real PERIOD = .03
        private constant real DURATION = 3.
        private constant real PERCENT = .05
    endglobals

    private struct Data

        unit caster
        unit target
        real time

        static method create takes unit caster, unit target returns thistype
            local thistype this = .allocate()
            set .caster = caster
            set .target= target
            set .time = 0.
            return this
        endmethod

        method onDestroy takes nothing returns nothing
            set .caster = null
            set .target= null
        endmethod

    endstruct

    private function Cond takes nothing returns boolean
        return GetSpellAbilityId()== ABILITY_ID
    endfunction

    private function Pull takes nothing returns nothing
        local timer t = GetExpiredTimer() //Get the Expired timer, to get the attached datas
        local Data d = GetTimerData(t) //Get the actual Data of this instance
        local real cx = GetUnitX(d.caster)
        local real cy = GetUnitY(d.caster)
        local real tx = GetUnitX(d.target)
        local real ty = GetUnitY(d.target)
        local real face = GetUnitFacing(d.target)
        local real r = (GetUnitState(d.target, UNIT_STATE_MAX_LIFE) * PERCENT) * PERIOD // Deals damage = 5% max hp per second
        local real distance = SquareRoot( (cx-tx)*(cx-tx) + (cy-ty)*(cy-ty) ) //DistanceBetweenXY
    
        call SetUnitFacing(d.target, bj_RADTODEG * Atan2(cy-ty,cx-tx) ) //AngleeBetweenXY
        call SetUnitX(d.target, tx + (distance * .03)*Cos(face * bj_DEGTORAD))
        call SetUnitY(d.target, ty + (distance * .03)*Sin(face * bj_DEGTORAD))
        call UnitDamageTarget(d.caster, d.target, r, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
    
        set d.time = d.time + PERIOD
        if d.time >= DURATION then
            call PauseUnit(d.target, false)
            call d.destroy() //Destroying the data holder
            call ReleaseTimer(t) //PauseTimer(t) + DestroyTimer(t) -- But the timer'll be recycled instead
        endif

        //Removing leaks
        set t = null
    endfunction

    private function Cast takes nothing returns nothing
        local timer t = NewTimer() //We need the TimerUtils, to store our struct (Data), and to recycle the timer
        local Data d = Data.create(GetTriggerUnit(), GetSpellTargetUnit())

        call PauseUnit(d.target, true)
        call SetTimerData(t, d) //Attaching the required data to the timer for later use
        call TimerStart(t, PERIOD, true, function Pull)

        //Removing leaks
        set t = null
    endfunction

    private function init takes nothing returns nothing
        local trigger t  = CreateTrigger()

        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(t, Condition(function Cond))
        call TriggerAddAction(t, function Cast)

        set t = null //Because we won't destroy the trigger, it isn't really necessary
    endfunction

endscope


@Sgqvur:
Yes, sorry, i simple copy+pasted from Cookiemaster's reply ( i was lazy to search it, becasue someone've already written it :D)

@tommerbob:
Yes, i forgot the "return this" in the create method.
 

Sgqvur

FullOfUltimateTruthsAndEt ernalPrinciples, i.e shi
Reaction score
62
I think those lines in the pull function should not be present:
JASS:
        //Removeing leaks
        set caster = null
        set target = null


At what line is the error reported?
 

luorax

Invasion in Duskwood
Reaction score
67
Sorry, as i said, it's fast and handwritten (I suggest everyone, to forget the Ctrl+C + Ctrl+V "methods" :D). But i've edited the last version, i hope this was the last error with it.
 

tommerbob

Minecraft. :D
Reaction score
110
Okay, so Sgqvur is right, I had to remove

JASS:
set caster = null
set target = null


from the Pull function. It runs, but not properly...lol.

The target faces the opposite direction of his original direction, and doesn't move. He only takes damage... Math is not my forte so I have no idea why.

Thanks for the help and patience. Once we actually get the spell working, I'll fire all my questions. :p
 

luorax

Invasion in Duskwood
Reaction score
67
Edited the last version, take a look at it (i always forgot that bj_RADTODEG -- this the the 5th time, when i forgot about it)

Also if you want to use the "pure coords" (X,Y), then here's some useful function (and some other):

JASS:
function ResEffect takes unit whichUnit returns nothing
    call DestroyEffect( AddSpecialEffectTarget("Abilities\\Spells\\Human\\Resurrect\\ResurrectTarget.mdl", whichUnit, "origin") )
endfunction

function DistanceBetweenXY takes real x1, real y1, real x2, real y2 returns real
    return SquareRoot((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
endfunction
    
function OffsetX takes real x1, real dist, real angle returns real
    return x1 + dist * Cos(angle * bj_DEGTORAD)
endfunction
    
function OffsetY takes real y1, real dist, real angle returns real
    return y1 + dist * Sin(angle * bj_DEGTORAD)
endfunction
    
function AngleBetweenXY takes real x1, real y1, real x2, real y2 returns real
    return bj_RADTODEG * (Atan2(y2 - y1, x2 - x1))
endfunction

function TriggerRegisterAnyUnitEventCustom takes trigger trig, playerunitevent whichEvent, integer maxPlayers returns nothing
    local integer index

    set index = 0
    loop
        call TriggerRegisterPlayerUnitEvent(trig, Player(index), whichEvent, null)

        set index = index + 1
        exitwhen index == maxPlayers
    endloop
endfunction

function CustomPolledWait takes real duration returns nothing
    local timer t = CreateTimer()
    local real  timeRemaining
     
    if (duration > 0) then  
        call TimerStart(t, duration, false, null)
        loop
            set timeRemaining = TimerGetRemaining(t)
            exitwhen timeRemaining <= 0
             
            if (timeRemaining > bj_POLLED_WAIT_SKIP_THRESHOLD) then
                call TriggerSleepAction(0.1 * timeRemaining)
            else
                call TriggerSleepAction(bj_POLLED_WAIT_INTERVAL)
            endif
        endloop
        call DestroyTimer(t)
        set t = null
    endif
endfunction


Note that, you can't use any waits (like TriggerSleepAction, PolledWait, CustomPolledWait) in timer callbacks.

Thanks for the help and patience. Once we actually get the spell working, I'll fire all my questions.

Feel free to ask. I'll help you if i can. (but as you can see, i'm a really big "rusher")
 

tommerbob

Minecraft. :D
Reaction score
110
Well it sort of works now... lol.

The target moves toward the caster, but in random patterns....

Here is a test map so you can see what I mean.
 

Attachments

  • Jass Testing.w3x
    20.6 KB · Views: 235

tommerbob

Minecraft. :D
Reaction score
110
Beautiful! Well I probably won't ever use this spell, but it was good practice. I got a few questions though, just to be able to understand how you set it up.

1. For the scope line:

JASS:
scope TestOne initializer init


Do the names (TestOne) and (init) matter? Or can I put whatever I want?

2. For the "private" before each function and the globals/structs, what does that do?

3. What is this part? Obviously its part of setting up the code, but how does it work?

JASS:
 static method create takes unit caster, unit target returns thistype
            local thistype this = .allocate()
            set .caster = caster
            set .target= target
            set .time = 0.
            return this
        endmethod

        method onDestroy takes nothing returns nothing
            set .caster = null
            set .target= null
        endmethod


What exactly is "thistype"?

4. What are these lines?

JASS:
call d.destroy() //Destroying the data holder

local Data d = Data.create(GetTriggerUnit(), GetSpellTargetUnit())


5. In cases like this spell, how can I prevent the target unit from turning collision off? I don't want it going through other objects...

I think that's about it for now. Everything else is (slowly) starting to make sense as I look at it. It's all still kinda confusing though. lol. Anyway, thanks for the help.
 

Zeth

Member
Reaction score
3
1. Yes, you can... But remember to change it here too:

JASS:
  private function init takes nothing returns nothing
        local trigger t  = CreateTrigger()

        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(t, Condition(function Cond))
        call TriggerAddAction(t, function Cast)

        set t = null //Because we won't destroy the trigger, it isn't really necessary
    endfunction



2. Private functions can't be used outside the scope where they are declared.

3. thistype is the name of the struct, in this case thistype it's the same at Data (name of the struct).

JASS:
 static method create takes unit caster, unit target returns Data
            local Data this = .allocate()
            set .caster = caster 
            set .target= target
            set .time = 0.
            return this
        endmethod

        method onDestroy takes nothing returns nothing
            set .caster = null
            set .target= null
        endmethod
 

luorax

Invasion in Duskwood
Reaction score
67
So:
1+2, scope TestOne initializer init
The scope is like a "namespace" (real programming languages). Using scopes/libraries we can hide our data from other spells/systems, etc (Data encapsulation). And we can "overload" our functions (For example you have 10 scope, but if you use public/private, you can use the "ACT" function name in every scope. In this spell: The function "Act" becomes "TestOne_Act", the "Cond" becomes "TestOne_Cond", and so one. The same with the globals. But using private, the variables/scopes're usable in the current scope only. So you can't call a scope's private function from another scope, but you can call a public (<scope_name>_<function_name>)
The only difference between libraries/scopes, that libraries will go before every script, direct to the map header. So you can use their usable functions everywhere in the map.

The init is the function, which runs at the map initialization. Every scope/library could contain an initializer, but it isn't necessary. You can define a scope without an initalizer. But we usually do the initialization things (for example InitHashtable, etc) in the initializer.

3-4. They're methods. A method is like a simple function, but only struct contains methods. If you define a struct, then you can use it as a new variable type (like an integer, real, unit, etc) But every object has it's own methods, which uses its own variables. So, for example:
I have this struct, the "Data". So i can use it as an object type, as i used in the spell:
JASS:
local Data d = Data.create(u1, u2)

Now i have a new object. But if i want, i could declare a new one:
JASS:
local Data d = Data.create(u1, u2)
local Data d2 = Data.create(u3, u4)

This will create another object. But why is this good? It won't overwrite the .caster and the .target variables, each object has its own members! So, we create and destroy the "variables" (members) dinamically. So d.caster is u2, and d2.caster is u3.
The private keyword means before the struct, that we can use that struct in the current namespace (library/scope) only. So if you use private struct in every spell, you can call them "Data" in every struct.
Thistype is the name of the struct. I wrote this:

JASS:
    private struct Data

        static method create takes unit caster, unit target returns thistype


But i could write this one:
JASS:
    private struct Data

        static method create takes unit caster, unit target returns Data

The two thing is the same.

5, In this spell, the collision of the unit isn't be turned off. It will move periodically, but its collision os still on. You can do it manually only, using the SetUnitPathing function.

If you want to know more about structs and namespaces, you'll have to learn some other tutorials. Only one post isn't enough to tell ypu everything.

But don't be scared: it won't be soo hard, as it seems. If you learn enough, and get some experience, you'll propably forgot the GUI things (i never use GUI), and everything. JASS is more faster, and gives you a LOT OF POSSIBILITIES! Belive me! :thup:
Also, I'm not the best JASS-er (as you can see), and my english shucks (i learn english 2 (this is my third year) ago, i started, when i started learning in the secondary school (high school). But i hope i was understandable, and at least, you learnt something new from me!
 

tommerbob

Minecraft. :D
Reaction score
110
So let me see if I understand this correctly.

1. The scope and private globals and struct basically makes all data inside this scope separate from all others, hence MUI?

2. "thistype" is merely the name of the struct? so I could used "billy" if I wanted? What is this line:

JASS:
local Data this = .allocate()


What does ".allocate()" mean?

3. The "method" basically means I can create global variables that can be used in all the functions of the spell, but are also local to this spell only?

4. So for the function SetUnitPathing, how would I do that with this spell, so that the target does not collide with the caster?

Thanks for the answers. :thup:
 
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