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;
}
}