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

Meteorites v1.01

Meteorites

This spell was inspired by AoM by vile1

Ability Type: Channeling
Target Type: None
Effect: Area Damage

Description:
Calls down meteorites from the sky to circle around the caster and deal damage to enemies on impact.

JASS:
/*
    meteorites v1.01 by scorpion182
    requires:
    timer utils, bound sentinel, xedamage, xefx by Vexorian
    grouputils by Rising_Dusk
*/
library Meteorites requires TimerUtils, xedamage, xefx, GroupUtils, BoundSentinel, SimError
    private keyword data //don't touch this
//---------------------------------------------------------------------------------------------
//--------------CALIBRATION SECTION------------------------------------------------------------
    globals
        private constant integer SPELL_ID='A000' //ability rawcode
        private constant string ORDER_ID="starfall" //ability order string
        private constant integer MAXMISSILE=5 //keep this value higher or equal than GetMissileCount
        private constant string PATH="Abilities\\Spells\\Other\\Volcano\\VolcanoMissile.mdl" //missile path
        private constant string DAMAGE_FX="Abilities\\Weapons\\DemolisherFireMissile\\DemolisherFireMissile.mdl" //on-damage effect
        private constant string DAMAGE_ATTCH="origin" //on-damage attachment point
        private constant real SCALE=1.5 //missile scale
        private constant real INIT_HEIGHT=1000. //missile initial height
        private constant real HEIGHT=100. //missile normal height
        private constant real HEIGHT_DEC=50. //missile height decrement per interval
        private constant real M_INTERVAL=1. //how fast the missile created
        private constant real ANGLE_SPEED=.15 //missile angle speed per interval
        private constant string ERROR_MSG="Another instance is running for this caster." //error message
    endglobals
    
    private constant function GetMissileCount takes integer lvl returns integer
        return 5+lvl*0 //how many missile created
    endfunction
    
    private constant function GetDuration takes integer lvl returns real
        return 15.+lvl*0 //spell duration
    endfunction
    
    private constant function GetAoE takes integer lvl returns real
        return 450.+lvl*0 //area of effect
    endfunction
    
    private constant function GetCollisionSize takes integer lvl returns real
        return 150.+lvl*0 //missile collision size
    endfunction
    
    private constant function GetDamage takes integer lvl returns real
        return 100.+lvl*50 //do damage each missile 
    endfunction
    
    //damage filter
    private function DamageOptions takes xedamage spellDamage returns nothing
        set spellDamage.dtype=DAMAGE_TYPE_UNIVERSAL
        set spellDamage.atype=ATTACK_TYPE_NORMAL
        set spellDamage.exception=UNIT_TYPE_STRUCTURE //don't damage building
        set spellDamage.visibleOnly=true //only damage visible unit
        set spellDamage.damageAllies=false //damage allies if true
    endfunction
    
    //filter the targets, should match the value from DamageOptions
    private function IsValidTarget takes unit u, data s returns boolean
        return not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) != 0 and not IsUnitType(u,UNIT_TYPE_STRUCTURE) and IsUnitEnemy(u,GetOwningPlayer(s.caster)) and IsUnitVisible(u,GetOwningPlayer(s.caster))
    endfunction
