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 havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  4. The poll for Hive's 12th Concept Art Contest is up! Go cast your vote for your favourite genie!
    Dismiss Notice
  5. Travel to distant realms and encounter scenes unknown to the common folk. The Greatest of Adventures is upon us with the 8th Cinematic Contest. Join in on a fun ride.
    Dismiss Notice
  6. The 18th Icon Contest is ON! Choose any ingame unit and give him/her Hero abilities. Good luck to all.
    Dismiss Notice
  7. Contestants are to create a scene set in the Stone Age. Come and see what you can come up with. We wish you the best of luck!
    Dismiss Notice
  8. Colour outside the lines! Techtree Contest #13 is a go. The contest is optionally paired.
    Dismiss Notice
  9. Greetings cerebrates, our Swarm needs new spawners that will have numerous children. Join the HIVE's 31st Modeling Contest - Spawners and Spawned! The contest is optionally paired.
    Dismiss Notice
  10. 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.

Warlike Spirit v1.1

Submitted by nhocklanhox6
This bundle is marked as approved. It works and satisfies the submission rules.
Spell Description

Call a force of spirit that fly around the caster,
spirit attacks the enemy in 1000 range and also steal 6/7/8/9% of
their hit points and return it to the caster.
But if the enemy die after spirit has stealed their hit points
a Warlike Spirit will be created. Spirit lasts 5.5/6.5/7.5/8.5 seconds.



Warlike Spirit
Code (vJASS):
//***************************************************************************
//***************************************************************************
//***************************************************************************
//
//              *          *            *   *    *   *
//            *   *      *   *           * *     *   *
//          *      *   *       *          *      * . *
//                                        Warlike Spirit
//                 *****                 ----------------
//                                           By: Hara
//
//          - Sepll Description:
//  `                       - Call a force of spirit that fly around the caster,
//                            spirit attacks the enemy in 1000 range and also steal 6/7/8/9% of
//                            their hit points and return it to the caster.
//                            But if the enemy die after spirit has stealed their hit points
//                            a Warlike Spirit will be created. Spirit lasts 5.5/6.5/7.5/8.5 seconds.
//
//          - Installation:
//                                - Import/copy Warlike Spirit code and Custom Units/Abilities/Buffs to your map
//                                - Change the SPELL_ID, WARLIKE_SPIRIT_ID, WS_STUN_ABI, DUMMY_ID if needed
//                                - You may view the raw ID of the objects by pressing CTRL+D in the object editor
//                                - You may play with the configurables below
//
//
//***************************************************************************
//***************************************************************************
//***************************************************************************

