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

[vJass] Shadow Explosion v1.02b

  • Like
Reactions: TriggerHappy
This is one of the two spells, I made for a german Spellcontest.

It's written in vJass, MUI und MPI and follows the JESP.

Discription:
Creates a circle of shadow missiles arround the target point over 1.25 seconds. After all missiles are created, they jump to the target point. When all missiles hit the point, they will explode, which deals damage over time to all enemies in the explosion AoE. The explosion crushes the earth, creating multiple splitters of earth out of the explosion, flying to random points arround the explosion, damaging enemies arround their impact area.


v1.02b:
- Changed from CreateTimer to NewTimer :/
v1.02:
- fixed some TimerUtils issues
- removed unrequired librarys
- added MyFunctions to the Spell Library



JASS:
/*
                                SHADOW EXPLOSION
                                    by Kricz
                                    
                                    Version:
                                       1.01
                                    
                                    
                                    Credits:
                    Vexorian, cedi, peq, D1000, moyack & SkriK
                        
                                    Requires:
                 TimerUtils, xebasic, xecast, xedamage, xepreload
            
                                  Discription:
        Creates a circle of shadow missiles arround the target area, jumping
      to the target point. When they reach the middle, a huge explosion appears,
    damaging enemies in the area arround it over time. After the explosion expires,
   shadow splitters spawn out of the explosion, flying to random point arround the
                area, dealing AoE Damage in their impact area.
   
*/

scope ShadowExplosion ////needs TimerUtils, xedamage, xepreload

globals
    /*MAIN SETUP*/
        //The Spell Id of the Spell
        private constant integer SPELL_ID           = 'A000'
        //The Number of Levels for the Spell
        private constant integer LEVELS             = 3
        
        //Should all destructibles/trees be destroyed by the explosion and the splitters?
        private constant boolean DESTROY_TREES      = true
    /*END OF MAIN SETUP*/
    
    /*MISSILE SETUP*/
        //The model of the missiles
        private constant string MISSILE_MODEL       = "Abilities\\Spells\\Items\\OrbCorruption\\OrbCorruptionMissile.mdl"
        //The size of the missiles
        private constant real MISSILE_SIZE          = 2.25         
        
        //The heigh of the missiles
        private constant real MISSILE_HEIGH         = 55.00
        //The duration, unitl all missiles are created at the start
        private constant real CREATE_INTERVAL       = 1.25
        
        //The number of missiles at the start
        private constant integer MISSILE_COUNT      = 25 //15 is also good, but 30 looks better for me^^
        //The start-speed of the missiles
        private constant real MISSILE_START_SPEED   = 200.00
        
        //The end-speed of the missiles
        private constant real MISSILE_END_SPEED     = 700.00
        //The distance between the casting point and the created missiles
        private constant real MISSILE_DISTANCE      = 500.00
        
        /*ONLY 3D MODE SETUPS*/
        
            //If this is true, the missiles will "jump" to the middle ;)
            private constant boolean MISSILE_3D_MODE    = true
            //The maximal heigh of the missiles
            private constant real MISSILE_3D_HEIGH      = 325.00
            
            //Should the Z-Angle also be changed while jumping? (Looks cooler)
            private constant boolean MISSILE_Z_ANGLE    = true
            
        /*END OF 3D MODE SETUPS*/
        
    /*END OF MISSILE SETUP*/
    
    
    /*SPLITTER SETUP*/
        //The model of the splitters
        private constant string SPLITTER_MODEL      = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl"
        //The size of the splitter
        private constant real SPLITTER_SIZE         = 0.85                    
        //The start heigh of the splitter
        
        private constant real SPLITTER_START_HEIGH  = 10.00
        //The end heigh of the splitter
        private constant real SPLITTER_END_HEIGH    = 15.00
        
        //The start-speed of the splitter
        private constant real SPLITTER_START_SPEED  = 255.00
        //The end-speed of the splitter
        private constant real SPLITTER_END_SPEED    = 485.00
        
        //The minimum distance between explosion and splitter impact point
        private constant real SPLITTER_MIN_DISTANCE = 125.00
        //The maximum distance between explosion and splitter impact point
        private constant real SPLITTER_MAX_DISTANCE = 325.00
        
        //The minimum heigh of the splitters
        private constant real SPLITTER_MIN_HEIGH    = 200.00
        //The maximum heigh of the splitters
        private constant real SPLITTER_MAX_HEIGH    = 445.00
        
        //Should the Z-Angle also be changed while flying? (Looks cooler)
        private constant boolean SPLITTER_Z_ANGLE   = true
        //How much splitters can be summoned maximum? (Don't make this value smaller then any value of SPLITTERS[X])
        private constant integer MAX_SPLITTERS      = 25
    /*END OF SPLITTER SETUP*/
    
    
    /*EXPLOSION SETUP*/
        //The model of the explosion
        private constant string EXPLOSION_MODEL     = "Effects\\ShadowExplosion.mdl"
        //The duration of the explosion (after that, no damage will be dealed and the splitters will be summoned)
        private constant real EXPLOSION_DURATION    =  0.5
    /*END OF EXPLOSION SETUP*/
    
    
     //Don't change anything here!
    //They are for private using!
    
    private real array  EXPLOSION_DMG[LEVELS]
    private real array  EXPLOSION_AOE[LEVELS]
    private real array EXPLOSION_SIZE[LEVELS]
    private integer array   SPLITTERS[LEVELS]
    private real array   SPLITTER_AOE[LEVELS]
    private real array   SPLITTER_DMG[LEVELS]
    
