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

Soul Consumption v1.02

Requires Jass NewGen Pack with the most recent Jasshelper.
Spell should be MUI and leakless.

Soul Consumption
The caster converts any nearby dead units into spririts that heals him depending on the unit's max life. The spirit also damages any enemy units it comes into contact with.
Level 1 - 10% of unit's max life is absorbed. Spirits deal 65 damage.
Level 2 - 15% of unit's max life is absorbed. Spirits deal 95 damage.
Level 3 - 20% of unit's max life is absorbed. Spirits deal 125 damage.


JASS:
//==========================================================================================
// SoulConsumption v1.02 by watermelon_1234
//******************************************************************************************
// Libraries required: (Libraries with * are optional)
//  - T32               http://www.thehelper.net/forums/showthread.php/132538-Timer32
//  - xe system         http://www.wc3c.net/showthread.php?t=101150
//  * BoundSentinel     http://www.wc3c.net/showthread.php?t=102576
//  * GroupUtils        http://www.wc3c.net/showthread.php?t=104464
//##########################################################################################
// Importing:
//  1. Copy the ability, Soul Consumption
//  2. Implement the required libraries
//  3. Copy this trigger
//  4. Configure the spell
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Notes:
//  - Right now, the spell will make missiles from units that ordinarily cannot be raised if 
//    the spell is used before the unit decays completely. (Example: Ghost creep)
//  - If the caster moves a great distance suddenly, the missiles will teleport to the 
//    caster instead of moving to that point like a real missile.
//==========================================================================================

scope SoulConsumption

//==========================================================================================
//                              CONSTANTS
//==========================================================================================    

    globals 
    
//------------------------------------------------------------------------------------------
//      General spell settings
//------------------------------------------------------------------------------------------

        private constant integer    SPELL_ID            = 'A000'    // The raw id of the spell
        
//------------------------------------------------------------------------------------------
//      Missile settings
//------------------------------------------------------------------------------------------

        private constant string     MISSILE_SFX         = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilMissile.mdl" // SFX for the missile
        private constant real       MISSILE_ROTATION    = bj_PI     // Determines how fast the missile will orbit the caster in radians
        private constant real       MISSILE_HEIGHT      = 60.       // Height of the missile
        private constant real       MISSILE_AREA        = 50.       // Area for the missile to damage enemy units
        private constant real       MAX_MISSILE_DUR     = 2.        // Max duration missiles will take to get to the caster

//------------------------------------------------------------------------------------------
//     Other SFX settings
//------------------------------------------------------------------------------------------        
        
        private constant boolean    PRELOAD             = true      // Determines if the spell preloads sfxs
        private constant string     DEAD_SFX            = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl" // Sfx that plays on the dead units               
        private constant string     HIT_SFX             = "Abilities\\Weapons\\IllidanMissile\\IllidanMissile.mdl"
        private constant string     HIT_ATTACH          = "chest"   // Attachment point for HIT_SFX
        private constant string     HEAL_SFX            = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl" // Sfx for healing
        private constant string     HEAL_ATTACH         = "origin"  // Attachment point for HEAL_SFX
        
//------------------------------------------------------------------------------------------
//      Damage settings
//------------------------------------------------------------------------------------------

        private constant attacktype ATK_TYPE            = ATTACK_TYPE_NORMAL
        private constant damagetype DMG_TYPE            = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype WPN_TYPE            = null
        
    endglobals    
    
//==========================================================================================
//                              OTHER CONFIGURATION
//==========================================================================================
    
    // Determines the area of the spell
    private constant function Area takes integer lvl returns real 
        return 450.+50*lvl
    endfunction
    
    // Determines which units will be consumed by the spell
    private function DeadTargets takes unit target returns boolean
        return IsUnitType(target, UNIT_TYPE_DEAD) and /*
            */ not IsUnitType(target, UNIT_TYPE_MECHANICAL) and /*
            */ not IsUnitType(target, UNIT_TYPE_HERO) and /*
            */ not IsUnitType(target, UNIT_TYPE_FLYING)
    endfunction
    
    // Determines which units will be damaged by the spell
    private function AffectedTargets takes unit target, player owner returns boolean
        return not IsUnitType(target, UNIT_TYPE_DEAD) and /*
                */ IsUnitEnemy(target, owner) and /*
                */ not IsUnitType(target, UNIT_TYPE_MECHANICAL) and /*
                */ not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)
    endfunction
    
    // Damage dealt to enemy units
    private constant function Damage takes integer lvl returns real 
        return 35. + 30*lvl
    endfunction
    
    // The % of dead unit's max life that will be used to heal the caster
    private constant function HealPercent takes integer lvl returns real 
        return .05 + .05*lvl
    endfunction 
    
