• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Meteor Swarm (V 1.20)

Description


Upload Date: 17:38, 6th Apr 2012
Update V 1.10
  • 11:42 PM 4/8/2012
Update V 1.11
  • 11:15 AM 4/11/2012
Update V 1.12
  • 5:50 PM 4/12/2012
Update V 1.20
  • 12:15 AM 4/28/2012

Learn

Learn Meteor Swarm [Level 1]
Learn Meteor Swarm [Level 2]
Learn Meteor Swarm [Level 3]
Learn Meteor Swarm [Level 4]
Learn Meteor Swarm [Level 5]
Calls down waves of fiery boulders with random sizes that fall at random points in the target area dealing fire damage to enemy units and buildings in the effective area, except for the first meteor which is always at the centre of the area and deals maximum damage.

Level 1 - Calls 30 meteors that deals 100 to 300 fire damage.
Level 2 - Calls 35 meteors that deals 125 to 350 fire damage.
Level 3 - Calls 40 meteors that deals 150 to 400 fire damage.
Level 4 - Calls 45 meteors that deals 175 to 450 fire damage.
Level 5 - Calls 50 meteors that deals 200 to 500 fire damage.

[Deals 10% of damage only to trees]
Normal
Meteor Swarm - [Level 1]
Calls down 30 waves of firey boulders with random sizes that fall at random points in the target area dealing 100 to 300 fire damage to enemy units and buildings in the effective area. The first meteor is always at the centre and deals the maximum damage.

[Deals 10% of damage only to trees]


Meteor Swarm - [Level 2]
Calls down 35 waves of firey boulders with random sizes that fall at random points in the target area dealing 125 to 350 fire damage to enemy units and buildings in the effective area. The first meteor is always at the centre and deals the maximum damage.

[Deals 10% of damage only to trees]


Meteor Swarm - [Level 3]
Calls down 40 waves of firey boulders with random sizes that fall at random points in the target area dealing 150 to 400 fire damage to enemy units and buildings in the effective area. The first meteor is always at the centre and deals the maximum damage.

[Deals 10% of damage only to trees]


Meteor Swarm - [Level 4]
Calls down 45 waves of firey boulders with random sizes that fall at random points in the target area dealing 175 to 450 fire damage to enemy units and buildings in the effective area. The first meteor is always at the centre and deals the maximum damage.

[Deals 10% of damage only to trees]


Meteor Swarm - [Level 5]
Calls down 50 waves of firey boulders with random sizes that fall at random points in the target area dealing 200 to 500 fire damage to enemy units and buildings in the effective area. The first meteor is always at the centre and deals the maximum damage.

[Deals 10% of damage only to trees]


Icon by M0rbid


Sevion: for his 'Alloc' module

Magtheridon96: For his very good and detailed review
Adiktuz: Suggested many good ideas
RetroSexual: For his help in providing me with a trueshot aura model with zero height
Bribe


If someone finds he/she didn't get a well earned credit for using a resource(s) he/she made, contact me.
  • Will add destructible targets
  • Will add Shake Camera option
  • Will add a cast-at-bound fail safe function


- Older versions is included in the map for full change preview, its in a category named 'Older Versions'

V 1.00
  • Release

  • Changed some variable names.
  • DUMMYID and ABIL_ID changed from public to private.
  • Added static if to constant boolean.
  • Added set t = null in the function Init_MeteorSwarm.
  • Merged actions in condition function and removed TriggerAddAction(t, function MeteorSwarm_Actions).
  • Renamed both static method Start and fall to meteorCall and meteorFall.
  • Renamed dat to this.
  • Removed if/then/else block from MatchingUnit.
  • method meteorDamage now uses bj_lastCreatedGroup instead of creating and destroying a local or even instead of a global group.
  • Changed GetRandomReal(1.0,360.0) to GetRandomReal(0.0,2*bj_PI) and so I can remove the bj_DEGTORAD from both Cos and Sin functions.
  • Used one hashtable only.
  • method meteorDamageandMatchingUnit are now normal functions outside the struct.
  • Added the option to let the first falling meteor to deal maximum damage or random instead of random only.

  • Added an indicator to show the targeted area. You also have the option to show/hide this indicator.
  • You can target destructables now and also you have the option to disable it.
  • The spell now will function even if cast outside the boundary.
  • Modified the BoundSentinel library; it now use the playable area not the camera bound in case the user changed the camera bound to minimum.

  • Added Camera shake option
  • Added Unit-Explode-on-Death option if killed by a meteor

  • Implemented CTL
  • Merged a library with the scope
  • Changed the scope name to MeteorSwarm


  • PM me or post for any inquiry/problem(s)/suggestion(s)
  • Credit when use.
