1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Melee Mapping contest #3 - Poll is up! Vote for the best 4v4 melee maps!
    Dismiss Notice
  3. The 30th edition of the Modeling Contest is finally up! The Portable Buildings need your attention, so come along and have a blast!
    Dismiss Notice
  4. We have a new contest going on right now! Join the 11th Music Contest! You are to make a Cinematic modern sound-track for this contest, so come and compete with other people for fun.
    Dismiss Notice

Cluster Rockets v1.0.7

Submitted by Ofel
This bundle is marked as approved. It works and satisfies the submission rules.
  • Description

Tinker deploys his rocket pads and fire all the rockets to any targeted unit or any units near the targeted location. Each rocket will explode if collide with any enemy units. The explosion causes damage to nearby units.
Level 1 - 16 rockets, 80 explosion damage.
Level 2 - 24 rockets, 90 explosion damage.
Level 3 - 32 rockets, 100 explosion damage.

Channeling
  • Triggers

Configuration (GUI)
  • Cluster Rockets Config
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- ============== --------
      • -------- Main configuration --------
      • -------- ============== --------
      • -------- --------------------------- --------
      • -------- Determines the ability data --------
      • Set ClsR_Ability = Cluster Rockets
      • -------- --------------------------- --------
      • -------- Determines ability string order --------
      • Set ClsR_AbilityOrder = (Order(firebolt))
      • -------- --------------------------- --------
      • -------- Determines dummies unit-type --------
      • Set ClsR_Dummy = Cluster Rockets Dummy
      • -------- --------------------------- --------
      • -------- Determines spell targeting type --------
      • Set ClsR_NoTarget = False
      • -------- ============== --------
      • -------- Other configuration --------
      • -------- ============== --------
      • -------- --------------------------- --------
      • -------- Determines the number of levels the ability has --------
      • Set ClsR_AbilityLevels = 3
      • -------- --------------------------- --------
      • -------- Determines the effected area range --------
      • Set ClsR_AreaOfEffect[1] = 450.00
      • Set ClsR_AreaOfEffect[2] = 450.00
      • Set ClsR_AreaOfEffect[3] = 450.00
      • -------- --------------------------- --------
      • -------- Determines the delay before launching the first wave --------
      • Set ClsR_FirstWaveDelay = 0.00
      • -------- --------------------------- --------
      • -------- Determines delay between each waves --------
      • Set ClsR_DelayPerWave = 1.20
      • -------- --------------------------- --------
      • -------- Determines rockets to be launched each wave --------
      • Set ClsR_RocketsPerWave = 8
      • -------- --------------------------- --------
      • -------- Determines how many rocket launches each second --------
      • Set ClsR_RocketsPerSecond = 16
      • -------- --------------------------- --------
      • -------- Determines rockets model --------
      • Set ClsR_RocketsModel = Abilities\Weapons\CannonTowerMissile\CannonTowerMissile.mdl
      • -------- --------------------------- --------
      • -------- Determines rockets size --------
      • Set ClsR_RocketsSize = 0.80
      • -------- --------------------------- --------
      • -------- Determines rockets collision size --------
      • Set ClsR_RocketsCollisionSize = 50.00
      • -------- --------------------------- --------
      • -------- Determines whether damaged units will explode on death or die normally --------
      • Set ClsR_MakeUnitExplode = False
      • -------- --------------------------- --------
      • -------- Determines rockets spawn offset --------
      • Set ClsR_RocketsSpawnOffset = 25.00
      • -------- --------------------------- --------
      • -------- Determines rockets spawn height offset --------
      • Set ClsR_RocketsSpawnHeightBonus = 125.00
      • -------- --------------------------- --------
      • -------- Determines rockets target height bonus --------
      • Set ClsR_RocketsUnitsHeightBonus = 64.00
      • -------- --------------------------- --------
      • -------- Determines rockets max facing angle --------
      • Set ClsR_RocketsMaxFacingVariation = 50.00
      • -------- --------------------------- --------
      • -------- Determines rockets min facing angle --------
      • Set ClsR_RocketsMinFacingVariation = 50.00
      • -------- --------------------------- --------
      • -------- Determines rockets max pitch angle --------
      • Set ClsR_RocketsMaxPitchVariation = 50.00
      • -------- --------------------------- --------
      • -------- Determines rockets min pitch angle --------
      • Set ClsR_RocketsMinPitchVariation = -20.00
      • -------- --------------------------- --------
      • -------- Determines rockets min (crash) height --------
      • Set ClsR_RocketsCrashHeight = 1.00
      • -------- --------------------------- --------
      • -------- Determines rockets expiration time --------
      • Set ClsR_RocketsExpirationTime = 5.00
      • -------- --------------------------- --------
      • -------- Determines whether the rockets will only hit target or any units --------
      • Set ClsR_HitTargetOnly = False
      • -------- --------------------------- --------
      • -------- Determines whether the rocket will target a unit rather than a single point on the map --------
      • Set ClsR_PreferTargetUnit = True
      • -------- --------------------------- --------
      • -------- Determines how the spell ends. By counting launched rockets or until channeling finished (Object Editor) --------
      • Set ClsR_CountRockets = True
      • -------- --------------------------- --------
      • -------- Determines how many rockets will be launched --------
      • Set ClsR_RocketsCount[1] = 16
      • Set ClsR_RocketsCount[2] = 24
      • Set ClsR_RocketsCount[3] = 32
      • -------- --------------------------- --------
      • -------- Determines damage dealt by each rocket --------
      • Set ClsR_DamagePerRocket[1] = 80.00
      • Set ClsR_DamagePerRocket[2] = 90.00
      • Set ClsR_DamagePerRocket[3] = 100.00
      • -------- --------------------------- --------
      • -------- Determines the damage radius of the rockets --------
      • Set ClsR_RocketsDamageRange[1] = 100.00
      • Set ClsR_RocketsDamageRange[2] = 100.00
      • Set ClsR_RocketsDamageRange[3] = 100.00
      • -------- --------------------------- --------
      • -------- Determines rocket attack-type --------
      • Set ClsR_RocketAttackType = Siege
      • -------- --------------------------- --------
      • -------- Determines rocket damage-type --------
      • Set ClsR_RocketDamageType = Normal
      • -------- --------------------------- --------
      • -------- Determines rockets base speed --------
      • Set ClsR_RocketsSpeedBase[1] = 800.00
      • Set ClsR_RocketsSpeedBase[2] = 800.00
      • Set ClsR_RocketsSpeedBase[3] = 800.00
      • -------- --------------------------- --------
      • -------- Determines rockets acceleration towards the target --------
      • Set ClsR_RocketsTurnAcceleration[1] = 112.00
      • Set ClsR_RocketsTurnAcceleration[2] = 112.00
      • Set ClsR_RocketsTurnAcceleration[3] = 112.00
      • -------- ============== --------
      • Custom script: call ExecuteFunc("ClsR_Initialize")

Core (JASS)
Code (vJASS):
//***************************************************************************
//*                                                                         *
//*                                                                         *
//*                     +----------------------------+                      *
//*                     |  Cluster Rockets [v1.0.7]  |                      *
//*                     |  by Ofel          [ClsR_]  |                      *
//*                     +----------------------------+                      *
//*                                                                         *
//*                                                                         *
//***************************************************************************

//***************************************************************************
//*
//*   Additional Configuration
//*

