• 🏆 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!

Moving projectiles - first timer

Status
Not open for further replies.
Level 6
Joined
Feb 6, 2008
Messages
166
I have a couple questions, since I haven't done this before:

How do people generally go about moving projectiles for MUI spells?

Right now, I'm using a hashtable to store the location of the caster, as well as the location's X and Y coordinates and a time value. Then it starts a periodic trigger that increments the time (by 0.03 seconds), and then sets the projectile's X and Y coordinates equal to the caster's X and Y plus the results from the parametric space-curves.

Long story short, the four projectiles move like this:
xprojectile,1=600tcos(6tpi)+xcaster
yprojectile,1=600tsin(6tpi)+ycaster

xprojectile,2=600tcos(6tpi + pi/2)+xcaster
yprojectile,2=600tsin(6tpi + pi/2)+ycaster

xprojectile,3=-600tcos(6tpi)+xcaster
yprojectile,3=-600tsin(6tpi)+ycaster

xprojectile,4=600tcos(6tpi + pi/2)+xcaster
yprojectile,4=600tsin(6tpi + pi/2)+ycaster


I would also have to use tangents to set their facing angle parallel to the direction of travel.



Is there a better way to do this? The part of me studying for an applied math minor is saying "No, this is how it's done," while the computer science major in me is saying, "There's clearly a better way to code it. You're just not seeing it."



About the spell itself, the caster will cast a "Channel" dummy skill, Instant (No Target), and a trigger will detect the start of its "empty" effect. The trigger will release four projectiles, which will sweep around for graphics purposes. Periodically, another trigger will find all valid targets within a gradually increasing radius not in the "already affected" unit group. This will prompt a trigger-casted, no-cast-time, no-cooldown, no-manacost, invisible Shadow Strike to be casted at each unit, and then add them to the "already affected" unit group.

What this will look like, is a caster that casts a spell in a similar manner as War Stomp or Fan of Knives. Instead, four projectiles will spiral outward in a counter-clockwise direction. At the same time, all enemy units within 600 range will be hit for initial damage and damage over time, starting from the closest units and ending with the furthest units.



Overall, what I'm asking is:
1. I'm new to using triggers to move projectile dummy units. Is this how people generally do it?
2. If so, did I do it right?
3. Is what I did an appropriate solution for the spell I had in mind?
 
Level 14
Joined
Apr 20, 2009
Messages
1,543
In what kind of triggering are you experienced?

Because in Jass there are some natives like these, which you can use to make the equation:

JASS:
Jass Math Natives:

native Deg2Rad takes real degrees returns real
native Rad2Deg takes real radians returns real
native Sin takes real radians returns real
function SinBJ takes real degrees returns real
    return Sin(degrees * bj_DEGTORAD)
endfunction
native Cos takes real radians returns real
function CosBJ takes real degrees returns real
    return Cos(degrees * bj_DEGTORAD)
endfunction
native Tan takes real radians returns real
function TanBJ takes real degrees returns real
    return Tan(degrees * bj_DEGTORAD)
endfunction
native Asin takes real y returns real
function AsinBJ takes real degrees returns real
    return Asin(degrees) * bj_RADTODEG
endfunction
native Acos takes real x returns real
function AcosBJ takes real degrees returns real
    return Acos(degrees) * bj_RADTODEG
endfunction
native Atan takes real x returns real
function AtanBJ takes real degrees returns real
    return Atan(degrees) * bj_RADTODEG
endfunction
native Atan2 takes real y, real x returns real
function Atan2BJ takes real y, real x returns real
    return Atan2(y, x) * bj_RADTODEG
endfunction
native SquareRoot takes real x returns real
native Pow takes real x, real power returns real
function ModuloInteger takes integer dividend, integer divisor returns integer
    local integer modulus = dividend - (dividend / divisor) * divisor

    // If the dividend was negative, the above modulus calculation will
    // be negative, but within (-divisor..0).  We can add (divisor) to
    // shift this result into the desired range of (0..divisor).
    if (modulus < 0) then
        set modulus = modulus + divisor
    endif

    return modulus
endfunction
function ModuloReal takes real dividend, real divisor returns real
    local real modulus = dividend - I2R(R2I(dividend / divisor)) * divisor

    // If the dividend was negative, the above modulus calculation will
    // be negative, but within (-divisor..0).  We can add (divisor) to
    // shift this result into the desired range of (0..divisor).
    if (modulus < 0) then
        set modulus = modulus + divisor
    endif

    return modulus
endfunction

A lot of them can also be used in GUI. Create a integer or real variable and you'll see that it's possible to use these natives in GUI too.

  • Set Real = (Random real number between 0.00 and 1.00)
  • Set Real = (Random angle)
  • Set Real = (Random percentage)
  • Set Real = (Distance between point and point)
  • Set Real = (Angle from point to point)
  • Set Real = (Min(0.00, 1.00))
  • Set Real = (Max(0.00, 1.00))
  • Set Real = (Abs(1.00))
  • Set Real = (Sign(1.00))
  • Set Real = (1.00 mod 1.00)
  • Set Real = (Power(1.00, 1.00))
  • Set Real = (Square root(1.00))
  • Set Real = (Sin(1.00))
  • Set Real = (Cos(1.00))
  • Set Real = (Tan(1.00))
  • Set Real = (Asin(1.00))
  • Set Real = (Acos(1.00))
  • Set Real = (Atan(1.00))
  • Set Real = (Atan2(1.00, 0.00))
  • Set Integer = (Random integer number between 1 and 10)
  • Set Integer = (Min(0, 1))
  • Set Integer = (Max(0, 1))
  • Set Integer = (Abs(1))
  • Set Integer = (Sign(1))
  • Set Integer = (0 mod 1)
