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

[1.24 compatible] Holy Barrage

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
Holy Barrages create some missiles in front of the caster. The missiles spawns one after one with a small delay. These missiles follow the facing of the caster, that means whenever the caster turn around the missiles do the same. When the missiles hit a unit they will heal or damage it. When its an enemy they will damage the unit otherways the heal it. The missiles will disappear when they hit something.
The missiles don't fly straight. They drift a bit to the sides.

JASS:
scope HolyBarrage initializer init

//*************************************************************************************************************//
//                                                  Holy Barrage                                               //
//                                                        by                                                   //
//                                                      cedi                                                   //
//                                                                                                             //
//                                         needs: TimerUtils by Vexorian                                       //
//                                                Bound Sentinel by Vexorian                                   //
//                                                IsTerrainWalkable by Antitarf                                //
//                                                Dummy Model by                                               //
//*************************************************************************************************************//

//For use, copy the trigger to your map, copy the dummy create a spell and adjust the values below.

globals
    //ID of the dummy spell
    private constant integer SPELL_ID            = 'A000'
    //ID of the dummy
    private constant integer DUMMY_ID            = 'h000'
    //How many levels does your spell have?
    private constant integer SPELL_LEVEL_COUNT   = 4
    //The move timer interval
    private constant real    TIMER_INTERVAL      = 0.02
    //The spawn interval ( for the missiles )
    private constant real    SPAWN_INTERVAL      = 0.2
    //Should the spell create the first missile with a delay?
    private constant real    FIRST_SPAWN_DELAY   = 0.00
    //The spawn isn't always the same. This value is the max difference
    private constant real    MAX_SPAWNINT_DIF    = 0.05
    //Each x seconds the system picks all units around ( higher numbers are better for the performance )
    private constant real    PICK_INTERVAL       = 0.10
    //How fast should the missiles be? In wc3 units
    private constant real    SPEED               = 700.00
    //The distance to the caster on create. BEWARE!! Must be bigger then the COLLISION_SIZE!!
    private constant real    START_DISTANCE      = 105.00
    //The collisions size of the missiles.
    private constant real    COLLISION_SIZE      = 75.00
    //Max drift to the sides.
    private constant real    MAX_DRIFT_PERSEC    = 550.00
    //Size of the missile. 1 == 100%. 1.5 == 150%
    private constant real    SIZE                = 2.00
    //Height of the missiles.
    private constant real    FLYHEIGHT           = 50.00
    //Model of the missiles,
    private constant string  DUMMY_MODEL         = "Abilities\\Weapons\\ProcMissile\\ProcMissile.mdl"
    //Effect on collision.
    private constant string  HIT_MODEL           = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
    //Effect when a unit takes damage because this spell.
    private constant string  DAMAGE_MODEL        = "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl"
    //Effect when a unit gets healed by this spell
    private constant string  HEAL_MODEL          = "Abilities\\Spells\\Human\\Heal\\HealTarget.mdl"
    
    private integer  array   BARRAGE_COUNT[SPELL_LEVEL_COUNT] //SYSTEM
    private real     array   BARRAGE_DAMAGE[SPELL_LEVEL_COUNT] //SYSTEM
    private real     array   BARRAGE_HEAL[SPELL_LEVEL_COUNT] //SYSTEM
    private real     array   BARRAGE_RANGE[SPELL_LEVEL_COUNT] //SYSTEM
    private real     array   BARRAGE_AOE[SPELL_LEVEL_COUNT] //SYSTEM
    
    private real             REAL_MOVE           = SPEED * TIMER_INTERVAL //SYSTEM
    private real             REAL_DRIFT          = MAX_DRIFT_PERSEC * TIMER_INTERVAL //SYSTEM
    private group            TEMPGROUP           = CreateGroup()//SYSTEM
endglobals

//When you want mor then 4 level c'n'p one line and adjust the numbers
//These functions sets the missiles amount for each level.
private function SET_BARRAGE_COUNT takes nothing returns nothing
    set BARRAGE_COUNT[1] = 5
    set BARRAGE_COUNT[2] = 7
    set BARRAGE_COUNT[3] = 9
    set BARRAGE_COUNT[4] = 11
