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

Mana Wave v1.4

Spell Look Like

Changelog
v1.0: First release version
v1.1: Bugs fixed, code optimized, name changed
v1.1b: Bugs fixed
v1.2: Leaks fixed, remove GetUnitTypeId on the onCast function.
v1.2b: Minor bugs fixed !
v1.3: Minor bugs fixed !, add some effect.
v1.4: Minor bugs fixed !
Spell Code
JASS:
//***********************************************************************************************************
//***********************************************************************************************************
//***********************************************************************************************************
//***********************************************************************************************************
//***********************************************************************************************************
// M A N A   W A V E
//                  By: Elphis
//          - Spell Information:
//                              - Calls forth a wave of energy that heals a target, if target belong to ally, 
//                                it will bounces to all nearby friendlies around 700 AOE, each bounces heals 100 mana. 
//                                But if the target belong to enemy, it will bounces to all nearby ememies around 700 AOE and 
//                                destroy 100 on the target, every time it bounces to enemy, each wave, mana desrtoy will decreasing by 20.
//          - Installation:
//                                - Import/copy Healing Mana code to your map
//                                - Import/copy the custom ability and unit to your map and change the SPELL_ID if needed
//                                - You may view the raw ID of the objects by pressing CTRL+D in the object editor
//                                - You may play with the configurables below
//***********************************************************************************************************
//***********************************************************************************************************
//***********************************************************************************************************
//***********************************************************************************************************
//***********************************************************************************************************
//***********************************************************************************************************
library ManaWave

    //**********************************CONFIGURABLE**********************************

    globals
        //Spell rawcode
        private constant    integer SPELL_ID = 'A000'
        //Spell period
        private constant    real    PERIODIC    =   .031250000
        //Area of effect
        private constant    real    AOE =   700.
        //Heal mana time intevar
        private constant    real    HEAL_INTEVAR    =   0.1
        //Mana heal count
        private constant    real    MANA_HEAL_BASE  =   100.
        //Mana destroy decrease every it link from enemy
        private constant    real    MANA_DESTROY_DECREASE = 20.
        //Lightning color fade timed
        private constant    real    LIGHTNING_FADE_INTEVAR = 0.02
        
        //***********LIGHTNING COLOR***********//
        private constant    real    LIGHTNING_COLOR_RED = 0.5
        private constant    real    LIGHTNING_COLOR_GREEN = 0.2
        private constant    real    LIGHTNING_COLOR_BLUE = 1.
        //***************************************
        
        //Allies buff count
        private constant    integer ALLIES_COUNT_BASE = 5
        //Lightning model
        private constant    string  LIGHTNING_MODEL =   "MFPB"
        //Effect when no unit in area of effect
        private constant    string  MODEL_EMPTY =   "Abilities\\Spells\\Human\\DispelMagic\\DispelMagicTarget.mdl"
        //Effect when buff allies
        private constant    string  BUFF_EFFECT =   "Abilities\\Spells\\Other\\Drain\\ManaDrainTarget.mdl"
        //Effect when buff enemies
        private constant    string  BUFF_EFFECT_ENEMY =   "Abilities\\Spells\\Other\\Drain\\ManaDrainCaster.mdl"
        //Effect linked of the lightning
        private constant    string  EFFECT_LINKED     = "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl"
        //Point attachment
        private constant    string  ATTACH_POINT =  "origin"
         //Point linked attachment
        private constant    string  ATTACH_POINT_LINKED =  "origin"
        
        //***********Non-Configurable***********//
        private             integer MUI = -1
        private             integer Plugin_MUI = -1
        private constant    group   G = CreateGroup()
        private constant    integer array Struct_Data
        private constant    integer array Plugin_Struct_Data
        private constant    timer   TIMER   =   CreateTimer()
        private constant    timer   TIMER_2 =   CreateTimer()
        private constant    location LOC    =   Location(0.,0.)
        //***************************************
    endglobals
    
    //**********************************END OF CONFIGURABLE**********************************//
    
    //**********************************DO NOT MODIFY ANYTHING BELOW**********************************//
    
    private struct ManaWave
        
        unit caster
        unit last_unit
        
        real intevar = 0.
        real mana
        
        boolean destroy_mana
        
        group g = CreateGroup()
        
        static method onPeriodic takes nothing returns nothing
            local thistype this
            local integer i = 0
            local unit r
            
            loop
                exitwhen i > MUI
                
                set this = Struct_Data[i]
                
                if intevar < HEAL_INTEVAR then
                    set intevar = intevar + PERIODIC
                else
                    set intevar = 0.
                    
                    set r = GroupPickRandomUnit(g)
                    
                    if r != null then
                        call GroupRemoveUnit(g,r)
                    
                        call ManaWave_ManaWavePlugin.add(LIGHTNING_MODEL,r,last_unit,mana,destroy_mana)
                        
                        if destroy_mana then
                            set mana = mana - MANA_DESTROY_DECREASE
                        endif
                        
                        set last_unit = r
                    else
                        set Struct_Data[i] = Struct_Data[MUI]
                        set Struct_Data[MUI] = -2
                        set MUI = MUI - 1
                        
                        if MUI == -1 then
                            call PauseTimer(TIMER)
                        endif
                        
                        call DestroyGroup(g)
                        set g = null
                        set caster = null
                        set last_unit = null
                        call destroy()
                    endif
                endif
                set i = i + 1
            endloop
        endmethod
        
        static method onCast takes nothing returns boolean
            local thistype this
            local unit f = GetSpellTargetUnit()
            local unit ct = GetTriggerUnit()
            local player owner = GetTriggerPlayer()
            local integer lvl
            local integer count
            local integer total_unit = 0
            local boolean destroy_mn = IsUnitEnemy(f,owner)
            
            //Checking unit around the target....
            
            call GroupEnumUnitsInRange(G,GetUnitX(f),GetUnitY(f),AOE,null)
            
            loop
                set f = FirstOfGroup(G)
                exitwhen f == null
                
                if f != ct and GetUnitState(f,UNIT_STATE_MAX_MANA) > 0. and not IsUnitType(f,UNIT_TYPE_DEAD) then
                    
                    if IsUnitEnemy(f,owner) == destroy_mn then
                        set total_unit = 1
                        exitwhen true
                    endif
                    
                endif
                
                call GroupRemoveUnit(G,f)
                
            endloop
            
            //Checking conditions before run this spell....
            
            if total_unit == 0 then
                call DestroyEffect(AddSpecialEffect(MODEL_EMPTY,GetUnitX(ct),GetUnitY(ct)))
                set ct = null
                set f = null
                return false
            endif
            
            set this = allocate()
            
            set total_unit = 0
            set MUI = MUI + 1
            set Struct_Data[MUI] = this
            set caster = ct
            set last_unit = GetSpellTargetUnit()
            set lvl = GetUnitAbilityLevel(caster,SPELL_ID)
            
            set mana = MANA_HEAL_BASE*lvl
            set count = ALLIES_COUNT_BASE+lvl
            
            set destroy_mana = destroy_mn
            
            call GroupEnumUnitsInRange(G,GetUnitX(f),GetUnitY(f),AOE,null)
            
            loop
                set f = FirstOfGroup(G)
                exitwhen f == null
                
                if f != caster and GetUnitState(f,UNIT_STATE_MAX_MANA) > 0. and not IsUnitType(f,UNIT_TYPE_DEAD) then
                    
                    if IsUnitEnemy(f,owner) == destroy_mana then
                        call GroupAddUnit(g,f)
                    endif
                    
                    set total_unit = total_unit + 1
                endif
                
                call GroupRemoveUnit(G,f)
                
            endloop
            
            set count = total_unit - count
            
            set f = GroupPickRandomUnit(g)
            
            if f != null then
            
                call ManaWave_ManaWavePlugin.add(LIGHTNING_MODEL,last_unit,caster,mana,destroy_mana)
                
                call GroupRemoveUnit(g,last_unit)
                
                loop
                    exitwhen count < 0
                    
                    set f = GroupPickRandomUnit(g)
                    call GroupRemoveUnit(g,f)
                    
                    set count = count - 1
                endloop
                
            endif
            
            if MUI == 0 then
                call TimerStart(TIMER,PERIODIC,true,function thistype.onPeriodic)
            endif
            
            set owner= null
            set ct = null
            set f = null
            
            return false
        endmethod
        
        static method onInit takes nothing returns nothing
            local integer i = 0
            local trigger t = CreateTrigger()
            //
            loop
                exitwhen i > 15
                call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
                set i = i + 1
            endloop
            //
            call TriggerAddCondition(t,function thistype.onCast)
            set t = null
        endmethod
        
    endstruct
    
    public struct ManaWavePlugin
        
        private unit plugin_unit
        private unit plugin_caster
        
        private real plugin_color
        private real plugin_mana
        private real plugin_intevar
        private real plugin_fade = 1.
        
        private effect plugin_effect
        
        private lightning plugin_lightning
        
        private boolean plugin_destroy
        
        static method onPeriodic takes nothing returns nothing
            local thistype this
            local integer i = 0
            local real zc
            local real zu
            local real xc
            local real yc
            local real xu
            local real yu
            
            loop
                exitwhen i > Plugin_MUI
                
                set this = Plugin_Struct_Data[i]
                
                set xc = GetUnitX(plugin_caster)
                set yc = GetUnitY(plugin_caster)
                set zu = GetUnitX(plugin_unit)
                set yu = GetUnitY(plugin_unit)
                
                call MoveLocation(LOC,xc,yc)
                set zc = GetLocationZ(LOC)+GetUnitFlyHeight(plugin_caster)
                call MoveLocation(LOC,xu,yu)
                set zc = GetLocationZ(LOC)+GetUnitFlyHeight(plugin_unit)
                
                call MoveLightningEx(plugin_lightning,true,xc,yc,zc,zu,yu,zu)
                
                if plugin_fade > 0. then
                    set plugin_fade = plugin_fade - LIGHTNING_FADE_INTEVAR 
                    call SetLightningColor(plugin_lightning,LIGHTNING_COLOR_RED,LIGHTNING_COLOR_GREEN,LIGHTNING_COLOR_BLUE,plugin_fade)
                    if not plugin_destroy then
                        call SetUnitState(plugin_unit,UNIT_STATE_MANA,GetUnitState(plugin_unit,UNIT_STATE_MANA)+plugin_intevar)
                    else
                        call SetUnitState(plugin_unit,UNIT_STATE_MANA,GetUnitState(plugin_unit,UNIT_STATE_MANA)-plugin_intevar)
                    endif
                else
                    set Plugin_Struct_Data[i] = Plugin_Struct_Data[Plugin_MUI]
                    set Plugin_Struct_Data[Plugin_MUI] = -2
                    set Plugin_MUI = Plugin_MUI - 1
                    
                    if Plugin_MUI == -1 then
                        call PauseTimer(TIMER_2)
                    endif
                    
                    call DestroyEffect(plugin_effect)
                    call DestroyLightning(plugin_lightning)
                    
                    set plugin_effect = null
                    set plugin_lightning = null
                    set plugin_unit = null
                    set plugin_caster = null
                    
                    call destroy()
                endif
                
                set i = i + 1
            endloop
        endmethod
        
        static method add takes string lmodel,unit u,unit caster,real mana,boolean destroy returns nothing
        
            local thistype this = allocate()
            
            set Plugin_MUI = Plugin_MUI + 1
            set Plugin_Struct_Data[Plugin_MUI] = this
            
            set plugin_lightning = AddLightningEx(lmodel,true,GetUnitX(caster),GetUnitY(caster),GetUnitFlyHeight(caster),GetUnitX(u),GetUnitY(u),GetUnitFlyHeight(u))
            call SetLightningColor(plugin_lightning,LIGHTNING_COLOR_RED,LIGHTNING_COLOR_GREEN,LIGHTNING_COLOR_BLUE,1.)
            set plugin_unit = u
            set plugin_caster = caster
            set plugin_mana = mana
            set plugin_intevar = plugin_mana*LIGHTNING_FADE_INTEVAR
            set plugin_destroy = destroy
            call DestroyEffect(AddSpecialEffectTarget(EFFECT_LINKED,u,ATTACH_POINT_LINKED))
            if plugin_destroy then
                set plugin_effect = AddSpecialEffectTarget(BUFF_EFFECT_ENEMY,u,ATTACH_POINT)
            else
                set plugin_effect = AddSpecialEffectTarget(BUFF_EFFECT,u,ATTACH_POINT)
            endif
            
            if Plugin_MUI == 0 then
                call TimerStart(TIMER_2,PERIODIC,true,function thistype.onPeriodic)
            endif
        endmethod
        
    endstruct
    
