• 🏆 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] Rapid Fire/ Zeal. Help Please

Status
Not open for further replies.
Level 2
Joined
Aug 30, 2007
Messages
23
For my map project, here is a current spell that I have heavily modified off of another one. Basically the unit stands still and attacks a number of times to all in range of the spell. The thing that gets me, is that the unit hurts itself when casting it, by the amount of damage the spell does. Also, if its any longer than 5 hits (Dont know the exact amount though), the unit can move around while the trigger continues to go through the remaining attacks (After the unit finishes the animations. So, I guess the problems that I dont know how to fix are- How to make the unit not take damage when casting (Not making the unit invulnerable or divine), and pausing the unit while the spell is cast, and unpausing when done. Help is greatly appreciated.

Also, this is not really needed, but would be nice- To play the riflemans attack sound while attacking, and having the riflemans projectile effect on the enemy units hit by the spell.


JASS:
function Trig_Rapid_Fire_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A021'        
endfunction

function Unit_Group takes nothing returns boolean
    return GetBooleanAnd( IsUnitAliveBJ(GetFilterUnit()) == true, IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit())) == true )
endfunction

function Trig_Rapid_Fire_Actions takes nothing returns nothing
    local unit Caster = GetTriggerUnit()
    local integer i = 0
    local group UnitGroup
    local unit TargetRandom
    local unit Target = GetSpellTargetUnit()
    local location R
    local real Damage = 100
    local integer Amount = 5 + ( GetUnitAbilityLevelSwapped('A021', Caster) * 0 )
    
    call TriggerSleepAction( 0.20 )
    call SetUnitVertexColor( Caster, 255, 255, 255, 255 )    
    call UnitDamageTarget( Caster, Target, Damage, false, true, ATTACK_TYPE_HERO, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_AXE_MEDIUM_CHOP )
    call SetUnitAnimation( Caster, "attack" )
    call TriggerSleepAction( 0.2 )   

    loop
        set i = i + 1        
        exitwhen i > Amount 
            set UnitGroup = GetUnitsInRangeOfLocMatching(600.00, GetUnitLoc(Caster), Condition(function Unit_Group))
            if ( IsUnitGroupEmptyBJ(UnitGroup) == false ) then
                set TargetRandom = GroupPickRandomUnit (UnitGroup)
                set R = GetUnitLoc(TargetRandom)
                call UnitDamageTarget( Caster, TargetRandom, Damage, false, true, ATTACK_TYPE_HERO, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_AXE_MEDIUM_CHOP )
                call SetUnitAnimation( Caster, "attack" )
                call RemoveLocation ( R )
                call TriggerSleepAction( 0.2 )              
            else
            endif 
            call DestroyGroup(UnitGroup)           
        endloop    
    call SetUnitVertexColor( Caster, 255, 255, 255, 255 )  
    set Caster = null     
    set UnitGroup = null
    set TargetRandom = null
    set Target = null    
    set Amount = 0
    set R = null
    set Damage = 0
endfunction

//===========================================================================
function InitTrig_Rapid_Fire_Copy_2 takes nothing returns nothing
    set gg_trg_Rapid_Fire_Copy_2 = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rapid_Fire_Copy_2, EVENT_PLAYER_UNIT_SPELL_CAST )
    call TriggerAddCondition( gg_trg_Rapid_Fire_Copy_2, Condition( function Trig_Rapid_Fire_Conditions ) )
    call TriggerAddAction( gg_trg_Rapid_Fire_Copy_2, function Trig_Rapid_Fire_Actions )
endfunction
 
Level 11
Joined
Feb 18, 2004
Messages
394
Lets go over a few problems with your script, because I'm bored:

Problem 1: you use the blizzard.j function GetBooleanAnd(). blizzard.j functions are coded in JASS, as opposed to common.j functions which are "natives", as they are implemented in the game itself, not in JASS. Anyway:
JASS:
function GetBooleanAnd takes boolean valueA, boolean valueB returns boolean
    return valueA and valueB
endfunction

Which means you can simplify part of your script to:
JASS:
function Unit_Group takes nothing returns boolean
    return IsUnitAliveBJ(GetFilterUnit()) and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit()))
