• 🏆 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] AdvancedProjectile

Level 3
Joined
Dec 16, 2010
Messages
31
Another Projectile library!

I was looking through the xe system/modules and really liked how xefx and xecollider worked. However I noticed that it was a bit difficult to get angled/arced motion with the xecollider. I knew there were other systems like custom missile or xemissile but I decided to try to make my own.

A lot of base code is heavily inspired/taken/modified from xecollider. This includes the indexing/timer/recycling system. However I chose to use GroupUtils for group recycling because I am used to it.

Requirements:
- Vector3 (By me, included below)
- xebasic, xefx from the xe system by Vexorian
- GroupUtils
- A dummy model that has pitch animations. I use the model and object data
From the xe system linked above.

Notes:
- Vector3 is a library/struct for 3D vectors and related functionality.
- Uses xefx for the effects aspect of the library (delegates like xecollider does)
- The test map I am including has a bunch of junk for presentation, such as hero revival, damage numbers, cast bar, effects, etc.
- The test map uses JCast for the spell system. This is again just for presentation.
- I am aware that Projectile is a generic name for a struct, but I was thinking that you wouldn't normally have more than one projectile library/struct (would you?)
- A simple example of code is shown at the bottom of this post.

Pros:
- Detailed properties and useful events for fully controllable projectiles.
- Easy to use, by just extending and implementing events (like in xecollider)
- Fire and forget! The system cleans up its own vectors, projectiles, effects, groups etc.

Cons:
- Uses multiple Vector3 structs per projectile.
- Does not consider terrain height. So a projectile flying over bumpy terrain will follow the ground and might look like it's tripping out. I may change this later.


I was hoping to get some comments/suggestions/criticism.
This is my first system I am trying to polish.
Please try out the test map to see cool spells, and thanks for reading.


Here's the Projectile library:
JASS:
library AdvancedProjectile uses Vector3, xefx, GroupUtils
//---------------------------------------------------------------------//
//                  Advanced Projectile by rogueteddybear
//---------------------------------------------------------------------//
//
// This was inspired heavily by the xecollider part of xe0.8 by Vexorian
// You will find A LOT of similarities between the two.
// What I was trying to get with this is a smooth path effect that goes
// in all directions. This takes advantage of some vector math.
//---------------------------------------------------------------------//
//
// How to use:
//
//  I recommend creating your own struct, and extending Projectile.
//  This gives you access to events triggered automatically by the system.
//
//  Events:
//   __________________________________________________________________
//  |onUnitHit takes unit hitTarget returns nothing defaults nothing
//  |   - hitTarget: the unit that the projectile passed through.
//  |
//  | Implement this if you want to detect when the projectile hit a unit.
//  | This is the best time to do damage.
//  +------------------------------------------------------------------
//  |onTargetReached takes nothing returns nothing defaults nothing
//  |
//  | Implement this if you want to detect when the projectile
//  | reaches its target (on the ground). By default this will trigger 
//  | only after onUnitHit thanks to collision size/distance checks.
//  | Note: projectiles will circle around targets if they can't turn
//  |       well enough. This is under your control.
//  +------------------------------------------------------------------
//  |onHitGround takes nothing returns nothing defaults nothing
//  |
//  | Implement this if you want to detect when the projectile
//  | hits the ground. This will trigger if the projectile STARTS
//  | from the ground (z = 0.0), so to avoid that simply set
//  | the origins vector's z value to something greater than 0 when
//  | passing it to the create() function (see below)
//  +------------------------------------------------------------------
//  |onTimerTick takes nothing returns nothing defaults nothing
//  |
//  | This is the equivelent of loopControl from xecollider.
//  | This gets called every update (every DEFAULT_INTERVAL)
//  +------------------------------------------------------------------
//  |onExpired takes nothing returns nothing defaults nothing
//  |
//  | This is triggered when the lifetime of the projectile reaches 0
//  +------------------------------------------------------------------
//
//  Creation/Destruction:
//   __________________________________________________________________
//  |static create(Vector3 origin, Vector3 direction, Vector3 target)
//  |
//  |    - origin: a vector specifying where the projectile starts
//  |    - direction: a vector pointing in the direction the projectile
//  |                 will move in at the start
//  |    - target: a vector specifying the initial target that the projectile
//  |              is headed fore. NOTE: this can and probably will change if
//  |              a homingTarget is specified.
//  |
//  | This creates the projectile and registers it for updates. At this point,
//  | its speed, and acceleration is 0. So you have to also set those.
//  | If you extend Projectile and need to use create, you can call:
//  | allocate(origin, direction, target) inside your custom create.
//  +------------------------------------------------------------------
//  |Projectile::terminate()
//  |
//  | Similarly to xecollider, this is the safest way to destroy.
//  | Try not to call destroy(), ever.
//  +------------------------------------------------------------------
//  
//  Useful public properties (case sensitive):
//  - [source]: set this to a unit to store the creator of the projectile.
//  - [owner]: the player owner
//  - [Source]: set this to set source, and owner at the same time
//  - [homingTarget]: set this to make the projectile home in on someone
//  - [speed], [maxSpeed], [acceleration]: movement vars you can change.
//  - [maxTurnXY], [maxTurnZ]: left/right and up/down rotation amounts per second
//  - [MaxTurn]: set both Z and XY rotation per second
//  - [x],[y],[z]: position vars, from xefx
//  - [fxpath]: the string path to the effect of the projectile.
//  - [checkCollisionHeight]: if true, considers height in hit detection
//  - [lifeTime]: The amount of time in seconds that this projectile 
//                has left before it terminates (counts down).
//  - [homingDelay]: The amount of time to delay the homing functionality.
//
//---------------------------------------------------------------------//
globals
    //the same constants as found in xecollider
    private constant real DEFAULT_COLLISION_SIZE  =   50.0
    private constant real DEFAULT_MAX_SPEED       = 1500.0
    private constant real DEFAULT_EXPIRATION_TIME =    5.0
    private constant real DEFAULT_TARGET_DISTANCE =   25.0 //max distance at which onTargetRecahed is triggered
    
    //Rotation modes/styles:
    //NORMAL:     The projectile will rotate up/down in a more casual fasion.
    //            This is recommended for projectiles originating for the ground.
    //AGGRESSIVE: The projectile will rotate up/down more directly, as if it were a missile.
    //            This is recommended for projectils originiating from the air (missiles, firebombs)
    constant integer ROTATE_NORMAL        = 0
    constant integer ROTATE_AGGRESSIVE    = 1
    
    //similar to the constant in xebasic
    private constant real DEFAULT_INTERVAL        = 0.025