//---------------------------------------------------------------------------
// Preloading spell can reduce lag on the first use of the spell.
// true: Spell will be preloaded on initialization.
// false: Spell will not be preloaded.
constant function ClsR_PreloadSpell takes nothing returns boolean
    return true
endfunction

//---------------------------------------------------------------------------
// When a rocket hit the border of the map, the movement position will be
//   bounded to avoid game crash.
// true: Rocket will be bounded on the edge of entire map border
// false: Rocket will be bounded on the edge of playable map border
constant function ClsR_BoundsEntireMap takes nothing returns boolean
    return true
endfunction

//---------------------------------------------------------------------------
// Since dummies are unit, they are counted on the player scoreboard. So
//   it's better to create the dummies with Neutral Passive as their owner.
//   Shouldn't change this.
constant function ClsR_DummiesOwner takes nothing returns player
    return Player(PLAYER_NEUTRAL_PASSIVE)
endfunction

//---------------------------------------------------------------------------
// Making a non-flying unit able to fly (change height), by the default uses
//   the Crow ability raw ID.
//   In case the rockets doesn't fly, change this to any Crow-based ability.
constant function ClsR_FlyAbilityID takes nothing returns integer
    return 'Amrf'
endfunction

//---------------------------------------------------------------------------
// Spell update periodic timeout.
//   Please consider twice before changing this as it can affect many things
//   including performance.
//   32 iterations/frames per second is a recommendation.
constant function ClsR_FrameUpdate takes nothing returns real
    return 1 / 32.
endfunction

//---------------------------------------------------------------------------
// Range for scanning collision size of nearby units.
//   In other hand, this represents the maximum collision size the spell can
//   detect.
//   Wisely increase this if you think the rockets doesn't properly collide
//   with any units.
constant function ClsR_CollisionScanRange takes nothing returns real
    return 200.00
endfunction

//---------------------------------------------------------------------------
// Degree and radian convert formula.
//   There is no reason to change these numbers. But here you can change the
//   precision, and you'll get a different result.

constant function ClsR_Deg2Rad takes nothing returns real
    // From Windows calculator --> 0.01745329251994329576923690768489
    return 0.0174532
endfunction

constant function ClsR_Rad2Deg takes nothing returns real
    // From Windows calculator --> 57.295779513082320876798154814105
    return 57.2957795
endfunction

//---------------------------------------------------------------------------
//*
//*   Filteration
//*
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// Filteration for units that can be acquired as target by the rockets.
function ClsR_AcquisitionFilter takes unit filterUnit, player casterOwner returns boolean
    return IsUnitEnemy(filterUnit, casterOwner) and not IsUnitType(filterUnit, UNIT_TYPE_MAGIC_IMMUNE)
endfunction

//---------------------------------------------------------------------------
// Filteration for units that can trigger collision with the rockets.
function ClsR_CollisionFilter takes unit filterUnit, player casterOwner returns boolean
    return IsUnitEnemy(filterUnit, casterOwner)
endfunction

//---------------------------------------------------------------------------
// Filteration for units that can receive damage.
function ClsR_DamageExplosionFilter takes unit filterUnit, player casterOwner returns boolean
    return not IsUnitType(filterUnit, UNIT_TYPE_MAGIC_IMMUNE)
endfunction

//*
//*   End of Configuration
//*
//***************************************************************************

//---------------------------------------------------------------------------
// Function that tell if a unit is alive.
function ClsR_IsUnitAlive takes unit targetUnit returns boolean
    return GetUnitTypeId(targetUnit) != 0 and not IsUnitType(targetUnit, UNIT_TYPE_DEAD)
endfunction

//---------------------------------------------------------------------------
// Function that tell if a unit is channeling the spell.
function ClsR_IsUnitChanneling takes unit targetUnit returns boolean
    return GetUnitCurrentOrder(targetUnit) == udg_ClsR_AbilityOrder
endfunction

//---------------------------------------------------------------------------
// Function to get a bounded x.
function ClsR_BoundX takes real x returns real
    if x>udg_ClsR_mapBorder[1] then
        set x = udg_ClsR_mapBorder[1]
    elseif x<udg_ClsR_mapBorder[3] then
        set x = udg_ClsR_mapBorder[3]
    endif
 
    // Return bounded x
    return x
endfunction

//---------------------------------------------------------------------------
// Function to get a bounded y.
function ClsR_BoundY takes real y returns real
    if y>udg_ClsR_mapBorder[2] then
        set y = udg_ClsR_mapBorder[2]
    elseif y<udg_ClsR_mapBorder[4] then
        set y = udg_ClsR_mapBorder[4]
    endif
 
    // Return bounded y
    return y
endfunction

//---------------------------------------------------------------------------
// Function to get 2D distance between points - adapted from a vJASS library
//   by Nestharus.
function ClsR_GetMagnitude2D takes real x, real y returns real
    return SquareRoot(x*x+y*y)
endfunction

//---------------------------------------------------------------------------
// Function to get 3D distance between points - adapted from a vJASS library
//   by Nestharus.
function ClsR_GetMagnitude3D takes real x, real y, real z returns real
    return SquareRoot(x*x+y*y+z*z)
endfunction

//---------------------------------------------------------------------------
// Function to get 2D angle between points - adapted from a vJASS library by
//   Nestharus.
function ClsR_GetAngle2D takes real x, real y returns real
    return Atan2(y, x)
endfunction

//---------------------------------------------------------------------------
// Function to get 3D angle between points - adapted from a vJASS library by
//   Nestharus.
function ClsR_GetAngle3D takes real distance2D, real z returns real
    return Atan2(z, distance2D)
endfunction

//---------------------------------------------------------------------------
// Function to make a unit able to fly - adapted from a vJASS library by
//   TriggerHappy.
function ClsR_UnitApplyFly takes unit targetUnit returns nothing
    if UnitAddAbility(targetUnit, ClsR_FlyAbilityID()) and UnitRemoveAbility(targetUnit, ClsR_FlyAbilityID()) then
    endif
endfunction

//---------------------------------------------------------------------------
// Function to get unit collision size - adapted from a vJASS library by
//   Nestharus.
function ClsR_GetUnitCollisionSize takes unit targetUnit returns real
    local real x
    local real y
    local real l
    local real h
    local real m
    local real nm
    local integer id = GetUnitTypeId(targetUnit)
 
    if LoadBoolean(udg_ClsR_Hashtable, id, 10) then
        return LoadReal(udg_ClsR_Hashtable, id, 10)
    endif
 
    set x = GetUnitX(targetUnit)
    set y = GetUnitY(targetUnit)
    set l = 0
    set h = 300
    set m = 150
    loop
        if (IsUnitInRangeXY(targetUnit, x+m, y, 0)) then
            set l = m
        else
            set h = m
        endif
        set nm = (l+h)/2
        exitwhen nm+.001 > m and nm-.001 < m
        set m = nm
    endloop
 
    set m = (R2I(m*10)/10.)/2 // Take half for the radius
 
    call SaveReal(udg_ClsR_Hashtable, id, 10, m)
    call SaveBoolean(udg_ClsR_Hashtable, id, 10, true)
 
    return m
endfunction

//---------------------------------------------------------------------------
// Function to get terrain z - adapted from a vJASS library by D.O.G.
function ClsR_GetTerrainZ takes real x, real y returns real
    // Move Z location finder
    call MoveLocation(udg_ClsR_ZLocator, x, y)
 
    // Return location terrain z
    return GetLocationZ(udg_ClsR_ZLocator)
