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

Energy Fissure - v2.0.3

energy_fissure.png


Latest Version
2.0.3
Uploaded 1 July 2013

Introduction

noobieatmaps came up with this spell idea for my map that I was working on long ago. I coded it and decided to upload it.

Spell Description
Active: Creates an energy fissure at the target location which draws on raw energy. It sends out a pulse, travelling a certain distance. Once the pulse reaches the distance, all units that are between the pulse and the energy fissure are zapped, losing mana. The targets receive damage equal to the amount of mana burned.

Level 1: 300 pulse distance; 100 mana burned
Level 2: 400 pulse distance; 200 mana burned
Level 3: 500 pulse distance; 300 mana burned

Cooldown: 25 seconds
Mana cost: 150.

Screenshots

Target a point:
c559.jpg


An energy fissure is created at the location which sends out an energy pulse:
fjfl.jpg


When the pulse reaches its max distance, enemies between the pulse and energy fissure are zapped, causing them to lose mana:
41vy.jpg

Requirements

- JASS NewGen Pack
- SpellEffectEvent by Bribe
- T32 by Jesus4Lyf
- TimerUtils by Vexorian
- xefx by Vexorian

Links for the above systems can be found in the spell's code (below)

The Code


JASS:
library EnergyFissure /*
****************************************************************************************************
* 
*   E N E R G Y    F I S S U R E
*
*       ~ version 2.0.2
*       ~ coded by Mr_Bean
*       ~ idea by noobieatmaps
*
****************************************************************************************************
*
*    */ requires /*
*       ¯¯¯¯¯¯¯¯
*           */ SpellEffectEvent     /*  http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193
*           */ T32                  /*  http://www.thehelper.net/threads/timer32.118245/
*           */ TimerUtils           /*  http://www.wc3c.net/showthread.php?t=101322
*           */ xefx                 /*  http://www.wc3c.net/showthread.php?t=101150
*
****************************************************************************************************
*
*       Installation
*       ¯¯¯¯¯¯¯¯¯¯¯¯
*   - Make sure you've got (and installed properly) the above systems.
*   - Copy the "Energy Fissure" hero ability. Update SPELL_ID to its raw ID.
*   - Go through the configurables section below and change what you want to.
*
****************************************************************************************************
*
*       Configuration
*       ¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
    globals
    
        //==================================================
        //=== RAW IDS
        private constant integer SPELL_ID       = 'A000'    // Raw ID of the ability.
        
        //==================================================
        //=== VOID CONFIGURATION
        private constant real    VOID_HEIGHT    = 100.0     // Height of void.
        private constant real    VOID_VISION    = 300.0     // Vision area you get of the void. Set to 0.0 to disable.
        private constant real    DESTROY_DELAY  = 2.0       // Delay between units being zapped and the void being removed.
        
        // Model of void:
        private constant string  VOID_EFFECT    = "DarkVoid.mdx"
        
        //==================================================
        //=== WAVE CONFIGURATION
        private constant integer NUM_WAVES      = 8         // Number of waves created.
        private constant real    WAVE_DELAY     = 1.0       // Delay between void created and waves created.
        private constant real    WAVE_SPEED     = 300.0     // Speed of waves.
        
        // Model of waves:
        private constant string  WAVE_EFFECT    = "DeathWave.mdx"
        
        //==================================================
        //=== LIGHTNING CONFIGURATION
        private constant real    BOLT_DURATION  = 0.5       // Duration of lightning.
        private constant real    BOLT_HEIGHT    = 50.0      // Height of lightning.
        private constant string  BOLT_CODE      = "MBUR"    // Lightning code.
        
        // Effect created on zapped units:
        private constant string  BOLT_EFFECT    = "Abilities\\Spells\\NightElf\\ManaBurn\\ManaBurnTarget.mdl"
        
        // Attachment point for above effect:
        private constant string  BOLT_FX_ATTACH = "origin"
        
        //==================================================
        //=== FLOATING TEXT CONFIGURATION
        private constant boolean DISPLAY_TEXT   = true      // Display floating text showing damage dealt?
        private constant real    TEXT_ANGLE     = 90.0      // Movement angle.
        private constant real    TEXT_SPEED     = 64.0      // Movement speed.
        private constant real    TEXT_OFFSET    = 50.0      // Height above target.
        private constant real    TEXT_DURATION  = 2.0       // Duration.
        private constant real    TEXT_FADEPOINT = 1.0       // Fade time.
        private constant real    TEXT_HEIGHT    = 0.023     // Size.
        
        // Colour:
        private constant string  TEXT_COLOUR    = "|cff8000FF"
        
    endglobals
    
    //==================================================
    //=== DISTANCE OF WAVES PER LEVEL
    private function GetWaveDistance takes integer level returns real
        return (100.0 * level) + 200.0
    endfunction
    
    //==================================================
    //=== MANA BURNED PER LEVEL
    private function GetManaBurned takes integer level returns real
        return 100.0 * level
    endfunction
    
    //==================================================
    //=== DAMAGE FUNCTION SETUP
    // This is if you want to change attack/damage types or if you use a custom
    // function for damage detection or something similar.
    private function DealDamage takes unit source, unit target, real amount returns nothing
        call UnitDamageTarget(source, target, amount, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
    endfunction
    
/***************************************************************************************************
*
*       END OF CONFIGURATION AND DOCUMENTATION
*
***************************************************************************************************/

    private struct ManaBolt
        static constant real TEXT_X_VELOCITY = (Cos(TEXT_ANGLE * bj_DEGTORAD) * TEXT_SPEED) * 0.071 / 128.0
        static constant real TEXT_Y_VELOCITY = (Sin(TEXT_ANGLE * bj_DEGTORAD) * TEXT_SPEED) * 0.071 / 128.0
        //-----
        static location loc = Location(0.0, 0.0)
        //-----
        unit      source
        unit      target
        lightning bolt
        real      sx
        real      sy
        real      sz
        real      time     = BOLT_DURATION
        real      manaBurn
        //-----
        
        /**
         * Destroys the lightning and cleans up.
         */
        private method destroy takes nothing returns nothing
            call DestroyLightning(bolt)
            set source = null
            set target = null
            set bolt = null
            call deallocate()
        endmethod
        
        static if (DISPLAY_TEXT) then
            /**
             * Creates text above the target showing the damage dealt.
             * Only created if DISPLAY_TEXT is true.
             */
            private method displayDamage takes real amount returns nothing
                local texttag tt
                
                if (IsVisibleToPlayer(GetUnitX(target), GetUnitY(target), GetLocalPlayer())) then
                    set tt = CreateTextTag()
                    call SetTextTagText(tt, TEXT_COLOUR + "-" + I2S(R2I(amount)) + "|r", TEXT_HEIGHT)
                    call SetTextTagPosUnit(tt, target, TEXT_OFFSET)
                    call SetTextTagVelocity(tt, TEXT_X_VELOCITY, TEXT_Y_VELOCITY)
                    call SetTextTagPermanent(tt, false)
                    call SetTextTagLifespan(tt, TEXT_DURATION)
                    call SetTextTagFadepoint(tt, TEXT_FADEPOINT)
                    call SetTextTagVisibility(tt, true)
                    set tt = null
                endif
            endmethod
        endif
        
        /** 
         * Drains the target's mana (if it has) and deals damage accordingly.
         * Displays text if it's enabled.
         */
        private method zap takes nothing returns nothing
            local real curr
            local real damage = 0.0
            
            if (GetWidgetLife(target) > 0.405) then
                set curr = GetUnitState(target, UNIT_STATE_MANA)
                
                if (curr >= manaBurn) then
                    call SetUnitState(target, UNIT_STATE_MANA, curr - manaBurn)
                    set damage = manaBurn
                elseif (curr > 0.0) then
                    call SetUnitState(target, UNIT_STATE_MANA, 0.0)
                    set damage = curr
                endif
                
                if (damage != 0.0) then
                    call DealDamage(source, target, damage)
                endif
                
                static if (DISPLAY_TEXT) then
                    call displayDamage(damage)
                endif
            endif
        endmethod
        
        /**
         * Moves the lightning. If the duration expires, zaps the target and
         * destroys the bolt.
         */
        private method periodic takes nothing returns nothing
            call MoveLocation(loc, GetUnitX(target), GetUnitY(target))
            call MoveLightningEx(bolt, true, sx, sy, BOLT_HEIGHT + GetLocationZ(loc), /*
                */ GetUnitX(target), GetUnitY(target), BOLT_HEIGHT)
            set time = time - T32_PERIOD
            
            if (time <= 0.0) then
                call stopPeriodic()
                call zap()
                call destroy()
            endif
        endmethod
        
        implement T32x
        
        /**
         * Creates a new instance. Creates BOLT_EFFECT on the target and starts
         * counting down until the damage is dealt.
         */
        static method create takes unit s, real x, real y, unit t, real amt returns thistype
            local thistype this = allocate()
            
            set source = s
            set target = t
            set sx = x
            set sy = y
            call MoveLocation(loc, sx, sy)
            set sz = BOLT_HEIGHT + GetLocationZ(loc)
            set manaBurn = amt
            set bolt = AddLightningEx(BOLT_CODE, true, sx, sy, sz, GetUnitX(target), GetUnitY(target), BOLT_HEIGHT)
            call DestroyEffect(AddSpecialEffectTarget(BOLT_EFFECT, target, BOLT_FX_ATTACH))
            call startPeriodic()
            
            return this
        endmethod
    
    endstruct
    
    private struct EnergyFissure
        static constant real ANGLE_DIV = (2.0 * bj_PI) / NUM_WAVES
        static constant real INCREMENT = WAVE_SPEED * T32_PERIOD
        //-----
        static real array X_VELOCITY[NUM_WAVES]
        static real array Y_VELOCITY[NUM_WAVES]
        static group enumG = CreateGroup()
        static unit temp
        //-----
        unit   caster
        player owner
        timer  tim
        real   tx
        real   ty
        real   time
        real   aoe
        real   manaBurn
        xefx   pointFx
        fogmodifier fog
        //-----
        xefx array waves[NUM_WAVES]
        //-----
        
        /**
         * Destroys the void and each wave. Cleans up then deallocates.
         */
        private method destroy takes nothing returns nothing
            if (VOID_VISION > 0.0) then
                call DestroyFogModifier(fog)
                set fog = null
            endif
            
            call pointFx.destroy()
            set caster = null
            set owner = null
            set tim = null
            call deallocate()
        endmethod
        
        private static method cleanUp takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            
            call ReleaseTimer(GetExpiredTimer())
            call destroy()
        endmethod
        
        /**
         * The filter applied while enumerating units in range.
         */
        private method filterUnit takes unit u returns boolean
            return IsUnitEnemy(u, owner)                         /* Enemy of caster.
                */ and not IsUnitType(u, UNIT_TYPE_DEAD)         /* Alive.
                */ and     GetUnitTypeId(u) != 0                 /* Still alive.
                */ and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) /* Not magic immune.
                */ and not IsUnitType(u, UNIT_TYPE_STRUCTURE)    /* Not a structure. */
        endmethod
        
        /**
         * Zaps all units in range of the void, burning their mana and
         * dealing damage equal to the amount burned.
         */
        private method zapUnits takes nothing returns nothing
            // First, destroy wave effects:
            local integer i = 0
            
            loop
                exitwhen i == NUM_WAVES
                call waves[i].destroy()
                set i = i + 1
            endloop
            
            // Then zap units:
            call GroupEnumUnitsInRange(enumG, tx, ty, aoe, null)
            
            loop
                set temp = FirstOfGroup(enumG)
                exitwhen temp == null
                call GroupRemoveUnit(enumG, temp)
                
                if (filterUnit(temp)) then
                    call ManaBolt.create(caster, pointFx.x, pointFx.y, temp, manaBurn)
                endif
            endloop
        endmethod
        
        /**
         * Moves each wave. If they reach their max distance, units within
         * range get zapped, and the spell ends.
         */
        private method periodic takes nothing returns nothing
            local integer i = 0
            
            loop
                exitwhen i == NUM_WAVES
                set waves[i].x = waves[i].x + X_VELOCITY[i]
                set waves[i].y = waves[i].y + Y_VELOCITY[i]
                set i = i + 1
            endloop
            
            set time = time - T32_PERIOD
            
            if (time <= 0.0) then
                call stopPeriodic()
                call zapUnits()
                call TimerStart(tim, DESTROY_DELAY, false, function thistype.cleanUp)
            endif
        endmethod
        
        implement T32x
        
        /**
         * Creates each wave effect. Also starts moving them.
         */
        private static method createPulses takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 0
            
            loop
                exitwhen i == NUM_WAVES
                set waves[i] = xefx.create(tx, ty, i * ANGLE_DIV)
                set waves[i].fxpath = WAVE_EFFECT
                set i = i + 1
            endloop
            
            call startPeriodic()
        endmethod
        
        /**
         * Creates a new instance. Creates the void effect at the target point
         * and waits before creating the waves.
         */
        private static method create takes nothing returns thistype
            local thistype this = allocate()
            local integer level
            
            set caster = GetTriggerUnit()
            set owner = GetTriggerPlayer()
            set tx = GetSpellTargetX()
            set ty = GetSpellTargetY()
            set level = GetUnitAbilityLevel(caster, SPELL_ID)
            set manaBurn = GetManaBurned(level)
            set aoe = GetWaveDistance(level)
            set time = aoe / WAVE_SPEED
            set pointFx = xefx.create(tx, ty, 0.0)
            set pointFx.z = VOID_HEIGHT
            set pointFx.fxpath = VOID_EFFECT
            
            if (VOID_VISION > 0.0) then
                set fog = CreateFogModifierRadius(owner, FOG_OF_WAR_VISIBLE, tx, ty, VOID_VISION, true, false)
                call FogModifierStart(fog)
            endif
            
            set tim = NewTimerEx(this)
            call TimerStart(tim, WAVE_DELAY, false, function thistype.createPulses)
            
            return this
        endmethod
    
        /**
         * Stores the x and y velocities for each wave. Registers the spell
         * and preloads the effects.
         */
        private static method onInit takes nothing returns nothing
            local integer i = 0
            
            loop
                exitwhen i == NUM_WAVES
                set X_VELOCITY[i] = INCREMENT * Cos(i * ANGLE_DIV)
                set Y_VELOCITY[i] = INCREMENT * Sin(i * ANGLE_DIV)
                set i = i + 1
            endloop
            
            call RegisterSpellEffectEvent(SPELL_ID, function thistype.create)
            call Preload(VOID_EFFECT)
            call Preload(WAVE_EFFECT)
            call Preload(BOLT_EFFECT)
        endmethod
    
    endstruct