//==========================================================================================
//                              END OF CONFIGURATION
//==========================================================================================
    
    globals
        private unit TempCast       // Stores caster unit for group enumeration
        private integer TempCount   // Used to count how many dead units are enumerated to determine rotation direction
    endglobals   
    
    private struct Data
        unit cast       // Caster of the spell
        integer lvl     // Level of the spell when it was first cast
        xefx missile    // Displays the missile
        real heal       // Stores how much to heal the caster by
        real ang        // Stores the angle for the missile rotation and its facing
        real dist       // The distance between the dead unit & caster
        real count = 0  // Counts how long the missile has lasted        
        real expire     // Used to determine when the missile should reach the caster.
        integer way     // Determines how the missiles will rotate, clockwise or counter-clockwise
        group hit       // Ensures that units won't be damaged twice by the same missile
        
        static thistype temp // For group enumeration
        
        // Damages units that collide with the missile
        static method damageUnits takes nothing returns boolean
            local unit u = GetFilterUnit()
            if not IsUnitInGroup(u, temp.hit) and AffectedTargets(u, GetOwningPlayer(temp.cast)) then
                call DestroyEffect(AddSpecialEffectTarget(HIT_SFX, u, HIT_ATTACH))
                call UnitDamageTarget(temp.cast, u, Damage(temp.lvl), false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE)

                call GroupAddUnit(temp.hit, u)                 
            endif
            set u = null
            return false
        endmethod
        
        // Moves the missile and damages nearby units
        method periodic takes nothing returns nothing
            local boolean dead = IsUnitType(.cast, UNIT_TYPE_DEAD) or GetUnitTypeId(.cast) == 0
            if .count < .expire and not dead then
            
                set .count = .count + T32_PERIOD                
                set .ang = .ang + .way * MISSILE_ROTATION * T32_PERIOD  
                
                set .missile.x = GetUnitX(.cast) + (.dist-.dist/.expire * .count)*Cos(.ang)
                set .missile.y = GetUnitY(.cast) + (.dist-.dist/.expire * .count)*Sin(.ang)
                set .missile.xyangle = .ang + bj_PI // Adds PI for correct facing
                
                set temp = this
                static if LIBRARY_GroupUtils then
                    call GroupEnumUnitsInArea(ENUM_GROUP, .missile.x, .missile.y, MISSILE_AREA, Filter(function thistype.damageUnits))
                else
                    call GroupEnumUnitsInRange(bj_lastCreatedGroup, .missile.x, .missile.y, MISSILE_AREA, Filter(function thistype.damageUnits))
                endif
                
            else
                
                // Don't heal the caster if it is already dead    
                if not dead then            
                    call SetWidgetLife(.cast, GetWidgetLife(.cast) + .heal)
                    call DestroyEffect(AddSpecialEffectTarget(HEAL_SFX, .cast, HEAL_ATTACH))
                else
                     call DestroyEffect(AddSpecialEffect(HEAL_SFX, .missile.x, .missile.y))
                endif
                
                call .missile.destroy()
                
                static if LIBRARY_GroupUtils then
                    call ReleaseGroup(.hit)
                else
                    call GroupClear(.hit)
                endif
                
                call .stopPeriodic()

                call .destroy()
            endif
        endmethod
        
        implement T32x
        
        // Creates the missile from the dead unit
        static method createMissile takes unit c, unit u returns nothing
            local thistype this = thistype.allocate()
            local real cx = GetUnitX(c)
            local real cy = GetUnitY(c)
            local real ux = GetUnitX(u)
            local real uy = GetUnitY(u) 
            local real dx = ux - cx
            local real dy = uy - cy   
            
            set .cast = c
            set .lvl = GetUnitAbilityLevel(c, SPELL_ID) 
            
            set .heal = HealPercent(.lvl)*GetUnitState(u, UNIT_STATE_MAX_LIFE)  
            
            set .ang = Atan2(dy, dx)   
            set .dist = SquareRoot(dx * dx + dy * dy)
            set .expire = .dist/Area(.lvl) * MAX_MISSILE_DUR
            
            set .missile = xefx.create(ux, uy, .ang + bj_PI)            
            set .missile.fxpath = MISSILE_SFX
            set .missile.z = MISSILE_HEIGHT       
            
            // Determines whether the missile will rotate clockwise or counter-clockwise
            if ModuloInteger(TempCount, 2) == 0 then
                set .way = -1
            else
                set .way = 1
            endif                                 
            
            call DestroyEffect(AddSpecialEffect(DEAD_SFX, ux, uy))
            call RemoveUnit(u) // To remove the corpse.            
            
            static if LIBRARY_GroupUtils then
                set .hit = NewGroup()
            else
                if .hit == null then
                    set .hit = CreateGroup()
                endif
            endif
            
            call .startPeriodic()   
        endmethod
        
        // Enumerates dead units to make missiles
        static method enumDeadUnits takes nothing returns boolean
            if DeadTargets(GetFilterUnit()) then 
                call thistype.createMissile(TempCast, GetFilterUnit())     
                set TempCount = TempCount + 1
            endif
            return false
        endmethod
        
        // Spell actions
        static method spellActions takes nothing returns boolean
            if GetSpellAbilityId() == SPELL_ID then            
                set TempCast = GetTriggerUnit()
                set TempCount = 0
                static if LIBRARY_GroupUtils then
                    call GroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(TempCast), GetUnitY(TempCast), Area(GetUnitAbilityLevel(TempCast,SPELL_ID)), Filter(function thistype.enumDeadUnits))
                else
                    call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(TempCast), GetUnitY(TempCast), Area(GetUnitAbilityLevel(TempCast,SPELL_ID)), Filter(function thistype.enumDeadUnits))
                endif
            endif
            return false 
        endmethod
        
        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.spellActions))
            set t = null
            
            static if PRELOAD then
                call Preload(MISSILE_SFX)
                call Preload(HIT_SFX)
                call Preload(HEAL_SFX)
                call Preload(DEAD_SFX)
            endif
        endmethod
    endstruct

