• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[Snippet] [Needs work] Projectile Utils

JASS:
//---------------------------------------------------------------------------
// Projectile Utilities by Bribe, for use with Berb's Projectiles Library.
//---------------------------------------------------------------------------
// 
// ProjectileUtils makes launching projectiles between units a user-friendly,
// automatic process. The main functions are listed under Users' API below.
// 
//---------------------------------------------------------------------------
// Thanks
//---------------------------------------------------------------------------
// 
// Berb for Custom Projectiles - the library that inspired me to learn vJass.
// &
// Vexorian for JassHelper and the dummy.mdx model.
// &
// Nestharus for inspiring ideas that add to the dynamics of this library.
// 
//---------------------------------------------------------------------------
// Requirements
//---------------------------------------------------------------------------
// 
// Projectile and ProjectileArt by Berb
//   - http://www.hiveworkshop.com/forums/showthread.php?t=162121
// 
// Table by Bribe
//   - http://www.hiveworkshop.com/forums/showthread.php?t=188084
// 
// xebasic by Vexorian
//   - http://www.wc3c.net/showthread.php?t=101150
// 
//---------------------------------------------------------------------------
// Dummy-unit settings to produce accurate and optimal projectile simulations
//---------------------------------------------------------------------------
// 
// Art - Model File
//   - Dummy.mdx
// 
// Art - Special (unit explodes)
//   - None
// 
// Animation - Blend Time
//   - 0.00
// 
// Pitch/Roll - Maximum Rotation
//   - 0.00
// 
// Abilities
//   - 'Aloc' (Locust)
// 
//---------------------------------------------------------------------------
// Users' API
//---------------------------------------------------------------------------
// 
// FireProjectile(unit, unit, real, real, real, boolean, string) -> projectile
// 
//   unit source,
//   :  The unit firing the projectile.
//
//   unit target,
//   :  The target unit of the projectile.
//
//   real damage,
//   :  The amount of damage the source deals to the target upon on-impact.
//
//   real speed,
//   :  Projectile's speed.
//
//   real arc,
//   :  Projectile's arc.
//
//   boolean homing,
//   :  The projectile will home in on the target unit.
//
//   string modelpath,
//   :  The file path of what you want the projectile to look like.
//
//   returns projectile,
//   :  A new projectile each time the function is called.
// 
//---------------------------------------------------------------------------
// 
// FireProjectileXY(unit, real, real, real, real, string) -> projectile
// 
//   unit source,
//   :  Same as FireProjectile.
//   
//   real x, y,
//   :  Coodinates to fire the missile towards.
//   
//   real speed, arc, string modelpath, returns projectile
//   :  Same as FireProjectile.
// 
//---------------------------------------------------------------------------
// 
// SetProjectileVisualsById(integer, real, integer, integer, integer, integer)
// 
//   integer unitid,
//   :   units of this id will have projectiles with these visual settings.
// 
//   real scale,
//   :   adjusts the projectile's visual size. 1.0 == 100% (default).
// 
//   integer alpha, red, green, blue,
//   :  adjusts projectiles' vertex colors. 255 is 100% (default). 0 alpha is
//      invisible.
// 
//---------------------------------------------------------------------------
// 
// SetProjectileImpactEvent(projectile, function)
// 
//   projectile proj,
//   :  This parameter can be easily filled using GetLastCreatedProjectile().
// 
//   function <name>,
//   :  The name of the function you want to call when the projectile impacts
//      with the target unit. The function should take a projectile instance
//      and return nothing.
//          '''
//          function exampleFunc takes projectile proj returns nothing
//              call UnitDamageTarget(proj.source, proj.target, GetProjectileDamage(proj), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
//              call BJDebugMsg(GetUnitName(proj.target) + " was hit by " + GetUnitName(proj.source) + " for " + I2S(R2I(GetProjectileDamage(proj))) + " damage!")
//          endfunction
//          '''
//      That example-function would be called like this:
//          '''
//          call SetProjectileImpactEvent(GetLastCreatedProjectile(), exampleFunc)
//          '''
//      WARNING: This function overrides the normal damaging method used by
//      the internal system. You can use this to your advantage if you want
//      an area-of-effect attack or if you want to have your own, customized
//      attack-type, damage type or the like.
// 
//---------------------------------------------------------------------------
// 
// GetLastCreatedProjectile() -> projectile
// 
//   returns projectile,
//   :  the last projectile-instance created by the FireProjectile function.
// 
//---------------------------------------------------------------------------
// 
// GetProjectileDamage(projectile) -> real
// 
//   projectile proj,
//   :  The projectile instance to retrieve damage from.
//   
//   returns real,
//   :  the damage specified by FireProjectile, else the damage set by...
// 
//---------------------------------------------------------------------------
// 
// SetProjectileDamage(projectile, real)
// 
//   projectile proj,
//   :  The projectile instance to be assigned the value of...
// 
//   real damage,
//   :  Can be set for a projectile instance of any type, retrievable via
//      GetProjectileDamage.
// 
//---------------------------------------------------------------------------
// 
// SetProjectileOffsetsById(integer, real, real, real, real)
// 
//   integer unitid,
//   :  units of this unit-type id will use these offsets for FireProjectile.
// 
//   real distance,
//   :  project an offset from the source unit's position to create & launch
//      the projectile; works like PolarProjectionBJ, offset by...
// 
//   real angle,
//   :  value taken in radians (must be between 0.00 and 2PI (~ 6.2831853)).
// 
//   real launchz,
//   :  directly offsets the z-coordinate of launch and is unaffected by the
//      given angle.
// 
//   real impactz,
//   :  projectiles will hit their targets from this height-offset.
// 
//---------------------------------------------------------------------------
library ProjectileUtils requires Projectile, ProjectileArt, Table, xebasic
    
    
globals
    //-----------------------------------------------------------------------
    // Internal-use
    // 
    
    // Configurable values
    private constant real       DEFAULT_LAUNCH_DISTANCE = 30.0   // Specify the default distance and angle to launch the projectile. These
    private constant real       DEFAULT_LAUNCH_ANGLE    = 0.00   // values offset the source x/y values and create the projectile unit there.
                                                                 // Angle must be in radians (between 0 and 2 * PI). Multiply by bj_DEGTORAD if
                                                                 // you are unsure how to calculate it.
    private constant real       DEFAULT_LAUNCH_HEIGHT   = 60.0   // Launch-height is not offset by launch-angle.
    private constant real       DEFAULT_IMPACT_HEIGHT   = 60.0   // When the projectile hits its target, it will be offset by this height.
    
    // Readability
    private constant real       PI2 = 6.2831853
    
    // Scalar variables
    private player      s_owner     = Player(15)
    private unit        s_unit      = null
    private integer     s_size      = 0
    private real        s_height    = 0.00
    private vector      s_launchVec
    private vector      s_impactVec
    private projectile  s_proj
    private Table       s_hash
    
    // Arrays
    private boolean array a_offsets
    private real    array a_distance
    private real    array a_angle
    private real    array a_launchz
    private real    array a_impactz
    
    private boolean array a_visuals
    private real    array a_scale
    private integer array a_alpha
    private integer array a_red
    private integer array a_green
    private integer array a_blue