endglobals

    //Setup for the damage dealt over the duration in the circle
    private function SETUP_EXPLOSION_DMG takes nothing returns nothing
        set EXPLOSION_DMG[1] = 150.00
        set EXPLOSION_DMG[2] = 225.00
        set EXPLOSION_DMG[3] = 300.00
    endfunction
    
    //Setup for the AoE, where units get damaged on the explosion
    private function SETUP_EXPLOSION_AOE takes nothing returns nothing
        set EXPLOSION_AOE[1] = 275.00
        set EXPLOSION_AOE[2] = 325.00
        set EXPLOSION_AOE[3] = 375.00
    endfunction
    
    //Setup for the model-scale of the explosion dummy
    private function SETUP_EXPLOSION_SIZE takes nothing returns nothing
        set EXPLOSION_SIZE[1] = 2.00
        set EXPLOSION_SIZE[2] = 2.25
        set EXPLOSION_SIZE[3] = 2.50
    endfunction
    
    //Setup for the summoned splitter each level
    private function SETUP_SPLITTERS takes nothing returns nothing
        set SPLITTERS[1] = 15
        set SPLITTERS[2] = 20
        set SPLITTERS[3] = 25
    endfunction
    
    //Setup for the damage AoE of each splitter
    private function SETUP_SPLITTER_AOE takes nothing returns nothing
        set SPLITTER_AOE[1] =  45.00
        set SPLITTER_AOE[2] =  60.00
        set SPLITTER_AOE[3] =  75.00
    endfunction
    
    //Setup for the dealt damage by each splitter
    private function SETUP_SPLITTER_DMG takes nothing returns nothing
        set SPLITTER_DMG[1] = 30.00
        set SPLITTER_DMG[2] = 45.00
        set SPLITTER_DMG[3] = 60.00
    endfunction
    
    //Setup for the Damage-Options
    //! textmacro SE_DAMAGE_OPTIONS
        set .dmg.dtype  = DAMAGE_TYPE_FIRE
        set .dmg.atype  = ATTACK_TYPE_HERO
        set .dmg.wtype  = WEAPON_TYPE_WHOKNOWS
    //! endtextmacro
    
    
 /**************************/   
/*END OF USER CONFIGURATION!*/
 /**************************/
 
  
//Don't change anything below here!!