endscope

v1.02
Uses T32 instead of TimerUtils
Made GroupUtils optional
No longer uses xedamage
No longer uses UnitAlive
Improved angle calculations
Removed a leak
Corrected the group recycling
Moved all of the code into the struct
Improved code readability
Increased missile damage

v1.01a
Instead of recycling groups every time, the struct now just creates a group and will continue to use it.

v1.01
Changed the way special effects will be preloaded. Added the PRELOAD boolean so that the user can determine whether or not the effects will be preloaded.

v1.00
Released.


Please credit me if you use this spell in your map.
Credits:
~ Vexorian for xe and TimerUtils
~ RisingDusk for GroupUtils
~ Jesus4Lyf for T32
~ TriggerHappy for tip on recycling groups

Any kind of feedback on how I could improve the spell would be appreciated.

Keywords:
soul,spirit,heal,corpse,missile,dead
Contents

SoulConsumption (Map)

Reviews
10:49, 30th Jan 2010 TriggerHappy: Looks great and a neat effect. I'm pretty sure to preload effects you can just use Preload. You could just be using a global group instead of recycling them.

Moderator

M

Moderator

10:49, 30th Jan 2010
TriggerHappy:

Looks great and a neat effect.

  • I'm pretty sure to preload effects you can just use Preload.
  • You could just be using a global group instead of recycling them.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
ummmmrmrmrmr, you said it doesnt work on Ghosts and such, will it work on Ghouls and Aboms? what type of units does it not work on?
Let me clarify: The Ghost creep has Combat - Death Type set to Can't raise, Does not Decay.
However, missiles will still be created anyway from the Ghost if it died recently, though it doesn't seem to fit with the spell.
In other words, the spell doesn't pay attention to that field unlike the abilities, Raise Dead and Animate Dead.
 
Level 3
Joined
May 28, 2012
Messages
32
You could improve your spell and make it more logical with canceling the ability when no corpse is in near.
But all in all its a very nice and useful spell!
 
Top