endfunction

//---------------------------------------------------------------------------
// Function to get (height + terrain z) of a unit alternatively.
function ClsR_GetUnitZAlt takes unit targetUnit, real x, real y returns real
    local real height
    if IsUnitType(targetUnit, UNIT_TYPE_FLYING) then
        set height = GetUnitFlyHeight(targetUnit)
    else
        // Add bonus height when unit is not a flying type
        set height = GetUnitFlyHeight(targetUnit) + udg_ClsR_RocketsUnitsHeightBonus
    endif
 
    // Return joined height and terrain z
    return height + ClsR_GetTerrainZ(x, y)
endfunction

//---------------------------------------------------------------------------
// Function to get 3D distance between a point and a unit.
function ClsR_GetDistanceToUnit3D takes real sourceX, real sourceY, real sz, unit targetUnit returns real
    local real targetX = GetUnitX(targetUnit)
    local real targetY = GetUnitY(targetUnit)
    local real targetZ = ClsR_GetUnitZAlt(targetUnit, targetX, targetY)
 
    // Return 3D distance
    return ClsR_GetMagnitude3D(targetX-sourceX, targetY-sourceY, targetZ-sz)
endfunction

//---------------------------------------------------------------------------
// Function to set a unit height by z value (new z - terrain z) of a unit.
function ClsR_SetUnitZAlt takes unit targetUnit, real z, real x, real y returns nothing
    local real surfaceZ = ClsR_GetTerrainZ(x, y)
    call SetUnitFlyHeight(targetUnit, z - surfaceZ, 0)
endfunction

//---------------------------------------------------------------------------
// Function to set a unit pitch angle - adapted from a vJASS library by
//   Vexorian.
function ClsR_SetUnitPitch takes unit targetUnit, real pitch returns nothing
    local integer i = R2I(pitch * ClsR_Rad2Deg() + 90.5)
    if (179 < i) then
        set i = 179
    elseif (0 > i) then
        set i = 0
    endif
    call SetUnitAnimationByIndex(targetUnit, i)
endfunction

//---------------------------------------------------------------------------
// Function to get new target in range (Area of Effect).
function ClsR_AcquireNewTarget takes real x, real y, integer index returns unit
    // Declare locals
    local unit enumUnit
    local integer i = 0
 
    // Null reference
    set udg_ClsR_Target[0] = null
 
    // Enumerate all units in range
    call GroupEnumUnitsInRange(udg_ClsR_tempGroup, x, y, udg_ClsR_AreaOfEffect[udg_ClsR_Level[index]], null)
    loop
        set enumUnit = FirstOfGroup(udg_ClsR_tempGroup)
        exitwhen enumUnit == null
     
        if enumUnit != udg_ClsR_Caster[index] and ClsR_IsUnitAlive(enumUnit) and ClsR_AcquisitionFilter(enumUnit, udg_ClsR_casterOwner[index]) then
            // Random target
            set i = i + 1
            if GetRandomInt(1, i) == 1 then
                set udg_ClsR_Target[0] = enumUnit
            endif
        endif
     
        call GroupRemoveUnit(udg_ClsR_tempGroup, enumUnit)
    endloop
 
    // Return new target
    return udg_ClsR_Target[0]
endfunction

//---------------------------------------------------------------------------
// Function to start a new instance and return new index for the instance.
function ClsR_Create takes nothing returns integer
    // Declare locals
    local integer newIndex = 0
 
    // Allocate index
    if udg_ClsR_Recyclable == 0 then
        set udg_ClsR_MaxIndex = udg_ClsR_MaxIndex + 1
        set newIndex = udg_ClsR_MaxIndex
    else
        set udg_ClsR_Recyclable = udg_ClsR_Recyclable - 1
        set newIndex = udg_ClsR_Recycle[udg_ClsR_Recyclable]
    endif
    set udg_ClsR_Next[newIndex] = 0
    set udg_ClsR_Next[udg_ClsR_Last] = newIndex
    set udg_ClsR_Prev[newIndex] = udg_ClsR_Last
    set udg_ClsR_Last = newIndex
 
    // Return new index
    return newIndex
endfunction

//---------------------------------------------------------------------------
// Function to finish an instance by index.
function ClsR_Destroy takes integer index returns nothing
 
    // Clean up
    set udg_ClsR_Caster[index] = null
    set udg_ClsR_Rocket[index] = null
    set udg_ClsR_Target[index] = null
    set udg_ClsR_casterOwner[index] = null
    set udg_ClsR_Stage[index] = 0
 
    // Recycle index
    if udg_ClsR_Last == index then
        set udg_ClsR_Last = udg_ClsR_Prev[index]
    endif
    set udg_ClsR_Recycle[udg_ClsR_Recyclable] = index
    set udg_ClsR_Recyclable = udg_ClsR_Recyclable + 1
    set udg_ClsR_Next[udg_ClsR_Prev[index]] = udg_ClsR_Next[index]
    set udg_ClsR_Prev[udg_ClsR_Next[index]] = udg_ClsR_Prev[index]
 
    if udg_ClsR_Next[0] == 0 then
        // --- When no active instances left
        call PauseTimer(udg_ClsR_Timer)
    endif
 
endfunction

//---------------------------------------------------------------------------
// Function to destroy rocket and damage nearby units.
function ClsR_TerminateRocket takes integer rocketIndex returns nothing
    // Declare locals
    local unit enumUnit
 
    // Enumerate all units near the rocket
    call GroupEnumUnitsInRange(udg_ClsR_tempGroup, udg_ClsR_posX[rocketIndex], udg_ClsR_posY[rocketIndex], udg_ClsR_RocketsDamageRange[udg_ClsR_Level[rocketIndex]]+ClsR_CollisionScanRange(), null)
    loop
        set enumUnit = FirstOfGroup(udg_ClsR_tempGroup)
        exitwhen enumUnit == null
     
        if ClsR_GetDistanceToUnit3D(udg_ClsR_posX[rocketIndex], udg_ClsR_posY[rocketIndex], udg_ClsR_posZ[rocketIndex], enumUnit)-ClsR_GetUnitCollisionSize(enumUnit) <= udg_ClsR_RocketsDamageRange[udg_ClsR_Level[rocketIndex]] then
         
            if enumUnit != udg_ClsR_Caster[rocketIndex] and ClsR_IsUnitAlive(enumUnit) and ClsR_DamageExplosionFilter(enumUnit, udg_ClsR_casterOwner[rocketIndex]) then
                // --- Exclude caster and dead units
                // Make unit explode on death
                call SetUnitExploded(enumUnit, udg_ClsR_MakeUnitExplode)
                // Damage unit
                call UnitDamageTarget(udg_ClsR_Caster[rocketIndex], enumUnit, udg_ClsR_DamagePerRocket[udg_ClsR_Level[rocketIndex]], true, false, udg_ClsR_RocketAttackType, udg_ClsR_RocketDamageType, WEAPON_TYPE_WHOKNOWS)
                // Make unit normal
                call SetUnitExploded(enumUnit, false)
            endif
         
        endif
     
        call GroupRemoveUnit(udg_ClsR_tempGroup, enumUnit)
    endloop
    call GroupClear(udg_ClsR_tempGroup)
 
    // Destroy attached dummy model
    call DestroyEffect(udg_ClsR_rocketModel[rocketIndex])
    call KillUnit(udg_ClsR_Rocket[rocketIndex])
 
    // End rocket instance
    call ClsR_Destroy(rocketIndex)
 