endglobals

//---------------------------------------------------------------------//
//                       IProjectile Interface
//---------------------------------------------------------------------//
// Interface for events that you can use when you extend Projectile.
interface IProjectile
    method onUnitHit takes unit hitTarget returns nothing defaults nothing
    method onTargetReached takes nothing returns nothing defaults nothing
    method onHitGround takes nothing returns nothing defaults nothing
    method onTimerTick takes nothing returns nothing defaults nothing
    method onExpired takes nothing returns nothing defaults nothing
endinterface

//---------------------------------------------------------------------//
//                   Projectile Struct - Extend this!
//---------------------------------------------------------------------//
struct Projectile extends IProjectile

    //effect carrier
    private delegate xefx fx
    
    //source of any damage
    unit source = null
    
    //unit to home in on, if needed
    unit homingTarget = null

    //Vectors for handling correct changes in position.
    //I suppose I could have just used a bunch of reals, or some arrays of reals.
    Vector3 forward  = 0 //<-- I don't recommend changing this vector directly, use SetProjectileFacing()
    Vector3 right    = 0 //<-- Same as above.
    Vector3 target   = 0 //<-- If the projectile has homingTarget, this vector is updated constantly.
    Vector3 position = 0
    
    integer rotationMode = ROTATE_NORMAL
    
    //basic properties - note these should be considered "per second"
    real speed          = 0.0
    real maxSpeed       = 0.0
    real acceleration   = 0.0
    real maxTurnZ       = 0.0
    real maxTurnXY      = (2.0 * bj_DEGTORAD)
    
    //life time and time before homing starts
    real lifeTime       = 0.0
    real homingDelay    = 0.0
    
    boolean hiddenExpiration = false
    boolean checkCollisionHeight = false
    
    //Static indexing stuff similar to in xecollider
    private static timer      TIMER
    private static integer    NUM_PROJECTILES = 0
    private static Projectile array PROJECTILES
    private static code       timerLoopFunction 
    
    //collision + data storage
    static group      enumGroup //i made this public so structs that extend this can use it.
    private static Projectile cinstance
    private static unit array picked
    private static integer    pickedN
    
    private real csize          = DEFAULT_COLLISION_SIZE
    private group seen
    private boolean dead        = false
    private boolean silent      = false
    
    //test
    //AxisDraw ad
    
    static method create takes Vector3 origin, Vector3 direction, Vector3 target returns thistype
        local thistype e = thistype.allocate()
        set e.position  = origin
        set e.target    = target
        set e.Forward   = direction
        set e.fx = xefx.create(e.position.x, e.position.y, e.forward.xyAngle)
        set e.lifeTime  = DEFAULT_EXPIRATION_TIME
        set e.maxSpeed  = DEFAULT_MAX_SPEED
        set e.seen      = NewGroup()
        set e.xyangle   = e.forward.xyAngle
        set e.zangle    = e.forward.zAngle
        
        //test
        //set e.ad        = AxisDraw.create(target.x, target.y, 0)
        
        set Projectile.PROJECTILES[Projectile.NUM_PROJECTILES] = e
        set Projectile.NUM_PROJECTILES = Projectile.NUM_PROJECTILES + 1
        if(Projectile.NUM_PROJECTILES==1) then
            call TimerStart(Projectile.TIMER, DEFAULT_INTERVAL, true, Projectile.timerLoopFunction )
        endif
        
        return e
    endmethod
    
    static method onInit takes nothing returns nothing
        set timerLoopFunction = (function thistype.Update)
        set TIMER = CreateTimer()
        set enumGroup = NewGroup()
    endmethod
    
    //use this to destroy
    method terminate takes nothing returns nothing
        set this.dead=true
        set this.fxpath=""
    endmethod
    
    method hiddenTerminate takes nothing returns nothing
        set silent = true
        call terminate()
    endmethod
    
    method onDestroy takes nothing returns nothing
        call forward.destroy()
        call target.destroy()
        call position.destroy()
        call right.destroy()
        if(silent) then
            call fx.hiddenDestroy()
        else
            call fx.destroy()
        endif
        call ReleaseGroup(seen)
        set source = null
    endmethod
    
    static method Update takes nothing returns nothing
        local Projectile this
        local integer i = 0
        local integer c = 0
        local integer j = 0
        
        loop
            exitwhen i >= Projectile.NUM_PROJECTILES
            set this = PROJECTILES[i]
            
            //Check liftime, simlarly to xecollider
            set this.lifeTime = this.lifeTime - DEFAULT_INTERVAL
            set this.homingDelay = RMaxBJ(this.homingDelay - DEFAULT_INTERVAL, 0.0)
            
            if(this.dead or (this.lifeTime <= 0.0) ) then
                if(this.onExpired.exists) then
                    call this.onExpired()
                endif
                if(hiddenExpiration) then
                    set silent = true
                endif
                call this.destroy()
            else
                if(this.HasHomingTarget()) then
                    call target.update(GetUnitX(this.homingTarget), GetUnitY(this.homingTarget), GetUnitZ(this.homingTarget)+50.0)
                endif
                
                //move the projectile forward
                call this.fwd()
                
                //if the homing delay is complete, home in on the target
                if(this.homingDelay <= 0.0) then
                    call this.steer()
                endif

                //Try collision at this point
                set .cinstance = this
                
                set Projectile.pickedN = 0
                call GroupEnumUnitsInRange( .enumGroup, x, y, .csize + XE_MAX_COLLISION_SIZE, function thistype.inRangeEnum)

                call GroupClear(this.seen)

                set j=0
                loop
                    exitwhen (j== Projectile.pickedN)
                        call GroupAddUnit( this.seen, Projectile.picked[j])
                    set j=j+1
                endloop

                //copy over destroyed structs
                set PROJECTILES[c]=this
                set c=c+1

                //loop control like in xecollider
                //call BJDebugMsg("Calling on Timer tick? "+B2S(this.onTimerTick.exists)+" vs "+B2S(this.onHitGround.exists))
                if( this.onTimerTick.exists and not this.dead ) then
                    call this.onTimerTick()
                endif
                
                //target was reached, most likely land, not a unit.
                if( this.onTargetReached.exists and not this.dead ) then
                    if(position.distSqr(target) < DEFAULT_TARGET_DISTANCE * DEFAULT_TARGET_DISTANCE) then
                        call this.onTargetReached()
                    endif
                endif
                
                //projectile hit the ground?
                if( this.onHitGround.exists and not this.dead ) then
                    if(position.z <= 0) then
                        call this.onHitGround()
                    endif
                endif
            endif
            set i = i + 1
        endloop
        
        //Pause the timer if there are no more projectiles
        set Projectile.NUM_PROJECTILES = c
        if(c==0) then
            call PauseTimer(Projectile.TIMER)
        endif
    endmethod
    
    //Collision detection filter
    //copied and altered from xecollider
    private static method inRangeEnum takes nothing returns boolean
        local Projectile this = .cinstance //adopt-a-instance
        local unit u=GetFilterUnit()
        
        if not IsUnitType(u, UNIT_TYPE_DEAD)  and not(this.dead) and (GetUnitTypeId(u)!=XE_DUMMY_UNITID) and IsUnitInRangeXY(u, x, y, .csize) then
            // ah, the advantages of a standardized unit id...
            if(not this.checkCollisionHeight or (this.checkCollisionHeight and this.HeightCheckSqr(u) <= .csize * .csize)) then
                //call BJDebugMsg("HIT UNIT: check? "+B2S(this.checkCollisionHeight)+" check: "+this.HeightCheckSqr(u)
                set Projectile.picked[Projectile.pickedN] = u 
                set Projectile.pickedN=Projectile.pickedN + 1

                if  not IsUnitInGroup (u, this.seen ) then
                    call this.onUnitHit(u)
                endif
            endif

        endif
        set u=null
        
        return false
    endmethod
    
    //Move the projectile forward in the direction it is facing.
    //Also calc speed acceleration
    private method fwd takes nothing returns nothing
        local real s = speed * DEFAULT_INTERVAL
        set s = RMinBJ(s + acceleration * DEFAULT_INTERVAL, maxSpeed)
        call position.update(position.x + forward.x * s, position.y + forward.y * s, position.z + forward.z * s)
        set x = position.x
        set y = position.y
        set z = position.z
    endmethod
    
    //Turn left (radians). This can be done with a global rotate because
    //the frame of reference never changes for x/y rotation
    private method turnLeft takes real angle returns nothing
        call forward.rotate(0, 0, angle)
        call right.rotate(0, 0, angle)
    endmethod
    
    //Turn right (radians). This can be done with a global rotate because
    //the frame of reference never changes for x/y rotation
    private method turnRight takes real angle returns nothing
        call forward.rotate(0, 0, -angle)
        call right.rotate(0, 0, -angle)
    endmethod
    
    //Change the direction of the projectil so that it points closer to
    //the target.
    private method steer takes nothing returns nothing
        local real sxy = maxTurnXY * DEFAULT_INTERVAL
        local real sz = maxTurnZ * DEFAULT_INTERVAL
        local Vector3 seek = VectorSeek(target, position)
        local Vector3 up = VectorCrossProduct(right, forward)
        local Vector3 seek2
        local Vector3 targetUp
        local real dotSide = 0.0
        local real dotForward = 0.0
        local real dotUp = 0.0
        
        if(rotationMode == ROTATE_AGGRESSIVE) then
            set targetUp = seek.copy()
        else
            set seek2 = VectorCrossProduct(seek, WORLD_UP)
            set targetUp = VectorCrossProduct(seek2, seek)
        endif
        
        call targetUp.normalize()
        call right.normalize()
        call up.normalize()
        
        set seek.z = 0
        call seek.normalize()
        
        set dotSide     = right.dot(seek)
        set dotForward  = forward.dot(seek)
        set dotUp       = up.dot(targetUp)
        
        //test
        //call ad.Update(forward, right, up)
        
        //Handle left/right steering
        if(dotForward < 0) then
            if(dotSide < 0) then
                call turnLeft(sxy)
            else
                call turnRight(sxy)
            endif
        else
            call turnRight(dotSide * sxy)
        endif
        
        set dotForward = forward.dot(targetUp)

        //handle up/down steering.
        //This uses local rotation because up/down rotation is relative
        //to the projectile, and not the world.
        if(rotationMode == ROTATE_AGGRESSIVE) then
            //Direct rotation - the projectile will always turn toward the target
            if(dotUp < 0.01) then
                call forward.rotateAxis(WORLD_ZERO, right, (1.0-RAbsBJ(dotForward)) * -sz)
            elseif(dotUp > 0.01) then
                call forward.rotateAxis(WORLD_ZERO, right, (1.0-RAbsBJ(dotForward)) * sz)
            endif
        else
            //Casual rotation, the projectil will not always turn directly toward the point.
            if(dotUp < 0.0) then
                if(dotForward > 0) then
                    call forward.rotateAxis(WORLD_ZERO, right, -sz)
                else
                    call forward.rotateAxis(WORLD_ZERO, right, sz)
                endif
            else
                call forward.rotateAxis(WORLD_ZERO, right, dotForward * -sz)
            endif
            
            call seek2.destroy()
        endif
        
        //rotate the effect so it lines up
        set xyangle = forward.xyAngle
        set zangle = forward.zAngle

        //cleanup
        call seek.destroy()
        call targetUp.destroy()
        call up.destroy()
    endmethod
    
    method HasHomingTarget takes nothing returns boolean
        return homingTarget != null and GetUnitState(homingTarget, UNIT_STATE_LIFE) > 0.
    endmethod
    
    method GetUnitZ takes unit u returns real
        local location xy = Location(GetUnitX(u),GetUnitY(u))
        local real z = GetLocationZ(xy)
        call RemoveLocation(xy)
        set xy = null
        return z
    endmethod
    
    //basically distance squared check, but put here
    //so i wouldn't have other outside library dependencies
    method HeightCheckSqr takes unit u returns real
        local real dx = x - GetUnitX(u)
        local real dy = y - GetUnitY(u)
        local real dz = z - (GetUnitZ(u)+50.)
        return dx * dx + dy * dy + dz * dz
    endmethod
    
    //Use this to set the direction the projectile is headed.
    //This also sets a correct "right" vector, which is essential.
    method operator Forward= takes Vector3 v returns nothing
        local Vector3 temp = 0
        if forward != 0 then
            call forward.destroy()
        endif
        
        set forward = v
        call forward.normalize()
        
        if(v.equals(WORLD_UP))then
            set temp = VectorSeek(target, position)
            set right = VectorCrossProduct(temp, WORLD_UP)
            call right.normalize()
            call temp.destroy()
        else
            set right = VectorCrossProduct(forward, WORLD_UP)
            call right.normalize()
        endif
    endmethod
    
    //Use this to make the projectile face certain angles (in radians)
    //However it will still home in on the target. To rotate the target as well,
    //use SetProjectileFacingEx()
    method SetProjectileFacing takes real angleXY, real angleZ returns nothing
        call turnLeft(angleXY - forward.xyAngle)
        call forward.rotateAxis(WORLD_ZERO, right, angleZ - forward.zAngle)
        set xyangle = forward.xyAngle
        set zangle = forward.zAngle
    endmethod
    
    //Rotate everything about this projectile to face a certain direction.
    //The target location is rotated ournd the projectiles current position
    //to keep it relatively accurate.
    method SetProjectileFacingEx takes real angleXY, real angleZ returns nothing
        local real diffXY = angleXY - forward.xyAngle
        local real diffZ  = angleZ  - forward.zAngle
        call SetProjectileFacing(angleXY, angleZ)
        call target.rotateAxis(position, WORLD_UP, diffXY)
    endmethod
    
    method operator Source= takes unit u returns nothing
        set source = u
        if(u != null) then
            set owner = GetOwningPlayer(u)
        endif
    endmethod
    
    method operator MaxTurn= takes real a returns nothing
        set maxTurnZ  = a
        set maxTurnXY = a
    endmethod
    
    //-------------------------------------------------//
    //XEFX - overwritten operators for safety
    method operator z= takes real value returns nothing
        set position.z = value
        set fx.z = value
    endmethod

    method operator x= takes real value returns nothing
        set position.x = value
        set fx.x = value
    endmethod

    method operator y= takes real value returns nothing
        set position.y = value
        set fx.y = value
    endmethod
    
