// Meteor Swarm Version 1.20
scope MeteorSwarm initializer Init_MeteorSwarm
globals
private real MIN_X
private real MIN_Y
private real MAX_X
private real MAX_Y
private constant real SAFETY_OFFSET = 16.00
private constant integer M_DUMMY_ID = 'e000'
private constant integer I_DUMMY_ID = 'b000'
private constant integer ABILITY_ID = 'A000'
private integer index = 0
private integer array temp_this
private constant integer INT_MIN = 3 // Minimum Interval Between Meteors Threshold
private constant integer INT_MAX = 32 // Maximum Interval Between Meteors Threshold
private constant real SCALE_MIN = 0.80 // Minimum Meteor Scale Threshold
private constant real SCALE_MAX = 3.50 // Maximum Meteor Scale Threshold
private constant real H_MIN = 750.00 // Minimum Meteor Height Threshold
private constant real H_MAX = 1000.00 // Maximum Meteor Height Threshold
private constant real INIT_DIST = 1000.00
private constant real STEP = 50.00
private constant real CHECK_DIST = 32.00
private constant integer HT_OFFSET = 4096
private hashtable mht = InitHashtable() // Meteor Hashtable
private constant timer shake_timer = CreateTimer()
private unit CASTER
private real DAMAGE
//====================================================================================================
// 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
// Number of Meteors Variables:
private constant boolean NUM_TABLE = false // true/false to use Table/Formula
// Table:
private constant integer array nMeteors // set the values in the function named "Spell_Data_Table"
// Formula:
private constant integer NUM_CF = 25 // changes 'ConstantFactor'
private constant integer NUM_LF = 5 // changes 'AbilityLevelFactor'
private constant real NUM_PLF = 0 // changes 'PrevLevelFactor'
// Minimum Damage Variables:
private constant boolean D_MIN_TABLE = false // true/false to use Table/Formula
// Table:
private constant real array MinDamage // set the values in the function named "Spell_Data_Table"
// Formula:
private constant real D_MIN_CF = 75.00 // changes 'ConstantFactor'
private constant real D_MIN_LF = 25.00 // changes 'AbilityLevelFactor'
private constant real D_MIN_PLF = 0 // changes 'PrevLevelFactor'
// Maximum Damage Variables:
private constant boolean D_MAX_TABLE = false // true/false to use Table/Formula
// Table:
private constant real array MaxDamage // set the values in the function named "Spell_Data_Table"
// Formula:
private constant real D_MAX_CF = 250.00 // changes 'ConstantFactor'
private constant real D_MAX_LF = 50.00 // changes 'AbilityLevelFactor'
private constant real D_MAX_PLF = 0 // changes 'PrevLevelFactor'
// Area of Effect (AoE) Variables:
// Grand Area of Effect:
private constant boolean GR_TABLE = false // true/false to use Table/Formula
// Table:
private constant real array GrandArea // set the values in the function named "Spell_Data_Table"
// Formula:
private constant real GR_CF = 750.00 // changes 'ConstantFactor'
private constant real GR_LF = 50.00 // changes 'AbilityLevelFactor'
private constant real GR_PLF = 0 // changes 'PrevLevelFactor'
// Maximum AOE per Meteor:
private constant boolean MAXR_TABLE = false // true/false to use Table/Formula
// Table:
private constant real array LargeArea // set the values in the function named "Spell_Data_Table"
// Formula:
private constant real MAXR_CF = 150.00 // changes 'ConstantFactor'
private constant real MAXR_LF = 10.00 // changes 'AbilityLevelFactor'
private constant real MAXR_PLF = 0 // changes 'PrevLevelFactor'
// Minimum AOE per Meteor:
private constant boolean MINR_TABLE = false // true/false to use Table/Formula
// Table:
private constant real array SmallArea // set the values in the function named "Spell_Data_Table"
// Formula:
private constant real MINR_CF = 100.00 // changes 'ConstantFactor'
private constant real MINR_LF = 10.00 // changes 'AbilityLevelFactor'
private constant real MINR_PLF = 0 // changes 'PrevLevelFactor'
// 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 = false // true/false to include/exclude flying units
private constant boolean STRUCTURE = true // true/false to include/exclude structure units
private constant boolean DESTRUCTABLES = true // true/false to enable/disable destructables
private constant real DESTRUCTABLE_FACTOR = 10.00 // this is the damage dealt to destructables divisor
// Flags:
private constant boolean INIT_MAX_D = true // true/false to enable/disable maximum damage on first meteor
private constant boolean KILLS_EXPLODE = true // true/false to enable/disable unit explosion if killed by a meteor
private constant boolean SHOW_INDICATOR = true // true/false to enable/disable cast area of effect indicator
private constant boolean ALLOW_CAMERA_SHAKE = true // true/false to enable/disable camera shaking
endglobals
// Just IGNORE this function (Table) IF you are using a formula (Just fold it)
private function Spell_Data_Table takes nothing returns nothing
// Number of Meteors Table
set nMeteors[1] = 30
set nMeteors[2] = 35
set nMeteors[3] = 40
set nMeteors[4] = 45
set nMeteors[5] = 50
// Minimum Damage Table
set MinDamage[1] = 100.00
set MinDamage[2] = 125.00
set MinDamage[3] = 150.00
set MinDamage[4] = 175.00
set MinDamage[5] = 200.00
// Maximum Damage Table
set MaxDamage[1] = 300.00
set MaxDamage[2] = 350.00
set MaxDamage[3] = 400.00
set MaxDamage[4] = 450.00
set MaxDamage[5] = 500.00
// Grand Area of Effect
set GrandArea[1] = 800.00
set GrandArea[2] = 850.00
set GrandArea[3] = 900.00
set GrandArea[4] = 950.00
set GrandArea[5] = 1000.00
// Maximum Area of Effect per Meteor
set LargeArea[1] = 160.00
set LargeArea[2] = 170.00
set LargeArea[3] = 180.00
set LargeArea[4] = 190.00
set LargeArea[5] = 200.00
// Minimum Area of Effect per Meteor
set SmallArea[1] = 110.00
set SmallArea[2] = 120.00
set SmallArea[3] = 130.00
set SmallArea[4] = 140.00
set SmallArea[5] = 150.00
//====================================================================================================
// End of User-edit data //
//====================================================================================================
endfunction
private function MeteorsNumber takes integer al returns integer
local integer prevNMet
static if NUM_TABLE then
return nMeteors[al]
else
if al == 1 then
set prevNMet = 0
else
set prevNMet = MeteorsNumber(al - 1)
endif
return NUM_CF + NUM_LF * al + R2I(NUM_PLF) * prevNMet
endif
endfunction
private function DamageMin takes integer al returns real
local real prevMinDmg
static if D_MIN_TABLE then
return MinDamage[al]
else
if al == 1 then
set prevMinDmg = 0.
else
set prevMinDmg = DamageMin(al - 1)
endif
return D_MIN_CF + D_MIN_LF * al + D_MIN_PLF * prevMinDmg
endif
endfunction
private function DamageMax takes integer al returns real
local real prevMaxDmg
static if D_MAX_TABLE then
return MaxDamage[al]
else
if al == 1 then
set prevMaxDmg = 0.
else
set prevMaxDmg = DamageMax(al - 1)
endif
return D_MAX_CF + D_MAX_LF * al + D_MAX_PLF * prevMaxDmg
endif
endfunction
private function AreaGrand takes integer al returns real
local real prevGrArea
static if GR_TABLE then
return GrandArea[al]
else
if al == 1 then
set prevGrArea = 0.
else
set prevGrArea = AreaGrand(al - 1)
endif
return GR_CF + GR_LF * al + GR_PLF * prevGrArea
endif
endfunction
private function MinAreaPerMeteor takes integer al returns real
local real prevMinA
static if MINR_TABLE then
return SmallArea[al]
else
if al == 1 then
set prevMinA = 0.
else
set prevMinA = MinAreaPerMeteor(al - 1)
endif
return MINR_CF + MINR_LF * al + MINR_PLF * prevMinA
endif
endfunction
private function MaxAreaPerMeteor takes integer al returns real
local real prevMaxA
static if MAXR_TABLE then
return LargeArea[al]
else
if al == 1 then
set prevMaxA = 0.
else
set prevMaxA = MaxAreaPerMeteor(al - 1)
endif
return MAXR_CF + MAXR_LF * al + MAXR_PLF * prevMaxA
endif
endfunction
private function GetSpellTargetXBounded takes nothing returns real
local real x = GetSpellTargetX()
if x < MIN_X then
return MIN_X + SAFETY_OFFSET
elseif x > MAX_X then
return MAX_X - SAFETY_OFFSET
else
return x
endif
endfunction
private function GetSpellTargetYBounded takes nothing returns real
local real y = GetSpellTargetY()
if y < MIN_Y then
return MIN_Y + SAFETY_OFFSET
elseif y > MAX_Y then
return MAX_Y - SAFETY_OFFSET
else
return y
endif
endfunction
private function MatchingUnit takes unit u,player owner returns boolean
return not( IsUnitType(u,UNIT_TYPE_DEAD) or/*
*/ IsUnitType(u,UNIT_TYPE_MAGIC_IMMUNE) or/*
*/( FRIENDLY and IsUnitAlly(u,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)))
endfunction
private function meteorDamageUnits takes player owner,unit caster,real X, real Y,real rad,real damage returns nothing
local unit target
call GroupEnumUnitsInRange(bj_lastCreatedGroup,X,Y,rad,null)
loop
set target = FirstOfGroup(bj_lastCreatedGroup)
exitwhen target == null
if MatchingUnit(target,owner) then
static if KILLS_EXPLODE then
call SetUnitExploded(target,true)
endif
call UnitDamageTarget(caster,target,damage,false,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_FIRE,null)
static if KILLS_EXPLODE then
call SetUnitExploded(target,false)
endif
endif
call GroupRemoveUnit(bj_lastCreatedGroup,target)
endloop
endfunction
/* this function/filter is not needed for now and is turned off
private function matchingDestructable takes nothing returns boolean
return GetDestructableTypeId(GetFilterDestructable()) == WHO_KNOWS_WHAT_TYPE
endfunction*/
private function meteorDamageDestructables takes nothing returns nothing
call UnitDamageTarget(CASTER,GetEnumDestructable(),DAMAGE,false,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_FIRE,null)
endfunction
private struct Meteor extends array
player owner
unit caster
unit target_indicator
unit meteor
real TX
real TY
real slope
real const
real face
real grandarea
real minarea
real maxarea
real mindamage
real maxdamage
integer meteors
integer ibem // Interval Between Each Meteor
integer cfm // Current Falling Meteors
static method cameraShake takes thistype this returns nothing
local integer i = 0
local real camX = GetCameraTargetPositionX()
local real camY = GetCameraTargetPositionY()
local real dx = this.TX - camX
local real dy = this.TY - camY
local real x1 = SquareRoot( dx*dx + dy*dy )
local real x2
local real magnitude
loop
exitwhen i >= index
set this = temp_this[i]
set dx = this.TX - camX
set dy = this.TY - camY
set x2 = SquareRoot( dx*dx + dy*dy )
if x1 > x2 then
set x1 = x2
endif
set i = i + 1
endloop
set magnitude = 20.0 * Pow(bj_E,- 0.001 * x1) // magnitude = 20*e^(-x)
call CameraSetSourceNoiseEx(magnitude,1000.,false)
call CameraSetTargetNoiseEx(magnitude,1000.,false)
if index == 0 then
call CameraSetSourceNoise(0, 0)
call CameraSetTargetNoise(0, 0)
endif
endmethod
implement CTL
local integer i = 0 // Instance Counter index
local integer j = 0 // Meteor Counter index
local unit u // This Dummy Unit
local unit nu // Next Dummy Unit
local real x // This Meteor X
local real y // This Meteor Y
local real z // This Meteor Z
local real ix // This Meteor Initial X
local real iy // This Meteor Initial Y
local real tx // This Meteor Target X
local real ty // This Meteor Target Y
local real dx // Delta X
local real dy // Delta Y
local real dv // XY Plane variable
local real face // This Meteor Facing
local real slope // This Meteor Fall Line Slope
local real const // This Meteor Fall Line Constant
local real ratio // This Meteor Scaling Ratio
local real ntx // Next Target X
local real nty // Next Target Y
local real nface // Next Facing
local real nix // Next Initial X
local real niy // Next Initial Y
local real niz // Next Initial Z
local real rnd_rad // Random Radius
local real rnd_ang // Random Angle
local real bound // Bound Size of Falling Meteor w.r.t Grand Area
local real scale // Next Meteor Scaling
local real nratio // Next Meteor Scaling Ratio
local real accurate_radius // Accurate Radius
local real accurate_damage // Accurate Damage
local rect effectrect // effective Rect for destructables
implement CTLExpire
set this.ibem = this.ibem - 1
if this.ibem <= 0 then
set this.ibem = GetRandomInt(INT_MIN,INT_MAX)
set this.cfm = this.cfm + 1
if this.cfm <= this.meteors then
// launch another meteor
set bound = this.grandarea - this.maxarea
loop
set rnd_rad = GetRandomReal(0.0,bound)
set rnd_ang = GetRandomReal(0.0,2.*bj_PI)
set ntx = this.TX + rnd_rad*Cos(rnd_ang)
set nty = this.TY + rnd_rad*Sin(rnd_ang)
exitwhen ntx > MIN_X and ntx < MAX_X and nty > MIN_Y and nty < MAX_Y
endloop
loop
set nface = GetRandomReal(0.0,2.*bj_PI)
set nix = ntx - INIT_DIST * Cos(nface)
set niy = nty - INIT_DIST * Sin(nface)
exitwhen nix > MIN_X and nix < MAX_X and niy > MIN_Y and niy < MAX_Y
endloop
set niz = GetRandomReal(H_MIN,H_MAX)
set nu = CreateUnit(this.owner,M_DUMMY_ID,nix,niy,nface*bj_RADTODEG)
call SetUnitPathing(nu,false)
set scale = GetRandomReal(SCALE_MIN,SCALE_MAX)
set nratio = (scale - SCALE_MIN)/(SCALE_MAX-SCALE_MIN)
call SetUnitScale(nu,scale,scale,scale)
call SetUnitFlyHeight(nu,niz,0.0)
call SaveUnitHandle(mht,this,this.cfm,nu)
call SaveBoolean(mht,this,this.cfm,false)
call SaveReal(mht, this, this.cfm,ntx)
call SaveReal(mht, this,-this.cfm,nty)
call SaveReal(mht,-this, this.cfm,nix)
call SaveReal(mht,-this,-this.cfm,niy)
call SaveReal(mht, this + HT_OFFSET, this.cfm,nface)
call SaveReal(mht, this + HT_OFFSET,-this.cfm,nratio)
call SaveReal(mht,-this - HT_OFFSET, this.cfm,-niz/INIT_DIST)
call SaveReal(mht,-this - HT_OFFSET,-this.cfm,niz)
endif
endif
set j = 1
loop
exitwhen j > this.cfm
set u = LoadUnitHandle(mht,this,j)
if not(LoadBoolean(mht,this,j)) then
set tx = LoadReal(mht, this, j)
set ty = LoadReal(mht, this,-j)
set ix = LoadReal(mht,-this, j)
set iy = LoadReal(mht,-this,-j)
set x = GetUnitX(u)
set y = GetUnitY(u)
set dx = tx - x
set dy = ty - y
set ratio = LoadReal(mht,this + HT_OFFSET,-j)
set accurate_radius = ratio * (this.maxarea - this.minarea ) + this.minarea
set accurate_damage = ratio * (this.maxdamage - this.mindamage) + this.mindamage
if SquareRoot( dx*dx + dy*dy ) < CHECK_DIST or GetUnitFlyHeight(u) < CHECK_DIST then
call KillUnit(u)
call SaveBoolean(mht,this,j,true)
// Shake camera
static if ALLOW_CAMERA_SHAKE then
call cameraShake(this)
endif
// Do Damage stuff
call meteorDamageUnits(this.owner,this.caster,tx,ty,accurate_radius,accurate_damage)
static if DESTRUCTABLES then
set CASTER = this.caster
set DAMAGE = accurate_damage/DESTRUCTABLE_FACTOR
set effectrect = Rect(tx - accurate_radius, ty - accurate_radius, tx + accurate_radius, ty + accurate_radius)
call EnumDestructablesInRect(effectrect,null,function meteorDamageDestructables)
call RemoveRect(effectrect)
endif
if IsUnitType(LoadUnitHandle(mht,this,this.meteors),UNIT_TYPE_DEAD) and/*
*/ LoadUnitHandle(mht,this,this.meteors) != null then
set index = index - 1
set temp_this[this] = temp_this[index]
call cameraShake(this)
static if SHOW_INDICATOR then
call RemoveUnit(this.target_indicator)
set this.target_indicator = null
endif
call this.destroy()
call FlushChildHashtable(mht, this)
call FlushChildHashtable(mht,-this)
call FlushChildHashtable(mht, this + HT_OFFSET)
call FlushChildHashtable(mht,-this - HT_OFFSET)
endif
else // Move the meteor
set face = LoadReal(mht,this + HT_OFFSET,j)
set slope = LoadReal(mht,-this - HT_OFFSET,j)
set const = LoadReal(mht,-this - HT_OFFSET,-j)
set x = x + STEP * Cos(face)
set y = y + STEP * Sin(face)
set dx = x - ix
set dy = y - iy
set dv = SquareRoot( dx*dx + dy*dy )
set z = slope * dv + const
call SetUnitX(u,x)
call SetUnitY(u,y)
call SetUnitFlyHeight(u,z,0.0)
endif
endif
set j = j + 1
endloop
implement CTLNull
set u = null
set nu = null
set effectrect = null
implement CTLEnd
static method meteorCall takes unit caster, real tx, real ty returns nothing
local thistype this = create()
local integer al = GetUnitAbilityLevel(caster,ABILITY_ID)
local unit u // Dummy Unit
local real cx // Cast Point X
local real cy // Cast Point Y
local real ix // First Meteor Initial X
local real iy // First Meteor Initial Y
local real iz // First Meteor Initial Z
local real dx
local real dy
local real face
local real scale
local real ratio
local real indicator_scale = 9. + I2R(al)
set temp_this[index] = this
set this.caster = caster
set this.owner = GetOwningPlayer(caster)
set this.TX = tx
set this.TY = ty
set cx = GetUnitX(caster)
set cy = GetUnitY(caster)
set dx = tx - cx
set dy = ty - cy
set face = Atan2(dy,dx)
set ix = tx - INIT_DIST * Cos(face)
set iy = ty - INIT_DIST * Sin(face)
set iz = GetRandomReal(H_MIN,H_MAX)
static if SHOW_INDICATOR then
set this.target_indicator = CreateUnit(this.owner,I_DUMMY_ID,tx,ty,0.0)
call SetUnitVertexColor(this.target_indicator, 255,255,255,255)
call SetUnitTimeScale(this.target_indicator,0.7)
call SetUnitScale(this.target_indicator,indicator_scale,indicator_scale,indicator_scale)
call SetUnitPathing(this.target_indicator,false)
call SetUnitX(this.target_indicator,tx)
call SetUnitY(this.target_indicator,ty)
endif
set u = CreateUnit(this.owner,M_DUMMY_ID,ix,iy,face*bj_RADTODEG)
call SetUnitPathing(u,false)
static if INIT_MAX_D then
set scale = SCALE_MAX
else
set scale = GetRandomReal(SCALE_MIN,SCALE_MAX)
endif
set ratio = (scale - SCALE_MIN)/(SCALE_MAX-SCALE_MIN)
call SetUnitScale(u,scale,scale,scale)
call SetUnitFlyHeight(u,iz,0.0)
set this.cfm = 1
set this.ibem = GetRandomInt(INT_MIN,INT_MAX)
call SaveUnitHandle(mht,this,1,u)
call SaveBoolean(mht,this,1,false)
call SaveReal(mht, this, 1,tx)
call SaveReal(mht, this,-1,ty)
call SaveReal(mht,-this, 1,ix)
call SaveReal(mht,-this,-1,iy)
call SaveReal(mht, this + HT_OFFSET, 1,face)
call SaveReal(mht, this + HT_OFFSET,-1,ratio)
call SaveReal(mht,-this - HT_OFFSET, 1,-iz/INIT_DIST)
call SaveReal(mht,-this - HT_OFFSET,-1,iz)
set this.meteors = MeteorsNumber(al)
set this.grandarea = AreaGrand(al)
set this.maxarea = MaxAreaPerMeteor(al)
set this.minarea = MinAreaPerMeteor(al)
set this.maxdamage = DamageMax(al)
set this.mindamage = DamageMin(al)
set u = null
set index = index + 1
endmethod
endstruct
private function MeteorSwarm_Conditions takes nothing returns boolean
if GetSpellAbilityId() == ABILITY_ID then
call Meteor.meteorCall(GetTriggerUnit(),GetSpellTargetXBounded(),GetSpellTargetYBounded())
endif
return false
endfunction
private function Init_MeteorSwarm takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( t, function MeteorSwarm_Conditions )
call RemoveUnit(CreateUnit(Player(12),M_DUMMY_ID,0.,0.,0.))
call RemoveUnit(CreateUnit(Player(12),I_DUMMY_ID,0.,0.,0.))
call Spell_Data_Table()
set MIN_X = GetCameraBoundMinX() - 512.000
set MIN_Y = GetCameraBoundMinY() - 256.000
set MAX_X = GetCameraBoundMaxX() + 512.000
set MAX_Y = GetCameraBoundMaxY() + 256.000
set t = null
endfunction
endscope