scope ArcaniaZero initializer init
//================================================================================================
// Arcania Zero
// by
// cedi
//
// Requires:
// TimerUtils by Vexorian
// Vector by Anitarf
// Heights by cedi
// IsTerrainWalkable by Anitarf
//================================================================================================
private keyword Arcania //System
private keyword Grabbed //System
private keyword ArcKB //System
globals
private constant integer SPELL_ID = 'A000'
//Raw Code of the Spell
private constant integer BUFF_ID = 'B000'
//Raw Code of the Buff, which is applied by the spell
private constant integer MAX_TARGETS = 10
//The max amount of units sucked at the same time
private constant real RED = 1.00
//Amount of Red color of the lightning ( Between 1 - 0 )
private constant real GREEN = 1.00
//Amount of Green color of the lightning ( Between 1 - 0 )
private constant real BLUE = 1.00
//Amount of Blue color of the lightning ( Between 1 - 0 )
private constant real ALPHA = 1.00
//Amount of Alpha color of the lightning ( Between 1 - 0 )
private constant real TIMER_INTERVAL = 0.035
//Interval of the moving should be around 0.035
private constant real PICK_INTERVAL = 0.10
//Each x will the spell search for new targets. Don't use a to small number
private constant real DRAG_SFX_INTERVAL = 0.10
//Each x the System will create a drag effect
private constant real KB_SFX_INTERVAL = 0.20
//Each x the System will create a knockback effect
private constant real COLLISION_RANGE = 100.00
//The range the suckeds unit have to reach to be knocked away
private constant real ADD_HEIGHT = 20.00
//Bonus Height that the lightning hits the middle of the sucked units
private constant real START_SPEED = 300.00
//The sucking speed in ms
private constant real ACCELERATION = 20.00
//The acceleration of the sucking speed in ms
private constant real INSTA_KILL_SPEED = 2000.00
//The needed speed to insta kill a unit in ms
private constant real START_KB_SPEED = 600.00
//The start speed of the knockback in ms
private constant real KB_SLOW_DOWN = 60.00
//Friction in ms
private constant real KB_MIN_SPEED = 20.00
//The knockback ends when the knockback speed is equal or smaller then this in ms
private constant string CANCEL_SFX = "Abilities\\Spells\\Human\\DispelMagic\\DispelMagicTarget.mdl"
//The effect when the connection is canceled
private constant string COLLISION_SFX = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"
//The effect when the unit collides
private constant string DRAG_SFX = "Abilities\\Spells\\Undead\\ReplenishMana\\ReplenishManaCaster.mdl"
//The effect when the units are sliding
private constant string GRAB_SFX = "Abilities\\Spells\\Undead\\AbsorbMana\\AbsorbManaBirthMissile.mdl"
//The effect attached to sucked units
private constant string LIGHTNING_STRING = "DRAM"
//String code of the lightning
private constant string KB_SFX = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
//Slide effect on the knockback
private constant string INSTA_KILL_SFX = "Objects\\Spawnmodels\\Human\\HumanLargeDeathExplode\\HumanLargeDeathExplode.mdl"
//Damage dealt only to enemies?
private constant boolean DAMAGE_ONLY_ENEMY = true
//The Attacktyp which is used to deal damage.
private constant attacktype ATTACK = ATTACK_TYPE_MAGIC
//System Variables
private Arcania TEMPDATA
private ArcKB TEMPKB
private integer TEMPINT = 0
private real REAL_START_SPEED = START_SPEED * TIMER_INTERVAL
private real REAL_ACCELERATION = ACCELERATION * TIMER_INTERVAL
private real REAL_INSTA_KILL_SPEED = INSTA_KILL_SPEED * TIMER_INTERVAL
private real REAL_START_KB_SPEED = START_KB_SPEED * TIMER_INTERVAL
private real REAL_KB_SLOW_DOWN = KB_SLOW_DOWN * TIMER_INTERVAL
private real REAL_KB_MIN_SPEED = KB_MIN_SPEED * TIMER_INTERVAL
private real TEMPREAL = 0.00
private vector TEMPVEC
private group TEMPGROUP = CreateGroup()
private real array DURATION
private real array AOE
private real array DAMAGE_COLLISION
private real array DAMAGE
private real array MANALOSE_COLLISION
private real array MANALOSE
endglobals
//Duration of the ability, should be the same for the spell
private function SET_DURATION takes nothing returns nothing
set DURATION[1] = 5.00
set DURATION[2] = 6.00
set DURATION[3] = 7.00
set DURATION[4] = 8.00
set DURATION[5] = 9.00
endfunction
//Suck AoE
private function SET_AOE takes nothing returns nothing
set AOE[1] = 500.00
set AOE[2] = 600.00
set AOE[3] = 700.00
set AOE[4] = 800.00
set AOE[5] = 900.00
endfunction
//Damage on collision
private function SET_DAMAGE_COLLISION takes nothing returns nothing
set DAMAGE_COLLISION[1] = 50.00
set DAMAGE_COLLISION[2] = 60.00
set DAMAGE_COLLISION[3] = 70.00
set DAMAGE_COLLISION[4] = 80.00
set DAMAGE_COLLISION[5] = 90.00
endfunction
//Damage per second ( when the unit is sucked )
private function SET_DAMAGE takes nothing returns nothing
set DAMAGE[1] = 50.00 * TIMER_INTERVAL
set DAMAGE[2] = 60.00 * TIMER_INTERVAL
set DAMAGE[3] = 70.00 * TIMER_INTERVAL
set DAMAGE[4] = 80.00 * TIMER_INTERVAL
set DAMAGE[5] = 90.00 * TIMER_INTERVAL
endfunction
//Mana lose on collision
private function SET_MANALOSE_COLLISION takes nothing returns nothing
set MANALOSE_COLLISION[1] = 50.00
set MANALOSE_COLLISION[2] = 60.00
set MANALOSE_COLLISION[3] = 70.00
set MANALOSE_COLLISION[4] = 80.00
set MANALOSE_COLLISION[5] = 90.00
endfunction
//Mana lose on suck, per second
private function SET_MANA takes nothing returns nothing
set MANALOSE[1] = 50.00 * TIMER_INTERVAL
set MANALOSE[2] = 60.00 * TIMER_INTERVAL
set MANALOSE[3] = 70.00 * TIMER_INTERVAL
set MANALOSE[4] = 80.00 * TIMER_INTERVAL
set MANALOSE[5] = 90.00 * TIMER_INTERVAL
endfunction
//================================================================================================
// SYSTEM
//================================================================================================
private function KBOuterControl takes nothing returns nothing
set TEMPKB = GetTimerData( GetExpiredTimer() )
call TEMPKB.InnerControl()
endfunction
private function GetNewAngle takes real angle returns real
if(angle<=45 and angle>=-45) then
set angle=angle*-1
elseif(angle>45 and angle<=135) then
set angle=180-angle
elseif(angle>135 and angle>=-135) then
set angle=360-angle
else
set angle=-180-angle
endif
return angle
endfunction
private struct ArcKB
unit target = null
Arcania data = 0
real cos = 0.00
real sin = 0.00
real speed = REAL_START_KB_SPEED
real interval = KB_SFX_INTERVAL
timer time = null
method onDestroy takes nothing returns nothing
call ReleaseTimer( .time )
if .data.mytargets != null and .data != 0 then
call GroupRemoveUnit( .data.mytargets, .target )
endif
endmethod
method InnerControl takes nothing returns nothing
local real x
local real y
set .speed = .speed - REAL_KB_SLOW_DOWN
if .speed <= REAL_KB_MIN_SPEED then
call .destroy()
endif
set x = GetUnitX( .target ) + .cos * .speed
set y = GetUnitY( .target ) + .sin * .speed
if IsTerrainWalkable( x, y ) then
call SetUnitX( .target, x )
call SetUnitY( .target, y )
else
call .destroy()
endif
set .interval = .interval - TIMER_INTERVAL
if .interval <= 0.00 then
set .interval = KB_SFX_INTERVAL
call DestroyEffect( AddSpecialEffect( KB_SFX, x, y ) )
endif
endmethod
static method createEx takes unit caster, unit buffed, unit target, Arcania data returns thistype
local thistype this = thistype.allocate()
set TEMPREAL = Atan2(GetUnitY( buffed ) - GetUnitY( target ), GetUnitX( buffed ) - GetUnitX( target ))
set TEMPREAL = GetNewAngle( TEMPREAL )
set .target = target
set .cos = Cos( TEMPREAL )
set .sin = Sin( TEMPREAL )
set .time = NewTimer()
set .data = data
call SetTimerData( .time, this )
call TimerStart( .time, TIMER_INTERVAL, true, function KBOuterControl )
return this
endmethod
static method create takes unit caster, unit buffed, unit target, Arcania data returns thistype
local thistype this = thistype.allocate()
set TEMPREAL = Atan2(GetUnitY( target ) - GetUnitY( buffed ), GetUnitX( target ) - GetUnitX( buffed ))
set .target = target
set .cos = Cos( TEMPREAL )
set .sin = Sin( TEMPREAL )
set .time = NewTimer()
set .data = data
call SetTimerData( .time, this )
call TimerStart( .time, TIMER_INTERVAL, true, function KBOuterControl )
return this
endmethod
endstruct
private function OuterControl takes nothing returns nothing
set TEMPDATA = GetTimerData( GetExpiredTimer() )
call TEMPDATA.InnerControl()
endfunction
private function IsTarget takes nothing returns boolean
return GetWidgetLife( GetFilterUnit() ) > 0.405 and IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false and IsUnitEnemy( GetFilterUnit(), GetOwningPlayer( TEMPDATA.caster ) ) and not IsUnitInGroup( GetFilterUnit(), TEMPDATA.mytargets ) and GetFilterUnit() != TEMPDATA.buffed and GetUnitAbilityLevel( GetFilterUnit(), BUFF_ID ) <= 0
endfunction
private struct Grabbed
unit grabbed = null
Arcania root = 0
lightning light = null
real speed = REAL_START_SPEED
real sfxinterval = DRAG_SFX_INTERVAL
effect grabbedsfx = null
vector vec
method End takes nothing returns nothing
debug call BJDebugMsg( "END" )
call DestroyEffect( AddSpecialEffectTarget( CANCEL_SFX, .grabbed, "origin" ) )
call .destroy()
endmethod
private method Collision takes nothing returns nothing
if not DAMAGE_ONLY_ENEMY or IsUnitEnemy( .root.buffed, GetOwningPlayer( .root.caster ) ) then
call UnitDamageTarget( .root.caster, .root.buffed, DAMAGE_COLLISION[.root.level], true, false, ATTACK, DAMAGE_TYPE_NORMAL, null )
call SetUnitState( .root.buffed, UNIT_STATE_MANA, GetUnitState( .grabbed, UNIT_STATE_MANA ) - MANALOSE_COLLISION[.root.level] )
endif
call UnitDamageTarget( .root.caster, .grabbed, DAMAGE_COLLISION[.root.level], true, false, ATTACK, DAMAGE_TYPE_NORMAL, null )
call SetUnitState( .grabbed, UNIT_STATE_MANA, GetUnitState( .grabbed, UNIT_STATE_MANA ) - MANALOSE_COLLISION[.root.level] )
call DestroyEffect( AddSpecialEffectTarget( COLLISION_SFX, .grabbed, "origin" ) )
call ArcKB.create( .root.caster, .root.buffed, .grabbed, .root )
//Lets do some damage and knock away
endmethod
method Control takes nothing returns boolean
local real x
local real y
//Death?
if GetWidgetLife( .grabbed ) <= 0.405 or IsUnitType( .grabbed, UNIT_TYPE_DEAD) == true or .grabbed == null or GetUnitAbilityLevel( .grabbed, BUFF_ID ) >= 1 then
call GroupRemoveUnit( .root.mytargets, .grabbed )
return false
endif
//New Direction
set TEMPREAL = Atan2(GetUnitY( .root.buffed ) - GetUnitY( .grabbed ), GetUnitX( .root.buffed ) - GetUnitX( .grabbed ))
//New Speed
set .speed = .speed + REAL_ACCELERATION
//Jep Insta kill ( could only happen when the unit is sucked by two buffs ).
if .speed >= REAL_INSTA_KILL_SPEED then
debug call BJDebugMsg( "insta kill" )
debug call BJDebugMsg( R2S( .speed ) )
call SetWidgetLife( .grabbed, 1.00 )
call UnitDamageTarget( .root.caster, .grabbed, 10.00, true, false, ATTACK, DAMAGE_TYPE_NORMAL, null )
call GroupRemoveUnit( .root.mytargets, .grabbed )
call DestroyEffect( AddSpecialEffectTarget( INSTA_KILL_SFX, .grabbed, "origin" ) )
return false
endif
//New Position
set TEMPVEC.x = Cos( TEMPREAL ) * .speed
set TEMPVEC.y = Sin( TEMPREAL ) * .speed
call .vec.add( TEMPVEC )
call .vec.scale( 0.5 )
call SetUnitX( .grabbed, GetUnitX( .grabbed ) + .vec.x )
call SetUnitY( .grabbed, GetUnitY( .grabbed ) + .vec.y )
call UnitDamageTarget( .root.caster, .grabbed, DAMAGE[.root.level], true, false, ATTACK, DAMAGE_TYPE_NORMAL, null )
call SetUnitState( .grabbed, UNIT_STATE_MANA, GetUnitState( .grabbed, UNIT_STATE_MANA ) - MANALOSE[.root.level] )
if not IsTerrainWalkable( GetUnitX( .grabbed ), GetUnitY( .grabbed ) ) then
debug call BJDebugMsg( "not walkable" )
call ArcKB.createEx( .root.caster, .root.buffed, .grabbed, .root )
return false
endif
//Collision?
set x = GetUnitX( .root.buffed ) - GetUnitX( .grabbed )
set y = GetUnitY( .root.buffed ) - GetUnitY( .grabbed )
set TEMPREAL = SquareRoot( x * x + y * y )
if TEMPREAL <= COLLISION_RANGE then
//Jep it's a collision
call .Collision()
return false
else
//Move light and create some nice effects
if .light != null then
call MoveLightningEx( .light, true, GetUnitX( .root.buffed ), GetUnitY( .root.buffed ), GetUnitZ( .root.buffed ) + ADD_HEIGHT, GetUnitX( .grabbed ), GetUnitY( .grabbed ), GetUnitZ( .grabbed ) + ADD_HEIGHT )
endif
//Some cool effects
set .sfxinterval = .sfxinterval - TIMER_INTERVAL
if .sfxinterval <= 0.00 then
set .sfxinterval = DRAG_SFX_INTERVAL
call DestroyEffect( AddSpecialEffect( DRAG_SFX, GetUnitX( .grabbed ), GetUnitY( .grabbed ) ) )
endif
endif
//Lets leave this
return true
endmethod
static method create takes Arcania root, unit target returns thistype
local thistype this = thistype.allocate()
local real angle = Atan2(GetUnitY( .root.buffed ) - GetUnitY( target ), GetUnitX( .root.buffed ) - GetUnitX( target ))
set .root = root
set .grabbed = target
set .light = AddLightningEx( LIGHTNING_STRING, true, GetUnitX( root.buffed ), GetUnitY( root.buffed ), GetUnitZ( root.buffed ) + ADD_HEIGHT, GetUnitX( target ), GetUnitY( target ), GetUnitZ( target ) + ADD_HEIGHT )
call SetLightningColor( .light, RED, GREEN, BLUE, ALPHA )
set .grabbedsfx = AddSpecialEffectTarget( GRAB_SFX, target, "chest" )
set .vec = vector.create( Cos( angle ) * REAL_START_SPEED, Sin( angle ) * REAL_START_SPEED, 0.00 )
return this
endmethod
method onDestroy takes nothing returns nothing
call DestroyEffect( .grabbedsfx )
set .grabbedsfx = null
call DestroyLightning( .light )
set .light = null
endmethod
endstruct
private struct Arcania
unit buffed = null
unit caster = null
integer count = 0
timer time = null
integer level = 1
boolean buffon = false
real duration = 0.00
real pickinterval = PICK_INTERVAL
real thereissomethingwrong = 5.00
group mytargets = CreateGroup()
boolean end = false
Grabbed array grabbeds[MAX_TARGETS]
private method PickThem takes nothing returns nothing
local unit u
if .count <= MAX_TARGETS then
set TEMPDATA = this
call GroupEnumUnitsInRange( TEMPGROUP, GetUnitX( .buffed ), GetUnitY( .buffed ), AOE[.level], Condition( function IsTarget ) )
loop
set u = FirstOfGroup( TEMPGROUP )
exitwhen u == null or .count > MAX_TARGETS
set .grabbeds[.count] = Grabbed.create( this, u )
call GroupAddUnit( .mytargets, u )
call GroupRemoveUnit( TEMPGROUP, u )
set .count = .count + 1
endloop
call GroupClear( TEMPGROUP )
endif
//Lets pick'em and don't forget to add them to your group
endmethod
private method SuckThem takes nothing returns nothing
local integer i = 0
//Should work
loop
exitwhen i >= .count //Exitwhen i reached free place
if .grabbeds[i] != 0 then //Is Grabbeds set?
if not .grabbeds[i].Control() then //Returns false when it should be destroyed
set .count = .count - 1 //Set count == last needed place
call .grabbeds[i].destroy() //Destroys light and sfx
set .grabbeds[i] = 0 //Clear the place of the destroyed struct
if i != .count then //If last place == destroyed struct
set .grabbeds[i] = .grabbeds[.count] //Set cleared place == last struct
set .grabbeds[.count] = 0 //Clear that place
endif
endif
endif
set i = i + 1 //Next
endloop
//Jep I remove them of the group when the knockback is done
debug call BJDebugMsg( "Count: " + I2S( .count ) )
endmethod
method InnerControl takes nothing returns nothing
set TEMPINT = GetUnitAbilityLevel( .buffed, BUFF_ID ) //Level wird gesetzt
if .end then //DIE DIE DIE DIE
call .destroy()
return
endif
if not .buffon then //Buff applayed?
if TEMPINT > 0 then
set .buffon = true //Jep
else
set .thereissomethingwrong = .thereissomethingwrong - TIMER_INTERVAL
if .thereissomethingwrong <= 0.00 then
debug call BJDebugMsg( "CRITICAL ERROR, BUFF IS NOT APPLIED" )
set .end = true
call .destroy()
return
endif
endif
else
//All Actions
if TEMPINT <= 0 then //Buff away == destroy
set .end = true
call .destroy()
return
endif
//Time's up
set .duration = .duration - TIMER_INTERVAL
if .duration <= 0.00 then
set .end = true
call .destroy()
return
endif
//Picking
set .pickinterval = .pickinterval - PICK_INTERVAL
if .pickinterval <= 0.00 and .end == false then
set .pickinterval = PICK_INTERVAL
call .PickThem()
endif
//Sucking
if .end == false then
call .SuckThem()
endif
endif
endmethod
method onDestroy takes nothing returns nothing
local integer i = 0
call ReleaseTimer( .time )
debug call BJDebugMsg( "Endcount: " + I2S( .count ) )
loop
exitwhen i == .count //Go through and destroy them
if .grabbeds[i] != 0 then //If that struct exists
debug call BJDebugMsg( I2S( i ) )
call .grabbeds[i].End() //Destroy it in a special way
endif
set i = i + 1
endloop
call GroupClear( .mytargets )
call DestroyGroup( .mytargets )
set .mytargets = null
endmethod
static method create takes unit caster, unit target returns thistype
local thistype this = thistype.allocate()
set .buffed = target
set .caster = caster
set .time = NewTimer()
set .level = GetUnitAbilityLevel( caster, SPELL_ID )
set .duration = DURATION[.level]
call SetTimerData( .time, this )
call TimerStart( .time, TIMER_INTERVAL, true, function OuterControl )
return this
endmethod
endstruct
private function IsSpell takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call Arcania.create( GetTriggerUnit(), GetSpellTargetUnit() )
endif
return false
endfunction
private function HasBuff takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID and GetUnitAbilityLevel( GetSpellTargetUnit(), BUFF_ID ) >= 1 then
call IssueImmediateOrder( GetTriggerUnit(), "stop" )
call DisplayTextToPlayer( GetOwningPlayer( GetTriggerUnit() ), 0.5, -0.50, "|cffffcc00Target has buff already|r" )
endif
return false
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
local trigger t2 = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( t2, EVENT_PLAYER_UNIT_SPELL_CHANNEL )
call TriggerAddCondition( t2, Condition( function HasBuff ) )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( t, Condition( function IsSpell ) )
set TEMPVEC = vector.create( 0.00, 0.00, 0.00 )
call SET_DURATION()
call SET_AOE()
call SET_DAMAGE_COLLISION()
call SET_DAMAGE()
call SET_MANALOSE_COLLISION()
call SET_MANA()
endfunction
endscope