endfunction

//These functions sets the missiles damage for each level.
private function SET_BARRAGE_DAMAGE takes nothing returns nothing
    set BARRAGE_DAMAGE[1] = 20.00
    set BARRAGE_DAMAGE[2] = 25.00
    set BARRAGE_DAMAGE[3] = 30.00
    set BARRAGE_DAMAGE[4] = 35.00
endfunction

//These functions sets the missiles heal amount for each level.
private function SET_BARRAGE_HEAL takes nothing returns nothing
    set BARRAGE_HEAL[1] = 20.00
    set BARRAGE_HEAL[2] = 25.00
    set BARRAGE_HEAL[3] = 30.00
    set BARRAGE_HEAL[4] = 35.00
endfunction

//These functions sets the missiles range for each level.
private function SET_BARRAGE_RANGE takes nothing returns nothing
    set BARRAGE_RANGE[1] = 2000.00
    set BARRAGE_RANGE[2] = 2000.00
    set BARRAGE_RANGE[3] = 2000.00
    set BARRAGE_RANGE[4] = 2000.00
endfunction

//These functions sets the missiles aoe for each level.
private function SET_BARRAGE_AOE takes nothing returns nothing
    set BARRAGE_AOE[1] = 150.00
    set BARRAGE_AOE[2] = 160.00
    set BARRAGE_AOE[3] = 170.00
    set BARRAGE_AOE[4] = 180.00
endfunction

//*************************************************************************************************************//
//                                                     !SYSTEM!                                                //
//*************************************************************************************************************//

private function IsAliveAndUnit takes nothing returns boolean
    return GetUnitState( GetFilterUnit(), UNIT_STATE_LIFE ) > 0.405 and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false
endfunction

private function IsAliveAndUnitAndNotMagicImmune takes nothing returns boolean
    return GetUnitState( GetFilterUnit(), UNIT_STATE_LIFE ) > 0.405 and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false
endfunction

private function Move takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local Missile data = GetTimerData( t )
    set data.pick = data.pick + TIMER_INTERVAL
    call data.MoveMissile()
    if data.pick >= PICK_INTERVAL then
        call data.Pick()
    endif
    set t = null
endfunction

