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

Spike Eruption

  • Like
Reactions: doomhammer99
Spell requested by claptomanic. It recquires the StunSystem and TimerUtils (since that's the timer system claptomanic uses and he is right about that :p).

Description :
The caster slams the ground causing masses of spikes burst out of the ground at random points in 400 range. These spikes bursts out of the ground and when they reach the max high of the effect they stop (means they dont move back into the ground again). if an enemy is hit by a spike it gets dragged upwards with the spike dealing damage to it. after 2 seconds the spikes go back into the ground (the units hit move with them). When they reach the ground they stay stunned for 3 seconds.
The spikes block movement but are not selectable.

JASS:
scope SpikeEruption initializer init
// requires:
//  - StunSystem
//     http://www.hiveworkshop.com/forums/jass-resources-412/system-stun-196749/
//  - TimerUtils
//     http://www.wc3c.net/showthread.php?t=101322
//     or
//     http://www.hiveworkshop.com/forums/graveyard-418/system-timerutilsex-204500/

// HOW TO IMPORT :
// 1) Import the recquired libraries.
// 2) Download the test map.
// 3) Copy/paste this scope in your map.
// 4) Copy/paste the spell "Spike Eruption" and the destructable "Spike (Spike Eruption)".
// 5) Look the obtained spell and destructable IDs and configure the scope.

//=====================================//
//                                     //
//         CONFIGURATION BLOC          //
//                                     //
//=====================================//

    globals
        private constant integer RAVEN_ID      = 'Amrf'
        private constant integer SPELL_ID      = 'A000'
        private constant integer DEST_ID       = 'B000'
        private constant attacktype ATTACK     = ATTACK_TYPE_MAGIC
        private constant damagetype DAMAGE     = DAMAGE_TYPE_UNIVERSAL
        private constant real MIN_RADIUS       = 100
        private constant real SPIKE_RADIUS     = 64
        private constant real MAX_HEIGHT       = 500 // The spike's top is at 362 height for scale 1
        private constant real TIMEOUT          = 0.03
        private constant boolean FOLLOW_CASTER = false // true: the center of the eruption area is always updated with the position of the caster
        
        private group TmpGroup = CreateGroup()
        
        private real array SpellRadius            // Radius of Eruption
        private real array SpikesDelay            // Delay between 2 spike waves
        private real array StunDuration           // Stun duration after reached the ground
        private real array SpikeStopDuration      // Duration of the spike remaining rised
        private integer array SpikesWaveAmount    // Amount of spikes per wave
        private integer array SpikesTotalAmount   // Total amount of spikes in one cast
    endglobals

    private function GetDamage takes unit caster, integer lvl returns real
        return 0.1*GetHeroStr(caster,true)*lvl
    endfunction

    // Filter for units affected by the spikes
    private function SpellFilter takes unit caster, unit target returns boolean
        return IsUnitEnemy(target,GetOwningPlayer(caster)) and /*
            */ not IsUnitType(target,UNIT_TYPE_DEAD) and /*
            */ not IsUnitType(target,UNIT_TYPE_STRUCTURE) and /*
            */ not IsUnitType(target,UNIT_TYPE_MECHANICAL) and /*
            */ not IsUnitType(target,UNIT_TYPE_MAGIC_IMMUNE) and /*
            */ GetUnitFlyHeight(target)<MAX_HEIGHT
    endfunction

    private function SetupDatas takes nothing returns nothing
        // Level 1
        set SpellRadius[1]=400
        set SpikesDelay[1]=0.3
        set StunDuration[1]=3
        set SpikeStopDuration[1]=2
        set SpikesWaveAmount[1]=1
        set SpikesTotalAmount[1]=6
        // Level 2
        set SpellRadius[2]=400
        set SpikesDelay[2]=0.2
        set StunDuration[2]=3
        set SpikeStopDuration[2]=2
        set SpikesWaveAmount[2]=2
        set SpikesTotalAmount[2]=8
        // Level 3
        set SpellRadius[3]=400
        set SpikesDelay[3]=0.2
        set StunDuration[3]=3
        set SpikeStopDuration[3]=2
        set SpikesWaveAmount[3]=2
        set SpikesTotalAmount[3]=10
    endfunction

//=====================================//
//                                     //
//     END OF CONFIGURATION BLOC       //
//                                     //
//=====================================//

    private struct SpellStructMain
        unit caster
        integer lvl
        real x
        real y
        real damage
        integer countleft
    endstruct

    private struct SpellStructSpike
        unit caster
        integer lvl
        destructable spike
        real time
        unit target
    endstruct

    public function GetRandomRadius takes real min, real max returns real
        return SquareRoot(min*min+GetRandomReal(0,max*max-min*min))
    endfunction

    private function SquaredDistance takes real x1, real y1, real x2, real y2 returns real
        local real dx=x2-x1
        local real dy=y2-y1
        return dx*dx+dy*dy
    endfunction

    private function SpikeFallsLoop takes nothing returns nothing
        local SpellStructSpike data=GetTimerData(GetExpiredTimer())
        set data.time=data.time-TIMEOUT*1.18
        if GetUnitTypeId(data.target)!=0 then
            call SetUnitFlyHeight(data.target,RMaxBJ(GetUnitDefaultFlyHeight(data.target),MAX_HEIGHT*data.time/0.37),0)
        endif
        if data.time<=0 then
            call RemoveDestructable(data.spike)
            call ReleaseTimer(GetExpiredTimer())
            call data.destroy()
        endif
    endfunction

    private function SpikeFallsBegin takes nothing returns nothing
        call SetDestructableAnimationSpeed(SpellStructSpike(GetTimerData(GetExpiredTimer())).spike,1)
        call TimerStart(GetExpiredTimer(),TIMEOUT,true,function SpikeFallsLoop)
    endfunction

    private function SpikeRisesLoop takes nothing returns nothing
        local SpellStructSpike data=GetTimerData(GetExpiredTimer())
        set data.time=data.time+TIMEOUT
        if GetUnitTypeId(data.target)!=0 then
            call SetUnitFlyHeight(data.target,RMaxBJ(GetUnitFlyHeight(data.target),MAX_HEIGHT*data.time/0.37),0)
            if data.time<=TIMEOUT then
                call SetUnitX(data.target,GetWidgetX(data.spike))
                call SetUnitY(data.target,GetWidgetY(data.spike))
            endif
        endif
        if data.time>=0.37 then
            call SetDestructableAnimationSpeed(data.spike,0)
            call TimerStart(GetExpiredTimer(),SpikeStopDuration[data.lvl],false,function SpikeFallsBegin)
        endif
    endfunction

    private function CreateOneSpike takes unit caster, real damage, real x, real y, integer lvl returns nothing
        local SpellStructSpike data=SpellStructSpike.create()
        local real range=99999
        local real tmprange
        local unit tmp
        set data.caster=caster
        set data.lvl=lvl
        set data.spike=CreateDestructable(DEST_ID,x,y,GetRandomReal(0,360),2,0)
        set data.time=0
        set data.target=null
        call GroupEnumUnitsInRange(TmpGroup,x,y,SPIKE_RADIUS+200,null)
        loop
            set tmp=FirstOfGroup(TmpGroup)
            exitwhen tmp==null
            call GroupRemoveUnit(TmpGroup,tmp)
            if IsUnitInRangeXY(tmp,x,y,SPIKE_RADIUS) and SpellFilter(caster,tmp) then
                set tmprange=SquaredDistance(x,y,GetUnitX(tmp),GetUnitY(tmp))
                if tmprange<=range then
                    set data.target=tmp
                    set range=tmprange
                endif
            endif
        endloop
        if GetUnitTypeId(data.target)!=0 then
            if UnitAddAbility(data.target,RAVEN_ID) then
                call UnitRemoveAbility(data.target,RAVEN_ID)
            endif
            call UnitDamageTarget(caster,data.target,damage,true,false,ATTACK,DAMAGE,null)
            if GetWidgetLife(data.target)>0.405 then
                call Stun.apply(data.target,0.7+StunDuration[lvl]+SpikeStopDuration[lvl],false)
            endif
        endif
        call TimerStart(NewTimerEx(data),TIMEOUT,true,function SpikeRisesLoop)
    endfunction

    private function SpellLoop takes nothing returns nothing
        local SpellStructMain data=GetTimerData(GetExpiredTimer())
        local integer i=0
        local real angle
        local real dist
        static if FOLLOW_CASTER then
            set data.x=GetUnitX(data.caster)
            set data.y=GetUnitY(data.caster)
        endif
        loop
            exitwhen i>=SpikesWaveAmount[data.lvl] or data.countleft<=0
            set angle=GetRandomReal(-bj_PI,bj_PI)
            set dist=GetRandomRadius(MIN_RADIUS,SpellRadius[data.lvl])
            call CreateOneSpike(data.caster,data.damage,data.x+dist*Cos(angle),data.y+dist*Sin(angle),data.lvl)
            set data.countleft=data.countleft-1
            set i=i+1
        endloop
        if data.countleft<=0 then
            call ReleaseTimer(GetExpiredTimer())
            call data.destroy()
        endif
    endfunction

    private function SpellCast takes nothing returns nothing
        local SpellStructMain data=SpellStructMain.create()
        local integer i=0
        local real angle
        local real dist
        set data.caster=GetTriggerUnit()
        set data.x=GetUnitX(data.caster)
        set data.y=GetUnitY(data.caster)
        set data.lvl=GetUnitAbilityLevel(data.caster,SPELL_ID)
        set data.damage=GetDamage(data.caster,data.lvl)
        set data.countleft=SpikesTotalAmount[data.lvl]
        loop
            exitwhen i>=SpikesWaveAmount[data.lvl] or data.countleft<=0
            set angle=GetRandomReal(-bj_PI,bj_PI)
            set dist=GetRandomRadius(MIN_RADIUS,SpellRadius[data.lvl])
            call CreateOneSpike(data.caster,data.damage,data.x+dist*Cos(angle),data.y+dist*Sin(angle),data.lvl)
            set data.countleft=data.countleft-1
            set i=i+1
        endloop
        if data.countleft>0 then
            call TimerStart(NewTimerEx(data),SpikesDelay[data.lvl],true,function SpellLoop)
        else
            call data.destroy()
        endif
    endfunction

    private function SpellCondition takes nothing returns boolean
        if GetSpellAbilityId()==SPELL_ID then
            call SpellCast()
        endif
        return false
    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 SpellCondition))
        call SetupDatas()
        set t=null
    endfunction