Now you should be abled to use your equation ;)

EDIT:
JASS:
local real array Xprojectile
local real array Ypojectile
local real Xcaster
local real Ycaster
local real t

set Xprojectile[0] = 600 * t * Cos(6 * t * 3.14159265358979323846264338327950288419716939937510582) + Xcaster
set Yprojectile[0] = 600 * t * Sin(6 * 6 * 3.14159265358979323846264338327950288419716939937510582) + Ycaster

set Xprojectile[1] = 600 * t * Cos(6 * t * 3.14159265358979323846264338327950288419716939937510582 + 3.14159265358979323846264338327950288419716939937510582 / 2) + Xcaster
set Yprojectile[1] = 600 * t * Cos(6 * t * 3.14159265358979323846264338327950288419716939937510582 + 3.14159265358979323846264338327950288419716939937510582 / 2) + Ycaster

set Xprojectile[2] = -600 * t * Cos(6 * t * 3.14159265358979323846264338327950288419716939937510582) + Xcaster
set Xprojectile[2] = -600 * t * Sin(6 * t * 3.14159265358979323846264338327950288419716939937510582) + Xcaster

set Xprojectile[3] = -600 * t * Cos(6 * t * 3.14159265358979323846264338327950288419716939937510582 + 3.14159265358979323846264338327950288419716939937510582 / 2) + Xcaster
set Yprojectile[3] = -600 * t * Cos(6 * t * 3.14159265358979323846264338327950288419716939937510582 + 3.14159265358979323846264338327950288419716939937510582 / 2) + Ycaster


These are already set I assume?

JASS:
local real Xcaster
local real Ycaster
local real t

I won't bother posting the GUI version because it looks almost the same...

Not sure if I'm doing it right though xD Correct me if I'm wrong please ^.^
I used to be pretty good in math, but I haven't done math in 4 years...
Okay, maybe a little a few days ago. But that doesn't really count since I had help.
 
Last edited:
Level 6
Joined
Feb 6, 2008
Messages
166
Alright, I've been working ferociously on this, and I've slammed my face onto my pillow a few dozen times, but I think I got it.

  • nWrath Init Hash
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set nWrath_Hash = (Last created hashtable)
  • nWrath Start Effect
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Nature's Wrath
      • ((Triggering unit) is in nWrath_Caster_Group) Equal to False
    • Actions
      • Set nWrath_Temp_Loc = (Position of (Triggering unit))
      • Hashtable - Save Handle OfnWrath_Temp_Loc as (Key origin) of (Key (Triggering unit)) in nWrath_Hash
      • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc)
      • Hashtable - Save 0.00 as (Key tValue) of (Key (Triggering unit)) in nWrath_Hash
      • Unit - Create 1 Nature's Wrath for (Triggering player) at nWrath_Temp_Loc facing 0.00 degrees
      • Hashtable - Save Handle Of(Last created unit) as (Key sCloud) of (Key (Triggering unit)) in nWrath_Hash
      • Unit - Create 1 Nature's Wrath for (Triggering player) at nWrath_Temp_Loc facing 90.00 degrees
      • Hashtable - Save Handle Of(Last created unit) as (Key eCloud) of (Key (Triggering unit)) in nWrath_Hash
      • Unit - Create 1 Nature's Wrath for (Triggering player) at nWrath_Temp_Loc facing 180.00 degrees
      • Hashtable - Save Handle Of(Last created unit) as (Key nCloud) of (Key (Triggering unit)) in nWrath_Hash
      • Unit - Create 1 Nature's Wrath for (Triggering player) at nWrath_Temp_Loc facing 270.00 degrees
      • Hashtable - Save Handle Of(Last created unit) as (Key wCloud) of (Key (Triggering unit)) in nWrath_Hash
      • Unit Group - Add (Triggering unit) to nWrath_Caster_Group
      • Trigger - Turn on nWrath Loop <gen>
  • nWrath Loop
    • Events
      • Time - Every 0.03 seconds of game time
    • Conditions
    • Actions
      • -------- Progress all four clouds of each caster by one interval. --------
      • Unit Group - Pick every unit in nWrath_Caster_Group and do (Actions)
        • Loop - Actions
          • Set nWrath_Temp_Unit_1 = (Picked unit)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (nWrath_Temp_Unit_1 is alive) Equal to True
              • (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) Less than 1.00
            • Then - Actions
              • -------- Spell not finished, increment this unit's timer by 0.03 --------
              • -------- Remember, use degrees, not radians. 6pi rad = 1080 deg, pi/2 rad = 90 deg. --------
              • -------- Why can't I use the variable nWrath_Temp_Unit1 as a key handle? --------
              • Hashtable - Save ((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) + 0.03) as (Key tValue) of (Key (Picked unit)) in nWrath_Hash
              • -------- Move eCloud --------
              • -------- x=600tcos(6tpi) --------
              • -------- y=600tsin(6tpi) --------
              • Unit - Move (Load (Key eCloud) of (Key (Picked unit)) in nWrath_Hash) instantly to (Point(((X of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) + ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Cos(((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00))))),
                • ((Y of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) + ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Sin(((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00)))))))
              • -------- Move nCloud --------
              • -------- x=600tcos(6tpi + pi/2) --------
              • -------- y=600tsin(6tpi + pi/2) --------
              • Unit - Move (Load (Key nCloud) of (Key (Picked unit)) in nWrath_Hash) instantly to (Point(((X of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) + ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Cos((((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00) + 90.00))))),
                • ((Y of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) + ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Sin((((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00) + 90.00)))))))
              • -------- Move wCloud --------
              • -------- x=-600tcos(6tpi) --------
              • -------- y=-600tsin(6tpi) --------
              • Unit - Move (Load (Key wCloud) of (Key (Picked unit)) in nWrath_Hash) instantly to (Point(((X of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) - ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Cos(((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00))))),
                • ((Y of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) - ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Sin(((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00)))))))
              • -------- Move sCloud --------
              • -------- x=-600tcos(6tpi + pi/2) --------
              • -------- y=-600tsin(6tpi + pi/2) --------
              • Unit - Move (Load (Key sCloud) of (Key (Picked unit)) in nWrath_Hash) instantly to (Point(((X of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) - ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Cos((((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00) + 90.00))))),
                • ((Y of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) - ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Sin((((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00) + 90.00)))))))
            • Else - Actions
              • -------- tValue should = 1.02, spell is finished --------
              • Unit - Remove (Load (Key eCloud) of (Key (Picked unit)) in nWrath_Hash) from the game
              • Unit - Remove (Load (Key nCloud) of (Key (Picked unit)) in nWrath_Hash) from the game
              • Unit - Remove (Load (Key wCloud) of (Key (Picked unit)) in nWrath_Hash) from the game
              • Unit - Remove (Load (Key sCloud) of (Key (Picked unit)) in nWrath_Hash) from the game
              • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in nWrath_Hash
              • Unit Group - Remove (Picked unit) from nWrath_Caster_Group
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Number of units in nWrath_Caster_Group) Equal to 0
        • Then - Actions
          • Trigger - Turn on nWrath Loop <gen>
        • Else - Actions