struct Missile
    unit missile
    unit caster
    real x
    real y
    real range
    real pick = 0.00
    integer level
    effect sfx
    timer time
    
    private method HealDamage takes nothing returns nothing
        local unit u
        call GroupEnumUnitsInRange( TEMPGROUP, .x, .y, BARRAGE_AOE[.level], Condition( function IsAliveAndUnitAndNotMagicImmune ) )
        call DestroyEffect( AddSpecialEffect( HIT_MODEL, .x, .y ) )
        loop
            set u = FirstOfGroup( TEMPGROUP )
            exitwhen u == null
            if IsUnitEnemy( u, GetOwningPlayer( .caster ) ) == true then
                call DestroyEffect( AddSpecialEffectTarget( DAMAGE_MODEL, u, "origin" ) )
                call UnitDamageTarget( .caster, u, BARRAGE_DAMAGE[.level], true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )
            else
                call DestroyEffect( AddSpecialEffectTarget( HEAL_MODEL, u, "origin" ) )
                call SetUnitState( u, UNIT_STATE_LIFE, GetUnitState( u, UNIT_STATE_LIFE ) + BARRAGE_HEAL[.level] )
            endif
            call GroupRemoveUnit( TEMPGROUP, u )
        endloop
    endmethod
    
    method MoveMissile takes nothing returns nothing
        local real angle = GetUnitFacing( .caster )
        local real r
        set .range = .range - REAL_MOVE
        if .range <= 0.00 then
            call .destroy()
        endif
        
        set .x = .x + Cos( angle * bj_DEGTORAD ) * REAL_MOVE
        set .y = .y + Sin( angle * bj_DEGTORAD ) * REAL_MOVE
        call SetUnitFacing( .missile, angle )
        set angle = ( angle + 90.00 ) * bj_DEGTORAD
        set r = GetRandomReal( -REAL_DRIFT, REAL_DRIFT )
        set .x = .x + Cos( angle ) * r
        set .y = .y + Sin( angle ) * r
        if IsTerrainWalkable( .x, .y ) == false then
            call .destroy()
        endif
        call SetUnitX( .missile, .x )
        call SetUnitY( .missile, .y )
    endmethod
    
    method Pick takes nothing returns nothing
        local unit u
        call GroupEnumUnitsInRange( TEMPGROUP, .x, .y, COLLISION_SIZE, Condition( function IsAliveAndUnit ) )
        set u = FirstOfGroup( TEMPGROUP )
        call GroupClear( TEMPGROUP )
        if u != null then
            call .HealDamage()
            call .destroy()
        endif
    endmethod
    
    method onDestroy takes nothing returns nothing
        call PauseTimer( .time )
        call ReleaseTimer( .time )
        call DestroyEffect( .sfx )
        call KillUnit( .missile )
    endmethod
    
    static method create takes unit caster, integer level returns Missile
        local Missile data = Missile.allocate()
        local real angle = GetUnitFacing( caster )
        set data.time = NewTimer()
        call SetTimerData( data.time, data )
        set data.caster = caster
        set data.x = GetUnitX( caster ) + Cos( angle * bj_DEGTORAD ) * START_DISTANCE
        set data.y = GetUnitY( caster ) + Sin( angle * bj_DEGTORAD ) * START_DISTANCE
        set data.level = level
        set data.range = BARRAGE_RANGE[level]
        set data.missile = CreateUnit( GetOwningPlayer( caster ), DUMMY_ID, data.x, data.y, angle )
        call SetUnitFlyHeight( data.missile, FLYHEIGHT, 0.00 )
        call SetUnitScale( data.missile, SIZE, SIZE, SIZE )
        set data.sfx = AddSpecialEffectTarget( DUMMY_MODEL, data.missile, "origin" )
        set caster = null
        call TimerStart( data.time, TIMER_INTERVAL, true, function Move )
        return data
    endmethod
    
endstruct

struct SpawnController
    unit caster
    integer count
    integer level
    
    static method create takes unit caster returns SpawnController
        local SpawnController data = SpawnController.allocate()
        set data.caster = caster
        set data.level = GetUnitAbilityLevel( caster, SPELL_ID )
        set data.count = BARRAGE_COUNT[data.level]
        set caster = null
        return data
    endmethod
    
endstruct

private function GetSpawnInterval takes nothing returns real
    local integer i = GetRandomInt( 0, 1 )
    local real r = GetRandomReal( 0.00, MAX_SPAWNINT_DIF )
    if i == 0 then
        return SPAWN_INTERVAL - r
    else
        return SPAWN_INTERVAL + r
    endif
endfunction

private function Spawn takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local SpawnController data = GetTimerData( t )
    local Missile m
    set data.count = data.count - 1
    set m = Missile.create( data.caster, data.level )
    if data.count <= 0 then
        call data.destroy()
        call ReleaseTimer( t )
    else
        call TimerStart( t, GetSpawnInterval(), false, function Spawn )
    endif
    set t = null
endfunction

private function Action takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local timer t = NewTimer()
    local integer lvl = GetUnitAbilityLevel( u, SPELL_ID )
    local SpawnController data = SpawnController.create( u )
    call SetTimerData( t, data )
    call TimerStart( t, FIRST_SPAWN_DELAY, false, function Spawn )
    set t = null
    set u = null
endfunction

private function IsSpell takes nothing returns boolean
    return GetSpellAbilityId() == SPELL_ID
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function IsSpell ) )
    call TriggerAddAction( t, function Action )
    
    call SET_BARRAGE_COUNT()
    call SET_BARRAGE_DAMAGE()
    call SET_BARRAGE_HEAL()
    call SET_BARRAGE_RANGE()
    call SET_BARRAGE_AOE()
    
    set t = null
endfunction

endscope

Credits: Vexorian for the TimerUtils and Sentinel Bound and Antitarf for the IsWalkable.
There is another credit for the dummy.mdx, but can't remember the creator...

