Moderator
M
Moderator
Water Ball v1.11 | Reviewed by Maker | 29th Sep 2013 | ||||
APPROVED | ||||
|
07:45, 14th Apr 2010 TriggerHappy: Not very original, but it's approvable.
|
// Spell : Water Ball
// by : Dark_Axl
// For the Spell Contest #1 at BlizzVault.com
// Library Required: SimError, xebasic, xepreload, TimerUtils, GroupUtils
// Optional Library: SpellEffectEvent
// Model Required : dummy.mdx
// Credits Goes to : Vexorian, for the SimError, xe, TimerUtils and dummy.mdx
// Rising_Dusk, for the GroupUtils
//
// Spell Description:
// Target an area, then launches a series of missile into random point in target area, each missile deals damage to an area, and
// has a chance to explode, dealing more damage. The spell ends when the caster dies or the duration has ended.
scope WaterBall initializer init
globals
private constant integer ABIL_ID = 'A000'
// Define the spell's rawcode ID.
private constant integer DUMMY_ID = XE_DUMMY_UNITID
// Define the dummy unit's rawcode ID.
private constant integer FLY_ID = XE_HEIGHT_ENABLER
// Define the Crow Form's rawcode ID.
private constant string ORDER = "channel"
// Define the orderstring of the spell.
private constant real PERIOD_T = 0.03125
// Define interval period of timer. Higher value will make missile look sloppy when move, Lower value might cause lag.
private constant boolean CHANNEL = true
// Define if its channeling or not, if set to true, the caster has to channel the ability for the spell to take effect.
// if set to false, it will spawn the missile from the startpoint of cast, not follow.
private constant attacktype ATT_TYPE = ATTACK_TYPE_NORMAL
// Define the attacktype of the damage dealt
private constant damagetype DMG_TYPE = DAMAGE_TYPE_NORMAL
// Define the damagetype of the damage dealt
private constant integer MAX_MISSILE = 200
// Define the amount of maximum missile per caster per cast at one time, needed for struct member's array size
private constant string MISSILE_SFX = "Abilities\\Weapons\\SeaElementalMissile\\SeaElementalMissile.mdl"
// Define the SFX attached to the missile, put "" if not use.
private constant string MISSILE_ATT = "origin"
// Define the attachment point of the MISSILE_SFX
private constant string SPAWN_SFX = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl"
// Define the SFX played when the missile spawns, put "" if not use.
private constant string HIT_SFX = "Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl"
// Define the SFX played if the missile not explode, put "" if not use.
private constant string EXPLODE_SFX = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
// Define the SFX played if the missile explode, put "" if not use.
private constant boolean ON_PATH = true
// Define if the caster must stand on certain pathable to be able to cast.
private constant pathingtype PATH_TYPE = PATHING_TYPE_FLOATABILITY
// Define the pathing type checked if ON_PATH = true.
// other pathingtype:
//PATHING_TYPE_WALKABILITY
//PATHING_TYPE_ANY
//PATHING_TYPE_AMPHIBIOUSPATHING
//PATHING_TYPE_BLIGHTPATHING
//PATHING_TYPE_BUILDABILITY
//PATHING_TYPE_FLYABILITY
//PATHING_TYPE_PEONHARVESTPATHING
private constant string ERROR_MSG = "Can't cast while not on water"
// Define the error message shown if the ON_PATH check return false.
private player TEMP_PLAYER
private boolexpr ENEMY_FILTER
endglobals
// Defines the delay between each missile spawn.
private constant function Spawn_Delay takes integer level returns real
return 0.1
endfunction
// Defines the duration of the spell. Number of the missiles is Duration / Spawn_Delay
private constant function Duration takes integer level returns real
return 2. + level
endfunction
// Defines the missile's size scale, value 1. = 100%.
private constant function Scale takes integer level returns real
return level*.1 + .9
endfunction
// Defines the radius of possible missile spawn area around caster.
private constant function MisAOE takes integer level returns real
return 150.
endfunction
// Defines the damage dealt by each missile normally.
private constant function HitDamage takes integer level returns real
return level*3.333 + 10
endfunction
// Defines the bonus damage dealt by each missile when explode.
private constant function ExpDamage takes integer level returns real
return level*3.333 + 10
endfunction
// Defines the chance that the missile explode, ranges from 0 to 1, with value 1. = 100%.
private constant function ExpChance takes integer level returns real
return 0.3
endfunction
// Defines the AOE of the spells, should match with the Area of Effect of the spell in Object Editor.
private constant function Area takes integer level returns real
return 200.
endfunction
// Defines the AOE of normal damage when missile hit.
private constant function HitAOE takes integer level returns real
return 200.
endfunction
// Defines the AOE of explosion damage when missile hit.
private constant function ExpAOE takes integer level returns real
return 200.
endfunction
// Defines the missile's fly time. This mean all missiles will reach target point after a certain equal delay
private constant function MisSpeed takes integer level returns real
return 0.5
endfunction
// Defines the minimum flying height of the missile.
private constant function MinHeight takes integer level returns real
return 100.
endfunction
// Defines the maximum flying height of the missile.
private constant function MaxHeight takes integer level returns real
return 400.
endfunction
// Defines the minimum horizontal deviation of the missile while moving.
private constant function MinWidth takes integer level returns real
return 100.
endfunction
// Defines the maximum horizontal deviation of the missile while moving.
private constant function MaxWidth takes integer level returns real
return 200.
endfunction
// use TEMP_PLAYER as reference to the caster's owner, and GetFilterUnit() as the filtered unit.
// currently it targets enemy units, alive, not magic immune and not flying unit.
private function Enemy_Check takes nothing returns boolean
local unit u = GetFilterUnit()
local boolean b = IsUnitEnemy(u,TEMP_PLAYER) and IsUnitType(u,UNIT_TYPE_DEAD) and not IsUnitType(u,UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(u,UNIT_TYPE_FLYING)
set u = null
return b
endfunction
//===============================THE SPELL PART====================================//
//==============================DON'T EDIT BELOW ==================================//
// this function is used to determine the parabolic movement for both xy and z plane
private constant function GetParabolaZ takes real x, real d, real h returns real
return 4 * h * x * (d - x) / (d * d)
endfunction
// this struct is used both as missile struct and cast struct
private struct Data
unit cast
player p
timer t
group g
real del
real spa
real minh
real maxh
real mind
real maxd
real r
real scale
real msdelay
real dmgA
real dmgB
real chance
real aoeA
real aoeB
unit array missile[MAX_MISSILE]
effect array sfx[MAX_MISSILE]
real array sin[MAX_MISSILE]
real array cos[MAX_MISSILE]
real array sin_c[MAX_MISSILE]
real array cos_c[MAX_MISSILE]
real array speed[MAX_MISSILE]
real array x1[MAX_MISSILE]
real array y1[MAX_MISSILE]
real array x2[MAX_MISSILE]
real array y2[MAX_MISSILE]
real array dis[MAX_MISSILE]
real array dur[MAX_MISSILE]
real array h[MAX_MISSILE]
real array d[MAX_MISSILE]
integer index
endstruct
// this part move the missile, and dealt the damage in the end.
private function move_t takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data data = GetTimerData(t)
local unit v
local real x2
local real y2
local real d
local real h
local real x
local integer i = 1
loop
exitwhen i > data.index
//substract "speed" from "remaining distance"
set data.dur[i] = data.dur[i] - data.speed[i]
set x2 = data.x2[i] + data.dur[i]*data.cos[i]
set y2 = data.y2[i] + data.dur[i]*data.sin[i]
set x = SquareRoot((y2-data.y1[i])*(y2-data.y1[i])+(x2-data.x1[i])*(x2-data.x1[i]))
//function GetParabolaZ is used to calculate curve both in xy and z plane.
set d = GetParabolaZ(x,data.dis[i],data.d[i])
set h = GetParabolaZ(x,data.dis[i],data.h[i])
set x2 = x2 + d*data.cos_c[i]
set y2 = y2 + d*data.sin_c[i]
call SetUnitX(data.missile[i],x2)
call SetUnitY(data.missile[i],y2)
call SetUnitFlyHeight(data.missile[i],h,0)
//if .dur[i] <= 0, means target is reached, deals damage and reindexing.
if data.dur[i] <= 0 then
// this part deals hit damage
set TEMP_PLAYER = data.p
call GroupClear(data.g)
call GroupEnumUnitsInRange(data.g,x2,y2,data.aoeA,ENEMY_FILTER)
loop
set v = FirstOfGroup(data.g)
exitwhen v == null
call GroupRemoveUnit(data.g,v)
call UnitDamageTarget(data.cast,v,data.dmgA,false,true,ATT_TYPE,DMG_TYPE,null)
endloop
call DestroyEffect(AddSpecialEffect(HIT_SFX,x2,y2))
// this part check for explosion chance and damage
if GetRandomReal(0,1) <= data.chance then
call GroupClear(data.g)
set TEMP_PLAYER = data.p
call GroupEnumUnitsInRange(data.g,x2,y2,data.aoeB,ENEMY_FILTER)
loop
set v = FirstOfGroup(data.g)
exitwhen v == null
call GroupRemoveUnit(data.g,v)
call UnitDamageTarget(data.cast,v,data.dmgB,false,true,ATT_TYPE,DMG_TYPE,null)
endloop
call DestroyEffect(AddSpecialEffect(EXPLODE_SFX,x2,y2))
endif
endif
set i = i + 1
endloop
// reindexing
set i = 1
loop
exitwhen i > data.index
if data.dur[i] <= 0 then
call DestroyEffect(data.sfx[i])
call KillUnit(data.missile[i])
call RemoveUnit(data.missile[i])
set data.missile[i] = data.missile[data.index]
set data.dur[i] = data.dur[data.index]
set data.sfx[i] = data.sfx[data.index]
set data.sin[i] = data.sin[data.index]
set data.cos[i] = data.cos[data.index]
set data.sin_c[i] = data.sin_c[data.index]
set data.cos_c[i] = data.cos_c[data.index]
set data.x1[i] = data.x1[data.index]
set data.y1[i] = data.y1[data.index]
set data.x2[i] = data.x2[data.index]
set data.y2[i] = data.y2[data.index]
set data.dis[i] = data.dis[data.index]
set data.h[i] = data.h[data.index]
set data.d[i] = data.d[data.index]
set data.speed[i] = data.speed[data.index]
set data.sfx[data.index] = null
set data.missile[data.index] = null
set data.index = data.index - 1
endif
set i = i + 1
endloop
//if index ever reaches zero, means spells end, cleanup.
if data.index <= 0 then
call ReleaseTimer(t)
call ReleaseGroup(data.g)
set data.cast = null
set data.t = null
set data.p = null
set data.g = null
call data.destroy()
endif
set t = null
endfunction
// this part checks for the channel part, and spawn the missile.
private function effect_t takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data data = GetTimerData(t)
local integer side
local real dis
local real ang
// this part check if the caster is still alive AND channeling the spell, if not, spell effect ends.
set data.dur[0] = data.dur[0] - data.del
if data.dur[0] < 0 or IsUnitType(data.cast,UNIT_TYPE_DEAD) or (GetUnitCurrentOrder(data.cast) != OrderId(ORDER) and CHANNEL) then
call ReleaseTimer(t)
set t = null
return
endif
//this part setup the missile trajectory
set data.index = data.index + 1
set dis = GetRandomReal(0,data.spa)
set ang = GetRandomReal(0,360)
set data.x1[data.index] = data.x1[0] + Cos(ang*0.0174533)*dis
set data.y1[data.index] = data.y1[0] + Sin(ang*0.0174533)*dis
set dis = GetRandomReal(0,data.r)
set ang = GetRandomReal(0,360)
set data.x2[data.index] = data.x2[0] + Cos(ang*0.0174533)*dis
set data.y2[data.index] = data.y2[0] + Sin(ang*0.0174533)*dis
set ang = Atan2((data.y2[data.index]-data.y1[data.index]),(data.x2[data.index]-data.x1[data.index]))
//.dis is used for parabola calculation, while .dur is used to store instance duration in distance before impact
set data.dis[data.index] = SquareRoot((data.y2[data.index]-data.y1[data.index])*(data.y2[data.index]-data.y1[data.index])+(data.x2[data.index]-data.x1[data.index])*(data.x2[data.index]-data.x1[data.index]))
set data.dur[data.index] = data.dis[data.index]
set data.h[data.index] = GetRandomReal(data.minh,data.maxh)
set data.d[data.index] = GetRandomReal(data.mind,data.maxd)
set data.speed[data.index]= data.dis[data.index]*PERIOD_T/data.msdelay
//this determine whether the missile curve left or right side
set side = GetRandomInt(0,1)
if side == 0 then
set side = -1
endif
set data.sin[data.index] = Sin(ang+bj_PI)
set data.cos[data.index] = Cos(ang+bj_PI)
set data.sin_c[data.index] = Sin(ang+(side*0.5*bj_PI))
set data.cos_c[data.index] = Cos(ang+(side*0.5*bj_PI))
call DestroyEffect(AddSpecialEffect(SPAWN_SFX,data.x1[data.index],data.y1[data.index]))
//preparing the missile(s)
set data.missile[data.index] = CreateUnit(data.p,DUMMY_ID,data.x1[data.index],data.y1[data.index],ang)
set data.sfx[data.index] = AddSpecialEffectTarget(MISSILE_SFX,data.missile[data.index],MISSILE_ATT)
call SetUnitScale(data.missile[data.index],data.scale,0,0)
call UnitAddAbility(data.missile[data.index],FLY_ID)
call UnitRemoveAbility(data.missile[data.index],FLY_ID)
// to make sure the missile looks launched from ground/water or z = 0
call SetUnitFlyHeight(data.missile[data.index],0,0)
//start missile timer if there is missile ready to run
if data.index == 1 then
call TimerStart(data.t,PERIOD_T,true,function move_t)
endif
set t = null
endfunction
// this part setup the variable and constant
private function postcast_t takes nothing returns nothing
local unit u = GetTriggerUnit()
local timer t = NewTimer()
local integer lv = GetUnitAbilityLevel(u,ABIL_ID)
local Data data = Data.create()
//this part setup all parameter used in spell based on function provided at top
set data.cast = u
set data.p = GetOwningPlayer(u)
set data.scale = Scale(lv)
set data.del = Spawn_Delay(lv)
set data.dur[0] = Duration(lv)
set data.spa = MisAOE(lv)
set data.x1[0] = GetUnitX(u)
set data.y1[0] = GetUnitY(u)
set data.x2[0] = GetSpellTargetX()
set data.y2[0] = GetSpellTargetY()
set data.minh = MinHeight(lv)
set data.maxh = MaxHeight(lv)
set data.mind = MinWidth(lv)
set data.maxd = MaxWidth(lv)
set data.msdelay= MisSpeed(lv)
set data.r = Area(lv)
set data.index = 0
set data.t = NewTimer()
set data.g = NewGroup()
set data.dmgA = HitDamage(lv)
set data.dmgB = ExpDamage(lv)
set data.chance = ExpChance(lv)
set data.aoeA = HitAOE(lv)
set data.aoeB = ExpAOE(lv)
//start the timer for channeling part
call SetTimerData(t,data)
call SetTimerData(data.t,data)
call TimerStart(t,data.del,true,function effect_t)
set t = null
set u = null
endfunction
// this part does the pathingtype check and cancel the spell respectively, only if ON_PATH == true.
static if ON_PATH then
private function precast_t takes nothing returns boolean
local unit u = GetTriggerUnit()
if GetSpellAbilityId() == ABIL_ID and IsTerrainPathable(GetUnitX(u),GetUnitY(u),PATH_TYPE) then
call PauseUnit(u,true)
call IssueImmediateOrder(u,"stop")
call PauseUnit(u,false)
call SimError(GetOwningPlayer(u),ERROR_MSG)
endif
set u = null
return false
endfunction
endif
//this is the condition of the spell cast
private function Abil_Check takes nothing returns boolean
return GetSpellAbilityId() == ABIL_ID
endfunction
//this is the initialization of the spell trigger
private function init takes nothing returns nothing
local trigger trig
//this part setup for the pre-cast check
static if ON_PATH then
set trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_CAST )
call TriggerAddCondition( trig , Condition( function precast_t ) )
endif
//this part setup for the spell effect itself
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(ABIL_ID, function postcast_t)
else
set trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( trig, Condition( function Abil_Check ) )
call TriggerAddAction( trig, function postcast_t )
endif
//this is setup for boolexpr used in group enumeration for damage effect
set ENEMY_FILTER = Filter(function Enemy_Check)
//preload ability and effect
call XE_PreloadAbility(ABIL_ID)
call Preload(MISSILE_SFX)
call Preload(HIT_SFX)
call Preload(EXPLODE_SFX)
call Preload(SPAWN_SFX)
set trig = null
endfunction
endscope
Spell InformationFully compatible with v1.24, MUI, configurable.
Requires JNGP
Uses:
- SimError
- xebasic
- xepreload
- TimerUtils
- GroupUtils
Supports usage of Bribe's SpellEffectEvent library
Credits goes to:
- Vexorian, for the SimError, xe, TimerUtils and dummy.mdx.
- Rising_Dusk, for the GroupUtils