Just download
Jass Newgen Pack, and update JassHelper as instructed. It has great highlighting, a function list with search function, and you can see the API for any function by ctrl-clicking it.
I am going to make a short example/tutorial on you on how you could make this script in vJass, stay put.
EDIT: Alright, let's go.
The most central part about vJass is that it introduces something called Object Oriented Programming. I am going to explain what this means.
A great example of an object is how units are handled in warcraft. A unit is basically a collection of data which has some functionality associated with it. You can extort information from it, such as position and facing, and you can use different methods to manipulate it, such as ordering it to move here, face there, etc. vJass allows you to replicate this.
Firstly, to keep your code sorted, you use something called libraries. A library is kinda like what paper is for a book, and you can use them to decide in what order your code should be compiled. You define your libraries much like you do with everything in jass, you use a start and end block which you paste inside an empty trigger:
JASS:
library Missiles
//CODE GOES HERE
endlibrary
I will explain more about libraries later, for now all you need to know is that it is the page on which you write your code. Next, you may want to define some global variables. You can still do that in the variable editor, but using a global block gets rid of the ugly "udg_" prefixes:
JASS:
library Missiles
globals
private constant real INTERVAL = 0.03
endglobals
endlibrary
There are a few keywords here you might want to note. "Private" means that this global can
only be accessed by functions inside this library! This is called encapsulation and is a good way to protect your code from yourself, making sure that things can only be accessed in a context where they are needed. "Constant" means that the variable is fixed to the value you assigned to it and cannot be changed ingame. It may be hard to see the point of this, but it is good coding practice to point out which data is meant to be changed and which is not. Constant variable names are written in upper case to clearify.
Next, we have the structs. We can start by defining a struct and some of its members we might find useful:
JASS:
library Missiles
globals
private constant real INTERVAL = 0.03
endglobals
struct missile
unit dummy
effect sfx
real x = 0
real y = 0
real vel_x = 0
real vel_y = 0
endstruct
endlibrary
This means that each missile will have a unit, an sfx, coordinates for x and y, and values for its velocity. To make a new missile, we need a constructor method, which is assigned like this:
JASS:
library Missiles
globals
private constant real INTERVAL = 0.03
private constant integer DUMMY_ID = 'dumm'
endglobals
struct missile
unit dummy
effect sfx
real x = 0
real y = 0
real vel_x = 0
real vel_y = 0
static method create takes real x, real y, real facing, real velocity, player owner returns missile
local missile this = missile.allocate() //THIS CREATES A NEW INDEX FOR THE STRUCT INSTANCE
set .dummy = CreateUnit(owner, DUMMY_ID, x, y, facing)
set .x = x
set .y = y
set .vel_x = velocity * Cos(angle*bj_DEGTORAD) //THIS IS THE SAME AS POLAR PROJECTION
set .vel_y = velocity * Sin(angle*bj_DEGTORAD)
return this
endmethod
endstruct
endlibrary
There is a lot to explain here - lets start from the top. "Static" is a keyword which means that this function does not refer to any specific missile. You will understand this once i explain normal methods, and you will understand how the constructor cannot refer to a specific missile since it creates them. If you have studied math, you are familiar with how functions work - you input some numbers or data, and get something in return, like the case y(x) = 2x where you input
x and get
y in return. In this case, you input coordinates, owner, etc and get a missile in return.
At the head of the function you have what we call a local variable. Locals exist only within the function you created them, so this minimises risk of other parts of your code interfering with their values. In this case we are declaring a variable called "this" for reasons i will explain later - the next function, called "allocate", gives it a unique ID. You will notice that we can now use "missile" as a variable type, same as you would use "real" or "unit", since it is now a class of its own. Members of a struct are refered to inside methods by placing a dot before them. Outside the method, you will have to place the variable name before the dot, like this:
JASS:
local missile m = missile.create(0, 0, 0, 0, Player(0))
set m.x = 320 //YOU CAN SET THE COORDINATES OF THE MISSILES LIKE THIS
set m.y = 560
Anyhow, there is some more functionality we need to add to our missile before it is done. For instance, we need to decide what is done with the missiles when they die and we no longer need them. To do this, we add something called a destructor:
JASS:
method onDestroy takes nothing returns nothing
call RemoveUnit(.dummy) //REMOVE THE DUMMY UNIT
if .sfx != null then
call DestroyEffect(.sfx) //DESTROY ANY EFFECTS
endif
endmethod
The != sign means "not equal to". There is lots more stuff you could learn in other jass tutorials, but suffice to say this method cleans all the leaks and frees the ID so that another instance can use it. Since structs are basically just arrays with some make-up on them, and the ID of each struct is just the array index, we are limited by the same things as arrays, such as having a maximum of 8191 instances. Therefore it is important to destroy instances when we are done with them.
Lets now say we want to have a way of looping through all misiles on the map, in order to make them move or check if they have hit a wall. To do this, we can use a clever construct known as a linked list. What it means is basically that each instance of your struct has a "prev" and "next" member, which points to the struct above and below it in the list. Think of it as a chain, where each instance is a link. We also have a "first" and possibly a "last" member. Thankfully, someone else made an easy way to implement this into any struct:
ListModule. Just paste it into an empty trigger in your map.
I am gonna increase the pace now, but i trust that you can learn the basics from other sources, such as the jasshelper manual which you can find in the install directory of Jass Newgen Pack. I added an update method for our struct, and an initializer function to our library. Initializers work just like triggers that run on initialization - you can use them to do stuff on startup in order to set up your system. Inside it, i created a timer, which we will use to periodically run the update function. I also added a region called "Walls".
Now, a region in jass is not the same as a region in GUI, which we in jass just call a rect. While a rect can only be a single rectangle, a region is bitmap-based and can include multiple rectangles - this means we can register all our wall rects into a single region, and then simply check if a point is inside this region to see if it is inside a wall!! Genious!
JASS:
library Missiles initializer Init requires ListModule
globals
private constant real INTERVAL = 0.03
private constant integer DUMMY_ID = 'dumm' //THE UNIT ID OF THE DUMMY UNIT. YOU CAN ALSO ADD THIS AS A PARAMETER TO THE FUNCTION IF YOU WANT TO USE SEVERAL DUMMIES.
private timer UpdateTimer
private region Walls
endglobals
struct missile
implement List //THIS ADDS THE LIST MEMBERS INTO OUR STRUCT
unit dummy
effect sfx
real x = 0
real y = 0
real vel_x = 0
real vel_y = 0
static method update takes nothing returns nothing
local missile this = .first //RETRIEVES THE FIRST INSTANCE IN THE LIST.
local missile temp
if .count == 0 then //THIS MEMBER BECOMES INCLUDED WHEN YOU USE THE LINKED LIST LIBRARY
call PauseTimer(GetExpiredTimer()) //PAUSE IF THERE ARE NO MISSILES
else
loop
exitwhen this == 0 //EXIT WHEN THERE IS NO NEXT INSTANCE
set temp = .next //FOR SAFETY, SINCE WE ARE REMOVING INSTANCES WITHIN THE LOOP
set .x = .x + .vel_x //ADD VELOCITY TO POSITION
set .y = .y + .vel_y
call SetUnitX(.dummy, .x) //UPDATE THE POSITION OF THE DUMMY
call SetUnitY(.dummy, .y)
if IsPointInRegion(Walls, .x, .y) then //IF THE MISSILE IS INSIDE A WALL, DESTROY IT!
call .destroy()
endif
set this = temp //SET 'THIS' TO BE THE NEXT INSTANCE IN THE LIST
endloop
endif
endmethod
static method create takes real x, real y, real facing, real velocity, player owner returns missile
local missile this = missile.allocate() //THIS CREATES A NEW INDEX FOR THE STRUCT INSTANCE
call .listAdd() //THIS REGISTERS THAT WE HAVE A NEW LINK IN THE CHAIN
set .dummy = CreateUnit(owner, DUMMY_ID, x, y, facing)
set .x = x
set .y = y
set .vel_x = velocity * Cos(angle*bj_DEGTORAD) //THIS IS THE SAME AS POLAR PROJECTION
set .vel_y = velocity * Sin(angle*bj_DEGTORAD)
if .count == 1 then //IF THERE USED TO BE NO INSTANCES, WE NEED TO START THE TIMER AGAIN
call TimerStart(UpdateTimer, INTERVAL, true, function missile.update)
endif
return this
endmethod
method onDestroy takes nothing returns nothing
call RemoveUnit(.dummy) //REMOVE THE DUMMY UNIT
if .sfx != null then
call DestroyEffect(.sfx) //DESTROY ANY EFFECTS
endif
call .listRemove() //MAKE SURE THIS INSTANCE IS NO LONGER A PART OF THE CHAIN
endmethod
endstruct
private function Init takes nothing returns nothing
set UpdateTimer = CreateTimer()
set Walls = CreateRegion()
call RegionAddRect(Walls, gg_rct_Wall1) //ADD ALL YOUR WALLS LIKE THIS
call RegionAddRect(Walls, gg_rct_Wall2)
call RegionAddRect(Walls, gg_rct_Wall3)
//....
endfunction
endlibrary
Next is to deal damage to any enemies we encounter. You will find that we now need some more data, like what type of missile we are dealing with, and how much damage it does. For this, we can simply create a "missiletype" struct to contain this information!
Now we have a code which looks something like this:
JASS:
library Missiles initializer Init requires ListModule
globals
private constant real INTERVAL = 0.03
private constant real RADIUS = 100 //THE RADIUS OF COLLISION
private constant integer FIGHTER_ID = 'figh' //UNIT ID OF THE PLAYER UNIT
private timer UpdateTimer
private region Walls
missiletype Spear
missiletype Dagger
endglobals
struct missiletype
real damage = 10 //IN THIS CASE, 10 IS JUST A DEFAULT VALUE. I JUST WANTED TO DEMONSTRATE THAT YOU CAN ASSIGN DEFAULT VALUES.
real max_age = 5 //NUMBER OF SECONDS BEFORE THE MISSILE IS AUTOMATICALLY DESTROYED, THE 'RANGE' OF THE MISSILE
real velocity //I MOVED THIS VARIABLE HERE SINCE IT MADE MORE SENSE
integer dummy_id //I ADDED THIS AS A MEMBER SINCE I KNOW YOU USE UNIQUE DUMMIES FOR EACH MISSILE TYPE
static method create takes real dmg, real lifetime, real id, real vel returns missiletype
local missiletype this = missiletype.allocate()
set .damage = dmg
set .max_age = lifetime
set .dummy_id = id
set .velocity = vel * INTERVAL //I MULTIPLY IT BY THE LOOP RATE SO THAT YOU CAN USE REGULAR WARCRAFT SPEEDS FOR THIS FIELD
return this
endmethod
endstruct
struct missile
implement List //THIS ADDS THE LIST MEMBERS INTO OUR STRUCT
missiletype class //WE NEED TO STORE THE MISSILETYPE INSIDE THE MISSILE
unit dummy
effect sfx
real age = 0 //THIS IS THE EXPIRATION TIME OF THE MISSILE
real x = 0
real y = 0
real vel_x = 0
real vel_y = 0
static method targetFilter takes nothing returns boolean
//HERE YOU PUT THE CONDITIONS FOR WHICH TARGETS ARE ALLOWED.
return GetUnitTypeId(GetFilterUnit()) == FIGHTER_ID //RETURNS TRUE IF UNIT IS A FIGHTER, FALSE IF NOT.
endmethod
method hit takes unit u returns nothing
call UnitDamageTarget(.dummy, u, .class.damage, true, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
//ADD ANY EFFECTS YOU NEED
call .destroy()
endmethod
static method update takes nothing returns nothing
local missile this = .first //RETRIEVES THE FIRST INSTANCE IN THE LIST.
local missile temp
local group g = CreateGroup()
if .count == 0 then //THIS MEMBER BECOMES INCLUDED WHEN YOU USE THE LINKED LIST LIBRARY
call PauseTimer(GetExpiredTimer()) //PAUSE IF THERE ARE NO MISSILES
else
loop
exitwhen this == 0 //EXIT WHEN THERE IS NO NEXT INSTANCE
set temp = .next //FOR SAFETY, SINCE WE ARE REMOVING INSTANCES WITHIN THE LOOP
set .x = .x + .vel_x //ADD VELOCITY TO POSITION
set .y = .y + .vel_y
call SetUnitX(.dummy, .x) //UPDATE THE POSITION OF THE DUMMY
call SetUnitY(.dummy, .y)
set .age = .age - INTERVAL //IF THE LOOP RUNS EVERY 1/30 SECONDS, WE SUBSTRACT 1/30 FROM THE AGE EVERY LOOP
if .age < 0 then
call .destroy() //DESTROY IT IF TIME RUNS OUT
endif
if IsPointInRegion(Walls, .x, .y) then //IF THE MISSILE IS INSIDE A WALL, DESTROY IT!
call .destroy()
endif
call GroupEnumUnitsInRange(g, .x, .y, RADIUS, Filter(function missile.targetFilter)) //GROUP ALL UNITS WITHIN THE RADIUS
if FirstOfGroup(g) != null then //IF THE GROUP IS NOT EMPTY
call .hit(FirstOfGroup(g)) //CALL THE DAMAGE METHOD ON THE FIRST UNIT WE HIT
endif
call GroupClear(g) //EMPTY THE GROUP, FOR NEXT LOOP.
set this = temp //SET 'THIS' TO BE THE NEXT INSTANCE IN THE LIST
endloop
endif
call DestroyGroup(g) //CLEAN GROUP LEAK
set g = null //YOU NEED TO NULL THE HANDLES OF LOCAL VARIABLES, OTHERWISE THEY LEAK
endmethod
static method create takes missiletype m, real x, real y, real facing, player owner returns missile
local missile this = missile.allocate() //THIS CREATES A NEW INDEX FOR THE STRUCT INSTANCE
call .listAdd() //THIS REGISTERS THAT WE HAVE A NEW LINK IN THE CHAIN
set .dummy = CreateUnit(owner, DUMMY_ID, x, y, facing)
set .class = m
set .age = .class.max_age
set .x = x
set .y = y
set .vel_x = .class.velocity * Cos(angle*bj_DEGTORAD) //THIS IS THE SAME AS POLAR PROJECTION
set .vel_y = .class.velocity * Sin(angle*bj_DEGTORAD)
if .count == 1 then //IF THERE USED TO BE NO INSTANCES, WE NEED TO START THE TIMER AGAIN
call TimerStart(UpdateTimer, INTERVAL, true, function missile.update)
endif
return this
endmethod
method onDestroy takes nothing returns nothing
call RemoveUnit(.dummy) //REMOVE THE DUMMY UNIT
if .sfx != null then
call DestroyEffect(.sfx) //DESTROY ANY EFFECTS
endif
call .listRemove() //MAKE SURE THIS INSTANCE IS NO LONGER A PART OF THE CHAIN
endmethod
endstruct
private function Init takes nothing returns nothing
set UpdateTimer = CreateTimer()
set Walls = CreateRegion()
set Spear = missiletype.create(18, 2.5, 'dagg', 900)
set Dagger = missiletype.create(10, 1.8, 'spea', 900)
call RegionAddRect(Walls, gg_rct_Wall1) //ADD ALL YOUR WALLS LIKE THIS
call RegionAddRect(Walls, gg_rct_Wall2)
call RegionAddRect(Walls, gg_rct_Wall3)
//....
endfunction
endlibrary
To create a missile, you now only need to call this function:
JASS:
//PUT THIS STUFF INSIDE A TRIGGER THAT FIRES WHEN A UNIT CASTS THE SPEAR SPELL:
local unit u = GetTriggerUnit()
call missile.create(Spear, GetUnitX(u), GetUnitY(u),GetUnitFacing(u), GetOwningPlayer(u))
set u = null
There are ofcourse still many things you could include which i will not explain here. For instance, it is not hard to make projectiles bounce, or to add air friction (simply multiply vel_x and vel_y with 0.98 in the update loop), or to adjust the height of the missile - right now it has a constant height which might look weird if you are shooting it off a cliff. Adding gravity is just as simple as adding a vel_z member and substracting it with a constant number each update loop. You are free to ask any questions about these things, but i thought you might want to get more into jass before we dig any deeper.
Good luck!