endstruct
endlibrary

And the vector library:
JASS:
library Vector3 initializer Init_Vector3
//---------------------------------------------------------------------//
//                               Vector3
//---------------------------------------------------------------------//
//
// This is a struct that represents an arrow or point in 3D space.
// It is pretty similar to most other 3D vector implementations.
//
// It is easy to get carried away creating vectors so you need to be
// sure to destroy them after use. NOTE: some function return brand new
// Vector3 structs. These functions/methods are labeled.
//---------------------------------------------------------------------//
globals
    Vector3 REAL_UP
    Vector3 WORLD_UP
    Vector3 WORLD_BACK
    Vector3 WORLD_ZERO
endglobals

struct Vector3
    real x = 0.0
    real y = 0.0
    real z = 0.0
    
    //Constructor
    //** Returns brand new Vector3!
    static method create takes real x, real y, real z returns Vector3
        local Vector3 v = Vector3.allocate()
        set v.x = x
        set v.y = y
        set v.z = z
        return v
    endmethod
    
    //Copy this vector
    //** Returns brand new Vector3!
    method copy takes nothing returns thistype
        local Vector3 v = Vector3.allocate()
        set v.x = x
        set v.y = y
        set v.z = z
        return v
    endmethod
    
    //Creates a vector poiting in these angle "directions"
    //Angle in paramteres are degrees for simplified use (just here though, radians everywhere else!)
    //** Returns brand new Vector3!
    static method fromDegreeAngles takes real xya, real za returns Vector3
        local Vector3 v = Vector3.create(1,0,0)
        call v.rotate(0, -za * bj_DEGTORAD, xya * bj_DEGTORAD)
        return v
    endmethod
    
    //--------------------------------//
    //*       Basic Properties       *//
    //--------------------------------//
    
    //Returns the real length of the vector
    method length takes nothing returns real
        return SquareRoot(x*x+y*y+z*z)
    endmethod
    
    //Get the X/Y angle this vector is poiting in
    method operator xyAngle takes nothing returns real
        return Atan2(y,x)
    endmethod
    
    //Get the Z (up/down) angle this vector is pointing in.
    //probably not the most efficient way to do this
    //but the first one that came to mind
    method operator zAngle takes nothing returns real
        local Vector3 c = copy()
        local Vector3 c2 = copy()
        local real angle = 0.0
        set c2.z = 0.0
        set angle = Atan2(c.z, c.dot(c2))
        call c.destroy()
        call c2.destroy()
        return angle
    endmethod
    
    //--------------------------------//
    //* Basic Manipulation Functions *//
    //--------------------------------//
    
    method update takes real nx, real ny, real nz returns nothing
        set x = nx
        set y = ny
        set z = nz
    endmethod
    
    method add takes Vector3 v returns nothing
        set x = x + v.x
        set y = y + v.y
        set z = z + v.z
    endmethod
    
    method subtract takes Vector3 v returns nothing
        set x = x - v.x
        set y = y - v.y
        set z = z - v.z
    endmethod
    
    method multiply takes Vector3 v returns nothing
        set x = x * v.x
        set y = y * v.y
        set z = z * v.z
    endmethod
    
    method scale takes real s returns nothing
        set x = x * s
        set y = y * s
        set z = z * s
    endmethod
    
    method translate takes real tx, real ty, real tz returns nothing
        set x = x + tx
        set y = y + ty
        set z = z + tz
    endmethod
    
    //This function forces the vector length = 1
    //Useful for many things.
    method normalize takes nothing returns nothing
        local real l = length()
        set x = x / l
        set y = y / l
        set z = z / l
    endmethod
    
    //Rotate around GLOBAL AXIS!, this will always rotate around
    //the global x/y/z axis. Angles in radians.
    //For local or arbitrary rotation see rotateAxis below.
    method rotate takes real angleX, real angleY, real angleZ returns nothing
        local real xp = 0.0
        local real yp = 0.0
        local real zp = 0.0
        set yp = y * Cos(angleX) - z * Sin(angleX)
        set zp = y * Sin(angleX) + z * Cos(angleX)
        set xp = x
        //call BJDebugMsg("Rotate X("+R2S(angleX)+") x: "+R2S(x)+" vs "+R2S(xp)+ " | y: "+R2S(y)+" vs "+R2S(yp)+" | z: "+R2S(z)+" vs "+R2S(zp))
        set x = xp
        set y = yp
        set z = zp
        set zp = z * Cos(angleY) - x * Sin(angleY)
        set xp = z * Sin(angleY) + x * Cos(angleY)
        set yp = y
        //call BJDebugMsg("Rotate Y("+R2S(angleY)+") x: "+R2S(x)+" vs "+R2S(xp)+ " | y: "+R2S(y)+" vs "+R2S(yp)+" | z: "+R2S(z)+" vs "+R2S(zp))
        set x = xp
        set y = yp
        set z = zp
        set xp = x * Cos(angleZ) - y * Sin(angleZ)
        set yp = x * Sin(angleZ) + y * Cos(angleZ)
        set zp = z
        //call BJDebugMsg("Rotate Z("+R2S(angleZ)+") x: "+R2S(x)+" vs "+R2S(xp)+ " | y: "+R2S(y)+" vs "+R2S(yp)+" | z: "+R2S(z)+" vs "+R2S(zp))
        set x = xp
        set y = yp
        set z = zp
    endmethod
    
    //rotate the vector about an arbitrary point+axis that is local to this vector, angle in radians
    //http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.html
    //local variables are extensive in order to show off the usage of the function found in the link above
    method rotateAxis takes Vector3 p, Vector3 axis, real angle returns nothing
        local real cos = Cos(angle)
        local real sin = Sin(angle)
        //Point around which we are rotating - this can be (0,0,0) for simple local rotation at the origin of the vector.
        local real a = p.x
        local real b = p.y
        local real c = p.z
        //axis about which we are rotating, helps if normalized
        local real u = axis.x
        local real v = axis.y
        local real w = axis.z
        //other vars
        local real u2 = u * u
        local real v2 = v * v
        local real w2 = w * w
        local real d = u2 + v2 + w2 //denominator
        local real dRoot = SquareRoot(d)
        local real newX = 0.
        local real newY = 0.
        local real newZ = 0.
        
        set newX = a*(v2+w2)+u*(-b*v-c*w+u*x+v*y+w*z)+((x-a)*(v2+w2)+u*(b*v+c*w-v*y-w*z))*cos+dRoot*(b*w-c*v-w*y+v*z)*sin
        set newX = newX / d
        set newY = b*(u2+w2)+v*(-a*u-c*w+u*x+v*y+w*z)+((y-b)*(u2+w2)+v*(a*u+c*w-u*x-w*z))*cos+dRoot*(-a*w+c*u+w*x-u*z)*sin
        set newY = newY / d
        set newZ = c*(u2+v2)+w*(-a*u-b*v+u*x+v*y+w*z)+((z-c)*(u2+v2)+w*(a*u+b*v-u*x-v*y))*cos+dRoot*(a*v-b*u-v*x+u*y)*sin
        set newZ = newZ / d
        set x = newX
        set y = newY
        set z = newZ
    endmethod
    
    //Dot product another vector onto this one
    //This is a projection of vector v on this vector.
    method dot takes Vector3 v returns real
        return x*v.x + y*v.y + z*v.z
    endmethod
    
    method equals takes Vector3 v returns boolean
        return x == v.x and y == v.y and z == v.z
    endmethod
    
    method distSqr takes Vector3 v returns real
        return (v.x-x)*(v.x-x) + (v.y-y)*(v.y-y) + (v.z-z)*(v.z-z)
    endmethod
    
    method printD takes nothing returns nothing
        debug call BJDebugMsg("Vector["+I2S(this)+"] x: "+R2S(x)+" y:"+R2S(y)+" z:"+R2S(z))
    endmethod
