library PolySlash initializer Init requires TNTK, GroupUtils
globals
private constant integer SID = 'pLsL'
//The rawcode of the ability
private constant real TRAVEL_SPEED = 8.43
//The movement speed of the "mirror images"
private constant real MOVE_ANIMATION_TIME = 1.133
//The duration of the animation which a mirror image performes
//while it is moving around
private constant real MOVE_ANIMATION_TIME_MOD = 1.5
//The animation de- or acceleration. Having a value of 1.0 means
//that the animation is played with regualr speed, a value of 2.0
//means the animation is played twice as fast as normal
private constant boolean FORCE_REPICK = true
//When the mirror images are reuniting back into the caster, it
//will be automatically picked for the controlling player
//if set to true
private constant string MOVE_ANIMATION_NAME = "Attack Slam"
//The name of the animation a mirror image performs while moving around
private constant string CASTER_SFX = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl"
//The path of a special effect which is being attached to the mirror images
private constant string TARGET_SFX = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"
//The path of a special effect which is being displayed when a target unit gets hit
private constant string CASTER_SFX_ATTACHMENT = "weapon"
//Where should the special effect be attached to the mirror image
private constant string TARGET_SFX_ATTACHMENT = "origin"
//Where should the special effect be attached to the target
//Selfexplaining damage options...
private constant attacktype ATTACK = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WEAPON = null
endglobals
//The amount of damage depending of the ability level being inflicted to a target when hit
private constant function GetDamage takes real level returns real
return 40 * level + 30
endfunction
//The amount of mirror images spawned per level
private constant function GetCopies takes integer level returns integer
return 1 * level + 2
endfunction
//The radius of the Area of Effect
private constant function GetAoERange takes real level returns real
return 100 * level + 150
endfunction
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
//Spellcode begins here. PLEASE DO NOT TOUCH ! Thank you !
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
//Forward struct declaration
private keyword polyslash_data
globals
private filterfunc ENUM_FILTER //Used to acquire valid targets
private player TMP_PLAYER //Used as paramter substitue
private group TMP_GROUP //Used filter invalid units off the selection
private constant real TM_RATIO = MOVE_ANIMATION_TIME / MOVE_ANIMATION_TIME_MOD
//Ratio between speed modifier an animation time
private integer TMP_COUNTER //Used to count the number of targets
endglobals
//Customized knockback for this spell
private struct polyslash_move extends TNTKnock
effect sfx
real anim_time = .0
method operator psd takes nothing returns polyslash_data
return polyslash_data(.data)
endmethod
method operator psd= takes polyslash_data psd returns nothing
set .data = integer(psd)
endmethod
//Responsible for movement and animations
method onKnock takes nothing returns nothing
call SetUnitX( .knocker, .x )
call SetUnitY( .knocker, .y )
call SetUnitFacing( .knocker, .radiants*bj_RADTODEG )
if anim_time <= .0 then
call SetUnitAnimation( .knocker, MOVE_ANIMATION_NAME )
set .anim_time = TM_RATIO
else
set .anim_time = .anim_time - .runtime
endif
endmethod
//Manages the target selection and ends a knockback
method onBreak takes nothing returns boolean
//If true a mirror image reached its target unit
if super.onBreak() then
//If the caster was the target this instance can be destroyed
if .target == .psd.caster then
return true
endif
//Damage the current target and play the effect
call UnitDamageTarget( .psd.caster, .target, .psd.damage, false, false, ATTACK, DAMAGE, WEAPON )
call DestroyEffect( AddSpecialEffectTarget( TARGET_SFX, .target, TARGET_SFX_ATTACHMENT ))
//Leave the old target behind ang acquire a new one if possible
if .psd.target_count > 0 then
call .psd.UpdateGroupState()
set .target = FirstOfGroup(.psd.targets)
call GroupRemoveUnit(.psd.targets, .target )
set .psd.target_count = .psd.target_count -1
else
//If all targets recieved their damage return to the caster
set .target = .psd.caster
endif
endif
//The mirror image is still on its way
return false
endmethod
//Customized knockback dstructor method
method onDestroy takes nothing returns nothing
//Clean up stuff
call RemoveUnit(.knocker)
call DestroyEffect(.sfx)
call SetUnitVertexColor( .psd.caster, 255, 255, 255, 255 / .psd.copy_count )
//If this is the first unit which should return to the caster then we must
//unhide the caster again
if .psd.target_count == 0 then
call ShowUnit( .psd.caster, true )
static if FORCE_REPICK then
if GetLocalPlayer() == GetOwningPlayer(.psd.caster) then
call SelectUnit( .psd.caster, true )
endif
endif
endif
//If there are no mirror images left we can destroy the remaining instances
set .psd.copy_count = .psd.copy_count -1
if .psd.copy_count == 0 then
call .psd.destroy()
endif
endmethod
endstruct
//Sorta managing struct
private struct polyslash_data
unit caster
group targets
integer target_count
integer copy_count
real damage
method onDestroy takes nothing returns nothing
call ReleaseGroup(.targets)
//call ShowUnit(.caster, true)
endmethod
//Loops through the selection and removes invalid units
static method UpdateGroupState_enum takes nothing returns nothing
local unit u = GetEnumUnit()
if (u == null) or (GetWidgetLife(u) < 0.405) then
call GroupRemoveUnit( TMP_GROUP, u )
else
set TMP_COUNTER = TMP_COUNTER +1
endif
set u = null
endmethod
//Starts the check loop
method UpdateGroupState takes nothing returns nothing
set TMP_COUNTER = 0
set TMP_GROUP = .targets
call ForGroup(.targets, function thistype.UpdateGroupState_enum )
set .target_count = TMP_COUNTER
endmethod
//Creates and configures a mirror image
method setup_copy takes real x, real y, unit t returns nothing
local unit u = CreateUnit( Player(13), GetUnitTypeId(.caster), x, y, 0. )
local polyslash_move psm = polyslash_move.create()
//Create the unit and its knockback instance
set psm.psd = this
set psm.sfx = AddSpecialEffectTarget( CASTER_SFX, u, CASTER_SFX_ATTACHMENT )
call psm.StartHomingTimed(u, t, x, y, TRAVEL_SPEED )
//Setup the knockback
call UnitAddAbility( u, 'Aloc' )
call SetUnitTimeScale( u, MOVE_ANIMATION_TIME_MOD )
call SetUnitColor( u, GetPlayerColor( TMP_PLAYER ))
call SetUnitVertexColor( u, 255, 255, 255, 50 + 255 / .copy_count )
call UnitShareVision( u, TMP_PLAYER , true )
//Setup the unit
endmethod
//Setup for the spell
static method create takes unit ca, real x, real y returns thistype
local thistype this = thistype.allocate()
local unit t
local integer i = GetUnitAbilityLevel(ca, SID)
local real cx = GetUnitX(ca)
local real cy = GetUnitY(ca)
set .caster = ca
set .targets = NewGroup()
set .damage = GetDamage(i)
set .copy_count = GetCopies(i)
set TMP_PLAYER = GetOwningPlayer(ca)
set TMP_COUNTER = 0
call GroupEnumUnitsInArea( .targets, x, y, GetAoERange(i), ENUM_FILTER )
call GroupRemoveUnit( .targets, .caster )
set .target_count = TMP_COUNTER
//Acquire target selection, level depeing stuff, coords, etc...
call SetUnitX( ca, x )
call SetUnitY( ca, y )
call ShowUnit( ca, false )
//Setup the caster
//In case there is no valid target we will just send a mirror image to the
//target location
if .target_count == 0 then
set .copy_count = 1
call .setup_copy( cx, cy, ca )
else
//In case there would be more mirror images than targets
//we will just leave out the obsolete ones
if .copy_count > .target_count then
set .copy_count = .target_count
endif
//Creates the required mirror images
set i = 0
loop
exitwhen i == .copy_count
call .UpdateGroupState()
set t = FirstOfGroup( .targets )
call GroupRemoveUnit( .targets, t)
call .setup_copy( cx, cy, t )
set i = i+1
endloop
call .UpdateGroupState()
endif
set t = null
return this
endmethod
endstruct
//Filter which specifies if a unit is a target or not
private function TargetFilter takes nothing returns boolean
local unit u = GetFilterUnit()
if IsUnitEnemy(u,TMP_PLAYER) and not ( /*
*/ GetWidgetLife(u) < 0.405 or /*
*/ IsUnitType(u, UNIT_TYPE_STRUCTURE) or /*
*/ IsUnitType(u, UNIT_TYPE_FLYING) or /*
*/ IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) or /*
*/ IsUnitType(u, UNIT_TYPE_ANCIENT) or /*
*/ IsUnitInvisible(u, TMP_PLAYER) ) /*
*/then
set TMP_COUNTER = TMP_COUNTER +1
set u = null
return true
endif
set u = null
return true
endfunction
//Launches the spell
private function onCast takes nothing returns boolean
if GetSpellAbilityId() == SID then
call polyslash_data.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY() )
endif
return false
endfunction
//Initializer....
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
set ENUM_FILTER = Filter( function TargetFilter )
call Preload(CASTER_SFX)
call Preload(TARGET_SFX)
call PreloadStart()
call TriggerAddCondition( trig, Filter( function onCast ) )
call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
endfunction
endlibrary