Please give credits when you use this.

cedi

Keywords:
missile, bullet, holy, arcane, heal
Contents

Holy Barrage (Map)

Reviews
00:24, 31st Dec 2009 TriggerHappy: Your structs should be private. FirstOfGroup loops are a slow method. SetUnitState -> SetWidgetLife. IsAliveAndUnitAndNotMagicImmune should use UnitAlive.

Moderator

M

Moderator

00:24, 31st Dec 2009
TriggerHappy:

Your structs should be private.
FirstOfGroup loops are a slow method.
SetUnitState -> SetWidgetLife.
IsAliveAndUnitAndNotMagicImmune should use UnitAlive.
 

Rmx

Rmx

Level 19
Joined
Aug 27, 2007
Messages
1,164
If someone wants to test it in 1.23 Patch just follow the instructions !

JASS:
private function H2I takes handle h returns integer     
return GetHandleId(h)
endfunction

Just Replace it with the function under.

JASS:
private function H2I takes handle h returns integer     
return h     
return 0
endfunction

Spell is nice .. but lacks creativity .. 3.5/5 :D

Still this is good coding :D
 
Level 17
Joined
Sep 8, 2007
Messages
994
Hey cedi!
This spell looks very nice, well done! However, I have to mention something...
JASS:
    method onDestroy takes nothing returns nothing
        call PauseTimer( .time )
        call ReleaseTimer( .time )
        call DestroyEffect( .sfx )
        call KillUnit( .missile )
    endmethod
You have to additionally nullify the handles here!
JASS:
    method onDestroy takes nothing returns nothing
        call PauseTimer( .time )
        call ReleaseTimer( .time )
        call DestroyEffect( .sfx )
        call KillUnit( .missile )
        set .missile = null
        set .sfx = null
    endmethod
I'm not sure with timers so I won't say you have to null them :p
Anyways, 5/5 +rep
 
Level 10
Joined
May 19, 2008
Messages
176
Structvariables are glovals. And you don't need to null globals ;).

You need to null local variables because there're saves the pointer. And when you don't null them the pointer get lost but is still there... That can't happen by globals.

cedi
 
Level 17
Joined
Sep 8, 2007
Messages
994
Hmmm I'm not really sure about this :/
Anyone who has more knowledge about this than me - feel free to tell me how it is xD

My opinion:
Those instances are global if they are static. Then I absolutely agree to not null them. However, always if you create a new data, you (at least should) create the matching instances as well. Thus, you have to null them.

cedi, if you are really sure about this I will trust you ofc x)
 
Level 10
Joined
May 19, 2008
Messages
176
Structs variables are only arrays with a special name. The struct is only an integer which stays for the index of the variable. Because of that you need to write in a constant array lenght for array variables in structs. Then this value representate the offset.

For example.

struct A
integer i
integer array i2[20]
endstruct

when you create a struct it has a number for example 0.
Yet there exist an integer array variable with the name A__i or something like that. When you set i == a value wc3 sets A__i[0] == the value.
On struct variable arrays they have reserved places. A__i2[0 - 20] is reserved for the struct.
The next struct has the places 21 - 41 and so on. Because of that you can have only 8191 / constant array size structs.

The result is, all struct members are globals. And globals can't leak.

cedi
 
Level 17
Joined
Sep 8, 2007
Messages
994
Structs variables are only arrays with a special name. The struct is only an integer which stays for the index of the variable. Because of that you need to write in a constant array lenght for array variables in structs. Then this value representate the offset.

For example.

struct A
integer i
integer array i2[20]
endstruct

when you create a struct it has a number for example 0.
Yet there exist an integer array variable with the name A__i or something like that. When you set i == a value wc3 sets A__i[0] == the value.
On struct variable arrays they have reserved places. A__i2[0 - 20] is reserved for the struct.
The next struct has the places 21 - 41 and so on. Because of that you can have only 8191 / constant array size structs.

The result is, all struct members are globals. And globals can't leak.

cedi

Convinced.
 
Top