endfunction

//---------------------------------------------------------------------------
// Function to create and start a new rocket instance.
function ClsR_LaunchRocket takes integer casterIndex returns nothing
    // Declare locals
    local integer rocketIndex
    local real casterFacing = GetUnitFacing(udg_ClsR_Caster[casterIndex])
    local real casterFacingRad = casterFacing * ClsR_Deg2Rad()
    local real sourceX = GetUnitX(udg_ClsR_Caster[casterIndex]) + udg_ClsR_RocketsSpawnOffset * Cos(casterFacingRad) // Spawn x
    local real sourceY = GetUnitY(udg_ClsR_Caster[casterIndex]) + udg_ClsR_RocketsSpawnOffset * Sin(casterFacingRad) // Spawn y
    local real sourceZ = GetUnitFlyHeight(udg_ClsR_Caster[casterIndex]) + ClsR_GetTerrainZ(sourceX, sourceY) + udg_ClsR_RocketsSpawnHeightBonus // Spawn height + terrain z
    local real targetX
    local real targetY
    local real targetZ
    local real angleZ
    local real rocketFacing
    local real rocketFacingZ
 
    // Allocate new index
    set rocketIndex = ClsR_Create()
 
    // Note: Target type 0 - Instant (No target)
    //       Target type 1 - Unit target
    //       Target type 2 - Point target
    if udg_ClsR_targetType[casterIndex] == 0 then
     
        set udg_ClsR_Target[rocketIndex] = ClsR_AcquireNewTarget(udg_ClsR_posX[casterIndex], udg_ClsR_posY[casterIndex], casterIndex)
        if udg_ClsR_Target[rocketIndex] != null then
            set udg_ClsR_targetCollision[rocketIndex] = ClsR_GetUnitCollisionSize(udg_ClsR_Target[rocketIndex])
            set udg_ClsR_lastX[rocketIndex] = GetUnitX(udg_ClsR_Target[rocketIndex])
            set udg_ClsR_lastY[rocketIndex] = GetUnitY(udg_ClsR_Target[rocketIndex])
            set udg_ClsR_lastZ[rocketIndex] = ClsR_GetUnitZAlt(udg_ClsR_Target[rocketIndex], udg_ClsR_lastX[rocketIndex], udg_ClsR_lastY[rocketIndex])
        else
            set rocketFacing = GetRandomReal(-bj_PI, bj_PI)
            set udg_ClsR_AreaOfEffect[0] = GetRandomReal(0, udg_ClsR_AreaOfEffect[udg_ClsR_Level[casterIndex]])
            set udg_ClsR_lastX[rocketIndex] = udg_ClsR_posX[casterIndex] + udg_ClsR_AreaOfEffect[0] * Cos(rocketFacing)
            set udg_ClsR_lastY[rocketIndex] = udg_ClsR_posY[casterIndex] + udg_ClsR_AreaOfEffect[0] * Sin(rocketFacing)
            set udg_ClsR_lastZ[rocketIndex] = ClsR_GetTerrainZ(udg_ClsR_lastX[rocketIndex], udg_ClsR_lastY[rocketIndex])
        endif
     
    elseif udg_ClsR_targetType[casterIndex] == 1 then
     
        if ClsR_IsUnitAlive(udg_ClsR_Target[casterIndex]) then
            set udg_ClsR_Target[rocketIndex] = udg_ClsR_Target[casterIndex]
            set udg_ClsR_targetCollision[rocketIndex] = udg_ClsR_targetCollision[casterIndex]
            set udg_ClsR_lastX[rocketIndex] = GetUnitX(udg_ClsR_Target[rocketIndex])
            set udg_ClsR_lastY[rocketIndex] = GetUnitY(udg_ClsR_Target[rocketIndex])
            set udg_ClsR_lastZ[rocketIndex] = ClsR_GetUnitZAlt(udg_ClsR_Target[rocketIndex], udg_ClsR_lastX[rocketIndex], udg_ClsR_lastY[rocketIndex])
        else
            set udg_ClsR_lastX[rocketIndex] = udg_ClsR_posX[casterIndex]
            set udg_ClsR_lastY[rocketIndex] = udg_ClsR_posY[casterIndex]
            set udg_ClsR_lastZ[rocketIndex] = udg_ClsR_posZ[casterIndex]
        endif
     
    elseif udg_ClsR_targetType[casterIndex] == 2 and udg_ClsR_AreaOfEffect[udg_ClsR_Level[casterIndex]] > 0 then
     
        if udg_ClsR_PreferTargetUnit then
            set udg_ClsR_Target[rocketIndex] = ClsR_AcquireNewTarget(udg_ClsR_posX[casterIndex], udg_ClsR_posY[casterIndex], casterIndex)
        endif
        if udg_ClsR_Target[rocketIndex] != null then
            set udg_ClsR_targetCollision[rocketIndex] = ClsR_GetUnitCollisionSize(udg_ClsR_Target[rocketIndex])
            set udg_ClsR_lastX[rocketIndex] = GetUnitX(udg_ClsR_Target[rocketIndex])
            set udg_ClsR_lastY[rocketIndex] = GetUnitY(udg_ClsR_Target[rocketIndex])
            set udg_ClsR_lastZ[rocketIndex] = ClsR_GetUnitZAlt(udg_ClsR_Target[rocketIndex], udg_ClsR_lastX[rocketIndex], udg_ClsR_lastY[rocketIndex])
        else
            set udg_ClsR_lastX[rocketIndex] = udg_ClsR_posX[casterIndex]
            set udg_ClsR_lastY[rocketIndex] = udg_ClsR_posY[casterIndex]
            set udg_ClsR_lastZ[rocketIndex] = udg_ClsR_posZ[casterIndex]
        endif
     
    endif
 
    // Copy properties to local reference variables
    set targetX = udg_ClsR_lastX[rocketIndex]
    set targetY = udg_ClsR_lastY[rocketIndex]
    set targetZ = udg_ClsR_lastZ[rocketIndex]
 
    // Get new 3D angles
    set angleZ = ClsR_GetAngle3D(ClsR_GetMagnitude2D(targetX-sourceX, targetY-sourceY), targetZ-sourceZ)
    set rocketFacing = GetRandomReal(casterFacing-udg_ClsR_RocketsMinFacingVariation, casterFacing+udg_ClsR_RocketsMaxFacingVariation)
    set rocketFacingZ = GetRandomReal(angleZ-udg_ClsR_RocketsMinPitchVariation, angleZ+udg_ClsR_RocketsMaxPitchVariation)
 
    set udg_ClsR_Rocket[rocketIndex] = CreateUnit(ClsR_DummiesOwner(), udg_ClsR_Dummy, sourceX, sourceY, rocketFacing)
 
    call ClsR_UnitApplyFly(udg_ClsR_Rocket[rocketIndex])
    call ClsR_SetUnitPitch(udg_ClsR_Rocket[rocketIndex], rocketFacingZ)
    call ClsR_SetUnitZAlt(udg_ClsR_Rocket[rocketIndex], sourceZ, sourceX, sourceY)
    call SetUnitScale(udg_ClsR_Rocket[rocketIndex], udg_ClsR_RocketsSize, 0, 0)
 
    set udg_ClsR_rocketModel[rocketIndex] = AddSpecialEffectTarget(udg_ClsR_RocketsModel, udg_ClsR_Rocket[rocketIndex], "origin")
    set udg_ClsR_Facing[rocketIndex] = rocketFacing*ClsR_Deg2Rad()
    set udg_ClsR_FacingZ[rocketIndex] = rocketFacingZ
    set udg_ClsR_posX[rocketIndex] = sourceX
    set udg_ClsR_posY[rocketIndex] = sourceY
    set udg_ClsR_posZ[rocketIndex] = sourceZ
    set udg_ClsR_Caster[rocketIndex] = udg_ClsR_Caster[casterIndex]
    set udg_ClsR_casterOwner[rocketIndex] = udg_ClsR_casterOwner[casterIndex]
    set udg_ClsR_Level[rocketIndex] = udg_ClsR_Level[casterIndex]
    // Set up rocket expiration timer
    set udg_ClsR_realTimer[rocketIndex] = udg_ClsR_RocketsExpirationTime
 
    // Stage 2 - Handle rockets movement
    set udg_ClsR_Stage[rocketIndex] = 2
 
    // Update launched rockets counter
    set udg_ClsR_Counter[casterIndex] = udg_ClsR_Counter[casterIndex] - 1
 