endstruct

//---------------------------------------------------------------------//
//                         External functions
//---------------------------------------------------------------------//
//
// These functions can be used as shortcuts to get a result vector
// or value quickly. Note that most of these create brand new vector 
// structs that you should destroy.
//---------------------------------------------------------------------//

//remeber to destroy vectors after usage!

//** Returns brand new Vector3!
function VectorSubtract takes Vector3 v1, Vector3 v2 returns Vector3
    return Vector3.create(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z)
endfunction

//** Returns brand new Vector3!
function VectorAdd takes Vector3 v1, Vector3 v2 returns Vector3
    return Vector3.create(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z)
endfunction

//** Returns brand new Vector3!
function VectorMultiply takes Vector3 v1, Vector3 v2 returns Vector3
    return Vector3.create(v1.x * v2.x, v1.y * v2.y, v1.z * v2.z)
endfunction

function VectorDotProduct takes Vector3 v1, Vector3 v2 returns real
    return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z
endfunction

//** Returns brand new Vector3!
function VectorCrossProduct takes Vector3 v1, Vector3 v2 returns Vector3
    local Vector3 cross = Vector3.create(0,0,0)
    if(not v1.equals(v2)) then
        set cross.x = v1.y*v2.z - v1.z*v2.y
        set cross.y = v1.z*v2.x - v1.x*v2.z
        set cross.z = v1.x*v2.y - v1.y*v2.x
    else
        //default two parallel vectors to a global right
        //for whatever reason
        set cross.x = 1
    endif
    return cross
