• 🏆 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] Lost Souls v1.01b

  • Like
Reactions: 1)ark_NiTe
This is the second spell, I made for a german spellcontest.

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

Discription:
Creates a circle of gravestone arround the target area. The earth in this circle is filled with evil, damaging enemies in it. Lost souls will spawn out of the stones in regular intervals, flying to enemies in the circle. On impact, the ghost infects the target with one of three diseases. |n|n|cffffcc00Level 1|r - 200 damage, 5 seconds duration and spawn interval of 0.6 seconds.


JASS:
/*
                                   LOST SOULS
                                    by Kricz
                                    
                                    Version:
                                      1.01b
                                    
                                    Credits:
                             Vexorian & BruZzl3R_17C
                        
                                    Requires:
             TimerUtils, xebasic, xecast, xedamage, xepreload, Table
            
                                  Discription:
         Summons a cricle of gravestones arround the target, damaging enemies
         in it. Summons lost souls in several intervals, flying to enemies in
        the circle and disease them on impact with one of three negative buffs.
       
*/




scope LostSouls //needs TimerUtils, xecast, xedamage, xepreload, Table, MyFunctions

globals
    /*MAIN SETUP*/
        
        //The Spell Id of the Spell
        private constant integer SPELL_ID           = 'A000'
        //The Order-String of the Spell
        private constant string ORDER_STRING        = "sacrifice"
       
       //The Number of Levels for the Spell
        private constant integer LEVELS             = 3
        //The Number of the effects, the souls can cast
        private constant integer DUMMY_SPELLS       = 3
        
        //Shouls all destructibles/trees be destroyed in the spell are on start?
        private constant boolean DESTROY_TREES      = true
        //How much auras (effects) should be created in the middle of the circle?
        private constant integer AURA_COUNT         = 2
    /*END OF MAIN SETUP*/
    
    
    /*CASTER EFFECT SETUP*/
        
        //Should a effect be created at the hero's location?
        private constant boolean CASTER_EFFECT      = true
        //The effect created on the hero while channeling
        private constant string CASTER_EFFECT_MODEL = "Abilities\\Spells\\Undead\\Possession\\PossessionCaster.mdl"
        
        //The heigh of the hero effect
        private constant real CASTER_EFFECT_HEIGH   = 62.50
        //The size of the created effect
        private constant real CASTER_EFFECT_SIZE    = 3.25   
    /*END OF CASTER EFFECT SETUP*/
    
    
    /*SETUP FOR THE STONE CIRCLE*/
        
        //The model of the stones
        private constant string GRAVE_STONE_MODEL   = "Abilities\\Spells\\Undead\\Graveyard\\GraveMarker.mdl"
        //How much gravestones should be in the circle?
        
        private constant integer STONE_COUNT        = 11
        //The size of the stones
        private constant real STONE_SIZE            = 1.35
    /*END OF STONE CIRCLE SETUP*/
    
    
    /*SETUP FOR THE SOULS*/
       
       //The model of the souls
        private constant string LOST_SOUL_MODEL     = "Abilities\\Spells\\Undead\\Possession\\PossessionMissile.mdl"
        //The distance a soul must have between the target to damage the target and get destroyed
        private constant real DISTANCE              = 8.5
       
       //The Z-Value of the souls (high)
        private constant real HEIGH                 = 72.50
        //The speed of the souls
        private constant real SPEED                 = 500.00
        
        //How much souls can be alive per spell at the same time? 
        //If there are MAX_SOULS per cast alive, no new will be created until the other reached their target
        private constant integer MAX_SOULS          = 13
        //The size of the souls
        private constant real SOUL_SIZE             = 1.3
    /*END OF SOULS SETUP*/
    
    
    //Don't change anything here!
    //They are for private using!
    
    private real array                  DAMAGE[LEVELS]
    private real array                DURATION[LEVELS]
    private real array                     AOE[LEVELS]
    private real array                INTERVAL[LEVELS]
    private string array        MODEL_AURA[AURA_COUNT]
    private real array          AURA_HEIGH[AURA_COUNT]
    private real array           AURA_SIZE[AURA_COUNT]
    
    private integer array DUMMY_SPELL_ID[DUMMY_SPELLS]
    private string array    DUMMY_ORDERS[DUMMY_SPELLS]
    private integer tempint                         = 0
    private unit tempunit                           = null
    
