1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  4. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  5. Units have been turned into heroes in our latest Icon Contest! Vote for the best icon set at the Icon Contest #18 Poll!
    Dismiss Notice
  6. The poll for Hive's 12th Concept Art Contest is up! Go cast your vote for your favourite genie!
    Dismiss Notice
  7. The raddest synthwave tracks were chosen - Check out our Music Contest #12 - Results and congratulate the winners!
    Dismiss Notice
  8. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[System] Custom Projectiles

Discussion in 'JASS Resources' started by Berb, Mar 25, 2010.

  1. Berb

    Berb

    Joined:
    Jan 21, 2006
    Messages:
    2,539
    Resources:
    2
    JASS:
    2
    Resources:
    2
    WarCraft III: Custom Projectiles v2.2.0 by Berb

    Optional
    • Dummy model with necessary pitch rotations (for pitch rotation projectile)
    • ProjectileArt Module

    Requirements
    • JassHelper
    • Vector Library (by Anitarf)

    Important
    • Special thanks to Vestras and Kenny for the Slow Time art.
    • This engine requires the use of 2 hashtables.

    This first page is incredibly out-dated, so I would like to update it. To start things off, I'll list the features that this system offers the user (in flexible vJass syntax) and can easily take advantage of in their own scripts:
    • Unit Collision Responses
    • Destructible Collision Responses
    • Terrain Collision Responses
    • Projectile Target Homing (supports varying point target homing)
    • Projectile Arc Simulation
    • Projectile Time Scale Properties
    • Advanced Projectile Groups and Enumeration

    The efficiency of this projectile engine also deserves recognition; in relatively stressful situations this system pulls through adequately, and (in ideal circumstances) can handle over 300~ projectiles without dropping much in frame-rate. With that said, newer computers will probably be able to handle well over 500 at any given time.

    The implementation of projectile groups is also very advanced, and this method has been used in Kenny's projectile-library that can be found on TheHelper.net. It uses many hashtables but the result is extremely efficient group cleanup, something that would otherwise bog the performance down in certain scenarios.

    Manual
    Instructions

    Instructions:
    ¯¯¯¯¯¯¯¯¯¯¯¯¯
    ==============================================================================
    • Copy and paste the projectile library (and required libraries) into your map script.
    • If pitch-rotation is desired, make sure that you have Vexorian's pitch angle dummy (I believe it can be found in xepreloader).

    There really isn't a lot to setup, once the library is in your custom script you will be able to use its API, such as "projectile.create()". In order to make a projectile, you need to specify a unit that you want to be projected.

    *static method create takes unit u returns thistype


    In order to launch the projectile you must have two 3D points, represented by vectors, for your start/finish positions as well as a designated speed and arc for the projectile motion.

    *method launchtakes vector start, vector finish, real speed, real arc returns boolean


    If the "launch()" method returns false that means that start or finish were incorrectly setup (they are null vectors) or the projectile has already been launched (and not completed).
    ==============================================================================
    WarCraft III Projectiles

    Now, say you wanted to use the pitch-animation dummy for your projectile so you could imitate that WarCraft III-style. Since the pitch-animation dummy does not actually have a model, you may be wandering how you're going to reproduce a WarCraft III projectile.

    Even though the pitch-rotation dummy model cannot be changed, it is possible to attach an effect to the "origin" of the projectile unit (referenced using projVar.toUnit) and have that effect show up as if it were the model of the dummy. This means that the model of your projectile is entirely dependent on effects. Because of this, you're probably going to want to make a struct that can handle this:

    Code (vJASS):
    struct basicmissile extends projectile
        //add the effect that is going to be used for the model:
        private effect  model   = null

        //make sure that when the projectile is destroyed, the model is too.
        method onDestroy takes nothing returns nothing
            call DestroyEffect(model)
        endmethod

        //attach the model to the unit that is used to create the projectile.
        static method create takes unit u returns thistype
            local thistype p=allocate(u)
            set p.model=AddSpecialEffectTarget("", p.toUnit, "origin")
            return p
        endmethod
    endstruct


    Now whenever you create a "basicmissile" struct it will automatically attach an effect to the projectile unit so that you don't have to do this every time, instead you only have to code each type of projectile once before it can be used undefinitely.

    Let's say you don't need the 'u' parameter, instead you just want to specify the source of the projectile that will be launched. You will still need a unit parameter to allocate the projectile, but you can pass a dummy unit that was just created.

    Code (vJASS):
    static method create takes unit source returns thistype
        //allocate with a poorly constructed dummy:
        local thistype p=allocate(CreateUnit(GetOwningPlayer(source), 'hpea', ...)
       
        //the projectile system also allows you to declare a source and
        //target for your projectile, but 'activeTargetFollow' must be
        //enabled on your projectile in order to use it.
        set p.source = source
        return p
    endmethod


    The ProjectileArt module is a recent addition to this submission. If the library is included in the map script along with the Projectile library it will allow the users availability to two additional instance methods that are used for controlling the model that "represents" the projectile. These methods include
    setModel( )
    which takes a string and
    killModel( )
    . Other than copying and pasting the script there is nothing else the user needs to do in order to have access to those methods.



    Modules
    Code (vJASS):
    library ProjectileArt
    //######################################################################################################
    //#
    //#     P R O J E C T I L E   A R T
    //#                                 by Berb
    //#
    //######################################################################################################

    module ProjectileArt
    //**********************************
    //* Projectile Art Module
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     The projectile-art module offers the Projectile library "improved" functionality, allowing the
    //*     user to designate an effect model that they would like to be attached to the origin of the
    //*     model. This is particularly useful when dealing with the dummy.mdx model.
    //*
    //*     This module is designed for the Projectile library version 1.8+; versions prior to 1.8 will
    //*     not have the necessary support ("implement optional" command).
    //*
        private     string      ART__path      
        private     effect      ART__model      = null
    //*
    //* ====================================================================================================
    //* method killModel( )         -> This will delete a projectile's model art by destroying the attached
    //*                             effect if it is present. It will return false if it has not.
    //*                  
        method killModel takes nothing returns boolean  
            if ART__model != null then
                call DestroyEffect(ART__model)
                return true
            endif
            return false
        endmethod
    //*
    //* ====================================================================================================
    //* method setModel( )
    //*         @param "filepath"   -> The filepath of the model that is going to be attached to the origin
    //*                             of the projectile unit.
    //*        
        method setModel takes string filepath returns nothing
            call killModel( )
            set ART__model = AddSpecialEffectTarget(filepath, toUnit, "origin")
            set ART__path  = filepath
        endmethod
    //*
    //*
    //******************************************************************************************************
    endmodule

    endlibrary


    Library Script
    Code (vJASS):
    library Projectile requires Vector, optional ProjectileArt
    //##########################################################################################################    
    //#
    //#     P R O J E C T I L E                                                                        
    //#                         by Berb
    //#
    //##########################################################################################################

    //**********************************************************************************************************
    //*
    //*     WarCraft III: Custom Projectiles Library
    //*     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     Author:     Berb / TheKid
    //*     Version:    2.2.0
    //*
    //*     Requirements:
    //*     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*         • JassHelper 0.A.2.B
    //*         • Vector by Anitarf
    //*
    //*     To Know:
    //*     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*         • Pitch variance requires the dummy.mdx model that is included in "xebasic".
    //*         • Projectile groups are limited by the number of projectiles that they can contain.
    //*
    //*     Description:
    //*     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*         The purpose of this library is to provide users with an interface by which they can easily
    //*         manage their projectiles. It is recommended that the user read through the API to discover
    //*         which control features he/she has access to.
    //*
    //*     Recent Changes:
    //*     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*         2.1.1   - Removed the "FLAG__onFinish" state flag which disallowed projectiles from being
    //*                   launched properly in succession. It isn't actually very useful, and it doesn't provide
    //*                   any bonus functionality - I'm not sure why I put it in there in the first place.
    //*
    //*                 - If necessary, projectiles can be launched in their "onFinish" method response as they
    //*                   are deactivated before that response is executed. By deactivating the "toDestroy"
    //*                   member users will more easily be able to make projectiles "bounce" from one target
    //*                   to another.
    //*
    //*         2.1.2   - Optimized the "enumNearby" method as corrected by Bribe.
    //*
    //*         2.1.3   - Fixed a very minor issue with projectiles being destroyed prematurely by adding a member
    //*                   "destroyed" to the projectile members.
    //*
    //*                     > Instead of checking "if (proj == 0) then" to detect if a projectile has been
    //*                       destroyed, the user can now do: "if (proj.destroyed) then". This simply removes
    //*                       the "double-free" message from a projectile being destroyed twice.
    //*
    //*                     > The value "destroyed" is defaulted to false, and set to true in the "onDestroy"
    //*                       method.
    //*
    //*                 - Added method operator to the "projectilegroup" struct to reference members of the
    //*                   group with array syntax, rather than using the array member "indexOf[]".
    //*
    //*                 - Changed the members "DATA__stack" and "DATA__size" to readonly so that users can
    //*                   perform iterations on the entire stack of projectiles.
    //*
    //*         2.2.0   - Removed the indexOf[] array member from the projectile-group struct. In substitution,
    //*                   the array syntax operator [] is available (since 2.1.3).
    //*
    //*                 - Removed one of the hashtable requirements for projectile group referencing. Instead,
    //*                   only one hashtable is required for this (and another for projectile groups).
    //*
    //*                 - Removed the limit on the amount of projectiles that can be stored in a projectile
    //*                   group by further utilizing the hashtable that was already used for the very quick
    //*                   "inGroup" method.
    //*
    //*
    //*
    //**********************************************************************************************************


    globals
    //**************************
    //* Configuration
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     Includes default definitions of projectile settings in addition to other values that are used
    //*     within the system but rely on a value in order to function.
    //*
    //* Fly Height Modifier Ability
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        public      constant integer            ID__flyHeightModifier           = 'Amrf'          
    //*          
    //* Settings
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        public      constant real               COLLISION__default              = 70.00         // The maximum and default settings for projectile collision.
        public      constant real               COLLISION__sizeMax              = 200.00
    //*
    //* Default Features Config
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        private     constant boolean            DEFAULT__targetFollow           = true          // These features enable certain elements of each projectile, giving the user
        private     constant boolean            DEFAULT__unitCollision          = false         // the option to customize what properties the projectile displays - like whether
        private     constant boolean            DEFAULT__destCollision          = false         // it can collide with units/destructables to whether or not it is a target                                                                                                            
        private     constant boolean            DEFAULT__faceRotation           = true          // "seeking" or following target.
        private     constant boolean            DEFAULT__facePitch              = true            
        private     constant boolean            DEFAULT__toExpire               = true      
        private     constant boolean            DEFAULT__toKill                 = true    
        private     constant boolean            DEFAULT__toRemove               = false  
    //*
    //*
        public      constant real               SETTING__timeout                = 0.03          // This identifies the amount of time (in seconds) that lapses between each
    //*                                                                                         // iteration that the projectiles are "updated".
    //*
    //*
    //**********************************************************************************************************
    endglobals




    interface projectileinterface
    //**************************
    //* Projectile Interface
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     Includes the names for methods (and their parameters) that users can declare in child-structures to
    //*     acquire reference to a projectile on different occurances that prompt a response.
    //*
    //* Event Response - State
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method  onStart             takes nothing returns nothing           defaults nothing
        method  onFinish            takes nothing returns nothing           defaults nothing    // Projectile completes trajectory.
        method  onGround            takes nothing returns nothing           defaults nothing    // Impact with terran.
    //*
    //* Event Response - Collision
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method  onUnitCollision     takes unit u returns nothing            defaults nothing
        method  onDestCollision     takes destructable d returns nothing    defaults nothing
    //*
    //* Filters
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method  isValidTargetUnit   takes unit u returns boolean            defaults true
        method  isValidTargetDest   takes destructable d returns boolean    defaults true
    //*
    //*
    //**********************************************************************************************************    
    endinterface




    globals
    //**************************
    //* Dynamic Storage
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     In order to optimize the Projectiles efficiency, certain global variables are required to make
    //*     special calculations and operations; such as GetLocationZ().
    //*
    //* References
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        private     projectile          projRef
        private     location            loc                     = Location(0, 0)
        private     group               grp                     = CreateGroup()
        private     rect                rct                    
    //*
        private     hashtable           projGroupTable          = InitHashtable( )
    //*
    //* Misc
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        private     integer             totalProjectiles        = 0
    //*
    //*
    //**********************************************************************************************************
    endglobals




    //**************************
    //* Total Projectile Count
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     The amount of projectiles that are currently in operation. The value that is returned is added to
    //*     and subtracted from whenever a projectile is created/destroyed.
    //*
    function GetTotalProjectiles takes nothing returns integer
        return totalProjectiles
       
    endfunction
    //*
    //*
    //**********************************************************************************************************    



    struct projectilegroup
    //**************************
    //* Projectile Group
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     Enumeration is a big part of WarCraft III, and it would be hard to really utilize projectiles
    //*     properly without having some method of enumeration.
    //*
    //* API:
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     @[]                     - Method operator for referencing members of the group with array syntax.
    //*     @size                   - The amount of projectiles that are currently stored in the group.
    //*
    //*     @add( )                 - Adds a specified projectile to the group if it hasn't been already.
    //*     @remove( )              - Removes a specified projectile from the group if it has been added.
    //*
    //*     @inGroup( )             - Returns true if the designated projectile is included in the group.
    //*
    //* Components
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        readonly        integer             size            = 0
        private static  hashtable           table           = InitHashtable( )
    //*
    //* Operators
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method operator [] takes integer index returns projectile
            return LoadInteger(table, this+8092, index)
        endmethod
    //*
    //* Utility Methods
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method inGroup takes projectile p returns boolean               // This will return true if the
            return HaveSavedInteger(thistype.table, this, p)            // specified projectile is included in
                                                                        // "this" group.
        endmethod
    //*
    //* Group Operators
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method add takes projectile p returns boolean    
            local integer indexOfLast
            if not inGroup(p) then          
                call SaveInteger(thistype.table, this, p, size)  
                call SaveInteger(thistype.table, this+8092, size, p)  
                set size = size + 1    
       
                set indexOfLast = 0
                if HaveSavedInteger(projGroupTable, p, -1) then
                    set indexOfLast = LoadInteger(projGroupTable, p, -1)
                endif
                call SaveInteger(projGroupTable, p, indexOfLast, this)
                call SaveInteger(projGroupTable, p+8092, this, indexOfLast)
                call SaveInteger(projGroupTable, p, -1, indexOfLast + 1)
               
                return true                                          
            endif                                                      
            return false
        endmethod
        method remove takes projectile p returns boolean
            local integer indexOfLast
            local integer indexOfThis
            if inGroup(p) then          
                set size = size - 1
                set indexOfThis = LoadInteger(table, this, p)
                call SaveInteger(table, this+8092, indexOfThis, LoadInteger(table, this+8092, size))
                call SaveInteger(table, this, LoadInteger(table, this+8092, indexOfThis), indexOfThis)
                call RemoveSavedInteger(table, this+8092, size)
                call RemoveSavedInteger(table, this, p)
           
                set indexOfLast = LoadInteger(projGroupTable, p, -1) - 1
                set indexOfThis = LoadInteger(projGroupTable, p+8092, this)
                call SaveInteger(projGroupTable, p, -1, indexOfLast)
                call SaveInteger(projGroupTable, p, indexOfThis, LoadInteger(projGroupTable, p, indexOfLast))
                call SaveInteger(projGroupTable, p+8092, LoadInteger(projGroupTable, p, indexOfThis), indexOfThis)
               
                if indexOfLast < 1 then
                    call FlushChildHashtable(projGroupTable, p)
                    call FlushChildHashtable(projGroupTable, p+8092)
                   
                endif
                return true
            endif
            return false
        endmethod
    //*
    //* Destructor & Clear
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     Both the .clear( ) and .destroy( ) methods will perform the exact same actions, however when
    //*     .destroy( ) is used it will recycle the group struct. If .clear( ) is used it will simply ensure
    //*     there are no projectiles included in the projectilegroup.
    //*
        method clear takes nothing returns nothing            
            local integer i = size - 1                        
            loop                                                
                exitwhen i < 0
                call remove(LoadInteger(table, this+8092, i))
                set i = i - 1
            endloop
        endmethod
        method onDestroy takes nothing returns nothing        
            local integer i = size - 1
            loop
                exitwhen i < 0
                call remove(LoadInteger(table, this+8092, i))
                set i = i - 1
            endloop
        endmethod
    //*
    //*
    //*********************************************************************************************************
    endstruct



    struct projectile extends projectileinterface
    //*******************************************
    //* Projectile
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     The projectile struct controls all aspects of the projectile motion, from creating it with a unit
    //*     handle to launching it through the air and destroying it. It has an extensive API as control can
    //*     get a little complex in areas.
    //*
    //* API: members
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     @toUnit                     - The unit handle that represents the projectile in-game.
    //*     @speed                      - The speed at which the projectile is moving in a 2D perspective.
    //*     @arc                        - The arc at which the projectile is moving.
    //*
    //*     @activeTargetFollow         - Enables/disables projectile homing.
    //*     @activePitch                - Enables/disables projectile pitch rotation (cosmetic).
    //*     @activeRotation             - Enables/disables projectile rotation (cosmetic).
    //*     @activeUnitCollision        - Enables/disables projectile-unit collision.
    //*     @activeDestCollision        - Enables/disables projectile-destructable collision.
    //*
    //*     @destroyed                  - Value of 'true' if the projectile has been destroyed.
    //*     @toDestroy                  - Allows the projectile struct to be destroyed once it finishes.
    //*     @toRemove (optimal)         - Allows "toUnit" to be removed upon projectile destruction.
    //*     @toKill                     - Allows "toUnit" to be killed upon projectile destruction.
    //*
    //*     @x                          - "x" coordinate of the projectile object.
    //*     @y                          - "y" coordinate of the projectile object.
    //*     @z                          - "z" coordinate of the projectile object.
    //*     @collision                  - The collision-size of the projectile object.
    //*     @timescale                  - The execution time-scale of the projectile. Setting this to 0 will
    //*                                   freeze the projectile in position until it is "unpaused".
    //*
    //*     @source                     - The source unit of the projectile being launched.
    //*     @target                     - The target unit of the projectile being launched.
    //*     @targetX                    - "x" coordinate of the projectile target.
    //*     @targetY                    - "y" coordinate of the projectile target.
    //*     @targetZ                    - "z" coordinate of the projectile target.
    //*     @targetZOffset              - "z" coordinate offset of the projectile target.
    //*
    //* API: instance methods
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     @setTargetPos               - Updates a projectile's target with 3 coordinate input parameters.
    //*
    //*     @launch                     - Launches a projectile from one point to another with given input.
    //*                                 @start      - The initial position vector.
    //*                                 @finish     - The final position vector.
    //*                                 @speed      - The velocity of the projectile in a 2D perspective.
    //*                                 @arc        - The arc that the projectile will be launched at.
    //*
    //*     @destroy                    - Destroys a projectile, cleaning all of its members and deallocating
    //*                                 it from the projectile engine in addition to removing the projectile
    //*                                 from any "projectilegroup" it was included in.
    //*
    //* API: static methods
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯    
    //*     @create                     - Creates a projectile object from a unit handle.
    //*
    //*     @enumNearby                 - Gathers nearby projectiles in a specified "projectilegroup".
    //*
    //*
    //* ########################################################################################################
    //*
        readonly        unit            toUnit                      = null
        readonly        real            speed                                                   // In order to launch a projectile, a "speed" and "arc" input is necessary. These
        readonly        real            arc                                                     // values are stored in readonly-scope variables so that they can be referenced
    //*                                                                                         // externally.  
    //*
        private         unit            PROP__target                = null                      // Both .priv_target and .priv_source can be referenced publicly by using the
        private         unit            PROP__source                = null                      // method operators without the "priv_" prefix.
        private         real            PROP__timescale             = 1.00
        private         real            PROP__collision             = COLLISION__default
        private         real            PROP__targetHeight          = 0
    //*
    //*
        public          boolean         activeTargetFollow          = DEFAULT__targetFollow     // These settings are all defaulted to the configuration constants that are
        public          boolean         activePitch                 = DEFAULT__facePitch        // declared above when the projectile is created. If the user wants to customize
        public          boolean         activeRotation              = DEFAULT__faceRotation     // these settings beyond their defaults (or change them mid-flight) then they can
        public          boolean         activeUnitCollision         = DEFAULT__unitCollision    // be referenced publicly and flagged on/off (true/false).
        public          boolean         activeDestCollision         = DEFAULT__destCollision
    //*
    //*
        public          boolean         destroyed                   = false
        public          boolean         toDestroy                   = DEFAULT__toExpire         // These flags are specific to the end-phase of the projectile flight. Flags that
        public          boolean         toKill                      = DEFAULT__toKill           // control whether the unit handle is killed/removed upon projectile destruction,
        public          boolean         toRemove                    = DEFAULT__toRemove         // and whether or not a projectile is destroyed upon its "finish" event.
    //*
    //*
        private         boolean         FLAG__onGround              = true                    
        private         boolean         FLAG__active                = false  
        private         boolean         FLAG__targetFollow          = false
        private         boolean         FLAG__followUnit            = false
        private         boolean         FLAG__isPaused              = false
    //*
    //*
        private         vector          VECTOR__position                                // Each projectile allocates 5 vectors that control its motion. These vectors
        private         vector          VECTOR__velocity                                // are mainly initiated to 0-vectors except for the "position vector", which
        private         vector          VECTOR__acceleration                            // immediately occupies with the projectile's in-game coordinates.
        private         vector          VECTOR__target
        private         vector          VECTOR__start                                   // The starting-position is necessary to reference the "total" distance that
    //*                                                                                 // the projectile has traveled from where it began (cosmetic calculations).
    //*
        private         integer         DATA__index                                    
        readonly static thistype array  DATA__stack                                    
        readonly static integer         DATA__size                  = 0
        private static  timer           DATA__loop                  = CreateTimer()
        private static  constant real   DATA__loopRef               = SETTING__timeout
        private         real            DATA__timeLeft    
    //*
    //*  
        private static  boolexpr        FUNC__enumUnits             = null
        private static  boolexpr        FUNC__enumDests             = null
    //*
    //*
    //*
    //* ########################################################################################################
    //* - MODULE IMPLEMENTATION -
        implement optional              ProjectileArt
    //*
    //* ########################################################################################################
    //*
    //* Position Interface
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method operator x takes nothing returns real
            return VECTOR__position.x
        endmethod
        method operator y takes nothing returns real
            return VECTOR__position.y
        endmethod
        method operator z takes nothing returns real
            return VECTOR__position.z
        endmethod  
    //*
    //*
    //* Timescale Interface
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method operator timescale takes nothing returns real
            if FLAG__isPaused then
                return 0.00
            endif
            return PROP__timescale
        endmethod
        method operator timescale= takes real r returns nothing    
            if (r == 0) then                                      
                set FLAG__isPaused = true                          
            else
                set FLAG__isPaused = false
                set PROP__timescale = r
            endif
        endmethod
    //*
    //*
    //* Collision Interface
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method operator collision takes nothing returns real
            return PROP__collision
        endmethod
        method operator collision= takes real r returns nothing
            if r > COLLISION__sizeMax then
                set r = COLLISION__sizeMax
            elseif r < 0 then
                set r = 0
            endif
            set PROP__collision = r
        endmethod
    //*
    //*
    //* Source Interface
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method operator source takes nothing returns unit
            return PROP__source
        endmethod
        method operator source= takes unit who returns nothing
            set PROP__source = who
        endmethod
    //*
    //*
    //* Target Interface
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //* ========================================================================================================
        method operator target takes nothing returns unit           //* The target object (unit-only) can be
            return PROP__target                                     //* referenced at any time through the
                                                                    //* projectile.
        endmethod
        method operator target= takes unit who returns nothing      //* The target can also be changed at any
            if GetUnitTypeId(who) != 0 then                         //* time; setting the target to "null" or
                set PROP__target = who                              //* even a removed unit will reset the
                set FLAG__followUnit = true                         //* effects of modifying the target.
            else
                set PROP__target = null                             //* If a target is selected then the system
                set FLAG__followUnit = false                        //* will automatically follow the unit's
            endif                                                   //* position as the target vector.
        endmethod
    //*
    //* ========================================================================================================
        method operator targetX takes nothing returns real          
            return VECTOR__target.x
        endmethod
        method operator targetY takes nothing returns real
            return VECTOR__target.y
        endmethod
        method operator targetZ takes nothing returns real          
            return VECTOR__target.z                                
        endmethod    
    //*
    //* ========================================================================================================
        method operator targetZOffset takes nothing returns real    
            return PROP__targetHeight
        endmethod
        method operator targetZOffset= takes real r returns nothing
            if r < 0 then                                          
                set r = 0                                        
            endif                    
            set PROP__targetHeight = r
        endmethod
    //*
    //* ========================================================================================================
        method setTargetPos takes real x1, real y1, real z1 returns boolean  
            local real xA
            local real yA  
            local vector v = VECTOR__velocity
            if (x1 != VECTOR__target.x) or (y1 != VECTOR__target.y) or (z1 != VECTOR__target.z) then
                set VECTOR__target.x    = x1
                set VECTOR__target.y    = y1
                set VECTOR__target.z    = z1 + PROP__targetHeight
                set xA                  = VECTOR__position.x
                set yA                  = VECTOR__position.y
               
                set xA                  = (x1-xA)*(x1-xA) + (y1-yA)*(y1-yA)
                set yA                  = (v.x*v.x + v.y*v.y)/(thistype.DATA__loopRef*thistype.DATA__loopRef)
                set DATA__timeLeft      = SquareRoot(xA/yA)
               
                set FLAG__targetFollow = true       // The "targetFollow" flag will be toggled to notify the
                return true                         // engine that the target has been changed from its
                                                    // original position.
            endif
            return false
        endmethod  
    //*
    //*
    //* Destructor
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method onDestroy takes nothing returns nothing
            local integer i    
            if not (destroyed) then
                set thistype.DATA__size = thistype.DATA__size - 1
                set thistype.DATA__stack[DATA__index] = thistype.DATA__stack[thistype.DATA__size]
                set thistype.DATA__stack[DATA__index].DATA__index = DATA__index
                if (thistype.DATA__size == 0) then
                    call PauseTimer(thistype.DATA__loop)
                   
                endif
                set totalProjectiles = totalProjectiles - 1        
               
                if LIBRARY_ProjectileArt then       // If the ProjectileArt module was implemented, this will execute
                    call killModel( )               // a command to remove the model art.
                endif
                if toRemove then                    // The projectile unit handle can either be removed or killed,
                    call RemoveUnit(toUnit)         // with removal in priority if they are both flagged.
                elseif toKill then
                    call KillUnit(toUnit)
                endif
               
                call VECTOR__position.destroy( )
                call VECTOR__velocity.destroy( )
                call VECTOR__acceleration.destroy( )
                call VECTOR__target.destroy( )
                call VECTOR__start.destroy( )
               
                if HaveSavedInteger(projGroupTable, this, -1) then
                    set i = LoadInteger(projGroupTable, this, -1) - 1
                    loop
                        exitwhen i < 0
                        call projectilegroup(LoadInteger(projGroupTable, this, i)).remove(this)
                        set i = i - 1
                    endloop
                    call FlushChildHashtable(projGroupTable, this)
                    call FlushChildHashtable(projGroupTable, this+8092)
                endif
                set destroyed = true
            endif
        endmethod
    //*
    //*
    //* Projectile Enumeration
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        static method enumNearby takes projectilegroup g, real x, real y, real z, real radius returns nothing
            local integer i=0
            local real x2
            local real y2
            local real z2
            local real r2 = radius*radius
            local thistype p
            local vector v
            loop
                exitwhen i == DATA__size
                set p  = DATA__stack[i]
                set v  = p.VECTOR__position
                set x2 = v.x
                set y2 = v.y
                set z2 = v.z
                if ((x2-x)*(x2-x)+(y2-y)*(y2-y)+(z2-z)*(z2-z)) <= (r2) then
                    call g.add(DATA__stack[i])
                endif
                set i=i+1
            endloop
        endmethod
    //*
    //*
    //* Launch Projectile
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        method launch takes vector start, vector finish, real speed, real arc returns boolean
            local real d
            local real a
            local boolean instant = false
            if (start != 0) and (finish != 0) and not (FLAG__active) then
                set VECTOR__position.x      = start.x      
                set VECTOR__position.y      = start.y      
                set VECTOR__position.z      = start.z
                set VECTOR__target.x        = finish.x
                set VECTOR__target.y        = finish.y
                set VECTOR__target.z        = finish.z + PROP__targetHeight
                set VECTOR__start.x         = start.x
                set VECTOR__start.y         = start.y
                set VECTOR__start.z         = start.z
                set this.speed              = speed
                set this.arc                = arc
               
                set d = SquareRoot((finish.x-start.x)*(finish.x-start.x) + (finish.y-start.y)*(finish.y-start.y))
                set a = Atan2(finish.y-start.y, finish.x-start.x)
               
                if (d == 0) or (speed == 0) then
                    set VECTOR__position.x      = VECTOR__target.x      // If the projectile speed is 0 or the distance to the target
                    set VECTOR__position.y      = VECTOR__target.y      // is 0 then the projectile will be considered "instant", and
                    set VECTOR__position.z      = VECTOR__target.z      // launch to its destination immediately.
                    set DATA__timeLeft          = 0.00
                    set instant                 = true
                else
                    set DATA__timeLeft          = d/speed
                    set VECTOR__acceleration.z  = (-8*arc*speed*speed/d)
                    set VECTOR__velocity.x      = speed*Cos(a)  *thistype.DATA__loopRef
                    set VECTOR__velocity.y      = speed*Sin(a)  *thistype.DATA__loopRef
                    set VECTOR__velocity.z      = (-VECTOR__acceleration.z * (d/speed)/2 + (VECTOR__target.z-start.z)/(d/speed)) /*
                                                            */
     *thistype.DATA__loopRef
                    set VECTOR__acceleration.z  = VECTOR__acceleration.z *thistype.DATA__loopRef*thistype.DATA__loopRef
                       
                endif
               
                call SetUnitX(toUnit, VECTOR__position.x)
                call SetUnitY(toUnit, VECTOR__position.y)
                call MoveLocation(loc, VECTOR__position.x, VECTOR__position.y)
                call SetUnitFlyHeight(toUnit, VECTOR__position.z-GetLocationZ(loc), 0)
               
                call onStart( )
                set FLAG__active = true
                if instant then
                    set FLAG__active = false
                    call onFinish( )
                    if (toDestroy and not destroyed) then
                        call destroy( )
                    endif
                endif
                return true
            endif
            return false    
        endmethod
    //*
    //* Filter Enumeration Methods
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     In order to efficiently cycle through nearby units and destructables it is necessary to use a
    //*     filterfunc callback which is automatically executed by the enumeration natives. These two filters
    //*     have global "boolexpr" references for efficient reference without having to use the Filter() native.
    //*
        private static method doLoopDestFilter takes nothing returns boolean
            local destructable d = GetFilterDestructable()
            local real x         = GetDestructableX(d)
            local real y         = GetDestructableY(d)
            local real xB        = projRef.VECTOR__position.x
            local real yB        = projRef.VECTOR__position.y
            if ((xB-x)*(xB-x) + (yB-y)*(yB-y)) <= projRef.PROP__collision*projRef.PROP__collision then
                if projRef.isValidTargetDest(d) then
                    call projRef.onDestCollision(d)
                endif
            endif               // Destructibles are picked if their x/y coordinates fall within a
            set d = null        // Circular area around the projectile.
            return false        
        endmethod
        private static method doLoopEnum takes nothing returns boolean          
            local unit filt = GetFilterUnit()
            local real xA   = GetUnitX(filt)
            local real yA   = GetUnitY(filt)
            local real xB   = projRef.VECTOR__position.x
            local real yB   = projRef.VECTOR__position.y
            if (((xA-xB)*(xA-xB) + (yA-yB)*(yA-yB)) <= projRef.PROP__collision*projRef.PROP__collision) then
                if (projRef.isValidTargetUnit(filt)) then
                    call projRef.onUnitCollision(filt)
                endif
            endif
            set filt = null
            return false
        endmethod
    //*
    //* Control Loop Iterator
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //*     The loop will run through each member in the stack of projectiles and use it's acceleration and
    //*     velocity to calculate its motion path. This control loop will detect when the projectile collides
    //*     with units, destructables, or even the terrain; in addition to throwing a few interface methods
    //*     that allow the user to customize the actions to his/her preference.
    //*
        private static method doLoop takes nothing returns nothing
            local integer i = DATA__size - 1
            local thistype dat
            local vector vA
            local vector vB
            local vector vC
            local real x
            local real y
            local real z
            local real x2
            local real y2
            local real z2
            local real e
            local unit u
            loop
                exitwhen (i < 0)
                set dat = DATA__stack[i]
                if (dat != 0) then
                    if dat.FLAG__active then
                        set e       = dat.timescale
                        set vA      = dat.VECTOR__velocity
                        set vB      = dat.VECTOR__acceleration
                        set vA.x    = vA.x + vB.x *e
                        set vA.y    = vA.y + vB.y *e
                        set vA.z    = vA.z + vB.z *e
                        set vB      = dat.VECTOR__position
                        set vB.x    = vB.x + vA.x *e
                        set vB.y    = vB.y + vA.y *e
                        set vB.z    = vB.z + vA.z *e
                    else
                        set vA      = dat.VECTOR__velocity
                        set vB      = dat.VECTOR__position
                    endif
                   
                    set u = dat.toUnit
                    set x = vB.x
                    set y = vB.y
                    set z = vB.z
                    call SetUnitX(u, x)
                    call SetUnitY(u, y)
                    call MoveLocation(loc, x, y)
                    call SetUnitFlyHeight(u, z-GetLocationZ(loc), 0)
                   
                    set x2 = vA.x
                    set y2 = vA.y
                    set z2 = vA.z
                    if (dat.FLAG__active) and (x2 != 0 or y2 != 0 or z2 != 0) then
                        if dat.activePitch then
                            call SetUnitAnimationByIndex(u, R2I(bj_RADTODEG*Atan2(z2, dat.speed*DATA__loopRef)+0.5)+90)
                            // This requires the dummy.mdx file that is included in xepreload. I should really make
                            // xepreload a requirement (perhaps optional requirement) of this system.
                        endif
                        if dat.activeRotation then
                            call SetUnitFacingTimed(u, Atan2(y2, x2)*bj_RADTODEG, 0)
                        endif
                    endif
                   
                    if dat != 0 then
                        if (z <= GetLocationZ(loc)) then                  
                            if not (dat.FLAG__onGround) then            // The ".onGround( )" method callback will only be executed
                                set dat.FLAG__onGround = true           // once upon hitting the ground, and not repeatedly if the
                                call dat.onGround( )                    // projectile remains under the terrain level.
                            endif                              
                        else
                            if (dat.FLAG__onGround) then
                                set dat.FLAG__onGround = false
                            endif
                        endif
                    endif
                   
                    if (dat.activeUnitCollision) and dat != 0 then // Unit Collision
                        set projRef = dat
                        call GroupEnumUnitsInRange(grp, x, y, dat.PROP__collision, FUNC__enumUnits)
                    endif
                    if (dat.activeDestCollision) and dat != 0 then // Destructable Collision
                        set projRef = dat
                        call SetRect(rct, 0, 0, dat.PROP__collision, dat.PROP__collision)
                        call MoveRectTo(rct, x, y)
                        call EnumDestructablesInRect(rct, FUNC__enumDests, null)
                    endif
                   
                    if (dat != 0) and (dat.FLAG__active) and (dat.activeTargetFollow) then
                        if (dat.FLAG__followUnit) and GetUnitTypeId(dat.target) != 0 then
                            set x = GetUnitX(dat.target)
                            set y = GetUnitY(dat.target)
                            call MoveLocation(loc, x, y)
                            set z = GetLocationZ(loc)+GetUnitFlyHeight(dat.target)
                           
                            call dat.setTargetPos(x, y, z)
                        endif
                        if (dat.FLAG__targetFollow) then
                            set vC           = dat.VECTOR__target
                            set x            = Atan2(vC.y-vB.y, vC.x-vB.x)                                      // Direction To Target (radians)
                            set y            = SquareRoot((vC.x-vB.x)*(vC.x-vB.x) + (vC.y-vB.y)*(vC.y-vB.y))    // Distance To Target
                            set z2           = vC.z-vB.z                                                        // Height Difference
                           
                            set vA.x         = dat.speed * Cos(x) * DATA__loopRef
                            set vA.y         = dat.speed * Sin(x) * DATA__loopRef
                            set vB           = dat.VECTOR__start
                            set x            = SquareRoot((vB.x-vC.x)*(vB.x-vC.x) + (vB.y-vC.y)*(vB.y-vC.y))    // Total Distance

                            set dat.VECTOR__acceleration.z  = -8*dat.arc*dat.speed*dat.speed/x                                                  
                            set vA.z                        = -dat.VECTOR__acceleration.z * y/(dat.speed*2) + dat.speed*z2/y
                           
                            set dat.VECTOR__acceleration.z  = dat.VECTOR__acceleration.z   *DATA__loopRef*DATA__loopRef
                            set vA.z                        = vA.z                         *DATA__loopRef
                        endif
                    endif
                       
                    if dat.FLAG__active then
                        if not dat.FLAG__isPaused then
                            set dat.DATA__timeLeft = dat.DATA__timeLeft - (DATA__loopRef*dat.timescale)
                        endif
                        if dat.DATA__timeLeft <= 0.00  then
                            set dat.FLAG__active = false
                            call dat.onFinish( )
                            if (dat.toDestroy and not dat.destroyed) then
                                call dat.destroy( )
                            endif
                        endif
                    endif
                endif
                set i = i - 1
            endloop
            set u = null
        endmethod
    //*
    //*
    //* Constructor
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        static method create takes unit u returns thistype
            local thistype dat  = thistype.allocate()
            local real x        = GetUnitX(u)
            local real y        = GetUnitY(u)
               
            call UnitAddAbility(u, ID__flyHeightModifier)
            call UnitRemoveAbility(u, ID__flyHeightModifier)
            if DATA__size == 0 then
                call TimerStart(DATA__loop, DATA__loopRef, true, function thistype.doLoop)
               
            endif
            call MoveLocation(loc, x, y)
            set totalProjectiles = totalProjectiles + 1
           
            set dat.toUnit                  = u
            set dat.DATA__index             = DATA__size
            set dat.VECTOR__position        = vector.create(x, y, GetLocationZ(loc)+GetUnitFlyHeight(u))
            set dat.VECTOR__target          = vector.create(0, 0, 0)
            set dat.VECTOR__velocity        = vector.create(0, 0, 0)
            set dat.VECTOR__acceleration    = vector.create(0, 0, 0)
            set dat.VECTOR__start           = vector.create(0, 0, 0)
            set DATA__stack[DATA__size]     = dat
            set DATA__size                  = DATA__size + 1
               
            return dat
        endmethod
    //*
    //*
    //* Setup & Initialization
    //* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
        private static method onInit takes nothing returns nothing
            set rct = Rect(0, 0, COLLISION__sizeMax, COLLISION__sizeMax)
           
            set FUNC__enumUnits = Filter(function thistype.doLoopEnum)
            set FUNC__enumDests = Filter(function thistype.doLoopDestFilter)
           
        endmethod
    //*
    //*
    //*********************************************************************************************************
    endstruct




    endlibrary


    Changelog
    Once version 2.0 was released I scrapped most of the change-log information for the previous versions and added headers to various data-types to explain how to use them. The change-log that is posted with the code is not guaranteed to always have a previous version included, so I'll try to keep them here.

    Version 2.1.0 - Bug fixed caused by a projectile-target being removed from the game. Also tweaked the
    target
    operator to halt the targeting if a
    null
    value is used.

    Added a new member to the projectile interface labeled
    targetZOffset
    which is designed to give the user control over where the projectile hits the target relative to its height (useful for target-homing projectiles).


    Version 2.1.1 - Removed
    FLAG__onFinish
    to allow users to repeatedly launch their projectiles once they have finished. The method
    launch
    should be able to be executed from within the
    onFinish
    method.


    Version 2.1.2 - Optimized the
    enumNearby
    method as corrected by Bribe.


    Version 2.1.3 - Fixed a minor issue with projectiles being destroyed prematurely by adding the member "destroyed" to the projectile struct. Instead of using the expression
    proj == 0
    to check if the projectile has been destroyed, the user can now use the explicit expression,
    proj.destroyed
    .

    Added method operator to "projectilegroup" to reference members of the group using array syntax, rather than the
    indexOf
    member. Both methods are available now.

    Changed the scope of
    DATA__stack
    and
    DATA__size
    to readonly so that users can now iterate through the stack of projectiles if they want to.


    Version 2.2.0 - Removed one of the hashtable requirements, the system only requires 2 hashtables now.

    Removed the array member in the projectile-group named
    indexOf
    . There is an available alternative by simply using array syntax on the group as if the group were an array.

    Removed the limit of how many projectiles can be stored in a projectile group.

     

    Attached Files:

    Last edited: Oct 16, 2011
  2. CHA_Owner

    CHA_Owner

    Joined:
    Feb 11, 2008
    Messages:
    799
    Resources:
    2
    Spells:
    2
    Resources:
    2
    Nice to see you did release one i remember reading you were going to in one of Anachron's threads.
     
  3. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Ew.

    1.) You have tons of variable settings & gettings in the loop method, which isn't necessary be used.
    2.) You only have zArc.
    3.) Way to complicated.
    4.) Private variables shouldn't have a pre with 'priv_'.
    5.) It's hard to manipulate the missle.
    6.) You should use stub methods to allow the user to run defined actions on collide, touch, etc.
    7.) Due to the above points, your engine is not really able to handle a lot missles.

    Edit:
    8.) projectilegroup should be private.
    9.) There is no option to pause a missle
    10.) And still, why do you also work on a missle engine now? You are free to help me with mine.
     
  4. Dynasti

    Dynasti

    Joined:
    Oct 18, 2007
    Messages:
    877
    Resources:
    4
    Maps:
    2
    Spells:
    1
    Tutorials:
    1
    Resources:
    4
    Eeew! THE LEAKS, IT BURNS!!!! Delete yourself from the internet now!



    O.o
    Nah I'm just kidding, code looks ok, nice documentation. Keep more comming! +cookie.
     
  5. Berb

    Berb

    Joined:
    Jan 21, 2006
    Messages:
    2,539
    Resources:
    2
    JASS:
    2
    Resources:
    2
    How would you recommend any dynamic elements at all without setting/getting variables. The variables that I have set up to use are there because if I were to expand them and just use the values they represent, it would actually take longer to derive those values and affect the performance of the system when it is stressed. I have optimized this in just about every place, over the past couple of weeks. It has been rewritten many times to update/optimize the code from a fresh perspective.

    Though I have not really added any way for the user to manipulate the projectile's acceleration, by doing so it would be possible to derive any arc on the (x, y, z) dimensions.

    What?

    I can name my private variables whatever I want, and its actually easier for the user to read through which variables he/she can reference or not.

    What are you trying to do?

    I use an interface that the struct extends, with the "stub methods" declared in the interface. These methods are not declared within the
    projectile
    struct, though they are referenced within various functions. This gives users the same responses to common projectile events, such as
    onUnitCollision
    or
    onFinish
    .

    Actually stub-methods (this is shared with interface methods) are known as "virtual methods" and they are quite slow, though I eliminate as much of the overhead as I can by sacrificing the
    onLoop
    method.

    Then you wouldn't be able to use it. The
    enumNearby
    static method takes a
    projectilegroup
    as a parameter, and adds enumerated units to it (just like the unit equivalent in the JASS API). The two members are
    readonly
    so they can only be read, and manipulated with the
    enumNearby
    method. In order to reference all of the projectiles within the group you can simply do:

    Code (vJASS):
    local projectilegroup g = projectilegroup.create()
    local integer i = 0

    call projectile.enumNearby(g, x, y, z, radius)
    loop
        exitwhen(i == g.max)
        // Reference the projectile as g.at[i]
        set g.at[i].timescale = 0.00
        set i = i + 1
    endloop
    call g.destroy()


    If you set the projectile's time-scale to 0.00, it will "pause" it. I have shown how to reference the projectile's time-scale in the example code above. If that is not enough then I could add a feature (and your request) to be able to pause the projectiles.

    Is that what this is about?
     
  6. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Still its somehow kinda fat.

    Then you should add that. I allowed the user to use acceleration and haze (which makes orb movement possible) and xy and zArc.

    No it's not and honestly, you shouldn't do that. Just because you can doesn't say you should.

    Yes, but you have like no user event responses.

    That's nothing compared to the hell of variables you set ever and every again. Check my Loc struct which does a lot of great stuff for me.

    Oh I got it wrong. Then it's okay I guess. But projectilegroup is really to much, users could simply do it themself.

    I guess a boolean whether the missle is paused or not would be a lot smarter.
     
  7. Berb

    Berb

    Joined:
    Jan 21, 2006
    Messages:
    2,539
    Resources:
    2
    JASS:
    2
    Resources:
    2
    I think that a projectile group is pretty standard in that it is going to be necessary, one way or another, to reference a specific set of projectiles. Since it will always be necessary, there is no reason that I should not include it with a safe implementation so that users can reference projectiles but not do things that make them unable to work.

    And where exactly do you find these "vJass Rules"?

    Why do you keep comparing mine to yours as if yours is some enigmatic entity of which only I could dream. It really sounds like you haven't even looked into anything you're saying.

    What do you mean, "user event responses"? I have ways for the user to define methods that are executed when specific projectile events are met --this is how it is intended to function, and does function, well.

    I don't really know what you mean by that.

    I don't get it. Your
    Loc
    struct just hides all of the variable "setting/getting" and replaces it with function calls that end up setting/getting these variables. Why do you keep on comparing this to yours (when yours isn't even done) and telling me to change things so that they are the same as yours.

    Also, in your
    run
    method all you do is call functions that call functions that do your calculations for you. This is an extremely inefficient approach because function calls in general take a long time to execute, and you're only multiplying the effect by having many functions called for doing many different things. What I do in my script is combine it all, and simplify everything down to its basics. This gives makes the operations that the projectile library must perform execute as quickly as possible, to maximize the performance return.

    None of the "facts" that you have stated so far are true, and I know you to be a level-headed fellow --which is why it seems kind of strange you're bringing up issues that aren't even real, such as the private variable naming convention. I have been coding in JASS since 2004 and there has never been an issue with syntax style, each library is coded in the manner of the author, simple as that. There is no standard for naming private/public variables, frankly that's horse sh*t.

    Code (vJASS):
            public method setTargetPos takes real x, real y returns nothing
                set .end.x = x
                set .end.y = y
                set .end.z = GetLocZ(x, y)
                set .target = null
                set .targetZ = false
            endmethod


    Here is a snippet of code from your system. You use
    GetLocZ(x, y)
    which just calls
    MoveLocation()
    and returns
    GetLocationZ()
    . You also have a whole chunk of "setting/getting" variables which you previously stated was "inefficient". Not to mention what I said before, about functions being slow, so not only do you perform the same "slow" operations that I do, but they are even slower due to your tree of function executions.
     
    Last edited: Mar 26, 2010
  8. syltman

    syltman

    Joined:
    Jun 13, 2007
    Messages:
    1,326
    Resources:
    0
    Resources:
    0
    you should do the users of this system a favor and link them to the vector system.
     
  9. Berb

    Berb

    Joined:
    Jan 21, 2006
    Messages:
    2,539
    Resources:
    2
    JASS:
    2
    Resources:
    2
    Done.
     
  10. xD.Schurke

    xD.Schurke

    Joined:
    Feb 22, 2006
    Messages:
    948
    Resources:
    5
    Maps:
    1
    Spells:
    4
    Resources:
    5
    don't use public :D.... there is acutally no sense in vJass to use public prefix
     
  11. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Of course. If you want to make the script be able to be used from other librarys but with the prefix only. This is for example if you mix a few systems which share the same function name but you want them not cause problems when used with another system.

    However, even then its up to the user to change the methods to public :D
     
  12. xD.Schurke

    xD.Schurke

    Joined:
    Feb 22, 2006
    Messages:
    948
    Resources:
    5
    Maps:
    1
    Spells:
    4
    Resources:
    5
    ....so in the end don't use public... use unique function names :D
    function theRealProjectileCreationFunction....
     
  13. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    No way, you are not serious?
     
  14. Berb

    Berb

    Joined:
    Jan 21, 2006
    Messages:
    2,539
    Resources:
    2
    JASS:
    2
    Resources:
    2
    The public keyword is, like my private naming, complete preference. I use public/private to explicitly declare the scope of a variable. It also lines up the code nicely.

    There is no benefit in not using public, so I'll stick with what I'm doing now.

    Not really sure what you're saying here.

    The test-map has been updated with a spell for the Footman that shows how this system can also be used to make units "jump".
     
    Last edited: Mar 26, 2010
  15. xD.Schurke

    xD.Schurke

    Joined:
    Feb 22, 2006
    Messages:
    948
    Resources:
    5
    Maps:
    1
    Spells:
    4
    Resources:
    5
    there is no benefit in using public prefix....
     
  16. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Berbanog, this is maybe offtopic, but why does your eventHandler work and mine not? I have it exactly like yours now, but I get a parse error. (JassHelper wants to have the methods that take something in the parent struct). Why?
     
  17. Berb

    Berb

    Joined:
    Jan 21, 2006
    Messages:
    2,539
    Resources:
    2
    JASS:
    2
    Resources:
    2
    What exactly do you mean by my "eventHandler"?

    No, other than readability I suppose. It makes the coder look nicer, too, to have explicit scope definitions even if the scope is public. Getting rid of it would not benefit my system in any way, so there is no point.
     
    Last edited: Mar 27, 2010
  18. watermelon_1234

    watermelon_1234

    Joined:
    Nov 18, 2007
    Messages:
    1,066
    Resources:
    10
    Spells:
    9
    JASS:
    1
    Resources:
    10
    I played the test-map and I don't really understand what's going on.
    I was expecting to see a fireball spell and some kind of barrier that slows missiles. Instead, I get to let my surplus army of footmen Jump and use some spell that slows their speed when jumping.

    There's also an Archmage and Warden with default spells, making it odd since I expected them to have spells showing the usage of the system.
    Units also jump around erratically whenever they cast a spell. It's actually kind of amusing.

    Sorry that I misunderstood how I was supposed to test the system. :\
     
  19. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    But if you use any unit as missile then you can't have that much features.
    (Like unselectable and such)
     
  20. watermelon_1234

    watermelon_1234

    Joined:
    Nov 18, 2007
    Messages:
    1,066
    Resources:
    10
    Spells:
    9
    JASS:
    1
    Resources:
    10
    Isn't it relatively easy to add 'Aloc' to a unit that you only want to treat as a missile?