endlibrary

Changelog

v2.0.3
- Fixed a major bug that caused no units to get zapped (the unit filter was incorrect). Thanks to hodenhelm!

v2.0.2
- Fixed it so that the waves are destroyed as soon as they reach their max distance, instead of after DESTROY_DELAY (when the energy fissure is destroyed).
- Changed the unit filter so that it properly checks for dead units.

v2.0.1
- Texttags are now only visible to players who have vision of the area.
- Lightnings' heights are now calculated properly.
- The player now gains vision of the target point while the spell is active (can be disabled).
- Added a delay between the units being zapped and the "void" being destroyed.

v2.0
- Completely rewrote everything from scratch. It made me want to cry when I saw how badly I used to code.

v1.3.0
- More minor code improvements.
- More configurables.

v1.2.0
- Slightly improved the code.
- Added another caster to the test map.
- Added another spawn region for creeps in the test map.
- Added a new ability in the test map: Refresh (resets all ability cooldowns and restores mana).

v1.1.0
- Further improved the code (thanks Ruke).
- You can now configure the following things:
> How many waves make up each pulse.
> What the effects are that are created around the circumference of the energy fissure.
> How many of these effects are created.
> The effect that is created when the spell is cast.​
v1.0.1
- Slightly improved the code as per Bribe's suggestion.

