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

[System] Custom Projectiles

Level 18
Joined
Jan 21, 2006
Messages
2,552
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:
¯¯¯¯¯¯¯¯¯¯¯¯¯
==============================================================================
  • 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:

JASS:
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.

JASS:
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.



JASS:
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

JASS:
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

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.

 

Attachments

  • Projectiles2.0.w3x
    80.1 KB · Views: 591
  • Projectile v2.1.0.w3x
    88.9 KB · Views: 490
  • Projectile v2.1.1.w3x
    89.4 KB · Views: 521
  • Projectile v2.1.2.w3x
    90.6 KB · Views: 713
  • Projectile v2.1.3.w3x
    93.8 KB · Views: 349
  • Projectile v2.2.0.w3x
    91.9 KB · Views: 820
Last edited:
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.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Anachron said:
1.) You have tons of variable settings & gettings in the loop method, which isn't necessary be used.

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.

Anachron said:
2.) You only have zArc.

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.

Anachron said:
3.) Way to complicated.

What?

Anachron said:
4.) Private variables shouldn't have a pre with 'priv_'.

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.

Anachron said:
5.) It's hard to manipulate the missle.

What are you trying to do?

Anachron said:
6.) You should use stub methods to allow the user to run defined actions on collide, touch, etc.

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.

Anachron said:
7.) Due to the above points, your engine is not really able to handle a lot missles.

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.

Anachron said:
8.) projectilegroup should be private.

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:

JASS:
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()

Anachron said:
9.) There is no option to pause a missle

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.

Anachron said:
10.) And still, why do you also work on a missle engine now? You are free to help me with mine.

Is that what this is about?
 
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.
Still its somehow kinda fat.

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.
Then you should add that. I allowed the user to use acceleration and haze (which makes orb movement possible) and xy and zArc.

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.
No it's not and honestly, you shouldn't do that. Just because you can doesn't say you should.

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.
Yes, but you have like no user event responses.

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

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:

JASS:
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()
Oh I got it wrong. Then it's okay I guess. But projectilegroup is really to much, users could simply do it themself.

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.
I guess a boolean whether the missle is paused or not would be a lot smarter.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Anachron said:
But projectilegroup is really to much, users could simply do it themself.

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.

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

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

Anachron said:
I allowed the user to use acceleration and haze (which makes orb movement possible) and xy and zArc.

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.

Anachron said:
Yes, but you have like no user event responses.

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.

Anachron said:
Still its somehow kinda fat.

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

Anachron said:
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.

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.

JASS:
        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:
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
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
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.

xD.Schurke said:
don't use public :D.... there is acutally no sense in vJass to use public prefix

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

Anachron said:
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.

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:
Level 18
Joined
Jan 21, 2006
Messages
2,552
What exactly do you mean by my "eventHandler"?

xD.Schurke said:
there is no benefit in using public prefix....

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:
Level 14
Joined
Nov 18, 2007
Messages
1,084
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. :\
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
I'm not really sure what you're saying so please bear with me.

Well when you use other unittypes then your missiletype you will not be able to access to its data, so it might be that the unit has Aloc by itself or such so it gives a lot of errors.
Are you saying that it would be bad to add 'Aloc' in case the unit already had the ability before acting as a missile?

Also I think it's just to easy to generate a new missile as effect with the unitmodel and hide it over the flighttime.
What?
 
What I am telling is.

When you allow the user to use any unit as missile:
  • The unit might be involved in other systems.
  • You don't really know which functions will work since the values of the unit are not set by you but by the user.
  • Missileengines are not thought to provide all features. (Jump actually has nothing to do with a missile engine)
  • However, if you still want to use a missile engine as jump engine you can create a new missile with the model of the unit and then hide the unit while the missile flies.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Anachron said:
The unit might be involved in other systems.

Nobody is forcing you to use normal units. You can always easily create a dummy unit that has the locust ability.

Anachron said:
You don't really know which functions will work since the values of the unit are not set by you but by the user.

All of the functions work, you need to give a more specific example.

