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

How Do I make a Drain Life Spell that allows you to Move while Casting?

Status
Not open for further replies.
Level 3
Joined
Aug 29, 2004
Messages
57
Hi guys, I've brought this problem from another forum where no one knows what's wrong with it. I'm pretty clueless at this point, I'll try pretty much anything, no matter how complex, JASS, GUI triggers, whatever works. Think you can solve this one?

http://www.thehelper.net/forums/showthread.php?t=75878

My current trigger to pull this off:

Connects caster and target with a custom lightning effect and drains health from target every 0.1 sec. Updates targetdistance (real) variable every iteration and moves lightning effect to new target and caster positions every iteration.
Code:
Moving Drain Life
    Events
        Unit - A unit Starts the effect of an ability
    Conditions
        (Ability being cast) Equal to Drain Beam (Slow Based)
    Actions
        Custom script:   call AddLightningLoc( "BEAM", GetUnitLoc(GetSpellAbilityUnit()), GetUnitLoc(GetSpellTargetUnit()) )
        For each (Integer A) from 1 to 80, do (Actions)
            Loop - Actions
                Lightning - Move (Last created lightning effect) to source (Position of (Casting unit)) and target (Position of (Target unit of ability being cast))
                Set TargetDistance = (Distance between (Position of (Casting unit)) and (Position of (Target unit of ability being cast)))
                Unit - Cause (Casting unit) to damage (Target unit of ability being cast), dealing 50.00 damage of attack type Magic and damage type Normal
                Wait 0.10 game-time seconds
        Lightning - Destroy (Last created lightning effect)


Stops previous trigger is drain range is exceeded:
Code:
TargetDistance
    Events
        Time - Every 3.00 seconds of game time
    Conditions
    Actions
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            If - Conditions
                TargetDistance Greater than 1000.00
            Then - Actions
                Lightning - Destroy (Last created lightning effect)
                Trigger - Remove Moving Drain Life <gen> from the trigger queue
            Else - Actions
                Do nothing