endglobals
    
    
//===========================================================================
// A user-customizable on-impact function.
//   - Re-set via SetProjectileImpactEvent
// 
function interface ProjectileImpactFunc takes projectile p returns nothing
    
    
//===========================================================================
private module m
    private static method onInit takes nothing returns nothing
        set s_hash=Table.create()
        set s_launchVec=vector.create(0., 0., 0.)
        set s_impactVec=vector.create(0., 0., 0.)
    endmethod
endmodule
    
    
//===========================================================================
private struct object extends projectile
    
    
    // Instance members
    real damage=0.
    ProjectileImpactFunc func=0
    
    
    //=======================================================================
    // This transforms the struct requirement into a user-defined function.
    // 
    method onFinish takes nothing returns nothing
        if (this.func != 0) then
            call this.func.evaluate(this)
        endif
    endmethod
    
    
    //=======================================================================
    // The default damaging function used by FireProjectile.
    // 
    method damager takes nothing returns nothing
        if (this.damage != 0.) then
            call UnitDamageTarget(this.source, this.target, this.damage, false, false, null, DAMAGE_TYPE_UNIVERSAL, null)
        endif
    endmethod
    
    
    //=======================================================================
    // Data processing
    // 
    static method operator [] takes integer unitid returns integer
        local integer i=s_hash[unitid]
        if (i==0) then
            set i=s_size+1
            set s_size=i
            set s_hash[unitid]=i
        endif
        return i
    endmethod
    
    
    //=======================================================================
    // Projectile creator...  
    //   - Creates a new projectile instance from a new projectile unit
    //   - Attaches dummy-skin
    //   - Assigns target/impact vectors
    //   - Adjusts for customized offsets
    //   
    static method create takes unit source, real x2, real y2, string modelpath returns thistype
        local integer i=s_hash[GetUnitTypeId(source)]
        local real x=GetUnitX(source)
        local real y=GetUnitY(source)
        local real angle=Atan2(y2 - y, x2 - x)
        local real tempang=DEFAULT_LAUNCH_ANGLE
        local real launchz=DEFAULT_LAUNCH_HEIGHT + GetUnitFlyHeight(source)
        local real launchd=DEFAULT_LAUNCH_DISTANCE
        
        if a_offsets[i] then
            set tempang=a_angle[i]
            set launchz=a_launchz[i]
            set launchd=a_distance[i]
            set s_height=a_impactz[i]
        else
            set s_height=DEFAULT_IMPACT_HEIGHT
        endif
        
        if (tempang != 0.) then
            set angle = angle + tempang
            if (angle > PI2) then
                set angle = angle - PI2
            elseif (angle < 0.) then
                set angle = angle + PI2
            endif
        endif
        
        call s_impactVec.getTerrainPoint(x2, y2)
        call s_launchVec.getTerrainPoint(x + launchd * Cos(angle), y + launchd * Sin(angle))
        
        set s_launchVec.z=s_launchVec.z + launchz
        
        set s_unit=CreateUnit(s_owner, XE_DUMMY_UNITID, s_launchVec.x, s_launchVec.y, bj_RADTODEG * angle)
        
        set s_proj=thistype.allocate(s_unit)
        set s_proj.source=source
        
        if (a_visuals[i]) then
            call SetUnitScale(s_unit, a_scale[i], 0., 0.)
            call SetUnitVertexColor(s_unit, a_red[i], a_green[i], a_blue[i], a_alpha[i])
        endif
        call SetUnitExploded(s_unit, true)
        call s_proj.setModel(modelpath)
        
        return s_proj
    endmethod
    
    implement m
    
