// Meteor Swarm Version 1.13
scope MS 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_dat
private constant integer INT_MIN = 1 // Minimum Interval Between Meteors Threshold
private constant integer INT_MAX = 10 // 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 SPEED = 0.01
private constant real STEP = 64.00
private constant real CHECK_DIST = 32.00
private boolean START_SHAKING = false
private constant integer HT_OFFSET = 4096
private hashtable mht = InitHashtable() // Meteor Hashtable
//private constant group damage_group = CreateGroup()
private constant timer tim = CreateTimer()
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 * I2R(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 targetindicator
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
implement Alloc
static method cameraShake takes nothing returns nothing
local integer i = 0
local thistype this = temp_dat[i]
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
if START_SHAKING then
loop
exitwhen i >= index
set this = temp_dat[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
set START_SHAKING = false
call PauseTimer(shake_timer)
call CameraSetSourceNoise(0, 0)
call CameraSetTargetNoise(0, 0)
endif
endif
endmethod
implement CTL
//local thistype this
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 i = 0
loop
exitwhen i >= index
set this = temp_dat[i]
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
set START_SHAKING = true
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)
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_dat[i] = temp_dat[index]
set i = i - 1
/*static*/ if SHOW_INDICATOR then
call RemoveUnit(this.targetindicator)
set this.targetindicator = null
endif
//call destroy()
call FlushChildHashtable(mht, this)
call FlushChildHashtable(mht,-this)
call FlushChildHashtable(mht, this + HT_OFFSET)
call FlushChildHashtable(mht,-this - HT_OFFSET)
if index == 0 then
//call PauseTimer(tim)
call destroy()
endif
//call this.deallocate()
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
set i = i + 1
endloop
implement CTLNull
set u = null
set nu = null
implement CTLEnd
static method meteorCall takes unit caster, real tx, real ty returns nothing
local thistype this = thistype.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 temp = 9. + I2R(al)
set temp_dat[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.targetindicator = CreateUnit(this.owner,I_DUMMY_ID,tx,ty,0.0)
call SetUnitVertexColor(this.targetindicator, 255,255,255,255)
call SetUnitTimeScale(this.targetindicator,0.7)
call SetUnitScale(this.targetindicator,temp,temp,temp)
call SetUnitPathing(this.targetindicator,false)
call SetUnitX(this.targetindicator,tx)
call SetUnitY(this.targetindicator,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)
if index == 0 then
//call TimerStart(tim,SPEED,true, function thistype.meteorFall )
call thistype.create()
/*static*/ if ALLOW_CAMERA_SHAKE then
//call TimerStart(shake_timer,0.04,true,function thistype.cameraShake)
endif
endif
set index = index + 1
set u = null
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