Does it look pretty solid? Are there any leaks I should be concerned about? I'm pretty sure it's fully MUI.

I'm exhausted! x3


Edit: Lol! You editted your post while I was working. I actually have no experience with JASS at the moment; I tried to learn it a while back, got confused out of my mind, and now I'm busy with college classes. I'll try to learn it again when I have more time, and maybe my Java and C++ experience will help me pick it up...?

Edit 2: Turns out, the trigger editor butchers both the displaying and copying of very long triggers. I manually patched it up in the post.

Edit 3: I'm retarded or something. After a good half hour of testing, I found out that in the very first trigger, I cleaned up a memory leak and deleted the point. And then tried to use the point variable again just below it. Multiple times. Going to replace it with the string key 'origin' as the location handle. That should fix it.

Edit 4: There is still something horribly wrong with my GUI. This is what I have right now, including debugging outputs. (Must resist the urge to call them debugging 'println's or 'cout's...) Everything checks out, as far as I can tell, but the triggers just won't create the 'projectile' units.
  • nWrath Start Effect
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Nature's Wrath
      • ((Triggering unit) is in nWrath_Caster_Group) Equal to False
    • Actions
      • Game - Display to (All players) the text: (Triggering Unit: + (String((Unit-type of (Triggering unit)))))
      • Game - Display to (All players) the text: (Casting Unit: + (String((Unit-type of (Casting unit)))))
      • Set nWrath_Temp_Loc = (Position of (Triggering unit))
      • Game - Display to (All players) the text: (Temp Loc: ( + ((String((X of nWrath_Temp_Loc))) + (, + ((String((Y of nWrath_Temp_Loc))) + )))))
      • Hashtable - Save Handle OfnWrath_Temp_Loc as (Key origin) of (Key (Triggering unit)) in nWrath_Hash
      • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc)
      • Hashtable - Save 0.00 as (Key tValue) of (Key (Triggering unit)) in nWrath_Hash
      • Game - Display to (All players) the text: (tValue = + (String((Load (Key tValue) of (Key (Triggering unit)) from nWrath_Hash))))
      • Game - Display to (All players) the text: (Triggering Player: + (String((Player number of (Triggering player)))))
      • Unit - Create 1 Nature's Wrath for (Triggering player) at (Load (Key origin) of (Key (Triggering unit)) in nWrath_Hash) facing 0.00 degrees
      • Hashtable - Save Handle Of(Last created unit) as (Key sCloud) of (Key (Triggering unit)) in nWrath_Hash
      • Unit - Create 1 Nature's Wrath for (Triggering player) at (Load (Key origin) of (Key (Triggering unit)) in nWrath_Hash) facing 90.00 degrees
      • Hashtable - Save Handle Of(Last created unit) as (Key eCloud) of (Key (Triggering unit)) in nWrath_Hash
      • Unit - Create 1 Nature's Wrath for (Triggering player) at (Load (Key origin) of (Key (Triggering unit)) in nWrath_Hash) facing 180.00 degrees
      • Hashtable - Save Handle Of(Last created unit) as (Key nCloud) of (Key (Triggering unit)) in nWrath_Hash
      • Unit - Create 1 Nature's Wrath for (Triggering player) at (Load (Key origin) of (Key (Triggering unit)) in nWrath_Hash) facing 270.00 degrees
      • Hashtable - Save Handle Of(Last created unit) as (Key wCloud) of (Key (Triggering unit)) in nWrath_Hash
      • Unit Group - Add (Triggering unit) to nWrath_Caster_Group
      • Trigger - Turn on nWrath Loop <gen>
 