endfunction

//Return a normalized desired velocity, make sure to destroy after use
//** Returns brand new Vector3!
function VectorSeek takes Vector3 target, Vector3 origin returns Vector3
    local Vector3 v = VectorSubtract(target, origin)
    call v.normalize()
    return v
endfunction

function Init_Vector3 takes nothing returns nothing
    set WORLD_UP    = Vector3.create(0,0,1)//z axis = up
    set WORLD_BACK  = Vector3.create(0,-1,0)//z axis = up
    set WORLD_ZERO  = Vector3.create(0,0,0)
    set REAL_UP     = Vector3.create(0,1,0)
endfunction

endlibrary

HOW TO USE:

JASS:
//---------------------------------------------------------------------//
//                       Custom Fire Projectile
//---------------------------------------------------------------------//
struct FireBoltMissile extends Projectile

    real damage          = 0.0
    
    //if the firebolt hits the ground, make it explode?
    method onHitGround takes nothing returns nothing
        call this.terminate()
    endmethod
    
    method onUnitHit takes unit target returns nothing
        if ((HasHomingTarget() and target == homingTarget) or (not HasHomingTarget() and this.source != target and not IsUnitAlly(target, owner) and IsUnitAlive(target))) then

            //Damage the target unit here!
            
            call this.terminate()
        endif
    endmethod
endstruct

function CastFireBolt takes nothing returns nothing
    local unit caster = [Set this to the caster of the spell]
    local unit target = [Set this to the target to home in on]

    local FireBoltMissile p

    //Where the fireball come out from
    local Vector3 origin = Vector3.create(GetUnitX(caster), GetUnitY(caster), 50.0)
    
    //Where the fireball is headed initially. The system changes this
    //automatically if you set a homing target (like below)
    local Vector3 target = Vector3.create(GetUnitX(target), GetUnitY(target), 0)
    
    //What direction should the fireball be facing at the start?
    //In this case, in the direction the caster is facing, and up at 45 degrees.
    local Vector3 direction = Vector3.fromDegreeAngles(GetUnitFacing(caster), 45.0)

    //this is important. If the length of the direction vector != 1.0 then it looks bad
    call direction.normalize()
    
    
    //Create fire projectile here
    set p = FireBoltMissile.create(origin, direction, target)
    set p.Source = caster 
    set p.fxpath = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
    set p.speed = 700.0
    set p.maxTurnXY = 400.0 * bj_DEGTORAD
    set p.maxTurnZ = 250.0 * bj_DEGTORAD
    set p.lifeTime = 5.0
    set p.homingTarget = target
    set p.homingDelay = 0.2
    
    //firebolt specific
    set p.damage = 50.0

    set caster = null
    set target = null
    //Don't destroy the vectors. The projectile will do it when it dies.