The spell is pasted below.
JASS:
// 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
IMPORTANT: You will need JNGP to customize it to your needs.


Keywords:
Meteor,Meteors,Comet,Comets,Asteroid,Asteroids,Swarm,Swarming,Shower,Showering,Rain,Raining,Fire,Fiery,Fall,Falling,Chaos,Catastrophe.
Contents

Meteor Swarm v1.13 (Map)

Reviews
16 July 2012 Bribe: Well-coded and as nice effects. Approved 4.4/5.

Moderator

M

Moderator

16 July 2012
Bribe: Well-coded and as nice effects. Approved 4.4/5.
 
Why not change the scope to a library so you can make the requirements explicit? :p
(This would actually help a lot of map-makers during the development of their maps because if they
wanted to remove a certain library and change it, they'd know what's going to be affected and shit
without actually reading the code)

JASS:
            private constant attacktype SPELL        = ConvertAttackType(0)

            private constant damagetype FIRE         = ConvertDamageType(8)

            private constant unittype   DEAD         = ConvertUnitType(1)
            private constant unittype   UT_STRUCTURE = ConvertUnitType(2)
            private constant unittype   UT_FLYING    = ConvertUnitType(3)
            private constant unittype   UT_GROUND    = ConvertUnitType(4)
            private constant unittype   MAGICIMMUNE  = ConvertUnitType(26)

Instead of using the Convert functions, you should use the constants given in Warcraft III's .j files. Meaning you would do this:

JASS:
            private constant attacktype SPELL        = ATTACK_TYPE_NORMAL

            private constant damagetype FIRE         = DAMAGE_TYPE_FIRE

            private constant unittype   DEAD         = UNIT_TYPE_DEAD
            private constant unittype   UT_STRUCTURE = UNIT_TYPE_STRUCTURE
            private constant unittype   UT_FLYING    = UNIT_TYPE_FLYING
            private constant unittype   UT_GROUND    = UNIT_TYPE_GROUND
            private constant unittype   MAGICIMMUNE  = UNIT_TYPE_MAGIC_IMMUNE
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
JASS:
            private constant attacktype SPELL        = ATTACK_TYPE_NORMAL

            private constant damagetype FIRE         = DAMAGE_TYPE_FIRE

            private constant unittype   DEAD         = UNIT_TYPE_DEAD
            private constant unittype   UT_STRUCTURE = UNIT_TYPE_STRUCTURE
            private constant unittype   UT_FLYING    = UNIT_TYPE_FLYING
            private constant unittype   UT_GROUND    = UNIT_TYPE_GROUND
            private constant unittype   MAGICIMMUNE  = UNIT_TYPE_MAGIC_IMMUNE

Aren't those BJs ? I remember you told me before to use true instead of TRUE in case a custom BJ is used. I did the same here.
 
Last edited:
BJ constants are fine.
BJ functions are not. (Well, except for TriggerRegisterAnyUnitEventBJ)

edit
Also:

JASS:
            private constant real       PI           = 3.141592654
            private constant real       DTR          = PI / 180.0
            private constant real       RTD          = 180.0 / PI

You can use bj_PI, bj_DEGTORAD and bj_RADTODEG

I'm going to give you a full review in this post.
<Will edit soon>

edit

  • Instead of using those arrays, wouldn't it be better to create user-configurable functions that take the level, use some formula and return the data? It would make it easier to configure for any number of levels.
  • I noticed that you made it optional to use either the formulas or the tables. Trust me, it would be much better to just let the user use the formulas.
  • if NUM_TABLE then -> NUM_TABLE is a constant boolean. This should be a static if.
  • There are other instances in your code in which you're using regular if statements to evaluate constant booleans. Those should be static ifs as well. Static ifs will erase the unneeded code and keep the code that will execute.
  • Your MatchingUnit function is using the wrong structure. Instead of using an if/then/endif block to check the state of the unit and filter out unwanted ones, you should just return the actual conditions of the if statement. Meaning: if (conditions) return true else return false should only be return (conditions). I hope you understood what I was talking about. This is much better because it allows the function to inline. You can still split this into multiple lines using the comment delimiters (/**/) trick.