endfunction

//---------------------------------------------------------------------------
// Function to periodically loop all active instances.
function ClsR_Periodic takes nothing returns nothing
    // Declare locals
    local real sourceX
    local real sourceY
    local real sourceZ
    local real targetX
    local real targetY
    local real targetZ
    local real tempX
    local real tempY
    local real tempZ
    local real dist
    local real dist3D
    local real angle
    local real angleZ
    local unit enumUnit
    local integer casterHandleID
    local integer index = 0
 
    // Loop all instances
    loop
        set index = udg_ClsR_Next[index]
        exitwhen index == 0
     
        // Stage 1: Handle spell channeling, launches x rockets each seconds
        if udg_ClsR_Stage[index] == 1 then
         
            if ClsR_IsUnitAlive(udg_ClsR_Caster[index]) then
             
                if ClsR_IsUnitChanneling(udg_ClsR_Caster[index]) and udg_ClsR_Counter[index] != 0 then
                    // Update wave interval timer
                    set udg_ClsR_realTimer[index] = udg_ClsR_realTimer[index] - ClsR_FrameUpdate()
                    if udg_ClsR_realTimer[index] <= 0 then
                        // Update rocket launch interval timer
                        set udg_ClsR_realTimer2[index] = udg_ClsR_realTimer2[index] + ClsR_FrameUpdate()
                        if udg_ClsR_realTimer2[index] >= udg_ClsR_RocketsInterval then
                            // Launch rocket
                            call ClsR_LaunchRocket(index)
                            // Reset rocket launch timer
                            set udg_ClsR_realTimer2[index] = 0
                         
                            set udg_ClsR_Counter2[index] = udg_ClsR_Counter2[index] + 1
                            if udg_ClsR_Counter2[index] >= udg_ClsR_RocketsPerWave then
                                // Reset wave timer
                                set udg_ClsR_realTimer[index] = udg_ClsR_DelayPerWave
                                // Reset rocket-per-wave counter
                                set udg_ClsR_Counter2[index] = 0
                            endif
                         
                        endif
                     
                    endif
                else
                    // --- When caster finished channeling or all rockets has launched
                    set casterHandleID = GetHandleId(udg_ClsR_Caster[index])
                    // Unmark caster from 'channeling' state
                    call SaveBoolean(udg_ClsR_Hashtable, casterHandleID, 1, false)
                    if not LoadBoolean(udg_ClsR_Hashtable, casterHandleID, 0) and ClsR_IsUnitChanneling(udg_ClsR_Caster[index]) and udg_ClsR_Counter[index] == 0 then
                        call IssueImmediateOrderById(udg_ClsR_Caster[index], 851972)
                    else
                        call SaveBoolean(udg_ClsR_Hashtable, casterHandleID, 0, false)
                    endif
                    call ClsR_Destroy(index)
                endif
            else
                // --- When caster died
                // Unmark caster from 'channeling' state
                call SaveBoolean(udg_ClsR_Hashtable, GetHandleId(udg_ClsR_Caster[index]), 1, false)
                call ClsR_Destroy(index)
            endif
     
        // Stage 2: Handle rockets, determines movement
        elseif udg_ClsR_Stage[index] == 2 then
         
            if udg_ClsR_realTimer[index] > 0 then
                // Update rocket expiration timer
                set udg_ClsR_realTimer[index] = udg_ClsR_realTimer[index] - ClsR_FrameUpdate()
             
                // Copy 3D position to local reference variables
                set sourceX = udg_ClsR_posX[index]
                set sourceY = udg_ClsR_posY[index]
                set sourceZ = udg_ClsR_posZ[index]
             
                // Check if there's a target unit to pursue
                if udg_ClsR_Target[index] != null and ClsR_IsUnitAlive(udg_ClsR_Target[index]) then
                    set targetX = GetUnitX(udg_ClsR_Target[index])
                    set targetY = GetUnitY(udg_ClsR_Target[index])
                    set targetZ = ClsR_GetUnitZAlt(udg_ClsR_Target[index], targetX, targetY)
                 
                    // Save target last known position
                    set udg_ClsR_lastX[index] = targetX
                    set udg_ClsR_lastY[index] = targetY
                    set udg_ClsR_lastZ[index] = targetZ
                else
                    // If target has disappear, use the last known position
                    set targetX = udg_ClsR_lastX[index]
                    set targetY = udg_ClsR_lastY[index]
                    set targetZ = udg_ClsR_lastZ[index]
                endif
             
                // Calculate difference
                set tempX = targetX - sourceX
                set tempY = targetY - sourceY
                set tempZ = targetZ - sourceZ
             
                if udg_ClsR_Target[index] != null and ClsR_IsUnitAlive(udg_ClsR_Target[index]) and ClsR_GetMagnitude3D(tempX, tempY, tempZ)-udg_ClsR_RocketsCollisionSize <= udg_ClsR_targetCollision[index] then
                    // --- When target is alive and is in collision range
                    call ClsR_TerminateRocket(index)
                else
                    set dist = ClsR_GetMagnitude2D(tempX, tempY)
                    set angle = ClsR_GetAngle2D(tempX, tempY)
                    set angleZ = ClsR_GetAngle3D(dist, tempZ)
                 
                    // Get new 3D position
                    set targetX = sourceX + udg_ClsR_RocketsSpeedBase[udg_ClsR_Level[index]] * Cos(udg_ClsR_Facing[index]) * Cos(udg_ClsR_FacingZ[index])
                    set targetY = sourceY + udg_ClsR_RocketsSpeedBase[udg_ClsR_Level[index]] * Sin(udg_ClsR_Facing[index]) * Cos(udg_ClsR_FacingZ[index])
                    set targetZ = sourceZ + udg_ClsR_RocketsSpeedBase[udg_ClsR_Level[index]] * Sin(udg_ClsR_FacingZ[index])
                    set udg_ClsR_posX[index] = targetX + udg_ClsR_RocketsTurnAcceleration[udg_ClsR_Level[index]] * Cos(angle) * Cos(angleZ)
                    set udg_ClsR_posY[index] = targetY + udg_ClsR_RocketsTurnAcceleration[udg_ClsR_Level[index]] * Sin(angle) * Cos(angleZ)
                    set udg_ClsR_posZ[index] = targetZ + udg_ClsR_RocketsTurnAcceleration[udg_ClsR_Level[index]] * Sin(angleZ)
                 
                    // Calculate difference
                    set tempX = udg_ClsR_posX[index] - sourceX
                    set tempY = udg_ClsR_posY[index] - sourceY
                    set tempZ = udg_ClsR_posZ[index] - sourceZ
                 
                    // Get new 3D angle
                    set udg_ClsR_Facing[index] = ClsR_GetAngle2D(tempX, tempY)
                    set udg_ClsR_FacingZ[index] = ClsR_GetAngle3D(ClsR_GetMagnitude2D(tempX, tempY), tempZ)
                 
                    // Get bounded coordinates
                    set udg_ClsR_posX[0] = ClsR_BoundX(udg_ClsR_posX[index])
                    set udg_ClsR_posY[0] = ClsR_BoundX(udg_ClsR_posY[index])
                 
                    // Apply new position
                    call SetUnitX(udg_ClsR_Rocket[index], udg_ClsR_posX[0])
                    call SetUnitY(udg_ClsR_Rocket[index], udg_ClsR_posY[0])
                    call ClsR_SetUnitZAlt(udg_ClsR_Rocket[index], udg_ClsR_posZ[index], udg_ClsR_posX[0], udg_ClsR_posY[0])
                 
                    // Apply new angle
                    call SetUnitFacing(udg_ClsR_Rocket[index], udg_ClsR_Facing[index]*ClsR_Rad2Deg())
                    call ClsR_SetUnitPitch(udg_ClsR_Rocket[index], udg_ClsR_FacingZ[index])
                 
                    if GetUnitFlyHeight(udg_ClsR_Rocket[index]) <= udg_ClsR_RocketsCrashHeight then
                        // --- When rocket touch the ground
                        call ClsR_TerminateRocket(index)
                    elseif not udg_ClsR_HitTargetOnly then
                        // Enumerate all units near the rocket
                        call GroupEnumUnitsInRange(udg_ClsR_tempGroup, udg_ClsR_posX[index], udg_ClsR_posY[index], udg_ClsR_RocketsCollisionSize+ClsR_CollisionScanRange(), null)
                        loop
                            set enumUnit = FirstOfGroup(udg_ClsR_tempGroup)
                            exitwhen enumUnit == null
                         
                            // Check if the rocket has hit a unit
                            if ClsR_GetDistanceToUnit3D(udg_ClsR_posX[index], udg_ClsR_posY[index], udg_ClsR_posZ[index], enumUnit)-udg_ClsR_RocketsCollisionSize <= ClsR_GetUnitCollisionSize(enumUnit) then
                             
                                if enumUnit != udg_ClsR_Caster[index] and ClsR_IsUnitAlive(enumUnit) and ClsR_CollisionFilter(enumUnit, udg_ClsR_casterOwner[index]) then
                                    // --- Exclude caster and dead units
                                    // --- When the rocket collide with filtered units
                                    call ClsR_TerminateRocket(index)
                                 
                                    set enumUnit = null
                                    exitwhen true
                                endif
                             
                            endif
                         
                            call GroupRemoveUnit(udg_ClsR_tempGroup, enumUnit)
                        endloop
                        call GroupClear(udg_ClsR_tempGroup)
                     
                    endif
                 
                endif
            else
                // --- When rocket expiration timer finished
                call ClsR_TerminateRocket(index)
            endif
         
        endif
     
    endloop
 