endfunction


Extra model resources thanks to: Vexorian (Dummy model), Dan van Ohllus (Missile), s4nji (Icy Spike)
 

Attachments

  • IceArrows.jpg
    IceArrows.jpg
    81.6 KB · Views: 169
  • ProjectileSpells.w3x
    266.2 KB · Views: 99
Level 3
Joined
Dec 16, 2010
Messages
31
Well I am not trying to dethrone anyone.

Custom Projectiles seems to have nice features like doodad collision and projectile groups, but I wanted to make something more lightweight that didn't need modules and could just work by extending, creating, forgetting.

Also I saw Berb say:
If you're going to start making projectiles fully move in 3-dimensions, it will completely eliminate the whole idea of "arc". I'm going to stick to using only two dimensions,

My system uses 3 dimensions as fully as it can so I would imagine you would get different movement effects from Custom Projectile.

I have not tried xe projectile. I assume it works well too.
 
Level 3
Joined
Dec 16, 2010
Messages
31
Can you link that one; and what is TH?

I also don't really need people telling me there are lots of these, because I had known that. Just like people who make similar systems that become popular knew there were others before them. I am not trying to reinvent the wheel, just making it lighter and easier to use (hopefully).

And also I wouldn't mind feedback on the code and spells.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
http://www.thehelper.net/forums/showthread.php/146416-Projectile

And I can comment on your actual code eventually, but keep in mind that I'd hit you for stuff like

JASS:
interface IProjectile
    method onUnitHit takes unit hitTarget returns nothing defaults nothing
    method onTargetReached takes nothing returns nothing defaults nothing
    method onHitGround takes nothing returns nothing defaults nothing
    method onTimerTick takes nothing returns nothing defaults nothing
    method onExpired takes nothing returns nothing defaults nothing
endinterface
interface overhead is globals + 2 function calls. Better to code from scratch

and

struct Projectile extends IProjectile
regular structs have quite a bit of overhead, especially with extensions

and

JASS:
    static method onInit takes nothing returns nothing
        set timerLoopFunction = (function thistype.Update)
        set TIMER = CreateTimer()
        set enumGroup = NewGroup()
    endmethod
this is unstable as it's not in a module onInit method

and*****************

JASS:
    method onDestroy takes nothing returns nothing
        call forward.destroy()
        call target.destroy()
        call position.destroy()
        call right.destroy()
        if(silent) then
            call fx.hiddenDestroy()
        else
            call fx.destroy()
        endif
        call ReleaseGroup(seen)
        set source = null
    endmethod
this is a trigger evaluation.... use destroy >.<.

and etc


Also, if you really wanted some advanced projectiles, you should try using http://www.hiveworkshop.com/forums/submissions-414/snippet-position-184578/#post1782391


Honestly, I wouldn't use this if I wanted a light advanced Projectile System ; P, lol.

The very fact that this uses xe (wc3c is renowned for its heavyset scripts) is enough to make me shy away ; P.
 
Level 3
Joined
Dec 16, 2010
Messages
31
Thank you for the response.

interface overhead is globals + 2 function calls. Better to code from scratch

The choice to use interface is because it is easy to read and interpret. Subsequently it becomes easy to extend and manipulate in children structs.
I think I can live with the globals + 2 function calls even though I'm not sure what the dastardly consequences are. I also don't know what you mean by coding from scratch.

this is unstable as it's not in a module onInit method

I don't know what you mean by unstable. I have never had problems with onInits before. Can you link a discussion or something that proves this is not a good idea despite it being a feature in vJass? I think I would like to know more.

this is a trigger evaluation.... use destroy >.<.

it is called automatically when destroy() is called on the projectile. Otherwise im not sure if you meant something else.

I also looked at your Position library and I think it is a little heavy for what I want. The vector3 class is all I need for position/direction and it encapsulates all the functionality that I need for my 3D manipulation. And I am not exactly sharing the position amongst many systems. It is just a vector in the projectile. Perhaps I shot myself in the foot by calling this an AdvancedProjectile library. I should call it ProjectileLite or something, yes?

I hope I can get some second opinions, not that yours aren't appreciated.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
destroy is called when destroy is called and it's not a trigger evaluation. When people see onDestroy here, they freak (not just me).

Furthermore, coding a struct from scratch using the struct extends array feature of vjass will give less overhead.

onInit in a struct runs after onInit in a module, meaning that any module that uses this lib will not work. It's pretty much standard practice to use a module initializer now.

Interfaces are typically coded from scratch through the use of TriggerAddCondition and a global, thus cutting out the 2 functions in between.

Just saying that those are blatant improvements that can be made to the script to make it use less memory and make it run faster ; P. Not making those improvements is just sheer laziness ; ).

Your projectile movement loop is also bad as it would be faster as a linked list. A linked list would also change how the recycling is done (one reason to typically code allocation and deallocation from scratch).


I wouldn't personally use this script for the reasons above and more ; ).
 
Level 3
Joined
Dec 16, 2010
Messages
31
destroy is called when destroy is called and it's not a trigger evaluation. When people see onDestroy here, they freak (not just me).

I still don't get what you're talking about here. If I create a projectile using Projectile.create() and then let it expire or call .terminate(), then Projectile.destroy() is called, which causes onDestroy to run. I think this is built into vjass. no? Perhaps you are referring to the behavior that comes with the "struct extends array" that you mention. I read that onDestroy is pointless when using that.

onInit in a struct runs after onInit in a module, meaning that any module that uses this lib will not work.

That is more helpful. I did not know this. Thankfully the onInit in my Projectile library does not need to be the first thing to init. As long as it is called at some point before a player uses a spell in-game (which it does).

Interfaces are typically coded from scratch through the use of TriggerAddCondition and a global, thus cutting out the 2 functions in between.

Is there an example tutorial about this anywhere that you recommend? I'd like to learn more about this.

Your projectile movement loop is also bad as it would be faster as a linked list.

This I have to disagree with unless I see proof of a significant increase in performance. All of my array index allocations are O(1) to my knowledge and the iteration is O(n). Perhaps more detail into what makes it bad?

Just saying that those are blatant improvements