edit
More:
  • Your Damage static method shouldn't creating a local group then destroying it. It should be using a global group. Don't worry, you wouldn't have to call GroupClear(group) or anything, because GroupEnumUnitsInRange(group, x, y, radius, filter) will clear the group for you. Actually, what would be better than creating your own global group is using bj_lastCreatedGroup declared inside Blizzard.j.
  • Instead of using two hashtables, you could merge them into one using an Offsetting trick (meaning, just add some large number to the first key or something.)
  • All your static methods inside the struct should be namedLikeThis. NotLikeThis. This is what we call camelCase. The first letter of the first word should be lower-case and the rest should be upper-case.
  • Your Conditions function is terribly designed. It shouldn't check if you didn't cast the spell and return false, it should simply return GetSpellAbilityId() == ABIL_ID.
  • In fact, you shouldn't be separating the actions and conditions. You should only register a condition to the trigger and move all the code inside the actions to an if/then/endif block in which you check if GetSpellAbilityId() == ABIL_ID. Conditions run much faster than Actions. Actions should never be used unless you want to use functions like TriggerSleepAction and maybe TriggerSyncReady() and such.
  • In your Init function, you should null the trigger t.
  • There are other instances in which you aren't nulling local agents. (Agents are handles that should be nulled when declared as locals.)

edit
Even more:
  • In the Start static method, you should null the variable u.
  • In your fall static method, Within this block if dat.cfm <= dat.meteors then, you should null the variable nu.
  • In your fall static method, you should null u at the end of the function.
  • In your fall static method, local unit tu // Target Unit is unused and useless. You should erase it.
  • In your Damage static method, I found that you're repeatedly calling GetOwningPlayer(dat.caster). Instead, you could add a declaration for a player in the struct called owner, and set it to the owner of the caster in your Start static method.
  • In your Damage static method, dat is pretty useless as a local. Using this would've been much more readable. It actually highlights.
  • In your Damage static method, the UnitDamageTarget call should use false for both boolean parameters.

edit
  • You should use more meaningful names for your constants and variables. Don't worry, this won't cut down speed, it would actually make your system better and easier to read.
  • You should use more comments in your code to explain exactly what's going on. If you want to make things even more readable, you could use Table by Bribe in the Jass section.
    This is also a plus because then you'll never hit the hashtable limit.
  • I noticed the use of constant booleans in your MatchingUnit static method. You don't need any of these constant booleans at all. You should make that static method a normal private function and move it to the top of the script for configuration, and use this as a default filter: return not IsUnitType(u, UNIT_TYPE_DEAD) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and IsUnitEnemy(u, casterOwner)
    This would be much cleaner, and it would make your spell more flexible.
  • In your fall static method, this: set rnda = GetRandomReal(1.0,360.0) should be finding a random real between 0 and 2*bj_PI. That way, in the next two lines: set ntx = dat.TX + rndr*Cos(rnda*DTR) and set nty = dat.TY + rndr*Sin(rnda*DTR), you wouldn't need to multiply by DTR. In fact, if you want, you could just replace the Sin and Cos calls with function calls to GetRandomReal in which you retrieve a random real between -1 and 1 since the range of values for Sin and Cos don't go beyond 1 or under -1.

edit
I hope you like my full review.
Good luck with updating this spell.
And you need to fix the indenting on this resource.
Click here to download a vJass Script Indenter by The_Witcher
 
Last edited:
That is really good spell, unlike blizzard and rain of fire that can harm your own unit. 5/5

its because you forgot to set the target units to enemies only (if ur making a custom map)...

anyway, the spell seems pretty nice... ^_^

Maggy already made a full review for you so just follow it... :)
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
Thanks for the review but I have a some opinions and questions
  • I noticed that you made it optional to use either the formulas or the tables. Trust me, it would be much better to just let the user use the formulas.
Just in case the user needed a non-constant pattern.
  • Instead of using two hashtables, you could merge them into one using an Offsetting trick (meaning, just add some large number to the first key or something.)
I thought of something like that but I must assume the worst case scenario.
  • In your Damage static method, the UnitDamageTarget call should use false for both boolean parameters.
