Moderator
M
Moderator
09:46, 4th Feb 2010
TriggerHappy:
Coding looks good, as well as the effect.
TriggerHappy:
Coding looks good, as well as the effect.
Water Spirits
Unleashes water spirits that rotate around the target ally, and attack nearby enemy units, dealing damage, then return to the target ally, healing for 50% damage that dealt by them.
library WaterSpirits requires TimerUtils, GetNearest, SimError, xefx, xedamage optional BoundSentinel
//-----------------------------------------------------------------------------------------------
// Water Spirits v1.01 by scorpion182
// requires:
// - TimerUtils, SimError, xe by Vexorian
// - GroupUtils (required by GetNearest) by Rising_Dusk
// - GetNearest by grim001
//
//
//
//
//-----------------------------------------------------------------------------------------------
//----------------CALIBRATION SECTION------------------------------------------------------------
globals
private keyword data //don't touch this
private constant integer SPELL_ID='A000' //ability rawcode
private constant integer MAX_MISSILE=50 //maximum missiles
private constant string ORB_PATH="Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl" //orb fx path
private constant string DAMAGE_FX="" //on-damage effect
private constant string DAMAGE_ATTCH="origin" //on-death effect attachment point
private constant string HEAL_FX="" //0n-heal effect
private constant string HEAL_ATTCH="origin" //on-heal effect attachment point
private constant real SCALE=1.2 //orb scale
private constant real ANGLE_SPEED=.15 //orb angle speed
private constant real HEIGHT=100. //orb height
private constant real MIN_DIST=80. //minimum distance to absorb, make sure the value higher than 80, if not it won't work correctly
private constant real MAX_DIST=1000. //max distance to attack the target
private constant string ERROR_MSG="Another instance is running for this unit." //error message
endglobals
private constant function GetDamage takes integer lvl returns real
return 15.+lvl*0 //do damage amount of hp each orb
endfunction
private constant function GetHealFactor takes integer lvl returns real
return 0.50+lvl*0 //heals 50% of the damage dealt by it
endfunction
private constant function GetMissileCount takes integer lvl returns integer
return 3+lvl*0 //orb count
endfunction
private constant function GetDuration takes integer lvl returns real
return 60.+lvl*0 //spell duration
endfunction
private constant function GetDistance takes integer lvl returns real
return 150.+lvl*0 //orb's rotation distance
endfunction
private constant function GetAoE takes integer lvl returns real
return 500.+lvl*0 //aoe each orb to find a target
endfunction
private constant function GetSpeed takes integer lvl returns real
return 800.*XE_ANIMATION_PERIOD+lvl*0. //orb missile speed
endfunction
private function DamageOptions takes xedamage spellDamage returns nothing
set spellDamage.dtype=DAMAGE_TYPE_UNIVERSAL
set spellDamage.atype=ATTACK_TYPE_NORMAL
set spellDamage.exception=UNIT_TYPE_STRUCTURE
set spellDamage.visibleOnly=true
set spellDamage.damageSelf=true
set spellDamage.damageAllies=true //heals for amount of the damage dealt by orb
set spellDamage.allyfactor=-1.0 //to the target ally.
endfunction
//filter the targets, should match the value from DamageOptions
private function IsValidTarget takes unit u, data s returns boolean
return not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) != 0 and IsUnitType(u,UNIT_TYPE_STRUCTURE)==false and IsUnitEnemy(u,GetOwningPlayer(s.caster))==true and IsUnitVisible(u,GetOwningPlayer(s.caster))==true
endfunction
//------------------END OF CALIBRATION-----------------------------------------------------------
globals
private constant real A=2*bj_PI
private xedamage xed
private group casters=CreateGroup()
endglobals
private struct data
unit caster //target ally
unit owner //caster
unit array target[MAX_MISSILE] //target enemies
xefx array fx[MAX_MISSILE] //effect
boolean array IsAttack[MAX_MISSILE] //attacking status
boolean array IsHome[MAX_MISSILE] //homing status
boolean array IsFinish[MAX_MISSILE] //finish status
real array angle[MAX_MISSILE] //current angle
real array tx[MAX_MISSILE] //current x track
real array ty[MAX_MISSILE] //current y track
real duration //spell duration
boolean array heal[MAX_MISSILE] //attacking succeed status
timer t
integer total //orbs total
integer lvl //ability level
private static thistype temp
static method create takes unit c, unit o returns data
local thistype this=data.allocate()
local integer i=0
local real x=GetUnitX(c)
local real y=GetUnitY(c)
local real a
set .lvl=GetUnitAbilityLevel(c,SPELL_ID)
set .total=GetMissileCount(.lvl)
set .duration=GetDuration(.lvl)
set .caster=c
set .owner=o
set .t=NewTimer()
loop
exitwhen i==.total
set a=i*A/.total
set .fx[i]=xefx.create(x,y,a)
set .fx[i].fxpath=ORB_PATH
set .fx[i].scale=SCALE
set .fx[i].z=HEIGHT
set .angle[i]=a
set i=i+1
endloop
call GroupAddUnit(casters,c)
return this
endmethod
static method VictimFilter takes nothing returns boolean
return IsValidTarget(GetFilterUnit(),temp)
endmethod
static method onLoop takes nothing returns nothing
local thistype this=data(GetTimerData(GetExpiredTimer()))
local real x=GetUnitX(.caster)
local real y=GetUnitY(.caster)
local integer i=0
local real x1
local real y1
local real x2
local real y2
local real ang
local real newx
local real newy
local real dx
local real dy
local real dist
if .duration>0. and not IsUnitType(.caster, UNIT_TYPE_DEAD) then
set .duration=.duration-XE_ANIMATION_PERIOD
loop
exitwhen i==.total
set .angle[i]=.angle[i]+ANGLE_SPEED
set tx[i]=x+GetDistance(.lvl)*Cos(.angle[i])
set ty[i]=y+GetDistance(.lvl)*Sin(.angle[i])
//if not attacking, order to rotate
if not .IsAttack[i] then
set .fx[i].x=tx[i]
set .fx[i].y=ty[i]
set temp=this
set .target[i]=GetNearestUnit(.fx[i].x,fx[i].y,GetAoE(.lvl),Condition(function data.VictimFilter))
if .target[i]!=null then
set .IsAttack[i]=true
endif
else
//if find a target, order to attack
if not .IsHome[i] then
set x1=.fx[i].x
set y1=.fx[i].y
set x2=GetUnitX(.target[i])
set y2=GetUnitY(.target[i])
set dx=x2-x1
set dy=y2-y1
set ang=Atan2(dy,dx)
set newx=x1+GetSpeed(.lvl)*Cos(ang)
set newy=y1+GetSpeed(.lvl)*Sin(ang)
set dist=SquareRoot(dx*dx+dy*dy)
if (dist>MIN_DIST) then
set .fx[i].x=newx
set .fx[i].y=newy
set .fx[i].xyangle=ang
//if the target is too far away from the caster or dead, order it to come back
if dist>MAX_DIST or IsUnitType(.target[i], UNIT_TYPE_DEAD) then
set .IsHome[i]=true
set .target[i]=null
endif
else
//damage the target
if not IsUnitType(.target[i], UNIT_TYPE_DEAD) then
call xed.useSpecialEffect(DAMAGE_FX,DAMAGE_ATTCH)
call xed.damageTarget(.owner,.target[i],GetDamage(.lvl))
set .heal[i]=true
endif
set .IsHome[i]=true
set .target[i]=null
endif
else
//back to caster
if not .IsFinish[i] then
set x1=.fx[i].x
set y1=.fx[i].y
set x2=GetUnitX(.caster)
set y2=GetUnitY(.caster)
set dx=x2-x1
set dy=y2-y1
set ang=Atan2(dy,dx)
set newx=x1+GetSpeed(.lvl)*Cos(ang)
set newy=y1+GetSpeed(.lvl)*Sin(ang)
set dist=SquareRoot(dx*dx+dy*dy)
if (dist>MIN_DIST) then
set .fx[i].x=newx
set .fx[i].y=newy
set .fx[i].xyangle=ang
else
//heal the caster
if not IsUnitType(.caster, UNIT_TYPE_DEAD) and .heal[i] then
call xed.useSpecialEffect(HEAL_FX,HEAL_ATTCH)
call xed.damageTarget(.owner,.caster,GetDamage(.lvl)*GetHealFactor(.lvl))
endif
set .IsFinish[i]=true
endif
else
//back to its rotation point
set x1=.fx[i].x
set y1=.fx[i].y
set x2=tx[i]
set y2=ty[i]
set dx=x2-x1
set dy=y2-y1
set ang=Atan2(dy,dx)
set newx=x1+GetSpeed(.lvl)*Cos(ang)
set newy=y1+GetSpeed(.lvl)*Sin(ang)
set dist=SquareRoot(dx*dx+dy*dy)
if (dist>MIN_DIST) then
set .fx[i].x=newx
set .fx[i].y=newy
set .fx[i].xyangle=ang
else
set .IsFinish[i]=false
set .IsHome[i]=false
set .IsAttack[i]=false
set .heal[i]=false
set .fx[i].x=tx[i]
set .fx[i].y=ty[i]
endif
endif
endif
endif
set i=i+1
endloop
else
call .destroy()
endif
endmethod
private method onDestroy takes nothing returns nothing
local integer i=0
call ReleaseTimer(.t)
call GroupRemoveUnit(casters,.caster)
loop
exitwhen i==.total
call .fx[i].destroy()
set i=i+1
endloop
endmethod
static method SpellEffect takes nothing returns boolean
local thistype this
if (GetSpellAbilityId()==SPELL_ID) then
set this=data.create(GetSpellTargetUnit(),GetTriggerUnit())
call SetTimerData(.t,this)
call TimerStart(.t,XE_ANIMATION_PERIOD,true,function data.onLoop)
endif
return false
endmethod
static method Stop takes nothing returns boolean
local unit c=GetSpellTargetUnit()
local unit u=GetTriggerUnit()
if GetSpellAbilityId()==SPELL_ID and IsUnitInGroup(c,casters) then
call SimError(GetOwningPlayer(u),ERROR_MSG)
call PauseUnit(u,true)
call IssueImmediateOrder(u,"stop")
call PauseUnit(u,false)
endif
set c=null
set u=null
return false
endmethod
static method onInit takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_CAST)
call TriggerAddCondition(t,Condition(function data.SpellEffect))
set t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_CHANNEL)
call TriggerAddCondition(t,Condition(function data.Stop))
//init xedamage
set xed=xedamage.create()
call DamageOptions(xed)
endmethod
endstruct
endlibrary