Last edited:
Level 14
Joined
Apr 20, 2009
Messages
1,543
Well, you could do something like this to check if the unit really is created or not:

  • Unit - Create 1 Nature's Wrath for (Triggering player) at nWrath_Temp_Loc facing 270.00 degrees
  • Custom script: call DisplayTextToPlayer(Player(0), 0, 0, I2S(GetHandleId(bj_lastCreatedUnit)))
This will display the unique id of the handle, which in this case is the last created unit. If it displays 0 the unit is not created. If it displays any other unique integer number, the unit is created because it displays the unique ID of the unit.

In addition here is some usefull Jass refference:
JASS:
function CreateNUnitsAtLoc takes integer count, integer unitId, player whichPlayer, location loc, real face returns group
    call GroupClear(bj_lastCreatedGroup)
    loop
        set count = count - 1
        exitwhen count < 0
        call CreateUnitAtLocSaveLast(whichPlayer, unitId, loc, face)
        call GroupAddUnit(bj_lastCreatedGroup, bj_lastCreatedUnit)
    endloop
    return bj_lastCreatedGroup
endfunction

This is basically what happens in GUI when you use:
  • Unit - Create 1 Nature's Wrath for (Triggering player) at nWrath_Temp_Loc facing 270.00 degrees
Example:
JASS:
call CreateNUnitsAtLoc(1, 'hfoo', GetTriggerPlayer(), udg_nWrath_Temp_Loc, 270.00)
Where 'hfoo' is the raw ID of the unit, I used footman as an example. Press control + D inside the object editor to see raw codes. In GUI the full name of the unit is displayed instead of the raw code.

And if you look at this line of code:
JASS:
call CreateUnitAtLocSaveLast(whichPlayer, unitId, loc, face)

You'll notice that it does this:
JASS:
function CreateUnitAtLocSaveLast takes player id, integer unitid, location loc, real face returns unit
    if (unitid == 'ugol') then
        set bj_lastCreatedUnit = CreateBlightedGoldmine(id, GetLocationX(loc), GetLocationY(loc), face)
    else
        set bj_lastCreatedUnit = CreateUnitAtLoc(id, unitid, loc, face)
    endif

    return bj_lastCreatedUnit
endfunction

Which means bj_lastCreatedUnit is now set to a newly created unit.
Which in GUI is displayed as: (Last created unit).
If anything goes wrong during creation then the unit will not be returned from CreateUnitAtLoc and bj_lastCreatedUnit will be set to null (empty).
Jass Refference:
JASS:
native CreateUnitAtLoc takes player id, integer unitid, location whichLocation, real face returns unit
When something goes wrong here CreateUnitAtLoc will return null instead of the unit it was supposed to create.

Now with this line of code:
JASS:
call DisplayTextToPlayer(Player(0), 0, 0, I2S(GetHandleId(bj_lastCreatedUnit)))

You basically display the unique ID of the (Last created unit).
If something went wrong during creation of the unit, which in fact is highly unlikely but possible. Then bj_lastCreatedUnit will be null and the unique ID will return 0 since it can't get the ID of a null variable.

I hope you've understood my explenation.
Just check if it works ;)
 
Last edited:
Level 6
Joined
Feb 6, 2008
Messages
166
Don't do this right after you store the loc into memory, since this destroyes the object:
Custom script: call RemoveLocation(udg_nWrath_Temp_Loc)
Remove the loc when the spell ends.

Move unit instantly leaks point (location).

Ahh, Maker! Coming to the rescue yet again!

If nWrath_Temp_Loc isn't removed until the spell ends, wouldn't that make it not MUI? As far as I've been told, temporary variables need to be used right away and then cleared/removed right away as one of the requirements for MUI. Plus, in my revised code, I no longer use nWrath_Temp_Loc except to store the point in the hashtable. Or does that store the variable in the hashtable?

This is going to be very frustrating for much longer. Do I need to set a temporary location variable equal to a point, then move the unit to that point?


Also, if either of you are planning to help me out in JASS, I should say this: Even with my Java and C++ background, JASS looks like a foreign language to me. I'd need to blindly copy-paste chunks of code, and I don't really like doing that because it feels like I didn't contribute anything. I suppose if JASS shortens the work by a lot, I might, but I wouldn't really be able to debug it if anything went wrong.