endstruct
    
    
    
//***************************************************************************
//* 
//* Users' API
//* 
//***************************************************************************
    
    
    //=======================================================================
    function GetLastCreatedProjectile takes nothing returns projectile
        return s_proj
        
    endfunction
        
        
    //=======================================================================
    function GetProjectileDamage takes object proj returns real
        return proj.damage
        
    endfunction
        
        
    //=======================================================================
    function SetProjectileDamage takes object proj, real damage returns nothing
        set proj.damage=damage
        
    endfunction
        
        
    //=======================================================================
    function SetProjectileImpactEvent takes object proj, ProjectileImpactFunc func returns nothing
        set proj.func=func
        
    endfunction
        
        
    //=======================================================================
    // Streamlined projectile-firing utility. Passes basic arguments to the
    // object.create method and then processes the additional arguments.
    // 
    function FireProjectile takes unit source, unit target, real damage, real speed, real arc, boolean homing, string modelpath returns projectile
        set object.create(source, GetUnitX(target), GetUnitY(target), modelpath).target=target
        set s_impactVec.z=s_impactVec.z + GetUnitFlyHeight(target) + s_height
        
        if (homing) and GetUnitAbilityLevel(target, 'Amov')!=0 then
            set s_proj.targetZOffset=s_height
        else
            set s_proj.activeTargetFollow=false
        endif
        
        call SetProjectileDamage(s_proj, damage)
        call SetProjectileImpactEvent(s_proj, object.damager)
        call s_proj.launch(s_launchVec, s_impactVec, speed, arc)
        
        return s_proj
    endfunction
        
        
    //=======================================================================
    // Targetless projectile-firing utility. Passes basic arguments to the
    // object.create method and then processes the additional arguments.
    // 
    function FireProjectileXY takes unit source, real x, real y, real speed, real arc, string modelpath returns projectile
        call object.create(source, x, y, modelpath).launch(s_launchVec, s_impactVec, speed, arc)
        
        return s_proj
    endfunction
        
        
    //=======================================================================
    function SetProjectileVisualsById takes integer unitid, real scale, integer alpha, integer red, integer green, integer blue returns nothing
        local integer i=object[unitid]
        
        set a_scale[i]=scale
        set a_alpha[i]=alpha
        set a_red[i]=red
        set a_green[i]=green
        set a_blue[i]=blue
        set a_visuals[i]=true
    endfunction
        
        
    //=======================================================================
    function SetProjectileOffsetsById takes integer unitid, real distance, real angle, real launchz, real impactz returns nothing
        local integer i=object[unitid]
        
        if (angle <= PI2) and (angle >= 0) then
            set a_angle[i]=angle
        endif
        
        set a_distance[i]=distance
        set a_launchz[i]=launchz
        set a_impactz[i]=impactz
        set a_offsets[i]=true
    endfunction
    
    
