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

Arcane Leash v1.3

  • Like
Reactions: Thanathos
192f41c4d9d19c4b7c4175dfa68a0c904g.jpg


This spell is made in Zinc, it's MUI and leakless.
I documented all the customizable properties of the spell.

Uses my under development projectile system and requires Jass NewGen Pack to be used.

Since my projectile system is still under development there may still be some things that should be improved, still, the spell should work without problems of permormance.

Enjoy and comment, suggestions are welcome!

JASS:
    library TheArcaneLeash requires DyMotionMain, DyMotionMath, DyMotionMotion, DyMotionAPI
    {
        //: ===------
        //  Customizable Properties
        //: ===------
       
        //*****************************\\
        //=-=-=» Main Properties «=-=-=\\
        //: ===------
        //  the raw code of the spell itself
        constant integer SPELL_ID = 'A000';
        //: ===------
        //  the raw code of the Dummy Stun Spell
        constant integer STUN_SPELL_ID = 'A002';
        //: ===------
        //  this is the buff that will be used when units are stunned
        constant integer STUN_BUFF_ID = 'BSTN';
        //: ===------
        //  this shouldn't be changed, unless you use another ability to stun, firebolt does that, so we put here the issue order id
        constant string STUN_ORDER_ID = "thunderbolt";
        //: ===------
        //  This is the duration of the stun, half duration for heroes
        constant real STUN_DURATION = 6.;
        //: ===------
        //  this is part of the Stun countdown, better not mess with it
        constant real INTERVAL = 0.2;
        //: ===------
        //  the radius at which each missile will pick a unit
        constant real GRAB_RADIUS = 100.;
        //: ===------
        //  This is the effect that will appear when the caster casts the spell
        constant string FX = "AncientExplode.mdl";
        //: ===------
        //  This effect appears when a grabbed unit is thrown away
        constant string FX2 = "Abilities\\Spells\\Undead\\OrbOfDeath\\AnnihilationMissile.mdl";
        //: ===------
        //  this is the damage dealt to the unit
        constant real DAMAGE = 170.;
       
        //***********************************\\
        //=-=-=» Projectile Properties «=-=-=\\
        //: ===------
        //  The model effect of the projectiles
        constant string PROJECTILE_FX = "ArcaneMissileComplete.mdl";
        //: ===------
        //  this is the trail effect of the projectiles
        constant string PROJECTILE_FX2 = "Doodads\\Cinematic\\GlowingRunes\\GlowingRunes7.mdl";
        //: ===------
        //  number of projectiles
        constant integer PROJECTILE_N = 10;
        //: ===------
        //  The speed of the projectiles while they are in the air
        constant real PROJECTILE_INITIAL_SPEED = 200.;//per second
        //: ===------
        //  The speed of the projectiles once they hit the ground
        constant real PROJECTILE_GROUND_SPEED = 800.;//per second
        //: ===------
        //  the angle speed at which the missiles will rotate, giving that awesome effect you've seen ^^
        constant real PROJECTILE_ANGLE_SPEED = bj_PI;//per second
        //: ===------
        //  The projectiles maximum height, or the maximum height they can reach during the flight
        constant real PROJECTILE_MAX_HEIGHT = 300.;
        //: ===------
        //  The maximum distance the projectiles will fly until hit the ground
        constant real PROJECTILE_MAX_DISTANCE = 300.;
        //: ===------
        //  this is the life time of the projectiles, it's also the same value of how long the hero is channeling the spell
        //  if you change this value, you better change the channel duration on the spell editor too
        constant real PROJECTILE_DURATION = 6.;
       
        //**********************************\\
        //=-=-=» Knockback Properties «=-=-=\\
        //: ===------
        //  Speed at which units will be knockbacked
        constant real KNOCK_SPEED = 1500.;//per second
        //: ===------
        //  the effect of the knockback, usually dust effects
        constant string KNOCK_FX = "Small Dust.mdl";
       
        //: ===------
        //  End of Customizable Properties
        //: ===------
       
        function SpellStartActions( )
        {
            Jump jp;
            unit c = GetTriggerUnit( );
            integer i;
            real a = dm_2Pi / PROJECTILE_N;
            real x = GetUnitX( c ), y = GetUnitY( c );
           
            for ( 1 <= i <= PROJECTILE_N )
            {
                jp = Jump.create( c, x, y, a * i, PROJECTILE_MAX_DISTANCE );
                jp.FxPath = PROJECTILE_FX;
                jp.Speed = PROJECTILE_INITIAL_SPEED;
                jp.MaxHeight = PROJECTILE_MAX_HEIGHT;
                jp.LifeTime = PROJECTILE_DURATION;
                jp.Size = 1.6;
            }
            DestroyEffect( AddSpecialEffect( FX, x, y ) );
           
            c = null;
        }
       
        struct Data
        {
            unit stunned;
            unit dummy;
            real duration;
            integer id;
           
            static method StunUnit( unit stunned, real duration )
            {
                Data d = Data.allocate( );
                d.stunned = stunned;
                d.duration = duration;
               
                d.dummy = Recycle.Dummy;
                SetUnitX( d.dummy, GetUnitX( stunned ) );
                SetUnitY( d.dummy, GetUnitY( stunned ) );
                PauseUnit( d.dummy, false );
                UnitAddAbility( d.dummy, STUN_SPELL_ID );
                IssueTargetOrder( d.dummy, STUN_ORDER_ID, stunned );
               
                if ( N == 0 ) TimerStart( T, INTERVAL, true, function Data.Update );
                d.id = N;
                dat[N] = d;
                N += 1;
            }
           
            static method Update( )
            {
                integer i;
               
                for ( 0 <= i < N )
                {
                    if ( dat[i].duration > 0. )
                        dat[i].duration -= INTERVAL;
                    else
                        dat[i].destroy( );
                }
            }
           
            method onDestroy( )
            {
                UnitRemoveAbility( stunned, STUN_BUFF_ID );
                UnitRemoveAbility( dummy, STUN_SPELL_ID );
                Recycle.Dummy = dummy;
                N -= 1;
                dat[id] = dat[N];
                dat[id].id = id;
                if ( N == 0 ) PauseTimer( T );
            }
            private static timer T = CreateTimer( );
            private static Data dat[];
            private static integer N = 0;
        }
       
        struct Jump extends dmMotion
        {
            private boolean IsFlying = true;
            private real MaxDist;
            private real dist = 0.; //current distance
            private real arc = 0.6;
            private real iniZ;
            unit Grabbed = null;
            unit Source;
           
            method operator Arc= ( real value )
            { arc = 4 * value; }
            method operator MaxHeight= ( real value )
            { arc = 4 * value / MaxDist; }
           
            private static group GrabbedGroup = CreateGroup( );
           
            static method create( unit caster, real x, real y, real angle, real maxDist ) -> Jump
            {
                Jump jp = Jump.allocate( x, y, angle, null );
               
                jp.Source = caster;
                jp.Owner = GetOwningPlayer( caster );
                jp.MaxDist = maxDist;
                MoveLocation( loc, x, y );
                jp.iniZ = GetLocationZ( loc );
               
                return jp;
            }
           
            method onMotion( )
            {
                unit u;
                if ( GetWidgetLife( Source ) < 0.405 )
                    destroy( );
                else
                {
                    if ( IsFlying )
                    {
                        dist += Speed * dm_UPDATE_INTERVAL;
                        MoveLocation( loc, X, Y );
                        H = arc * ( MaxDist - dist ) * ( dist / MaxDist ) - GetLocationZ( loc ) + iniZ;
                       
                        if ( H > 0. ) Z = H;
                        else
                        {
                            IsFlying = false;
                            Speed = PROJECTILE_GROUND_SPEED;
                            Z = 70.;
                            AngleSpeed = PROJECTILE_ANGLE_SPEED;
                        }
                    }
                    else
                    {
                        DestroyEffect( AddSpecialEffect( PROJECTILE_FX2, X, Y ) );
                        if ( Grabbed != null )
                        {
                            SetUnitX( Grabbed, X );
                            SetUnitY( Grabbed, Y );
                        }
                        else
                        {
                            GroupEnumUnitsInRange( tempGroup, X, Y, GRAB_RADIUS, null );
                            u = FirstOfGroup( tempGroup );
                            if ( u != null && IsUnitEnemy( u, Owner ) && GetWidgetLife( u ) > 0.405 && !IsUnitType( u, UNIT_TYPE_FLYING )
                                 && !IsUnitType( u, UNIT_TYPE_MAGIC_IMMUNE ) && !IsUnitInGroup( u, GrabbedGroup ) )
                            {
                                Grabbed = u;
                                GroupAddUnit( GrabbedGroup, u );
                                PauseUnit( u, true );
                            }
                            GroupClear( tempGroup );
                            u = null;
                        }
                    }
                }
            }
           
            method onDestroy( )
            {
                if ( Grabbed != null )
                {
                    KnockbackAngleRadians( Grabbed, Angle, KNOCK_SPEED, KNOCK_FX );
                    GroupRemoveUnit( GrabbedGroup, Grabbed );
                    PauseUnit( Grabbed, false );
                    UnitDamageTarget( Source, Grabbed, DAMAGE, true, false, null, null, null );
                    if ( IsUnitType( Grabbed, UNIT_TYPE_HERO ) )
                        Data.StunUnit( Grabbed, STUN_DURATION / 2. );
                    else
                        Data.StunUnit( Grabbed, STUN_DURATION );
                    Grabbed = null;
                    DestroyEffect( AddSpecialEffect( FX2, X, Y ) );
                }
            }
           
            private static location loc = Location( 0., 0. );
            private static real H = 0.;
        }
       
        function onInit( )
        {
            trigger t = CreateTrigger( );
            integer i;
            unit u = Recycle.Dummy;
           
            for ( 0 <= i < 12 )
                TriggerRegisterPlayerUnitEvent( t, Player(i), EVENT_PLAYER_UNIT_SPELL_CHANNEL, null );
               
            TriggerAddCondition( t, function( ) -> boolean { return GetSpellAbilityId( ) == SPELL_ID; } );
            TriggerAddAction( t, function SpellStartActions );
           
            UnitAddAbility( u, SPELL_ID );
            UnitRemoveAbility( u, SPELL_ID );
            UnitAddAbility( u, STUN_SPELL_ID );
            UnitRemoveAbility( u, STUN_SPELL_ID );
            Recycle.Dummy = u;
            Preload( FX );
            Preload( FX2 );
            Preload( PROJECTILE_FX );
            Preload( PROJECTILE_FX2 );
            Preload( KNOCK_FX );
           
            u = null;
            t = null;
        }
    }