private function ParabolaZ takes real ZStart, real ZEnd, real High, real MaxDist, real Distance returns real
  local real A = ( 2 * ( ZStart + ZEnd ) - 4 * High ) / ( MaxDist * MaxDist )
  local real B = ( ZEnd - ZStart - A * MaxDist * MaxDist ) / MaxDist
  return A * Distance * Distance + B * Distance + ZStart
endfunction

private function ParabolaZalpha takes real ZStart, real ZEnd, real High, real MaxDist, real Distance returns real
  local real A = ( 2 * ( ZStart + ZEnd )- 4 * High ) / ( MaxDist * MaxDist )
  local real B = ( ZEnd - ZStart - A * MaxDist * MaxDist ) / MaxDist
  return Atan( 2 * A * Distance + B)
endfunction


private keyword missile
private keyword splitter


private struct spell
    unit caster = null                      //The caster
    delegate xefx explosionFX = 0           //The explosion xefx
    real px = 0.00                          //The X of the cast point
    real py = 0.00                          //The Y of the cast point
    integer lvl = 0                         //The level of the spell
    integer count = 0                       //Counts the summoned missiles
    real exp_counter = 0.00                 //Counts the seconds of the explosion
    timer t = null                         //The used timer
    real counter = 0.00                     //This real is used to check the time for creating the missiles
    integer mode = 0                        //The mode for the periodic method (0 = missiles, 1 = explosion, 2 = splitter)
    real middlez = 0.00                     //The terrain heigh of the middle point
    boolean explosion = false               //Used to check if it's time for the explosion
                                            //Used to save the actual explosion animation speed
    missile array missiles[MISSILE_COUNT]   //The missiles at the start
    splitter array splitters[MAX_SPLITTERS]
                                            //The splitters created after the explosion
    
    static real interval = CREATE_INTERVAL / MISSILE_COUNT
    static group tempgroup = CreateGroup()  //Group to detect units each interval
    static xedamage dmg                     //Used to deal damage
    static boolexpr filter                  //filter for tempgroup
    static spell temp                       //required by the filter
    static location loc = Location(0., 0.)  //Required location to check Terrain Z-Values
                                            
static method create takes unit caster, real x, real y, integer lvl returns spell
    local spell this = spell.allocate()
    set .caster = caster
    set .lvl = lvl
    set .px = x
    set .py = y
    set .t = NewTimer()
    call MoveLocation(.loc, .px, .py)
    set .middlez = GetLocationZ(.loc)
    set .explosionFX = xefx.create(.px, .py, 0.00)
    set .scale = EXPLOSION_SIZE[.lvl]
    debug call BJDebugMsg(GetUnitName(.caster) + " casted Shadow Explosion, creating datas...")
    call SetTimerData(.t, this)
    call TimerStart(.t, .interval, true, function spell.createMissiles)
    return this
endmethod

static method createMissiles takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local spell this = GetTimerData(t)
    if .count >= MISSILE_COUNT then
        call PauseTimer(.t)
        debug call BJDebugMsg("All missiles are created now. Starting the running the timer with the method periodic.")
        call TimerStart(.t, XE_ANIMATION_PERIOD, true, function spell.periodic)
        return
    else
        set .missiles[.count] = missile.create(this)
        set .count = .count + 1
    endif    
endmethod

static method actions takes nothing returns boolean
    local unit caster = GetTriggerUnit()
    local integer lvl = GetUnitAbilityLevel(caster, SPELL_ID)
    local real x = GetSpellTargetX()
    local real y = GetSpellTargetY()
    if GetSpellAbilityId() == SPELL_ID then
        call spell.create(caster, x, y, lvl)
    endif
    set caster = null
    return false
endmethod