Hashjie, if I make any amount of sense saying this, I understand your explanations, but I still don't understand the JASS code you're explaining. x.x;

This makes me wish there were "classes" on JASS...


((When I make edits to the code, should I put it in my recent post, original post, or both?))
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
If nWrath_Temp_Loc isn't removed until the spell ends, wouldn't that make it not MUI? As far as I've been told, temporary variables need to be used right away and then cleared/removed right away as one of the requirements for MUI. Plus, in my revised code, I no longer use nWrath_Temp_Loc except to store the point in the hashtable. Or does that store the variable in the hashtable?

Variables hold the address to the object in memory.

Set loc = position of unit
^There the position is the object located somewhere in memory. The loc variable holds information where the address is.

Set loc = position of unit 1
Set loc = position of unit 2
^Now the position of unit 1 still remains in memory, but we don't know where it is as loc variable now points to position of unit 2 object.

Set loc = position of unit 1
Save handle of loc into hashtable
set loc = position of unit 2
^This is ok since we still can retrieve the address of position of unit 1 from the hashtable.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
It removes the object in memory. The variable still has the "address" to the memory space that held the object.

Globals don't need to be nulled since they will be used again. Locals on the other hand are not reused and they will point to some object unless nulled.
 
Level 14
Joined
Apr 20, 2009
Messages
1,543
Hashjie, if I make any amount of sense saying this, I understand your explanations, but I still don't understand the JASS code you're explaining. x.x;

  • Custom script: call DisplayTextToPlayer(Player(0), 0, 0, I2S(GetHandleId(bj_lastCreatedUnit)))
This simply displays the unique ID that is assigned to an instance when it is created.
(a object that can be anything except integers, strings, booleans and reals)
In this case: last created unit.

If the unit doesn't get created for some reason, it does not get a unique id. Thus displaying 0. If it is created it should show a unique number given to that unit. This way you can debug the creation of the unit.

JASS:
call DisplayTextToPlayer(Player(0), 0, 0, I2S(GetHandleId(bj_lastCreatedUnit)))
basically is the same as

  • Game - Display to Player 1 (Red) the text: the unique ID of the unit (which is impossible in GUI)
This makes me wish there were "classes" on JASS...
That's why vJass was invented. To make things object orientated ;)

Take a look in its documentation if you're interested in learning vJass:
http://www.wc3c.net/vexorian/jasshelpermanual.html

It might be usefull to understand Jass before learning vJass...

Even with my Java and C++ background, JASS looks like a foreign language to me.
I have the same area of expertise, after downloading JassNewGenPackage and playing around by converting triggers to custom text + using the function list it seemed quite easy.
Then after reading tutorials it becomes even more clear... ;)

And ofcourse, learning Jass allows you to fully understand GUI since you then know how the underlying code works... Just saying ^.^
And the fact that a lot of things can not be done in GUI but can be done in Jass. Which gives you more controll over what you want to accomplish.
Once you get a hang of it, you'll think by yourself: dear god, it looks like GUI yet I have more controll over what I do ^.^
 
Level 6
Joined
Feb 6, 2008
Messages
166
Maker:
So, I really shouldn't use
  • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc)
at all, until the code reaches
  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • (Number of units in nWrath_Caster_Group) Equal to 0
    • Then - Actions
      • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc)
      • Trigger - Turn off (This trigger)
    • Else - Actions
Is that what you meant before by "Remove the loc when the spell ends."? Wait until there's no one currently casting the spell?

Hashjie:
I just thought something.
JASS:
function function_name takes parameter_type parameter returns return_type
Is this on the right track? I think now that I have college comp sci classes under my belt, I just might have a shot at learning JASS/vJASS.
 
Level 14
Joined
Apr 20, 2009
Messages
1,543
Maker:
So, I really shouldn't use
  • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc)
at all, until the code reaches
  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • (Number of units in nWrath_Caster_Group) Equal to 0
    • Then - Actions
      • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc)
      • Trigger - Turn off (This trigger)
    • Else - Actions
Is that what you meant before by "Remove the loc when the spell ends."? Wait until there's no one currently casting the spell?

You do know that a variable of type location is stored inside your RAM memory?
And that when using call RemoveLocation you will remove that location in your RAM memory.
Which means that the variable that contained the pointer to the location inside your RAM
memory where the location was stored, will point to a location in memory that doesn't exist.
Since the location inside the RAM memory is destroyed.

Therefore you can not use the variable after you've destroyed the location in your memory any more.

Example:
  • Set TempLoc = (Position of (Triggering unit))
  • Unit - Create 1 Unit for (Triggering player) at TempLoc facing 0.00 degrees
  • Custom script: call RemoveLocation(udg_TempLoc)
  • -------- Now the location that was stored inside your memory is destroyed so you can not use the variable TempLoc since it points to a location inside your RAM memory that is empty. --------
  • -------- So if you do this it will not work: --------
  • Unit - Create 1 Unit for (Triggering player) at TempLoc facing 0.00 degrees

