• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Simple Spell Collection#1 v.2.0

Simple Spell Collection #1


May these spells be an inspiration for you.


Obligatory RequirementsOptional Requirements


Spells included in the demo map

  • Chaos Flare v.2.0
  • Ice Siege v.2.0
  • Bone Spirit v.2.0
  • The Reaper's Scythe v.2.0
  • Fireball v.2.0
  • Whirlwind v.2.0
  • Leap v.1.0 ( New! )
  • Fist of Thunder v.1.0 ( New! )
  • Chaotic Boulder v.1.0 ( New! )
  • Guided Arrow v.1.0 ( New! )

Each spell is placed in a seperated library, but they all have library SpellIndex as common requirement.
It allocates spell instances and provides most important struct members for all spells.

Spell Index

JASS:
library SpellIndex/* v1.1
************************************************************
*
*   Makes spell indexing global and nulls members automatically.
*   A screen freeze is more likely than an overflow.
*
*   API: 
*       --> SpellIndex.create() and index.destroy()
*
************************************************************
*
*   */ uses /*
*       
*       */ Table                   /* hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*       */ Missile                 /* hiveworkshop.com/forums/jass-resources-412/missile-265370/
*       */ TimerUtils              /* wc3c.net/showthread.php?t=101322
*       */ DummyCaster             /* github.com/nestharus/JASS/tree/master/jass/Systems/DummyCaster
*       */ WorldBounds             /* github.com/nestharus/JASS/tree/master/jass/Systems/WorldBounds
*       */ SpellEffectEvent        /* hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
*       */ RegisterPlayerUnitEvent /* hiveworkshop.com/forums/showthread.php?t=203338
*
***************************************************************
*
*   Credits to Bribe, Nestharus, Maghteridon96 and Vexorian.
*
***************************************************************/
    
    native UnitAlive takes unit id returns boolean
    
    struct SpellIndex
        //*  implement your Alloc module here.
    
        static constant group GLOBAL_GROUP = bj_lastCreatedGroup 
        //*  Add or remove struct members you to needs.
        //*  Units.
        unit      source
        unit      target
        player    user
        //* Effects.
        effect    fx
        ubersplat splat
        lightning flash
        //* Timer.
        timer     clock
        //*  Damage options.
        real      damage
        real      collision
        //*  Misc.
        real      time
        integer   count
        integer   level
        integer   phase
        
        //*  Conditionally destroy an handle on instance.destroy().
        //! textmacro SPELL_INDEX_CHECK_MEMBER takes VAR, TYPE
            if $VAR$ != null then
                call Destroy$TYPE$($VAR$) 
                set $VAR$ = null
            endif 
        //! endtextmacro
        
        method destroy takes nothing returns nothing
            //! runtextmacro SPELL_INDEX_CHECK_MEMBER("fx", "Effect")
            //! runtextmacro SPELL_INDEX_CHECK_MEMBER("splat", "Ubersplat")
            //! runtextmacro SPELL_INDEX_CHECK_MEMBER("flash", "Lightning")
            set target    = null
            set clock     = null
            set source    = null
            call deallocate()
        endmethod
    
    endstruct

endlibrary

Keywords:
Pack, Collection, Simple, Ice Siege, Reaper's Scyte, Fireball, Chaosflare, Fire, Ice, Death, Necro
Contents

Simple Spell Collection #1 (Map)

Reviews
16:07, 13th Apr 2014 PurgeandFire: Approved. Review: http://www.hiveworkshop.com/forums/2512151-post22.html

Moderator

M

Moderator

16:07, 13th Apr 2014
PurgeandFire: Approved.

Review:
http://www.hiveworkshop.com/forums/2512151-post22.html
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
It's nothing particularly dazzling, decent coding.
I'm always eager to learn and improve. Can you be more specific?

Just dumping such a statement doesn't help me or anyone. Its just bad advertising.


Edit: I think I got you wrong. Yes those resources are very simple. I'm already planning some cool stuff for a more creative collection pack.

Also I'm a big fan of simple resources, such which do not influence the game performance at all.
 
Last edited:
On first look spells looked very same as wc3 spells to me, but actually after testing I liked them.

You can cast Fireball on a tower, but actually it just goes thourgh it.. maybe you can change it that buildings are not selectable then.

After Reaper's Scythe, this special effect lasts a bit too long for me, I think you can change this by decreasing the destroying time for effects in gameplay constants. (thats not really important, but just a note ^^)
_______

I dont know what I should say much more.. just wondering your title is "Don't use GUI" and here you also created your spells in GUI :d

I think it's cool idea to make a collection of simple spells, I'm also a fan of holding things simple. I believe you can create many cool and creative spells with only simple coding.
(whereas on other side using so much vJASS requirements is maybe not very simple coding :p )

Edit:

You noted that your map is designed very poor.. you can search for a template map in Map Section, maybe you will find a matching one. :wwink:
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
whereas on other side using so much vJASS requirements is maybe not very simple coding :p
In case you are creating you map in vJass, you probably can't avoid using Table, TimerUtils, WorldBounds, UnitIndexer, Event and a system that recycles dummies.

I personally chose CTL over T32x, because it can be found on THW and Nestharus is still keeping his resources up to date. Same thing with Dummy over Missle, Xe_Dummy.

DummyCaster and SpellEffectEvent are 100% optional. I'll consider using Alloc as optional resources on future updates. At the moment it is mandatory.

These are very common resources and can be used in nearly any system or spell. Also the project would be pointless, if I wouldn't use Dummy. Recycling missles is one of the biggest advantages of my resources.

Personally I have an aversion towards GUI, because its very difficult to create a smooth running map with GUI only. I've seen and played many beautiful maps with awesome terrain and storyline, but as soon as fights are getting bigger, fps drops by 30. (I've once re-coded a GUI map into vJass and the result was 21 vs 55 fps in fights. I should add here, that my pc is not the best)

On the other side it is very popular, that why I offered to provide a GUI/JASS version for every spell aswell
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Update: I've added a fifth spell: Bone Spirit. Also Fireball now explodes if it collides with a structure. It is still going through walls, because I not sure if I want to add library TerrainPathability.

Also I've added a quick explanation to each line of the code, in case someone wants to know what's going on there, but has no experience with vJass.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I've taken a look into Rolling Bolder ... if (error>4) then, DamageUnitByOptions, AddCasterFacing, ... I really hope what Vexorian did there is cool, because the read-ability is literally the worst.

Edit: I'll do Rolling Bolder, but sure without that hillarious damage modification by Vexorian. He considered everything. Its 50 lines of code just to find the correct damage according to the unit type ....
Plus I've never seen this spell, so the outcome might be different to the original one.
 
Last edited:
Level 18
Joined
Sep 14, 2012
Messages
3,413
You could use constant function for this :
JASS:
private function GetDamage takes integer level returns real
        return 45. + (10*level)
    endfunction
    
    //Define the periodic damage. level is the ability level for the caster 
    private function GetDamageOverTime takes integer level returns real
        return 20. + (10*level)
    endfunction
    
    //Define how often periodic damage is applied 
    private function GetWaves takes integer level returns integer
        return 4 + 0*level
    endfunction
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I have an more updated version on my hardware. No typos in the docs (hopefully), better docs and I removed the GetUnitTypeId != 0 check, because it is not required when using UnitAlive.

I'm not going to bump it to the top of the spell section for such a minor change until its approved.

Edit: Double checked everything and made a few small cosmetic changes in the code.
 
Last edited:
I just skimmed each spell's code I haven't tested the them.

General comments

  • Just place the UnitAlive line in each spell, JassHelper only adds it once.
  • I suggest BoundSentinel over WorldBounds so you don't have to constantly check coordinates. I figure it's more efficient.
  • I like this -> implement optional Alloc
  • You probably already know this but your triggers/timers don't need to be nulled.
Fireball

  • You don't need to clear the group (enu).
Bone Spirit

  • static constant real TIMEOUT = 0.031250000should be in the globals block, or at least point to a constant global.
Reaper's Scythe

  • GetDamageFactor could probably be inlined, to better support all levels.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Just place the UnitAlive line in each spell, JassHelper only adds it once.
Only true for Vexorians JassHelper. But I see no reason to use Cohadars in the first place.
So yes I'll do that.
I suggest BoundSentinel over WorldBounds so you don't have to constantly check coordinates. I figure it's more efficient.
Yes I also figured that BoundSentinel is a really useful and lightweight resource.
Nestharus decided that UnitIndexer uses WorldBounds, well actually just here ... call TriggerRegisterEnterRegion(q,WorldBounds.worldRegion,bc2) and I didn't want to add another library into the map. I also though about excluding the coordinate condition wtih static if BoundSentinel exists, but then I have to add a statif if around an endif and that looks so stupid. ^^

Alloc is only optional, because you once said it's not a standart in maps and I agree on that.

You probably already know this but your triggers/timers don't need to be nulled.
It's just an old habit, however that way I never forget to null those who have to be nulled.

static constant real TIMEOUT = 0.031250000
The spell uses CTL and not TimerUtils. The timeout is not configurable.

Group behaviour is sometimes really strange. Although I added a lot of debug messages (IsGroupEmpty, CountUnitsInGroup, ..) I still don't get it 100%. I'll just trust you here :).
 
Last edited:
Simple Spell Collection#1 v1.0.0.0 - by BPower

Good spell pack, and nice coding. I only can mention minor things.