Why, what will happen with those booleans ?

  • In your Damage static method, dat is pretty useless as a local. Using this would've been much more readable. It actually highlights.
Do you mean that I don't need to add the line local thistype dat = this or do I change dat to this
  • You should use more comments in your code to explain exactly what's going on. If you want to make things even more readable, you could use Table by Bribe in the Jass section.
What is this Table ?

  • I noticed the use of constant booleans in your MatchingUnit static method. You don't need any of these constant booleans at all.
Well, those constant booleans work like the check boxes you find in the OE data field 'Targets Allowed'. If the users don't want to target (air/ground/structures/etc) they simply set (FLYING/GROUND/STRUCTURE/...) to false.

In fact, if you want, you could just replace the Sin and Cos calls with function calls to GetRandomReal in which you retrieve a random real between -1 and 1 since the range of values for Sin and Cos don't go beyond 1 or under -1.
I don't fully understand this.
 
yeah, remove the local thistype dat = this... though I think you should change integer this into thistype this on the parameters...

Actually, I suggest keeping the Sin/Cos to ensure a circular AOE... from what I understand, Maggy was suggesting to replace the Sin and Cos with the GetRandomReals for both x and y, but that would cause a square AOE since the x,y offset will not be bounded by the Sin/Cos correspondence of an angle... imagine having a value for rndr equal to max bound and then obtaining a value of 1.00 for BOTH RandomReals, that will make the meteor fall into a corner of a square not on the bounds of a circle...

JASS:
integer bound = 100.0
set offsetx = GetRandomReal(-1.00,1.00)*bound
set offsety = GetRandomReal(-1.00,1.00)*bound

/*
now if both of those GetRandomReal are 1.00 (or both -1.00) you will have an offset of 100 for both x,y
and computing for the hyposomethingsomething of the triangle which will be supposedly equal 
to the radius it will be Squareroot of (100^2 + 100^2) which is greater than the bounds 
which is actually the radius of the circular AOE...

while if we do this:
*/

integer bound = 100.0
real angle = GetRandomReal(0.0,PI*2) 
//yes, instead of using 0.0 to 360.0 then using DegreesToRadians conversion, just use a bound of 0.0 to PI*2
//since radians is from 0.0 to PI*2
set offsetx = Cos(angle)*bound
set offsety = Sin(angle)(-1.00,1.00)*bound

/*
the hypo of the triangle formed from the x,y will never be greater than the bounds 
since Sin + Cos of the same angle is always equal to 1.00, in this case
since I only used bounds instead of GetRandomReal(0.0,bounds), the meteors will always 
be at the circumference of the circle

*/


I know this becoz this is how my Nova and Meteor system was made... Yes, they both have square AOE... I'm still thinking if I'd change the codes to make it circular...

and I suggest you take note of the angular computation, use 0.0 to PI*2 as bounds rather than using 1 to 360 and then using DTR to convert... also I used 0.0 to PI*2 rather than 1.00 to PI*2, I know 0 radians is also the point for PI*2 radians, but using 1 will remove lots of angles from the randomization (that is the radians between 0.0 and 1.00)

and Table was a library made by Bribe... You can look at it at the JASS section, or you can view NovaSystem from my sig, there's a link there towards Table...

Also, I highly suggest that you add a check so that the meteors won't be created outside of the map bounds coz that would cause errors...
 
Last edited:
Just in case the user needed a non-constant pattern.

If the user didn't want a constant pattern, he would change the formula function to something like:
JASS:
function GetDamage takes integer level returns real
    if level < 3 then
        return level * 120
    else
        return level * 120 + 60
    endif
endfunction

I thought of something like that but I must assume the worst case scenario.

Then use a /really bigass/ number xD

Why, what will happen with those booleans ?

Using false will take up less memory (obviously)
This is because the 2 booleans signify whether the damage is an attack or a ranged attack. By saying false, you're telling warcraft III to just deal the damn damage without creating any extra shit that will give you the same results but with extra overhead.

Do you mean that I don't need to add the line local thistype dat = this or do I change dat to this

You don't need the line, so just use this

What is this Table ?

http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/

I don't fully understand this.

Sin and Cos will always return values between -1 and 1. Right?
So, instead of getting a random angle and passing it to Sin/Cos functions, you could just generate a random real between -1 and 1 and use it as if it's Sin/Cos.
I realized that this could be a bad idea, so instead, just change the random real range from 1-360 to 0-2*bj_PI and don't multiply by DTR inside the Cos/Sin function.
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
Then use a /really bigass/ number xD.