Hashjie:
I just thought something.
JASS:
function function_name takes parameter_type parameter returns return_type
Is this on the right track? I think now that I have college comp sci classes under my belt, I just might have a shot at learning JASS/vJASS.

Now you're starting to understand ;)
a function takes parameters which you can specify, and returns a variable type.

So if you wrote this:
JASS:
function GiveUnit takes nothing returns unit
local unit u = CreateUnit(Player(0), 'hfoo', 0, 0, 0) //Here I create a local variable u. A local variable only exists within the function it is declared in. You can not alter nor retrieve any local variables outside its parent function. This local variable contains a unit which is created for player 1 (because in jass the index starts with 0). 'hfoo' is the raw code of a footman unit, which you can see when you press control + D inside the object editor. The rest should be clear in the example underneath.
return u //here we return the unit that was created inside the function
endfunction

Then you can use a variable to store the unit that was returned in the function. For example:
JASS:
set udg_myVariable = GiveUnit()
Now udg_myVariable contains the unit returned from the GiveUnit() function.

Here is the example I mentioned earlyer:
JASS:
native CreateUnit takes player id, integer unitid, real x, real y, real face returns unit

A native is a function that was build into the editor by blizzard. CreateUnit is one of these natives.
Here you can easilly see what this function does, which parameters you should use and what it returns.

If you download JassNewGenPack over here
You will get some usefull extenstions for Jass.
Including features such as:
Syntax color highlighting.
A function list that contains all the blizzard natives and more...
It's a real WIN to use JNGP :D

You'll also get a lot of extensions that can for example:
Give you the ability to add 28 texture space (terrain tiles) instead of 16.
You can also merge object data, trigger data, constants.
You can customize your tile pathability.

Just start using it and you'll find out lots of the nifty features added to the editor with JNGP ^.^

I hope I've explained this thoroughly enough.
Tell me if there are parts which you might not understand and I'll be happy to explain them step by step.

EDIT: Oops, it was never my intention to make this double post...
Please remove this post I accidentally clicked submit instead of edit.
I'm way to stoned 0.o

Either ways, now that I made this post. I wanted to say that this might be usefull to you ;)
 
Last edited by a moderator:
Level 37
Joined
Mar 6, 2006
Messages
9,240
Maker:
So, I really shouldn't use
  • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc)
at all, until the code reaches
  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • (Number of units in nWrath_Caster_Group) Equal to 0
    • Then - Actions
      • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc)
      • Trigger - Turn off (This trigger)
    • Else - Actions
Is that what you meant before by "Remove the loc when the spell ends."? Wait until there's no one currently casting the spell?

Trigger 1
Events
Unit starts the effect
Actions
Add triggering unit into group
Set loc = Target point of ability being cast
Save handle of loc into hashtable
*don't use RemoveLocation here*

Trigger 2
Events
Every 0.03 seconds
Actions
Pick all units in group
if unit is alive then
-create special effect at load loc handle from hashtable
else
-set loc = load loc handle
-call RemoveLocaton(loc) // or just RemoveLocation(load loc handle)
-remove unit from group
-flush child hashtable
-if group is empty then
--turn off trigger
-endif
endif
 
Level 6
Joined
Feb 6, 2008
Messages
166
In the pseudo-code you wrote, wouldn't RemoveLocaton(loc) interfere with MUI? If one hero starts casting, loc will be assigned to his coordinates (and then saved to the hashtable). While his spell is in progress, if an enemy begins to cast that same spell with their own hero, loc will be set to that caster's coordinates. When the first caster's spell ends, RemoveLocation(loc) would kill off loc, which is currently the most-recent-caster's location. RemoveLocation(load loc handle) should work, if you can show me the syntax for hashtable handles in RemoveLocation(). I'm guessing it's slightly different from the usual since it's not merely a variable name.

Also, if you don't mind me asking, in RemoveLocation(udg_variableName), what does udg stand for? I find that it helps me learn programming when I know what abbreviations and acronyms stand for.
 
Level 17
Joined
Nov 13, 2006
Messages
1,814
In the pseudo-code you wrote, wouldn't RemoveLocaton(loc) interfere with MUI? If one hero starts casting, loc will be assigned to his coordinates (and then saved to the hashtable). While his spell is in progress, if an enemy begins to cast that same spell with their own hero, loc will be set to that caster's coordinates. When the first caster's spell ends, RemoveLocation(loc) would kill off loc, which is currently the most-recent-caster's location. RemoveLocation(load loc handle) should work, if you can show me the syntax for hashtable handles in RemoveLocation(). I'm guessing it's slightly different from the usual since it's not merely a variable name.

Also, if you don't mind me asking, in RemoveLocation(udg_variableName), what does udg stand for? I find that it helps me learn programming when I know what abbreviations and acronyms stand for.