Maybe these reals could be constants:
JASS:
 call SetUnitPosition(c,2147483647,2147483647)
Maybe different scale values depending on ability-level would be cool. (fire ball)

To exit loops exitwhen(true) would suffice. (fire ball)
JASS:
if (UnitAlive(.aim)) then
    call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, .aim, DAMAGE_EFFECT_ATTACH))
    set f    = GetUnitState(.aim, UNIT_STATE_MAX_LIFE) - GetWidgetLife(.aim)
    set .dmg = .dmg*f
    call UnitDamageTarget(.caster, .aim, .dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
^The real "f" is redundant. (Reaper's Scythe) Or maybe just -->
JASS:
if (UnitAlive(.aim)) then
    call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, .aim, DAMAGE_EFFECT_ATTACH))
    call UnitDamageTarget(.caster, .aim, .dmg*(GetUnitState(.aim, UNIT_STATE_MAX_LIFE) - GetWidgetLife(.aim)), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif

set .t = 0 - SLEEP_TIME. The 0 is not even needed. Also maybe you could mention that SPELL_TIME is not in secods, but in *TIMEOUT seconds. (Bone Spirit)

It looks a bit weird in game if fire ball can pass cliffs. Maybe it should get destroyed if it reaches a higher cliff level.

Execution is totaly fine. The spells can be useful and look cool in game. I rate this 4/5 and vote for approval.
 
Review:
Chaosflare

  • The callback's if/then/elses can be simplified to this:
    JASS:
                /*
                *   When dealing with integers "most times"
                *   I do prefet < 1 over == 0.
                */
                if (wave < 1) or not UnitAlive(.aim) then 
                    /*
                    *   Keep track how many instances are
                    *   allocated.
                    */
                    set track[.id] = track[.id] - 1
                    if (track[.id] == 0) then
                        call UnitRemoveAbility(.aim, BUFF_ID)
                    endif
                    
                    set .caster = null
                    set .aim    = null
                    call ReleaseTimer(t)
                    
                    static if (thistype.deallocate.exists) then
                        call this.deallocate()
                    else
                        set rn[this] = rn[0]
                        set rn[0] = this
                    endif
                else
                	/*
                    *   Apply periodic damage 
                    *   and destroy the burn effect.
                    */
                    call UnitDamageTarget(.caster, .aim, .dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                    call DestroyEffect(AddSpecialEffectTarget(BURN_EFFECT, .aim, ATTACH_BURN))
                endif
    Or you can switch them around (you would just reverse the condition). Whichever you prefer.
Ice Siege
  • Just a quick note:
    JASS:
    static if DummyCaster.castTarget.exists then
    I can't check right now, but I wonder if that will cause an error if you don't have DummyCaster in
    your map. Or maybe jasshelper ignores it if it can't even find the source struct?

    I don't know. I'm just curious. :)
    .
  • You can do a similar consolidation of the if/then/else blocks as in Chaosflare.
Fireball
  • Just for future reference, you can use exitwhen true. But your way is fine too. :p
Reaper's Scythe
  • Nice. Although, the scope's name is "ReapersScyte" instead of "ReapersScythe".
Bone Spirit
  • You use ForGroup(..., GroupPickRandomUnitEnum) but you don't use the
    resulting: bj_groupRandomCurrentPick.
    So the ForGroup doesn't do anything.

Overall, the spells are very well-coded, fun to play with, and they have a nice balance of effects. Fireball is a little bland (but it is very useful), but the rest are unique. Eye-candy wise, Chaosflare is my favorite. :) In terms of idea, Reaper's Scythe.

Nice work! I'm going to go ahead and approve it since it's been sitting here so long, but feel free to fix any of the stuff above wherever you see fit. They aren't anything major anyway.
 

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
Lately I was browsing through the Spells section with special focus on the old resources. I was wondering how spells were coded back then on 2009, since I have started wc3 modding in summer 2013.

Sadly, most of these spells have the following behaviour: Apply timed life and create a dummy in a certain angle and order a standart Warcraft 3 spell.

So I've decided to rebuild and enhance old spells and gather them in small Spell Collections. Currently each spell only exists in vJass.
I like the idea, but the problem is, most user prefer to code simple spell by themselves :?
So my suggestion is: make a little bit confusing/complex coded spell that not many user can do it by themselves, so that at least user has reason to import it

off-topic, how's your vacation? >.>
 
Last edited:

Deleted member 219079

D

Deleted member 219079

v1.0.0.0
is the first 0 major update, second 0 minor update and third 0 hotfix? asking out of curiosity :D
 
Level 18
Joined
Oct 17, 2012
Messages
849
Does whirlwind drag along enemy units with the caster?
Such an effect would spice the spell up and provide one more reason to use this alternative version of the spell over the default one.