v1.0.0
- Initial release.

Credits

- noobieatmaps -> spell idea
- JetFangInferno -> models used for the spell
For Code Improvements:
- Ruke
- Maker
- Bribe
- baassee

Keywords:
Mr_Bean, noobieatmaps, energy, fissure, void, mana, burn
Contents

Energy Fissure - v2.0.2 (Map)

Reviews
16th Dec 2011 Bribe: This is a recommended spell. Nice work! Your timeouts and "dummy count" stuff could be configurable. Think of things that can be configured, for example the thunderclap special effect. I think after making all these things...

Moderator

M

Moderator

16th Dec 2011
Bribe: This is a recommended spell. Nice work!

Your timeouts and "dummy count" stuff could be configurable. Think of things that can be configured, for example the thunderclap special effect. I think after making all these things configurable it should be "Highly Recommended" because I really like the spell.
 
Level 10
Joined
Sep 19, 2011
Messages
527
Nice spell.
Maybe you can try with T32 with short periods.

If a variable will never change his value, make it constant (look your globals).

Maybe you can make your filters configurables for the user (when you enumerate units in a group and work with them in the loop).

In the Timer_Actions3 method, the timer was not nulled.

You don't need null the trigger (you will never destroy it).

set D.owner = GetOwningPlayer(D.caster)