what about this?

  • Add unit
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Flame Strike
    • Actions
      • Set IndexInteger = (IndexInteger + 1)
      • Set UnitArray[IndexInteger] = (Triggering unit)
      • Set PointVar[IndexInteger] = (Target point of ability being cast)
  • Periodic trigger
    • Events
      • Time - Every 0.03 seconds of game time
    • Conditions
    • Actions
      • For each (Integer A) from 1 to IndexInteger, do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (UnitArray[(Integer A)] is alive) Equal to True
            • Then - Actions
              • Special Effect - Create a special effect at PointVar[(Integer A)] using Abilities\Spells\Other\Monsoon\MonsoonBoltTarget.mdl
              • Special Effect - Destroy (Last created special effect)
            • Else - Actions
              • Custom script: call RemoveLocation(udg_PointVar[GetForLoopIndexA()])
              • Custom script: set udg_UnitArray[GetForLoopIndexA()]=null
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Integer A) Not equal to IndexInteger
                • Then - Actions
                  • Set PointVar[(Integer A)] = PointVar[IndexInteger]
                  • Custom script: call RemoveLocation(udg_PointVar[udg_IndexInteger])
                  • Set UnitArray[(Integer A)] = UnitArray[IndexInteger]
                • Else - Actions
              • Set IndexInteger = (IndexInteger - 1)
 

Attachments

  • mui.w3x
    17.5 KB · Views: 61
Level 6
Joined
Feb 6, 2008
Messages
166
Thanks, but RemoveLocation(udg_array[udg_index]) is similar to what Maker suggested. I'm still going to stick to using RemoveLocation(udg_loadLocHandle) if I can though, because all the data is already in hashtable format. It saves me from needing to update an array to replicate the hashtable's data.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
In the pseudo-code you wrote, wouldn't RemoveLocaton(loc) interfere with MUI?

No, the pseudo code is MUI.

Also, if you don't mind me asking...what does udg stand for?

User Defined Globals

udg_ prefix is added to all variable names created in the GUI variable editor. The prefix wonät show up in pure GUI, but it must be used in JASS.
 
Level 6
Joined
Feb 6, 2008
Messages
166
Thanks, Maker, but I still seem to be having problems. I've had to change my triggers from move actions to create unit with expiration timer actions (because the model for the effects are clouds of smoke that don't show up enough).