endfunction

//---------------------------------------------------------------------------
// Function to find any unit that casts the spell.
function ClsR_onEffect takes nothing returns boolean
    // Declare locals
    local integer casterIndex
    local integer casterHandleID
    local unit targetUnit
    local real targetX
    local real targetY
 
    if GetSpellAbilityId() == udg_ClsR_Ability then
        set udg_ClsR_Caster[0] = GetTriggerUnit()
     
        set casterIndex = ClsR_Create()
        set casterHandleID = GetHandleId(udg_ClsR_Caster[0])
     
        call SaveBoolean(udg_ClsR_Hashtable, casterHandleID, 0, false)
        // Mark caster as 'channeling'
        call SaveBoolean(udg_ClsR_Hashtable, casterHandleID, 1, true)
        // Save index to end this instance when the caster starts another one
        call SaveInteger(udg_ClsR_Hashtable, casterHandleID, 1, casterIndex)
     
        set udg_ClsR_Caster[casterIndex] = udg_ClsR_Caster[0]
        set udg_ClsR_casterOwner[casterIndex] = GetTriggerPlayer()
        set udg_ClsR_Level[casterIndex] = GetUnitAbilityLevel(udg_ClsR_Caster[casterIndex], udg_ClsR_Ability)
     
        set targetUnit = GetSpellTargetUnit()
        // Note: When target type in Object Editor is set to "Instant
        //   (No Target)", GetSpellTargetUnit returns the caster.
        if targetUnit == udg_ClsR_Caster[0] then
            set udg_ClsR_targetType[casterIndex] = 0
            set udg_ClsR_posX[casterIndex] = GetUnitX(udg_ClsR_Caster[0])
            set udg_ClsR_posY[casterIndex] = GetUnitY(udg_ClsR_Caster[0])
        elseif targetUnit != null then
            set udg_ClsR_targetType[casterIndex] = 1
            set udg_ClsR_Target[casterIndex] = targetUnit
            set udg_ClsR_targetCollision[casterIndex] = ClsR_GetUnitCollisionSize(targetUnit)
            set udg_ClsR_posX[casterIndex] = GetUnitX(targetUnit)
            set udg_ClsR_posY[casterIndex] = GetUnitY(targetUnit)
            set udg_ClsR_posZ[casterIndex] = ClsR_GetUnitZAlt(targetUnit, udg_ClsR_posX[casterIndex], udg_ClsR_posY[casterIndex])
        else
            set udg_ClsR_targetType[casterIndex] = 2
            set udg_ClsR_Target[casterIndex] = null
            set udg_ClsR_posX[casterIndex] = GetSpellTargetX()
            set udg_ClsR_posY[casterIndex] = GetSpellTargetY()
            set udg_ClsR_posZ[casterIndex] = ClsR_GetTerrainZ(udg_ClsR_posX[casterIndex], udg_ClsR_posY[casterIndex])
        endif
        set targetUnit = null
     
        // Set up wave interval timer
        set udg_ClsR_realTimer[casterIndex] = udg_ClsR_FirstWaveDelay - ClsR_FrameUpdate()
        // Set up rocket launch interval timer
        set udg_ClsR_realTimer2[casterIndex] = ClsR_FrameUpdate()
     
        if udg_ClsR_CountRockets then
            // Set up launched rockets counter
            set udg_ClsR_Counter[casterIndex] = udg_ClsR_RocketsCount[udg_ClsR_Level[casterIndex]]
        else
            // Give a signal not to count launched rockets
            set udg_ClsR_Counter[casterIndex] = -1
        endif
        // Set up rocket-per-wave counter
        set udg_ClsR_Counter2[casterIndex] = 0
     
        // Stage 1 - Handle spell channeling, launches rockets
        set udg_ClsR_Stage[casterIndex] = 1
     
        if udg_ClsR_Prev[casterIndex] == 0 then
            // --- When no instances ever activated
            call TimerStart(udg_ClsR_Timer, ClsR_FrameUpdate(), true, function ClsR_Periodic)
        endif
    endif
 
    return false