endfunction

note that i've inlined GetBooleanAnd() and removed == true. == true is very redundant, and the only time you really need to use it is with IsUnitType(). (If i remember right thats the one...)

Problem 2: You use the blizzard.j (BJ for short) function IsUnitAliveBJ():
JASS:
function IsUnitAliveBJ takes unit whichUnit returns boolean
    return not IsUnitDeadBJ(whichUnit)
endfunction
IsUnitDeadBJ() is als oa blizzard.j function:
JASS:
function IsUnitDeadBJ takes unit whichUnit returns boolean
    return GetUnitState(whichUnit, UNIT_STATE_LIFE) <= 0
endfunction
GetUnitState() is a native, but there is another native thats just a bit simpler for the job:
JASS:
native GetWidgetLife takes widget whichWidget returns real

So changing the code again, we end up with:
JASS:
function Unit_Group takes nothing returns boolean
    return GetWidgetLife(GetFilterUnit()) > 0 and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit()))
endfunction

Problem 3: The line:
JASS:
local integer Amount = 5 + ( GetUnitAbilityLevelSwapped('A021', Caster) * 0 )
uses the BJ GetUnitAbilityLevelSwapped(). Also, why the heck are you multiplying by 0? That would make the result always 5. And the parenthesis are unnecessary:
JASS:
local integer Amount = 5 + GetUnitAbilityLevel(Caster, 'A021') * 0

Problem 4: The line:
JASS:
set UnitGroup = GetUnitsInRangeOfLocMatching(600.00, GetUnitLoc(Caster), Condition(function Unit_Group))

First of all, GetUnitsInRangeOfLocMatching is a useless blizzard.j function. Second of all, you leak a location. (The one returned by GetUnitLoc())

JASS:
function GetUnitsInRangeOfLocMatching takes real radius, location whichLocation, boolexpr filter returns group
    local group g = CreateGroup()
    call GroupEnumUnitsInRangeOfLoc(g, whichLocation, radius, filter)
    call DestroyBoolExpr(filter)
    return g
endfunction

This is a horrible function. For one, you do not destroy boolexprs, as they do not actually leak! (And it can cause some nasty bugs if you do destroy them.) Another reason this function is evil is because it uses locations at all. You can completely avoid using locations via the function:
JASS:
native GroupEnumUnitsInRange takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
Most often its possible to avoid using locations at all, instead using two real coordinates. The advantage to using coordinates is that you do not create locations, and thus you do not have to destroy locations to fix memory leaks!

So, editing that line, we're left with:
JASS:
set UnitGroup = CreateGroup()
call GroupEnumUnitsInRange(UnitGroup, GetUnitX(Caster), GetUnitY(Caster), 600, Condition(function Unit_Group))

Problem 5: The line:
JASS:
if ( IsUnitGroupEmptyBJ(UnitGroup) == false ) then

Another BJ function:
JASS:
function IsUnitGroupEmptyBJ takes group g returns boolean
    // If the user wants the group destroyed, remember that fact and clear
    // the flag, in case it is used again in the callback.
    local boolean wantDestroy = bj_wantDestroyGroup
    set bj_wantDestroyGroup = false

    set bj_isUnitGroupEmptyResult = true
    call ForGroup(g, function IsUnitGroupEmptyBJEnum)

    // If the user wants the group destroyed, do so now.
    if (wantDestroy) then
        call DestroyGroup(g)
    endif
    return bj_isUnitGroupEmptyResult
endfunction

That function is completely useless. The native function FirstOfGroup() will return the value null if a group is empty, so you can just do:
JASS:
if FirstOfGroup(UnitGroup) != null then

Problem 6: The lines:
JASS:
set R = GetUnitLoc(TargetRandom)
call UnitDamageTarget( Caster, TargetRandom, Damage, false, true, ATTACK_TYPE_HERO, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_AXE_MEDIUM_CHOP )
call SetUnitAnimation( Caster, "attack" )
call RemoveLocation ( R )