Credits
  • Arcane Missile Special Effect made by Weep
  • AncientExplode Special Effect made by WILLTHEALMIGHTY
  • Small Dust Special Effect made by me
  • BoundSentinel Lib by Vexorian
  • IsTerrainWalkable Lib by Anitarf

I know I shouldn't use imported special effects, but I couldn't find any good effects in wc3 for what I had in mind.

Keywords:
arcane leash magic power purple grab fancy effects Zinc
Contents

Arcane Leash (Map)

Reviews
20:25, 8th Apr 2010 The_Reborn_Devil: The effects are really nice, but I noticed that some places in the code you could have made the functions simpler. An example is the function GetDistance in your math lib. You don't need those locals, but...

Moderator

M

Moderator

20:25, 8th Apr 2010
The_Reborn_Devil:

The effects are really nice, but I noticed that some places in the code you could have made the functions simpler. An example is the function GetDistance in your math lib. You don't need those locals, but that's just some trivial things though. Other than that the code looks good.


Status: Approved
Rating: Recommended
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
It's okay that this is coded in Zinc since it comes with jasshelper. There are also other spells coded in Zinc that have been approved here.

Note that I did not check this spell in-game; I'm only looking at the code right now.


  • Make damage improve with level of the spell. You could do the same for other parts of the spell, like stun duration.
  • It looks like you are using custom effects; you should give credit to their makers.
  • I think you don't need to use GroupClear on tempGroup.
  • FirstOfGroup loops are slower than using ForGroup, but it's not mandatory to change it if you don't want to use temporary globals.
  • You don't really need to inline TriggerRegisterAnyUnitEventBJ
 