I cannot wait to see more spells added to this simply awesome pack.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
As beeing the most downloaded submission of mine, it was about time to update it to a more advanced coding standard.

I re-wrote every single spell without changing their concepts.
All spell are now much more lightweight and should work flawless.

The following requirements got removed in this update:
  • UnitIndexer ( Nestharus )
  • Dummy ( Nestharus ), instead Missile was added. Dummy is an optional addition to Missile.
  • Event ( as a consequence of UnitIndexer beeing removed )

Critical changes in code:
  • Whirlwind is now turned off on EVENT_PLAYER_UNIT_ISSUE_ORDER, in the previous version it was a periodic timer callback.
  • Bone spirit now properly considers random units in range. ( Assuming GroupPickRandomUnitEnum is accurate enough for your standards. )
  • All spells using missiles ( Bone Spirit, Fireball ) are now highly configurable, because they hand the work over to library Missile.
  • Reaper's Scythe creates/destroys an ubersplat, which I never saw in any spell before :p.
  • The DoT of ChaosFlare no longer fires, if the buff couldn't be applied. It also stops now, when buff gets purged.
  • All module initializers got replaced by normal function initializers ( No-one casts spell during loading screen time )

I will add further spell soon. The next I planned is a leap for the blademaster.

Update: Added Leap
JASS:
library Leap initializer Init uses SpellIndex, IsTerrainWalkable, CameraEQNoise/*v1.0
*************************************************************************************
*
*   Leap into the air, dealing  damage to all close enemies of your destination
*   and slowing their movement speed by 20% for 3 seconds. 
*
*************************************************************************************/
//**
//*  User settings:
//*  ==============
    //*  The slow duration is hardcoded in the leap buff. Change it there.
    globals
        private constant integer LEAP_ABILITY = 'A003'
        //*  Damage type options.
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL 
        //*  Effect options
        private constant string ON_LANDING_FX    = "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl"
        private constant string WHILE_LEAPING_FX = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl"
        private constant string FX_ATTACH_POINT  = "weapon"
        //*  Buff variables.
        private constant integer    BUFF_CAST_ID = 'A00B'//*  Raw code of the Leap Apply buff ability  - Object Editor (F6)
        private constant integer    ORDER_ID     = 852075//*  Order of the Leap Apply buff ability     - Trigger Editor (OrderIds) --> slow
    endglobals
    
    //*  Set the impact damage.
    private constant function GetImpactDamage takes integer level returns real
        return 0. + 70*level
    endfunction
    //*  Set the arc of the unit. Play a bit with the values to find a good setup.
    private function GetArc takes real distance returns real
        return 45.*bj_DEGTORAD + RMinBJ(20., distance*.1)*bj_DEGTORAD
    endfunction
    //*  Set the damage collision radius on leap finish.
    private function GetDamageRadius takes integer level returns real
        return 190. + 0.*level
    endfunction
    //*  Set the total air time. Returning 0. is invalid! 
    //* The current setup is -->  minimum + (distance/2)/movement speed.
    private function GetAirTime takes real distance, integer level returns real
        return .3 + (distance*.5)/500.
    endfunction
    //*  Set the offset for the damage effects. It's an empirical value. 
    private constant function GetUnitWeaponOffset takes unit source returns real
        return 75.
    endfunction
    //*  Read the animation time out from the object editor field for your unit.
    //* The current setup is --> slam animation time blademaster 1.133
    private constant function GetUnitAnimationTime takes unit source returns real
        return 1.133
    endfunction
    //*  Filter valid targets.
    private function FilterUnits takes unit target, player owner returns boolean
        return UnitAlive(target) and IsUnitEnemy(target, owner) and not IsUnitType(target, UNIT_TYPE_FLYING)
    endfunction
    //*  Code which should run on finish leap.
    private function OnFinishLeap takes unit source returns nothing
        if UnitAlive(source) then
            call CameraSetEQNoise(GetOwningPlayer(source), 3., .33)
        endif
    endfunction
    