You create a location, don't use it, then destroy it. Why? Removing the unneeded location, the function gets a bit smaller:
JASS:
function Trig_Rapid_Fire_Actions takes nothing returns nothing
    local unit Caster = GetTriggerUnit()
    local integer i = 0
    local group UnitGroup
    local unit TargetRandom
    local unit Target = GetSpellTargetUnit()
    local real Damage = 100
    local integer Amount = 5 + GetUnitAbilityLevel(Caster, 'A021') * 0

    call TriggerSleepAction( 0.20 )
    call SetUnitVertexColor( Caster, 255, 255, 255, 255 )
    call UnitDamageTarget( Caster, Target, Damage, false, true, ATTACK_TYPE_HERO, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_AXE_MEDIUM_CHOP )
    call SetUnitAnimation( Caster, "attack" )
    call TriggerSleepAction( 0.2 )

    loop
        set i = i + 1
        exitwhen i > Amount
            set UnitGroup = CreateGroup()
            call GroupEnumUnitsInRange(UnitGroup, GetUnitX(Caster), GetUnitY(Caster), 600, Condition(function Unit_Group))
            if FirstOfGroup(UnitGroup) != null then
                set TargetRandom = GroupPickRandomUnit(UnitGroup)
                call UnitDamageTarget( Caster, TargetRandom, Damage, false, true, ATTACK_TYPE_HERO, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_AXE_MEDIUM_CHOP )
                call SetUnitAnimation( Caster, "attack" )
                call TriggerSleepAction( 0.2 )
            else
            endif
            call DestroyGroup(UnitGroup)
    endloop
    call SetUnitVertexColor( Caster, 255, 255, 255, 255 )
    set Caster = null
    set UnitGroup = null
    set TargetRandom = null
    set Target = null
    set Amount = 0
    set Damage = 0
endfunction

Problem 7: The lines:
JASS:
    call TriggerSleepAction( 0.2 )
else
endif

The "else" is not needed. Only the "if condition then" and "endif" are required.

Problem 8: The lines:
JASS:
set Amount = 0
set Damage = 0
Are not needed. You only need to null variables which desend from the type handle, and which will be destroyed. Examples include: unit item destructable widget location group. You certainly do not need to null / zero: code boolean real integer string.


The resulting code looks like this:
JASS:
function Trig_Rapid_Fire_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A021'
endfunction

function Unit_Group takes nothing returns boolean
    return GetWidgetLife(GetFilterUnit()) > 0 and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit()))
endfunction

function Trig_Rapid_Fire_Actions takes nothing returns nothing
    local unit Caster = GetTriggerUnit()
    local integer i = 0
    local group UnitGroup
    local unit TargetRandom
    local unit Target = GetSpellTargetUnit()
    local real Damage = 100
    local integer Amount = 5 + GetUnitAbilityLevel(Caster, 'A021') * 0

    call TriggerSleepAction(0.20)
    call SetUnitVertexColor(Caster, 255, 255, 255, 255)
    call UnitDamageTarget(Caster, Target, Damage, false, true, ATTACK_TYPE_HERO, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_AXE_MEDIUM_CHOP)
    call SetUnitAnimation(Caster, "attack")
    call TriggerSleepAction(0.2)

    loop
        set i = i + 1
        exitwhen i > Amount
            set UnitGroup = CreateGroup()
            call GroupEnumUnitsInRange(UnitGroup, GetUnitX(Caster), GetUnitY(Caster), 600, Condition(function Unit_Group))
            if FirstOfGroup(UnitGroup) != null then
                set TargetRandom = GroupPickRandomUnit(UnitGroup)
                call UnitDamageTarget(Caster, TargetRandom, Damage, false, true, ATTACK_TYPE_HERO, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_AXE_MEDIUM_CHOP)
                call SetUnitAnimation(Caster, "attack")
                call TriggerSleepAction(0.2)
            endif
            call DestroyGroup(UnitGroup)
    endloop
    call SetUnitVertexColor(Caster, 255, 255, 255, 255)
    set Caster = null
    set UnitGroup = null
    set TargetRandom = null
    set Target = null
endfunction