You are more experienced than I so how big are we talking at here ?

I realized that this could be a bad idea, so instead, just change the random real range from 1-360 to 0-2*bj_PI and don't multiply by DTR inside the Cos/Sin function
Yes, if you get both "ones" its like you are subtracting a square from a circle.

Also, I would like your opinion on this matter, please:

do I leave the function like

JASS:
        static method meteorFall takes nothing returns nothing

                // ....

                if dat.ibem <= 0 then
                    set dat.ibem = GetRandomInt(INT_MIN,INT_MAX)
                    set dat.cfm = dat.cfm + 1
                    if dat.cfm <= dat.meteors then
                        // launch another meteor
                        call thistype.meteorCallAnother(dat)
                        set bound = dat.grandarea - dat.maxarea
                        set rnd_rad = GetRandomReal(0.0,bound)
                        set rnd_ang = GetRandomReal(0.0,2.*bj_PI)
                        set ntx = dat.TX + rnd_rad*Cos(rnd_ang)
                        set nty = dat.TY + rnd_rad*Sin(rnd_ang)
                        set nface = GetRandomReal(0.0,2.*bj_PI)
                        set nix = ntx - M_DIST * Cos(nface)
                        set niy = nty - M_DIST * Sin(nface)
                        set niz = GetRandomReal(H_MIN,H_MAX)
                        set nu = CreateUnit(dat.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,dat,dat.cfm,nu)
                        call SaveBoolean(mht,dat,dat.cfm,false)
                        call SaveReal(mht , dat, dat.cfm,ntx)
                        call SaveReal(mht , dat,-dat.cfm,nty)
                        call SaveReal(mht ,-dat, dat.cfm,nix)
                        call SaveReal(mht ,-dat,-dat.cfm,niy)
                        call SaveReal(mvht, dat, dat.cfm,nface)
                        call SaveReal(mvht, dat,-dat.cfm,nratio)
                        call SaveReal(mvht,-dat, dat.cfm,-niz/M_DIST)
                        call SaveReal(mvht,-dat,-dat.cfm,niz)
                    endif
                endif

                // ....

        endmethod

or change it to:

JASS:
        static method meteorCallAnother takes thistype dat returns nothing
                        set bound = dat.grandarea - dat.maxarea
                        set rnd_rad = GetRandomReal(0.0,bound)
                        set rnd_ang = GetRandomReal(0.0,2.*bj_PI)
                        set ntx = dat.TX + rnd_rad*Cos(rnd_ang)
                        set nty = dat.TY + rnd_rad*Sin(rnd_ang)
                        set nface = GetRandomReal(0.0,2.*bj_PI)
                        set nix = ntx - M_DIST * Cos(nface)
                        set niy = nty - M_DIST * Sin(nface)
                        set niz = GetRandomReal(H_MIN,H_MAX)
                        set nu = CreateUnit(dat.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,dat,dat.cfm,nu)
                        call SaveBoolean(mht,dat,dat.cfm,false)
                        call SaveReal(mht , dat, dat.cfm,ntx)
                        call SaveReal(mht , dat,-dat.cfm,nty)
                        call SaveReal(mht ,-dat, dat.cfm,nix)
                        call SaveReal(mht ,-dat,-dat.cfm,niy)
                        call SaveReal(mvht, dat, dat.cfm,nface)
                        call SaveReal(mvht, dat,-dat.cfm,nratio)
                        call SaveReal(mvht,-dat, dat.cfm,-niz/M_DIST)
                        call SaveReal(mvht,-dat,-dat.cfm,niz)
        endmethod

        static method meteorFall takes nothing returns nothing

                // ....

                set dat.ibem = dat.ibem - 1
                if dat.ibem <= 0 then
                    set dat.ibem = GetRandomInt(INT_MIN,INT_MAX)
                    set dat.cfm = dat.cfm + 1
                    if dat.cfm <= dat.meteors then
                        // launch another meteor
                        call thistype.meteorCallAnother(dat)
                    endif
                endif

                // ....

        endmethod
 
Last edited:
You are more experienced than I so how big are we talking at here ?

Well, if you're using +this in the hashtable, add 4096 (This is way more than enough bro)
If you're using -this in the hashtable, subtract 4096.

