1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  3. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still haven't received your rank award? Then please contact the administration.
    Dismiss Notice
  4. Lead your forces to battle in the 15th Techtree Contest. The call is yours, commander!
    Dismiss Notice
  5. The reforging of the races is complete. Come see the 14th Techtree Contest Results.
    Dismiss Notice
  6. It's time to choose your horse in the race - the 32nd Modeling Contest Poll is up!
    Dismiss Notice
  7. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Water Spirits v1.01

Submitted by scorpion182
This bundle is marked as approved. It works and satisfies the submission rules.
Water Spirits

Unleashes water spirits that rotate around the target ally, and attack nearby enemy units, dealing damage, then return to the target ally, healing for 50% damage that dealt by them.


Codes

Code (vJASS):

library WaterSpirits requires TimerUtils, GetNearest, SimError, xefx, xedamage optional BoundSentinel
//-----------------------------------------------------------------------------------------------
// Water Spirits v1.01 by scorpion182
// requires:
//          - TimerUtils, SimError, xe by Vexorian
//          - GroupUtils (required by GetNearest) by Rising_Dusk
//          - GetNearest by grim001
//
//
//
//
//-----------------------------------------------------------------------------------------------
//----------------CALIBRATION SECTION------------------------------------------------------------
    globals
        private keyword data //don't touch this
        private constant integer SPELL_ID='A000' //ability rawcode
        private constant integer MAX_MISSILE=50 //maximum missiles
        private constant string ORB_PATH="Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl" //orb fx path
        private constant string DAMAGE_FX="" //on-damage effect
        private constant string DAMAGE_ATTCH="origin" //on-death effect attachment point
        private constant string HEAL_FX="" //0n-heal effect
        private constant string HEAL_ATTCH="origin" //on-heal effect attachment point
        private constant real SCALE=1.2 //orb scale
        private constant real ANGLE_SPEED=.15 //orb angle speed
        private constant real HEIGHT=100. //orb height
        private constant real MIN_DIST=80. //minimum distance to absorb, make sure the value higher than 80, if not it won't work correctly
        private constant real MAX_DIST=1000. //max distance to attack the target
        private constant string ERROR_MSG="Another instance is running for this unit." //error message
    endglobals
   
    private constant function GetDamage takes integer lvl returns real
        return 15.+lvl*0 //do damage amount of hp each orb
    endfunction
   
    private constant function GetHealFactor takes integer lvl returns real
        return 0.50+lvl*0 //heals 50% of the damage dealt by it
    endfunction
   
    private constant function GetMissileCount takes integer lvl returns integer
        return 3+lvl*0 //orb count
    endfunction
   
    private constant function GetDuration takes integer lvl returns real
        return 60.+lvl*0 //spell duration
    endfunction
   
    private constant function GetDistance takes integer lvl returns real
        return 150.+lvl*0 //orb's rotation distance
    endfunction
   
    private constant function GetAoE takes integer lvl returns real
        return 500.+lvl*0 //aoe each orb to find a target
    endfunction
   
    private constant function GetSpeed takes integer lvl returns real
        return 800.*XE_ANIMATION_PERIOD+lvl*0. //orb missile speed
    endfunction
   
    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
        set spellDamage.visibleOnly=true
        set spellDamage.damageSelf=true
        set spellDamage.damageAllies=true //heals for amount of the damage dealt by orb
        set spellDamage.allyfactor=-1.0   //to the target ally.
    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 IsUnitType(u,UNIT_TYPE_STRUCTURE)==false and IsUnitEnemy(u,GetOwningPlayer(s.caster))==true and IsUnitVisible(u,GetOwningPlayer(s.caster))==true
    endfunction