endlibrary
Spell Screen Shot
untit221.jpg
Keywords:
mana,wave
Contents

Just another Warcraft III map (Map)

Reviews
08:17, 10th Feb 2014 BPower: The coding looks now far better and the objects are fair enough. What's still left undone is the way you determine the z offset of the lightning. Using GetUnitFlyHeight only will look bad if one adds cliffs or valleys...

Moderator

M

Moderator

08:17, 10th Feb 2014
BPower:
Review
The coding looks now far better and the objects are fair enough.
What's still left undone is the way you determine the z offset of the lightning.
Using GetUnitFlyHeight only will look bad if one adds cliffs or valleys to the map.
The correct way would be:

JASS:
call MoveLocation(loc, GetUnitX(unit), GetUnitY(unit))
set z  = GetLocationZ(loc) + GetUnitFlyHeight(unit)
04.02.2014:
BPower:


You have unit allies as struct member, but it isn't used at all.

Per spell cast you start 46 needless timers call TimerStart(TIMER,0.,false,function ManaWave.onPeriodic),
because unlike the ManaWavePluggin struct the corresponding ManaWave struct instance is already deallocated.

19:04, 30th Jan 2014:
Make those two changes I mentioned:
http://www.hiveworkshop.com/forums/spells-569/mana-wave-v1-2-a-246277/?prev=status%3Dp%26of%3Ddateline%26o%3DASC%26imporder%3D1#post2479763
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Use GroupPickRandomUnit
Or can you point out one valid argument, why you made an excat copy of an existing function.

