• 🏆 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] Moving on

Status
Not open for further replies.
Level 6
Joined
Aug 1, 2009
Messages
159
I'm gonna ask, how do I create further vJASS spells? Without actually using math, I mean basic ones, I know how to make AoE spells which I learned from watermelon. I admire him because of helping me solve my problems. Can someone tell me how other spells are made? I can't make an instant spell like novas. Novas needs math for calculating the radians and such, I'm only a 12 year old boy and in the 1st year of high school (10 years of basic education). But can somebody give me a sample code of a vJASS spell similar to instant spells like Starfall, and a single targeted spell that hits the enemy many times (100 hits then when 100 hits is achieve it stops and it will charge for the final blow), please get it document and I'm going to learn it from your documents and the code itself. Obviously I'm going to ask questions about how they are made like before.

Thanks,
for the people who did help me I'm going to give a +rep even though it only helped me a bit or only tried.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
But can somebody give me a sample code of a vJASS spell similar to instant spells like Starfall, and a single targeted spell that hits the enemy many times (100 hits then when 100 hits is achieve it stops and it will charge for the final blow)
Starfall isn't an instant spell. Did you mean channeling?
Also, would the last spell be some kind of DoT (Damge over time)?
 
Level 13
Joined
Jul 26, 2008
Messages
1,009
Well I myself am not too good at math. Most that I use is learned from middleschool. For the difficult math I've asked around and grabbed systems that did it for me.

Your two biggest friends will be this:

AngleBetweenPoints/Atan2. This one lets you find out the angle between two things by their location or by getting their X coordinate and Y coordinate. AngleBetween takes locations in order of A(The Source) and B(The Target), Atan2 takes X and Y but in order of B and A.

JASS:
AngleBetweenPoints(GetUnitLoc(A), GetUnitLoc(B))

vs

Atan2(GetUnitY(B) - GetUnitY(A), GetUnitX(B) - GetUnitX(A))

They also return different numbers. Atan2 returns Radians, which can be converted to Degrees with bj_DEGTORAD. AngleBetween returns Degrees, which can be converted to Radians with bj_RADTODEG. Different functions and systems require Radians or Degrees. Don't worry about knowing Radians, since you can always convert. Like if you want a 90 degree angle in radians just do 90*bj_DEGTORAD.

What's important is to know which function takes what. :) Now that's the basis of learning. Understand where something is pointing in 360 degrees, which at this point you should in your education.

Great! You now know how to point things at where they need to go. Now how do you get them there? PolarProjection.

Polar Projection takes where the object starts, how far it will move, and at what angle. It takes angle in degrees. So lets say you want to move a Unit, A, 90 units at 45 degrees of where it's standing, which'd be at a nice even slant. PolarProjection(GetUnitLocation(A), 90, 45). That will return the point that the unit is moving to. Set it to a variable then SetUnitPositionLocation(A, TheVariable).

But lets say we want to move something towards a target point picked by a spell. Well, just find the Angle with AngleBetweenPoints(GetUnitLocation(A), GetSpellTargetLocation()). Then set that to a variable and plug it into PolarProjection. Easy as pie.

Polar Projection can also be determined in X and Y. Just remember angle must be converted to radians for this:

x = HowFar*Cos(angle*bj_DEGTORAD)
y = HowFar*Sin(angle*bj_DEGTORAD)

That gives the X and Y that Polar Projection would return as a location.

Knowing this, you can move units and special effects where you want them. Just determine the units moved by the grid on the WorldEdit minimap.

I used this to make frogs appear randomly in the vicinity of the caster which explode on death.


JASS:
     local real ang = GetRandomReal(0,360)*bj_DEGTORAD
     set x = GetUnitX(A)+GetRandomReal(50,400)*Cos(ang)
     set y = GetUnitY(A)+GetRandomReal(50,400)*Sin(ang)

and I used this to move my hero in a rush towards his targeted point.

JASS:
     local real angle = Atan2(GetSpellTargetY() - GetUnitY(A), GetSpellTargetX() - GetUnitX(A))
     local real x = GetUnitX(A) + 20 * Cos(angle)
     local real y = GetUnitY(A) + 20 * Sin(angle)

It's in a timer that goes off every 0.034 seconds, the recommended amount for moving graphics or units.

Now the final operation that will help you is finding the distance between two points. DistanceBetweenPoints() It takes two locations and gives you the distance between those two points. Great to know how close you are to a targeted point. Also great to know how long it'd take to get to the point a certain speed, but i've forgotten that formula.