endlibrary


JASS:
library SplashingProjectile requires ProjectileUtils
    
    
    globals
        // Configurables
        private constant integer MED_DAMAGE_FACTOR = 60 // deals 60% of normal damage
        private constant integer MIN_DAMAGE_FACTOR = 20 // deals 20% of normal damage
        
        private constant integer MED_DAMAGE_POINT = 25 // med. damage applied for units past 25% of radius
        private constant integer MIN_DAMAGE_POINT = 50 // min. damage applied for units past 50% of radius
        
        // Data storage
        private unit s_source
        private unit s_dummy
        private real s_radius
        private real s_damage
        private real array a_radius
        private string array a_fx
    endglobals
    
    
    //=======================================================================
    // Configurable damage function.
    // 
    private function inflict takes real damage returns nothing
        call UnitDamageTarget(s_source, GetFilterUnit(), damage, false, false, null, null, null)
    endfunction
    
    
    //=======================================================================
    private function enum takes nothing returns boolean
        
        if GetWidgetLife(GetFilterUnit()) > 0.405 then
            if (IsUnitInRange(GetFilterUnit(), s_dummy, s_radius * MED_DAMAGE_POINT)) then
                call inflict(s_damage)
                
            elseif (IsUnitInRange(GetFilterUnit(), s_dummy, s_radius * MIN_DAMAGE_POINT)) then
                call inflict(s_damage * MED_DAMAGE_FACTOR)
                
            elseif (IsUnitInRange(GetFilterUnit(), s_dummy, s_radius)) then
                call inflict(s_damage * MIN_DAMAGE_FACTOR)
            endif
        endif
        
        return false
    endfunction
    
    
    //=======================================================================
    private function splash takes projectile proj returns nothing
        set s_source=proj.source
        set s_radius=a_radius[proj]
        set s_damage=GetProjectileDamage(proj)
        set s_dummy=proj.toUnit
        call DestroyEffect(AddSpecialEffect(a_fx[proj], proj.x, proj.y))
        call GroupEnumUnitsInRange(bj_lastCreatedGroup, proj.x, proj.y, s_radius + 90.0, Filter(function enum))
    endfunction
    
    
    //=======================================================================
    function SplashingProjectile takes projectile proj, real damage, real radius, string fx returns nothing
        set a_fx[proj]=fx
        set a_radius[proj]=radius
        call SetProjectileDamage(proj, damage)
        call SetProjectileImpactEvent(proj, splash)
    endfunction
    
    
endlibrary

Example use of SplashingProjectile:

JASS:
library Example initializer init requires SplashingProjectile, SpellEffectEvent
    
    // Example response to fire a missile
    private function OnCast takes nothing returns boolean
        
        // Fire it
        call FireProjectileXY(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY(), 600.0, 0.20, "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl")
        
        // Make it a splashing projectile
        call SplashingProjectile(GetLastCreatedProjectile(), 100.0, 150.0, "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl")
        
        return false
    endfunction
    
    // Initialize
    private function init takes nothing returns nothing
        call RegisterSpellEffectEvent(Condition(function OnCast), 'A000')
    endfunction
    
endlibrary
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
private constant integer DUMMY_ID = 'n000' // Unit model should be DUMMY.mdx from Vexorian's XE.

ProjectileArt has dummy stuff ... also if this is an add on for Berb's projectile library, why are you using vex's xe stuff over berb's? Doesn't make sense to me.