endscope

Keywords:
Impale, Spike, Stun
Contents

[SPELL] Spike Eruption (Map)

Reviews
00:42, 13th Nov 2012 Magtheridon96: Approved Improvements: - You can use one timer per spell cast rather than one timer per spike to improve performance greatly in actual maps.

Moderator

M

Moderator

00:42, 13th Nov 2012
Magtheridon96: Approved

Improvements:
- You can use one timer per spell cast rather than one timer per spike to improve performance greatly in actual maps.
 
Level 12
Joined
Jul 11, 2010
Messages
422
Thx.

About coding, there is an usefull function in that code : GetRandomRadius.
You should know that using an uniform random function (like GetRandomReal) to get the radius of a spell will tends to return a point close to the center. GetRandomRadius fixes that and really gives an uniform law for an annulus shape (or a circle if you use min=0).
 
- You can optimize the distance retrieval by getting rid of the SquareRoot call, which could be done by comparing the distance you achieved with range*range instead of range.

- The trigger in the Init function should be nulled because it's better to stay on the safe side and null all handles whether they extend agents or not, but that's totally up to you.

- Shouldn't those constants like 1.18, 0.37 and 0.7 be configurable? Or do you really need them to be hardcoded? :eek:

- You can damage into the struct so you wouldn't end up repeating the GetDamage function call more than once. (It's in the loop)