//This method is called the wohle spell over every interval and moves missiles, creates the explosions and so on
static method periodic takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local spell this = GetTimerData(t)
    local integer i = 0
    local unit u = null
    if .mode <= 0 then
        //move missiles and check for explosion
        loop
            exitwhen i >= MISSILE_COUNT or .explosion
            call .missiles[i].move()
            set i = i + 1
        endloop
        set i = .count - 1
        // if explosion is true, destroy missiles and create explosion
        if .explosion then
            loop
                exitwhen i < 0
                call .missiles[i].destroy()
                set .count = .count - 1
                set i = i - 1
            endloop
            //create the explosion dummy, destroy trees if it wanted and set the mode to 1 (explosion mode)
            set .fxpath = EXPLOSION_MODEL
            call .explosionFX.destroy()
            if DESTROY_TREES then
                call .dmg.damageDestructablesAOE(.caster, .px, .py, EXPLOSION_AOE[.lvl], 1000000.00) //Found no better way with xedamage^^
            endif
            debug call BJDebugMsg("Missiles reached the middle, starting explosion now.")
            set .mode = 1
            return
        endif
    elseif .mode == 1 then
        //checks the counter for the explosion
        if .exp_counter < EXPLOSION_DURATION then
            set .exp_counter = .exp_counter + XE_ANIMATION_PERIOD
            set .temp = this
            call GroupEnumUnitsInRange(.tempgroup, .px, .py, EXPLOSION_AOE[.lvl], .filter)
            //damage units near the explosion
            loop
                set u = FirstOfGroup(.tempgroup)
                exitwhen u == null
                call .dmg.damageTarget(.caster, u, EXPLOSION_DMG[.lvl] / EXPLOSION_DURATION * XE_ANIMATION_PERIOD)
                call GroupRemoveUnit(.tempgroup, u)
                set u = null
            endloop
            call GroupClear(.tempgroup)
        //if explosion is done, create the spiltters
        return
        else
            debug call BJDebugMsg("Explosion is over, creating splitters now.")
            set .mode = 2
            loop
                exitwhen .count >= SPLITTERS[.lvl]
                set .splitters[.count] = splitter.create(this)
                set .count = .count + 1
            endloop
            return
        endif
    elseif .mode >= 2 then
        if .count <= 0 then
            call .destroy()
            debug call BJDebugMsg("No splitters left, destroying datas.")
            return
        else
            set i = .count - 1
            loop
                exitwhen i < 0        
                if not .splitters[i].Control() then
                    set .count = .count - 1
                    if .count < 0 then
                        set .count = 0
                    else
                        set .splitters[i] = .splitters[.count]
                    endif
                endif
                set i = i - 1
            endloop
        endif
    endif
endmethod

//Destroys the Mainstruct and the Timer
method onDestroy takes nothing returns nothing
    call ReleaseTimer(.t)
endmethod

//This is the filter used to check for enemy units and so on...
private static method FilterFunc takes nothing returns boolean
    return GetFilterUnit() != spell.temp.caster and GetWidgetLife(GetFilterUnit()) > 0.405 and not IsUnitType(GetFilterUnit(), UNIT_TYPE_RESISTANT) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(spell.temp.caster))
endmethod

//Initialization of the Spell, same like the normal initializers, just in a struct
static method onInit takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0
    
    set .dmg = xedamage.create()
    set .filter = Condition(function spell.FilterFunc)
    
    //adding Events and Actions
    loop
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_CAST, null)
        set i = i + 1
        exitwhen i == bj_MAX_PLAYER_SLOTS
    endloop
    call TriggerAddCondition(t, Condition(function spell.actions)) //cause conditions are faster than actions ;)
    
    call SETUP_EXPLOSION_DMG()
    call SETUP_EXPLOSION_AOE()
    call SETUP_EXPLOSION_SIZE()
    call SETUP_SPLITTERS()
    call SETUP_SPLITTER_AOE()
    call SETUP_SPLITTER_DMG()
    //! runtextmacro SE_DAMAGE_OPTIONS()
    
    call Preload(MISSILE_MODEL)
    call Preload(SPLITTER_MODEL)
    set t = null
endmethod

endstruct