//========================================================================
//*  Leap code. Make changes carefully.
//========================================================================

    //*  Quite useful, you may outsource it and make it a public function.
    private function IsPointJumpable takes real x, real y returns boolean
        if not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then
            return IsTerrainWalkable(x, y)
        endif
        return false
    endfunction
    
    globals
        private sound error
    endglobals
    
    //*  Uses Missile's API. For a destructable enum, add .onDestructable and .onDestructableFilter.
    private struct Leap extends array
        
        //*  Runs on finish.
        static method onFinish takes Missile missile returns boolean
            local unit source = missile.dummy
            local real unitX = GetUnitX(source)
            local real unitY = GetUnitY(source)
            local real posX = unitX + GetUnitWeaponOffset(source)*Cos(missile.angle)
            local real posY = unitY + GetUnitWeaponOffset(source)*Sin(missile.angle)   
            local unit u
            //*  Restore pathing.
            call SetUnitPathing(source, true)
            if UnitAlive(source) then
                //*  Run effects.
                call DestroyEffect(AddSpecialEffect(ON_LANDING_FX, posX, posY))
                call GroupEnumUnitsInRange(SpellIndex.GLOBAL_GROUP, posX, posY, missile.collision, null)
                loop
                    set u = FirstOfGroup(SpellIndex.GLOBAL_GROUP)
                    exitwhen u == null
                    call GroupRemoveUnit(SpellIndex.GLOBAL_GROUP, u)
                    if (FilterUnits(u, missile.owner)) then
                        //*  Make pathing space for the leaping unit.
                        call SetUnitPosition(u, GetUnitX(u), GetUnitY(u))
                        //*  Deal damage and apply the buff.
                        if (UnitDamageTarget(source, u, missile.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)) then
                            call DummyCaster[BUFF_CAST_ID].castTarget(missile.owner, 1, ORDER_ID, u)
                        endif
                    endif
                endloop
            endif
            call SetUnitPosition(source, unitX, unitY)
            call OnFinishLeap(source)
            call SetUnitTimeScale(source, 1.)
            call SpellIndex(missile.data).destroy()
            set source = null
            return true
        endmethod
        
        implement MissileStruct
    endstruct
    
    private function OnEffect takes nothing returns nothing
        local string prefix = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00"
        local unit source = GetTriggerUnit()
        local integer level =  GetUnitAbilityLevel(source, LEAP_ABILITY)
        local real time 
        local real distance
        local Missile missile
        //*  Check for terrain pathability.
        if IsPointJumpable(GetSpellTargetX(), GetSpellTargetY()) then
            //*  Make sure the caster can fly.
            if UnitAddAbility(source, 'Amrf') and UnitRemoveAbility(source, 'Amrf') then
            endif
            //*  Transform the source unit into a Missile instance.
            set missile = Missile.createEx(source, GetSpellTargetX(), GetSpellTargetY(), GetUnitDefaultFlyHeight(source))
            set distance = missile.origin.distance
            set time = GetAirTime(distance, level)
            set missile.arc = GetArc(distance)
            set missile.speed = distance/time*Missile_TIMER_TIMEOUT
            set missile.owner = GetTriggerPlayer()
            set missile.damage = GetImpactDamage(level)
            set missile.collision = GetDamageRadius(level)
            call Leap.launch(missile)
            //*  Add effect.
            set missile.data = SpellIndex.create()
            set SpellIndex(missile.data).fx = AddSpecialEffectTarget(WHILE_LEAPING_FX, source, FX_ATTACH_POINT)
            //*
            call SetUnitTimeScale(source, GetUnitAnimationTime(source)*.5/time)
            call SetUnitPathing(source, false)
        else
            //*  Invalid jump location!
            call PauseUnit(source, true)
            call IssueImmediateOrderById(source, 851972)
            call PauseUnit(source, false)
            //*  Simulate Warcraft III error message.
            if GetLocalPlayer() == GetTriggerPlayer() then
                call StartSound(error)
                call ClearTextMessages()
            endif
            call DisplayTimedTextToPlayer(GetTriggerPlayer(), .52, .96, 2., prefix + GetUnitName(source) + " can't jump there!|r")
        endif
        set source = null
    endfunction
    
    private function Init takes nothing returns nothing
        set error = CreateSoundFromLabel("InterfaceError", false, false, false, 10, 10)
        call RegisterSpellEffectEvent(LEAP_ABILITY, function OnEffect)
    endfunction
    
endlibrary