//! zinc
library WarlikeSpirit{

    //------------ CONFIGURATION ------------//
   
    //Spirit count called per cast
    private constant integer    SPIRIT_COUNT        = 10;
    //Dummy rawcode, change if needed
    private constant integer    DUMMY_ID            = 'e000';
    //Warlike spirit rawcode, change if needed
    private constant integer    WARLIKE_SPIRIT_ID   = 'e001';
    //Spell rawcode, change if needed
    private constant integer    SPELL_ID            = 'A000';
    //Warlke Spirit's stun rawcode, change if needed
    private constant integer    WS_STUN_ABI         = 'A001';
    //An effect when spirit hits enemy
    private constant string     HIT_EFFECT          = "Abilities\\Spells\\Human\\StormBolt\\StormBoltMissile.mdl";
    //Enemy's effect attachment
    private constant string     HIT_ATTACHMENT      = "chest" ;
    //An effect when spirit return hit points to the caster
    private constant string     HIT_CASTER          = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl";
    //Caster's effect attachment
    private constant string     CASTER_ATTACHMENT   = "origin" ;
    //Distance between the caster and the force of spirit
    private constant real       DISTANCE            = 250;
    //Hit distance between spirit and enemy
    private constant real       HIT_DISTANCE        = 3;
    //Spirit missle speed/PERIODIC when it attacks enemy
    private constant real       TARGET_SPEED        = 25;
    //Spirit swivel speed/PERIODIC
    private constant real       SPIRIT_SPEED        = 5;
    //Spirit's enemy detection range
    private constant real       RANGE               = 1000;
    //Time before fire spirit to the enemy
    private constant real       SHOOT_TIME          = 0.5;
    //Default scale value of warlike spirit
    private constant real       WS_DEFAULT_SCALE    = 1.5;
    //Timer period
    private constant real       PERIODIC            = .0312500;

    private constant attacktype ATTACK_TYPE         =   ATTACK_TYPE_MAGIC;
    private constant damagetype DAMAGE_TYPE         =   DAMAGE_TYPE_COLD;
    private constant weapontype WEAPON_TYPE         =   WEAPON_TYPE_METAL_HEAVY_BASH;
   
    //***************************************//
   
    //------------ NON-CONFIGURATION --------//
   
    private constant timer      TIMER            = CreateTimer();
   
    private constant real       inBetween        = 6.283188 / SPIRIT_COUNT ; //6.283188 = bj_PI*2
   
    private constant group      G                = CreateGroup();
   
    private constant location  locZ              = Location(0,0);
   
    private          integer    MUI                = -1;
    private          integer    MUI2[];
   
    private          unit       Caster;
    private          player     CasterOwner;
   
   
    //***************************************//
   
    //IsUnitAlive func
    function UnitAlive (unit filterUnit)                -> boolean{
        return (!IsUnitType(filterUnit,UNIT_TYPE_DEAD) && GetUnitTypeId(filterUnit)!=0);
    }
   
    //Spell damage
    function getDamagePercentage (integer abiLevel)     -> real{
        return (abiLevel + 5.);
    }
   
    //Spell duration
    function getDuration (integer abiLevel)             -> real {
        return (abiLevel + 4.5);
    }
   
    //Summon warlike spirit duration
    function getWarlikeDuration (integer abiLevel)      -> real {
        return (abiLevel + 1.5);
    }
   
    //--------------------------------
   
    function DistanceBetween (unit A, unit B)           -> real {
        real dx = GetUnitX(B) - GetUnitX(A);
        real dy = GetUnitY(B) - GetUnitY(A);
        return SquareRoot(dx * dx + dy * dy);
    }
   
    function filterFunc()                               -> boolean{
        return UnitAlive(GetFilterUnit());
    }
   
    function GroupPickEnum (){
   
        unit    tempU   =   GetEnumUnit();
   
        if(UnitAlive(tempU) && IsUnitEnemy(tempU,CasterOwner)) {
       
            bj_groupRandomConsidered = bj_groupRandomConsidered + 1;
       
            if (GetRandomInt(1,bj_groupRandomConsidered) == 1)
                bj_groupRandomCurrentPick = tempU;
       
        }
       
        tempU   =   null;
       
    }

    function RandomUnit (group whichGroup) -> unit{
   
        bj_groupRandomConsidered = 0;
        bj_groupRandomCurrentPick = null;
        ForGroup(whichGroup, function GroupPickEnum);
       
        return bj_groupRandomCurrentPick;
       
    }
   
    //-----------------------------------
   
    private struct WarlikeSpiritPlugin{
   
        unit caster;
        unit dummy;
        unit target;
        unit warlike_spirit=null;
       
        real angle;
        real height = 0.;
        real time   = 0.;
        real maxDis;
        real maxHei;
        real dmg;
        real ws_dur;
       
        integer abiLevel;
       
        player casterOwner;
       
        boolean fire  = false;
        boolean back  = false;
        boolean pause = false;
       
        static method release( integer tempInt ){
       
            //Recycle
            MUI2[tempInt] = MUI2[MUI];
            MUI2[MUI]     = -1;
            MUI           = MUI - 1;
           
            if( MUI == -1 )
                PauseTimer(TIMER);
       
        }

        method getHeight(boolean b,real tempX,real tempY) -> real{
       
            real tempA;
       
            if( b ){
                           
                tempA               = GetUnitFlyHeight(target) + 1.;
                height              = tempA - (tempA * ( (DistanceBetween(target , dummy)/maxDis) ));
                return                bj_RADTODEG*Atan2(GetUnitY(target)-tempY,GetUnitX(target)-tempX ) * bj_DEGTORAD;
                               
            }else{
           
                tempA               = GetUnitFlyHeight(dummy) + 1.;
                height              = maxHei * ( (DistanceBetween(caster,dummy)/maxDis) );
                return                bj_RADTODEG*Atan2(GetUnitY(caster)-tempY,GetUnitX(caster)-tempX ) * bj_DEGTORAD;
               
            }
       
        }
       
        method shootBack(){
       
            maxHei          = GetUnitFlyHeight(dummy);
            maxDis          = DistanceBetween(caster , dummy);
            back            = true;
            target          = null;
       
        }
       
        static method onPeriodic(){
       
            thistype    this;
       
            integer     tempInt     =       0;
            integer     tempInt2;
           
            real        tempX;
            real        tempY;
            real        tempA;
           
            unit        tempU       =       null;
           
            while(tempInt <= MUI){
           
                this                = MUI2[tempInt];
               
                //
               
                if( UnitAlive(dummy) && UnitAlive(caster) ){
               
                    if(  fire  ) {
                   
                        if( !pause ){
                       
                            tempX                   = GetUnitX(dummy);
                            tempY                   = GetUnitY(dummy);
                           
                            tempA                   = getHeight(target != null,tempX,tempY);
                                                     
                            tempX                   = tempX + TARGET_SPEED * Cos(tempA);
                            tempY                   = tempY + TARGET_SPEED * Sin(tempA);
                           
                            if( !back ){

                                if( UnitAlive(target) ){
                               
                                    if( IsUnitInRangeXY(target,tempX,tempY,HIT_DISTANCE) ){
                               
                                        DestroyEffect(AddSpecialEffectTarget(HIT_EFFECT,target,HIT_ATTACHMENT));
                                       
                                        dmg=(GetUnitState(target,UNIT_STATE_MAX_LIFE)*dmg)/100.;
                                       
                                        UnitDamageTarget(caster,target,dmg,true,false,ATTACK_TYPE,DAMAGE_TYPE,WEAPON_TYPE);
                                       
                                        //Check if the enemy is alive
                                       
                                        if(!UnitAlive(target)){
                                       
                                            //Create warlike spirit
                                            warlike_spirit  =   CreateUnit(casterOwner,WARLIKE_SPIRIT_ID,tempX,tempY,0.);
                                            SetUnitAbilityLevel(warlike_spirit,WS_STUN_ABI,abiLevel);
                                            UnitApplyTimedLife(warlike_spirit,0x0,ws_dur);
                                            SetUnitScale(dummy,0.,0.,0.);
                                            pause           =   true;
                                       
                                        }else
                                            shootBack();
                                       
                                   
                                    }
                               
                                }else
                                    KillUnit(dummy);
                               
                           
                            }else{
                           
                                if( IsUnitInRangeXY(caster,tempX,tempY,HIT_DISTANCE) ){
                               
                                    DestroyEffect(AddSpecialEffectTarget(HIT_CASTER,caster,CASTER_ATTACHMENT));
                                   
                                    if( warlike_spirit != null ){
                                        dmg            = dmg/2;
                                        warlike_spirit = null;
                                    }
                                   
                                    SetWidgetLife(caster,GetWidgetLife(caster)+dmg);
                                   
                                    RemoveUnit(dummy);
                                   
                                    dummy       = null;
                                    caster      = null;
                                    casterOwner = null;
                                   
                                    this.destroy();
                                    release(tempInt);
                                       
                                }

                            }
                       
                        }else if ( !UnitAlive( warlike_spirit ) ){
                            pause           = false;
                           
                            SetUnitX(dummy,GetUnitX(warlike_spirit));
                            SetUnitY(dummy,GetUnitY(warlike_spirit));
                           
                            SetUnitScale(dummy,WS_DEFAULT_SCALE,WS_DEFAULT_SCALE,0.);
                           
                            shootBack();
                        }
                   
                    }else {
                   
                        //
                       
                        angle               = angle + SPIRIT_SPEED;
                        tempA               = angle *  bj_DEGTORAD;
                       
                        tempX               = GetUnitX(caster) + DISTANCE * Cos(tempA);
                        tempY               = GetUnitY(caster) + DISTANCE * Sin(tempA);
                       
                        //

                        if( target == null ){
                       
                            CasterOwner         = casterOwner;
                            GroupEnumUnitsInRange(G,tempX,tempY,RANGE,function filterFunc);
                            target              = RandomUnit(G);
                            GroupClear(G);
                       
                        }else{
                       
                            if( time <= 0. ){
                           
                                fire                = true;
                                maxDis              = DistanceBetween(target , dummy);
                               
                                UnitPauseTimedLife(dummy,true);
                           
                            }else
                                time         = time - PERIODIC;
                       
                        }
                       
                    }
                   
                    if( !pause ){
                   
                        MoveLocation(locZ,tempX,tempY);
                        SetUnitX(dummy,tempX);
                        SetUnitY(dummy,tempY);
                        SetUnitFlyHeight(dummy,GetLocationZ(locZ)+height,99999.);
                   
                    }
               
                }else{
               
                    this.destroy();
                    release(tempInt);
                }

                //
               
                tempInt              = tempInt + 1;
           
            }
           
       
        }
   
        static method new_WarlikeWpirit(unit whichDummy , real whichAngle, real shootTime){
       
            thistype this       = allocate();
           
            MUI                   = MUI + 1;
            MUI2[MUI]               = this;
       
            caster              = Caster;
            dummy               = whichDummy;
           
            abiLevel            = GetUnitAbilityLevel(caster,SPELL_ID);
           
            angle               = whichAngle;
           
            casterOwner         = CasterOwner;
           
            time                = shootTime;
           
            dmg                 = getDamagePercentage(abiLevel);
            ws_dur              = getWarlikeDuration(abiLevel);
           
            UnitApplyTimedLife(dummy,0x0,getDuration(abiLevel));
           
            if(MUI == 0){
       
                TimerStart(TIMER,PERIODIC,true,function thistype.onPeriodic);
       
            }
       
        }
   
    }
   
    //
   
    function onCast() -> boolean{

        real     angle    = 360./SPIRIT_COUNT;
        real     x;
        real     y;
        real     tempX;
        real     tempY;
        real     tempA;
        real     tempT    = 0.;
       
        integer  temp     = 1;
       
        Caster            = GetTriggerUnit();
        CasterOwner       = GetTriggerPlayer();
       
        tempX             = GetUnitX(Caster);
        tempY             = GetUnitY(Caster);
       
        while(temp <= SPIRIT_COUNT){
       
            tempA         = temp  * inBetween;
           
            x             = tempX + DISTANCE * Cos(tempA);
            y             = tempY + DISTANCE * Sin(tempA);
           
            //
            tempT         = tempT + SHOOT_TIME;
            WarlikeSpiritPlugin.new_WarlikeWpirit( CreateUnit(CasterOwner,DUMMY_ID,x,y,0.) , bj_RADTODEG*Atan2(y-tempY,x-tempX ) , tempT );
           
            //
       
            temp          = temp + 1;
       
        }

        return false;
       
    }
   
    function onInit(){
       
        integer i               = 0;
        trigger trig            = CreateTrigger();
       
        while(i<bj_MAX_PLAYERS){
       
            TriggerRegisterPlayerUnitEvent(trig,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null);
            i = i + 1;
           
        }
       
        TriggerAddCondition(trig,function onCast);
   
    }

}
//! endzinc