->

set D.owner = GetTriggerPlayer()

I think that's all.
Again, nice spell :).

Greetings.

EDIT: Only if you want, you can add a Preload() to your special effect to prevent lag in the first time :).
 
Last edited:
Level 17
Joined
Feb 11, 2011
Messages
1,860
Thanks Ruke!

Update:
- Further improved the code (thanks Ruke).
- You can now configure the following things:
> How many waves make up each pulse.
> What the effects are that are created around the circumference of the energy fissure.
> How many of these effects are created.
> The effect that is created when the spell is cast.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
No need to null u in Timer_Actions_3 as the loop exits when u == null.

(not(IsUnitType(u, UNIT_TYPE_DEAD)))
->
not IsUnitType(u, UNIT_TYPE_DEAD)
^Becomes more readable

In Timer_Actions_2, and Timer_Actions calculate degtorad here:
local real mod = 360 / EFFECT_COUNT * bj_DEGTORAD
and not in Cos and Sin.

Variables of type trigger need not be nulled, only if you destroy the trigger. I think I said to null them in the other spell of yours, but ignore that :)

Always place at least two casters in test map and you could apply something that restores mana/resets cooldowns.
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Update:
- Slightly improved the code.
- Added another caster to the test map.
- Added another spawn region for creeps in the test map.
- Added a new ability in the test map: Refresh (resets all ability cooldowns and restores mana).
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
-JNGP is not made by Vexorian. JASSHelper is.

