• 🏆 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!

Aerial Aid v1.01

  • Like
Reactions: Iraxx
Zephyr Contest #14 Entry

JASS:
scope AerialAid/*

    *************************** Aerial Aid v1.01 *****************************
                                    by Flux
  
  SPELL DESCRIPTION:
    Calls for an air support containing goblin paratrooper soldiers. The Plane
    is equipped with 2/3/4 guns which fires at enemy units in its field of sight
    targeting heroes and units with lower hitpoints. Upon activation, 'Aerial Aid'
    is replaced by the sub-skill 'Deploy Paratroopers' which commands your backup
    soldiers to parachute from the Plane.
 
  REQUIRES:
      (nothing)
 
  OPTIONALLY REQUIRES:
      - BonusMod and SetUnitMaxState
          If not found, it will generate it's own object editor data to manipulate the
          stats per level.
          Recommended if your other spells uses BonusMod or SetUnitMaxState as optional
          requirement, avoiding redundant functions and object editor abilities.
      - DummyRecycler/MissileRecycler
          If not found, it will create new units for all the dummy units needed
          everytime the spell is cast. Dummy units are used as Parachute, Visual Effects
          and Bullet Projectile.
          Strongly recommended because this spell creates a lot of units.
      - SpellEffectEvent
          If not found, it will create another trigger with 'A unit Starts the effect of an
          ability' event.
          Strongly recommended for maps with huge number of spells.
      - Table
          If not found, this spell will require 1 hashtable. Hashtable is limited to 256
          per map.
          Recommended if you used a lot of hashtable in your map to avoid reaching the
          hashtable limit.
      - WorldBounds
          If not found, this spell will create 4 more variables for the map boundary parameters.
          Recommended if your other spells uses WorldBounds as optional requirement, avoiding
          redundant variables.
    
    
  ---------------------------------- CREDITS: --------------------------------------------
      OPTIONAL SYSTEMS:
      Bribe                       - MissileRecycler, SpellEffectEvent, Table
      Earth-Fury                  - BonusMod, SetUnitMaxState
      Flux                        - DummyRecycler
      Magtheridon96               - RegisterPlayerUnitEvent
      Nestharus                   - WorldBounds
    
      MODELS:
      Anitarf, iNfraNe, Vexorian  - Attachable Dummy Model with Pitch Animation
      WILL THE ALMIGHTY           - Goblin Scout, Goblin Soldier
      NatDis                      - Dwarven Air Force DF 35 Fighter
      Fingolfin                   - Parachute
      Grey Knight                 - Bullet
      Tranquil                    - Snipe Target Effect (edited)
    
    
      ICONS:
      Sin'dorei300                - BTNGoblinRifleman, BTNGoblinFireworker
      Marcos DAB                  - BTNGoblinHeroAviator
    
      SKINS:
      Erkki2                      - Blue Laser
  -----------------------------------------------------------------------------------------*/


    native UnitAlive takes unit u returns boolean
  
    //====================================================================================
    //============================== CONFIGURATION =======================================
    //====================================================================================
  
    globals
        //Rawcodes
        private constant integer SPELL_ID = 'Aaid'
        private constant integer SPELL_ID_DEPLOY = 'Adpy'
        private constant integer TOGGLE_ID = 'AAtg'
      
        //Dummy Unit Properties
        private constant integer DUMMY_ID = 'dumi'
        private constant player DUMMY_OWNER = Player(14)
      
        //Summoned Units
        private constant integer PARATROOPER_AIR_ID = 'pair'
        private constant integer PLANE_ID = 'plne'
        private constant integer TRANSFORM_ID = 'AaTF'
      
        //Periodic Timing of the Spell (Plane and Paratrooper timing)
        private constant real TIMEOUT = 0.03125
      
        //Visual Effects
        private constant string PARACHUTE_MODEL = "war3mapImported\\Parachute.mdl"
        private constant string TARGET_MODEL = "war3mapImported\\Target.mdl"
        private constant string TARGET_ATTACHMENT = "overhead"
        private constant string LASER_CODE = "LASR"    //Laser string code as configured in LightningData.slk
        private constant real LASER_DISTANCE = 50      //Max Distance of straight laser lightning in the curve
        private constant string BULLET_MODEL = "war3mapImported\\Bullet.mdl"

      
        //Attack and Damage types of Plane Gun
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
        private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
      
        //Preload abilities at Map Loading Screen to avoid lag spike the first time it is cast?
        private constant boolean PRELOAD = true
      
      
        //************************************************************************************
        //**************************** PLANE CONFIGURATION ***********************************
        //************************************************************************************
        private constant real PLANE_HEIGHT_INITIAL = 1200
      
        private constant real PLANE_HEIGHT = 600
      
        private constant real PLANE_FALL_SPEED = 700 
      
        //************************************************************************************
        //***************************** GUN CONFIGURATION ************************************
        //************************************************************************************
        //When the absolute z difference between the plane and target is PLANE_HEIGHT, the
        //plane can't hit units under this range.
        //When the absolute z difference between the plane and target is 0, the
        //plane can hit units directly in front of it even at close range.
        private constant real MIN_HIT_DISTANCE = 500
      
        //Conic Radius for the Plane Gun (degrees)
        private constant real PLANE_SIGHT_ANGLE = 40
      
        //Plane Gun will prioritize targeting hero
        private constant boolean PRIORITIZE_HERO = true
      
        //Speed of Gun bullets (displacement per second)
        private constant real BULLET_SPEED = 3200
        //Is the BULLET_SPEED considered a 3D speed? If false, it will be considered as a 2D speed
        //meaning the horizontal (ground) speed is constant.
        private constant boolean BULLET_SPEED_3D = true
      
        //An optional different TIMEOUT for the Bullet projectiles just in case
        private constant real BULLET_TIMEOUT = TIMEOUT
      
        //Collision Size of Bullet
        private constant real BULLET_COLLISION = 110
      
        //Gun Field of Sight visible to enemy? Recommended to be set to false
        private constant boolean VISIBLE_LASER = false
      
        //************************************************************************************
        //************************ PARATROOPER CONFIGURATION *********************************
        //************************************************************************************
      
        //When the Paratrooper's height is below GND_HEIGHT, it is now targeted
        //as a ground unit
        private constant real GND_HEIGHT = 100
      
        //Does the Paratroopers have a Timed Life?
        private constant boolean APPLY_TIMED_LIFE = false
      
        //Is the Paratrooper vulnerable while on air?
        private constant boolean CAN_BE_HIT = true
      
        //When the current height of the Paratrooper is less than PARACHUTE_ACTIVATE, it will
        //activate its parachute
        private constant real PARACHUTE_ACTIVATE = 400
      
        //How fast Paratroopers accelerates when their parachute is not yet used (speed per second)
        private constant real PARATROOPER_GRAVITY = 150
      
        //How fast Paratroopers will fall upon using their parachute (displacement per second)
        private constant real PARACHUTE_FALL_SPEED = 180
      
        //When the Paratrooper hits the ground at this z-speed (displacement per second), it will die!
        private constant real SPEED_WITH_DAMAGE = 400
      
        //The lower the values, the greater the friction (for calculation simplicity)
        private constant real DRAG_COEFFICIENT = 0.3
      
    endglobals
  
    //************************************************************************************
    //*************************** PLANE CONFIGURATION ************************************
    //************************************************************************************
    //Plane Hitpoints per level
    private function PlaneLife takes integer lvl returns real
        return 200.0 + 100.0*lvl    //300, 400, 500
    endfunction
  
    //Plane Bonus armor per level.
    private function PlaneArmor takes integer lvl returns integer
        return 1 + 1*lvl       //2, 3, 4
    endfunction
  
    //How fast the Plane moves (displacement per second)
    private function PlaneSpeed takes integer lvl returns real
        return 350.0 + 0.0*lvl
    endfunction
  
    //How long the Plane will last
    private function Duration takes integer lvl returns real
        return 8.0 + 0.0*lvl    //0.0*lvl exist to make the function inline
    endfunction
  
    //************************************************************************************
    //***************************** GUN CONFIGURATION ************************************
    //************************************************************************************
  
    //Search radius for gun target
    private function GunRadius takes integer lvl returns real
        return 1400 + 0.0*lvl
    endfunction
  
    //How fast the gun fires
    private function GunFiringRate takes integer lvl returns real
        return 0.1 + 0.0*lvl             //Every 0.1 second, each Plane gun will fire
    endfunction
  
    //Damage per gun bullet
    private function GunDamage takes integer lvl returns real
        return 4.0 + 1.0*lvl        //5, 6, 7  
    endfunction
  
    //Number of Plane's gun per level
    private function GunCount takes integer lvl returns integer
        return 1 + lvl              //2, 3, 4
    endfunction
  
    //What gets hit by the Gun
    private function TargetFilter takes unit u, player owner returns boolean
        return UnitAlive(u) and IsUnitEnemy(u, owner)
    endfunction
  
    //Position of Guns with reference to the Plane's center when the Plane is facing 0 degrees.
    //gunNumber starts at 1
    private function GunOffsetX takes integer gunNumber, integer totalGunNumber returns real
        return 0. + 0*gunNumber + 0*totalGunNumber    //All guns will belong to the same x-axis of the Plane
    endfunction
  
    private function GunOffsetY takes integer gunNumber, integer totalGunNumber returns real
        if totalGunNumber == 2 then
            return -150. + gunNumber*100   //-50, 50
        elseif totalGunNumber == 3 then
            return -100. + gunNumber*50   //-50, 0, 50
        elseif totalGunNumber == 4 then
            return -100. + gunNumber*40   //-60, -20, 20, 60
        endif
        return 0.
    endfunction
  
    //************************************************************************************
    //************************** PARATROOPER CONFIGURATION *******************************
    //************************************************************************************
  
    //Paratrooper Hitpoints per level
    private function ParatrooperLife takes integer lvl returns real
        return 100.0 + 50.0*lvl         //150, 200, 250
    endfunction
  
    //Paratrooper Bonus Damage per level (See also Attack Cooldown of Paratrooper to balance)
    private function ParatrooperAttackDamage takes integer lvl returns integer
        return 2 + lvl            //3, 4, 5
    endfunction
  
    //Paratrooper Bonus Armor per level
    private function ParatrooperArmor takes integer lvl returns integer
        return lvl                 //1, 2, 3
    endfunction
  
    //Paratrooper Spawnrate (second)
    private function SpawnRate takes integer lvl returns real
        return 1.0 - 0.2*lvl       //Every 0.8/0.6/0.4 second, a Paratrooper will spawn
    endfunction
  
    //The maximum number of paratroopers the spell can spawn
    private function MaxSpawnCount takes integer lvl returns integer
        return 3 + lvl            //4, 5, 6
    endfunction
  
    static if APPLY_TIMED_LIFE then
        //Only applicable if APPLY_TIMED_LIFE = true
        //Determines how long each Paratrooper last.
        private function TimedLife takes integer lvl returns real
            return 7.0 + 1.0*lvl
        endfunction
    endif
  
    //====================================================================================
    //============================ END CONFIGURATION =====================================
    //====================================================================================
  
    static if not LIBRARY_BonusMod then
      
        //For the static if to work, the variables must be a static member of a struct
        private struct S1 extends array
            static constant integer BONUS_ARMOR = 0
            static constant integer BONUS_DAMAGE = 1
            static integer array pow2
            static integer array bonusAbility
        endstruct

        private function UnitSetBonus2 takes unit u, integer bonusType, integer amount returns boolean
            local integer i = 7
            loop
                if amount >= S1.pow2[i] then
                    call UnitAddAbility(u, S1.bonusAbility[bonusType*8 + i])
                    call UnitMakeAbilityPermanent(u, true, S1.bonusAbility[bonusType*7 + i])
                    set amount = amount - S1.pow2[i]
                endif
                set i = i - 1
                exitwhen i < 0 or amount == 0
            endloop
            return true
        endfunction
      
        private function BonusMod_Init takes nothing returns nothing
            local integer i = 0
            static if PRELOAD then
                local unit u = CreateUnit(DUMMY_OWNER, PARATROOPER_AIR_ID, 0, 0, 0)
            endif
          
            // Bonus Mod - Armor abilitys
            set S1.bonusAbility[i + 0]  = 'ZxA0' // +1
            set S1.bonusAbility[i + 1]  = 'ZxA1' // +2
            set S1.bonusAbility[i + 2]  = 'ZxA2' // +4
            set S1.bonusAbility[i + 3]  = 'ZxA3' // +8
            set S1.bonusAbility[i + 4]  = 'ZxA4' // +16
            set S1.bonusAbility[i + 5]  = 'ZxA5' // +32
            set S1.bonusAbility[i + 6]  = 'ZxA6' // +64
            set S1.bonusAbility[i + 7]  = 'ZxA7' // +128
            // Bonus Mod - Damage abilitys
            set i = 8
            set S1.bonusAbility[i + 0]  = 'ZxB0' // +1
            set S1.bonusAbility[i + 1]  = 'ZxB1' // +2
            set S1.bonusAbility[i + 2]  = 'ZxB2' // +4
            set S1.bonusAbility[i + 3]  = 'ZxB3' // +8
            set S1.bonusAbility[i + 4]  = 'ZxB4' // +16
            set S1.bonusAbility[i + 5]  = 'ZxB5' // +32
            set S1.bonusAbility[i + 6]  = 'ZxB6' // +64
            set S1.bonusAbility[i + 7]  = 'ZxB7' // +128
            //Initialize cached power of 2
            set S1.pow2[0] = 1
            set i = 1
            loop
                set S1.pow2[i] = 2*S1.pow2[i - 1]
                set i = i + 1
                exitwhen i == 8
            endloop
            //Preload
            static if PRELOAD then
                set i = 0
                loop
                    exitwhen i > 15
                    call UnitAddAbility(u, S1.bonusAbility[i])
                    set i = i + 1
                endloop
                call RemoveUnit(u)
                set u = null
            endif
        endfunction
    endif
  
    static if not LIBRARY_SetUnitMaxState then
      
        private struct S2 extends array
            static integer array pow2
        endstruct
      
        private function SetUnitMaxState2 takes unit u, unitstate state, real newValue returns nothing
            local integer newVal = R2I(newValue)
            local integer i = 8
            local integer offset
          
            set newVal = newVal - R2I(GetUnitState(u, state))
          
            if newVal > 0 then
                set offset = 11
            elseif newVal < 0 then
                set offset = 2
                set newVal = -newVal
            else
                return
            endif
          
            loop
                exitwhen newVal == 0 or i < 0
                if newVal >= S2.pow2[i] then
                    call UnitAddAbility(u, 'Zx01')
                    call SetUnitAbilityLevel(u, 'Zx01', offset + i)
                    call UnitRemoveAbility(u, 'Zx01')
                    set newVal = newVal - S2.pow2[i]
                else
                    set i = i - 1
                endif
            endloop
        endfunction
      
        private function SetUnitMaxState_Init takes nothing returns nothing
            local integer i = 1
            static if PRELOAD then
                local unit u = CreateUnit(DUMMY_OWNER, PARATROOPER_AIR_ID, 0, 0, 0)
            endif
          
            set S2.pow2[0] = 1
            loop
                set S2.pow2[i] = 2*S2.pow2[i - 1]
                set i = i + 1
                exitwhen i == 11
            endloop
            static if PRELOAD then
                call UnitAddAbility(u, 'Zx01')
                call RemoveUnit(u)
                set u = null
            endif
        endfunction
    endif
  
    static if not LIBRARY_WorldBounds then
        //Uses a private struct WorldBounds2
        private struct WorldBounds2 extends array
      
            readonly static real maxX
            readonly static real maxY
            readonly static real minX
            readonly static real minY
          
            private static method onInit takes nothing returns nothing
                local rect map = GetWorldBounds()
                set thistype.maxX = GetRectMaxX(map)
                set thistype.maxY = GetRectMaxY(map)
                set thistype.minX = GetRectMinX(map)
                set thistype.minY = GetRectMinY(map)
                call RemoveRect(map)
                set map = null
            endmethod
          
        endstruct
    endif
  
    globals
        private location l = Location(0, 0)
        private real playMinX
        private real playMaxX
        private real playMinY
        private real playMaxY
    endglobals
  
    private struct Paratrooper
      
        private unit soldier
        private unit parachute
        private effect parachuteModel
        private boolean activated
        private boolean transformed
        private integer damage //bonus damage is lost upon transformation
                               //that's why this needs to be stored
        //Movement
        private real z
        private real dz
      
        private thistype next
        private thistype prev
      
        private static timer t = CreateTimer()
      
        //constants to avoid repeating calculations
        //constants to fill in the TIMEOUT
        private static constant real GRAVITY = -PARATROOPER_GRAVITY*TIMEOUT
        private static constant real SPEED_DAMAGE = SPEED_WITH_DAMAGE*TIMEOUT
        private static constant real TERMINAL_VELOCITY = -PARACHUTE_FALL_SPEED*TIMEOUT
      
        static if not LIBRARY_BonusMod then
            private static constant integer BONUS_ARMOR = 0
            private static constant integer BONUS_DAMAGE = 1
        endif
      
        //Calls when the soldier landed
        private method destroy takes nothing returns nothing
            //Remove from List
            set .prev.next = .next
            set .next.prev = .prev
            if thistype(0).next == 0 then
                call PauseTimer(t)
            endif

            //Remove Parachute
            call DestroyEffect(.parachuteModel)
            static if LIBRARY_DummyRecycler then
                call DummyAddRecycleTimer(.parachute, 3.0)
            elseif LIBRARY_MissileRecycler then
                call RecycleMissile(.parachute)
            else
                call UnitApplyTimedLife(.parachute, 'BTLF', 3.0)
            endif
            set .parachuteModel = null
            set .soldier = null
            set .parachute = null
            call .deallocate()
        endmethod
      
        //! textmacro AERIAL_AID_PARATROOPER_UPDATE
            set .z = .z + .dz
            if .activated then
                if .dz < TERMINAL_VELOCITY then
                    //Apply drag (air-resistance)
                    set .dz = .dz*DRAG_COEFFICIENT
                    if .dz > TERMINAL_VELOCITY then
                        set .dz = TERMINAL_VELOCITY
                    endif
                else
                    //increase speed due to gravity until it reaches terminal velocity
                    set .dz = .dz + GRAVITY
                endif
                call SetUnitX(.parachute, GetUnitX(.soldier))
                call SetUnitY(.parachute, GetUnitY(.soldier))
            else
                set .dz = .dz + GRAVITY
                if .z < PARACHUTE_ACTIVATE then
                    set .activated = true
                    //Create the Parachute
                    static if LIBRARY_DummyRecycler then
                        set .parachute = GetRecycledDummy(GetUnitX(.soldier), GetUnitY(.soldier), .z, GetRandomReal(0, 360))
                    elseif LIBRARY_MissileRecycler then
                        set .parachute = GetRecycledMissile(GetUnitX(.soldier), GetUnitY(.soldier), .z, GetRandomReal(0, 360))
                    else
                        set .parachute = CreateUnit(DUMMY_OWNER, DUMMY_ID, GetUnitX(.soldier), GetUnitY(.soldier), GetRandomReal(0, 360))
                        call SetUnitFlyHeight(.parachute, .z, 0)
                    endif
                    set .parachuteModel = AddSpecialEffectTarget(PARACHUTE_MODEL, .parachute, "origin")
                endif
            endif
            if .z <= 0 then
                call SetUnitFlyHeight(.parachute, .z, 0)
                //For realism effect
                if .dz < -SPEED_DAMAGE then
                    call KillUnit(.soldier)
                endif
                call .destroy()
            else
                if .activated then
                    call SetUnitFlyHeight(.parachute, .z, 0)
                endif
                if .z < GND_HEIGHT and not .transformed then
                    call UnitAddAbility(.soldier, TRANSFORM_ID)
                    call UnitRemoveAbility(.soldier, TRANSFORM_ID)
                    set .transformed = true
                    static if LIBRARY_BonusMod then
                        call UnitSetBonus(.soldier, BONUS_DAMAGE, .damage)
                    else
                        call UnitSetBonus2(.soldier, BONUS_DAMAGE, .damage)
                    endif
                endif
                call SetUnitFlyHeight(.soldier, .z, 0)
            endif
        //! endtextmacro
      
        private static method pickAll takes nothing returns nothing
            local thistype this = thistype(0).next
            local real x
            local real y
            loop
                exitwhen this == 0
                //Using a textmacro to save a function call per paratrooper instance
                //! runtextmacro AERIAL_AID_PARATROOPER_UPDATE()
                set this = .next
            endloop
        endmethod
      
        static method create takes player owner, integer lvl, real x, real y, real z returns thistype
            local thistype this = .allocate()
            local real spd = 0

            //Paratrooper Initialization
            set .transformed = false
            set .activated = false
            set .z = z
            set .dz = 0
          
            //Create the soldier
            set .soldier = CreateUnit(owner, PARATROOPER_AIR_ID, x, y, GetRandomReal(0, 360))
            static if APPLY_TIMED_LIFE then
                call UnitApplyTimedLife(.soldier, 'BTLF', TimedLife(lvl))
            endif
            call MoveLocation(l, x, y)
            call SetUnitFlyHeight(.soldier, .z - GetLocationZ(l), 0)
            static if not CAN_BE_HIT then
                call UnitAddAbility(.soldier, 'Avul')
            endif
          
            //Stats
            set .damage = ParatrooperAttackDamage(lvl)
            static if LIBRARY_BonusMod then
                call UnitSetBonus(.soldier, BONUS_DAMAGE, .damage)
                call UnitSetBonus(.soldier, BONUS_ARMOR, ParatrooperArmor(lvl))
            else
                call UnitSetBonus2(.soldier, BONUS_DAMAGE, .damage)
                call UnitSetBonus2(.soldier, BONUS_ARMOR, ParatrooperArmor(lvl))
            endif
            static if LIBRARY_SetUnitMaxState then
                call SetUnitMaxState(.soldier, UNIT_STATE_MAX_LIFE, ParatrooperLife(lvl))
            else
                call SetUnitMaxState2(.soldier, UNIT_STATE_MAX_LIFE, ParatrooperLife(lvl))
            endif
          
            //Linked-list
            set .next = 0
            set .prev = thistype(0).prev
            set .next.prev = this
            set .prev.next = this
            if .prev == 0 then
                call TimerStart(t, TIMEOUT, true, function thistype.pickAll)
            endif
            return this
        endmethod
      
    endstruct

    private struct Bullet
  
        private player owner
        private unit source
        private unit u
        private effect bulletModel
        private real damage
        private boolean lineCollision
      
        private real x
        private real y
        private real z
      
        private real dx
        private real dy
        private real dz
        private real dxy
        private real dxyz
      
        private thistype next
        private thistype prev
      
        private static timer t = CreateTimer()
        private static group g = CreateGroup()
      
        method destroy takes nothing returns nothing
            //Remove from List
            set .prev.next = .next
            set .next.prev = .prev
            if thistype(0).next == 0 then
                call PauseTimer(t)
            endif
            //Clean handles
            static if LIBRARY_DummyRecycler then
                call DummyAddRecycleTimer(.u, 1.0)
            elseif LIBRARY_MissileRecycler then
                call RecycleMissile(.u)
            else
                call UnitApplyTimedLife(.u, 'BTLF', 1.0)
            endif
            call DestroyEffect(.bulletModel)
            set .u = null
            set .source = null
            set .bulletModel = null
            call .deallocate()
        endmethod
      
        private static constant real BULLET_COLLISION2 = BULLET_COLLISION*BULLET_COLLISION
      
        //! textmacro AERIAL_AID_BULLET_LOCALS
            local real x
            local real y
            local real z
            local real dx
            local real dy
            local real dz
            local real height
            local real dist
            local real checkDist
            local unit u
            local unit hit
            local boolean withinBound
      
            //line data for line collision
            local real xPrev
            local real yPrev
            local real zPrev
            local real radius
            local real dxL
            local real dyL
            local real dzL
            local real projection
        //! endtextmacro
      
        //! textmacro AERIAL_AID_BULLET_UPDATE
            if .lineCollision then
                //Store last position
                set xPrev = .x
                set yPrev = .y
                set zPrev = .z
                set radius = 0.5*.dxy
            endif
          
            //Initially no one is hit
            set hit = null
          
            //Update position
            set .x = .x + .dx
            set .y = .y + .dy
            set .z = .z + .dz
            call MoveLocation(l, .x, .y)
            set height = .z - GetLocationZ(l)
            static if LIBRARY_WorldBounds then
                set withinBound = .x > WorldBounds.minX and .x < WorldBounds.maxX and .y > WorldBounds.minY and .y < WorldBounds.maxY
            else
                set withinBound = .x > WorldBounds2.minX and .x < WorldBounds2.maxX and .y > WorldBounds2.minY and .y < WorldBounds2.maxY
            endif
            if withinBound then
                call SetUnitX(.u, .x)
                call SetUnitY(.u, .y)
                call SetUnitFlyHeight(.u, height, 0)
            endif
          
            //Check Collision
            if .lineCollision then  //when bullet moves to fast
                set dxL = .x - xPrev
                set dyL = .y - yPrev
                set dzL = .z - zPrev
                set checkDist = (.dxyz + BULLET_COLLISION)*(.dxyz + BULLET_COLLISION)
                call GroupEnumUnitsInRange(g, xPrev + 0.5*.dx, yPrev + 0.5*.dy, radius + 2*BULLET_COLLISION, null)
                loop
                    set u = FirstOfGroup(g)
                    exitwhen u == null
                    call GroupRemoveUnit(g, u)
                  
                    set x = GetUnitX(u)
                    set y = GetUnitY(u)
                    call MoveLocation(l, x, y)
                    set z = GetLocationZ(l) + GetUnitFlyHeight(u)
                    set dx = x - xPrev
                    set dy = y - yPrev
                    set dz = z - zPrev
                    //Scalar Projection of vector (xPrev -> x, yPrev -> y) on line (xPrev -> x, yPrev -> y)
                    set projection = (dx*dxL + dy*dyL + dz*dzL)/(.dxyz)
                    set dist = dx*dx + dy*dy + dz*dz    //distance of target to line point1
                    //dist - projection*projection = 3D distance of the picked unit to the line
                    if dist - projection*projection <= BULLET_COLLISION2 then
                        if TargetFilter(u, .owner) then
                            if dist < checkDist then   //if it is closer to the line point1
                                set checkDist = dist
                                set hit = u
                            endif
                        endif
                    endif
                endloop
            else
                set checkDist = BULLET_COLLISION2
                call GroupEnumUnitsInRange(g, .x, .y, BULLET_COLLISION, null)
                loop
                    set u = FirstOfGroup(g)
                    exitwhen u == null
                    call GroupRemoveUnit(g, u)
                  
                    set x = GetUnitX(u)
                    set y = GetUnitY(u)
                    call MoveLocation(l, x, y)
                    set z = GetLocationZ(l) + GetUnitFlyHeight(u)
                    set dx = x - .x
                    set dy = y - .y
                    set dz = z - .z
                    //Check if it collides in 3D
                    set dist = dx*dx + dy*dy + dz*dz    //distance of target to bullet
                    if dist <= checkDist then
                        if TargetFilter(u, .owner) then
                            set checkDist = dist
                            set hit = u
                        endif
                    endif
                endloop
            endif
          
            if hit != null then
                call UnitDamageTarget(.source, hit, .damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                call .destroy()
                set hit = null
            elseif height < 0 or height > 1500 or not withinBound then
                call .destroy()
            endif
          
        //! endtextmacro
      
        private static method pickAll takes nothing returns nothing
            local thistype this = thistype(0).next
            //! runtextmacro AERIAL_AID_BULLET_LOCALS()
            loop
                exitwhen this == 0
                //Use textmacro to save a function call per bullet instance
                //! runtextmacro AERIAL_AID_BULLET_UPDATE()
                set this = .next
            endloop
        endmethod
      
        static method create takes real x1, real y1, real z1, real x2, real y2, real z2, unit source, player owner, real damage returns thistype
            local thistype this = .allocate()
            local real dx = x2 - x1
            local real dy = y2 - y1
            local real dz = z2 - z1
            local real facing = Atan2(dy, dx)
            local real pitch = Atan(dz/SquareRoot(dx*dx + dy*dy))
          
            static if BULLET_SPEED_3D then
                set .dxy = BULLET_SPEED*BULLET_TIMEOUT*Cos(pitch)
            else
                set .dxy = BULLET_SPEED*BULLET_TIMEOUT
            endif
          
            //Initialize positions and speeds
            set .x = x1
            set .y = y1
            set .z = z1
            set .dx = dxy*Cos(facing)
            set .dy = dxy*Sin(facing)
            static if BULLET_SPEED_3D then
                set .dz = BULLET_SPEED*BULLET_TIMEOUT*Sin(pitch)
                set .dxyz = BULLET_SPEED*BULLET_TIMEOUT
            else
                set .dz = BULLET_SPEED*BULLET_TIMEOUT*Tan(pitch)
                if pitch == 0 then
                    set .dxyz = .dxy
                else
                    set .dxyz = .dz/Sin(pitch)
                endif
            endif
            set .lineCollision = dxy > BULLET_COLLISION
            //Initialize bullet interact properties
            set .source = source
            set .owner = owner
            set .damage = damage
          
            //Create Bullet and set appropriate angle
            //Safety precaution, make sure bullet is not outside bounds
            static if LIBRARY_WorldBounds then
                if x1 > WorldBounds.maxX then
                    set x1 = WorldBounds.maxX
                elseif x1 < WorldBounds.minX then
                    set x1 = WorldBounds.minX
                endif
                if y1 > WorldBounds.maxY then
                    set y1 = WorldBounds.maxY
                elseif y1 < WorldBounds.minY then
                    set y1 = WorldBounds.minY
                endif
            else
                if x1 > WorldBounds2.maxX then
                    set x1 = WorldBounds2.maxX
                elseif x1 < WorldBounds2.minX then
                    set x1 = WorldBounds2.minX
                endif
                if y1 > WorldBounds2.maxY then
                    set y1 = WorldBounds2.maxY
                elseif y1 < WorldBounds2.minY then
                    set y1 = WorldBounds2.minY
                endif
            endif
            call MoveLocation(l, x1, y1)
            static if LIBRARY_DummyRecycler then
                set .u = GetRecycledDummy(x1, y1, z1 - GetLocationZ(l), facing*bj_RADTODEG)
            elseif LIBRARY_MissileRecycler then
                set .u = GetRecycledMissile(x1, y1, z1 - GetLocationZ(l), facing*bj_RADTODEG)
            else
                set .u = CreateUnit(DUMMY_OWNER, DUMMY_ID, x1, y1, facing*bj_RADTODEG)
                call SetUnitFlyHeight(.u, z1 - GetLocationZ(l), 0)
            endif
            set .bulletModel = AddSpecialEffectTarget(BULLET_MODEL, .u, "origin")
            call SetUnitAnimationByIndex(.u, R2I(pitch*bj_RADTODEG + 90.5))
          
            //Linked-list
            set .next = 0
            set .prev = thistype(0).prev
            set .next.prev = this
            set .prev.next = this
            if .prev == 0 then
                call TimerStart(t, BULLET_TIMEOUT, true, function thistype.pickAll)
            endif
            return this
        endmethod
      
    endstruct
  
    private struct Gun
      
        private effect targetSfx
      
        readonly unit target
        readonly real dx
        readonly real dy
  
        readonly thistype next
        readonly thistype prev
      
        method destroy takes nothing returns nothing
            //Remove from List
            set .prev.next = .next
            set .next.prev = .prev
            if .target != null then
                call DestroyEffect(.targetSfx)
            endif
            set .targetSfx = null
            set .target = null
            call .deallocate()
        endmethod
      
        method update takes unit newTarget returns nothing
            if newTarget != .target then
                if .target != null then
                    call DestroyEffect(.targetSfx)
                endif
                if newTarget != null then
                    set .targetSfx = AddSpecialEffectTarget(TARGET_MODEL, newTarget, TARGET_ATTACHMENT)
                endif
            endif
            set .target = newTarget
        endmethod
      
        static method create takes thistype head, real dx, real dy returns thistype
            local thistype this = .allocate()

            set .dx = dx
            set .dy = dy
            set .target = null
          
            set .next = head.next
            set .prev = head
            set .next.prev = this
            set .prev.next = this
            return this
        endmethod
      
        static method head takes nothing returns thistype
            local thistype this = .allocate()
            set .next = 0
            set .prev = 0
            return this
        endmethod
      
    endstruct
  
  
    //The self-sorting list, using an Insertion Sort algorithm
    private struct List
  
        private real value
        readonly unit unit
        readonly thistype next
        readonly thistype prev
      
        method destroy takes nothing returns nothing
            //Remove List
            set .prev.next = .next
            set .next.prev = .prev
            set .value = 0
            set .next = 0
            set .unit = null
            call .deallocate()
        endmethod
      
        static method empty takes nothing returns nothing
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0
                call .destroy()
                set this = thistype(0).next
            endloop
        endmethod
      
        static method add takes unit u, real hp, integer count returns nothing
            local thistype node = thistype(0).next  //head node is thistype(0)
            local boolean insert = true
            local thistype this
            static if PRIORITIZE_HERO then
                local boolean isHero = IsUnitType(u, UNIT_TYPE_HERO)
            endif
            //Insert in the current list
            loop
                exitwhen node == 0
                static if PRIORITIZE_HERO then
                    if isHero then
                        exitwhen hp < node.value or not IsUnitType(node.unit, UNIT_TYPE_HERO)
                    else
                        exitwhen hp < node.value and not IsUnitType(node.unit, UNIT_TYPE_HERO)
                    endif
                else
                     exitwhen hp < node.value
                endif
                set count = count - 1
                //If it will not get included in the count anyway, end the insertion and do not insert it in the list
                if count == 0 then
                    set insert = false
                    exitwhen true
                endif
                set node = node.next
            endloop
            //Insert 'this' before 'node'
            if insert then
                set this = .allocate()
                set .unit = u
                set .value = hp
                set .next = node
                set .prev = node.prev
                set .next.prev = this
                set .prev.next = this
            endif
        endmethod
    endstruct
  
    private struct Effect
  
        private lightning laser
        private real x1
        private real y1
        private real x2
        private real y2
      
        private thistype next
        private thistype prev
      
        private method destroy takes nothing returns nothing
            //Remove from List
            set .prev.next = .next
            set .next.prev = .prev
            //Clean ups
            call DestroyLightning(.laser)
            set .laser = null
            call .deallocate()
        endmethod
      
        method destroyAll takes nothing returns nothing
            local thistype node = .next
            loop
                exitwhen node == 0
                call node.destroy()
                set node = node.next
            endloop
            call .destroy()
        endmethod
      
        //Used on head instances
        method updateHead takes real dx, real dy returns nothing
            local thistype node = .next
            loop
                exitwhen node == 0
                set node.x1 = node.x1 + dx
                set node.y1 = node.y1 + dy
                set node.x2 = node.x2 + dx
                set node.y2 = node.y2 + dy
                call MoveLightningEx(node.laser, false, node.x1, node.y1, 0, node.x2, node.y2, 0)
                set node = node.next
            endloop
        endmethod
      
        static method create takes thistype head, real x1, real y1, real x2, real y2, player p returns thistype
            local thistype this = .allocate()
            set .laser = AddLightning(LASER_CODE, false, x1, y1, x2, y2)
            static if not VISIBLE_LASER then
                if IsPlayerEnemy(GetLocalPlayer(), p) then
                    call SetLightningColor(.laser, 0, 0, 0, 0)
                endif
            endif
            set .x1 = x1
            set .y1 = y1
            set .x2 = x2
            set .y2 = y2
            set .next = head.next
            set .prev = head
            set .next.prev = this
            set .prev.next = this
            return this
        endmethod
      
        private static constant real PLANE_SIGHT_HALF_ANGLE_RAD = 0.5*PLANE_SIGHT_ANGLE*bj_DEGTORAD
      
        static method head takes real x, real y, real angle, real radius, player owner returns thistype
            local thistype this = .allocate()
            local real dx = Cos(angle + PLANE_SIGHT_HALF_ANGLE_RAD)
            local real dy = Sin(angle + PLANE_SIGHT_HALF_ANGLE_RAD)
            local real limit = angle + PLANE_SIGHT_HALF_ANGLE_RAD
            local real tempAngle
            local real tempX
            local real tempY
          
            set .next = 0
            set .prev = 0
            //Create AREA_SFX radius
            call Effect.create(this, x + MIN_HIT_DISTANCE*dx, y + MIN_HIT_DISTANCE*dy, x + radius*dx, y + radius*dy, owner)
            set dx = Cos(angle - PLANE_SIGHT_HALF_ANGLE_RAD)
            set dy = Sin(angle - PLANE_SIGHT_HALF_ANGLE_RAD)
            call Effect.create(this, x + MIN_HIT_DISTANCE*dx, y + MIN_HIT_DISTANCE*dy, x + radius*dx, y + radius*dy, owner)

            //Create AREA_SFX curve
            //! textmacro AERIAL_AID_AREA_SFX_CURVE takes RADIUS
                set tempAngle = angle - PLANE_SIGHT_HALF_ANGLE_RAD
                set tempX = x + $RADIUS$*Cos(tempAngle)
                set tempY = y + $RADIUS$*Sin(tempAngle)
                loop
                    set tempAngle = tempAngle + LASER_DISTANCE/$RADIUS$
                    exitwhen tempAngle > limit
                    set dx = x + $RADIUS$*Cos(tempAngle)
                    set dy = y + $RADIUS$*Sin(tempAngle)
                    call Effect.create(this, tempX, tempY, dx, dy, owner)
                    set tempX = dx
                    set tempY = dy
                endloop
                call Effect.create(this, tempX, tempY, x + $RADIUS$*Cos(limit), y + $RADIUS$*Sin(limit), owner)
            //! endtextmacro
                      
            //! runtextmacro AERIAL_AID_AREA_SFX_CURVE("MIN_HIT_DISTANCE")
            //! runtextmacro AERIAL_AID_AREA_SFX_CURVE("radius")
            return this
        endmethod
      
      
    endstruct
  
    private struct Plane
      
        //Spell Mechanics
        private unit caster
        private unit plane
        private player owner
        private integer lvl
        private boolean toggled
        private boolean outside
        private real duration
      
      
        //Gun-related
        private Gun gunHead
        private real radius
        private integer gunCount
        private real gunDamage
        private real gunFireTime
        private real gunCtr     //counter for gun firing rate
      
        //Paratrooper-related
        private real soldierSpawnRate
        private real soldierCtr   //counter for paratrooper deployment
        private boolean deploy
        private integer spawnCtr    //number of paratrooper deployed
      
        //Visual Effect related
        private Effect sfxHead
      
        //Movement
        private real x      //Contains the location of the Plane, the Plane is not meant
        private real y      //to be moved by Moving effects such as Swap, Hook, Knockback, etc.
        private real z
      
        private real angle
        private real dx
        private real dy
        private real dz
      
        private thistype next
        private thistype prev
      
        private static timer t = CreateTimer()
        private static group g = CreateGroup()
      
        static if LIBRARY_Table then
            private static Table tb
            private static Table tbCount
        else
            private static hashtable hash = InitHashtable()
        endif
      
        static if not LIBRARY_BonusMod then
            private static constant integer BONUS_ARMOR = 0
        endif
      
        private method destroy takes nothing returns nothing
            local integer id = GetHandleId(.caster)
            //Remove from List
            set .prev.next = .next
            set .next.prev = .prev
            if thistype(0).next == 0 then
                call PauseTimer(t)
            endif

            //Table Clean-up
            static if LIBRARY_Table then
                set tbCount[id] = tbCount[id] - 1
                if tbCount[id] == 0 then
                    call tb.remove(id)
                    call tbCount.remove(id)
                    call UnitRemoveAbility(.caster, TOGGLE_ID)
                    call UnitRemoveAbility(.caster, SPELL_ID_DEPLOY)
                endif
            else
                call SaveInteger(hash, id, 1, LoadInteger(hash, id, 1) - 1)
                if LoadInteger(hash, id, 1) == 0 then
                    call FlushChildHashtable(hash, id)
                    call UnitRemoveAbility(.caster, TOGGLE_ID)
                    call UnitRemoveAbility(.caster, SPELL_ID_DEPLOY)
                endif
            endif
            //Destroy Handles
            if UnitAlive(.plane) then
                call RemoveUnit(.plane)
            endif
            //For perfect clean-up
            set .caster = null
            set .plane = null
            //Recycle Index
            call .deallocate()
        endmethod

        //Avoid recalculating them everytime
        private static constant real PLANE_SIGHT_HALF_ANGLE_RAD = 0.5*PLANE_SIGHT_ANGLE*bj_DEGTORAD
        private static constant real SLOPE_CHECK = MIN_HIT_DISTANCE/PLANE_HEIGHT
      
        private method update takes nothing returns nothing
            local Gun gun = .gunHead.next
            local boolean withinPlay    //within Playable Area
            local List list
            local unit u
            local real x
            local real y
            local real z
            local real dx
            local real dy
            local real angle
            local real diff
            local real height
          
            //Movement
            set .x = .x + .dx
            set .y = .y + .dy
          
            //Safety check
            set withinPlay = .x < playMaxX and .x > playMinX and .y < playMaxY and .y > playMinY
            if withinPlay then
                call SetUnitX(.plane, .x)
                call SetUnitY(.plane, .y)
            endif
          
            //Hide/Unhide the Plane when leaving/entering playable map area
            if .outside then
                if withinPlay then
                    set .outside = false
                    call ShowUnit(.plane, true)
                endif
            else
                if not withinPlay then
                    set .outside = true
                    call ShowUnit(.plane, false)
                endif
            endif
          
            if .duration > 0 and UnitAlive(.plane) then
                //Change Plane Height
                if .z > PLANE_HEIGHT then
                    set .z = .z + .dz
                    if .z < PLANE_HEIGHT then
                        set .z = PLANE_HEIGHT
                    endif
                    call SetUnitFlyHeight(.plane, .z, 0)
                endif
              
                //Will only do some action if it has stable height
                if .z == PLANE_HEIGHT then
                    //Create SFX Laser Border when the Plane is active, it will only happen once
                    if .sfxHead == 0 then
                        set .sfxHead = Effect.head(.x, .y, .angle, .radius, .owner)
                    else
                        //Update SFX
                        call .sfxHead.updateHead(.dx, .dy)
                    endif
          
                    set .duration = .duration - TIMEOUT
                    //Targeting
                    call GroupEnumUnitsInRange(g, .x, .y, .radius, null)
                    call MoveLocation(l, .x, .y)
                    set z = GetLocationZ(l) + PLANE_HEIGHT
                    loop
                        set u = FirstOfGroup(g)
                        exitwhen u == null
                        call GroupRemoveUnit(g, u)

                        if TargetFilter(u, .owner) then
                            set x = GetUnitX(u)
                            set y = GetUnitY(u)
                            set dx = x - .x
                            set dy = y - .y
                            set angle = Atan2(dy, dx)
                            set diff = RAbsBJ(angle - .angle)
                            //Check if it is within range of gun angle
                            if RMinBJ(diff, 2*bj_PI - diff) <= PLANE_SIGHT_HALF_ANGLE_RAD then
                                //Then check if it is within range of gun pitch
                                call MoveLocation(l, x, y)
                                //GetUnitFlyHeight(u) + GetLocationZ(l) = absolute z of the target
                                //recycle variable 'real angle' as the check distance
                                set angle = SLOPE_CHECK*(z - GetUnitFlyHeight(u) - GetLocationZ(l)) //recycled real-type variable
                                if dx*dx + dy*dy >= angle*angle then 
                                    call List.add(u, GetWidgetLife(u), .gunCount)
                                endif
                            endif
                        endif
                    endloop
                    //Update Gun Targets
                    set list = List(0).next
                    loop
                        exitwhen gun == 0
                        call gun.update(list.unit)
                        set list = list.next
                        //If there are more guns than targets, go back to the first target so that all guns are active
                        if list == 0 then
                            set list = List(0).next
                        endif
                        set gun = gun.next
                    endloop
                    //Empty the list
                    call List.empty()
                    //Are the guns ready to fire?
                    set .gunCtr = .gunCtr + TIMEOUT
                    if .gunCtr >= .gunFireTime then
                        set .gunCtr = .gunCtr - .gunFireTime
                        set gun = .gunHead.next
                        loop
                            exitwhen gun == 0 or gun.target == null
                            set x = GetUnitX(gun.target)
                            set y = GetUnitY(gun.target)
                            call MoveLocation(l, x, y)
                            call Bullet.create(.x + gun.dx, .y + gun.dy, z, x, y, GetUnitFlyHeight(gun.target) + GetLocationZ(l), .plane, .owner, .gunDamage)
                            set gun = gun.next
                        endloop
                    endif
                    //Are paratroopers commanded to deploy?
                    if .deploy and .spawnCtr < MaxSpawnCount(.lvl) then
                        set .soldierCtr = .soldierCtr + TIMEOUT
                        if .soldierCtr >= .soldierSpawnRate then
                            set .soldierCtr = .soldierCtr - .soldierSpawnRate
                            if withinPlay then
                                call Paratrooper.create(.owner, .lvl, .x, .y, .z)
                                set .spawnCtr = .spawnCtr + 1
                            endif
                        endif
                    endif
                endif
            else
                //Destroy Guns
                if .gunHead != 0 then
                    loop
                        exitwhen gun == 0
                        call gun.destroy()
                        set gun = gun.next
                    endloop
                    call .gunHead.destroy()
                    set .gunHead = 0
                endif
                //Destroy Effects
                if .sfxHead != 0 then
                    call .sfxHead.destroyAll()
                    set .sfxHead = 0
                endif
                //Make the plane rise again until it is no longer seen in the screen
                set .z = .z - .dz
                if .z > PLANE_HEIGHT_INITIAL or not UnitAlive(.plane) then
                    call .destroy()
                else
                    call SetUnitFlyHeight(.plane, .z, 0)
                endif
            endif
        endmethod

      
        private static method pickAll takes nothing returns nothing
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0
                call .update()
                set this = .next
            endloop
        endmethod
      
        //Delayed toggle
        private static method toggle takes nothing returns nothing
            local timer delay = GetExpiredTimer()
            static if LIBRARY_Table then
                local thistype this = tb[GetHandleId(delay)]
            else
                local thistype this = LoadInteger(hash, GetHandleId(delay), 0)
            endif
          
            set .toggled = true
            call UnitAddAbility(.caster, TOGGLE_ID)
            call UnitAddAbility(.caster, SPELL_ID_DEPLOY)
            call DestroyTimer(delay)
            set delay = null
        endmethod
      
        private static method onCast takes nothing returns nothing
            local thistype this = .allocate()
            local timer delay = CreateTimer()
            local integer i = 1
            local integer id
            local real x
            local real y
            local real cos
            local real sin
            local real spd
            local real gunX
            local real gunY
      
            //Spell Data and Initializations
            set .caster = GetTriggerUnit()
            set .owner = GetTriggerPlayer()
            set .deploy = false
            set .sfxHead = 0
            set .lvl = GetUnitAbilityLevel(.caster, SPELL_ID)
            set .spawnCtr = 0
            set .gunHead = Gun.head()
            set .gunCtr = 0
            set .gunFireTime = GunFiringRate(.lvl)
            set .gunDamage = GunDamage(.lvl)
            set .soldierCtr = 0
            set .soldierSpawnRate = SpawnRate(.lvl)
            set .duration = Duration(.lvl)
            set .radius = GunRadius(.lvl)
            set x = GetSpellTargetX()
            set y = GetSpellTargetY()
            set .angle = Atan2(y - GetUnitY(.caster), x - GetUnitX(.caster))
            set cos = Cos(.angle)
            set sin = Sin(.angle)
            set .gunCount = GunCount(.lvl)
            loop
                exitwhen i > .gunCount
                set gunX = GunOffsetX(i, .gunCount)
                set gunY = GunOffsetY(i, .gunCount)
                call Gun.create(.gunHead, gunX*cos - gunY*sin, gunY*cos + gunX*sin)
                set i = i + 1
            endloop
          
            //Movement Data
            set spd = PlaneSpeed(.lvl)*TIMEOUT
            set .dx = spd*cos
            set .dy = spd*sin
            set .dz = -PLANE_FALL_SPEED*TIMEOUT
            //Calculate the spawn loc based on duration and target location with an offset for the rise/fall of the plane
            set .x = x - (0.5*.duration + (PLANE_HEIGHT_INITIAL - PLANE_HEIGHT)/PLANE_FALL_SPEED)*PlaneSpeed(.lvl)*cos
            set .y = y - (0.5*.duration + (PLANE_HEIGHT_INITIAL - PLANE_HEIGHT)/PLANE_FALL_SPEED)*PlaneSpeed(.lvl)*sin
            set .z = PLANE_HEIGHT_INITIAL
            if .x > playMaxX or .x < playMinX or .y > playMaxY or .y < playMinY then
                //Safety precaution, make sure plane is not outside bounds to prevent fatal error
                //recycle 'x' and 'y' as the spawn loc, because .x and .y must not be overwritten
                set x = .x
                set y = .y
                static if LIBRARY_WorldBounds then
                    if x > WorldBounds.maxX then
                        set x = WorldBounds.maxX
                    elseif x < WorldBounds.minX then
                        set x = WorldBounds.minX
                    endif
                    if y > WorldBounds.maxY then
                        set y = WorldBounds.maxY
                    elseif y < WorldBounds.minY then
                        set y = WorldBounds.minY
                    endif
                else
                    if x > WorldBounds2.maxX then
                        set x = WorldBounds2.maxX
                    elseif x < WorldBounds2.minX then
                        set x = WorldBounds2.minX
                    endif
                    if y > WorldBounds2.maxY then
                        set y = WorldBounds2.maxY
                    elseif y < WorldBounds2.minY then
                        set y = WorldBounds2.minY
                    endif
                endif
                set .outside = true
                set .plane = CreateUnit(.owner, PLANE_ID, x, y, .angle*bj_RADTODEG)
                call ShowUnit(.plane, false)
            else
                set .outside = false
                set .plane = CreateUnit(.owner, PLANE_ID, .x, .y, .angle*bj_RADTODEG)
            endif
            call SetUnitFlyHeight(.plane, .z, 0)
            static if not CONTROLLABLE then
                call SetUnitTurnSpeed(.plane, 0.0)
            endif
          
            //Stats
            static if LIBRARY_BonusMod then
                call UnitSetBonus(.plane, BONUS_ARMOR, PlaneArmor(lvl))
            else
                call UnitSetBonus2(.plane, BONUS_ARMOR, PlaneArmor(lvl))
            endif
            static if LIBRARY_SetUnitMaxState then
                call SetUnitMaxState(.plane, UNIT_STATE_MAX_LIFE, PlaneLife(lvl))
            else
                call SetUnitMaxState2(.plane, UNIT_STATE_MAX_LIFE, PlaneLife(lvl))
            endif
          
            //Store caster handle id for onDeploy data retrieval
            set id = GetHandleId(.caster)
            static if LIBRARY_Table then
                set tb[id] = this
                set tbCount[id] = tbCount[id] + 1
                set tb[GetHandleId(delay)] = this
            else
                call SaveInteger(hash, id, 0, this)
                call SaveInteger(hash, id, 1, LoadInteger(hash, id, 1) + 1)
                call SaveInteger(hash, GetHandleId(delay), 0, this)
            endif
          
            //Toggle Ability
            call TimerStart(delay, 0., false, function thistype.toggle)
          
            //Linked-list
            set .next = 0
            set .prev = thistype(0).prev
            set .next.prev = this
            set .prev.next = this
            if .prev == 0 then
                call TimerStart(t, TIMEOUT, true, function thistype.pickAll)
            endif
        endmethod
      
        private static method onDeploy takes nothing returns nothing
            static if LIBRARY_Table then
                local thistype this = tb[GetHandleId(GetTriggerUnit())]
            else
                local thistype this = LoadInteger(hash, GetHandleId(GetTriggerUnit()), 0)
            endif

            set .deploy = true
          
            //Toggle Ability
            set .toggled = false
            call UnitRemoveAbility(.caster, TOGGLE_ID)
            call UnitRemoveAbility(.caster, SPELL_ID_DEPLOY)
        endmethod
      
        static if not LIBRARY_SpellEffectEvent then
            private static method castCondition takes nothing returns boolean
                local integer spell = GetSpellAbilityId()
                if spell == SPELL_ID then
                    call thistype.onCast()
                elseif spell == SPELL_ID_DEPLOY then
                    call thistype.onDeploy()
                endif
                return false
            endmethod
        endif

        private static method onInit takes nothing returns nothing
            local integer i = 0
            static if PRELOAD then
                local unit u1 = CreateUnit(DUMMY_OWNER, PARATROOPER_AIR_ID, 0, 0, 0)
                local unit u2 = CreateUnit(DUMMY_OWNER, PLANE_ID, 0, 0, 0)
            endif
            static if LIBRARY_SpellEffectEvent then
                call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
                call RegisterSpellEffectEvent(SPELL_ID_DEPLOY, function thistype.onDeploy)
            else
                local trigger t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddCondition(t, Condition(function thistype.castCondition))
            endif
            static if LIBRARY_Table then
                set tb = Table.create()
                set tbCount = Table.create()
            endif
            static if not LIBRARY_BonusMod then
                call BonusMod_Init()
            endif
            static if not LIBRARY_SetUnitMaxState then
                call SetUnitMaxState_Init()
            endif
            //For toggling from Aerial Aid -> Deploy Paratroopers and vice versa
            loop
                exitwhen i == bj_MAX_PLAYER_SLOTS
                call SetPlayerAbilityAvailable(Player(i), TOGGLE_ID, false)
                set i = i + 1
            endloop
            //Playable Map bounds for non-locust units
            set playMinX = GetRectMinX(bj_mapInitialPlayableArea)
            set playMaxX = GetRectMaxX(bj_mapInitialPlayableArea)
            set playMinY = GetRectMinY(bj_mapInitialPlayableArea)
            set playMaxY = GetRectMaxY(bj_mapInitialPlayableArea)
            static if PRELOAD then
                call UnitAddAbility(u1, TRANSFORM_ID)
                call UnitAddAbility(u2, SPELL_ID_DEPLOY)
                call UnitAddAbility(u2, SPELL_ID)
                call RemoveUnit(u1)
                call RemoveUnit(u2)
                set u1 = null
                set u2 = null
            endif
        endmethod
  
    endstruct

endscope


v1.00 - [23 May 2016]
- Initial Release

v1.01 - [29 May 2016]
- Generated structs and functions when WorldBounds, BonusMod and SetUnitMaxState is not present is now private, avoiding redeclaration when other spells uses these systems as optional requirement but those other spells uses a non-private structs and functions as replacement.
- Included the Dummy.mdx in the credit list.
Contents

Aerial Aid v1.01 (Map)

Level 10
Joined
Aug 21, 2010
Messages
316
It seems that the installation requires imports.File size 341.42
If so, why it was not mentioned in the description?


"How to install" not against the rules
 
Last edited:
Level 22
Joined
Feb 6, 2014
Messages
2,466
Where's the gunshot sound?
It's easier if the sound is in the model itself and not coded. That can easily be done on Magos by adding a Sound through Node Manager and adding it at the Bullet's Birth Animation. I completely forgot to consider the sound because I usually test stuffs with no sound.

Btw, how can you put a 1600 line code in the description?
I tried to put my 900+ line of code and Chrome say "Snap, something when wrong".
I just pasted it, but when I did that, Chrome stops responding for a while but I after some time, it did go back to normal.
 
Top