//This struct is for the missiles, created in the circle at the start
private struct missile
    delegate xefx model = 0              //the missile itself
    delegate spell root = 0             //the struct the missiles are from
    real angle = 0.00                   //the angle of the missile
    real speed = MISSILE_START_SPEED    //The actual speed of the missiles
    real dist = 0.00                    //The distance the missile has moved(required by the 3D-Mode)
    
    static real ac = (MISSILE_END_SPEED - MISSILE_START_SPEED) / (MISSILE_DISTANCE / ((MISSILE_END_SPEED + MISSILE_START_SPEED) /2)) * XE_ANIMATION_PERIOD
                                        //The acceleration of the missiles
                                        
//Creates a new missile and attaches it to the main struct..
static method create takes spell root returns missile
    local missile this = missile.allocate()
    local real mx = 0.00
    local real my = 0.00
    local real angle = 0.00
    set .root = root
    set angle = I2R(.count) / MISSILE_COUNT * 2 * bj_PI
    set mx = .px + MISSILE_DISTANCE * Cos(angle)
    set my = .py + MISSILE_DISTANCE * Sin(angle)
    set .angle = Atan2(.py - my, .px - mx)
    set .model = xefx.create(mx, my, .angle)
    debug call BJDebugMsg("Creating new missile with index " + I2S(.count) + " and an angle of " + I2S(R2I(angle * bj_RADTODEG + 0.5)) + ".")
    set .fxpath = MISSILE_MODEL
    set .z = MISSILE_HEIGH
    set .scale = MISSILE_SIZE
    return this
endmethod

//moves the missiles each interval
method move takes nothing returns nothing
    if .speed < MISSILE_END_SPEED and .dist < MISSILE_DISTANCE then
        set .speed = .speed + .ac
        set .dist = .dist + .speed * XE_ANIMATION_PERIOD
        set .x = .x + .speed * XE_ANIMATION_PERIOD * Cos(.angle)
        set .y = .y + .speed * XE_ANIMATION_PERIOD * Sin(.angle)
        if MISSILE_3D_MODE then
            set .z = ParabolaZ(MISSILE_HEIGH, MISSILE_HEIGH, MISSILE_3D_HEIGH, MISSILE_DISTANCE, .dist)
            if MISSILE_Z_ANGLE then
                set .zangle = ParabolaZalpha(MISSILE_HEIGH, .middlez + MISSILE_HEIGH, MISSILE_3D_HEIGH, MISSILE_DISTANCE, .dist)
            endif
        endif
        return
    elseif .speed >= MISSILE_END_SPEED or .dist >= MISSILE_DISTANCE then
        set .explosion = true
    endif
endmethod

//Who would believe it? This method destroys the missile
method onDestroy takes nothing returns nothing
    call .model.destroy()
endmethod

endstruct

//This is the struct fot the splitter created after the explosion
private struct splitter
    delegate xefx model = 0         //The xefx model of the splitter
    delegate spell root = 0         //The struct, the splitter are from
    real dist = 0.00               //The randomed distance of the splitter
    real heigh = 0.00              //The randomed heigh of the splitter
    real angle = 0.00              //The angle, the splitter moves
    real ac = 0.00                 //The acceleration of the splitter
    real speed = SPLITTER_START_SPEED //The actual speed of the missile
    real moved = 0.00              //The moved distance of the missile

//Just the same as the missiles...
static method create takes spell root returns splitter
    local splitter this = splitter.allocate()
    set .root = root
    set .dist = GetRandomReal(SPLITTER_MIN_DISTANCE, SPLITTER_MAX_DISTANCE)
    set .heigh = GetRandomReal(SPLITTER_MIN_HEIGH + SPLITTER_START_HEIGH, SPLITTER_MAX_HEIGH)
    set .angle = GetRandomReal(0.00, 360.00)
    set .ac = (SPLITTER_END_SPEED - SPLITTER_START_SPEED) / (.dist / ((SPLITTER_END_SPEED + SPLITTER_START_SPEED) / 2)) * XE_ANIMATION_PERIOD
    debug call BJDebugMsg("Creating new splitter with index " + I2S(.count) + ".")
    set .model = xefx.create(.px, .py, .angle)
    set .fxpath = SPLITTER_MODEL
    set .scale = SPLITTER_SIZE
    set .z = SPLITTER_START_HEIGH
    return this