Anachron said:
(Jump actually has nothing to do with a missile engine)

Well why not include as many possibilities as the user may desire. There is no point in restricting the functionality on the user-end because the user is capable of causing inconsistencies. I am not trying to make this system for people who just jam code together without having any idea whats going on.

watermelon_1234 said:
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.

Well when you issue an order it sends a fire-ball to the target of the order. The spell simply picks nearby projectiles and sets their time-scale to 30% (or 0.3) which "slows" them. I added the "Jump" spell to all of the Footman so that you could see an example of how this can be used to simulate jumps (missile-type movement on non-missile units).

I will update the test-map after I put some work into it and give it more of a tester-interface, so that it is clear and concise as to what the user is doing. I will also add some cool spells to show the capabilities of the missile system.

I would also like to add, though it is besides the point, that this system can handle quite a large quantity of projectiles being launched/destroyed at any given time. I found it difficult to optimize it further such that I could have more features while still keeping the weight of the system slim.

Anachron, how did you instantly get your system up in the spells section?
 
All of the functions work, you need to give a more specific example.
If everything works, you might not have the functionality of changing the zAngle of units, do you?

Anachron, how did you instantly get your system up in the spells section?
Yes, just make an example and upload it there too.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Anachron said:
If everything works, you might not have the functionality of changing the zAngle of units, do you?

No, the "z angle" or pitch rotation of the unit will be adjusted automatically (if pitch rotation is enabled) based on the velocity of the unit.

I just downloaded the current test-map to see what was missing from it, and it turns out that the fireballs don't even fire when you're being issued a point order. I must have disabled this or something lol I'm sorry for the confusion.

