library ChargedBolt initializer Init requires xebasic
//---------------------------------------------------
// 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
//---------------------------------------------------
globals
private constant integer ABILITY_ID = 'A000' //Id of the casting ability
private constant integer ABILITY_MAX_LVL = 4 //Max level of the casting ability. Match these with the level value field of the ability in the ability editor
private constant integer MISSILE_QTY_BASE = 3 //Amount of missile bolts released on level 1
private constant integer MISSILE_QTY_INC = 2 //Amount increased on levels > 1, eg, in this case, lvl 2 gives 3+2*(level-1) bolts
private constant integer MISSILE_QTY_MAX = 50 //Due to a JassHelper bug, set this constant to be at least your qty_base+qty_inc*(max_lvl-1). Note that this should be a fixed integer value.
//Self explanatory, hopefully
private constant string MISSILE_ART = "Abilities\\Spells\\Orc\\LightningBolt\\LightningBoltMissile.mdl"
private constant integer MISSILE_SPEED = 500
private constant integer MISSILE_RANGE = 800
private constant integer MISSILE_HEIGHT = 32
private constant real MISSILE_SCALE = 0.5
private constant real MISSILE_VIB_PER_MIN = 6*bj_PI //The min value factor describing how fast the missile vibrates. (see a formula as y = A*sin(kx) where k is this vibration factor.)
private constant real MISSILE_VIB_PER_MAX = 12*bj_PI //The max value factor describing how fast the missile vibrates.
private constant integer MISSILE_VIB_RADIUS = 32 //The altitude factor of the missile vibration. (see the formula above, where A is this radius factor.)
private constant real MISSILE_QTY_SPREAD = bj_PI/16 //The max radian angle possible between the missiles.
private constant real MISSILE_MAX_SPREAD = bj_PI/4 //The max radian angle spread, in which all missiles are moving in. For instance bj_PI/4 means that all missiles move within the caster_facing+-spread
private constant string IMPACT_ART = "Abilities\\Spells\\Items\\AIlb\\AIlbSpecialArt.mdl"
private constant string IMPACT_ATTACH_POINT = "chest"
private constant integer IMPACT_DAMAGE_BASE = 50 //Amount of damage at level 1
private constant integer IMPACT_DAMAGE_INC = 25 //Amount of damage added per each increased level
private constant integer IMPACT_RADIUS = 32 //See this as the missile collision size. Note that the missile will only collide and deal damage to one unit.
//Self explanatory, hopefully
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
// Target filter, used to determinate what enemies the spell will hit.
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, IMPACT_RADIUS)
endfunction
//---------------------------------------------------
// Main spell code below
// Don't edit unless you know what you are doing
//---------------------------------------------------
globals
//Preserved in case of JassHelper being fixed
//private constant integer MISSILE_QTY_MAX = (MISSILE_QTY_BASE+(MISSILE_QTY_INC*(ABILITY_MAX_LVL-1)))
private constant real MISSILE_TIME_MAX = 1.0*MISSILE_RANGE/MISSILE_SPEED
private constant group ENUM_GROUP = CreateGroup()
private constant timer ANIM_TIMER = CreateTimer()
endglobals
//Missile struct used upon creating then charged missiles
private struct missile
unit mob
effect gfx
real xvel
real yvel
real vvel
static method create takes player sp, real sx, real sy, real ra returns missile
local missile m = missile.allocate()
set m.mob = CreateUnit(sp, XE_DUMMY_UNITID, sx, sy, ra)
set m.gfx = AddSpecialEffectTarget(MISSILE_ART, m.mob, "origin")
call UnitAddAbility(m.mob, XE_HEIGHT_ENABLER)
call UnitRemoveAbility(m.mob, XE_HEIGHT_ENABLER)
call SetUnitAnimationByIndex(m.mob, 90)
call SetUnitFlyHeight(m.mob, MISSILE_HEIGHT, 0)
call SetUnitScale(m.mob, MISSILE_SCALE, MISSILE_SCALE, MISSILE_SCALE)
set m.xvel = MISSILE_SPEED*Cos(ra)
set m.yvel = MISSILE_SPEED*Sin(ra)
set m.vvel = GetRandomReal(MISSILE_VIB_PER_MIN, MISSILE_VIB_PER_MAX)
return m
endmethod
method onDestroy takes nothing returns nothing
call DestroyEffect(.gfx)
call KillUnit(.mob)
set .mob = null
set .gfx = null
endmethod
endstruct
//Main struct used to handle and run spell data
private struct data
player sp
integer slvl
real sx
real sy
real t1
missile array mis[MISSILE_QTY_MAX]
integer mqty
static data array dat
static integer dqty = 0
static method callback takes nothing returns nothing
local data d
local missile m
local integer i = 0
local integer j
local real x
local real y
local unit u
loop
exitwhen i >= data.dqty
set d = data.dat[i]
set d.t1 = d.t1+XE_ANIMATION_PERIOD
set j = 0
loop
exitwhen j >= d.mqty
set m = d.mis[j]
set x = d.sx+m.xvel*d.t1
set y = d.sy+m.yvel*d.t1
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, XE_MAX_COLLISION_SIZE+IMPACT_RADIUS, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen u == null
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen TargetFilter(u, d.sp, x, y)
endloop
call GroupClear(ENUM_GROUP)
// RectContainsCoords is a basic bj inline for GetRectMinX < x... blabla
if d.t1 <= MISSILE_TIME_MAX and RectContainsCoords(bj_mapInitialPlayableArea, x, y) and u == null then
call SetUnitX(m.mob, x+MISSILE_VIB_RADIUS*Sin(m.vvel*d.t1))
call SetUnitY(m.mob, y+MISSILE_VIB_RADIUS*Sin(m.vvel*d.t1))
set j = j+1
else
if u != null then
call UnitDamageTarget(m.mob, u, IMPACT_DAMAGE_BASE+IMPACT_DAMAGE_INC*(d.slvl-1), true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call DestroyEffect(AddSpecialEffectTarget(IMPACT_ART, u, IMPACT_ATTACH_POINT))
endif
call m.destroy()
set d.mqty = d.mqty-1
set d.mis[j] = d.mis[d.mqty]
endif
endloop
if d.mqty == 0 then
call d.destroy()
set data.dqty = data.dqty-1
set data.dat[i] = data.dat[data.dqty]
if data.dqty == 0 then
call PauseTimer(ANIM_TIMER)
endif
else
set i = i+1
endif
endloop
set u = null
endmethod
static method create takes unit su, real nx, real ny returns data
local data d = data.allocate()
local integer i = 0
local real ra
local real spr
local real ramin
set d.sx = GetUnitX(su)
set d.sy = GetUnitY(su)
set ra = Atan2(ny-d.sy, nx-d.sx)
set d.sp = GetOwningPlayer(su)
set d.slvl = GetUnitAbilityLevel(su, ABILITY_ID)
set d.mqty = MISSILE_QTY_BASE+MISSILE_QTY_INC*(d.slvl-1)
set spr = MISSILE_QTY_SPREAD*(d.mqty-1)
if spr > MISSILE_MAX_SPREAD then
set spr = MISSILE_MAX_SPREAD
endif
set ramin = ra-spr/2
set spr = spr/(d.mqty-1)
loop
exitwhen i >= d.mqty
set d.mis[i] = missile.create(d.sp, d.sx, d.sy, ramin+spr*i)
set i = i+1
endloop
set d.t1 = 0
set data.dat[data.dqty] = d
if data.dqty == 0 then
call TimerStart(ANIM_TIMER, XE_ANIMATION_PERIOD, true, function data.callback)
endif
set data.dqty = data.dqty+1
return d
endmethod
endstruct
private function Evaluate takes nothing returns boolean
if GetSpellAbilityId() == ABILITY_ID then
call data.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))
set t = null
endfunction
endlibrary