In JASS:

JASS:
SquareRoot((GetUnitX(A)-GetSpellTargetX()) * (GetUnitX(A)-GetSpellTargetX()) + (GetUnitY(A)-GetSpellTargetY()) * (GetUnitY(A)-GetSpellTargetY()))

or

local real dx = GetUnitX(A) - GetSpellTargetX()
local real dy = GetUnitY(A) - GetSpellTargetY()
local real dist = SquareRoot(dx*dx+dy*dy)

Knowing how these three functions operate and what they can do will help you tremendously on making your own special effects, slide systems, movement systems, etc.

For your nova, well you'd want to send something out from your hero or cause an AoE damage around your hero. Best example: Increase your angle by how many things you want to send out/explosions you want to cause.

Lets say angle1 would be 30, 2 would be 60, etc. so 360/30 = 12. Create a loop that takes i*30 and exitwhen i < 12.
 
Last edited:
Level 6
Joined
Aug 1, 2009
Messages
159
First of all TitanHex, thank you for your response, but I am still confused about it, I'm going to try that when I have time. Watermelon, sorry, I thought it was an instant spell but, sorry about that lol. You can do what ever you want, just as long as its an Single-Targeted spell. :D

+rep for Titanhex :D
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Example of a healing over time spell (Everything is done in the struct):
JASS:
scope HealOverTime
    globals
        private constant integer SPELL_ID = 'A000' // Raw id of the ability
        private constant integer BUFF_ID = 'Brej' // Raw id of the buff
        private constant real TIMER_LOOP = 1. // How often the timer will loop. Affects how often the unit will be healed         
    endglobals

    // How much the unit will be healed per second.
    private function Heal takes integer lvl returns real
        return 75. + 50*lvl // Note the . behind 75 allows us to avoid using I2R
    endfunction  
    
    // How long should the buff last on the target unit. This is also there so that the multiple healing effects stack.
    private constant function Duration takes integer lvl returns real
        return 5. + 5*lvl
    endfunction
    
    // This struct deals with the entire spell.
    private struct Data
        // The struct members:
        unit target // The target unit of the spell.
        boolean hasBuff = false // Used to check when the target unit gets the buff.
        real count = 0 // Counts how long the buff has been on the unit.
        real heal // Amount healed per second.
        real dur // How long the buff should last
        timer tim 
        
        // This method runs periodically to heal the target or destroy the struct once the spell is finished
        static method onLoop takes nothing returns nothing           
            local thistype this = GetTimerData(GetExpiredTimer())  // This gets the struct attached to the timer.
            // Checks to see when .target has the buff. If it does, set .hasBuff to true.
            if not .hasBuff and GetUnitAbilityLevel(.target,BUFF_ID) > 0 then
                set .hasBuff = true
            endif
            // Heals the unit as long as the buff is still there and .hasBuff is true. Also increases .count
            if .hasBuff and GetUnitAbilityLevel(.target,BUFF_ID) > 0 then
                call SetWidgetLife(.target,GetWidgetLife(.target)+.heal)
                set .count = .count + TIMER_LOOP 
            endif
            // Stops the spell when either the buff is gone or the duration has been exceeded.
            if .hasBuff and (.count > .dur or GetUnitAbilityLevel(.target,BUFF_ID) == 0) then
                call ReleaseTimer(.tim) // Releases the timer or else it will keep running.
                call .destroy() // Destroy the struct because we don't need it anymore.                
            endif
        endmethod
        
        // This basically starts our spell
        static method create takes unit target, integer lvl returns thistype // thistype is basically the same as writing Data, which is the struct's name.
            local thistype this = thistype.allocate()            
            set .target = target
            set .heal = Heal(lvl)*TIMER_LOOP // Multiply by TIMER_LOOP since the unit will be healed every TIMER_LOOP
            set .dur = Duration(lvl)
            
            set .tim = NewTimer() // We're using TimerUtils for recycling timers and attaching data
            call SetTimerData(.tim,this)
            call TimerStart(.tim,TIMER_LOOP,true,function thistype.onLoop)
            return this
        endmethod
        
        // This is actually a trigger-condition thread. We're just doing everything in here to reduce the amount of threads needed.
        static method spellActions takes nothing returns boolean
            if GetSpellAbilityId() == SPELL_ID then
                call thistype.create(GetSpellTargetUnit(),GetUnitAbilityLevel(GetTriggerUnit(),SPELL_ID))
            endif
            return false // Always return false since we never need to use a trigger-actions thread.
        endmethod
        
        // This special method runs on map initialization. It must be named onInit.
        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.spellActions))
        endmethod
    endstruct