You do not need to belittle my efforts in creating a library that I wish to share with others. If they were blatant I would not have to ask about them. People learn at different paces and I would appreciate it if you stopped with the downers.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
->This I have to disagree with unless I see proof of a significant increase in performance. All of my array index allocations are O(1) to my knowledge and the iteration is O(n). Perhaps more detail into what makes it bad?

JASS math is much slower than an array read, which is why people always use linked lists when doing intense iterations. If you look at any Projectile lib, the iteration is done via linked list.

->Is there an example tutorial about this anywhere that you recommend? I'd like to learn more about this.

There is none ; P, but there are plenty of systems that do it in the background if you look around.

->I still don't get what you're talking about here. If I create a projectile using Projectile.create() and then let it expire or call .terminate(), then Projectile.destroy() is called, which causes onDestroy to run. I think this is built into vjass. no? Perhaps you are referring to the behavior that comes with the "struct extends array" that you mention. I read that onDestroy is pointless when using that.

The point is if you just use destroy, then onDestroy isn't called ; |, thus saving you a trigger evaluation >.>. Now, if it's called in regular structs regardless, I suggest moving to struct extends array. Many people working at optimal systems already use struct extends array in virtually everything anyways.

Don't claim that your Projectile system is super optimal in lightweight if you don't care to make these improvements is all I'm saying ; ). I still wouldn't use this, rofl ; P.

Even if you fix the things I listed out, that was just from browsing the code in a few seconds. I'm sure by actually reading through the code that I'd find many more ; ).

Furthermore, multiple submissions on one thread, unless those other submissions are very specific extensions, aren't accepted. You should split them up.

Also, here's an example of an interface that only has 1 extra function call instead of 2 and passes in the arguments directly.

JASS:
    module UnitIndexStruct
        static method operator [] takes unit u returns thistype
            return GetUnitUserData(u)
        endmethod
        method operator unit takes nothing returns unit
            return units[this]
        endmethod
        
        static if thistype.unitFilter.exists then
            static if thistype.unitIndex.exists then
                readonly boolean allocated
            else
                method operator allocated takes nothing returns boolean
                    return unitFilter(units[this])
                endmethod
            endif
        elseif thistype.unitIndex.exists then
            static if thistype.unitDeindex.exists then
                readonly boolean allocated
            else
                method operator allocated takes nothing returns boolean
                    return GetUnitUserData(units[this]) == this
                endmethod
            endif
        else
            method operator allocated takes nothing returns boolean
                return GetUnitUserData(units[this]) == this
            endmethod
        endif
        
        static if thistype.unitIndex.exists then
            private static method onIndexEvent takes nothing returns boolean
                static if thistype.unitFilter.exists then
                    if (unitFilter(GetIndexedUnit())) then
                        set thistype(GetIndexedUnitId()).allocated = true
                        call thistype(GetIndexedUnitId()).unitIndex()
                    endif
                else
                    static if thistype.unitDeindex.exists then
                        set thistype(GetIndexedUnitId()).allocated = true
                    endif
                    call thistype(GetIndexedUnitId()).unitIndex()
                endif
                return false
            endmethod
        endif
        
        static if thistype.unitDeindex.exists then
            private static method onDeindexEvent takes nothing returns boolean
                static if thistype.unitFilter.exists then
                    static if thistype.unitIndex.exists then
                        if (thistype(GetIndexedUnitId()).allocated) then
                            set thistype(GetIndexedUnitId()).allocated = false
                            call thistype(GetIndexedUnitId()).unitDeindex()
                        endif
                    else
                        if (unitFilter(GetIndexedUnit())) then
                            call thistype(GetIndexedUnitId()).unitDeindex()
                        endif
                    endif
                else
                    static if thistype.unitIndex.exists then
                        set thistype(GetIndexedUnitId()).allocated = false
                    endif
                    call thistype(GetIndexedUnitId()).unitDeindex()
                endif
                return false
            endmethod
        endif
        
        static if thistype.unitIndex.exists then
            static if thistype.unitDeindex.exists then
                private static method onInit takes nothing returns nothing
                    call CodeRegisterUnitIndexEvent(Condition(function thistype.onIndexEvent), UnitIndexer.INDEX)
                    call CodeRegisterUnitIndexEvent(Condition(function thistype.onDeindexEvent), UnitIndexer.DEINDEX)
                endmethod
            else
                private static method onInit takes nothing returns nothing
                    call CodeRegisterUnitIndexEvent(Condition(function thistype.onIndexEvent), UnitIndexer.INDEX)
                endmethod
            endif
        elseif thistype.unitDeindex.exists then
            private static method onInit takes nothing returns nothing
                call CodeRegisterUnitIndexEvent(Condition(function thistype.onDeindexEvent), UnitIndexer.DEINDEX)
            endmethod
        endif
    endmodule

Obviously, you can make it tighter by reducing it to 0 extra function calls. This interface is better than the standard vjass interface as it uses static ifs rather than defaults to minimize code and evaluations as well as 1 function call vs 2 ; P.

This is ripped from UnitIndexer.
 
Level 3
Joined
Dec 16, 2010
Messages
31
JASS math is much slower

Well i stopped right there. If adding 1 to a number is slow then I would consider it negligible compared to the math required to rotate the projectile about an arbitrary axis every timer update. Maybe I'm missing the point, but I doubt it.

if you just use destroy, then onDestroy isn't called

Oh. I didn't understand you could overwrite destroy in the struct. I guess it should have been obvious since I am already overwriting create. I interpreted your posts as if you were telling me that I would be calling onDestroy myself.

I will look into making struct arrays.

Also that UnitIndexer interface example makes sense although it is incredibly messy and less readable. I guess if the efficiency increase is that significant I will give it a shot.

Don't claim that your Projectile system is super optimal in lightweight if you don't care to make these improvements

I never claimed to be making a super optimal system. I did say I was going for lightweight. By this I meant that the code is very easy to read, implement and use. The code is also not that long compared to other projectile libs (I think?).

Also, to claim that I don't care to make improvements is ridiculous. I have asked for examples, explanations and tutorials. Why would I even respond to you otherwise. Please don't put words in my mouth. The only thing I am not going to do so far is the linked list approach because I feel the improvement would be negligible.

Furthermore, multiple submissions on one thread, unless those other submissions are very specific extensions, aren't accepted.

I can't find the written rule for this. I looked in the obvious places like the rules thread and the global rules. Thanks for the help so far though.


P.S.
;P
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Actually, with your projectile lib, you would likely have 0 trigger evaluations (straight out method call off of the event).