- Can you link to the regular TimerUtils by Vexorian? He deserves the recognition because it's /his/ resource after all. Also, the new TimerUtils supports everything that TimerUtilsEx does (Except for the fact that it does not return the timer data upon calling ReleaseTimer, which is useful for creating one-liners often :p)

- I noticed that you have public functions in there.
Why though? :eek:
 
Level 12
Joined
Jul 11, 2010
Messages
422
1) Ok.

2) I don't null it because it's a local trigger and hence cannot be destroyed, or so I think. It's just the same as not nulling TimerUtils's timer because they shouldn't be destroyed anyway. But I guess there might be cleanup systems that allows to destroy triggers like that so I will null it.

3) Those constants are model-related. I don't think it's a good idea to use another model for spikes (and those constants are not related to scale). 0.37 is the time needed for the spike to reach its highest position, 0.7 is the time for rise/fall and 1.18 is a speed-up factor because the spikes are slightly faster in their descending movement.

4) 'kay, nice one.

5) He should have updated his TimerUtils... I'll put both anyway.

6) Public functions are for functions that may have use outside of the scope (see my previous post). I mean, nobody will ever name a function "SpikeEruption_GetRandomRadius" so there can't possibly be any compatibility issue. Besides, it doesn't lengthens the jass converted function's name.

Tell me if you disagree on something ^^.
 
Last edited:
Top