//------------------END OF CALIBRATION-----------------------------------------------------------
    globals
        private constant real A=2*bj_PI
        private xedamage xed
        private group casters=CreateGroup()
    endglobals
   
    private struct data
        unit caster //target ally
        unit owner //caster
        unit array target[MAX_MISSILE] //target enemies
        xefx array fx[MAX_MISSILE] //effect
        boolean array IsAttack[MAX_MISSILE] //attacking status
        boolean array IsHome[MAX_MISSILE] //homing status
        boolean array IsFinish[MAX_MISSILE] //finish status
        real array angle[MAX_MISSILE] //current angle
        real array tx[MAX_MISSILE] //current x track
        real array ty[MAX_MISSILE] //current y track
        real duration //spell duration
        boolean array heal[MAX_MISSILE] //attacking succeed status
        timer t
        integer total //orbs total
        integer lvl //ability level
        private static thistype temp
       
        static method create takes unit c, unit o returns data
            local thistype this=data.allocate()
            local integer i=0
            local real x=GetUnitX(c)
            local real y=GetUnitY(c)
            local real a
           
            set .lvl=GetUnitAbilityLevel(c,SPELL_ID)
            set .total=GetMissileCount(.lvl)
            set .duration=GetDuration(.lvl)
            set .caster=c
            set .owner=o
            set .t=NewTimer()
           
            loop
            exitwhen i==.total
                set a=i*A/.total
                set .fx[i]=xefx.create(x,y,a)
                set .fx[i].fxpath=ORB_PATH
                set .fx[i].scale=SCALE
                set .fx[i].z=HEIGHT
                set .angle[i]=a
                set i=i+1
            endloop
           
            call GroupAddUnit(casters,c)
           
            return this
        endmethod
       
        static method VictimFilter takes nothing returns boolean
            return IsValidTarget(GetFilterUnit(),temp)
        endmethod
       
        static method onLoop takes nothing returns nothing
            local thistype this=data(GetTimerData(GetExpiredTimer()))
            local real x=GetUnitX(.caster)
            local real y=GetUnitY(.caster)
            local integer i=0
            local real x1
            local real y1
            local real x2
            local real y2
            local real ang
            local real newx
            local real newy
            local real dx
            local real dy
            local real dist
           
            if .duration>0. and not IsUnitType(.caster, UNIT_TYPE_DEAD) then
                set .duration=.duration-XE_ANIMATION_PERIOD
               
                loop
                exitwhen i==.total
                   
                    set .angle[i]=.angle[i]+ANGLE_SPEED
                    set tx[i]=x+GetDistance(.lvl)*Cos(.angle[i])
                    set ty[i]=y+GetDistance(.lvl)*Sin(.angle[i])
                   
                    //if not attacking, order to rotate
                    if not .IsAttack[i] then
                   
                        set .fx[i].x=tx[i]
                        set .fx[i].y=ty[i]
                       
                        set temp=this
                        set .target[i]=GetNearestUnit(.fx[i].x,fx[i].y,GetAoE(.lvl),Condition(function data.VictimFilter))
                       
                       
                        if .target[i]!=null then
                            set .IsAttack[i]=true
                        endif

                    else
                        //if find a target, order to attack
                        if not .IsHome[i] then
                       
                            set x1=.fx[i].x
                            set y1=.fx[i].y
                            set x2=GetUnitX(.target[i])
                            set y2=GetUnitY(.target[i])
                            set dx=x2-x1
                            set dy=y2-y1
                            set ang=Atan2(dy,dx)
                            set newx=x1+GetSpeed(.lvl)*Cos(ang)
                            set newy=y1+GetSpeed(.lvl)*Sin(ang)
                            set dist=SquareRoot(dx*dx+dy*dy)
                       
                            if (dist>MIN_DIST) then
                                set .fx[i].x=newx
                                set .fx[i].y=newy
                                set .fx[i].xyangle=ang
                               
                                //if the target is too far away from the caster or dead, order it to come back
                                if dist>MAX_DIST or IsUnitType(.target[i], UNIT_TYPE_DEAD) then
                                    set .IsHome[i]=true
                                    set .target[i]=null
                                endif
                               
                            else
                                //damage the target
                                if not IsUnitType(.target[i], UNIT_TYPE_DEAD) then
                                    call xed.useSpecialEffect(DAMAGE_FX,DAMAGE_ATTCH)
                                    call xed.damageTarget(.owner,.target[i],GetDamage(.lvl))
                                    set .heal[i]=true
                                endif
                                set .IsHome[i]=true
                                set .target[i]=null
                            endif
                        else
                        //back to caster
                            if not .IsFinish[i] then
                                set x1=.fx[i].x
                                set y1=.fx[i].y
                                set x2=GetUnitX(.caster)
                                set y2=GetUnitY(.caster)
                                set dx=x2-x1
                                set dy=y2-y1
                                set ang=Atan2(dy,dx)
                                set newx=x1+GetSpeed(.lvl)*Cos(ang)
                                set newy=y1+GetSpeed(.lvl)*Sin(ang)
                                set dist=SquareRoot(dx*dx+dy*dy)
                           
                                if (dist>MIN_DIST) then
                                    set .fx[i].x=newx
                                    set .fx[i].y=newy
                                    set .fx[i].xyangle=ang
                                else
                                    //heal the caster
                                    if not IsUnitType(.caster, UNIT_TYPE_DEAD) and .heal[i] then
                                        call xed.useSpecialEffect(HEAL_FX,HEAL_ATTCH)
                                        call xed.damageTarget(.owner,.caster,GetDamage(.lvl)*GetHealFactor(.lvl))
                                    endif
                                    set .IsFinish[i]=true
                                endif
                            else
                                //back to its rotation point
                                set x1=.fx[i].x
                                set y1=.fx[i].y
                                set x2=tx[i]
                                set y2=ty[i]
                                set dx=x2-x1
                                set dy=y2-y1
                                set ang=Atan2(dy,dx)
                                set newx=x1+GetSpeed(.lvl)*Cos(ang)
                                set newy=y1+GetSpeed(.lvl)*Sin(ang)
                                set dist=SquareRoot(dx*dx+dy*dy)
                           
                                if (dist>MIN_DIST) then
                                    set .fx[i].x=newx
                                    set .fx[i].y=newy
                                    set .fx[i].xyangle=ang
                                else
                                    set .IsFinish[i]=false
                                    set .IsHome[i]=false
                                    set .IsAttack[i]=false
                                    set .heal[i]=false
                                    set .fx[i].x=tx[i]
                                    set .fx[i].y=ty[i]
                                endif
                            endif
                        endif
                       
                    endif
                   
                    set i=i+1
               
                endloop
            else
                call .destroy()
            endif
        endmethod
       
        private method onDestroy takes nothing returns nothing
            local integer i=0
           
            call ReleaseTimer(.t)
            call GroupRemoveUnit(casters,.caster)
           
            loop
            exitwhen i==.total
                call .fx[i].destroy()
                set i=i+1
            endloop
           
        endmethod
       
        static method SpellEffect takes nothing returns boolean
            local thistype this
           
            if (GetSpellAbilityId()==SPELL_ID) then
                set this=data.create(GetSpellTargetUnit(),GetTriggerUnit())
                call SetTimerData(.t,this)
                call TimerStart(.t,XE_ANIMATION_PERIOD,true,function data.onLoop)
            endif
            return false
        endmethod
       
        static method Stop takes nothing returns boolean
            local unit c=GetSpellTargetUnit()
            local unit u=GetTriggerUnit()
           
            if GetSpellAbilityId()==SPELL_ID and IsUnitInGroup(c,casters) then
                call SimError(GetOwningPlayer(u),ERROR_MSG)
                call PauseUnit(u,true)
                call IssueImmediateOrder(u,"stop")
                call PauseUnit(u,false)
            endif
           
            set c=null
            set u=null
            return false
        endmethod
       
        static method onInit takes nothing returns nothing
            local trigger t=CreateTrigger()
           
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_CAST)
            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 (Optional) by Vexorian