JASS:
    call TriggerRegisterPlayerUnitEvent(jump, Player(0), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
    call TriggerAddCondition(t, Filter(function jumpfilter))
    call TriggerAddAction(jump, function jumpmain)

When I added the jump (instead of having a condition on the jump trigger) I have the condition on the t trigger. My mistake :S

Okay, after fixing the test-map (will upload this temporarily before a newer, better one) and testing it on my laptop (which apparently runs faster than my desktop) I handled over 500 projectiles at the same time, with a drop in frame-rate of 10 frames per second. If I have 700 projectiles there is a drop of 30 frames per second. This is still an extremely good return.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Because you need to use a unit to create a projectile. Once the projectile is created, it can be launched using method doLaunch. The create method adds/removes the raven-form ability and adds the projectile to a stack. When a projectile is launched then .active is flagged off, disallowing the projectile from being launched until it has finished its projection and .active is flagged on again.

I wanted to keep creation/launching separate, I suppose would be my answer.

The examples of how to use it are in the custom script, but I will be updating the test-map shortly. I've made a few important updates since the release so I'll also be including those changes in my update.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Because there is no point in only optionally allowing "own" unit using. If you want to use a unique unit then pass the function CreateUnit as its parameter. I don't think that's what he meant when he asked why my create method takes a unit parameter.

When I release the update to my test-map I'll include a good example of how you can easily customize a projectile to your liking. It would go something like this:

JASS:
struct missile extends projectile

    effect      model       = null
    string      modelpath
    
    static constant integer     dummyid     = 'dumy'
    
    method onDestroy takes nothing returns nothing
        if (.model!=null) then
            call DestroyEffect(.model)
        endif
    endmethod

    static method create takes unit source, real heightoffset, real x, real y, real zoffset, real speed, real arc, string modelpath returns thistype
        local thistype  proj
        local vector    vA
        local vector    vB
        local real      xA      = GetUnitX(source)
        local real      yA      = GetUnitY(source)
        
        call MoveLocation(tempLoc, xA, yA)
        set vA = vector.create(xA, yA, GetLocationZ(tempLoc)+heightoffset)
        call MoveLocation(tempLoc, x, y)
        set vB = vector.create(x, y, GetLocationZ(tempLoc)+zoffset)
        
        set proj            = thistype.allocate(CreateUnit(GetOwningPlayer(source), .dummyid, xA, yA, Atan2(y-yA, x-xA)*bj_RADTODEG))
        set proj.model      = AddSpecialEffectTarget(modelpath, proj.toUnit, "origin")
        set proj.modelpath  = modelpath
        set proj.source     = source
        call proj.doLaunch(vA, vB, speed, arc)
        
        call vA.destroy()       // The projectile constructor method copies these vectors, so they must be destroyed.
        call vB.destroy()
        
        return proj
    endmethod

endstruct
 
Last edited:
Level 18
Joined
Jan 21, 2006
Messages
2,552
Having the constructor take a unit parameter means that there are no restrictions for the units. If you're recycling your units then there wouldn't be a problem because you can recycle a unit, and then pass the unit as a projectile.

Just to clarify, what I've done here is made a basic blue-print of projectile motion. I've left as many areas of the code as I could open-ended so it is not restricted by what the user desires to do with the projectile. In terms of the motion-path, it is relatively restricted at this point because there are only a few methods available to the user that can change the course of the projectile.

If unit recycling is something you do in your map, or wish to do, then there should be no problem using recycled units as projectiles. Once a projectile completes it's course, and toDestroy is enabled, then the projectile will be destroyed once it completes. After this point you can do what you want to the unit, so long as toKill is disabled (so the unit is not automatically destroyed). Then, once the projectile finishes and onFinish is executed, you could recycle the units as you see fit.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
When you want to apply the laws of a projectile to a non-missile unit. Jumping is a good example. Having a special dummy with locust would be handled the exact same way as a non-dummy without locust; so there is no point in depriving users of as much functionality as possible.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
That may be because you have no easy way how to set/change the missile sfx. (f.E.)
Could you just use the specialized dummy for that? (And add the special effect with AddSpecialEffectTarget. Then use a struct member to store that effect to destroy it later when the missile is complete. This was done in the example.)
It's a bit harder to do though since the user has to manually do it himself but it's not incredibly difficult if you have basic vJass knowledge.

I haven't tried the new testmap yet. I'll post or edit this post if I find the time to.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
watermelon_1234 said:
Could you just use the specialized dummy for that? (And add the special effect with AddSpecialEffectTarget. Then use a struct member to store that effect to destroy it later when the missile is complete. This was done in the example.)
It's a bit harder to do though since the user has to manually do it himself but it's not incredibly difficult if you have basic vJass knowledge.

Plus you only have to do it once. Its not like you have to re-write it over and over for each projectile. If you want I can include a basicprojectile or something that stores the values of common missile properties such as damage, model, area of effect, etc. When I made this I was thinking that people would be willing to extend the projectile struct and manage their own members on top of the default projectile struct members allowing the user to do virtually whatever he/she wants.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Anachron said:
Yes, that was a nice idea. However, people also like functionality that can be easily enabled by putting the values into the objects so they don't have to make it themself.

So what you're saying is I should dumb my system down for the average Joe. No thanks.

Anachron said:
However, I see no reason for this library now since I made CustomMissile.

I've also had this up in the submissions since before you even had a test-map, at which I could say I don't see the point of yours.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
I finally got around to actually stress-testing your system, and on my laptop I can only handle around 160 before the frame-rate starts dropping to an unplayable level. The playback on my projectiles allows me (on the same computer) to create nearly three times as many as yours under the exact same conditions.

Also when I use your system and apply set .zarc = 0.03 the missile seems to just disappear shortly after being created. You seem to have a lot of special functionality in your system but the stability of it all really fails. I'm going to go through some of the functionality that your library claims and see how stable all of this really is... there is no use in having several thousand features if only one of them actually works.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Well there is a large inefficiency coming from your abuse of function calls. In addition to that there is a reason I excluded the interface method onLoop and that's because it is extremely slow. Virtual methods being executed on quick periodic intervals results in a low efficiency return.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
It may be easier for the user, but it also makes your system about 25% slower. I doubt it allows that much more user ease anyways, after all everything that must be done with the projectile seems to be done directly within your system, so why slow things down.
 
Top