JASS:
        private real LaunchOffsetFront  = 30.0
        private real LaunchOffsetAngle  = 0.0  // Should be between 0.0 and 2 * PI (radians)
        private real LaunchOffsetHeight = 60.0
        
        private real ImpactOffsetRear   = 30.0
        private real ImpactOffsetAngle  = 0.0  // Should be between 0.0 and 2 * PI (radians)
        private real ImpactOffsetHeight = 60.0

Shouldn't these be specific to each unit/projectile?

JASS:
    private module Init
        static method onInit takes nothing returns nothing
            set LaunchVector = vector.create(0.0, 0.0, 0.0)
            if (LaunchOffsetAngle > MAX or LaunchOffsetAngle < 0.0) then
                set LaunchOffsetAngle = 0.0
            endif
            set ImpactVector = vector.create(0.0, 0.0, 0.0)
            if (ImpactOffsetAngle > MAX or ImpactOffsetAngle < 0.0) then
                set ImpactOffsetAngle = 0.0
            endif
        endmethod
    endmodule
    
    private struct xyz extends array
        implement Init
    endstruct

While I find the fact that you did an initializer this way rather dumb, I find the initialization code itself useless.


I think the wrappers in this are pretty spiffy in the way they simplify using Berb's projectile lib though : D.
 
> ProjectileArt has dummy stuff ... also if this is an add on for Berb's projectile library, why are you using vex's xe stuff over berb's? Doesn't make sense to me.

Berb's Projectile library is meant to be used with the XE dummy. Also, ProjectileArt does not have dummy stuff but a simple Special Effect attachment utility.

> Shouldn't these be specific to each unit/projectile?
By adding six new parameters to the LaunchProjectile function? If you want it to be different for each, use SetProjectileLaunchOffsets/SetProjectileImpactOffsets.

> While I find the fact that you did an initializer this way rather dumb, I find the initialization code itself useless.
Besides the obvious lazy attitude you have, you don't know the way Berb's projectile system works. You have to input two vectors into the 'launch' method, so this saves having to re-create the vectors per-launch.


> I think the wrappers in this are pretty spiffy in the way they simplify using Berb's projectile lib though : D.
Indeed.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
sigh....

JASS:
ProjectileUtils[unitTypeId].launchOffsetFront = 
ProjectileUtils[unitTypeId].launchOffsetAngle = 
ProjectileUtils[unitTypeId].launchOffsetHeigh =

and impacts should be based upon both the projectile type id and the unit type id... that is unless you think something like a massive Ogre should shoot and have the same impact radius as a sheep.


And if you didn't get my initialization comment, libraries have initializers and checking the initial values of your vars seems stupid >.>. If a user inputs something that's out of whack (you have the input limits in comments next to the vars), it's their own fault.
 
It should require xebasic if it is meant to be used with it.
--
ProjectileArt's functionality doesn't even come close to xefx's.

ProjectileArt doesn't require xebasic yet is almost useless without the dummy model from Vexorian's map. But I kind of eye where you are coming from and I will alter the API in the library to support optional xebasic and that cool unit-type id matching Nestharus suggested, sometime today or tomorrow. I am glad this community does not sleep.
 
Alright, the API has been updated with some great new features; many thanks to Nestharus for the cool idea to match offsets to unit-types. Each of the three functions has been updated with one additional parameter -

function FireProjectile now takes an additional damage parameter.

function SetProjectileLaunchOffsets/SetProjectileImpactOffsets take an integer (unit-type-id) as the first parameter.
 
FireProjectile now has another additional parameter - homing. If the missile should home in on its target or not is up to the user, though the proj.target reference will not return the correct unit if it is a non-homing missile. To make sure you always get the correct target unit, use set u = GetProjectileTarget(proj). It safely inlines.

SetProjectileDisplays is another new function that helps simulate an object-editor missile. This function allows you to control the size of the projectile as well as its alpha, red, green and blue visuals. Another implementation in-place thanks to Nestharus: projectiles with a larger scale will have a greater impact-offset, so larger projectiles need not traverse as much distance to hit their target.

Optional support for GetUnitCollisionSize has been added as well. If a target-unit has a large collision size, the projectile will impact further away than a target-unit with a smaller collision size.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
There's no need for you to use another unit handle for the "victim". If you don't want the projectile to "follow" it's target but you still want reference to the target-unit then you can do:

JASS:
set proj.activeTargetFollow = false
set proj.target = yourTargetUnit

This is the block of code that handles target homing:

JASS:
                if (dat.FLAG__active) and (dat.activeTargetFollow) then
                    if (dat.FLAG__followUnit) and (dat.target != null) 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 the activeTargetFollow member is set to false then none of this is executed, regardless of whether or not a target has been set.

I just found a potential problem with controlling the offset when the target vector is updated automatically - the Z-coordinate of the unit is used in addition to it's fly-height, however the height offset that may have been intended in the first place will no longer be in effect, thus the projectile will home in around the feet of the unit. What you'll want to do in order to help this problem is use setTargetPos and use the method operators targetX, targetY and targetZ+heightOffset.

JASS:
call yourProjectile.setTargetPos(yourProjectile.targetX, yourProjectile.targetY, yourProjectile.targetZ + heightOffset)

The height offset of the initial launch can be handled quite easily when you're giving values to the start/finish vectors.
 
>> If the activeTargetFollow member is set to false then none of this is executed, regardless of whether or not a target has been set.

Very good to know, thank you :)

>> What you'll want to do in order to help this problem is use setTargetPos

If Projectile had an onLoop method, this problem could easily be solved. But I've no way to hook the TimerStart call you do if someone creates a projectile without this system, so whatever timer I use is not reliable to be synced with yours and will cause bad motion.

Projectile could use another update. I can make the changes myself if you are short on time. It would just be a matter of a user being able to sync a TriggerRegisterTimerExpireEvent with the core timer.
 
OK, I have partially resolved the conflict. I prefer not to use proj.target for this system because I need activeTargetFollow on but I don't want it to lock in the target-unit, I want to do that myself so that it saves excessive function calls. The target should be referenced as GetProjectileTarget(proj).

Unless Berb thinks of another way for me to implement the same thing without slowing down the system.

I'd like to use proj.target as a reference, but to re-iterate, that would add a lot of function calls as I reference GetUnitFlyHeight, GetUnitX/Y and MoveLocation/GetLocationZ, eliminating everything the projectile library does automatically to update the target vector.
 
It does a bit more than just fly-height offset, I have it preserve its offset angle as well as its impact distance offset which factors in collision size;

JASS:
                set thistype.tempUnit = dat.victim
                set x = GetUnitX(thistype.tempUnit)
                set y = GetUnitY(thistype.tempUnit)
                set a = Atan2(dat.y - y, dat.x - x) + dat.offsetang
                if (a > MAX) then
                    set a = a - MAX
                elseif (a < 0.0) then
                    set a = a + MAX
                endif
                set x = x + dat.offset1 * Cos(a)
                set y = y + dat.offset1 * Sin(a)
                call MoveLocation(thistype.aloc, x, y)
                call dat.setTargetPos(x, y, GetLocationZ(thistype.aloc) + GetUnitFlyHeight(thistype.tempUnit) + dat.offset2)
 
OK, more changes have been implemented. I have stripped some existing features so that future systems will have a more readable API. It was not very easy to determine functions which take different parameters.

SetProjectileImpactEvent takes a projectile as its first parameter, yet it looks similar to SetProjectileOffsets, which, by looking at it, could also appear to take a projectile as its first parameter.

The new API makes a handy distinction;

SetProjectileVisualsById - takes a unit id as a first parameter
SetProjectileOffsetsById - takes a unit id...
SetProjectileImpactEvent - takes a projectile.

LastCreatedProjectile is no longer a variable, but I don't think it was that useful. Users can still reference that same thing via GetLastCreatedProjectile(). The variable now uses a shorter name internally for readability.

Impact offsets have been reduced to a single height-offset. WarCraft III projectiles do not factor in collision size, so to make things simple, more efficient and compatible, this library now only has the three launch-offsets and an impact-height offset.

The two functions SetProjectileImpactOffsets and SetProjectileLaunchOffsets have been consolidated into SetProjectileOffsetsById.

The 'Table' library is now a requirement. I could make this script look even uglier and make it optional with static-ifs, but it's a great library to have in your map anyway.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
That isn't really Projectile Utilities though, it would be pretty easy to sequentially launch a projectile once it executes the onFinish method and keep toDestroy as false until the last target.