function InitTrig_Rapid_Fire_Copy_2 takes nothing returns nothing
    set gg_trg_Rapid_Fire_Copy_2 = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Rapid_Fire_Copy_2, EVENT_PLAYER_UNIT_SPELL_CAST)
    call TriggerAddCondition(gg_trg_Rapid_Fire_Copy_2, Condition( function Trig_Rapid_Fire_Conditions) )
    call TriggerAddAction(gg_trg_Rapid_Fire_Copy_2, function Trig_Rapid_Fire_Actions)
endfunction

Now, as for why the casting unit gets damaged, is because you do not filter it out in the function Unit_Group, so it gets added to the group. You'll have to use a global variable to store the casting unit in right before you call GroupEnumUnitsInRange, using the global to check if the FilterUnit() is the caster or not.

As for the other problems...

You use TriggerSleepAction() instead of timers. (I'm way too lazy to teach you how to use timers...)

You probobly want to use the event EVENT_PLAYER_UNIT_SPELL_EFFECT instead of EVENT_PLAYER_UNIT_SPELL_CAST. EFFECT fires when the effect of an ability begins (when its done casting). CAST fires when the unit BEGINS casting the ability.

To pause the unit, you would use:
JASS:
native PauseUnit takes unit whichUnit, boolean flag returns nothing
As such:
JASS:
call PauseUnit(Caster, true)
//...
call PauseUnit(Caster, false)
Probably not the best solution, but the simplest one.

There are more problems... But I'm not so much bored anymore as tired. So you're on your own from here...



You should look in to using the NewGen world editor hack. It includes things like a function finder and syntax highlighting within the editor. (It also keeps the editor from crashing when you make a syntax error!) You can download it here: http://wc3campaigns.net/showthread.php?t=90999
NewGen also includes JASS Helper, the vJASS compiler. This adds many cool features to JASS, and in the end is compiled down to normal JASS so maps made using vJASS even work online. The manual outlining all the crap vJASS adds is here: http://wc3campaigns.net/vexorian/jasshelpermanual.html
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
Now, as for why the casting unit gets damaged, is because you do not filter it out in the function Unit_Group, so it gets added to the group. You'll have to use a global variable to store the casting unit in right before you call GroupEnumUnitsInRange, using the global to check if the FilterUnit() is the caster or not.

Wtf noob ?
GetFilterUnit() != GetTriggerUnit() in the group conditions.
 
Level 2
Joined
Aug 30, 2007
Messages
23
Not Exactly sure what you mean Ghostwolf. Where would I put GetFilterUnit() != GetTriggerUnit() ? Sorry, but I am still a noob with jass, and need examples. I put the code in this area so it looks like this
JASS:
   function Unit_Group takes nothing returns boolean  
   return GetWidgetLife(GetFilterUnit()) > 0 and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit()))
   return GetFilterUnit() != GetTriggerUnit()
   endfunction
And he still shoots himself once. I do have him standing still now, and thats good.
 
Level 11
Joined
Feb 18, 2004
Messages
394
Wtf noob ?
GetFilterUnit() != GetTriggerUnit() in the group conditions.
Hmn, GetTriggerUnit() is preserved throgh filter callbacks? Neat.

Not Exactly sure what you mean Ghostwolf. Where would I put GetFilterUnit() != GetTriggerUnit() ? Sorry, but I am still a noob with jass, and need examples. I put the code in this area so it looks like this
JASS:
   function Unit_Group takes nothing returns boolean  
   return GetWidgetLife(GetFilterUnit()) > 0 and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit()))
   return GetFilterUnit() != GetTriggerUnit()
   endfunction
And he still shoots himself once. I do have him standing still now, and thats good.
once you return from a function, you stop running the function and go back to the place it was called. Thus, you return, then return again... so the second return will never be checked. Instead you want to use the "and" operator, as you want all 3 to have to be true for the unit to be added to the group. thus:
JASS:
   function Unit_Group takes nothing returns boolean  
   return GetWidgetLife(GetFilterUnit()) > 0 and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit())) and GetFilterUnit() != GetTriggerUnit()
   endfunction
 
Level 2
Joined
Aug 30, 2007
Messages
23
He still shoots himself once... Well not really shooting himself, he just deals himself whatever the listed damage is.
 
Last edited:
Status
Not open for further replies.
Top