endfunction

//---------------------------------------------------------------------------
// Function to pre-cast the spell.
function ClsR_PrecastSpell takes integer casterHandleID returns nothing
    // End previous spell channeling instance
    set udg_ClsR_Counter[LoadInteger(udg_ClsR_Hashtable, casterHandleID, 1)] = 0
    //call ClsR_Destroy(LoadInteger(udg_ClsR_Hashtable, casterHandleID, 1))
 
    // Mark unit before it starts casting the spell
    // Note: Since the caster will be ordered to 'stop' when launched rockets
    //   reaches a limit, this one will helps ignore that action when the
    //   caster start another instance.
    call SaveBoolean(udg_ClsR_Hashtable, casterHandleID, 0, true)
endfunction

//---------------------------------------------------------------------------
// Function to find any unit that is about to cast the spell (No target).
function ClsR_onCast takes nothing returns boolean
    // Declare locals
    local integer casterHandleID
 
    if GetSpellAbilityId() == udg_ClsR_Ability then
        set casterHandleID = GetHandleId(GetTriggerUnit())
     
        // Check if the caster is already channeling the spell
        if LoadBoolean(udg_ClsR_Hashtable, casterHandleID, 1) then
            call ClsR_PrecastSpell(casterHandleID)
        endif
    endif
 
    return false
endfunction

//---------------------------------------------------------------------------
// Function to find any unit that is about to cast the spell (Unit or point
//   target).
function ClsR_onOrder takes nothing returns boolean
    // Declare locals
    local integer casterHandleID
 
    if GetUnitCurrentOrder(GetTriggerUnit()) == udg_ClsR_AbilityOrder then
        // Note: This function is also executed when a caster learn the spell
        //   while channeling.
        set casterHandleID = GetHandleId(GetTriggerUnit())
     
        // Check if the caster is already channeling the spell
        if LoadBoolean(udg_ClsR_Hashtable, casterHandleID, 1) then
            call ClsR_PrecastSpell(casterHandleID)
        endif
    endif
 
    return false
endfunction

//---------------------------------------------------------------------------
// Function to initialize spell properties.
function ClsR_Initialize takes nothing returns nothing
    local integer i = 0
    local trigger trig = CreateTrigger()
    local rect map
 
    if ClsR_BoundsEntireMap() then
        set map = GetWorldBounds()
    else
        set map = bj_mapInitialPlayableArea
    endif
    set udg_ClsR_mapBorder[1] = GetRectMaxX(map)
    set udg_ClsR_mapBorder[2] = GetRectMaxY(map)
    set udg_ClsR_mapBorder[3] = GetRectMinX(map)
    set udg_ClsR_mapBorder[4] = GetRectMinY(map)
    call RemoveRect(map)
 
    // Convert pitch angle from degree to radian
    set udg_ClsR_RocketsMaxPitchVariation = udg_ClsR_RocketsMaxPitchVariation * ClsR_Deg2Rad()
    set udg_ClsR_RocketsMinPitchVariation = udg_ClsR_RocketsMinPitchVariation * ClsR_Deg2Rad()
 
    set udg_ClsR_RocketsInterval = 1 / I2R(udg_ClsR_RocketsPerSecond)
 
    // Initialize additional properties
    loop
        set i = i + 1
        exitwhen i > udg_ClsR_AbilityLevels
     
        set udg_ClsR_RocketsSpeedBase[i] = udg_ClsR_RocketsSpeedBase[i] * ClsR_FrameUpdate()
        set udg_ClsR_RocketsTurnAcceleration[i] = udg_ClsR_RocketsTurnAcceleration[i] * ClsR_FrameUpdate()
    endloop
 
    // Initialize terrain z finder
    if udg_ClsR_ZLocator == null then
        set udg_ClsR_ZLocator = Location(0, 0)
    endif
    // Initialize spell hashtable
    if udg_ClsR_Hashtable == null then
        set udg_ClsR_Hashtable = InitHashtable()
    endif
    // Initialize iteration timer
    if udg_ClsR_Timer == null then
        set udg_ClsR_Timer = CreateTimer()
    endif
    // Initialize dummy group
    if udg_ClsR_tempGroup == null then
        set udg_ClsR_tempGroup = CreateGroup()
    endif
 
    if ClsR_PreloadSpell() then
        // Preload files
        call Preload(udg_ClsR_RocketsModel)
        set udg_ClsR_Rocket[0] = CreateUnit(ClsR_DummiesOwner(), udg_ClsR_Dummy, 0, 0, 0)
        call UnitAddAbility(udg_ClsR_Rocket[0], udg_ClsR_Ability)
        call KillUnit(udg_ClsR_Rocket[0])
    endif
 
    if udg_ClsR_NoTarget then
        // Register a trigger for when a unit cast the spell
        call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_CAST)
        call TriggerAddCondition(trig, Condition(function ClsR_onCast))
    else
        // Register a trigger for when a unit is ordered to cast the spell
        call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
        call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
        call TriggerAddCondition(trig, Condition(function ClsR_onOrder))
    endif
endfunction

//===========================================================================
// This function automatically runs on map initialization.
function InitTrig_Cluster_Rockets takes nothing returns nothing
    set gg_trg_Cluster_Rockets = CreateTrigger()
 
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Cluster_Rockets, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_Cluster_Rockets, Condition(function ClsR_onEffect))
endfunction

 

  • Changelog

--- v1.0.0 ---
First public release version.

--- v1.0.1 ---
Optimized scripts based on some review.

--- v1.0.2 ---
Optimized scripts and fixed a small leak based on reviews.

--- v1.0.3 ---
Optimized scripts and a little performance fix based on review.
Changed tooltip and hotkey.
New ability icon.

--- v1.0.4 ---
Fixed one fatal problem: spell freezes due to bad indexing.

--- v1.0.5 ---
Fixed another fatal problem: can't cast at the center of map.

--- v1.0.6 ---
Optimized unit target acquiring.

--- v1.0.7 ---
Fixed bug: rockets stop when casting rapidly.​

  • Credits

Some of the code are adapted from vJASS libraries. Credits to Vexorian, Nestharus, D.O.G, and TriggerHappy.
Icon by darkdeathknight.​

  • Other

You can find importing instruction and some Q&A inside the map.
Thanks also to Tank-Commander, Almia, Meatmuffin, BPower, Kam and ILH for their reviews that helped me discover things need to be fixed.​

Keywords:
Rocket, Explosive, Collision, Cluster.
Previews
Contents

Cluster Rockets (Map)