-call RemoveGuardPosition(dummy) Why?

-You don't check if the enumed units are magic immune thus wastes a lot of dummies if cast on a group which all are magic immune.

-You should use Maker's lightning library and trigger the manaburn. Then you can also trigger custom mana burn values and damage.

-EFFECT_COUNT and WAVE_COUNT are both uppcase but not constants, maybe make a function instead?

-Because of this above, things like this 360 / EFFECT_COUNT * bj_DEGTORAD should be stored into a global.

-I although disagree with myself with the above point. Why use degrees in the first place? Use radians all the way aka 2PI / EFFECT_COUNT which should be stored into a constant.

-You can cast carrion swarm on places which are not pathable? Or am I wrong?

-This should have the debug keyword all over it (infront of each row).
JASS:
            if WAVE_INDEX <= 0 or WAVE_INDEX > 4 then
                call ClearTextMessages()
                call BJDebugMsg("|cffff0000Energy Fissure Error:|r WAVE_INDEX is set to an invalid value. Defaulting to 2.")
                set WAVE_COUNT = 8
            endif
            if EFFECT_INDEX <= 0 or EFFECT_INDEX > 4 then
                call ClearTextMessages()
                call BJDebugMsg("|cffff0000Energy Fissure Error:|r EFFECT_INDEX is set to an invalid value. Defaulting to 2.")
                set EFFECT_COUNT = 8
            endif