endscope
This should be a pretty basic example of how effects over time should be done. If you want a more in-depth explanation you could look at this.
 
Level 6
Joined
Aug 1, 2009
Messages
159
Thank you, but how do structs works? I'm confused about it, do you really need to make a struct for this spell?
 
Level 13
Joined
Jul 26, 2008
Messages
1,009
I just wanted to add a little gem to spellmaker tips:

When doing the special effects of spells using triggers, GetAbilityEffectById() is truly useful. It allows you to set the data in the Art fields to the special effect you want. In terms of configuration, it's really quiet helpful.

As for structs, I use them but I'm not entirely familar with them. Everything they do can be done in regular JASS. The secret is in the compiler, which makes things a lot easier. It turns JASS into an OOP language.

Basically all those variables at the top of the struct are states of the struct. They can be changed by the methods of the struct depending on the data flowing through them. The struct seems to be an object, which holds different functions it can do (methods) and several variables which can be changed. It can be converted into an integer and have it's data shared.

Methods are just functions, that's all. Method = function. But they can do things functions cannot. My suggestion is to learn how to use returns and takes, so functions make a lot more sense to you (If they don't already).

Structs are useful, and so are scopes. Especially for spell-makers. Functions are very helpful for systems makers. You should learn these. They're not that hard to use.
 
Level 11
Joined
Sep 12, 2008
Messages
657
well.. when he sayd 100 hits, then charge for the final blow,

i think he meant like, 100 quick hits, fimiliar to neji - 128 palms, from naruto (anime).

he does 128 strikes really fast, and at the end he does a fatal blow knocking you back..

im pretty sure thats what he meant.. and its quite simple, making him repeat the same old attack animation at loops with timers, or w/e, unless you want it mui.
if you want it mpi, its quite simple aswell, making the timers as array's for each player.

then repeat the attack animation and deal damage each blow.
if you want me to tell you every step how to do it, i'll do that..
 
Level 15
Joined
Oct 18, 2008
Messages
1,588
im pretty sure thats what he meant.. and its quite simple, making him repeat the same old attack animation at loops with timers, or w/e, unless you want it mui.
if you want it mpi, its quite simple aswell, making the timers as array's for each player.

then repeat the attack animation and deal damage each blow.
if you want me to tell you every step how to do it, i'll do that..

I would use a global/local integer as a couneter :) local is way easyer to make MUI I think :D
 
Level 6
Joined
Aug 1, 2009
Messages
159
well.. when he sayd 100 hits, then charge for the final blow,

i think he meant like, 100 quick hits, fimiliar to neji - 128 palms, from naruto (anime).

he does 128 strikes really fast, and at the end he does a fatal blow knocking you back..

im pretty sure thats what he meant.. and its quite simple, making him repeat the same old attack animation at loops with timers, or w/e, unless you want it mui.
if you want it mpi, its quite simple aswell, making the timers as array's for each player.

then repeat the attack animation and deal damage each blow.
if you want me to tell you every step how to do it, i'll do that..

Yes but its just an example, the kind person can make a spell on what he prefers.

TitanHex said:
I just wanted to add a little gem to spellmaker tips:

When doing the special effects of spells using triggers, GetAbilityEffectById() is truly useful. It allows you to set the data in the Art fields to the special effect you want. In terms of configuration, it's really quiet helpful.

As for structs, I use them but I'm not entirely familar with them. Everything they do can be done in regular JASS. The secret is in the compiler, which makes things a lot easier. It turns JASS into an OOP language.

Basically all those variables at the top of the struct are states of the struct. They can be changed by the methods of the struct depending on the data flowing through them. The struct seems to be an object, which holds different functions it can do (methods) and several variables which can be changed. It can be converted into an integer and have it's data shared.

Methods are just functions, that's all. Method = function. But they can do things functions cannot. My suggestion is to learn how to use returns and takes, so functions make a lot more sense to you (If they don't already).

Structs are useful, and so are scopes. Especially for spell-makers. Functions are very helpful for systems makers. You should learn these. They're not that hard to use.

Yes, I already know how to use scopes, but, I'm going to try learning more about structs, or learn the whole thing about it.

