scope Orbital initializer Init
//*********************************************************************
//* Orbital v1.02
//* by: emjlr3
//* ----------
//*
//* Requirements:
//* *An instant cast ability, like the "Orbital" ability found in this map
//* *A dummy unit, like the "Caster Dummy" unit found in this map
//* *UnitAlive native, found in the custom script section of this map
//* *A copy of this trigger
//* *Although not required, the BoundSentinel library is recommended
//* [url]http://www.wc3c.net/showthread.php?t=102576[/url]
//*
//* (requires vJass) More abilities at [url]http://www.thehelper.net/forums[/url]
//*
//* Credits:
//* *DotA for spell architecture inspiration
//*
//* Important:
//* *SFX strings can be set to "" for no effect.
//* *Instance limit of 8190/OMAX.
//* *One instance per unit. This is, for the most part, rendered
//* safe through clever ability availability manipulation.
//*
//********************************************************************
//==CONFIGURATION==\\
globals
private constant attacktype ATTACK = ATTACK_TYPE_MAGIC // Collision damage attacktype
private constant boolean CLK = true // Rotate clockwise
private constant boolean PRELOAD = true // Preload effects and TARGET ability, will use DUM as dummy unit
private constant damagetype DAMAGE = DAMAGE_TYPE_MAGIC // Collision damage damagetype
private constant integer ABIL = 'A002' // Orbital ability rawcode
private constant integer DUM = 'n000' // Orbital dummy unit rawcode
private constant integer MAXHANDLES = 8190 // Map's max handle count, don't change if unsure
private constant integer OFFSET = 0x100000 // Map's first handle id, don't change if unsure
private constant integer OMAX = 4 // Max number of orbitals
private constant integer TARGET = 'A004' // Target Orbitals ability rawcode
private constant real SPEED = 18. // Speed at which orbitals follow targets, distance/TIMEOUT
private constant real TIMEOUT = .03 // Periodic timer interval
private constant string MDL = "Abilities\\Spells\\Other\\Volcano\\VolcanoMissile.mdl" // Orbital model
private constant string MDLAP = "origin" // Attachment point for MDL
private constant string SFX = "Abilities\\Spells\\Other\\Volcano\\VolcanoMissile.mdl" // Effect on targets following collision
private constant string SND = "Abilities\\Spells\\Other\\SoulBurn\\SoulBurn1.wav" // Sound to play when orbitals reach max strength
endglobals
private function Damage takes integer level, real currtime, real maxtime returns real
// currtime is current elapsed time of spell, maxtime is the total time required for orbitals to reach maximum power
return level*10+level*20*RMinBJ(currtime/maxtime,1.) // Damage/heal effect on applicable targets
endfunction
private function Distance takes integer level returns real
return 200. // Orbitals rotation distance from caster
endfunction
private function Orbitals takes integer level returns integer
return 4 // Number of orbitals
endfunction
private function Range takes integer level, real currtime, real maxtime returns real
// currtime is current elapsed time of spell, maxtime is the total time required for orbitals to reach maximum power
return 200.+100.*RMinBJ(currtime/maxtime,1.) // Collision effects radius
endfunction
private function ReachMax takes integer level returns real
return 12.*.75 // Time required for orbitals to reach maximum strength
endfunction
private function ScaleIncrease takes integer level, real currtime, real maxtime returns real
// currtime is current elapsed time of spell, maxtime is the total time required for orbitals to reach maximum power
return .5+1.*RMinBJ(currtime/maxtime,1.) // Orbital model scale @ time currtime
endfunction
private function StartScale takes integer level returns real
return .5 // Orbital initial model scale
endfunction
private function Targets takes unit caster, unit target returns boolean
return true // Custom orbital targeting boolean function
endfunction
private function Time takes integer level returns real
return 12. // Max timed life of orbitals
endfunction
private function Update takes integer level returns real
return 360.*TIMEOUT // Rate at which orbitals coords around caster change, angle degrees/TIMEOUT
endfunction
//==NO TOUCHING PAST THIS POINT==\\
private keyword data
globals
private boolexpr BOOL
private integer COUNT = 0
private data array DATA[MAXHANDLES]
private data array DATASTACK
private group EXPLODE = CreateGroup()
private group GROUP = CreateGroup()
private constant real MODSPEED = SPEED*SPEED
private constant integer RMAX = OMAX+1
private sound SOUND
private unit TARG
private data TEMPD
private timer TIMER = CreateTimer()
endglobals
private function Filt takes nothing returns boolean
local data d=TEMPD
local unit TARG=GetFilterUnit()
local real r
local real rr
if UnitAlive(TARG) and Targets(d.u,TARG) then
set r=Damage(d.lvl,d.time,d.maxtime)
if IsUnitEnemy(TARG,d.p) then
call UnitDamageTarget(d.u,TARG,r,false,false,ATTACK,DAMAGE,null)
else
call SetWidgetLife(TARG,GetWidgetLife(TARG)+r)
endif
endif
return false
endfunction
private struct data
unit u
unit target
unit array orbital[RMAX]
effect array sfx[RMAX]
player p
integer lvl
integer stack
integer mode=0
integer array gotem[RMAX]
integer count
real time=0.
real maxtime
real update
real array degree[RMAX]
real reachmax
real dist
real startscale
boolean max=false
method destroy takes nothing returns nothing
local integer i=1
loop
exitwhen i>.count
if .gotem[i]!=1 then
set TEMPD=this
call GroupEnumUnitsInRange(EXPLODE,GetUnitX(.orbital[i]),GetUnitY(.orbital[i]),Range(.lvl,.time,.maxtime),BOOL)
call DestroyEffect(AddSpecialEffect(SFX,GetUnitX(.orbital[i]),GetUnitY(.orbital[i])))
call DestroyEffect(.sfx[i])
call RemoveUnit(.orbital[i])
endif
set i=i+1
endloop
set DATASTACK[.stack]=DATASTACK[COUNT]
set COUNT=COUNT-1
call GroupRemoveUnit(GROUP,.u)
call UnitRemoveAbility(.u,TARGET)
call SetPlayerAbilityAvailable(.p,ABIL,true)
call .deallocate()
endmethod
endstruct
private function Effects takes nothing returns nothing
local data d
local integer i=1
local integer c=1
local real x
local real y
local real r
local boolean b=true
loop
exitwhen i>COUNT
set d=DATASTACK[i]
if d.time>d.maxtime and d.mode==0 then
set d.mode=1
set d.target=d.u
call UnitRemoveAbility(d.u,TARGET)
call SetPlayerAbilityAvailable(d.p,ABIL,true)
elseif d.mode==0 then
set x=GetUnitX(d.u)
set y=GetUnitY(d.u)
set r=ScaleIncrease(d.lvl,d.time,d.reachmax)
loop
exitwhen c>d.count
if UnitAlive(d.orbital[c]) and d.orbital[c]!=null then
set d.degree[c]=d.degree[c]+d.update
static if CLK then
call SetUnitFacing(d.orbital[c],d.degree[c]+90.)
else
call SetUnitFacing(d.orbital[c],d.degree[c]-90.)
endif
call SetUnitX(d.orbital[c],x+d.dist*Cos(d.degree[c]*bj_DEGTORAD))
call SetUnitY(d.orbital[c],y+d.dist*Sin(d.degree[c]*bj_DEGTORAD))
call SetUnitScale(d.orbital[c],r,r,r)
endif
set c=c+1
endloop
if d.time>=d.reachmax and not d.max then
if GetLocalPlayer()==d.p then
call StartSound(SOUND)
set d.max=true
endif
endif
set d.time=d.time+TIMEOUT
elseif d.mode==1 then
loop
exitwhen c>d.count
if UnitAlive(d.target) and d.target!=null then
if d.gotem[c]!=1 then
set x=GetUnitX(d.orbital[c])
set y=GetUnitY(d.orbital[c])
if Pow(GetUnitX(d.target)-x,2)+Pow(GetUnitY(d.target)-y,2)<=MODSPEED then
set TEMPD=d
call GroupEnumUnitsInRange(EXPLODE,x,y,Range(d.lvl,d.time,d.maxtime),BOOL)
call DestroyEffect(AddSpecialEffect(SFX,x,y))
call DestroyEffect(d.sfx[c])
call RemoveUnit(d.orbital[c])
set d.gotem[c]=1
else
set b=false
set r=Atan2(GetUnitY(d.target)-y,GetUnitX(d.target)-x)
call SetUnitFacing(d.orbital[c],r*bj_RADTODEG)
call SetUnitX(d.orbital[c],x+SPEED*Cos(r))
call SetUnitY(d.orbital[c],y+SPEED*Sin(r))
endif
endif
else
call d.destroy()
set i=i-1
exitwhen true
endif
set c=c+1
endloop
if b then
call d.destroy()
set i=i-1
exitwhen true
endif
endif
set i=i+1
endloop
if COUNT==0 then
call PauseTimer(TIMER)
endif
endfunction
private function Actions takes nothing returns nothing
local data d=data.create()
local real x
local real xu
local real y
local real yu
local integer i=1
set d.u=GetTriggerUnit()
set d.p=GetOwningPlayer(d.u)
set d.lvl=GetUnitAbilityLevel(d.u,ABIL)
set d.update=Update(d.lvl)
set d.maxtime=Time(d.lvl)
set d.reachmax=ReachMax(d.lvl)
set d.count=Orbitals(d.lvl)
set d.dist=Distance(d.lvl)
set d.startscale=StartScale(d.lvl)
set COUNT=COUNT+1
set d.stack=COUNT
if COUNT==1 then
call TimerStart(TIMER,TIMEOUT,true,function Effects)
endif
set DATASTACK[COUNT]=d
call SetPlayerAbilityAvailable(d.p,ABIL,false)
call UnitAddAbility(d.u,TARGET)
call SetPlayerAbilityAvailable(d.p,TARGET,true)
set xu=GetUnitX(d.u)
set yu=GetUnitY(d.u)
loop
exitwhen i>d.count
set d.degree[i]=360.*i/I2R(d.count)
set x=xu+d.dist*Cos(bj_DEGTORAD*d.degree[i])
set y=yu+d.dist*Sin(bj_DEGTORAD*d.degree[i])
static if CLK then
set d.orbital[i]=CreateUnit(d.p,CasterId,x,y,d.degree[i]+90.)
else
set d.orbital[i]=CreateUnit(d.p,CasterId,x,y,d.degree[i]-90.)
endif
call SetUnitPathing(d.orbital[i],false)
call SetUnitScale(d.orbital[i],d.startscale,d.startscale,d.startscale)
set d.sfx[i]=AddSpecialEffectTarget(MDL,d.orbital[i],MDLAP)
set d.gotem[i]=0
set i=i+1
endloop
set DATA[GetHandleId(d.u)-OFFSET]=d
call GroupAddUnit(GROUP,d.u)
endfunction
//======================================================================
private function Conditions takes nothing returns boolean
local data d
if GetSpellAbilityId()==ABIL then
call Actions()
elseif GetSpellAbilityId()==TARGET then
set d=DATA[GetHandleId(GetTriggerUnit())-OFFSET]
set d.target=GetSpellTargetUnit()
set d.mode=1
call UnitRemoveAbility(d.u,TARGET)
call SetPlayerAbilityAvailable(d.p,ABIL,true)
elseif IsUnitInGroup(GetTriggerUnit(),GROUP) and GetTriggerEventId()==EVENT_PLAYER_UNIT_DEATH then
set d=DATA[GetHandleId(GetTriggerUnit())-OFFSET]
call d.destroy()
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
local integer i=0
loop
exitwhen i>bj_MAX_PLAYERS
call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_DEATH,null)
set i=i+1
endloop
call TriggerAddCondition(t,Condition(function Conditions))
set BOOL=Condition(function Filt)
set SOUND=CreateSound(SND,false,false,true,10,10,"DefaultEAXON")
call SetSoundDuration(SOUND,1579)
call SetSoundChannel(SOUND,0)
static if PRELOAD then
set TARG=CreateUnit(Player(15),DUM,0.,0.,0.)
call UnitAddAbility(TARG,TARGET)
call UnitApplyTimedLife(TARG,'BTLF',.001)
call Preload(MDL)
call Preload(SFX)
endif
endfunction
endscope