Currently, my triggers are:
  • nWrath Start Effect
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Nature's Wrath
      • ((Triggering unit) is in nWrath_Caster_Group) Equal to False
    • Actions
      • Set nWrath_Temp_Loc_1 = (Position of (Triggering unit))
      • Hashtable - Save Handle OfnWrath_Temp_Loc_1 as (Key origin) of (Key (Triggering unit)) in nWrath_Hash
      • Hashtable - Save 0.00 as (Key tValue) of (Key (Triggering unit)) in nWrath_Hash
      • Unit - Create 1 Nature's Wrath for (Triggering player) at (Load (Key origin) of (Key (Triggering unit)) in nWrath_Hash) facing Default building facing degrees
      • Unit - Add a 0.50 second Generic expiration timer to (Last created unit)
      • Unit Group - Add (Triggering unit) to nWrath_Caster_Group
      • Trigger - Turn on nWrath Loop <gen>
  • nWrath Loop
    • Events
      • Time - Every 0.03 seconds of game time
    • Conditions
    • Actions
      • -------- Progress all four clouds of each caster by one interval. --------
      • Unit Group - Pick every unit in nWrath_Caster_Group and do (Actions)
        • Loop - Actions
          • Set nWrath_Temp_Unit_1 = (Picked unit)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (nWrath_Temp_Unit_1 is alive) Equal to True
              • (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) Less than 1.00
            • Then - Actions
              • -------- Spell not finished, increment this unit's timer by 0.03 --------
              • -------- Remember, use degrees, not radians. 6pi rad = 1080 deg, pi/2 rad = 90 deg. --------
              • Hashtable - Save ((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) + 0.03) as (Key tValue) of (Key (Picked unit)) in nWrath_Hash
              • -------- x=600tcos(6tpi) --------
              • -------- y=600tsin(6tpi) --------
              • Set nWrath_Temp_Loc_2 = (Point(((X of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) + ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Cos(((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00))))), ((Y of (Load (Key origin) of
              • Unit - Create 1 Nature's Wrath for (Owner of (Picked unit)) at nWrath_Temp_Loc_2 facing Default building facing degrees
              • Unit - Add a 0.50 second Generic expiration timer to (Last created unit)
              • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc_2)
              • -------- x=600tcos(6tpi + pi/2) --------
              • -------- y=600tsin(6tpi + pi/2) --------
              • Set nWrath_Temp_Loc_2 = (Point(((X of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) + ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Cos((((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00) + 90.00))))), ((Y of (Load (Key
              • Unit - Create 1 Nature's Wrath for (Owner of (Picked unit)) at nWrath_Temp_Loc_2 facing Default building facing degrees
              • Unit - Add a 0.50 second Generic expiration timer to (Last created unit)
              • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc_2)
              • -------- x=-600tcos(6tpi) --------
              • -------- y=-600tsin(6tpi) --------
              • Set nWrath_Temp_Loc_2 = (Point(((X of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) - ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Cos(((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00))))), ((Y of (Load (Key origin) of
              • Unit - Create 1 Nature's Wrath for (Owner of (Picked unit)) at nWrath_Temp_Loc_2 facing Default building facing degrees
              • Unit - Add a 0.50 second Generic expiration timer to (Last created unit)
              • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc_2)
              • -------- x=-600tcos(6tpi + pi/2) --------
              • -------- y=-600tsin(6tpi + pi/2) --------
              • Set nWrath_Temp_Loc_2 = (Point(((X of (Load (Key origin) of (Key (Picked unit)) in nWrath_Hash)) - ((600.00 x (Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash)) x (Cos((((Load (Key tValue) of (Key (Picked unit)) from nWrath_Hash) x 1080.00) + 90.00))))), ((Y of (Load (Key
              • Unit - Create 1 Nature's Wrath for (Owner of (Picked unit)) at nWrath_Temp_Loc_2 facing Default building facing degrees
              • Unit - Add a 0.50 second Generic expiration timer to (Last created unit)
              • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc_2)
            • Else - Actions
              • -------- tValue should = 1.02, spell is finished --------
              • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in nWrath_Hash
              • Unit Group - Remove (Picked unit) from nWrath_Caster_Group
              • Custom script: call RemoveLocation(udg_nWrath_Temp_Loc_1)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Number of units in nWrath_Caster_Group) Equal to 0
        • Then - Actions
          • Trigger - Turn off (This trigger)
        • Else - Actions
For the most part, the skill works great (I might decrease the expiration timer to expire faster).

The problem comes when two heroes cast the skill in sequence, ie:
- Hero 1 casts spell
- First set of poison clouds begin to spread
- Hero 2 casts spell slightly later
- Both sets of poison clouds spread
- Hero 1 finishes casting. First set of poison clouds stop spreading at maximum distance. Second set of poison clouds stop at the same time, and the full sweep is not reached.

Something about the first cast finishing interrupts and ends the second cast. I'm not sure yet why it is doing this.
 
Level 6
Joined
Feb 6, 2008
Messages
166
What's the syntax for hashtables inside JASS functions? I'm having trouble finding it in the tutorials.

Edit: Would something like this be what I'm supposed to use?
JASS:
RemoveLocation( StringHashBJ("origin"), GetHandleIdBJ(GetEnumUnit()), udg_nWrath_Hash )
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
BJ (Blizzard Jass?) functions are used in GUI. BJ functions are, in many cases, only wrapper functions for the native functions.

GUI
  • Dialog - Clear (Clicked dialog)
JASS
JASS:
function DialogClearBJ takes dialog whichDialog returns nothing
    call DialogClear(whichDialog)
endfunction

The GUI line calls a function that calls a function. It would be better to avoid the first function call and use the DialogClear function directly.
 
Level 14
Joined
Apr 20, 2009
Messages
1,543
Ah, thanks. I totally see why the tutorial calls GUI inefficient. >>;

Yes, in GUI a lot of BJ's are being called. It's better to prevent this since it's possible to call the function the BJ calls. In Jass, the less function calls you have, the faster your code is.
Here is a good example:

JASS:
function CreateNUnitsAtLoc takes integer count, integer unitId, player whichPlayer, location loc, real face returns group
    call GroupClear(bj_lastCreatedGroup)
    loop
        set count = count - 1
        exitwhen count < 0
        call CreateUnitAtLocSaveLast(whichPlayer, unitId, loc, face)
        call GroupAddUnit(bj_lastCreatedGroup, bj_lastCreatedUnit)
    endloop
    return bj_lastCreatedGroup
endfunction

This is the standard BJ used in GUI for creating units. This function will set the bj_lastCreatedUnit and bj_lastCreatedGroup variables. These can then be used for last created unit or last created group. However the BJ calls a lot of functions to accomplish just 1 thing: create a unit.

As you can see this BJ calls another BJ:

JASS:
function CreateUnitAtLocSaveLast takes player id, integer unitid, location loc, real face returns unit
    if (unitid == 'ugol') then
        set bj_lastCreatedUnit = CreateBlightedGoldmine(id, GetLocationX(loc), GetLocationY(loc), face)
    else
        set bj_lastCreatedUnit = CreateUnitAtLoc(id, unitid, loc, face)
    endif

    return bj_lastCreatedUnit
endfunction

Which basically sets bj_lastCreatedUnit to be the unit that is created.
So now we can simply do this in our script when we want to optimize it:

JASS:
set bj_lastCreatedUnit = CreateUnitAtLoc(Player(0), 'hfoo', loc, 0)

This will create a unit and sets it to be bj_lastCreatedUnit. Basically the same as above.
If you hate locations ^.^ you can also use:
JASS:
native CreateUnit takes player id, integer unitid, real x, real y, real face returns unit

Now we scaled down a function from 5 function calls to 1.

5 function calls:
JASS:
1 call GroupClear(bj_lastCreatedGroup)
2 call CreateUnitAtLocSaveLast(whichPlayer, unitId, loc, face)
3 call GroupAddUnit(bj_lastCreatedGroup, bj_lastCreatedUnit)
4 set bj_lastCreatedUnit = CreateBlightedGoldmine(id, GetLocationX(loc), GetLocationY(loc), face)
5 set bj_lastCreatedUnit = CreateUnitAtLoc(id, unitid, loc, face)

1 function call:
JASS:
set bj_lastCreatedUnit = CreateUnitAtLoc(id, unitid, loc, face)
 
Status
Not open for further replies.
Top