Making MUI in vJASS or JASS isn't clear atm to me, but I can do MUI AoE spells, only the simple ones.
 
Level 6
Joined
Aug 1, 2009
Messages
159
Example of a healing over time spell (Everything is done in the struct):
JASS:
scope HealOverTime
    globals
        private constant integer SPELL_ID = 'A000' // Raw id of the ability
        private constant integer BUFF_ID = 'Brej' // Raw id of the buff
        private constant real TIMER_LOOP = 1. // How often the timer will loop. Affects how often the unit will be healed         
    endglobals

    // How much the unit will be healed per second.
    private function Heal takes integer lvl returns real
        return 75. + 50*lvl // Note the . behind 75 allows us to avoid using I2R
    endfunction  
    
    // How long should the buff last on the target unit. This is also there so that the multiple healing effects stack.
    private constant function Duration takes integer lvl returns real
        return 5. + 5*lvl
    endfunction
    
    // This struct deals with the entire spell.
    private struct Data
        // The struct members:
        unit target // The target unit of the spell.
        boolean hasBuff = false // Used to check when the target unit gets the buff.
        real count = 0 // Counts how long the buff has been on the unit.
        real heal // Amount healed per second.
        real dur // How long the buff should last
        timer tim 
        
        // This method runs periodically to heal the target or destroy the struct once the spell is finished
        static method onLoop takes nothing returns nothing           
            local thistype this = GetTimerData(GetExpiredTimer())  // This gets the struct attached to the timer.
            // Checks to see when .target has the buff. If it does, set .hasBuff to true.
            if not .hasBuff and GetUnitAbilityLevel(.target,BUFF_ID) > 0 then
                set .hasBuff = true
            endif
            // Heals the unit as long as the buff is still there and .hasBuff is true. Also increases .count
            if .hasBuff and GetUnitAbilityLevel(.target,BUFF_ID) > 0 then
                call SetWidgetLife(.target,GetWidgetLife(.target)+.heal)
                set .count = .count + TIMER_LOOP 
            endif
            // Stops the spell when either the buff is gone or the duration has been exceeded.
            if .hasBuff and (.count > .dur or GetUnitAbilityLevel(.target,BUFF_ID) == 0) then
                call ReleaseTimer(.tim) // Releases the timer or else it will keep running.
                call .destroy() // Destroy the struct because we don't need it anymore.                
            endif
        endmethod
        
        // This basically starts our spell
        static method create takes unit target, integer lvl returns thistype // thistype is basically the same as writing Data, which is the struct's name.
            local thistype this = thistype.allocate()            
            set .target = target
            set .heal = Heal(lvl)*TIMER_LOOP // Multiply by TIMER_LOOP since the unit will be healed every TIMER_LOOP
            set .dur = Duration(lvl)
            
            set .tim = NewTimer() // We're using TimerUtils for recycling timers and attaching data
            call SetTimerData(.tim,this)
            call TimerStart(.tim,TIMER_LOOP,true,function thistype.onLoop)
            return this
        endmethod
        
        // This is actually a trigger-condition thread. We're just doing everything in here to reduce the amount of threads needed.
        static method spellActions takes nothing returns boolean
            if GetSpellAbilityId() == SPELL_ID then
                call thistype.create(GetSpellTargetUnit(),GetUnitAbilityLevel(GetTriggerUnit(),SPELL_ID))
            endif
            return false // Always return false since we never need to use a trigger-actions thread.
        endmethod
        
        // This special method runs on map initialization. It must be named onInit.
        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.spellActions))
        endmethod
    endstruct
endscope
This should be a pretty basic example of how effects over time should be done. If you want a more in-depth explanation you could look at this.

Sorry with the double post, but I used your and it gives me errors, it has an error in this part :
JASS:
static method create takes unit target, integer lvl returns thistype

It says:
Line 131: method create must return HealOverTime__Data
 
Level 13
Joined
Jul 26, 2008
Messages
1,009
Hmm for me it worked.

Here is an example that comes up with no errors on my compiler. It's a spell that heals allies when the Duration reaches the end.

JASS:
scope Bloom initializer onInit

globals
    private constant integer SPELLID        = 'bloo'
    private constant integer BUFFID         = 'Bloo'
    private constant real TICK              = 1
    private unit TEMP
endglobals