Update: Added FistOfThunder
JASS:
library FistOfThunder initializer Init uses SpellIndex /* v1.0
*************************************************************************************
*
*   Slams the ground in front of you, dealing damage to close enemies.
*   Emerging lightnings deal extra damage, when colliding with an enemy.
*
*************************************************************************************/
//**
//*  User settings:
//*  ==============
    globals
        private constant integer FIST_OF_THUNDER_ABILITY = 'A00C'
        //*  Damage options.
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        //*  Set the lightning end z.
        private constant real MISSILE_END_Z = 175.
        //*  Effect options.
        private constant string LIGHTNING_MODEL         = "CLPB"
        private constant string SLAM_CENTER_FX          = "Abilities\\Spells\\Orc\\Purge\\PurgeBuffTarget.mdl"
        private constant real   SLAM_CENTER_FX_DURATION = 2.
        //*  Concept.
        private constant boolean DESTROY_MISSILE_ON_COLLIDE = true
    endglobals
    
    //*  Set the slam collision size.
    private constant function GetSlamCollisionSize takes integer level returns real
        return 180.
    endfunction
    //*  Set the damage dealt on slam.  
    private constant function GetSlamDamage takes integer level returns real
        return 40.*level - 10.
    endfunction
    //*  Set the travel distance for each missile.
    private constant function GetMissileFlyDistance takes integer level returns real
        return  600. + (50*level)
    endfunction
    //*  Set the amount of Missiles based on the ability level.
    private constant function GetMissileCount takes integer level returns integer
        return 4 + 2*level
    endfunction
    //*  Filter valid target units.
    private function FilterUnits takes unit target, player owner returns boolean
        return UnitAlive(target) and IsUnitEnemy(target, owner) and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
    endfunction
    //*  Customize all missile members to your needs.
    private function CustomizeMissile takes Missile missile, integer level returns nothing
        set missile.speed  = 22. + 0.*level
        set missile.damage = 10. + 10.*level
        set missile.collision = 32.
        set missile.model = "Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl"
        set missile.scale = 1.
    endfunction
    //*  These effects run on spell effect event.  Customize them to your needs.  
    private function RunOnSlamEffects takes unit source, real slamX, real slamY returns nothing
        //*  Runs sounds.
        local string file = "Abilities\\Spells\\Orc\\LightningBolt\\LightningBolt.wav"
        local sound s
        if (GetRandomInt(0, 1) == 0) then
            set file = "Abilities\\Spells\\Orc\\LightningShield\\LightningShieldTarget.wav" 
        endif
        set s = CreateSound(file, false, false, false, 10, 10, "")
        call AttachSoundToUnit(s, source)
        call StartSound(s)
        call KillSoundWhenDone(s)
        //*  Run special effects.
        call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl", slamX, slamY))
        call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\StormBolt\\StormBoltMissile.mdl", source, "weapon,right"))
        //*
        set s = null
    endfunction
    //*  These effects run when a missile ( source ) collides with a unit ( hit )
    private function RunOnCollideEffects takes unit source, unit hit returns nothing
        call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl", hit, "origin"))
    endfunction 
    