endmethod

//This method is called, when a splitter "lands". It damages enemies arround it.
method damage takes nothing returns nothing
    local unit u = null
    set .temp = .root
    call GroupEnumUnitsInRange(.tempgroup, .x, .y, SPLITTER_AOE[.lvl], .filter)
    loop
        set u = FirstOfGroup(.tempgroup)
        exitwhen u == null
        call .dmg.damageTarget(.caster, u, SPLITTER_DMG[.lvl])
        call GroupRemoveUnit(.tempgroup, u)
        set u = null
    endloop
    if DESTROY_TREES then
        call .dmg.damageDestructablesAOE(.caster, .x, .y, SPLITTER_AOE[.lvl], 100000.)
    endif
    call GroupClear(.tempgroup)
endmethod

//w00t?? You destroy the poor splitter???
method onDestroy takes nothing returns nothing
    call .model.destroy()
endmethod

//moves the splitter and refreshes its angle, blablabla...
method Control takes nothing returns boolean
    if .moved < .dist then
        set .speed = .speed + .ac
        set .moved = .moved + .ac
        set .x = .x + .speed * XE_ANIMATION_PERIOD * Cos(.angle)
        set .y = .y + .speed * XE_ANIMATION_PERIOD * Sin(.angle)
        set .z = ParabolaZ(SPLITTER_START_HEIGH, SPLITTER_START_HEIGH, .heigh, .dist, .moved)
        if SPLITTER_Z_ANGLE then
            set .zangle = ParabolaZalpha(SPLITTER_START_HEIGH, .middlez + SPLITTER_START_HEIGH, .heigh, .dist, .moved)
        endif
        return true
    else
        debug call BJDebugMsg("A splitter reached its target point and will deal damage and destroyed.")
        call .damage()
        call this.destroy()
        return false
    endif
    return false
endmethod

endstruct

endscope

Please give credits to me and the creators of the required systems, and discripe, how you like the spell

Keywords:
purple, missiles, explosion, splitter, crush, shadow, DoT, vJass. Circle, Jump
Contents

Shadow Explosion (Map)

Reviews
11:24, 8th Oct 2009 TriggerHappy187: I think your MyFunctions library should just be a part of your spell (as private functions). This would be different if you were submitting a spellpack. Anyways, the spell is visually great and well made...

Moderator

M

Moderator

11:24, 8th Oct 2009
TriggerHappy187:

I think your MyFunctions library should just be a part of your spell (as private functions). This would be different if you were submitting a spellpack.

Anyways, the spell is visually great and well made. The description is decent but overall I think I will rate this spell highly recommended. Meaning I will approve it but I highly suggest you use the suggestions I stated below.

Any why are you destroying timers? Just use the NewTimer function from TimerUtils and release it instead of destroy. This will let you recycle timers and be much more efficient.

Also instead of using GetWidgetLife(GetFilterUnit()) > 0.405 to check life, use this method.

Also when is your initializer ran?
 
Level 8
Joined
Aug 2, 2008
Messages
193
Hey cool direct approval, thanks :D

To your points, @TriggerHappy187:
I'll pack these functions into the library as private members.
The Timer Destruction is stupid I know.. D1000 told me that also all the time...^^
And I forgot it every time.... will fix that XD
And I think I don't need to change this "GetWidgetLife", because it's save. Vexorian, Rising_Dusk and all the other good scripter in Wc3, I know, use GetWidgetLife, and I never had problems with that..
My initializer rums inside the struct, look at the method "onInit".
It runs automatically inside a struct. Such as an Struct Initializer :)

Updated to v1.02b, Changelog in first "post"
 
Last edited:
Top