//------------END OF CALIBRATION----------------------------------------------------------------
    globals
        private group casters=CreateGroup()
        private xedamage xed
        private constant real A=2*bj_PI
    endglobals
    
    private struct data
        unit caster
        timer t
        integer lvl
        xefx array fx[MAXMISSILE]
        real array angle[MAXMISSILE]
        integer array flag[MAXMISSILE]
        private static thistype temp
        integer count=0
        real interval=0.
        real duration=0.
        integer total=0
        integer n=0
        boolean finish=false
        
        static method create takes unit c returns data
            local data this=data.allocate()
            local integer i=0
            
            set .lvl=GetUnitAbilityLevel(c,SPELL_ID)
            set .caster=c
            set .t=NewTimer()
            set .total=GetMissileCount(.lvl)
            //create the missiles
            loop
            exitwhen i==.total
                set .fx[i]=xefx.create(GetUnitX(c),GetUnitY(c),i*A/.total)
                set .fx[i].fxpath=""
                set .fx[i].scale=SCALE
                set .fx[i].z=INIT_HEIGHT
                set .angle[i]=i*A/.total
                set .flag[i]=0
                set i=i+1
            endloop
            
            call GroupAddUnit(casters,c)
            
            return this
        endmethod
        
        static method move takes nothing returns nothing
            local integer i=0
            local real a
            local unit u
            local unit target=null
            
            loop
            exitwhen i==temp.count
                //configure the height each missile
                if (temp.fx[i].z>HEIGHT) then
                    set temp.fx[i].z=temp.fx[i].z-HEIGHT_DEC
                endif
               
                set i=i+1
            endloop
            
            set i=0
            loop
            exitwhen i==temp.total
                if temp.fx[i]!=0 then
                    //move the missiles
                    set a=temp.angle[i]+ANGLE_SPEED
                    set temp.fx[i].x=GetUnitX(temp.caster)+GetAoE(temp.lvl)*Cos(a)
                    set temp.fx[i].y=GetUnitY(temp.caster)+GetAoE(temp.lvl)*Sin(a)
                    set temp.angle[i]=a
                    //search a target
                    call GroupEnumUnitsInArea(ENUM_GROUP,temp.fx[i].x,temp.fx[i].y,GetCollisionSize(temp.lvl),BOOLEXPR_TRUE)
                    
                    //i don't know how to exit in enumeration function, so i use this firstofgroup loop :D
                    loop
                        set u=FirstOfGroup(ENUM_GROUP)
                    exitwhen u==null or target!=null
                        call GroupRemoveUnit(ENUM_GROUP,u)
                        if IsValidTarget(u,temp) and temp.fx[i].z==HEIGHT then
                            
                            
                            call temp.fx[i].destroy()
                            set temp.n=temp.n+1
                            set temp.flag[i]=1
                           
                            if temp.n==temp.count and GetUnitCurrentOrder(temp.caster)!=OrderId(ORDER_ID) then
                                set temp.finish=true
                                set temp.duration=GetDuration(temp.lvl)
                            endif
                            //damage
                            call xed.useSpecialEffect(DAMAGE_FX,DAMAGE_ATTCH)
                            call xed.damageTarget(temp.caster,u,GetDamage(temp.lvl))
                            set target=u
                        endif
                    endloop
                endif
                
                set i=i+1
            endloop
            
            set target=null
            set u=null
        endmethod
        
        static method Loop takes nothing returns nothing
            local data this=data(GetTimerData(GetExpiredTimer()))
            
            set temp=this
            
            if (.duration<GetDuration(.lvl) and not IsUnitType(.caster, UNIT_TYPE_DEAD) and not .finish ) then
                call data.move()
                set .duration=.duration+XE_ANIMATION_PERIOD
            else
                call .destroy()
                return
            endif
            
            if .count<GetMissileCount(.lvl) and .interval==M_INTERVAL and GetUnitCurrentOrder(.caster)==OrderId(ORDER_ID) then
                set .fx[.count].fxpath=PATH
                set .count=.count+1
                //order caster to stop when he succes summons all missiles
                if (.count==GetMissileCount(.lvl)) then
                    call IssueImmediateOrder(.caster,"stop")
                endif
                
                set .interval=0.
            else
                //preventing bug when aborting spell before the first missile created
                //if GetUnitCurrentOrder(.caster)!=OrderId(ORDER_ID) and .count==0 then
                if GetUnitCurrentOrder(.caster)!=OrderId(ORDER_ID) and (.count==0 or .n==.count)then
                    call .destroy()
                endif
                
                set .interval=.interval+XE_ANIMATION_PERIOD
            endif
            
        endmethod
        
        static method SpellEffect takes nothing returns boolean
            local data this
            local unit c=GetSpellAbilityUnit()
            
            if GetSpellAbilityId()==SPELL_ID and not IsUnitInGroup(c,casters) then
                set this=create(c)
                call SetTimerData(.t,this)
                call TimerStart(t,XE_ANIMATION_PERIOD,true,function data.Loop)
            endif
            
            set c=null
            return false
        endmethod
        
        private method onDestroy takes nothing returns nothing
            local integer i=0
            
            call ReleaseTimer(.t)
            call GroupRemoveUnit(casters,.caster)
            
            loop
            exitwhen i==.total
                if .fx[i]!=0 and .flag[i]==0 then
                    call .fx[i].destroy()
                endif
                set i=i+1
            endloop
            
        endmethod
        
        static method Stop takes nothing returns boolean
            local unit c=GetSpellAbilityUnit()
            
            if GetSpellAbilityId()==SPELL_ID and IsUnitInGroup(c,casters) then
                call SimError(GetOwningPlayer(c),ERROR_MSG)
                call PauseUnit(c,true)
                call IssueImmediateOrder(c,"stop")
                call PauseUnit(c,false)
            endif
            
            set c=null
            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 data.SpellEffect))
            
            set t=CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_CHANNEL)
            call TriggerAddCondition(t,Condition(function data.Stop))
            
            //init xedamage
            set xed=xedamage.create()
            call DamageOptions(xed)
        endmethod

    endstruct

endlibrary


Requires:
- TimerUtils by Vexorian
- xe by Vexorian
- BoundSentinel by Vexorian
- GroupUtils by Rising_Dusk
- SimError by Vexorian

Changelog:
~ v1.00 First release.
~ v1.01 Fixed bugs.


Keywords:
meteor, meteorites, fire, aom, nova, circle, duration, channeling, damage, area, massive, epic
Contents

Meteorites v1.01 (Map)

Reviews
17:20, 4th Jan 2010 The_Reborn_Devil: The coding looks good and the bug has been fixed. Status: Approved Rating: Useful

Moderator

M

Moderator

17:20, 4th Jan 2010
The_Reborn_Devil:
The coding looks good and the bug has been fixed.

Status: Approved
Rating: Useful
 
Level 6
Joined
Jan 6, 2006
Messages
204
The spell is ok.

Its a nice effect, but we all know that kind of spell/shield

Btw, you got a bug there. When casting then aborting and immediately trying to cast once again the error message "another instance is already in progress". Though you don't have any meteors flying around you. It seems that when walking in the near of enemy units the non-existing meteors trigger the collision and destroy. Damage(0) is being tried to dealt and units are getting aggro to your caster.

Coding seemed ok and the missing documentation is not required I guess since this spell is easy to figure out.

7/10 from me.
 
Level 16
Joined
Jun 9, 2008
Messages
734
brb, trying to fix the bugs :)

EDIT:

UPDATE v1.01

call temp.fx.destroy()
set temp.fx = temp.fx[temp.Total]
set temp.Total = temp.Total - 1
set i = i - 1


it can't becuse the replaced missile height will got screwed with the missing/destroyed height :grin:

JASS:
loop
            exitwhen i==temp.count
                //configure the height each missile
                if (temp.fx[i].z>HEIGHT) then
                    set temp.fx[i].z=temp.fx[i].z-HEIGHT_DEC
                endif

                set i=i+1
            endloop
 
Last edited:
Top