library FrozenOrb initializer Init requires xebasic
//------------------------------------------------------<>
// 'FROZEN ORB'
//
// Submitted by Eccho 2010-02-05 (1.0)
// 2010-02-15 (1.01)
// Changelog can be found at the official submission post
// at
// http://www.hiveworkshop.com/forums/spells-569/frozen-orb-v1-0-a-158026/
//
// Give credits if used!!
//------------------------------------------------------<>
//------------------------------------------------------<>
// 'Native including'
//
// If you have this in your code somewhere else,
// make sure to not double define it.
//------------------------------------------------------<>
native UnitAlive takes unit id returns boolean
//------------------------------------------------------<>
// 'Configuration section'
//
// Change the spell to fit your needs.
//------------------------------------------------------<>
globals
private constant integer ABILITY_ID = 'A000' //The ability triggering the spell
private constant integer FROST_SLOW_ID = 'A001' //The ability containing the frost attack. It the duration isn't altered of the already existing one in wc3, use that id instead.
private constant integer FROST_SLOW_BUFF = 'Bfro' //The buff which is used in the slow attack ability
//Art of the main orb unit
private constant string ORB_ART = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
private constant integer ORB_SPEED = 800 //Max travel speed
private constant integer ORB_RANGE = 900 //Max travel distance
private constant integer ORB_RADIUS = 32 //The radius the orb has. It is required as a polar projection offset when the orb explodes in the end
private constant integer ORB_HEIGHT = 48 //The z-height from the ground the orb will travel
private constant real ORB_SCALE = 1.0 //The size/scaling of the orb (I believe the current model have some issues here, but it works with other models)
//Art of the released bolts. The bolts released when the orb explodes uses this art too.
private constant string MISSILE_ART = "Abilities\\Weapons\\LichMissile\\LichMissile.mdl"
private constant integer MISSILE_SPEED = 450 //...
private constant integer MISSILE_RANGE = 400 //...
private constant integer MISSILE_RADIUS = 32 //Bolt radius. It is used to check the collision of which enemies the bolts will hit and damage.
private constant integer MISSILE_HEIGHT = 48 //...
private constant real MISSILE_SCALE = 0.5 //...
private constant real MISSILE_RAD_OFFSET = 3*bj_PI/5 //Each bolt is released with a certain angle offset (in radians), and is defined by this constant. Each bolt is added this constant + the previous angle.
private constant integer MISSILE_AMOUNT_MAX = 100 //Keep this constant at a secure amount. The spell could go very wrong if this value is less than the maximum amount of bolts released. 100 is used by default.
private constant integer ORB_EXPLODE_AMOUNT = 12 //Amount of bolts released when the orb explodes
private constant integer ORB_EXPLODE_SPEED = 500 //Special bolt travel speed
private constant integer ORB_EXPLODE_RANGE = 400 //Special bolt travel distance
//If a unit shatters, this effect will be created at it's position
private constant string IMPACT_SHATTER_ART = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl"
private constant integer IMPACT_SHATTER_PER_BASE = 8 //Percental base chance to shatter a unit which dies from the spell
private constant integer IMPACT_SHATTER_PER_INC = 4 //Percental increament chance per level to shatter a unit which dies from the spell (example, if base is 8 and incr is 4, each level adds 4 chance)
private constant integer IMPACT_DAMAGE_BASE = 20 //Base damage
private constant integer IMPACT_DAMAGE_INC = 10 //Increamental damage (works in the same way as the shatter base/incr fields)
//Self-explanatory
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
endglobals
//------------------------------------------------------<>
// 'Damage filter'
//
// Add more options as desired.
// Some info: It is used as a condition not a boolexpr,
// thus, not going to give any issue with IsUnitType.
//------------------------------------------------------<>
private function TargetFilter takes unit enemy, player caster, real x, real y returns boolean
return UnitAlive(enemy) and IsUnitEnemy(enemy, caster) and IsUnitType(enemy, UNIT_TYPE_GROUND) and IsUnitInRangeXY(enemy, x, y, MISSILE_RADIUS)
endfunction
//------------------------------------------------------<>
// 'Shatter filter'
//
// Add more options as desired.
// It determines the units which may shatter
// The filter does not need to contain the same context
// as the damage filter. It is only potentially ran after
// the target filter have became true.
//------------------------------------------------------<>
private function ShatterFilter takes unit enemy returns boolean
return not IsUnitType(enemy, UNIT_TYPE_MECHANICAL) and not IsUnitType(enemy, UNIT_TYPE_HERO) and not IsUnitType(enemy, UNIT_TYPE_MAGIC_IMMUNE)
endfunction
//------------------------------------------------------<>
// 'Other constants'
//
// These should not be altered by default, but if you
// know what you are doing, or see a way to use other
// constants instead, feel free.
//------------------------------------------------------<>
globals
private constant real ORB_TMAX = 1.0*ORB_RANGE/ORB_SPEED
private constant real MISSILE_TMAX = 1.0*MISSILE_RANGE/MISSILE_SPEED
private constant real EXPLODE_TMAX = 1.0*ORB_EXPLODE_RANGE/ORB_EXPLODE_SPEED
private constant real RAD_BETWEEN_EXPL = bj_PI*2/ORB_EXPLODE_AMOUNT
private constant group ENUM_GROUP = CreateGroup()
private constant timer ANIM_TIMER = CreateTimer()
endglobals
//------------------------------------------------------<>
// 'Spell code'
//
// If you even think up of an optimize fully working
// version, tell me about it.
//------------------------------------------------------<>
private struct missile
unit obj
effect art
real sx
real sy
real xvel
real yvel
static method create takes string art, player p, real x, real y, real z, real rad, integer speed, real scale returns thistype
local thistype this = thistype.allocate()
set this.obj = CreateUnit(p, XE_DUMMY_UNITID, x, y, rad*bj_RADTODEG)
call UnitAddAbility(this.obj, XE_HEIGHT_ENABLER)
call UnitRemoveAbility(this.obj, XE_HEIGHT_ENABLER)
call SetUnitFlyHeight(this.obj, z, 0)
call SetUnitScale(this.obj, scale, scale, scale)
call SetUnitAnimationByIndex(this.obj, 90)
call UnitRemoveAbility(this.obj, 'Aatk')
set this.art = AddSpecialEffectTarget(art, this.obj, "origin")
set this.sx = x
set this.sy = y
set this.xvel = speed*Cos(rad)
set this.yvel = speed*Sin(rad)
return this
endmethod
method clear takes nothing returns nothing
call DestroyEffect(this.art)
call KillUnit(this.obj)
set this.art = null
set this.obj = null
endmethod
endstruct
private struct spell
missile orb
real otick
missile array mis[MISSILE_AMOUNT_MAX]
real array mtick[MISSILE_AMOUNT_MAX]
integer mcount
integer mtot
missile array xpl[ORB_EXPLODE_AMOUNT]
real xtick //All exploding missiles have the same tickoffset
player owner
integer damage
integer chance
static thistype array tta
static integer tot = 0
static method callback takes nothing returns nothing
local thistype this
local missile m
local integer i = 0
local integer j
local integer k
local real x
local real y
local unit u
local unit v
loop
exitwhen (i >= thistype.tot)
set this = thistype.tta[i]
//Bolts goes here
set j = 0
loop
exitwhen (j >= this.mtot)
if (this.mis[j].obj != null) then
set this.mtick[j] = this.mtick[j]+XE_ANIMATION_PERIOD
set x = this.mis[j].sx+this.mis[j].xvel*this.mtick[j]
set y = this.mis[j].sy+this.mis[j].yvel*this.mtick[j]
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, XE_MAX_COLLISION_SIZE+MISSILE_RADIUS, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen (u == null)
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen (TargetFilter(u, this.owner, x, y))
endloop
//A nice BJ, wrapped anyway
if (this.mtick[j] <= MISSILE_TMAX and RectContainsCoords(bj_mapInitialPlayableArea, x, y) and u == null) then
call SetUnitX(this.mis[j].obj, x)
call SetUnitY(this.mis[j].obj, y)
else
if (u != null) then
call UnitDamageTarget(this.mis[j].obj, u, this.damage, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
if (UnitAlive(u)) then
if (GetUnitAbilityLevel(u, FROST_SLOW_BUFF) == 0) then
set v = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), XE_DUMMY_UNITID, x, y, 0)
call UnitAddAbility(v, FROST_SLOW_ID)
call UnitApplyTimedLife(v, 'BTLF', 1.0)
call IssueTargetOrder(v, "attackonce", u)
endif
elseif (GetRandomInt(0, 100) <= this.chance and ShatterFilter(u)) then
call DestroyEffect(AddSpecialEffect(IMPACT_SHATTER_ART, x, y))
call RemoveUnit(u)
endif
endif
//A note - instances are not destroyed until the end of the spell, when all instances are properly cleared.
call this.mis[j].clear()
set this.mcount = this.mcount-1
endif
endif
set j = j+1
endloop
//Orb goes here
if (this.orb.obj != null) then
set this.otick = this.otick+XE_ANIMATION_PERIOD
set x = this.orb.sx+this.orb.xvel*this.otick
set y = this.orb.sy+this.orb.yvel*this.otick
if (this.otick < ORB_TMAX and RectContainsCoords(bj_mapInitialPlayableArea, x, y)) then
call SetUnitX(this.orb.obj, x)
call SetUnitY(this.orb.obj, y)
set this.mis[this.mtot] = missile.create(MISSILE_ART, this.owner, x, y, MISSILE_HEIGHT, this.mtot*MISSILE_RAD_OFFSET, MISSILE_SPEED, MISSILE_SCALE)
set this.mtick[this.mtot] = 0
set this.mcount = this.mcount+1
set this.mtot = this.mtot+1
else
//Clears the orb
call this.orb.clear()
//Proceeds with creating special bolts, aka bolts released when the orb vanishes.
set j = 0
loop
exitwhen (j >= ORB_EXPLODE_AMOUNT)
set this.xpl[j] = missile.create(MISSILE_ART, this.owner, x+ORB_RADIUS*Cos(j*RAD_BETWEEN_EXPL), y+ORB_RADIUS*Sin(j*RAD_BETWEEN_EXPL), MISSILE_HEIGHT, j*RAD_BETWEEN_EXPL+bj_PI*0.25, ORB_EXPLODE_SPEED, MISSILE_SCALE)
set j = j+1
endloop
set this.xtick = 0
endif
else
//Special bolt stuff goes here
set this.xtick = this.xtick+XE_ANIMATION_PERIOD
set j = 0
loop
exitwhen (j >= ORB_EXPLODE_AMOUNT)
if (this.xpl[j].obj != null) then
set x = this.xpl[j].sx+this.xpl[j].xvel*this.xtick
set y = this.xpl[j].sy+this.xpl[j].yvel*this.xtick
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, XE_MAX_COLLISION_SIZE+MISSILE_RADIUS, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen (u == null)
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen (TargetFilter(u, this.owner, x, y))
endloop
if (this.xtick < EXPLODE_TMAX and RectContainsCoords(bj_mapInitialPlayableArea, x, y) and u == null) then
call SetUnitX(this.xpl[j].obj, x)
call SetUnitY(this.xpl[j].obj, y)
else
if (u != null) then
call UnitDamageTarget(this.xpl[j].obj, u, this.damage, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
if (UnitAlive(u)) then
if (GetUnitAbilityLevel(u, FROST_SLOW_BUFF) == 0) then
set v = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), XE_DUMMY_UNITID, x, y, 0)
call UnitAddAbility(v, FROST_SLOW_ID)
call UnitApplyTimedLife(v, 'BTLF', 1.0)
call IssueTargetOrder(v, "attackonce", u)
endif
elseif (GetRandomInt(0, 100) <= this.chance and ShatterFilter(u)) then
call DestroyEffect(AddSpecialEffect(IMPACT_SHATTER_ART, x, y))
call RemoveUnit(u)
endif
endif
//Clears a special bolt
call this.xpl[j].clear()
endif
endif
set j = j+1
endloop
endif
//Completely destroys the spell and all instances
if (this.mcount == 0 and this.orb.obj == null and this.xtick >= ORB_TMAX) then
set j = 0
loop
exitwhen (j >= this.mcount)
call this.mis[j].destroy()
set j = j+1
endloop
set j = 0
loop
exitwhen (j >= ORB_EXPLODE_AMOUNT)
call this.xpl[j].destroy()
set j = j+1
endloop
call this.orb.destroy()
call this.destroy()
set thistype.tot = thistype.tot-1
set thistype.tta[i] = thistype.tta[thistype.tot]
if (thistype.tot == 0) then
call PauseTimer(ANIM_TIMER)
endif
else
set i = i+1
endif
endloop
set v = null
endmethod
static method create takes unit su, real tx, real ty returns thistype
local thistype this = thistype.allocate()
local real x = GetUnitX(su)
local real y = GetUnitY(su)
local integer level = GetUnitAbilityLevel(su, ABILITY_ID)
set this.owner = GetOwningPlayer(su)
set this.orb = missile.create(ORB_ART, this.owner, x, y, ORB_HEIGHT, Atan2(ty-y, tx-x), ORB_SPEED, ORB_SCALE)
set this.otick = 0
set this.mcount = 0
set this.mtot = 0
set this.damage = IMPACT_DAMAGE_BASE+IMPACT_DAMAGE_INC*(level-1)
set this.chance = IMPACT_SHATTER_PER_BASE+IMPACT_SHATTER_PER_INC*(level-1)
set thistype.tta[thistype.tot] = this
if (thistype.tot == 0) then
call TimerStart(ANIM_TIMER, XE_ANIMATION_PERIOD, true, function thistype.callback)
endif
set thistype.tot = thistype.tot+1
return this
endmethod
endstruct
private function Evaluate takes nothing returns boolean
if (GetSpellAbilityId() == ABILITY_ID) then
call spell.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Filter(function Evaluate))
endfunction
endlibrary