So what is the problem? The problem is that this causes Warcraft 3 to crash after 3 seconds of using it. What happens is that the spell works for the 1st second then suddenly the lightning effect changes place and connects the caster to the center of the map instead of to its target. Then the game proceeds to crash 1 second later. I don't know if it's because the caster has moved or not (doesn't appear to be though). There's probably an easier way to do this in GUI. I'm hoping to make this MUI as soon as I can figure out how.
 
Level 11
Joined
Aug 25, 2006
Messages
971
I can't really tell because its not in the correct trigger tags! Please add tags so that it actually looks like GUI. (Just remove the *)
Wait! I figured it out by looking through it. The functions (Casting unit) and (Target unit of ability being cast) Will return null after a wait. So it works until the end of the wait. The minimum wait is approximitly .29. You should consider timers.

Or local variables. Local variables are a form of variables that are relevant to only the current trigger.
This means that say you set X to 10 and X is a local variable. If you try and find what X is on a different trigger it will tell you that X doesn't exist.
 
Level 5
Joined
Aug 8, 2007
Messages
112
  • Moving Drain Life
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Drain Beam (Slow Based)
    • Actions
      • Custom script: call AddLightningLoc( "BEAM", GetUnitLoc(GetSpellAbilityUnit()), GetUnitLoc(GetSpellTargetUnit()) )
      • For each (Integer A) from 1 to 80, do (Actions)
        • Loop - Actions
          • Lightning - Move (Last created lightning effect) to source (Position of (Casting unit)) and target (Position of (Target unit of ability being cast))
          • Set TargetDistance = (Distance between (Position of (Casting unit)) and (Position of (Target unit of ability being cast)))
          • Unit - Cause (Casting unit) to damage (Target unit of ability being cast), dealing 50.00 damage of attack type Magic and damage type Normal
          • Wait 0.10 game-time seconds
      • Lightning - Destroy (Last created lightning effect)

  • TargetDistance
    • Events
      • Time - Every 3.00 seconds of game time
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • TargetDistance Greater than 1000.00
        • Then - Actions
          • Lightning - Destroy (Last created lightning effect)
          • Trigger - Remove Moving Drain Life <gen> from the trigger queue
        • Else - Actions
          • Do nothing
 
Level 3
Joined
Aug 29, 2004
Messages
57
SOLVED but...how do I make this MUI?

Well this is crazy but.. I solved the problem by adding unit arrays castingunit and targetunit to the mix and ditching the targetdistance check trigger (this is also very good also because it doesn't lag the game anymore).

  • Moving Drain Life
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Drain Beam (Slow Based)
    • Actions
      • Lightning - Destroy LightningEffect
      • Custom script: call AddLightningLoc( "BEAM", GetUnitLoc(GetSpellAbilityUnit()), GetUnitLoc(GetSpellTargetUnit()) )
      • Set LightningEffect = (Last created lightning effect)
      • Set CastingUnit[0] = (Casting unit)
      • Set TargetUnit[0] = (Target unit of ability being cast)
      • For each (Integer A) from 1 to 80, do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (TargetDistance Less than or equal to 1000.00) and (((Distance between (Position of CastingUnit[0]) and (Position of TargetUnit[0])) Less than or equal to 1000.00) and (((TargetUnit[0] is alive) Equal to True) and ((CastingUnit[0] is alive) Equal to True)))
            • Then - Actions
              • Lightning - Move LightningEffect to source (Position of CastingUnit[0]) and target (Position of TargetUnit[0])
              • Set TargetDistance = (Distance between (Position of CastingUnit[0]) and (Position of TargetUnit[0]))
              • Unit - Cause CastingUnit[0] to damage TargetUnit[0], dealing 50.00 damage of attack type Magic and damage type Normal
              • Wait 0.10 game-time seconds
            • Else - Actions
              • Do nothing
      • Set TargetDistance = 0.00
      • Lightning - Destroy LightningEffect
However this trigger is NOT MUI because my unit arrays are all set to index 0. I have programming experience so I know how these work, however I fail to understand why people say that using unitgroups and/or unit arrays will make triggers like this MUI.

I intend to have the possibility of many drains firing at once from different units, that means I need an array for casting units, target units and lightning effects as well as some way to track the index of each unit (this is easy). The problem is though, that the arrays have limited storage space. What if there are many many drain life abilities being cast at once? We could just make the array size so large that no game would ever conceivably cast the ability so many times at once, but this is very bad practice. Also if I do this, then how big should I make the array as to not take too much memory up and lag the game? How is this usually done?
 
Last edited:
Level 11
Joined
Aug 25, 2006
Messages
971
Arrays = Bad
Locals = Good
I explained your problem and how to fix it. Please read my above post. \This is definitely best done in JASS because if statements actually call another function thus not being able to get the same locals. A local variable only affects a single function. So if there are multiples of the trigger running the locals won't change each othe rlike global variables. I really don't have time to convert the whole thing to jass right at the moment. However if I have any time tomorrow I'll do it for you.
 
Level 3
Joined
Aug 29, 2004
Messages
57
Ahh I see what you're saying now. Unfortunately, you can tell I am not a guy who does a lot of JASS (mainly because I don't know all the function calls). I'm guessing you can't declare locals in GUI?
 
Level 11
Joined
Aug 25, 2006
Messages
971
You can because of a glitch. The glitch is you can redeclare a normal variable as a local through custom script. Then you can use that variable in GUI. Unfortunitly, this won't work so will in your case because locals only work for the function they are declared in. If statements in GUI call new functions. Thats why I suggested you switch to JASS.

When I was first learning GUI and I didn't know a function I needed I would make it in GUI. I used jasscraft to look up the function. In the bottom of jasscraft it'll show what code the function contains. If it contained code (thus it must be a BJ function) I looked at what that code did. Then I could use the natives that the BJ function called.
 
Level 3
Joined
Aug 29, 2004
Messages
57
Regardless, can you tell me what the call is for declaring a unit local? I would like to try out this glitch if I can.

I can't seem to find the call in JASSCraft. Maybe you can only declare locals with JASSHelper?
 
Level 11
Joined
Aug 25, 2006
Messages
971
Saying that is like saying you can only use the 'call' statement with JASSHelper. Locals are built into the syntax. The call for local is... local. Hold on while I fetch an example.

I posted the map, but I'll also post the code.
JASS:
function ShowGlobals_Child takes nothing returns nothing
    set udg_Rand = GetRandomReal(0,10000) //udg is the automaticlly added to globals you make through GUI
    call TriggerSleepAction(3) 
    call DisplayTextToPlayer(Player(0),0,0,"Globals: The unique value is = " + R2S(Rand))
endfunction

function ShowGlobals takes nothing returns nothing
local integer GrAx = 30 
    loop
    exitwhen GrAx == -1
        call ExecuteFunc("ShowGlobals_Child") 
        set GrAx = GrAx -1
    endloop
endfunction
When it runs the DisplayTextToPlayer it shows all the same number because the last time I randomized the global I changed it for all the functions.
JASS:
function ShowLocals_Child takes nothing returns nothing
local real Rand = GetRandomReal(0,10000) //I made a random value here!
    call TriggerSleepAction(3) //During this action the others of this function would overwrite my value if it was global
    call DisplayTextToPlayer(Player(0),0,0,"Locals: The unique value is = " + R2S(Rand))
endfunction

function ShowLocals takes nothing returns nothing
local integer GrAx = 30 //I could use a global here, but declaring a local is so much faster
    loop
    exitwhen GrAx == -1
        call ExecuteFunc("ShowLocals_Child") //Using this is like running another trigger for just that function
        set GrAx = GrAx -1
    endloop
        //Locals (not including strings, integers, players, reals and maybe a few others) must be nulled at the end of the function otherwise they will leak
endfunction
Now this is with a local variable in the child function. It'll display a bunch of unique numbers.

To declare a global you go like this
JASS:
globals
    integer CrapCrap
endglobals
Note: You can't declare globals anywhere but at the header of the map, and WE won't let you edit the header of the map except with their crappy GUI. However with vJass you can declare globals like that wherever you want and it will automaticlly put them all together at the map header when you save.

For locals you go like this:
JASS:
function Junk takes nothing returns nothing
local integer Junk
endfunction
Also locals can only be declared before everything else in a function.
JASS:
function Junk2 takes nothing returns nothing
local location Mex
    call PauseGame(true)
endfunction
(Above) that will work!
(Below) This won't!
JASS:
function Junk2 takes nothing returns nothing
    call PauseGame(true)
local location Mex
endfunction

Heres an example of using locals in gui
  • GUItrig
    • Events
      • Player - Player 1 (Red) types a chat message containing Test as An exact match
    • Conditions
    • Actions
      • Custom script: local real udg_Test //notice the udg_ It is always infront of Gui Created variables!
      • -------- I actually redefined Test as a local variable instead of a global one. --------
      • Set Test = 93.91
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • True Equal to True
        • Then - Actions
          • -------- This can see the value, but the condition CANT. It can't because the condition is in another function!!! --------
          • Game - Display to (All players) the text: ((Inside If)Test = + (String(Test)))
        • Else - Actions
      • Game - Display to (All players) the text: ((Outside If)Test = + (String(Test)))
      • -------- Almost all conditions in GUI are called in another function and can't see the locals of the current function. --------
Both if statements will print out the correct value.

So actually unless you need a condition which is local you might be able to keep working in GUI using locals. Have fun!

For the record, heres what the above GUI looks like when World edit converts it:
JASS:
function Trig_GUItrig_Func004C takes nothing returns boolean
    if ( not ( true == true ) ) then
        return false
    endif
    return true
endfunction

function Trig_GUItrig_Actions takes nothing returns nothing
    local real udg_Test //notice the udg_ It is always infront of Gui Created variables!
    // I actually redefined Test as a local variable instead of a global one.
    set udg_Test = 93.91
    if ( Trig_GUItrig_Func004C() ) then
        // This can see the value, but the condition CANT. It can't because the condition is in another function!!!
        call DisplayTextToForce( GetPlayersAll(), ( "(Inside If)Test = " + R2S(udg_Test) ) )
    else
    endif
    call DisplayTextToForce( GetPlayersAll(), ( "(Outside If)Test = " + R2S(udg_Test) ) )
    // Almost all conditions in GUI are called in another function and can't see the locals of the current function.
endfunction

//===========================================================================
function InitTrig_GUItrig takes nothing returns nothing
    set gg_trg_GUItrig = CreateTrigger(  )
    call TriggerRegisterPlayerChatEvent( gg_trg_GUItrig, Player(0), "Test", true )
    call TriggerAddAction( gg_trg_GUItrig, function Trig_GUItrig_Actions )
endfunction

See how the condition is in another function? Thats why it can't use the locals you declared in the main function. (of the trigger)
 

Attachments

  • Locals.w3x
    13.1 KB · Views: 53
Last edited:
Status
Not open for further replies.
Top