endglobals

    //Setup for the damage dealt over the duration in the circle
    private function SETUP_DAMAGE takes nothing returns nothing
        set DAMAGE[1] = 200
        set DAMAGE[2] = 300
        set DAMAGE[3] = 400
    endfunction
    
    //Setup for the channel duration
    private function SETUP_DURATION takes nothing returns nothing
        set DURATION[1] = 5.00
        set DURATION[2] = 6.00
        set DURATION[3] = 7.00
    endfunction
    
    //Setup for the AoE each level
    private function SETUP_AOE takes nothing returns nothing
        set AOE[1] = 350.00
        set AOE[2] = 400.00
        set AOE[3] = 450.00
    endfunction
    
    //Setup for the Intervals between the summons
    private function SETUP_INTERVAL takes nothing returns nothing
        set INTERVAL[1] = 0.60
        set INTERVAL[2] = 0.45
        set INTERVAL[3] = 0.30
    endfunction
    
    //Setup for the order-id's for the dummys
    private function SETUP_DUMMY_IDS takes nothing returns nothing
        set DUMMY_SPELL_ID[0] = 'LSD1'
        set DUMMY_SPELL_ID[1] = 'LSD2'
        set DUMMY_SPELL_ID[2] = 'LSD3'
    endfunction
    
    //Setup for the order-strings for the dummys. Note, that the order strings must have the same array number like the ID's!!
    private function SETUP_DUMMY_ORDERS takes nothing returns nothing
        set DUMMY_ORDERS[0] = "curse"
        set DUMMY_ORDERS[1] = "cripple"
        set DUMMY_ORDERS[2] = "silence"
    endfunction
    
    //Setup for the Auras things...
    private function SETUP_AURAS takes nothing returns nothing
        set MODEL_AURA[0] = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeTarget.mdl"
        set AURA_HEIGH[0] = 2.50
        set AURA_SIZE[0] =  1.00
        set MODEL_AURA[1] = "Abilities\\Spells\\Undead\\Unsummon\\UnsummonTarget.mdl"
        set AURA_HEIGH[1] = 0.00
        set AURA_SIZE[1] =  1.80
    endfunction
    
    //Setup for the Damage-Options
    //! textmacro LS_DAMAGE_OPTIONS
        set .dmg.dtype  = DAMAGE_TYPE_MAGIC
        set .dmg.atype  = ATTACK_TYPE_MAGIC
        set .dmg.wtype  = WEAPON_TYPE_WHOKNOWS
    //! endtextmacro
    
    
 /**************************/   
/*END OF USER CONFIGURATION!*/
 /**************************/ 

 
//Don't change anything below here!!

private function GroupGetRandomUnitEnum takes nothing returns nothing
    set tempint = tempint + 1
    if ( GetRandomInt(1, tempint) == 1 ) then
		set tempunit = GetEnumUnit()
	endif
endfunction

private function GroupGetRandomUnit takes group g returns unit
	set tempint = 0
    set tempunit = null
    call ForGroup(g, function GroupGetRandomUnitEnum)
    return tempunit
endfunction

private keyword soul

//The "Main-Struct". It countains the caster and all required datas, also the active souls
private struct spell
    xefx array stone[STONE_COUNT]   //The xefx models for the stone circle
    xefx array aura[AURA_COUNT]     //The xefx models for the auras
    xefx cfx = 0                    //The xefx effect created at the hero
    unit caster = null              //The channeling unit
    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
    timer t = null                 //The used timer
    real ticker = 0.00              //The real counts the time
    real counter = 0.00             //This real will summon the souls if it has the value of TICK
    integer count = 0               //This counter is used to count actual living souls
    boolean summon = true           //If this is true, more souls will be summoned (also for debuging)
    soul array souls[MAX_SOULS]     //The souls, which are currently flying
    real dist = 0.00                //I use this to store the distance between target point and caster to check if the caster moves during channeling, which is caused through Wc3 itself:
                                    //If you cast the spell while channeling to a point far away from the caster location, he has to move to the new point.
                                    //While moving, his actual oder isn't "move" but ORDER_STRING, and so, the old spell will be channeled further.
    
    static delegate xecast cast = 0                 //Used to cast the spells on impact
    static group tempgroup = CreateGroup()          //Group to detect units each interval
    static xedamage dmg                             //Used to deal damage
    static HandleTable table                        //Uses to detect, if a unit was channeling when casting again
    static boolexpr filter                          //filter for tempgroup
    static spell temp                               //required by the filter