Well I have one against it. It prolongs your code needlessly.

Edit: Same issue for call TriggerRegisterAnyUnitEventBJ

What do you think about
JASS:
            if IsUnitEnemy(f,caster_owner) then
                set destroy_mana = true
            else
                set destroy_mana = false
            endif
--> set destroy_mana = IsUnitEnemy(f, caster_owner) :thumbs_up:

use one static group for the enumeration instead of creating a local Group G per cast instance.

JASS:
                    if destroy_mana then
                        if IsUnitEnemy(f,caster_owner) then
                            call GroupAddUnit(g,f)
                        endif
                        
                        set total_unit = total_unit + 1
                    else
                        if IsUnitAlly(f,caster_owner) then
                            call GroupAddUnit(g,f)
                        endif
                        
                        set total_unit = total_unit + 1
                    endif
move total_unit outside the condition and delete one of these.

set caster_owner = null is not nulled in all cases.

I guess it is not mandatory, but still JPAG.

You never destroy nor null group this.g
You have a unit handle leak --> local unit r
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
I don't think you are right here. CnP from the JassHelper manual:
  • The function must be a one-liner.
  • Every argument must be evaluated once and only once by the function, in the same order as they appear in the arguments list.
Also not every BJ is trash.

Edit:
Also BJs are just ugly looking in code IMO.
Because of the color and the cursive text? ... Then change the settings of the highlighting in the editor or in the styles.ini :)
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
If you target yourself while no unit is near the whole skript will fail. Afterwards you can't cast the spell anymore.