https://www.youtube.com/watch?v=Vq9vbPkDV50&feature=youtu.be

Changelog
v1.0: First release version.
v1.1: Minor bugs fixed.


Keywords:
warlike,spirit
Contents

Just another Warcraft III map (Map)

Reviews
Moderator
Submission: [c]Warlike Spirit v1.1[r] Date: [c]18.05.2015[r] Status: [c]Approved[r] Rating: [c]4/5[r] Link [c]Post[r] Moderator: [c]IcemanBo
  1. Submission: Warlike Spirit v1.1
    Date: 18.05.2015
    Status: Approved
    Rating: 4/5
    Link Post
    Moderator: IcemanBo
     
  2. Almia

    Almia

    Joined:
    Apr 24, 2012
    Messages:
    4,860
    Resources:
    35
    Spells:
    30
    Tutorials:
    4
    JASS:
    1
    Resources:
    35
    OMG.

    Why would you code functions like that? (Those spacings).

    I can't review your Zinc code like that.
     
  3. bowser499

    bowser499

    Joined:
    Jul 20, 2009
    Messages:
    782
    Resources:
    7
    Tools:
    1
    Maps:
    3
    Spells:
    3
    Resources:
    7
    Okay, let's analyze this spell properly.

    Visual:
    Volchachka told me his opinion about visuals. I'm not very good at rating these since I love every flashy eyecandy. ^^

    Effects are 10/10. Swift execution, nice idea, good harmony, no effect overloading and stable FPS rate on them.

    Coding:
    • In Configuration section of your code there are following things which can be improved:
      • PERIODIC            = .0312500
        is better to be typed without unnecessary zeros, just like:
        .03125
        .
      • constant attacktype ATTACK_TYPE
        ,
        constant damagetype DAMAGE_TYPE
        and
        constant weapontype WEAPON_TYPE
        should definetly be private. Else your spell may be incompatible with something that somebody implemented in the map. For example, in his own damage system he wants something names
        ATTACK_TYPE
        to be public.
      • private constant group      G
        is not a good name for global variable because it's not too understandable for end user (and no matter that it's non-configurable).
      • private constant real       inBetween        = 360. / SPIRIT_COUNT * .0174533;
        , if it's constant, must be simplified as much as its possible. For the compiler, a/b*c is same with a*c/b (to make it as a/(b*c) for compiler you'd placed brackets. So you can calculate the value of 360*0.0174533 = 360*PI/180 = 2*PI which is equal to 6.283185. With the way I have shown, in your case, you can get much more precise result than just reapproximating the approximated. The direct calculation'd given you 6.283188.
      • You do not need the
        GetUnitTypeId(filterUnit) != 0
        comparison because only units which do not exist in the map have the unit type 0. I did a test on a blank 32x32 map:
        Tested Code
        Code (vJASS):
        function CheckUnitWithZeroType takes nothing returns nothing
            local unit  testUnit  = CreateUnit(Player(0),0,0,0,0)
            local group testGroup = CreateGroup()
            local unit  groupUnit
           
            if IsUnitType(testUnit,UNIT_TYPE_DEAD) then
                call BJDebugMsg("The unit with type 0 is dead.")
            else
                call BJDebugMsg("The unit with type 0 is not dead.")
            endif
           
            call GroupEnumUnitsInRange(testGroup,0.,0.,200.,null)
           
            loop
                set groupUnit = FirstOfGroup(testGroup)
                exitwhen groupUnit == null
                call BJDebugMsg("Unit detected! Type: "+I2S(GetUnitTypeId(groupUnit)))
                call GroupRemoveUnit(testGroup,groupUnit)
            endloop
           
            call DestroyGroup(testGroup)
           
            set testGroup = null
            set testUnit  = null
        endfunction

        This resulted the following debug: truly, the unit with a type 0 considered as not dead, but such a unit wasn't included in group (no debug from group loop at all => 0 units). So you gotta remove this condition as unnecessary one. Units with a type 0 will be correctly interpreted by Blizzard's functions, this should not be a point of worry to filter it out. It turns out as unnecessary code line.
    • In Spell section of your code there are following things which can be improved:
      • As endless times before this one, I'm pointing out the same mistake: please use
        GetWidgetX
        and
        GetWidgetY
        instead of
        GetUnitX
        and
        GetUnitY
        because first ones are slightly faster than second ones because of less type inherits.
      • Why
        struct WarlikeSpiritPlugin
        is public? This is used only by your spell, so how about making it private as all other things which are inside? This increases the compatibility of your spell with other spells in some map.
      • Regarding
        dmg=(GetUnitState(target,UNIT_STATE_MAX_LIFE)*dmg)/100.;
        ... Actually *.01 is much faster than /100.
      • Why you need to declare
        player   p        = GetTriggerPlayer();
        when you already have
        CasterOwner       = GetTriggerPlayer();
        ? Besides of that, that player wasn't nulled properly by you => it produced a memory leak.
      • I fear that it's pretty much non-MUI because of globals like Caster and CasterOwner which will overwrite if a different unit will cast the spell. Please reorganize that part so the spell becomes full MUI.

      Cannot give it more than 4/5 mostly because of that non-MUI'ness. Fix it up and I will update my review. :)

      4/5: Recommended


      How that helped me to become better coder:
      • Very smart usage of
        IsUnitInRangeXY
        function. After I did a couple of benchmark, comparing the speed of
        SquareRoot
        method and
        IsUnitInRangeXY
        , second one executed 81 times more than the SquareRoot-based method. That's pretty impressive finding!
     
  4. nhocklanhox6

    nhocklanhox6

    Joined:
    Feb 12, 2012
    Messages:
    343
    Resources:
    29
    Models:
    6
    Spells:
    22
    Tutorials:
    1
    Resources:
    29
    Fixed XD.

    Oops!, forgot :p.
     
  5. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,165
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    I had the honour to read 2 zinc spells today in spell section. \o/
    • Please deindex properly if caster was removed from game. That's necessarily needed.
    • Your speed setting is actually influenced by interval. Better would be to adapt it in config to normal WC3 movement speed/second.
      Or at least you should mention that it's /interval.
    • 6.283188
      ->
      bj_pi*2
      .
      You can stay with it actually, but with "pi" it's more intuitive and easier to understand/read.
    • bj_RADTODEG*Atan2(GetUnitY(target)-tempY,GetUnitX(target)-tempX ) * .0174533;

      why multiply with bj_RADTODEG, then with .0174533? Also .0174533 is harder to understand, it's not good to work with such values directly in code.
    • Names like "M" and "MD" are not descritive.

    nhocklanhox6 has not commented it, but as I could see he uses them as temporary container.

    Good and solid spell. But daheck, for me personaly you use too much free space sometimes in variable assignment or just between some code. :D ... ^_°
     
  6. nhocklanhox6

    nhocklanhox6

    Joined:
    Feb 12, 2012
    Messages:
    343
    Resources:
    29
    Models:
    6
    Spells:
    22
    Tutorials:
    1
    Resources:
    29
    :D, I just split types of variable assignment like: real with real, integer with integer, I think it will increase readable things. And the code, if the code length is not fit like: AAAAAAA with AAAAAAAAAAAAAAAA, I will split them by those spacings ^_<..

    Btw, thanks for the review :)..
     
    Last edited: May 1, 2015
  7. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,165
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    I just notice it now, because you're using the constants now,
    but if you use DEGTORAD and then RADTODEG again, it's redundant.

    MUI
    and
    MUI2
    is also not descriptive. "MUI" actually describes a compatibility.

    Is it intended that you still see the spirits for a few more seconds after death/removal?

    = 6.283188 / SPIRIT_COUNT ; //6.283188 = bj_PI*2

    ^I don't understand why you still prefer to use the number over the constant. :/

    Edit:

    But spell is coded well and works fine all in all.

    Approved
     
    Last edited: May 19, 2015
  8. bowser499

    bowser499

    Joined:
    Jul 20, 2009
    Messages:
    782
    Resources:
    7
    Tools:
    1
    Maps:
    3
    Spells:
    3
    Resources:
    7
    Because that's the way on how it works faster and has more oplimit space remaining.