Level 9
Joined
Sep 5, 2007
Messages
358
  • Make damage improve with level of the spell. You could do the same for other parts of the spell, like stun duration.
  • It looks like you are using custom effects; you should give credit to their makers.
  • I think you don't need to use GroupClear on tempGroup.
  • FirstOfGroup loops are slower than using ForGroup, but it's not mandatory to change it if you don't want to use temporary globals.
  • You don't really need to inline TriggerRegisterAnyUnitEventBJ
  • I didn't make it level scale because it was supposed to be a ultimate spell, but that's something to remember for a next spell
  • The credits are inside the map, but I forgot to tell it here, going to update it now.
  • I used that because, I enumed all units within a distance from a point, and in the end the group would still hold units, so it would be better to clear the group to avoid eventual conflicts.
  • I used FirstOfGroup only once, I just wanted to get a single unit from the group and make the missile grab it, that's why ^^
  • oh I know of that, but sometimes I like to keep my code clean of red stuff. Still I'm sure it won't cause any performance issues.

MortAr- said:
Compatibility
Currently, spells are allowed in Vanilla GUI (No third party GUI modifications), JASS, or vJASS. All spells must be 1.24 compatible.
Where did you saw "Zinc" here?

indeed you're right, but I'm sure Zinc was released after that rule went out.
Besides if vexorian approves it and uses it (sometimes I believe) and also implemented it in JNGP why shouldn't Hive approves it as well? :)
 
[*]I used that because, I enumed all units within a distance from a point, and in the end the group would still hold units, so it would be better to clear the group to avoid eventual conflicts.

Enumerations auto-clear the groups. The only real case where you need GroupClear() is for shadow references, but I don't think you'd have a problem with that in your case.

Good job though on the rest of the code. =)
 
Level 9
Joined
Sep 5, 2007
Messages
358
The code seems efficient (when not looking at the spell there is no lag), but the effects used are too much - my FPS drops from ~200 to ~25...

that might be because of the trail effects.
The glowing runes death effect is sort of too long, but if I put an interval between effects, that would look quite ugly. :S
And I don't know another good SFX fit for the job here :S
 
Level 1
Joined
Oct 16, 2010
Messages
7
Im very new/noob in map editing i try to follow the step but i cant make it work. When I try to save the map an error appears. It say "se esperaba final de linea" in english "was expeted end of line"

//! zinc
library TheArcaneLeash requires DyMotionMain, DyMotionMath, DyMotionMotion, DyMotionAPI
{

Where is my mistake? Can you help me?
by the way it looks awesome and is very usefull fot the hero im creating
 
Top