That way, you'd only need one hashtable :D
But if you are going to use Table by Bribe, you could use a million Tables and no one would care :p (The max Table count is a little more than 2 billion)


And about that code, I'd suggest keeping the first one.
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
yeah, remove the local thistype dat = this... though I think you should change integer this into thistype this on the parameters...

Yes, you are correct I already changed it to that but thanks for telling anyway.

Also, I highly suggest that you add a check so that the meteors won't be created outside of the map bounds coz that would cause errors...
Yes, I noticed that and I am trying to solve it.

Also, I was thinking of adding the option to destroy destructible (trees, gates, etc), but the JASS function of "Pick every destructible" must use a filter, and from what I understood from Bribe is that filterfunc creates a new thread and causes more usage for CPU, he also told me that using FirstOfGroup is much better but the problem is that I can't find a FirstOfGroup like-function for destructibles.
 
Last edited:
Level 16
Joined
Mar 3, 2006
Messages
1,564
This will be v1.11 but I thought it would be better to post the script before, so I could get your feedback.

JASS:
// Meteor Swarm Version 1.11

library Utils

    function GetSpellTargetXBounded takes nothing returns real
        local real safety_offset = 32.00
        local real x = GetSpellTargetX()
        local real minX = GetRectMinX(bj_mapInitialPlayableArea)
        local real maxX = GetRectMaxX(bj_mapInitialPlayableArea)
        if x < minX then
            return minX + safety_offset
        elseif x > maxX then
            return maxX - safety_offset
        else
            return x
        endif
    endfunction

    function GetSpellTargetYBounded takes nothing returns real
        local real safety_offset = 32.00
        local real y = GetSpellTargetY()
        local real minY = GetRectMinY(bj_mapInitialPlayableArea)
        local real maxY = GetRectMaxY(bj_mapInitialPlayableArea)
        if y < minY then
            return minY + safety_offset
        elseif y > maxY then
            return maxY - safety_offset
        else
            return y
        endif
    endfunction

endlibrary

scope MS initializer Init_MeteorSwarm

    globals

        private constant integer M_DUMMY_ID = 'e000'
        private constant integer ABILITY_ID = 'A000'
        private constant timer tim = CreateTimer()
        private integer array temp_dat
        private integer       index = 0

        private constant integer INT_MIN    = 10                 // Minimum Interval Between Meteors Threshold
        private constant integer INT_MAX    = 100                // 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       = 16.00
        private constant real    CHECK_DIST = 32.00

        private constant integer HT_OFFSET = 4096
        private hashtable mht  = InitHashtable()                 // Meteor Hashtable
        //private constant group damage_group = CreateGroup()
        private unit CASTER
        private real DAMAGE

        private real MIN_X
        private real MIN_Y
        private real MAX_X
        private real MAX_Y

//====================================================================================================
//                                           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

    // Flags:
        private constant boolean INIT_MAX_D  = true     // true/false to enable/disable maximum damage on first meteor

    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 SetBoundary takes nothing returns nothing
        set MIN_X = GetRectMinX(bj_mapInitialPlayableArea)
        set MIN_Y = GetRectMinY(bj_mapInitialPlayableArea)
        set MAX_X = GetRectMaxX(bj_mapInitialPlayableArea)
        set MAX_Y = GetRectMaxY(bj_mapInitialPlayableArea)
    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 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
                call UnitDamageTarget(caster,target,damage,false,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_FIRE,null)
            endif
            call GroupRemoveUnit(bj_lastCreatedGroup,target)
        endloop
    endfunction

    private function matchingDestructable takes nothing returns boolean
        return true
    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    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 meteorFall takes nothing returns nothing
            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 act_rad  // Accurate Radius
            local real act_dmg  // Accurate Damage
            local rect effectrect

            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 act_rad = ratio * (this.maxarea   - this.minarea  ) + this.minarea
                        set act_dmg = 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 (to be made)
                            // Do Damage stuff here
                            call meteorDamageUnits(this.owner,this.caster,tx,ty,act_rad,act_dmg)
                            static if DESTRUCTABLES then
                                set CASTER = this.caster
                                set DAMAGE = act_dmg/10.
                                set effectrect = Rect(tx - act_rad, ty - act_rad, tx + act_rad, ty + act_rad)
                                call EnumDestructablesInRect(effectrect,function matchingDestructable,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
                                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)
                                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
            set u = null
            set nu = null

        endmethod

        static method meteorCall takes unit caster, real tx, real ty returns nothing
            local thistype this = thistype.allocate()
            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

            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)
            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

            if index == 0 then
                call TimerStart(tim,SPEED,true, function thistype.meteorFall )
            endif

            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 true
    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 Spell_Data_Table()
        call SetBoundary()
        set t = null
    endfunction

endscope
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
what's the utils for? you don't seem to be using it...

also merge that SetBoundary into the init trigger... making it a separate function just wastes another function call...

Utils contains the function GetSpellTargetXBounded() and GetSpellTargetYBounded().

The script may be messy for now but I wanted to show it just before I update the spell and specially that Bribe don't have enough time to look for every spell.
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
I haven't seen you use those two functions... but anyway, using locals for things that are constant (minX etc) is pretty bad...

Its at the buttom

JASS:
    private function MeteorSwarm_Conditions takes nothing returns boolean
        if GetSpellAbilityId() == ABILITY_ID then
            call Meteor.meteorCall(GetTriggerUnit(),@GetSpellTargetXBounded()@,@GetSpellTargetYBounded()@)
        endif
        return true
    endfunction


<<< EDIT >>>

About using the local, I guess you are correct I already defined those variables in a globals
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
I missed that... XD... also, why not just add it to the scope rather than making a library for it... there's a limit on the amount of libraries and scopes in a map so its better to combine those two...

I didn't know that, thanks for telling. Anyway, I will modify the script and update the map but I am too lazy to make the change log and all that bla bla in the description.
 
set START_SHAKING = false

This won't compile.
START_SHAKING is a constant boolean and thus cannot be set to something other than it's initial value. Also, I noticed this line: if START_SHAKING then. This is wrong. You shouldn't use a normal if statement here to evaluate a constant boolean. You should use a static if.

I think you could accomplish what you're trying to aim for creating a boolean in the struct that only gets created if START_SHAKING is true.

This boolean would determine whether the camera should be shaking or not.
If this is not what you were trying to aim for with your original setup, then simply.. well, actually, I'm not sure o_O

edit
Oh and that Utils library could be merged with the spell scope.
The globals Min/MaxX/Y could be replaced with those found in Nestharus' WorldBounds library.
You would use WorldBounds.minX, WorldBounds.maxX, WorldBounds.minY and WorldBounds.maxY
As for the functions, they could simply be placed in the scope, OR, you could submit them as a resource in the Jass section or the Code Snippets thread sticky in the Triggers and Scripts section.

By the way, using longer and more meaningful names would be much better because then it would be easier to understand the spell as we read the code without having to constantly refer to the comments you put up at the top of the struct.
And when I saw the number of locals you had, I was pretty shocked :p
When you're not using one anymore, you could make use of it to store some other piece of data.
Here's an example of what I mean:

JASS:
function Example takes nothing returns nothing
    local integer i = 0
    local integer j = 0

    loop
        call CreateUnit(Player(0), 'hpea', 0, 0, 0)
        set i = i + 1
        exitwhen i == 10
    endloop

    loop
        call CreateUnit(Player(0), 'Hamg', 0, 0, 0)
        set j = j + 1
        exitwhen j == 20
    endloop
endfunction

In this example, we used the integer i for the first loop and j for the second when we could've done this:
JASS:
function Example takes nothing returns nothing
    local integer i = 0

    loop
        call CreateUnit(Player(0), 'hpea', 0, 0, 0)
        set i = i + 1
        exitwhen i == 10
    endloop

    set i = 0

    loop
        call CreateUnit(Player(0), 'Hamg', 0, 0, 0)
        set i = i + 1
        exitwhen i == 20
    endloop
endfunction
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
This won't compile.
START_SHAKING is a constant boolean and thus cannot be set to something other than it's initial value.

Are you certain its constant I checked the script I pasted and its only private. I know about that; constants can't be reset. Is what in the map different from what is pasted in the description ?
 
Last edited:
Level 16
Joined
Mar 3, 2006
Messages
1,564
SetBoundary is only a function inside his library and the channeling part is for the user to decide I think... Though right now, its pretty tedious for the user to make this available for channeling...

and oh, you might want to make it into a system...