- GroupUtils by Rising_Dusk
- GetNearest by grim001
- SimError by Vexorian

History:
~ v1.00 First Release.
~ v1.01 Fixed the codes.

Keywords:
water, shield, spirits, unit, target, rotate, spiral, order, heal, damage, around, bounce
Contents

Water Spirits v1.01 (Map)

Reviews
Moderator
09:46, 4th Feb 2010 TriggerHappy: Coding looks good, as well as the effect.
  1. 09:46, 4th Feb 2010
    TriggerHappy:

    Coding looks good, as well as the effect.
     
  2. Maker

    Maker

    Joined:
    Mar 6, 2006
    Messages:
    9,174
    Resources:
    17
    Maps:
    2
    Spells:
    14
    Tutorials:
    1
    Resources:
    17
    This is a cool spell. I like it. Good job there.

    It's easy to customize which is great.

    The only I'd like to improve is the tooltip, you could add the duration, damage and range there.
     
  3. Kingz

    Kingz

    Joined:
    Jun 5, 2008
    Messages:
    2,470
    Resources:
    6
    Spells:
    5
    Tutorials:
    1
    Resources:
    6
    You could add a "hit" effect.

    Also instead of this:
    Code (vJASS):

    .duration<GetDuration(.lvl)


    Couldn't you just use:
    Code (vJASS):

    set .duration = .duration - *interval*
    if .duration > 0.00

    ?

    Also i dunno why you use a method which calls upon a private functions:
    Code (vJASS):

            static method VictimFilter takes nothing returns boolean
                local unit u=GetFilterUnit()
               
                return IsValidTarget(u,temp)
            endmethod


    Why not use the function itself and cut out the middle man?

    Why are you using a boolexpr variable? Why not use the Condition(function)?

    No need for this:
    Code (vJASS):
    call DestroyBoolExpr(b)


    Boolean expresions don't leak anymore.(they never did aparently).

    Instead of:
    IsUnitType(.target[i], UNIT_TYPE_DEAD)


    You could use:
    GetWidgetLife(.target[i]) > .405



    I heard somewhere that you shouldn't use negative damage for healing, use SetWidgetLife() instead, since negative damage could bug some damage detection systems.

    EDIT:

    In creation and loop methods you could replace "data" with "thistype".
     
  4. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,221
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Dude. You should really learn to comment more. Indenting and spaces is also beh,...

    Code (vJASS):

        private struct data
            unit caster
            unit owner
            unit array target[MAX_MISSILE]
            xefx array fx[MAX_MISSILE]
            boolean array IsAttack[MAX_MISSILE]
            boolean array IsHome[MAX_MISSILE]
            boolean array IsFinish[MAX_MISSILE]
            real array angle[MAX_MISSILE]
            real array tx[MAX_MISSILE]
            real array ty[MAX_MISSILE]
            real duration=0
            boolean array heal[MAX_MISSILE]
            timer t
            integer total
            integer lvl
     


    Would'nt
    Code (vJASS):

        private struct data
            unit        array       target      [MAX_MISSILE]   //: The targets
            xefx        array       fx          [MAX_MISSILE]   //: The effects
            boolean     array       IsAttack    [MAX_MISSILE]   //: Boolean whether they are attacked
            boolean     array       IsHome      [MAX_MISSILE]   //: Boolean whether they are at home
            boolean     array       IsFinish    [MAX_MISSILE]   //: Boolean whether they finished
            real        array       angle       [MAX_MISSILE]   //: An array of angles
            real        array       tx          [MAX_MISSILE]   //: An array of X-Coordinates
            real        array       ty          [MAX_MISSILE]   //: ^ same for y
            boolean     array       heal        [MAX_MISSILE]   //: The heal in an array

            timer                   t                           //: The current object timer
            integer                 total                       //: Total number
            integer                 lvl                         //: The level of the spell
            real                    duration    = 0             //: Value about the duration
            unit                    caster                      //: Caster unit.
            unit                    owner                       //: Owning unit
     

    be better?

    It doesn't matter that much actually, but imho, I think its a lot better to read (and understand)
     
    Last edited: Feb 4, 2010
  5. scorpion182

    scorpion182

    Joined:
    Jun 9, 2008
    Messages:
    575
    Resources:
    21
    Maps:
    5
    Spells:
    16
    Resources:
    21
    UPDATED v1.01

    @Maker
    added in-game.

    @TriggerHappy
    ~VictimFilter leaks, you never null the unit reference.
    fixed.

    ~What's the point of destroying a boolexpr? They are stored in a table and recycled afaik.
    fixed.

    @ Kingz

    ~You could add a "hit" effect.
    added.


    ~Also instead of this:...
    fixed.

    ~Also i dunno why you use a method which calls upon a private functions:
    ~Why not use the function itself and cut out the middle man?

    err..it's to make the target configureable at the calibration section.

    ~Why are you using a boolexpr variable? Why not use the Condition(function)?
    fixed.

    ~Instead of:
    IsUnitType(.target, UNIT_TYPE_DEAD)
    You could use:
    GetWidgetLife(.target) > .405


    GetWidgetLife(.target) > .405 sometimes work, sometimes don't. Also it can break if you increase the health of a unit after it has died.

    ~I heard somewhere that you shouldn't use negative damage for healing, use SetWidgetLife() instead, since negative damage could bug some damage detection systems.

    it comes from xedamage, i cant do anything about that.

    ~In creation and loop methods you could replace "data" with "thistype".
    fixed.

    @ Anachron
    don't ask my indenting :p
     
  6. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,221
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Indenting is important for me. Its kinda bad to not have an standard for indents in your code.
     
  7. scorpion182

    scorpion182

    Joined:
    Jun 9, 2008
    Messages:
    575
    Resources:
    21
    Maps:
    5
    Spells:
    16
    Resources:
    21
    I'm just too lazy to do it :p, but it would be better next time :grin:
     
  8. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,221
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Sounds good enough for me.
    Still, I like this spell. Keep it up.
     
  9. Kingz

    Kingz

    Joined:
    Jun 5, 2008
    Messages:
    2,470
    Resources:
    6
    Spells:
    5
    Tutorials:
    1
    Resources:
    6
    What is this used for:

    GetUnitTypeId(u) != 0


    It makes no sense at all :p

    How can a unit have an ID of 0?
     
  10. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,221
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    When its null.

    Scorpion, atleast CnP my indenting please.
     
  11. Kingz

    Kingz

    Joined:
    Jun 5, 2008
    Messages:
    2,470
    Resources:
    6
    Spells:
    5
    Tutorials:
    1
    Resources:
    6
    How can a unit have a null ID?
     
  12. scorpion182

    scorpion182

    Joined:
    Jun 9, 2008
    Messages:
    575
    Resources:
    21
    Maps:
    5
    Spells:
    16
    Resources:
    21
    ok i'll fix the indenting later :grin:

    the unit does not exist in the game :cute:
     
  13. D4RK_G4ND4LF

    D4RK_G4ND4LF

    Joined:
    Feb 4, 2009
    Messages:
    1,196
    Resources:
    20
    Models:
    3
    Spells:
    15
    Tutorials:
    2
    Resources:
    20
    great spell :D
    and 'cause your spell is so great I made a GUI version :grin:
    full credits + rep to you of cause
    (it probably has a few problems I did not find yet but most people should prefer yours anyway)

    5/5
     

    Attached Files:

  14. scorpion182

    scorpion182

    Joined:
    Jun 9, 2008
    Messages:
    575
    Resources:
    21
    Maps:
    5
    Spells:
    16
    Resources:
    21
    thank you :smile: