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

Gatling Bot v3.7

235220-albums7952-picture108461.png

235220-albums7952-picture108454.png


Summons an automatic drone at the target location. The drone will attack enemy units around with hail of gun firings.


235220-albums7952-picture108455.png


235220-albums7952-picture108463.gif


235220-albums7952-picture108453.png



  • (Required)
    • Missile
    • RapidSound
    • LockBone
    • CTL
  • (Optional)
    • -

235220-albums7952-picture108456.png


JASS:
scope GatlingBot /*
   
                              Gatling Bot v3.7
                            ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                            Created by: Quilnez

    A. Description
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        Summons an automatic drone at the target location. The drone will
        attack enemy units around with hail of gun firings.
           
       
    B. How to import
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        1. Import these stuffs into your map:
            - (Object Editor - Unit) Missile Dummy
            - (Object Editor - Unit) Gatling Bot
            - (Object Editor - Abilities) Summon Gatling Bot
            - (Import Manager) dummy.mdx
            - Other files are rather optional, you can use yours instead
        2. Import Gatling Bot trigger into your map
        3. Import and configure all required external dependencies properly
        4. Configure the spell properly
        5. Done!


    C. External Dependencies
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
             - CTL                   @ github.com/nestharus/JASS/tree/master/jass/Systems/ConstantTimerLoop32
             - Missile               @ hiveworkshop.com/forums/jass-resources-412/system-missile-207854/
             - RapidSound            @ hiveworkshop.com/threads/snippet-rapidsound.258991/
             - LockBone              @ hiveworkshop.com/forums/submissions-414/snippet-lockbone-259005/
       
       
    D. Credits
    ¯¯¯¯¯¯¯¯¯¯
                  Author    |       Contribution(s)
            ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
             Nestharus      |  CTL
             BPower         |  Missile
             Muoteck        |  BTNSpiderbot
             Vexorian       |  dummy.mdx
             Gottfrei       |  LT-1.mdx
             Grey Knight    |  Bullet.mdx
       
    */
    private keyword SFX
    /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
                       ______________________________
                              General Settings 
                       ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                   */
    globals
       
        // Main spell's raw code
        private constant integer    SPELL_ID             = 'A000'
       
        // Drone unit's raw code
        private constant integer    DRONE_ID             = 'h000'
       
        // Dummy unit's raw code (only useful if you don't use MissileRecycler)
        private constant integer    DUMMY_ID             = 'dumi'
       
        // Range to indicate that the master is moving
        private constant real       MOVE_RANGE_INDICATOR = 64.0
       
        // Drone model's bone that will be rotated around
        private constant string     LOCKED_BONE          = BONE_PART_CHEST
       
        // This value will make sure the drone will
        // face straight forward
        private constant real       LOCKED_BONE_ZOFFSET  = 100.0
       
        // If true, the accuracy's inconsistency will be calculated only for once
        // Will be noticeable when the drone fires more than one missile at once
        private constant boolean    ACCURACY_MODE        = false
       
        // "move" order id
        private constant integer    MOVE_ORDER           = 851986
       
        // Drone's animation that going to be played
        // when it attacks
        private constant string     ATTACK_ANIMATION     = "attack"
       
        // If true all damages by drone will be considered to come
        // from it's master instead
        private constant boolean    CASTER_DAMAGE_SOURCE = true
       
        // If false, the missile will only travel from drone's
        // position to it's attack target, ignoring it's optimum
        // attack range
        private constant boolean    STATIC_ATTACK_RANGE  = true
       
        // Attack & damage type of dealt damage
        private constant attacktype ATTACK_TYPE          = ATTACK_TYPE_PIERCE
        private constant damagetype DAMAGE_TYPE          = DAMAGE_TYPE_NORMAL
       
        // Time for special effects to complete their animations
        private constant real       SFX_DECAY_TIME       = 3.0
       
        // Played when the drone attacks
        private constant string     FIRE_SOUND_PATH      = "war3mapImported\\fire.wav"
        private constant integer    FIRE_SOUND_VOLUME    = 200
       
        // Missile's detail
        private constant string     MISSILE_MODEL_PATH   = "war3mapImported\\Bullet.mdx"
        private constant real       MISSILE_LAUNCH_Z     = 40.0
        private constant real       MISSILE_IMPACT_Z     = 40.0
        private constant real       MISSILE_COLLISION_SIZE = 45.0
       
    endglobals
   
    private module DroneInitialization
/*                     ______________________________
                           Drone's attribute data 
                       ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                  
            Notes:
                - This block will execute every time a drone
                  is created
                - Use "level" to refer to drone's level
                - Use ".unit" to refer to the drone unit                     */
               
        // 1. Damage amount
        // Drone's damage is increased by 6 pts every 5 levels
        set .dmgTop    = 14.0 + 6.0*I2R(level/5)
        set .dmgLow    = 11.0 + 6.0*I2R(level/5)
       
        // 2. Piercing attack
        // If true, drone's missiles will become piercing
        // If ability's level is equal or more than 15, drone's attacks
        // will become piercing
        set .setPierce = level >= 15
       
        // 3. Impact damage
        // Area damage dealt when drone's missile is destroyed
        if level < 20 then
            set .impactDmg = 0.0
        else
            set .impactDmg = GetRandomReal(.dmgLow, .dmgTop)
        endif
       
        // 4. Impact damage AoE
        // AoE range of impact damage
        // Impact events will only fire if this AoE > 0
        if level < 20 then
            set .impactAoE = 0.0
        else
            set .impactAoE = 200.0
        endif
       
        // 5. Missile count
        // Number of missile launched at once
        // If ability's level is less than 10, drone fires 1 missile
        // at once. Else it fires two missiles at once.
        if level < 10 then
            set .mslCount  = 1
        else
            set .mslCount  = 2
        endif
       
        // 6. Missile spacing
        // Distance between missile. Works if only mslCount is more than 1
        set .mslSpace  = 30
       
        // 7.Accuracy
        // Drone's firing accuracy. Higher value means less accurate
        // it would be
        set .accuracy  = 45.0*bj_DEGTORAD
       
        // 8. Drone's head turn rate
        // Drone's head can turn 4 degrees per tick (0.03125 s)
        set .turnRate  = 4.0*bj_DEGTORAD
       
        // 9. Acquisition range
        // Drone can acquire targets within 800 range (level 1-4)
        // and 1000 range on higher levels
        if level < 5 then
            set .acqRange  = 800.0
        else
            set .acqRange  = 1000.0
        endif
       
        // 10. Attack range
        // If ability's level is less than 5, drone's attack range
        // is 600. Else it's 800.
        if level < 5 then
            set .atkRange = 600.0
        else
            set .atkRange = 800.0
        endif
       
        // 11. Attack rate
        // Drone will attack every 0.075s
        set .atkSpeed  = 0.075
       
        // 12. Follow interval
        // Drone will follow its master every 0.5s
        set .fllwRate  = 0.5
       
        // 13. Wander area
        // How far the drone is allowed to wander around
        // it's master
        set .wanderArea = 300.0
       
        // 14. Wander interval
        // Drone will wander around every 4s
        set .wndrRate  = 4.0
       
        // 15. Cautious (self secure)
        // If true, the drone will keep some distances from it's target
        set .cautious  = true
       
/*                     ______________________________
                               Missile's data 
                       ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                            */
       
        // 1. Missile move rate
        // Missiles can move 50 range away per tick
        set .mslSpeed  = 50.0
       
        // 2. Missile acceleration
        // May causes missiles to move faster over times
        set .mslAccel  = 0.0
       
        // 3. Missile homing state
        // If true, the missile will become homing
        set .setHoming = false
       
        // 4. Missile turn rate (in radians)
        // How agile can the missile turn around (if setHoming=true)
        set .mslTrRate = 0.0
       
        // 5. Missile curve in radians (the absolute value must be below HP/2)
        set .mslCurve  = 0.0
       
        // 6. Missile arc in radians (the absolute value must be below HP/2)
        set .mslArc    = 0.0
       
/*                     ______________________________
                            Additional Config. 
                       ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                            */
       
        // Drone has 25 additional health points every level
        call SetUnitState(.unit, UNIT_STATE_LIFE, 125 + 25*level)
        // Drone lasts for 59+level seconds
        call UnitApplyTimedLife(.unit, 'BTLF', 59+level)
       
    endmodule
   
    // p = owner of the drone, t = damage target, level = drone level
    // By default drone will only attack visible alive units
    private function droneTargets takes player p, unit t, integer level returns boolean
        return IsUnitEnemy(t, p) and not IsUnitType(t, UNIT_TYPE_MECHANICAL)
    endfunction
   
/*                     ______________________________
                               Event Catcher
                       ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                            */
   
    private module EventCatcher
       
        // You can use these parameters to manipulate things:
        // - "dex"    refers to the triggering missile instance ..
        //            .. read Missile lib's API for more detail
        // - "source" refers to the source of the damage
        // - "target" refers to the target of the damage
        // - "level"  refers to the drone's level
       
        // Fires when a unit receive damage from missiles
        static method onMissileHit takes Missile dex, unit source, unit target, integer level returns nothing
            // Bonus API to create effects with detailed data
            // call SFX.onPoint(modelPath, facing, scale, x, y, z, duration)
            call SFX.onPoint("war3mapImported\\bullet hit blood.mdx", dex.angle*bj_RADTODEG+180, 1, dex.x, dex.y, dex.z, 3)
        endmethod
       
        // Fires when a missile impacts
        static method onImpact takes Missile dex, integer level returns nothing
            call SFX.onPoint("Abilities\\Weapons\\Rifle\\RifleImpact.mdl", dex.angle*bj_RADTODEG, 1, dex.x, dex.y, dex.z, 1)
        endmethod
       
        // Fires when a unit takes damage from impact
        static method onImpactHit takes Missile dex, unit source, unit target, integer level returns nothing
        endmethod
       
    endmodule
   
    /** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **/
   
    globals
        private constant real INTERVAL = 0.031250000
    endglobals
   
    native  UnitAlive takes unit id returns boolean
    private keyword GatlingBot
   
    private struct ResetDummy
       
        unit dummy
        real dur
        static real MapEdgeX
        static real MapEdgeY
   
        implement CTLExpire
           
            set .dur = .dur - INTERVAL
            if .dur <= 0 then
                call SetUnitX(.dummy, MapEdgeX)
                call SetUnitY(.dummy, MapEdgeY)
                call SetUnitScale(.dummy, 1, 1, 1)
                call destroy()
                set .dummy = null
            endif
           
        implement CTLEnd
       
        static method reset takes unit whichUnit returns nothing
       
            local thistype this = create()
           
            set .dummy = whichUnit
            set .dur   = SFX_DECAY_TIME
           
        endmethod
       
        static method onInit takes nothing returns nothing
            set MapEdgeX = GetRectMaxX(bj_mapInitialPlayableArea)
            set MapEdgeY = GetRectMaxY(bj_mapInitialPlayableArea)
        endmethod
       
    endstruct
   
    private struct SFX
       
        static constant player PASSIVE = Player(PLAYER_NEUTRAL_PASSIVE)
       
        static method onPoint takes string model, real angle, real scale, real x, real y, real z, real dur returns nothing
           
            local unit u
           
            static if LIBRARY_MissileRecycler then
                set u = GetRecycledMissile(x, y, z, angle)
            else
                set u = CreateUnit(PASSIVE, DUMMY_ID, x, y, angle)
                static if not LIBRARY_AutoFly then
                    if UnitAddAbility(u, 'Amrf') and UnitRemoveAbility(u, 'Amrf') then
                    endif
                endif
                call SetUnitFlyHeight(u, z, 0)
            endif
            static if LIBRARY_MissileRecycler then
                call RecycleMissile(u)
                call ResetDummy.reset(u)
            else
                call UnitApplyTimedLife(u, 'BTLF', dur)
            endif
            call SetUnitScale(u, scale, 1, 1)
            call DestroyEffect(AddSpecialEffectTarget(model, u, "origin"))
            set u = null
           
        endmethod
       
    endstruct
   
    private struct Bullet
       
        real distance
        Missile missile
        GatlingBot index
       
        static group Group = CreateGroup()
       
        implement EventCatcher
   
        static method onRemove takes Missile dex returns boolean
       
            local thistype   this = dex.data
            local GatlingBot dex2 = .index
            local unit u
           
            if dex2.impactAoE > 0 then
                call thistype.onImpact(dex, dex2.level)
                // Deal impact damage
                call GroupEnumUnitsInRange(Group, dex.x, dex.y, dex2.impactAoE, null)
                loop
                    set u = FirstOfGroup(Group)
                    exitwhen u == null
                    call GroupRemoveUnit(Group, u)
                    if u == dex2.target or (droneTargets(dex2.owner, u, dex2.level) and UnitAlive(u) and IsUnitVisible(u, dex2.owner)) then
                        // If damage source should be drone's master and the master is available
                        if CASTER_DAMAGE_SOURCE and UnitAlive(dex2.master) then
                            call thistype.onImpactHit(dex, dex2.master, u, dex2.level)
                            call UnitDamageTarget(dex2.master, u, dex2.impactDmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                        else
                            call thistype.onImpactHit(dex, dex2.unit, u, dex2.level)
                            call UnitDamageTarget(dex2.unit, u, dex2.impactDmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                        endif
                    endif
                endloop
            endif
            call ResetDummy.reset(dex.dummy)
            call .destroy()
           
            return true
        endmethod
       
        static method onPeriod takes Missile dex returns boolean
       
            local thistype this   = dex.data
            local GatlingBot dex2 = .index
           
            // If the missile doesn't collide with ground
            if GetUnitFlyHeight(dex.dummy) > .01 then
                set .distance = .distance+dex.speed
                return (dex2.setHoming or not STATIC_ATTACK_RANGE) and .distance > dex2.atkRange
            else
                return true
            endif
           
        endmethod
   
        static method onCollide takes Missile dex, unit justHit returns boolean
       
            local thistype   this = dex.data
            local GatlingBot dex2 = .index
            local real d
       
            if justHit == dex2.target or (droneTargets(dex2.owner, justHit, dex2.level) and UnitAlive(justHit) and IsUnitVisible(justHit, dex2.owner)) then
                set d = GetRandomReal(dex2.dmgLow, dex2.dmgTop)
                // If damage source should be drone's master and the master is available
                if CASTER_DAMAGE_SOURCE and UnitAlive(dex2.master) then
                    call thistype.onMissileHit(dex, dex2.master, justHit, dex2.level)
                    call UnitDamageTarget(dex2.master, justHit, d, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                else
                    call thistype.onMissileHit(dex, dex2.unit, justHit, dex2.level)
                    call UnitDamageTarget(dex2.unit, justHit, d, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                endif
                call dex.hitWidget(justHit)
                // Instantly destroy missile if not piercing
                return not dex2.setPierce
            else
                return false
            endif
           
        endmethod
           
        implement MissileStruct
       
        static method fire takes GatlingBot dex returns nothing
       
            local thistype this
            local integer i   = 0
           
            local real a      = dex.angle-bj_PI/2
            local real cos    = Cos(a)
            local real sin    = Sin(a)
           
            local real d      = (dex.mslSpace*dex.mslCount)/2
            local real x      = GetUnitX(dex.unit)-d*cos
            local real y      = GetUnitY(dex.unit)-d*sin
            local real droneZ = GetUnitFlyHeight(dex.unit)
           
            local real targetX
            local real targetY
            local real targetZ
            local real acc
            local real ar
           
            // If true, the inconsistency will only be calculated once
            static if ACCURACY_MODE then
                set acc = GetRandomReal(-dex.accuracy,dex.accuracy)/2
            endif
            set targetZ = GetUnitFlyHeight(dex.target)
            if STATIC_ATTACK_RANGE then
                set ar = dex.atkRange
            else
                set targetX = GetUnitX(dex.target)
                set targetY = GetUnitY(dex.target)
                set ar = SquareRoot((targetX-x)*(targetX-x)+(targetY-y)*(targetY-y))
            endif
            // Launch missiles
            loop
                exitwhen i >= dex.mslCount
                set this = allocate()
                set .distance = 0
               
                static if not ACCURACY_MODE then
                    set acc = GetRandomReal(-dex.accuracy,dex.accuracy)/2
                endif
               
                set .index          = dex
                set .missile        = Missile.create(x, y, droneZ+MISSILE_LAUNCH_Z, dex.angle+acc, ar, MISSILE_IMPACT_Z+targetZ)
                set .missile.curve  = dex.mslCurve
                set .missile.source = dex.unit
                set .missile.speed  = dex.mslSpeed
                set .missile.model  = MISSILE_MODEL_PATH
                set .missile.arc    = dex.mslArc
                set .missile.data   = this
                set .missile.acceleration = dex.mslAccel
                set .missile.collision    = MISSILE_COLLISION_SIZE
               
                if dex.setHoming then
                    set .missile.target = dex.target
                    set .missile.turn   = dex.mslTrRate
                endif
               
                call launch(.missile)
                // Calculate new offset
                set x = x+dex.mslSpace*cos
                set y = y+dex.mslSpace*sin
                set i = i+1
            endloop
           
        endmethod
       
    endstruct
   
    private struct GatlingBot extends array
       
        real dmgLow
        real dmgTop
        real accuracy
        real acqRange
        real atkRange
        real atkSpeed
       
        real fllwRate
        real wndrRate
        real turnRate
        real impactAoE
        real impactDmg
       
        real mslAccel
        real mslSpace
        real mslSpeed
        real mslTrRate
        real mslCurve
        real mslArc
       
        integer mslCount
        boolean cautious
        boolean setPierce
        boolean setHoming
       
        integer level
        player  owner
       
        unit unit
        unit target
        unit master
       
        real angle
        real masterLocX
        real masterLocY
        real moveDelay
        real attackDelay
        real wanderArea
       
        RSound sound
        static  group TempGroup = CreateGroup()
        static  unit  TempUnit
       
        method getClosestTarget takes real x, real y returns unit
           
            local unit fog
            local real closestDist
            local real x2
            local real y2
            local real d
           
            set TempUnit = null
            call GroupEnumUnitsInRange(TempGroup, x, y, .acqRange, null)
            loop
                set fog = FirstOfGroup(TempGroup)
                exitwhen fog == null
                call GroupRemoveUnit(TempGroup, fog)
                if droneTargets(.owner, fog, .level) and UnitAlive(fog) and IsUnitVisible(fog, .owner) then
                    set x2 = GetUnitX(fog)
                    set y2 = GetUnitY(fog)
                    set d = (x2-x)*(x2-x)+(y2-y)*(y2-y)
                    if TempUnit == null or d<closestDist then
                        set closestDist = d
                        set TempUnit    = fog
                    endif
                endif
            endloop
           
            return TempUnit
        endmethod
       
        implement CTL
           
            local real a
            local real d
            local real r
            local real droneX
            local real droneY
           
            local real targetX
            local real targetY
            local real masterX
            local real masterY
            local unit nt
           
            local boolean b
           
        implement CTLExpire
           
            if UnitAlive(.unit) then
                set droneX = GetUnitX(.unit)
                set droneY = GetUnitY(.unit)
                // If doesn't have target, attempt to obtain one
                if .target == null then
                    set .target = getClosestTarget(droneX, droneY)
                    set a = GetUnitFacing(.unit)*bj_DEGTORAD
                else
                    // If the target is still valid
                    if UnitAlive(.target) and IsUnitVisible(.target, .owner) then
                        set targetX = GetUnitX(.target)
                        set targetY = GetUnitY(.target)
                        set r  = (droneX-targetX)*(droneX-targetX)+(droneY-targetY)*(droneY-targetY)
                        // If target is too far
                        if r > .acqRange*.acqRange then
                            set .target = null
                        // If target is beyond the max attack range
                        elseif r > .atkRange*.atkRange then
                            // Search for new closer target
                            set nt = getClosestTarget(droneX, droneY)
                            if nt != null then
                                // Switch to the new target
                                set .target = nt
                                set targetX = GetUnitX(.target)
                                set targetY = GetUnitY(.target)
                                set nt = null
                            endif
                            // If it's time to move, then allow drone to come closer to target
                            if .moveDelay > .fllwRate then
                                set .moveDelay = 0
                                if .master != null then
                                    set a = Atan2(GetUnitY(.master)-targetY, GetUnitX(.master)-targetX)+GetRandomReal(-bj_PI, bj_PI)/4
                                else
                                    set a = Atan2(droneY-targetY, droneX-targetX)+GetRandomReal(-bj_PI, bj_PI)/4
                                endif
                                call IssuePointOrderById(.unit, MOVE_ORDER, targetX+.atkRange*Cos(a), targetY+.atkRange*Sin(a))
                            endif
                        else
                            if .cautious and .moveDelay > .fllwRate then
                                set .moveDelay = 0
                                if .master != null then
                                    set a = Atan2(GetUnitY(.master)-targetY, GetUnitX(.master)-targetX)+GetRandomReal(-bj_PI, bj_PI)/4
                                else
                                    set a = Atan2(droneY-targetY, droneX-targetX)+GetRandomReal(-bj_PI, bj_PI)/4
                                endif
                                call IssuePointOrderById(.unit, MOVE_ORDER, targetX+.atkRange*Cos(a), targetY+.atkRange*Sin(a))
                            endif
                            // If ready to fire (attack)
                            if .attackDelay > .atkSpeed then
                                set .attackDelay = 0
                                call Bullet.fire(this)
                                call SetUnitAnimation(.unit, ATTACK_ANIMATION)
                                if .sound != 0 then
                                    call .sound.play(droneX, droneY, MISSILE_LAUNCH_Z, FIRE_SOUND_VOLUME)
                                endif
                            else
                                set .attackDelay = .attackDelay+INTERVAL
                            endif
                        endif
                        set a = Atan2(targetY-droneY, targetX-droneX)
                    else
                        if .target != null then
                            set .moveDelay = .wndrRate-.fllwRate
                            set .target = null
                        endif
                        set a = GetUnitFacing(.unit)*bj_DEGTORAD
                    endif
                endif
               
                set b = false
                // Update the bone rotation angle
                if .turnRate > 0 and Cos(.angle-a) < Cos(.turnRate) then
                    if Sin(a-.angle) >= 0 then
                        set .angle = .angle+.turnRate
                    else
                        set .angle = .angle-.turnRate
                    endif
                    set b = true
                elseif .angle != a then
                    set .angle = a
                    set b = true
                endif
               
                if b then
                    // If drone doesn't have target yet
                    if .target == null then
                        set d = LOCKED_BONE_ZOFFSET+GetUnitFlyHeight(.unit)
                        call LockBone.lockAtAngle(.unit, LOCKED_BONE, .angle, d, d)
                    else
                        set d = SquareRoot((targetX-droneX)*(targetX-droneX)+(targetY-droneY)*(targetY-droneY))
                        call LockBone.lockAtAngle(.unit, LOCKED_BONE, .angle, d, LOCKED_BONE_ZOFFSET+GetUnitFlyHeight(.target))
                    endif
                endif
               
                if UnitAlive(.master) then
                    set masterX = GetUnitX(.master)
                    set masterY = GetUnitY(.master)
                    // If master is moving
                    if (.masterLocX-masterX)*(.masterLocX-masterX)+(.masterLocY-masterY)*(.masterLocY-masterY) > MOVE_RANGE_INDICATOR*MOVE_RANGE_INDICATOR then
                        if .moveDelay > .fllwRate then
                            set .moveDelay = 0
                            set a = (GetUnitFacing(.master)*bj_DEGTORAD+bj_PI)+GetRandomReal(-bj_PI, bj_PI)/2
                            set d = GetRandomReal(0, .wanderArea)
                            call IssuePointOrderById(.unit, MOVE_ORDER, .masterLocX+d*Cos(a), .masterLocY+d*Sin(a))
                        endif
                        set .masterLocX = masterX
                        set .masterLocY = masterY
                    elseif .moveDelay > .wndrRate then
                        set .moveDelay = 0
                        set a = GetRandomReal(-bj_PI, bj_PI)
                        set d = GetRandomReal(0, .wanderArea)
                        call IssuePointOrderById(.unit, MOVE_ORDER, .masterLocX+d*Cos(a), .masterLocY+d*Sin(a))
                    endif
                    set .moveDelay = .moveDelay+INTERVAL
                endif
            else
                if .sound != 0 then
                    call .sound.kill()
                    set  .sound = 0
                endif
                call destroy()
                set .unit   = null
                set .target = null
                set .master = null
            endif
           
        implement CTLEnd
       
        static method onCast takes nothing returns boolean
           
            local thistype this
           
            if GetSpellAbilityId() == SPELL_ID then
                set this = create()
           
                set .owner  = GetTriggerPlayer()
                set .angle  = GetRandomReal(-bj_PI, bj_PI)
                set .sound  = RSound.create(FIRE_SOUND_PATH, true, false, 12700, 12700)
                set .unit   = CreateUnit(.owner, DRONE_ID, GetSpellTargetX(), GetSpellTargetY(), .angle*bj_RADTODEG)
                set .level  = GetUnitAbilityLevel(GetTriggerUnit(), SPELL_ID)
                set .target = null
               
                set .attackDelay = 999999
                set .master      = GetTriggerUnit()
                set .masterLocX  = GetUnitX(.master)
                set .masterLocY  = GetUnitY(.master)
                set .moveDelay   = 0.0
               
                implement DroneInitialization
            endif
           
            return false
        endmethod
       
        static method onInit takes nothing returns nothing
           
            local trigger t = CreateTrigger()
           
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.onCast))
           
        endmethod
   
    endstruct
   
endscope

235220-albums7952-picture108457.png


Author
Contribution
NestharusCTL
BPowerMissile
MuoteckBTNSpiderbot
Vexoriandummy.mdx
Grey KnightBullet.mdx
GottfreiLT-1.mdx

235220-albums7952-picture108458.png


Code:
v1.0
    - Uploaded

v1.1
    - Fixed fatal mistake at timer releasing
    - Removed bunch of leaks
    - Improved demo map
    - Many more

v1.2
    - Added new attributes
    - Homing is now working
    - So many improvements
    - Fixed some errors

v1.3
    - One new attribute
    - Improved my external libraries
    - Now missile can collide with ground
    - Slight better drone behavior
    - Some bug fixes

v1.4
    - I was focusing to improve my external libraries
    - Fixed some errors in demo spell
    - Now drone will behave normally without being binded
    - Many pedantic improvements
    - Some new attributes
    - Better and easier demo map

v1.5
    - UnitIndexer library is now correctly installed
    - Fixed slight mistakes at new target acquiring behavior
    - Now it allows you to set different targets for every created drone,
      credits to The Wrecker's filtering system
    - Some improvements at my LockBone library,
      minor glitch at bone locking has fixed.
    - Bunch of minor code improvements
    - Example ability is extended up to 20 levels

v1.6
    - Now it's completely compatible for flying drone!
    - Improved demo map, including boss and flying drone
    - Bone locking x&y offset is now dynamic
    - Some new attributes

v1.7
    - Fixed bug at ExSound library, mis-termination has prevented
    - Added minimum follow range aspect

v1.7a
    - I'm sorry I was too lazy to test after last slight updates
    - Fixed wrong drone behavior
    - Fixed impact sfx bug on demo spell

v1.7b
    - There were massive thread crashes in droid library. They've been fixed.

v1.7c
    - Fixed small glitch at demo boss.

v1.8
    - Fixed logical mistakes at bot behavior
    - Fixed bone locking z offset glitch
    - Removed ExSound instance leak
    - Tons of minor improvements
    - Improved documentations
    - Improved variable names

v3.0
    - Updated Missile library to the most recent version.
    - Now it only concentrates on summoning gatling bot. No longer able to create other types of drone.
    - Heavily reduced code size.
    - New missile model. Credits to Grey Knight for this sweet bullet model.
    - New structure of configuration, to make it more comfortable for users.
    - Improved documentations.
    - GroupUtils is removed from requirements. Still there are a lot of requirements which is a bit of scary. : p

v3.1
    - Now drones can attack flying unit
    - Updated ExSound library
    - New fire sound effect
    - Improved demo (new swarm unit, etc.)
    - Improved installation guide

v3.2
    - Upgraded LockBone library
    - TimedEffect is no longer required
    - Now missiles can't be removed instantly
    - Improved some of old behaviors
    - New behavior for drones (self secure)
    - Fixed bug on death master
    - Fixed some distance calculations mistakes
    - Some drone attributes balancings
    - Improved demo map

v3.4
    - FPS drop is fixed!
    - Utilized new Missile feature of hitWidget
    - Some documentation improvements
    - Updated LockBone lib (fixed double free bug)

v3.5
    - Now drone will return to the master immediately (depends on follow rate) after kill
    - New on hit effect (new struct for creating effects)
    - Added event catcher, removed some drone attributes
    - Updated LockBone library
    - Fixed wrong ability tooltip
    - Improved demo

v3.6
    - Removed 2 libs from requirements, FilteringSystem and GetClosestWidget
    - Some drone attributes are moved to static configuration
    - Correctly utilized Missile arc and curve feature
    - Replaced TimerUtils with CTL
    - A lot of nerdy improvements
    - Removed some unused variables
    - New demo terrain layout, less boring :p
    - Improved documentation

v3.7
    - Replaced ExtendedSound with its newer version, RapidSound, more robust
Contents

Summon Gatling Bot (Map)

Reviews
Bribe: 31 May 2016 Re-approved. Well-coded, as efficient as possible and no discernible errors. The end result is pleasing to see and could help mapmakers create some very interesting gameplay! Flux: 10th May 2016 Requested to be set to pending...

Moderator

M

Moderator

Bribe: 31 May 2016
Re-approved. Well-coded, as efficient as possible and no discernible errors. The end result is pleasing to see and could help mapmakers create some very interesting gameplay!

Flux: 10th May 2016
Requested to be set to pending due to huge changes/update.

02:48, 02nd Apr 2015
IcemanBo:
The system helps to create supportive drones without too much of effort.
http://www.hiveworkshop.com/forums/spells-569/gatling-bot-v1-9-a-259137/#post2670158

 
Level 7
Joined
Oct 11, 2008
Messages
304
JASS:
        External Dependencies
        ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
            */requires /*
                */ GetClosestWidget,    /* hiveworkshop.com/forums/jass-resources-412/snippet-getclosestwidget-204217/
                */ ExtendedSound,       /* hiveworkshop.com/forums/submissions-414/snippet-extendedsound-258991/
                */ TimerUtils,          /* wc3c.net/showthread.php?t=101322
                */ LockBone,            /* hiveworkshop.com/forums/submissions-414/snippet-lockbone-259005/
                */ Missile              /* hiveworkshop.com/forums/jass-resources-412/system-missile-207854/
                   UnitIndexer             github.com/nestharus/JASS/tree/master/jass/Systems/Unit%20Indexer

Did you really requires UnitIndexer or you just forget it? :p haha

I know is not the primary function of your library, but maybe a way to allow custom things onCollide? (I hear an event?)

For things like, life steal, splash damage, also maybe a spell effect based on the master. Maybe a way (this is way too far from what your system is created for) a way to make the drone cast a spell that the user designate?

For the spell thing, this probably should change completely the system itself, Drone should be a separated library, with extensions for spellCast and attackCast, and maybe more. But is up to you (Nestharus feeling..).


Also, good work, the old system of drone in warcraft (I didn't remember from where it is, probably wc3c) is really outdated. Good work :)
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Is it possible to disable the "attack while moving" feature? - What I've seen, not. But I did not read all code in detail.
Yup. Just set acquisition range to 0 and your drone's not going to attack.
JASS:
set new.acqRange  = 0.0

Why the "?" in description?
That means I can't explain it. It's related to Dirac's Missile system. But he did not explain that one at all, actually he did not explain anything. So I have to try'em one by one to understand what exactly they do. But I still don't understand that "curve" thingy. His variable names are extremely vague you know.

Thanks for your comment bro!
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
These are terrible variable names. Given that they are part of the API, they must be changed.

JASS:
            readonly unit u             => drone
            readonly unit t             => current target
            readonly unit m             => master
            
            readonly real f             => current bone rotation angle
            readonly player p           => owner of drone
            readonly att_type att_name  => refer to value of each attribute

These should probably be completely written out, not shorthanded.

JASS:
            dmgLow              real            Attack damage low bound
            dmgTop              real            Attack damage top bound
            accuracy            real            Consistency of drone's firings
            acqRange            real            Acquisition range
            
            atkRange            real            Maximum attack range
            atkSpeed            real            Attack rate
            fllwRate            real            Delay to follow its master
            wndrRate            real            Delay to wander around when master is idle
            
            hitRange            real            Collision size of fired missile
            turnRate            real            Bone's turn rate
            mslAccel            real            Missile acceleration
            mslCurve            real            ?
            
            mslSpace            real            Offset between each missile (if mslCount>1)
            mslSpeed            real            Move speed of fired missile
            mslTrRate           real            Missile's turn rate (if setHoming is true)
            mslHeight           real            Maximum height of fired missile*
            
            impactAoE           real            AoE of impactDmg
            impactDmg           real            Dealt damage when missile is destroyed
            boneZOff            real            Bone locking z offset
            launchZ             real            Launch Z of fired missile
            
            impactZ             real            Impact Z of fired missile
            trgtFltr            integer         Classifications of unit that can be targeted by drone
            mslCount            integer         Number of missiles fired at once
            attkAnmtn           string          Played animation when drone attacks
            
            targetSfx           string          Sfx attached to victims on hit
            trgtSfxPt           string          The attachment point of targetSfx
            impactSfx           string          Sfx create when missile is destroyed
            missileFx           string          Filepath of missile's model
            
            fireSound           string          Played sound when drone is attacking
            whichBone           string          Rotated bone part
            setPierce           boolean         If true, missiles can hit multiple targets
            setHoming           boolean         If true, the missile will home to target
            
            damageSrc           boolean         If true, all damages done by drone will come from master
            staticAtk           boolean         If true, attack range will be static
            atkType             attacktype      Attack type of damages dealt by drones
            dmgType             damagetype      Damage type of damages dealt by drones

should do .03125000000 for accuracy

private constant real INTERVAL = 0.03125

These should have proper variable names

JASS:
        real  d
        group g

Don't capitalize variables

static group Group = CreateGroup()

What is dex supposed to even mean?

Just fix your variable names...

This is not helpful

JASS:
            local real cos  = Cos(a)
            local real sin  = Sin(a)

Honestly, it's very difficult to read your code with these variable names, so I will just ask a few questions.

1. Why are you creating a new group for each missile? In fact, why are you checking if units were already hit by a given missile? When you do your aoe damage for a given missile, you'll only ever hit each unit once, no? With variable names like dex and m, I just can't understand your logic or figure out the code.


In fact, how about you just fix the variable names and then I can give this a review. Until then, we can leave this as pending and nigh unreadable. I am not going to hunt for your variable declarations to find your comments to decrypt what the single letters actually stand for. Just properly name the variables.

Back when the optimizer was broken, doing short variables was ok. Now that it's fixed, it is not ok. You're going to have to fix your variables either way, so I'll just wait until they are fixed.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
1. Why are you creating a new group for each missile? In fact, why are you checking if units were already hit by a given missile? When you do your aoe damage for a given missile, you'll only ever hit each unit once, no? With variable names like dex and m, I just can't understand your logic or figure out the code.
It's just right already.

Okay, I will improve those beautiful names. Presonally, short names make it looks cleaner, but you are right, people may find it not understandable.

EDIT:
should do .03125000000 for accuracy
Seriously? I think you use 0.03125 for your CTL too.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I don't. The code in CTL was also pre-optimizer fix, which is why it also has terrible variable names. It hasn't been updated yet.

JASS:
    private function CT takes integer r returns integer
        local integer i
        local integer f
        if (0==n[0]) then
            set i=ic+1
            set ic=i
        else
            set i=n[0]
            set n[0]=n[i]
        endif
        set th[i]=r
        set ns[i]=-1
        set f=rf[r]
        if (0==f) then
            set n[i]=0
            set p[i]=0
            set rf[r]=i
            set ct[r]=TriggerAddCondition(t,rc[r])
            //set ct[r] = null
            if (0==tc) then
                call TimerStart(m,.031250000,true,function E)
            endif
            set tc=tc+1
        else
            set n[i]=f
            set p[i]=0
            set p[f]=i
            set rf[r]=i
        endif
        return i
    endfunction

It's just right already.

I'll judge that for myself, thanks
 
  • User can choose the damage source. Or it's the drone or the master.
    If the master unit is not in game anymore but is also the damage source there would be no damage dealt anymore.
    The drone would get useless, as it won't be removed when the master dies/ gets removed.
  • JASS:
    if dex2.damageSrcVar and dex2.master != null then
        call UnitDamageTarget(dex2.master, justHit, d, false, false, dex2.atkTypeVar, dex2.dmgTypeVar, null)
    else
        call UnitDamageTarget(dex2.unit, justHit, d, false, false, dex2.atkTypeVar, dex2.dmgTypeVar, null)
    endif
    ^Instead of this check you could set a damage source unit once onCreate.
  • Drone's scale could be setable. User could use different scale values to better visualisze different drone levels.
    Well at least I personal think it would improve it.
  • A new group gets created each time onFire. These can be pretty much creations and destructions during a game.
    I suggest you considering to use GroupUtils library which recylces groups.
  • It could be stated that your drone create method expects the facing to be in radian.
    set .unit = CreateUnit(p, id, x, y, face)
    ^And here we got a radian, but the unit create function expects degrees.
  • Names could be improved. Some are not descriptive, some could just be the full name.
    Also something like "Var" is not really needed in a variable name to indicate that it's a variable.
  • Just in case you don't know, but released timers don't need to be nulled. They will be recycled.
  • I would like you to be more precise here in spell description under "Installation":
    Import all necessary datas (imports, objects, triggers, etc.) into your map.
    Install all required dependencies correctly.

Nice demo map! It pretty much shows your spell system in a good way, I like it.
For now I would like you to go through and think about the mentioned points, but it's pretty cool I think.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Updated with these followings:
- Improved documentation, including importing instruction (it's now included inside spell documentation)
- Add new library (GroupUtils)
- Some small fixes

@IcemanBo:
User can choose the damage source. Or it's the drone or the master.
If the master unit is not in game anymore but is also the damage source there would be no damage dealt anymore.
The drone would get useless, as it won't be removed when the master dies/ gets removed.
Fixed.
^Instead of this check you could set a damage source unit once onCreate.
In case the caster is dead, it should be checked on every attack event.
A new group gets created each time onFire. These can be pretty much creations and destructions during a game.
I suggest you considering to use GroupUtils library which recylces groups.
Added.
It could be stated that your drone create method expects the facing to be in radian.
set .unit = CreateUnit(p, id, x, y, face)
^And here we got a radian, but the unit create function expects degrees.
Fixed. Now it requires degrees instead to avoid confusion.
Names could be improved. Some are not descriptive, some could just be the full name.
Also something like "Var" is not really needed in a variable name to indicate that it's a variable.
It's for differentiating the variable with it's operator.
I would like you to be more precise here in spell description under "Installation":
Granted.

But the main post isn't updated yet. I will do later. Enjoy the spell!
 
In case the caster is dead, it should be checked on every attack event.
For dealing damage in general? - It's good.
For evaluating the damage source? - Not necessarily needed more often.

Don't be disappointed because I don't rate it now, but if I had to I would rate it something like 4-5.
I might do so at some other day.:)

Some good changed were made, even not logged in changelog yet.
You were right about not bothering about drone's scale for example in your code.
Have not looked deep enough into your comments. I agree with you now.

Approved.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Big update! I'm not sure this update is acceptable or not tho.
- Updated Missile library to the most recent version.
- Now it only concentrates on summoning gatling bot. No longer able to create other types of drone.
- Heavily reduced code size.
- New missile model. Credits to Grey Knight for this sweet bullet model.
- New structure of configuration, to make it more comfortable for users.
- Improved documentations.
- GroupUtils is removed from requirements. Still there are a lot of requirements which is a bit of scary. : p

Reason behind this update is because I was kind of upset that IcemanBo thought it's rather a system than a spell, whereas it's supposed to be a spell. :p I was confused how to structure the code. But now it's been decided that it can only summon gatling bot!

Just use TimedHandle instead. My resource is broken D:

BTW. Why didn't you fix it? D: TimedHandle can't create effects on certain height offset. D:
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
I'll think about it :D

I think that's just needs a minor fix, tho there was someone who always tell that use a Dummy lib instead :V

Just do it then. ;P And please don't use dummy. ;P

Anyway, updated!
- Now drones can attack flying unit
- Updated ExSound library
- New fire sound effect
- Improved demo (new swarm unit, etc.)
- Improved installation guide
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Update!
- Upgraded LockBone library
- TimedEffect is no longer required
- Now missiles can't be removed instantly
- Improved some of old behaviors
- New behavior for drones (self secure)
- Fixed bug on death master
- Fixed some distance calculations mistakes
- Some drone attributes balancings
- Improved demo map

EDIT:
Nice habit to update older submissions!

What is the reason for your doubts towards it's acceptance?
Because of this:
?

Sorry I made you upset about "system/spell" this in past. :p
Hey, I didn't notice your comment IceBro : P

Why? bcz I wan som ratin!! Lol I'm too honest. But yeah, I just realized how much I love the spell's concept. I think I'm going to do a lot of improvements to this spell. Currently, I'm working with Mr Fingo to create a drone model specifically to work with this spell.

It's okay. The spell was in mess. It was indeed hard to call it a spell or a system. ;)

EDIT 2:
I have found that the fps drops gradually when testing the spell for several minutes. I thought it was caused by TimedEffect lib. Even tho it's indeed dropping slower now. But there still must be something leaking somewhere. This problem has been haunting this spell since the beginning, literally. I hope somebody can help me to find the problem. The fps drop is worse on spots where my unit have been there more often.
 
Last edited:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Thanks for the comments!

I have updated the Timed Effect, but it has a different API compared to the older one :)

Oh, well. It's no longer using TimedEffect : P I hope it will be re-approved tho!

@BPower:
Hey, you probably need to put this back to pending or needs fix.
Quilnez said:
EDIT 2:
I have found that the fps drops gradually when testing the spell for several minutes. I thought it was caused by TimedEffect lib. Even tho it's indeed dropping slower now. But there still must be something leaking somewhere. This problem has been haunting this spell since the beginning, literally. I hope somebody can help me to find the problem. The fps drop is worse on spots where my unit have been there more often.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Update!
- FPS drop is fixed!
- Utilized new Missile feature of hitWidget
- Some documentation improvements
- Updated LockBone lib (fixed double free bug)

EDIT:
Update! (v3.5)
- Now drone will return to the master immediately (depends on follow rate) after kill
- New on hit effect (new struct for creating effects)
- Added event catcher, removed some drone attributes
- Updated LockBone library
- Fixed wrong ability tooltip
- Improved demo

In the next update, I will probably remove 3 of current required libs.

EDIT:
Minor update: improved hit sfx

I will replace TimerUtils with CTL and remove FilterSystem and GetClosestWidget in the next update.
 
Last edited:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
@Almia:
:goblin_yeah:

Updated to v3.6! Turns out to be another big update!
- Removed 2 libs from requirements, FilteringSystem and GetClosestWidget
- Some drone attributes are moved to static configuration
- Replaced TimerUtils with CTL
- A lot of nerdy improvements
- Removed some unused variables
- New demo terrain layout, less boring :p
- Improved documentation

EDIT:
Presentation is complete. Now I'm waiting for Mr Fingo to finish my model request. Then the spell will be ready to take off! :goblin_yeah:
 
Last edited:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
The massive dependency list holding you back?
Are you referring to this resource specifically? Well, if it's dependency looks massive, you should have seen its previous versions before I removed half of it :grin: I mean, well, it's not massive at all, for me.

EDIT:
Anyway, Flux is right. There is no point in avoiding vJass. Even tho we code it in vJass, we are always trying to make it as convenience as possible for anyone, even for GUI users. Just spend 10 minutes of your time to download JNGP and to learn how to configure vJass resources (it's very simple, really) and you will be able to use almost all resources in spell and jass section.
 
Last edited:

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
I has this tool but i don't know how to use :p
You can use JNGP just like using normal WE. But first, I recommend you to enable Vexorian's JassHelper, by going to "JassHelper" at the menu bar, and select Vexorian's JassHelper. Then you can use pretty much any vJass resource. Then if you have trouble in adding the spell to your map, you can contact me or moderators.
 
Top