I already merged the functions with the scope, I add it to library first because I wanted to add it in the JASS section but I forgot to merge them in the scope. Its already merged in version 1.13 which will be updated. And as for channeling, I guess what he means is the caster must be standing performing an animation and if he stopped or moved or killed the channeling will end, ending the spell with it. This will be different from setting the dummy spell cast time to a value.

Channeling will take more functions to check the status of the caster but I guess I am up to the challenge.
 
On my nova, I added a parameter (maybe just a var in your case since its just a spell) which the user can set to define whether its channeling or not, which is attached to the unit... now I have a trigger which fires whenever that unit gets an order... now on that trigger, we check if the unit has an instance of channeling this spell and if he has, we will instantly cancel that instance...
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
Im rating it 4/5 for now until timer systems applied, +rep also...
Thanks for the rating and the rep.

I took a look on CTL. Its very complicated to understand, let alone to implement. I would like to know more about your point in using this timer library, I am using only two timers is it really necessary ? (I know its just your recommendation but I would like to know your point)
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
its really hard at first but quite easy when you know it...

sample;
JASS:
scope CTLTest initializer init

globals
    private constant integer ABILITY_ID = 'A000'
    private real PERIOD = 0.03125
endglobals

private struct CTLTest
    unit u
    real duration
   
    implement CTL
        local unit uu = .u
    
    implement CTLExpire
        if .duration > 0 then
            set .duration = .duration - PERIOD
            call BJDebugMsg(GetHeroProperName(uu))
            call BJDebugMsg(R2S(.duration))
        else
            call .destroy()
        endif
    
    implement CTLNull
        set .u = null
        set uu = null
    
    implement CTLEnd
    
    static method actions takes thistype this, unit caster returns nothing
        set .u = caster
        set .duration = 5.0
    endmethod   

endstruct

private function Test takes nothing returns boolean
    local CTLTest this
    if GetSpellAbilityId() == ABILITY_ID then
        set this = CTLTest.create()
        call CTLTest.actions(this, GetTriggerUnit())
        call BJDebugMsg(I2S(this))
    endif
    return false
endfunction
    
private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition( t, function Test)
    set t = null
endfunction

endscope
 
Actually mckill, that snippet isn't exactly right :\

You have to take note of this:

implement CTL

creates:

JASS:
private static method create takes nothing returns thistype
    // ...
endmethod
private method destroy takes nothing returns nothing
    // ...
endmethod
private static method e takes nothing returns nothing
    local thistype this = next[0]

and

implement CTLExpire

creates

JASS:
loop
    exitwhen this == 0

So, you should never reference anything related to this above CTLExpire unless you only want to get the first in the linked list of instances.

And since implement CTLNull creates:

JASS:
    set this = next[this]
endloop

You shouldn't reference anything with this after CTLNull unless you only want to reference the last instance in the linked list :/
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
... since 0.01 is too fast and it's converted automatically to 0.03...

edit:
take note that when I said automatically, means there is 99% no speed difference...

I tried changing the timer to Time Out to:
- 0.03 and the missile was falling slowly
- 0.003215 and it was really very fast
- 0.02 and the fall speed was a bit slow
- reset it to 0.01 and it returns to its normal speed.

So I don't get it when you said 0.01 is converted to 0.03 (are you certain its 99% there is no difference, because in my tests it was 100% there is difference ?)
 
CTLNull isn't useless, it's used to null the local variables you should declare under the CTL implementation.
If you don't need to declare locals, you can use CTLExpire and ignore CTL because CTL is optional.
If you don't need to null anything, you could simply use CTLEnd and ignore CTLNull.
The reason this works is because Nestharus implements CTLNull in the CTLEnd module.
If you don't implement the module yourself, it will be implemented automatically.
If you do, no errors will occur <:
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
@Adik & Star
...since 0.01 is too fast and it's converted automatically to 0.03...
edit:
take note that when I said automatically, means there is 99% no speed difference...
The other 1% doesnt really matter since people can hardly notice the difference...
sample:
try making and effect and destroying it using 0.03125 & 0.01, it lags but do you know the difference?, except it's runnig too fast?...
and like Adik said, 0.03125 is the best considering...

If you do, no errors will occur <:
the code I posted have no errors, even though I null the set .u = null, it still work (which is really weird coz Im nulling the instance)...
I can post a video tonight if you want just to proce it :)...
 
Top