Also I think you could optimize the code by alot. For instance:
JASS:
            if IsUnitEnemy(f,caster_owner) then
                set destroy_mana = true
            else
                set destroy_mana = false
            endif
           
            call GroupEnumUnitsInRange(G,GetUnitX(f),GetUnitY(f),AOE,null)
           
            loop
                set f = FirstOfGroup(G)
                exitwhen f == null
               
                if f != caster and GetUnitState(f,UNIT_STATE_MAX_MANA) > 0. and GetWidgetLife(f) > .0425 then
                    if destroy_mana then
                        if IsUnitEnemy(f,caster_owner) then
                            call GroupAddUnit(g,f)
                        endif
                       
                        set total_unit = total_unit + 1
                    else
                        if IsUnitAlly(f,caster_owner) then
                            call GroupAddUnit(g,f)
                        endif
                       
                        set total_unit = total_unit + 1
                    endif
                endif
               
                call GroupRemoveUnit(G,f)
            endloop
           
            set integer_loop = total_unit - count
---->
JASS:
            set destroy_mana = IsUnitEnemy(f,caster_owner)
            /*
            *   static group enu = CreateGroup()
            */
            call GroupEnumUnitsInRange(enu ,GetUnitX(f),GetUnitY(f),AOE,null)
            
            loop
                set f = FirstOfGroup(enu)
                exitwhen f == null
                /*
                *     UnitAlive is faster than GetWidgetLife
                */
                if f != caster and GetUnitState(f,UNIT_STATE_MAX_MANA) > 0. and UnitAlive(f) then
                    /*
                    *     ^^
                    */
                    if IsUnitEnemy(f,caster_owner) == destroy_mana then
                        call GroupAddUnit(g, f)
                        set total_unit = total_unit + 1
                    endif
                    
                endif
                call GroupRemoveUnit(enu,f)
            endloop
            /*
            *     re-use count
            */
            set count = total_unit - count
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
I truly suggest to change the name -> Healing Mana is a bit strange. Healing refers to health whereas mana refers to mana ^^
So why not Mana Replenish or something like this.