Reviews
Moderator
11:09, 31st May 2016 Tank-Commander: Thanks for the great submission, just have a few notes on some issues. See my post
  1. 11:09, 31st May 2016
    Tank-Commander: Thanks for the great submission, just have a few notes on some issues. See my post
     
  2. Tank-Commander

    Tank-Commander

    Spell Reviewer

    Joined:
    May 26, 2009
    Messages:
    1,547
    Resources:
    44
    Packs:
    1
    Spells:
    41
    Tutorials:
    2
    Resources:
    44
    Just went over very quickly (haven't quite finished going through)

    - You never flush your hashtable
    - Your check unit exists function is flawed: a unit can have x and y co-ordinates of 0 and still exist albeit very unlikely, you can check if the UnitTypeId is null (which will be the case if it doesn't exist)
    - This is a very weird hybrid of using a Linked List and a Hashtable, why?
    - You only need one initialisation function, they can be combined
     
  3. Almia

    Almia

    Joined:
    Apr 24, 2012
    Messages:
    4,839
    Resources:
    35
    Spells:
    30
    Tutorials:
    4
    JASS:
    1
    Resources:
    35
    GetWidgetLife cannot detect whether or not a unit(widget) is alive in some circumstances.
    You can just check if the UnitTypeId is 0

    Check whether the unit exist in the map should be removed. It is just equal to UnitAlive

    FlyAbilityID is not recommended to become a constant. Users might change it and break the spell :V
     set rFacing = GetRandomReal(0, 360) * bj_DEGTORAD

    ->
     set rFacing = GetRandomReal(-bj_PI, bj_PI) 


    As for Tank-Commander's comment
    - The reason his hashtable is not flushed is because it is being used for unit collision values. But I think the keys used for channeling check shall be flushed.

    - " This is a very weird hybrid of using a Linked List and a Hashtable, why?" Hashtable was used for collision check(Again) and for channeling check. He doesn't use a Unit Indexer so he doesn't want to do a linear search for it :V

    Overall, I liked it :D

    I should test this tomorrow

    opinion : I think you should use the Barrage missile model :V
     
  4. Ofel

    Ofel

    Joined:
    Mar 29, 2012
    Messages:
    406
    Resources:
    10
    Spells:
    10
    Resources:
    10
    You mean the custom script inside the configuration?
    Nah, I think it's not neat :3
    It's also made for if the user want to reconfigure the spell for some reason, they can just put it at the end of their new configuration (that's the bonus maybe).

    Done.

    I was thinking that in worst case, that the default crow ability has been somehow edited. So the user can use this to rescue the spell. *\o/*

    Forgot this one. Done.

    It's just boolean and integer. Right?
     
  5. Almia

    Almia

    Joined:
    Apr 24, 2012
    Messages:
    4,839
    Resources:
    35
    Spells:
    30
    Tutorials:
    4
    JASS:
    1
    Resources:
    35
    you need to flush it for the sake of leaks i guess
     
  6. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,746
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    You leak a local group handle in function ClsR_AcquireNewTarget also in function ClsR_TerminateRocket. and in the periodic function.

    You eventually leak a local unit when you exit the FoG iteration once a proper
    target unit was found.

    Very cool spell.
     
  7. Ofel

    Ofel

    Joined:
    Mar 29, 2012
    Messages:
    406
    Resources:
    10
    Spells:
    10
    Resources:
    10
    I did destroy the group below
    endloop
    .

    But I thought local unit is already nulled when FoG return null, isn't?
     
  8. Meatmuffin

    Meatmuffin

    Joined:
    Jul 25, 2014
    Messages:
    447
    Resources:
    9
    Maps:
    2
    Spells:
    7
    Resources:
    9
    It is not when you exit the group with
    exitwhen true


    Set the unit to null before you do that.
     
  9. Ofel

    Ofel

    Joined:
    Mar 29, 2012
    Messages:
    406
    Resources:
    10
    Spells:
    10
    Resources:
    10
    That! I missed.
    Gonna fix soon.

    Thanks.
    Should I null both local group and unit?
     
  10. Meatmuffin

    Meatmuffin

    Joined:
    Jul 25, 2014
    Messages:
    447
    Resources:
    9
    Maps:
    2
    Spells:
    7
    Resources:
    9
    I doubt it, but I prefer to use global variables when it comes to unit groups
    instead of local ones -- they are just more reliable and you don't have to
    create/destroy them, you just refer to them when you're using GroupEnum.
     
  11. Ofel

    Ofel

    Joined:
    Mar 29, 2012
    Messages:
    406
    Resources:
    10
    Spells:
    10
    Resources:
    10
    Yup. I might try to save as many locals as possible next time.
     
  12. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,746
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    It's true that the FoG loop automatically nulls the local unit handle,
    if exitwhen u == null is the exit condition. Otherwise not.

    Yes you should null the local group handle.
     
  13. Dat-C3

    Dat-C3

    Joined:
    Mar 15, 2012
    Messages:
    2,436
    Resources:
    10
    Models:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    1
    Resources:
    10
    Very cool, way better then the default cluster rockets.

    4/5 - Will raise my rating to 5/5 after I finish reading through the triggers. =)
     
  14. Tank-Commander

    Tank-Commander

    Spell Reviewer

    Joined:
    May 26, 2009
    Messages:
    1,547
    Resources:
    44
    Packs:
    1
    Spells:
    41
    Tutorials:
    2
    Resources:
    44
    I've gone over the code a bit more thoroughly now and noted a few things:

    - You use a lot of local unit groups and destroy them, since you always utilise them in a way that leaves them empty you do not need to do this and can use one global unit group for all uses (adding one global but removing all the lines creating/destroying local groups)
    - Please give your local variables meaningful names using single letters is very unhelpful; while you do explain what they are in most functions (some are not) having to scroll back up every two seconds isn't that great
    - You have the number for bj_DEGTORAD (57.2957795) instead of the constant bj_DEGTORAD in your code, seems a bit pointless
    - When you recycle with a linked list you don't need to null all the unit variables, it will not leak regardless (as the variable will be reused)
    - Having a GUI configuration does not a GUI spell make; this is a pure JASS submission
    - udg_ClsR_trig can be a local making
    Code (vJASS):
        if udg_ClsR_trig != null then
            call DestroyTrigger(udg_ClsR_trig)
        endif
    redundant
    - this makes the spell uncastable on the line x == 0 of any map (again unlikely but it is a problem)
    Code (vJASS):
    if GetOrderTarget() != null or GetOrderPointX() != 0 then


    I'd note not all of these are mandatory (the ones towards the top particularly) but it would be helpful and would be slight improvements in my opinion (notably in terms of readability) The spell is great and I'd like to approve it once these things have been solved
     
  15. Ofel

    Ofel

    Joined:
    Mar 29, 2012
    Messages:
    406
    Resources:
    10
    Spells:
    10
    Resources:
    10
    Gonna fix this ASAP.
     
  16. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    5,821
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Pretty much was Tank already mentioned--and also
    null
    the local
    rect
    in the init.
     
  17. Tank-Commander

    Tank-Commander

    Spell Reviewer

    Joined:
    May 26, 2009
    Messages:
    1,547
    Resources:
    44
    Packs:
    1
    Spells:
    41
    Tutorials:
    2
    Resources:
    44
    I noticed this has been updated to v1.0.3 but the one critical issue (the GetOrderPointX() != 0 problem) still remains the spell is very near perfect bar that code-wise
     
  18. Ofel

    Ofel

    Joined:
    Mar 29, 2012
    Messages:
    406
    Resources:
    10
    Spells:
    10
    Resources:
    10
    Can the one problem be ignored? I can't figure out the solution.
     
  19. jakeZinc

    jakeZinc

    Joined:
    Aug 13, 2013
    Messages:
    1,362
    Resources:
    20
    Spells:
    20
    Resources:
    20
    And still theres a bug occuring ( cant post screenshot atm ), the current missiles just paused the whole spell instances ( the debug expiration timer didnt also work on missiles ), it is frequently visible by lowering the missile speed and rapidly casting it alternatively on the ally and enemy unit.