Also, with Projectiles especially, efficiency is key. You want to save on every single little operation so that you can increase your maximum projectile count. I think the max projectile count on a computer for one of my older ones was 650ish? Something like that. I think the average was 325ish for projectile systems?

Anyways, saving on every little operation really does make a difference. Put the work into making something perfect. You may say the difference is negligible, but iterating over 400 or 500 projectiles really shows that difference. If a system is even 20% faster, than 1200 instances vs 1000.
 
Level 3
Joined
Dec 16, 2010
Messages
31
I will be gone a few days, but afterward I will add a more "efficient" interface, module init, and better destroy. I still have to look into struct arrays and how they will affect my recycling.

I will be sticking with adding 1 to an integer as opposed to involving a new data structure to iterate through the projectiles.
 
Level 3
Joined
Dec 16, 2010
Messages
31
Please don't assume things about other people's knowledge. You're rather insulting in a casual way and irritating to converse with.

I am aware of how linked lists worked. I suppose I just didn't think of the unique integers that the projectiles can be converted to.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
->Please don't assume things about other people's knowledge. You're rather insulting in a casual way and irritating to converse with.

I'm not trying to be insulting or irritating ; P. Most JASSers, when creating linked lists for something, use the ids of that something rather than generating their own ;). IsUnitMoving is a prime example of that.

The main ones who start generating new ids for dif lists are the ones who don't really get linked lists, so pardon my assumption ; ).

Then you get into setting delegates to the parent for extension rather than generating an entire new struct.

Then you get into implementing modules into a struct rather than using an interface to avoid all trigger evaluations or cut down on extraneous trigger evaluations and 1 function call per method.

And etc etc ;o.

I apologize, I guess you are just more of a wc3c coder. You code very similarly to how they code at wc3c, and I employ very different techniques than wc3cers when it comes down to vJASS ; P.

Don't take that badly, people at wc3c are considered to be elite coders. I'm just not a fan of that method for coding in JASS and vJASS as it involves incurring major overheads =).

For unique id retrieval, rather than generating your own for the units, just use their indexed ids generated by a unit indexer. This will cut back on overhead as most any map has one of the three unit indexers in it. This goes for targets as well.

This way, you can simple do something like this
JASS:
local thistype this = GetUnitUserData(CreateUnit(...))
set thistype(0).previous.next = this
set previous = thistype(0).previous
set next = 0
set thistype(0).previous = this
set target = utargetId

You don't need a multiple linked list here. Now, if you wanted to do target bouncing, you could make utargetId a stacked field (moving from target to target until target == 0).

When using a unit indexer, it also allows you to pass all units around as integers (not having to null locals and cutting down on handle storage is always a nice thing).

You should also take into account unit and projectile collision. There is a GetUnitCollision script I wrote somewhere.

And yes, I see you do c# or Java other than vjass by your naming convention? : )

Also, in the case of projectiles, you want to minimize the operations done in your movement loop. Extra function calls and etc shouldn't be done.

call this.steer()

For example ; P. I know this is a pain and seems unsightly in every other language, but this is vjass, and what's more, this is for projectiles ;o. The projectile loop is major in a projectile system, I can't stress that enough. One less function call may allow your thing to support 5 more projectiles without going to such a low fps that it makes the map unplayable.

The average projectile count for most systems is between 200 and 350 I think (depending on 2D or 3D, though 3D doesn't incur too much xtra overhead, most of it comes from homing), so that's really not a lot. It's crucial for the projectile loop to squeeze out every ounce of performance. The one I once wrote for 2D could do around 600 with the usual features like homing and etc? Yes, it makes a difference.


There's also no reason not to make a JASS resource script squeeze out every last ounce of performance =). People irritate me when they say, no. It makes me feel like they are too lazy to care, and it also makes me not want to use their resources because of those slight blemishes I mentioned to them that they don't care enough about to fix ^_-. Negligible overhead is still overhead ; P.

Yea, I don't get this hardcore unless the code has to do with a game =).
 
Last edited:
Level 3
Joined
Dec 16, 2010
Messages
31
I worked on this a bit.

The heaviest processing time is, unsurprisingly, in the steering.
I think I might ditch the extra vectors I create and destroy constantly in there and replace them with reals. I'll keep the persistent position/forward/target vectors though.

Other things delaying me from working more on this a lot more.


@Nestharus:
You mentioned that struct arrays have less overhead than normal structs. What did you mean/examples?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,468
'Extends array' structs use more strict protocols than ones that don't. They don't have the invisible allocate/deallocate methods which keep things flexible in case you already have a way to get unique integers for each struct (ie. if your struct-integers are generated by UnitIndexer instead of by the allocate/deallocate).
 
Level 3
Joined
Dec 16, 2010
Messages
31
So is that it? I have to implement my own unique integers for each struct?
Is that all the overhead I am replacing?


also:
Interfaces are typically coded from scratch through the use of TriggerAddCondition and a global, thus cutting out the 2 functions in between.

Looking it over, I don't really get this, even with the UnitIndexer example. Wouldn't I have to call TriggerEval anyway. Why not just keep them methods and have one method call? Could I get a bit of explanation on this technique?

Edit: I am just using Bribe's DequeueStruct for linked-list-ish behavior. I removed some excess function calls and am able to get ~30 fps with 601 projectiles moving without collision or rotation. I am hoping to get 300 moving at at least 30 fps after new collision detection and rotation.
 
Last edited:
Level 19
Joined
Feb 4, 2009
Messages
1,313
recycling the dummies will make it a lot faster
all the stuff you did before is nice but does not really help a lot
and you can also recycle the dummies by attached effect type so it will be even faster because creating stuff is the main bottle neck right now I think
same with GetUnitZ
use a global location

http://www.wc3c.net/showthread.php?t=105830
reduces the size of your dummy.mdx and probably makes math easier
http://www.hiveworkshop.com/forums/triggers-scripts-269/xefx-z-angle-limitations-186321/
dummy
edit:
these functions and GetZ (again without a global loc) is in the core lib already

effects look very awesome btw :)
 
Last edited:
Level 3
Joined
Dec 16, 2010
Messages
31
recycling the dummies will make it a lot faster
because creating stuff is the main bottle neck right now I think

I think xefx auto recycles dummy units. It might be heavy handed but I am more concerned right now about the projectile update loop.

same with GetUnitZ
use a global location

Oh right.


Really? my dummy model is from xe. I even followed that guide once and the size of the model is still the same (~40kb). However I will take a look at that yaw/pitch/roll tutorial for comparison.

edit:
these functions and GetZ (again without a global loc) is in the core lib already

Oh really. I didn't realize that because it never showed up as purple or in the documentation.
 
Top