// Immolation Fireball Version 1.24
scope FNova initializer Init_IFireball
private keyword NovaData
private keyword MislData
private keyword ImmoData
globals
// Constants
private constant integer ABILTY_ID = 'A000' // rawcode of the Dummy Spell
private constant integer M_UNIT_ID = 'e000' // rawcode of the Missile Unit
private constant integer N_UNIT_ID = 'e001' // rawcode of the Nova Unit
private constant integer NOVA_SIZE = 18 // number of the Nova Units
private constant real M_TIMER_INTERVAL = 0.03 // this will determine the missile speed
private constant real N_TIMER_INTERVAL = 0.02 // this will determine the nova expand speed
private constant real I_TIMER_INTERVAL = 0.1 // this will determine the immolation damage frequency
private constant real M_DIST = 32.00 // missile step
private constant real N_DIST = 16.00 // nova expand step
private constant real dA = 360/NOVA_SIZE
private constant real K = 60.00
private constant string UnitModel = "Abilities\\Spells\\Other\\BreathOfFire\\BreathOfFireDamage.mdl"
private constant string BuildingModel = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeEmbers.mdl"
private constant string UAttPoint = "chest"
private constant string BAttPoint = "sprite"
// Variables
private timer mtm = CreateTimer()
private timer ntm = CreateTimer()
private timer itm = CreateTimer()
private MislData array temp_mdat
private NovaData array temp_ndat
private ImmoData array temp_idat
private integer mindex = 0
private integer nindex = 0
private integer iindex = 0
private group TempGroup = CreateGroup()
private NovaData iFilter
private hashtable Nova_Flag = InitHashtable()
//====================================================================================================
// User-edit data //
//====================================================================================================
// Make sure to change the dummy spell text to be appropriate with the following variables.
// General Formula: ConstantFactor + AbilityLevel * AbilityLevelFactor + PrevLevelFactor * PrevLevelValue
// The following variable can be changed according to the desired results
// Damage Variables:
private constant boolean D_TABLE = FALSE // TRUE/FALSE to use Table/Formula
// Table:
private constant real array Damage // set the values in the function named "Spell_Data_Table"
// Formula:
private constant real D_CONST_FACTOR = 50.00 // changes 'ConstantFactor'
private constant real D_LEVEL_FACTOR = 50.00 // changes 'AbilityLevelFactor'
private constant real D_PREV_LVL_FACTOR = 0 // changes 'PrevLevelFactor'
// Area of Effect (AoE) Variables:
private constant boolean R_TABLE = FALSE // TRUE/FALSE to use Table/Formula
// Table:
private constant real array Range // set the values in the function named "Spell_Data_Table"
// Formula:
private constant real R_CONST_FACTOR = 500.00 // changes 'ConstantFactor'
private constant real R_LEVEL_FACTOR = 0.00 // changes 'AbilityLevelFactor'
private constant real R_PREV_LVL_FACTOR = 0 // changes 'PrevLevelFactor'
// Range can be edited from the dummy spell
// Targets:
private constant boolean FRIENDLY = TRUE // TRUE/FALSE to harm enemy/all units
private constant boolean GROUND = TRUE // TRUE/FALSE to include/exclude ground units
private constant boolean FLYING = TRUE // TRUE/FALSE to include/exclude flying units
private constant boolean STRUCTURE = TRUE // TRUE/FALSE to include/exclude structure units
// Factors
private constant real UNIT_FACTOR = 1.00 // Unit damage factor.
private constant real BUILDN_FACTOR = 0.75 // Building damage factor.
// this will be multiplied with the spell damage
// Becareful -ve values will heal.
// Destroy Trees/Destructibles
// Sorry, not done yet !!
// Immolation (Damage Over Time)
private constant boolean IMMOLATION = TRUE // TRUE/FALSE to Allow/Disallow Immolation (Damage Over Time)
// Immolation Damage (ID) [per 1 Second]
private constant boolean ID_TABLE = FALSE // TRUE/FALSE to use Table/Formula
// Table:
private constant real array ImmoDmg // set the values in the function named "Spell_Data_Table"
// Formula
private constant real ID_CONST_FACTOR = 00.00 // changes 'ConstantFactor'
private constant real ID_LEVEL_FACTOR = 01.00 // changes 'AbilityLevelFactor'
private constant real ID_PREV_LVL_FACTOR = 0 // changes 'PrevLevelFactor'
// Immolation Length(Duration) (IL) [in seconds]
private constant boolean IL_TABLE = FALSE // TRUE/FALSE to use Table/Formula
// Table:
private constant real array ImmoDur // set the values in the function named "Spell_Data_Table"
// Formula
private constant real IL_CONST_FACTOR = 05.00 // changes 'ConstantFactor'
private constant real IL_LEVEL_FACTOR = 05.00 // changes 'AbilityLevelFactor'
private constant real IL_PREV_LVL_FACTOR = 0 // changes 'PrevLevelFactor'
private constant real CASTER_HAND_Z = 64.00 // This value controls the height at which the missile will appear relative to the caster. Change it according to the model and its scale
//====================================================================================================
// The following variables can also be changed but will affect the spell
// This is the Attack type and damage type; they are affected
//by the type of the target unit's armor.
private constant attacktype ATKTYPE = ATTACK_TYPE_NORMAL // Spell Attack Type
private constant damagetype DMGTYPE = DAMAGE_TYPE_FIRE // Fire Damage Type
endglobals
function Spell_Data_Table takes nothing returns nothing
// If your spell is more than 3 levels just add Damage[]/Range[]/ImmoDmg[]/ImmoDur[] under its appropriate place
// Remember that if you don't add this line the Damage/Range/ImmoDmg/ImmoDur will be 0 for level 4+
// Damage Table
set Damage[1] = 50.0
set Damage[2] = 100.0
set Damage[3] = 200.0
// Range Table
set Range[1] = 500.0
set Range[2] = 500.0
set Range[3] = 500.0
// Immolation Damage Table
set ImmoDmg[1] = 2.0
set ImmoDmg[2] = 4.0
set ImmoDmg[3] = 8.0
// Immolation Duration Table
set ImmoDur[1] = 10.0
set ImmoDur[2] = 15.0
set ImmoDur[3] = 30.0
//====================================================================================================
// End of User-edit data //
//====================================================================================================
endfunction
function NovaDamage takes integer al returns real
local real prevDmg
if D_TABLE then
return Damage[al]
else
if al == 1 then
set prevDmg = 0
else
set prevDmg = NovaDamage(al - 1)
endif
return D_CONST_FACTOR + D_LEVEL_FACTOR * al + D_PREV_LVL_FACTOR * prevDmg
endif
endfunction
function NovaRadius takes integer al returns real
local real prevRad
if R_TABLE then
return Range[al]
else
if al == 1 then
set prevRad = 0
else
set prevRad = NovaRadius(al - 1)
endif
return R_CONST_FACTOR + R_LEVEL_FACTOR * al + R_PREV_LVL_FACTOR * prevRad
endif
endfunction
function ImmoDamage takes integer al returns real
local real prevIDmg
if ID_TABLE then
return ImmoDmg[al]
else
if al == 0 then
set prevIDmg = 0
else
set prevIDmg = ImmoDamage(al - 1)
endif
return ID_CONST_FACTOR + ID_LEVEL_FACTOR * al + ID_PREV_LVL_FACTOR * prevIDmg
endif
endfunction
function ImmoDuration takes integer al returns real
local real prevIDur
if IL_TABLE then
return ImmoDur[al]
else
if al == 0 then
set prevIDur = 0
else
set prevIDur = ImmoDuration(al - 1)
endif
return IL_CONST_FACTOR + IL_LEVEL_FACTOR * al + IL_PREV_LVL_FACTOR * prevIDur
endif
endfunction
private struct ImmoData extends array
unit caster
unit target
integer executecount
real damage
real duration
effect FX
implement Alloc
static method Interval takes nothing returns nothing
local thistype dat
local integer i = 0
loop
exitwhen i >= iindex
set dat = temp_idat[i]
set dat.executecount = dat.executecount + 1
if dat.executecount > dat.duration/I_TIMER_INTERVAL then
set iindex = iindex - 1
set temp_idat[i] = temp_idat[iindex]
set i = i - 1
set dat.caster = null
set dat.target = null
set dat.executecount = 0
call DestroyEffect(dat.FX)
set dat.FX = null
call dat.deallocate()
if iindex == 0 then
call PauseTimer(itm)
endif
else
if IsUnitType(dat.target,UNIT_TYPE_STRUCTURE) then
call UnitDamageTarget(dat.caster,dat.target,BUILDN_FACTOR*dat.damage,false,false,ATKTYPE,DMGTYPE,null)
if dat.FX == null then
set dat.FX = AddSpecialEffectTarget(BuildingModel,dat.target,BAttPoint)
endif
else
call UnitDamageTarget(dat.caster,dat.target, UNIT_FACTOR*dat.damage,false,false,ATKTYPE,DMGTYPE,null)
if dat.FX == null then
set dat.FX = AddSpecialEffectTarget(UnitModel,dat.target,UAttPoint)
endif
endif
endif
set i = i + 1
endloop
endmethod
static method StartDoT takes unit caster,unit target, real damage, real duration returns nothing
local thistype dat = thistype.allocate()
local integer i = 0
local unit temp_u
set temp_idat[iindex] = dat
set dat.caster = caster
set dat.target = target
set dat.executecount = 0
set dat.duration = duration
set dat.damage = damage
if iindex == 0 then
call TimerStart(itm, I_TIMER_INTERVAL , true , function thistype.Interval )
endif
set iindex = iindex + 1
endmethod
endstruct
private struct NovaData extends array
unit Caster
real expand
real X
real Y
player Owner
real dmg
real rad
real IDamage
real IDuration
implement Alloc
static method MatchingUnit takes unit u returns boolean
if IsUnitType(u,UNIT_TYPE_DEAD) or/*
*/ IsUnitType(u,UNIT_TYPE_MAGIC_IMMUNE) or/*
*/( LoadBoolean(Nova_Flag, iFilter, GetHandleId(u))) or/*
*/( FRIENDLY and IsUnitAlly(u,iFilter.Owner)) or/*
*/(not FLYING and IsUnitType(u,UNIT_TYPE_FLYING)) or/*
*/(not GROUND and IsUnitType(u,UNIT_TYPE_GROUND)) or/*
*/(not STRUCTURE and IsUnitType(u,UNIT_TYPE_STRUCTURE)) then
return false
endif
return true
endmethod
static method NovaExpand takes nothing returns nothing
local NovaData dat
local integer i = 0
local integer j
local real X
local real Y
local real A
local unit u
local unit n
loop // this loop cycles through all the casters which have cast the spell
exitwhen i >= nindex
set dat = temp_ndat[i]
// Nova Expanding
set j = 0
set A = 0
set dat.expand = dat.expand + N_DIST
loop // loop for moving the nova units
exitwhen j >= NOVA_SIZE
set X = dat.X + dat.expand * Cos( A * bj_DEGTORAD )
set Y = dat.Y + dat.expand * Sin( A * bj_DEGTORAD )
set n = LoadUnitHandle( Nova_Flag , - dat , j )
call SetUnitX(n,X)
call SetUnitY(n,Y)
set A = A + dA
set j = j + 1
endloop
// Nova Damage
call GroupEnumUnitsInRange(TempGroup, dat.X, dat.Y, dat.expand, null)
set iFilter = dat
loop
set u = FirstOfGroup(TempGroup)
exitwhen u == null
if MatchingUnit(u) then
if IsUnitType(u,UNIT_TYPE_STRUCTURE) then
call UnitDamageTarget(dat.Caster, u, BUILDN_FACTOR*dat.dmg, false, false, ATKTYPE, DMGTYPE, null)
else
call UnitDamageTarget(dat.Caster, u, UNIT_FACTOR*dat.dmg, false, false, ATKTYPE, DMGTYPE, null)
endif
if IMMOLATION == true and GetWidgetLife(u) >= 0.405 then
call ImmoData.StartDoT(dat.Caster,u,dat.IDamage,dat.IDuration)
endif
call SaveBoolean(Nova_Flag,dat,GetHandleId(u),true)
endif
call GroupRemoveUnit(TempGroup, u)
endloop
if dat.expand >= dat.rad then // check if the nova has reached its max. AoE
set nindex = nindex - 1
set temp_ndat[i] = temp_ndat[nindex]
set i = i - 1
set j = 0
loop
exitwhen j >= NOVA_SIZE
set n = LoadUnitHandle( Nova_Flag , - dat , j )
call KillUnit(n)
set dat.expand = 0
set j = j + 1
endloop
call FlushChildHashtable( Nova_Flag , - dat )
call FlushChildHashtable( Nova_Flag , dat )
call dat.deallocate()
if nindex == 0 then
call PauseTimer(ntm)
endif
endif
set i = i + 1
endloop
set n = null
endmethod
static method NovaCreate takes unit caster,real damage,real radius,real idamage,real iduration,real posX,real posY returns nothing
local NovaData dat = NovaData.allocate()
local integer i = 0
local real A = 0.00
local unit u
set dat.Caster = caster
set dat.Owner = GetOwningPlayer(caster)
set dat.X = posX
set dat.Y = posY
set dat.dmg = damage
set dat.rad = radius
set dat.IDamage = idamage
set dat.IDuration = iduration
loop
exitwhen i >= NOVA_SIZE
set u = CreateUnit( dat.Owner , N_UNIT_ID , dat.X , dat.Y , A )
call SaveUnitHandle( Nova_Flag , - dat , i , u )
set A = A + dA
set i = i + 1
endloop
set u = null
if nindex == 0 then
call TimerStart(ntm,N_TIMER_INTERVAL,true,function NovaData.NovaExpand)
endif
set temp_ndat[nindex] = dat
set nindex = nindex + 1
endmethod
endstruct
private struct MislData extends array
unit Caster
unit Missile
real CastPointX
real CastPointY
real Damage
real Radius
real IDamage
real IDuration
real face
real a
real h
real k
real v1
real x1
real y1
implement Alloc
static method MissileMove takes nothing returns nothing
local MislData dat
local integer i = 0
local real x = 0
local real y = 0
local real z
local real x2 = 0
local real y2 = 0
local real dx = 0
local real dy = 0
local real v
loop
exitwhen i >= mindex
set dat = temp_mdat[i]
set x = GetUnitX(dat.Missile)
set y = GetUnitY(dat.Missile)
set x2 = dat.CastPointX
set y2 = dat.CastPointY
set dx = x2 - x
set dy = y2 - y
if SquareRoot(dx*dx + dy*dy) < M_DIST then
set mindex = mindex - 1
set temp_mdat[i] = temp_mdat[mindex]
set i = i - 1
call NovaData.NovaCreate(dat.Caster,dat.Damage,dat.Radius,dat.IDamage,dat.IDuration,x,y)
call KillUnit(dat.Missile)
set dat.Missile = null
set dat.face = 0
call dat.deallocate()
if mindex == 0 then
call PauseTimer(mtm)
endif
else
set x = x + M_DIST * Cos(dat.face)
set y = y + M_DIST * Sin(dat.face)
set dx = x - dat.x1
set dy = y - dat.y1
set v = dat.v1 + SquareRoot(dx*dx + dy*dy)
set z = (dat.a * ( v - dat.h ) * ( v - dat.h )) + dat.k
call SetUnitPosition(dat.Missile,x,y)
call SetUnitFlyHeight(dat.Missile,z,0.0)
endif
set i = i + 1
endloop
endmethod
static method MissileCreate takes unit u , real X , real Y returns nothing
local MislData dat = MislData.allocate()
local integer al = GetUnitAbilityLevel(u , ABILTY_ID)
local real x1 = 0.0
local real y1 = 0.0
local real x2 = 0.0
local real y2 = 0.0
local real dx = 0.0
local real dy = 0.0
local real z1 = 0.0
local real v1 = 0.0
local real z2 = 0.0
local real v2 = 0.0
local real phi = 0.0
local real dist = 0.0
local real error = 0.0
set temp_mdat[mindex] = dat
set x1 = GetUnitX(u)
set dat.x1 = x1
set y1 = GetUnitY(u)
set dat.y1 = y1
// Fail-Safe in case the caster and the cast location is at points (x=[-1,1],y=[-1,1])
if ((x1 >= -1 and x1 <= 1) and (y1 >= -1 and y1 <= 1) and X == x1 and Y == y1) then
set error = 1.0
endif
set z1 = CASTER_HAND_Z
set dat.k = GetUnitFlyHeight(u) + z1 + K // in case of flying units
set x2 = X + error
set y2 = Y + error
set z2 = 0.0
set phi = SquareRoot((z2 - dat.k)/(z1 - dat.k))
set v1 = SquareRoot(x1*x1 + y1*y1)
set dat.v1 = v1
set dx = x2 - x1
set dy = y2 - y1
set dat.Caster = u
set dat.CastPointX = X
set dat.CastPointY = Y
set dist = SquareRoot(dx * dx + dy * dy)
set dat.face = Atan2(y2 - y1, x2 - x1)
set v2 = v1 + dist
set dat.h = ((phi*v1) + v2)/(1.0 + phi)
set dat.a = (z2 - dat.k)/((dat.h - v2)*(dat.h - v2))
set dat.Missile = CreateUnit(GetOwningPlayer(u),M_UNIT_ID,x1,y1,dat.face*bj_RADTODEG)
call SetUnitFlyHeight(dat.Missile,z1,0.0)
set dat.Damage = NovaDamage(al)
set dat.Radius = NovaRadius(al)
set dat.IDamage = ImmoDamage(al) * I_TIMER_INTERVAL
set dat.IDuration = ImmoDuration(al)
if mindex == 0 then
call TimerStart(mtm,M_TIMER_INTERVAL,true,function MislData.MissileMove)
endif
set mindex = mindex + 1
endmethod
endstruct
private function FB_Actions takes nothing returns nothing
if GetSpellAbilityId() == ABILTY_ID then
call MislData.MissileCreate(GetTriggerUnit(), GetSpellTargetX(),GetSpellTargetY())
endif
endfunction
private function Init_IFireball takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddAction(t,function FB_Actions)
call Spell_Data_Table()
//Preload the dummy units to reduce lag on first cast
call RemoveUnit(CreateUnit(Player(12),M_UNIT_ID,0.,0.,0.))
call RemoveUnit(CreateUnit(Player(12),N_UNIT_ID,0.,0.,0.))
endfunction
endscope