Else I couldn't find any more... fun to pick on. Good job.
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
baassee said:
call RemoveGuardPosition(dummy) Why?

Because if the caster is owned by a computer player, they will control the dummy, therefore preventing it from casting Manaburn. I have tested this...

baassee said:
You can cast carrion swarm on places which are not pathable? Or am I wrong?

I'm pretty sure you can't. I ran into this issue while I was coding this spell for the first time.

I will update the spell tomorrow. Any more things I can improve on?
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Update:
- More minor code improvements.
- More things you can configure. Among others, you can now configure how much mana is burned and how much damage is dealt. Check out "What You Can Configure".

EDIT: The next quick-fix release will fix the Manaburn effect from being visible in fogged areas. It will also move these lines: call SetTimerData(t, D) and call TimerStart(t, 0.5, false, function thistype.Timer_Actions_4) out of the loop.
 
Level 10
Joined
Sep 19, 2011
Messages
527
if SHOW_MANA_BURNED then

->

static if SHOW_MANA_BURNED then

set t = null
Don't null the trigger, is not required ;).

Try to add http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/

if IsUnitEnemy(u, D.owner) and not IsUnitType(u, UNIT_TYPE_DEAD) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then

It be awesome if this was configurable.

if DAMAGE_IS_INDEPENDENT then

->

static if DAMAGE_IS_INDEPENDENT then

Greetings :)
 
Level 10
Joined
Sep 19, 2011
Messages
527
Read the manual:

Static ifs

static ifs are like normal ifs, except that a) the condition must contain only constant booleans, the and operator and the not operator and b) They are evaluated during compile time. Which means that the code that is not matched to its condition is simply ignored.

JASS:
    library OptionalCode requires optional UnitKiller
        globals
            constant boolean DO_KILL_LIB = true
        endglobals

        function fun takes nothing returns nothing
            local unit u = GetTriggerUnit()
            //the following code may need to kill the unit
            //but is alternatively able to use the external
            //'UnitKiller' library to do the library.
            // ONLY when DO_KILL_LIB is true AND the
            // library UnitKiller is in the map.
            static if DO_KILL_LIB and LIBRARY_UnitKiller then
                //static if because if the UnitKiller
                // library was not in the map, using a normal
                // if would not remove this line of code and
                // therefore it would cause a syntax error.
                // (unable to find function UnitKiller)
                call UnitKiller(u)
            else
                call KillUnit(u)
            endif
        endfunction

    endlibrary

    library UnitKiller

        function UnitKiller(unit u)
            call BJDebugMsg("Unit kill!")
            call KillUnit(u)
        endfunction

    endfunction
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Update - v2.0:
- Completely rewrote everything from scratch. It made me want to cry when I saw how badly I used to code.

I would like to apologise to the Hive community for my poor coding back at the end of 2011. I thought that since I'm running a spell workshop, my resources should be up-to-scratch.
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Sigh, okay. I will fix those issues soon.

Update - v2.0.1:
- Texttags are now only visible to players who have vision of the area (thanks Maker).
- Lightnings' heights are now calculated properly (thanks Maker).
- The player now gains vision of the target point while the spell is active (can be disabled).
- Added a delay between the units being zapped and the "void" being destroyed.
 
Last edited:
Level 17
Joined
Feb 11, 2011
Messages
1,860
Update - v2.0.2:
- Fixed it so that the waves are destroyed as soon as they reach their max distance, instead of after DESTROY_DELAY (when the energy fissure is destroyed).
- Changed the unit filter so that it properly checks for dead units.

I also added screenshots that illustrate what the spell does.
 
Top