I'll review the code later ;)

EDIT :
JASS:
static method onInit takes nothing returns nothing
            local integer i = 0
            local trigger t = CreateTrigger()
            //
            loop
                exitwhen i > bj_MAX_PLAYERS
                call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
                set i = i + 1
            endloop
            //
            call TriggerAddCondition(t,function thistype.onCast)
            set t = null
        endmethod
If you truly want to do so -> bj_MAX_PLAYERS = 12 ;)
So just replace the constant by 12.

JASS:
if IsUnitEnemy(f,caster_owner) then
                set destroy_mana = true
            else
                set destroy_mana = false
            endif
->
set destroy_mana = IsUnitEnemy(f, caster_owner)

GetWidgetLife(f) > .0425 is not enough to detect living units. Either use native UnitAlive takes unit id returns boolean or IIRC IsUnitType(u, UNIT_TYPE_DEAD) or GetUnitTypeId(u)==0

I suggest to start MUI and PLUGIN_MUI at -1 to use the 0 index of arrays ;)

You can private the second struct, you'll be able to access its function even if it is private because you're inside the scope !

It is a fast one if you think I said something wrong feel free to say ^^


EDIT FOR BPower : One group per cast isn't that slow to have a static one.
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
JASS:
set ct = null
set f = null           
call DestroyEffect(AddSpecialEffect(MODEL_EMPTY,GetUnitX(ct),GetUnitY(ct)))
Well that one doesn't work out so good ^^ --> null ct after you tried to get its coordinates.
Create group g in onCast, because it is not required if there are no targets around.
Also, but not nessesary to get it approved:
  • You could use TriggerRegisterAnyUnitEventBJ instead of writing it by yourself.
  • caster_owner could be owner, but that just a personal preference of mine to avoid overlong local variable names
  • TIMER shouldn't be in the configurable part.
 
Top