//This is called, when the caster starts the spell. It creates all datas and attachs them to the table
private static method create takes unit caster, integer level, real x, real y returns spell
    local spell this = spell.allocate()
    local integer i = 0
    local real dx = 0.00
    local real dy = 0.00
    local real angle = 360 / STONE_COUNT
    set .caster = caster
    set .lvl = level 
    set .px = x
    set .py = y
    set .t = NewTimer()
    loop
        exitwhen i >= AURA_COUNT
        set .aura[i] = xefx.create(.px, .py, 0.00)
        set .aura[i].fxpath = MODEL_AURA[i]
        set .aura[i].scale = AURA_SIZE[i]
        set .aura[i].z = AURA_HEIGH[i]
        set i = i + 1
    endloop
    set i = 0
    set .dist = SquareRoot((.px - GetUnitX(.caster)) * (.px - GetUnitX(.caster)) + (.py - GetUnitY(.caster)) * (.py - GetUnitY(.caster)))    
    
    set .table[.caster] = this
    if DESTROY_TREES then
        call .dmg.damageDestructablesAOE(.caster, .px, .py, AOE[.lvl], 1000000.00) //Yay i know, it's ugly^^
    endif
    
    loop
        set angle = I2R(i) / STONE_COUNT * 2 * bj_PI
        set dx = x + AOE[.lvl] * Cos(angle)
        set dy = y + AOE[.lvl] * Sin(angle)
        set angle = Atan2(y - dy, x - dx)
        debug call BJDebugMsg("Creating new stone with index: " + I2S(i) + ", angle: " + R2S(angle))
        set .stone[i] = xefx.create(dx, dy, angle)
        set .stone[i].xyangle = angle
        set .stone[i].fxpath = GRAVE_STONE_MODEL
        set .stone[i].scale = STONE_SIZE
        set i = i + 1
        exitwhen i == STONE_COUNT
    endloop
    
    if CASTER_EFFECT then
        set .cfx = xefx.create(GetUnitX(.caster), GetUnitY(.caster), 0.00)
        set .cfx.fxpath = CASTER_EFFECT_MODEL
        set .cfx.scale = CASTER_EFFECT_SIZE
        set .cfx.z = CASTER_EFFECT_HEIGH
    endif
    
    call SetTimerData(.t, this)
    call TimerStart(.t, XE_ANIMATION_PERIOD, true, function spell.periodic)
    return this
endmethod

//This method will be called every XE_ANIMATION_PERIOD (that means 0.025 seconds) and moves the souls and checks, if the caster is still channeling
private static method periodic takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local spell this = spell(GetTimerData(t))
    local integer i = .count - 1
    local unit u = null
    local real newdist = SquareRoot((.px - GetUnitX(.caster)) * (.px - GetUnitX(.caster)) + (.py - GetUnitY(.caster)) * (.py - GetUnitY(.caster)))
    
    set .ticker = .ticker + XE_ANIMATION_PERIOD
    set .counter = .counter + XE_ANIMATION_PERIOD
    
    if GetWidgetLife(.caster) <= 0.405 or .counter >= DURATION[.lvl] or GetUnitCurrentOrder(.caster) != OrderId(ORDER_STRING) or newdist != .dist then
        if CASTER_EFFECT and .cfx != 0 then
            call .cfx.destroy()
        endif
        if .count <= 0 then
            debug call BJDebugMsg("Destroying Datas")
            call .destroy()
        else
            debug call BJDebugMsg("Cannot destroy datas yet, because there are living souls, which require these datas.")
            set .summon = false
        endif
    elseif GetWidgetLife(.caster) > 0.405 and .counter < DURATION[.lvl] and GetUnitCurrentOrder(.caster) == OrderId(ORDER_STRING) and newdist == .dist then                
        call GroupEnumUnitsInRange(.tempgroup, .px, .py, AOE[.lvl], .filter)
        loop
            set u = FirstOfGroup(.tempgroup)
            exitwhen u == null
                call SetUnitExploded(u, true)
                call .dmg.damageTarget(.caster, u, DAMAGE[.lvl] / DURATION[.lvl] * XE_ANIMATION_PERIOD)
                call SetUnitExploded(u, false)
            call GroupRemoveUnit(.tempgroup, u)
            set u = null
        endloop
        call GroupClear(.tempgroup)
        if .ticker >= INTERVAL[.lvl] and .summon then
            set .ticker = 0.00
            call .SummonSoul()
        endif
    endif
    loop
        exitwhen i < 0        
        if not .souls[i].Control() then
            debug call BJDebugMsg("A Soul reached its target and will be destroyed.")
            set .count = .count - 1
            if .count < 0 then
                set .count = 0
            else
                set .souls[i] = .souls[.count]
            endif
        endif
        set i = i - 1
    endloop