//========================================================================
//*  Fist of Thunder code. Make changes carefully.
//========================================================================
    
    //*  Uses Missile's API. For a destructable enum, add .onDestructable and .onDestructableFilter.
    private struct FistOfThunder extends array
        
        //*  Releases the lightning model.
        private static method onRemove takes Missile missile returns boolean
            call SpellIndex(missile.data).destroy()
            return true
        endmethod
        
        static method onCollide takes Missile missile, unit hit returns boolean
            if FilterUnits(hit, missile.owner) then
                call UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                call RunOnCollideEffects(missile.dummy, hit)
                return DESTROY_MISSILE_ON_COLLIDE
            endif
            return false
        endmethod
        
        static method onPeriod takes Missile missile returns boolean
            local MissilePosition pos = missile.origin
            call MoveLightningEx(SpellIndex(missile.data).flash, true, pos.x, pos.y, pos.z, missile.x, missile.y, missile.z + missile.terrainZ)
            return false
        endmethod
        
        implement MissileStruct
    endstruct
    
    private function Callback takes nothing returns nothing
        static if LIBRARY_TimerUtilsEx then
            call SpellIndex(ReleaseTimer(GetExpiredTimer())).destroy()
        else
            call SpellIndex(GetTimerData(GetExpiredTimer())).destroy()
            call ReleaseTimer(GetExpiredTimer())
        endif
    endfunction
    
    private function CreateMissiles takes unit source, player user, real x, real y, integer level returns nothing
        local integer dex = 0
        local integer end = GetMissileCount(level)
        local real spacing = bj_PI*2/end
        local real angle = GetRandomReal(-bj_PI, bj_PI)
        local real distance = GetMissileFlyDistance(level)
        local Missile missile
        loop   
            exitwhen (dex == end)
            //*  Create a new missile and assign it's members.
            set missile = Missile.create(x, y, 0., angle, distance, MISSILE_END_Z)
            call CustomizeMissile(missile, level)
            set missile.source = source
            set missile.owner = user
            set missile.data = SpellIndex.create()
            set SpellIndex(missile.data).flash = AddLightningEx(LIGHTNING_MODEL, true, x, y, missile.origin.z, x, y, missile.z + missile.terrainZ)
            call FistOfThunder.launch(missile)
            //*  Next missile.
            set angle = angle + spacing
            set dex = dex + 1
        endloop
    endfunction
    
    private function OnEffect takes nothing returns nothing
        local unit source = GetTriggerUnit()
        local player user = GetTriggerPlayer()
        local integer level = GetUnitAbilityLevel(source, FIST_OF_THUNDER_ABILITY)
        local real x = GetSpellTargetX()
        local real y = GetSpellTargetY()
        local real damage = GetSlamDamage(level)
        local SpellIndex dex = SpellIndex.create()
        local unit u
        //*  Runs Effects.
        call RunOnSlamEffects(source, x, y)
        call GroupEnumUnitsInRange(SpellIndex.GLOBAL_GROUP, x, y, GetSlamCollisionSize(level), null)
        loop
            set u = FirstOfGroup(SpellIndex.GLOBAL_GROUP)
            exitwhen u == null
            call GroupRemoveUnit(SpellIndex.GLOBAL_GROUP, u)
            if (FilterUnits(u, user)) then
                call UnitDamageTarget(source, u, damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
            endif
        endloop
        //*  Create all missiles.
        call CreateMissiles(source, user, x, y, level)
        //*  Create center model
        set dex.fx = AddSpecialEffect(SLAM_CENTER_FX, x, y)
        call TimerStart(NewTimerEx(dex), SLAM_CENTER_FX_DURATION, false, function Callback)
        set user = null
        set source = null
    endfunction
    
    private function Init takes nothing returns nothing
        call RegisterSpellEffectEvent(FIST_OF_THUNDER_ABILITY, function OnEffect)
    endfunction
endlibrary
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Due to the update of Missile to version 2.0 I made a new spell.
For that I added. integer phase to SpellIndex.

Guided arrow
JASS:
library GuidedArrow initializer Init uses SpellIndex, Missile /* v2.0
*************************************************************************************
*
*   The caster fires an arrow which tracks down it's target.
*   On impact the arrow eventually pierces and hits the target again.
*
*************************************************************************************/
//**
//*  User settings:
//*  ==============
    globals
        private constant integer GUIDED_ARROW_ABILITY = 'A00E'
        // Damage options.
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        // Effect options.
        private constant string ON_TARGET_FX    = "Abilities\\Spells\\NightElf\\Barkskin\\BarkSkinTarget.mdl"
        private constant string FX_ATTACH_POINT = "overhead"
        // Constant missile options.
        private constant real ARROW_ACTION_DELAY         = 0.3 // Timeout before the arrow starts to turn.
        private constant real UNIT_DETECTION_AOE         = 400.// Area in which the arrow searches for targets. 
        private constant real ARROW_FLY_RUN_OUT_DISTANCE = 350.// Distance traveled if the target dies.
        private constant real ARROW_FLY_HEIGHT           = 65.
        private constant real ARROW_TURN_RATE            = 8.*bj_DEGTORAD
        private constant real ALLOW_HIT_AFTER            = 1.  // Minimum amount of seconds until the missile can damage again.
    endglobals
    
    // Filter valid target units.
    private function FilterUnits takes unit target, player owner returns boolean
        return UnitAlive(target) and IsUnitEnemy(target, owner)
    endfunction
    // Set the maximum travel distance. Does no longer count, when a traget is found.
    private constant function GetFlyDistance takes integer level returns real
        return  800. + (200*level)
    endfunction
    // Set the number of pierce events. 
    private function GetPiercingCount takes integer level returns integer
        return GetRandomInt(0, 3) + 1*level 
    endfunction
    //*  Customize all missile members to your needs.
    private function CustomizeMissile takes Missile missile, integer level returns nothing
        set missile.speed  = 20. + 0.*level
        set missile.damage = 0. + 30.*level
        set missile.collision = 32.
        set missile.model = "Abilities\\Spells\\Other\\BlackArrow\\BlackArrowMissile.mdl"
        set missile.scale = 1.
    endfunction
    
//========================================================================
// Guided arrow code. Make changes carefully.
//========================================================================

    globals
        // "tempOwner" is always set to the owner of the arrow before "ConsiderUnitFiltered" is called.
        private player tempOwner = null
    endglobals
    
    // Filters valid targets for the arrow.
    private function ConsiderUnitsFiltered takes nothing returns boolean
        return FilterUnits(GetFilterUnit(), tempOwner)
    endfunction

    private struct GuidedArrow extends array
        static boolexpr filter 
        
        // Runs when the missile is deallocated.
        private static method onRemove takes Missile missile returns boolean
            call SpellIndex(missile.data).destroy()
            return true
        endmethod
        
        // Runs when the maximum range is reached.
        private static method onFinish takes Missile missile returns boolean
            local SpellIndex dex = missile.data
            return (dex.phase == 0) or (GetUnitTypeId(dex.target) == 0)
        endmethod
        
        // Runs when a missile collides with a unit.
        private static method onCollide takes Missile missile, unit hit returns boolean
            local SpellIndex dex = missile.data
            // Only the target is valid.
            if (hit == missile.target) then
                // Allows the missile to hit the target again after 1 second.
                call missile.enableHitAfter(hit, ALLOW_HIT_AFTER)
                call UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                set missile.target = null
                set missile.turn = 0
                // Reduce the total amount of pierces.
                set dex.count = dex.count - 1
            endif
            return (dex.count < 0)
        endmethod
        
        private static method searchTarget takes Missile missile returns nothing
            local SpellIndex dex = missile.data
            //
            set tempOwner = missile.owner
            call GroupEnumUnitsInRange(SpellIndex.GLOBAL_GROUP, missile.x, missile.y, UNIT_DETECTION_AOE , thistype.filter)
            if (FirstOfGroup(SpellIndex.GLOBAL_GROUP) != null) then
                set bj_groupRandomConsidered = 0
                set bj_groupRandomCurrentPick = null
                call ForGroup(SpellIndex.GLOBAL_GROUP, function GroupPickRandomUnitEnum)
                call GroupClear(SpellIndex.GLOBAL_GROUP)
                // 
                set missile.target = bj_groupRandomCurrentPick
                set dex.target = bj_groupRandomCurrentPick
                set dex.time = ARROW_ACTION_DELAY
                set missile.turn = ARROW_TURN_RATE
                // Just in case this unit was already hit, while missile.target was null.
                // Remember: onCollide runs before onPeriod.
                call missile.removeHitWidget(bj_groupRandomCurrentPick)
                //
                set bj_groupRandomCurrentPick = null
                if (dex.fx != null) then
                    call DestroyEffect(dex.fx)
                    set dex.fx = null
                endif
                set dex.fx = AddSpecialEffectTarget(ON_TARGET_FX, dex.target, FX_ATTACH_POINT) 
            endif
        endmethod
        
        // Runs every timer interval. Let's break the mechanics down.
        private static method onPeriod takes Missile missile returns boolean
            local SpellIndex dex = missile.data
            
            // In this phase no target was found yet. And no pierce event did take place.
            if (dex.target == null) and (dex.count >= 0) then              
                // Check the action delay.
                if (dex.time > 0.) then
                    set dex.time = dex.time - Missile_TIMER_TIMEOUT
                    return false
                endif
                call searchTarget(missile)
            //
            // In this phase we have a target stored on dex.target, but the missile doesn't know that.
            elseif (missile.target == null) and (UnitAlive(dex.target)) then
                // Check the action delay.
                if (dex.time > 0.) then
                    set dex.time = dex.time - Missile_TIMER_TIMEOUT
                    return false
                endif
                set dex.time = ARROW_ACTION_DELAY
                set missile.turn = ARROW_TURN_RATE
                set missile.target = dex.target
            //
            // In this phase the target died. Let the missile run out.
            elseif not UnitAlive(dex.target) then
                set dex.count = -1
                set dex.target = null
                set missile.turn = 0.
                set missile.target = null
                set missile.collision = 0.
                if (dex.phase == 1) then
                    set dex.phase = 0
                    call missile.origin.move(missile.x, missile.y, missile.z)
                    call missile.impact.move(missile.x + Cos(missile.angle)*ARROW_FLY_RUN_OUT_DISTANCE, missile.y + Sin(missile.angle)*ARROW_FLY_RUN_OUT_DISTANCE, missile.z)
                endif
            endif
            return false
        endmethod

        implement MissileStruct
    endstruct 

    private function OnEffect takes nothing returns nothing
        local unit source = GetTriggerUnit()
        local integer level = GetUnitAbilityLevel(source, GUIDED_ARROW_ABILITY)
        local real x = GetUnitX(source)
        local real y = GetUnitY(source)
        local real angle = Atan2(GetSpellTargetY() - y, GetSpellTargetX() - x)
        local SpellIndex dex = SpellIndex.create()
        // Create a new missile and assign its members.
        local Missile missile = Missile.create(x, y, ARROW_FLY_HEIGHT, angle, GetFlyDistance(level), ARROW_FLY_HEIGHT)
        // Allows configuration.
        call CustomizeMissile(missile, level)
        // Override user settings. These missile members are reserved.
        set missile.source = source
        set missile.owner = GetTriggerPlayer()
        set missile.data = dex
        set missile.target = null
        call GuidedArrow.launch(missile)
        // Assign data to the spell index.
        set dex.count = GetPiercingCount(level)
        set dex.time = ARROW_ACTION_DELAY
        set dex.target = GetSpellTargetUnit()
        if not FilterUnits(dex.target, missile.owner) then
            set dex.target = null
        endif
        set dex.phase = 1
        // Run effects.
        if (dex.target != null) then
            set dex.fx = AddSpecialEffectTarget(ON_TARGET_FX, dex.target, FX_ATTACH_POINT) 
        endif
        set source = null
    endfunction

    private function Init takes nothing returns nothing
        set GuidedArrow.filter = Filter(function ConsiderUnitsFiltered)
        call RegisterSpellEffectEvent(GUIDED_ARROW_ABILITY, function OnEffect)
    endfunction

endlibrary
 
Top