Alright so you've convinced me to update Projectile again; this time I've removed the flag that disallows a projectile from "finishing" twice (not even sure why I implemented this in the first place) which will make it easier to do what you've described Anachron, being able to launch a projectile from within its onFinish callback response method in conjunction with deactivating the toDestroy member.

I'll update the submission thread shortly.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Yeah, having a projectile bounce would require a lot of parameters. Should there be an on-bounce event? How many bounces? The max range? Does the damage reduce per hit? What kind of unit qualifies the check? I could implement it, though. I like a challenge.

Another thing you could do that would be a much better option is a projectile factory >.>... when I see all of these parameters and all of the things you can do with projectiles I automatically think that the factory design pattern should be used, but that's just me.
 
From what I've gathered, a factory method pattern lets a person choose which struct to instanciate. I can't find a practical application for using a hub in this library just to select a child struct, when the user could have just selected their own specific child struct in the first place.

Unless I am getting you wrong.

EDIT: Adding yet another parameter to FireProjectile could accomplish this, but is that really what you want?
 
OK, added a new function, FireProjectile2. This has fewer parameters though it requires the user to do more things manually.

Four basic parameters - unit from, unit to, real speed, real arc. Work the same as FireProjectile. A new parameter, onCreateFunc func allows the user to put in the create method of a struct of his/her own choice, so whatever struct that is will be instanciated instead of the generic struct I have in the library. A user will inherit all of the methods from the projectile struct, but with the auto-vectors this library offers.

I can see how this would be useful, so thanks again, Nestharus :)
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
From what I've gathered, a factory method pattern lets a person choose which struct to instanciate. I can't find a practical application for using a hub in this library just to select a child struct, when the user could have just selected their own specific child struct in the first place.

The Factory Design Pattern implies you (or other programmers) are in some way going to be extending and interfacing an internal class - if the user is even capable of this then I have hints of this "Factory Design Pattern" in my code, and it is easier for the user to just extend the projectile struct as I outline in the first post of my submission thread.

The purpose of this is to supply the user with a predefined pattern of projectiles that simply handles certain internal work so that the user does not have to.

Bribe said:
JASS:
    function FireProjectile takes unit from, unit to, real damage, real speed, real arc, boolean homing, string art returns projectile
    
    - $from - The unit firing the projectile.
    
    - $to - The target unit of the projectile.
    
    - $damage - The amount of damage 'from' deals to 'to' upon the missile's impact.  A value of 0 will deal no
                damage; a value greater than 0 will deal damage; a value less than 0 rather heals the target unit.
    
    - $speed - Projectile's speed.
    
    - $arc - Projectile's arc.
    
    - $homing - The projectile will home in on the 'to' unit.
    
    - $art - The file path of what you want the projectile to look like.
    
    - returns a new projectile each time the function is called.

What Bribe is doing is taking the vJass projectile interface that I laid out in my engine and simplifying it to a more basic JASS syntax. Let's take a closer look at this FireProjectile function. It takes parameters that cover various areas of my projectile engine, from the projectile arc/speed which is required to launch a projectile in addition to a damage amount (this isn't even included in my engine) and the source/target of the fired projectile.

OK, added a new function, FireProjectile2. This has fewer parameters though it requires the user to do more things manually.

Four basic parameters - unit from, unit to, real speed, real arc . Work the same as FireProjectile. A new parameter, onCreateFunc func allows the user to put in the create method of a struct of his/her own choice, so whatever struct that is will be instanciated instead of the generic struct I have in the library. A user will inherit all of the methods from the projectile struct, but with the auto-vectors this library offers.

I can see how this would be useful, so thanks again, Nestharus :)

I don't really see how this has truly made things any easier.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I was thinking more in response to the chained projectile module (and other possible modules). Rather than having it all be separate modules, I was thinking an extensible factory design would be a better choice. Maybe not quite a factory design pattern but more of a sort of Composite Pattern (that's why factory came to mind, putting the different pieces together to form an object). Like rather than a chain, bounce, and etc projectile module, you could just have an ambiguous system of projectiles that can support all of that if you get what I mean and each projectile could be in some sort of State Pattern.
 
Last edited:
More new features;

JASS:
SetProjectileDamage(projectile p, real damage)
GetProjectileDamage(projectile p)

Since Projectile lacks a "damage" member, these variables will appropriately store an retrieve damage associated with a projectile instance.