endmethod

//Checks first, if a new soul can be summoned, if true, it creates one and attaches it to the main-struct
method SummonSoul takes nothing returns nothing
    local unit u = null
    local integer from = 0
    if .count < MAX_SOULS - 1 then
        set .temp = this
        call GroupEnumUnitsInRange(.tempgroup, .px, .py, AOE[.lvl], .filter)
        set u = GroupGetRandomUnit(.tempgroup)
        call GroupClear(.tempgroup)
        if u != null then
            set from = GetRandomInt(0, STONE_COUNT -1)
            set .souls[.count] = soul.create(this, from, u)
            debug call BJDebugMsg("Registring soul in array number '" + I2S(.count) + "'.")
            set .count = .count + 1
            set u = null
        endif
    debug else
        debug call BJDebugMsg("Soul Storage is full. Cannot create a new soul.")
    endif
endmethod

//When the caster dies or the duration over is, the struct need to be destroyed with this method
method onDestroy takes nothing returns nothing
    local integer i = 0
    call ReleaseTimer(.t)
    
    loop
        exitwhen i >= STONE_COUNT
        call .stone[i].destroy()
        set i = i + 1
    endloop
    set i = 0
    
    loop
        exitwhen i >= .count
        set .souls[i].destroyMe = true
        set i = i + 1
    endloop
    set i = 0
    
    loop
        exitwhen i >= AURA_COUNT
        call .aura[i].destroy()
        set i = i + 1
    endloop
    if CASTER_EFFECT and .cfx != 0 then
        call .cfx.destroy()
    endif
    call .table.flush(.caster)
endmethod

//This method creates the struct for the casting unit
private static method actions takes nothing returns boolean
    local unit caster = GetTriggerUnit()
    local integer lvl = GetUnitAbilityLevel(caster, SPELL_ID)
    local spell this = spell.table[caster]
    local real x = GetSpellTargetX()
    local real y = GetSpellTargetY()
    if GetSpellAbilityId() == SPELL_ID then
            if spell.table.exists(caster) and this != null and this != 0 then
            call .destroy()
            debug call BJDebugMsg("Already channeling, destroying old datas and creating new ones.")
        else
            debug call BJDebugMsg("Caster wasn't channeling, creating datas.")
        endif
        call spell.create(caster, lvl, x, y)
    endif
    set caster = null
    return true
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 not IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(spell.temp.caster))
endmethod

private static method onInit takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0
    
    set .table = HandleTable.create()
    set .dmg = xedamage.create()
    set .cast = xecast.create()
    set .filter = Condition(function spell.FilterFunc)
    
    //adding Events and Actions
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CHANNEL)
    call TriggerAddCondition(t, Condition(function spell.actions)) //cause conditions are faster than actions ;)

    //call the Setup functions
    call SETUP_DAMAGE()
    call SETUP_DURATION()
    call SETUP_AOE()
    call SETUP_DUMMY_IDS()
    call SETUP_DUMMY_ORDERS()
    call SETUP_INTERVAL()
    call SETUP_AURAS()
    //! runtextmacro LS_DAMAGE_OPTIONS()
    
    //Preloading dummy-spells and effects/models
    call XE_PreloadAbility(SPELL_ID)
    set i = 0
    loop
        call XE_PreloadAbility(DUMMY_SPELL_ID[i])
        exitwhen i >= DUMMY_SPELLS - 1
        set i = i + 1
    endloop
    
    call Preload(GRAVE_STONE_MODEL)
    call Preload(LOST_SOUL_MODEL)
    
    set i = 0
    loop 
        exitwhen i == AURA_COUNT
        call Preload(MODEL_AURA[i])
        set i = i + 1
    endloop
    
    set t = null