private struct Data

    unit c
    real dur

    static method GroupEm takes nothing returns boolean
     local integer lvl = GetUnitAbilityLevel(TEMP, SPELLID)
     local unit t= GetFilterUnit()
        if IsUnitAlly(t, GetOwningPlayer(TEMP)) and not(IsUnitType(t, UNIT_TYPE_DEAD) or IsUnitType(t, UNIT_TYPE_STRUCTURE)) then
            call SetWidgetLife(t, GetWidgetLife(t)+30+50*lvl)
        endif
     set t= null
     return false
    endmethod

    static method Timer takes nothing returns nothing
     local timer tim = GetExpiredTimer()
     local thistype this = GetTimerData(tim)
     local group g= NewGroup()
        set .dur = .dur+TICK
        if .dur >= 5 then
            set TEMP = .c
            call GroupEnumUnitsInRange(g, GetUnitX(.c),GetUnitY(.c), 400, Filter(function thistype.GroupEm))
            call ReleaseTimer(tim)
            call .destroy()
        endif
        if GetUnitAbilityLevel(.c, BUFFID) < 1 then
            call ReleaseTimer(tim)
            call .destroy()
        endif
     call ReleaseGroup(g)
    endmethod

    static method create takes unit c returns thistype
     local timer tim = NewTimer()
     local thistype this = thistype.allocate()
        set .c = c
        set .dur = 0
        call SetTimerData(tim,this)
        call TimerStart(tim, TICK, true, function thistype.Timer)
     return this
    endmethod

    static method Conditions takes nothing returns nothing
        if GetSpellAbilityId() == SPELLID then
            call thistype.create(GetTriggerUnit())
        endif
    endmethod


    static method onInit takes nothing returns nothing
     local trigger t = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddAction( t, function thistype.Conditions )
        call XE_PreloadAbility(SPELLID)
     set t = null
    endmethod

    endstruct
endscope

Just a note: These require TimerUtils to use.

To do MUI, you'll want to use a lot of local variables and avoid global variables. TimerUtils and Hashtables help with that greatly.

It's important to know that if you use a global variable, you must use it IMMEDIATELY to keep MUI. The thread cannot have a wait in it and the unit must not have to take further action before the Global is used.

Also my explanation was primarily about structs. Hopefully I didn't confuse you into thinking I was talking about scopes :) Scopes and libraries are actually very easy to use, but necessary for vJASS.

Private vs Public is important to learn too. I assume those are familiar to you.
 
Level 14
Joined
Nov 18, 2007
Messages
816
It says:
Line 131: method create must return HealOverTime__Data
You have an outdated version of JassHelper.

You can either update JassHelper manually (head over to wc3c.net and download the most recent version from there, then replace jasshelper.exe, clijasshelper.exe and jasshelper.conf in the jasshelper folder of your JNGP with the most recent versions), or download this version of JNGP which already has JassHelper updated to the most recent version (among a multitude of other changes).

Example of a healing over time spell (Everything is done in the struct):
You see, this is why i hate using scopes for spells (i have never needed to use scopes in the last 2 to 3 years, i always used libraries instead). You have no clear indication as to which 3rd party libraries are being used.
That spell requires TimerUtils, and that post fails to link to it.
Theres also no indication that the spells needs to be based off an ability that places a buff (BUFF_ID) on the target unit (like Acid Bomb).
 
Level 6
Joined
Aug 1, 2009
Messages
159
Thank you it worked, I'm kinda starting to understand Structs ATM, but @TitanHex, why do you need TimerUtils for this spell?

+rep
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Sorry, I should have mentioned several things before I posted that code. I did mention that I was using TimerUtils in the code, but I should have at least posted a link to it.

To expand on what TitanHex said:
You attach structs to a timer using SetTimerData
This function takes a timer and an integer as its parameters.
It's interesting to note that structs can be treated like integers which allows their data to be easily passed.

When you retrieve data back from a timer, you'll want to turn that integer back to the struct so you can access the data, which is why you do this:
JASS:
local thistype this = GetTimerData(GetExpiredTimer())
instead of this
JASS:
local integer i = GetTimerData(GetExpiredTimer()) // This is pretty much useless to you like this.
Note that you can only use those functions on timers given by TimerUtils through NewTimer.

When done with a timer, use ReleaseTimer to recycle it.
This is done before the struct is destroyed or else you wouldn't be able to refer to the struct member.
 
Level 6
Joined
Aug 1, 2009
Messages
159
Can you send me your JNGP? My JNGP have problems with those JassHelper stuffs, now I'm getting code errors from the TimerUtils itself, -.-
 
Status
Not open for further replies.
Top