SetProjectileImpactEvent now overrides the normal impact-event used by FireProjectile, which was what prompted me to include a GetProjectileDamage function. If this function is called by the user, the projectile will not deal damage the normal way (the user would have to handle that himself).
 
Added an extension library called SplashingProjectile which deals splash-damage on-impact.

A useful new function has been added to the fray called FireProjectileXY. This impacts once it reaches ground-level at the specified coordinates. The need for such a thing arose when I just wanted to have a splashing-projectile target a general area.

I have eliminated FireProjectile2 because I feel it strays from the direction this library should take.

I will soon include another add-on library (BouncingProjectile) to handle some more dirty work.
 
Level 7
Joined
Oct 11, 2008
Messages
304
static method create takes unit source, real x2, real y2, string modelpath returns projectile


returns projectile? :vw_wtf:

other thing...

constant native IsUnitInRange takes unit whichUnit, unit otherUnit, real distance returns boolean

JASS:
//
if (IsUnitInRange(g_target, g_x, g_y, g_radius * MED_DAMAGE_POINT)) then
                call inflict(g_damage)
                
            elseif (IsUnitInRange(g_target, g_x, g_y, g_radius * MIN_DAMAGE_POINT)) then
                call inflict(g_damage * MED_DAMAGE_FACTOR)
                
            elseif (IsUnitInRange(g_target, g_x, g_y, g_radius)) then
                call inflict(g_damage * MIN_DAMAGE_FACTOR)
            endif

>>

constant native IsUnitInRangeXY takes unit whichUnit, real x, real y, real distance returns boolean

JASS:
//
if (IsUnitInRangeXY(g_target, g_x, g_y, g_radius * MED_DAMAGE_POINT)) then
                call inflict(g_damage)
                
            elseif (IsUnitInRangeXY(g_target, g_x, g_y, g_radius * MIN_DAMAGE_POINT)) then
                call inflict(g_damage * MED_DAMAGE_FACTOR)
                
            elseif (IsUnitInRangeXY(g_target, g_x, g_y, g_radius)) then
                call inflict(g_damage * MIN_DAMAGE_FACTOR)
            endif

:vw_death:

JASS:
    function FireProjectile takes unit source, unit target, real damage, real speed, real arc, boolean homing, string modelpath returns projectile
        set object.create(source, GetUnitX(target), GetUnitY(target), modelpath).target=target
        set g_impactVec.z=g_impactVec.z + GetUnitFlyHeight(target) + height

JASS:
set g_proj.targetZOffset=height

height? where's it?
 
Level 7
Joined
Dec 3, 2006
Messages
339
Are you working on the bounce type projectile script anytime soon? Cause i tried to create one using this before and kind of failed(Something about not knowing how to launch another missile to figure out bounces).
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
The whole idea of bouncing the projectile is quite simple:

JASS:
struct bouncingprojectile extends projectile
    private integer bounces = 0
    
    method onFinish takes nothing returns nothing
        if (bounces > 0) then
            set bounces = bounces-1
            // find new target
            // designate target vector (start vector can use position, I believe)
            call launch(position, newEndVector, speed, arc)
            if (bounces == 0) then
                set toDestroy = true
            endif
        endif
    endmethod
    
    static method create takes unit u, integer bounces returns thistype
        local thistype p = allocate(u)
        set p.bounces = bounces
        if (bounces > 0) then
            set p.toDestroy = false
        endif
        return p
    endmethod
endstruct
 
I can't wait for this to get updated :p
Kenny's Projectile System is quite cumbersome, uses AIDS, has VERY BAD documentation, uses T32, and the rest are libraries that no one uses... (Linked List Module)
Plus, it uses a group handle when it could use a simple Table...
Also, there's tons of shitcode..
Oh and the API is as bad as AIDS' >_>
 
Level 6
Joined
Jun 20, 2011
Messages
249
I can't wait for this to get updated :p
Kenny's Projectile System is quite cumbersome, uses AIDS, has VERY BAD documentation, uses T32, and the rest are libraries that no one uses... (Linked List Module)
Plus, it uses a group handle when it could use a simple Table...
Also, there's tons of shitcode..
Oh and the API is as bad as AIDS' >_>

Looks like a proper, straight-to-approval resource on the helper.
 
Top