endmethod

endstruct


//Each soul has it own struct.
private struct soul
    delegate xefx model = 0      //the soul itself
    delegate spell root = 0     //the struct the souls are from
    
    unit target = null          //The target of the soul
    real angle = 0.00           //the angle of the soul
    boolean destroyMe = false   //If true, the missile will be destroyed in the next timer expiration    
    
    //creates the soul...
    static method create takes spell root, integer from, unit target returns soul        
        local soul this = soul.allocate()
        set .root = root
        set .target = target
        set .angle = Atan2(GetUnitY(.target) - .stone[from].y, GetUnitX(.target) - .stone[from].x)
        set .model = xefx.create(.stone[from].x, .stone[from].y, .angle)
        set .z = HEIGH
        set .fxpath = LOST_SOUL_MODEL
        set .scale = SOUL_SIZE 
        return this
    endmethod
    
    //Will be called every time for every soul, when the periodic method is called and returns if it should be destroy or not
    method Control takes nothing returns boolean
        local real dx = GetUnitX(.target) - .x
        local real dy = GetUnitY(.target) - .y
        local real dist = SquareRoot(dx * dx + dy * dy)  
        if dist > DISTANCE and .target != null and not .destroyMe and GetWidgetLife(.target) > 0.405 then
            set .angle = Atan2(GetUnitY(.target) - .y, GetUnitX(.target) - .x)
            set .x = .x + ( SPEED * XE_ANIMATION_PERIOD ) * Cos(.angle)
            set .y = .y + ( SPEED * XE_ANIMATION_PERIOD ) * Sin(.angle)            
            set .xyangle = .angle
            return true
        elseif dist <= DISTANCE or .target == null or .destroyMe or GetWidgetLife(.target) <= 0.405 then
            if GetWidgetLife(.target) > 0.405 and .target != null then
                call .cast()
            endif
            call .destroy()
            return false
        endif
        return false
    endmethod  
    
    //casts the a random dummy spell on impact
    method cast takes nothing returns nothing
        local integer id = GetRandomInt(0, DUMMY_SPELLS - 1)
        set spell.level = .lvl
        set spell.orderstring = DUMMY_ORDERS[id]
        set spell.abilityid = DUMMY_SPELL_ID[id]
        set spell.owningplayer = GetOwningPlayer(.caster)
        call spell.castOnTarget(.target)
        debug call BJDebugMsg("Ability |cffffcc00" + GetObjectName(DUMMY_SPELL_ID[id]) + "|r was casted on a |cffffcc00" + GetUnitName(.target) + "|r, with level |cffffcc00" + I2S(.lvl) + "|r.")
    endmethod
    
    //destroys the souls
    method onDestroy takes nothing returns nothing
        call .model.destroy()
    endmethod
    
endstruct

endscope

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

Keywords:
Souls, Grave, Circle, Pit, Ghosts, Buffs
Contents

Lost Souls (Map)

Reviews
13:27, 8th Oct 2009 TriggerHappy187: This spell is awesome, I love the effect. Though it may look cooler if you make the units inside the AOE slow. Just a thought. I also couldn't find any leaks or anything wrong in the code. The only thing I...

Moderator

M

Moderator

13:27, 8th Oct 2009
TriggerHappy187:

This spell is awesome, I love the effect. Though it may look cooler if you make the units inside the AOE slow. Just a thought. I also couldn't find any leaks or anything wrong in the code. The only thing I would say is that inlining the TriggerRegisterAnyUnitBJ event is useless.
 
Level 18
Joined
Nov 1, 2006
Messages
1,612
Using this idea as a basis for "Spirit Rift" in Against the Darkness. The visual effect will be very similar but I did not even look at your coding once. My spell will have it's own physical effects that are not based at all on yours.

Nonetheless I still thought you should know that this ability inspired the one that will be in my map.
 
Top