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

Master Projectile System V1.02b

[TD]Master Projectile System[/TD]
Tooltip

Icon
icons_15627_btn.jpg

Having been here so long I decided it was high time I tried my hand at this whole projectile engine game
So in my over-the-top and far-too-mathematical way I give you the Master Projectile System!

Now, this isn't Master in the sense of the best, but rather in the sense that projectiles have Slaves which
orbit around them (optionally) in addition, I've used strict vector based trajectories so this isn't going
to replace some or possibly any of the older projectile systems since I'm going out in my own direction
to bring some unique and eye-candy projectiles

Code


JASS:
////////////////////////////////////////////////////////////////////
//               MASTER PROJECTILE SYSTEM V1.02b                  //
//  Author: Tank-Commander                                        //
//  Requires: Dummy.mdl                                           //
//  Purpose: Create complex and attractive projectiles            //
//                                                                //
//  Notes:                                                        //
//    -  Read the readme before you try modifying the config      //
//    -  Use the "Helpful files" to help you import the spell     //
//                                                                //
//  Credits:                                                      //
//    -  (Dummy.mdl) Vexorian                                     //
//                                                                //
//  If you have used this system in your map, you are required    //
//  to give credits to Tank-Commander for the creation of it      //
//  If you would like to use snippets of code from this for       //
//  whatever, getting permission and crediting the source/linking //
//  would be much appreciated.                                    //
////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////
//  README:                                                       //
//    Before modifying this system a few things need to be        //
//    understood and read, this is one of those things, while     //
//    most modification can be considered intuitive, it still     //
//    helps to read through these instructions, as they will      //
//    inform you about how to configure this system to your       //
//    desire.                                                     //
//----------------------------------------------------------------//
constant function MPS_ProjectileTimerSpeed takes nothing returns real
    return 0.031250000
endfunction
//----------------------------------------------------------------//
//  DummyID: This is the data value of the unit that serves as    //
//  the dummy, it should have Dummy.mdl set to its model have     //
//  locust as its ability, movement type float (or fly) and 0     //
//  pitch and roll angle for optimal use                          //
constant function MPS_ProjectileDummyID takes nothing returns integer
    return 'u000'
endfunction
//----------------------------------------------------------------//
//  AttachmentPoint: This is the location on the Dummy unit that  //
//  Master and Slave projectiles will have their effects placed   //
constant function MPS_ProjectileAttachmentPoint takes nothing returns string
    return "origin"
endfunction
//----------------------------------------------------------------//
//  DummyPlayer: This is the player who will own all dummy units  //
//  created by this ability, by default this is Player(14)        //
constant function MPS_DummyPlayer takes nothing returns player
    return Player(14)
endfunction
//----------------------------------------------------------------//
//  HeightLet: This is how far off the ground a projectile can be //
//  while still being considered to be in the ground (can prevent //
//  odd changes in arc when hitting the ground)                   //
constant function MPS_HeightLet takes nothing returns real
    return 3.00
endfunction
//----------------------------------------------------------------//
//  Gravity: This is how fast projectiles are pulled back into    //
//  the ground                                                    //
constant function MPS_Gravity takes nothing returns real
    return 0.3065
endfunction
//----------------------------------------------------------------//
// -READ ONLY-READ ONLY-READ ONLY-READ ONLY-READ ONLY-READONLY-   //
constant function MPS_ProjectileStageID takes nothing returns integer
    return 1
endfunction
//----------------------------------------------------------------//
constant function MPS_ProjectileRecycleStageID takes nothing returns integer
    return 2
endfunction
//----------------------------------------------------------------//
//                      END OF CONFIGURATION                      //
//----------------------------------------------------------------//
////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////
//  Function used for registering new ammo types into the system  //
//  call this function after setting up a new ammo type will      //
//  allow it to be used as an ammo type for spells/turrets/etc.   //
////////////////////////////////////////////////////////////////////
function MPS_RegisterAmmo takes integer RegisterNumber returns nothing
    local integer Id
    
    if (RegisterNumber == 0) then
        set udg_MPS_AmmoNumber = ( udg_MPS_AmmoNumber + 1 )
        set Id = udg_MPS_AmmoNumber
    else
        set Id = RegisterNumber
    endif
    
    set udg_MPS_ProjectileName[Id] = StringCase(udg_MPS_ProjectileName[0], true)
    set udg_MPS_ProjectileModel[Id] = udg_MPS_ProjectileModel[0]
    set udg_MPS_ProjectileDeathModel[Id] = udg_MPS_ProjectileDeathModel[0]
    set udg_MPS_ProjectileModelScale[Id] = udg_MPS_ProjectileModelScale[0]
    set udg_MPS_ProjectileAOE[Id] = udg_MPS_ProjectileAOE[0]
    set udg_MPS_ProjectileHealthDamage[Id] = udg_MPS_ProjectileHealthDamage[0]
    set udg_MPS_ProjectileManaDamage[Id] = udg_MPS_ProjectileManaDamage[0]
    set udg_MPS_ProjectileAttackType[Id] = udg_MPS_ProjectileAttackType[0]
    set udg_MPS_ProjectileDamageType[Id] = udg_MPS_ProjectileDamageType[0]
    set udg_MPS_ProjectileAcceleration[Id] = udg_MPS_ProjectileAcceleration[0]
    set udg_MPS_ProjectileTurnRate[Id] = udg_MPS_ProjectileTurnRate[0]
    set udg_MPS_ProjectileTurnEfficiency[Id] = udg_MPS_ProjectileTurnEfficiency[0]
    set udg_MPS_ProjectileTargetAimOffset[Id] = udg_MPS_ProjectileTargetAimOffset[0]
endfunction

////////////////////////////////////////////////////////////////////
//  This function returns the Id number of the ammunition         //
//  which shares the name with the passed parameter, it can be    //
//  used to consistently refer to ammunition without knowing the  //
//  order by which it was declared (allowing dynamic introduction //
//  of ammunition types                                           //
////////////////////////////////////////////////////////////////////
function MPS_GetAmmoByName takes string name returns integer
    local integer iLoop = 0
   
    set name = StringCase(name, true)
   
    loop
        set iLoop = iLoop + 1
        exitwhen iLoop > udg_MPS_AmmoNumber
       
        if (name == udg_MPS_ProjectileName[iLoop]) then
            return iLoop
        endif
       
    endloop
   
    return 0
endfunction

////////////////////////////////////////////////////////////////////
//  Function used for registering new slave types into the system //
//  call this function after setting up a new slave type will     //
//  allow it to be used as an slave type for spells/turrets/etc.  //
////////////////////////////////////////////////////////////////////
function MPS_RegisterSlave takes integer RegisterNumber returns nothing
    local integer Id
    
    if (RegisterNumber == 0) then
        set udg_MPS_SlaveNumber = ( udg_MPS_SlaveNumber + 1 )
        set Id = udg_MPS_SlaveNumber
    else
        set Id = RegisterNumber
    endif
    
    set udg_MPS_SlaveName[Id] = StringCase(udg_MPS_SlaveName[0], true)
    set udg_MPS_SlaveModel[Id] = udg_MPS_SlaveModel[0]
    set udg_MPS_SlaveDeathModel[Id] = udg_MPS_SlaveDeathModel[0]
    set udg_MPS_SlaveModelScale[Id] = udg_MPS_SlaveModelScale[0]
    set udg_MPS_SlaveAOE[Id] = udg_MPS_SlaveAOE[0]
    set udg_MPS_SlaveHealthDamage[Id] = udg_MPS_SlaveHealthDamage[0]
    set udg_MPS_SlaveManaDamage[Id] = udg_MPS_SlaveManaDamage[0]
    set udg_MPS_SlaveAttackType[Id] = udg_MPS_SlaveAttackType[0]
    set udg_MPS_SlaveDamageType[Id] = udg_MPS_SlaveDamageType[0]
    set udg_MPS_SlaveOffset[Id] = udg_MPS_SlaveOffset[0]
    set udg_MPS_SlavePull[Id] = udg_MPS_SlavePull[0]
    set udg_MPS_SlaveSpin[Id] = udg_MPS_SlaveSpin[0]
endfunction

////////////////////////////////////////////////////////////////////
//  This function returns the Id number of the slave              //
//  which shares the name with the passed parameter, it can be    //
//  used to consistently refer to a slave without knowing the     //
//  order by which it was declared (allowing dynamic introduction //
//  of slave types                                                //
////////////////////////////////////////////////////////////////////
function MPS_GetSlaveByName takes string name returns integer
    local integer iLoop = 0
   
    set name = StringCase(name, true)
   
    loop
        set iLoop = iLoop + 1
        exitwhen iLoop > udg_MPS_SlaveNumber
       
        if (name == udg_MPS_SlaveName[iLoop]) then
            return iLoop
        endif
       
    endloop
   
    return iLoop
endfunction
////////////////////////////////////////////////////////////////////
//  Function used to get the z height of locations needed by the  //
//  spell, since it can only be done with locations, this one     //
//  is reused throughout, instead of creating/destroying them     //
////////////////////////////////////////////////////////////////////
function MPS_GetZ takes real x, real y returns real

    //Gets the location Z of the selected location
    call MoveLocation(udg_MPS_Loc, x, y)
    return GetLocationZ(udg_MPS_Loc)
   
endfunction

////////////////////////////////////////////////////////////////////
//  Function used to make sure that the location is within the    //
//  map bounds so that units cannot be moved outside of it and    //
//  get permanently stuck                                         //
////////////////////////////////////////////////////////////////////
function MPS_ValidateLocation takes real x, real y returns boolean
   
    //Check if the point is within the map bounds
    return (x < udg_MPS_MapMaxX) and (x > udg_MPS_MapMinX) and (y < udg_MPS_MapMaxY) and (y > udg_MPS_MapMinY)

endfunction

////////////////////////////////////////////////////////////////////
//  Function used to identify what units are valid damaging       //
//  targets for a given projectile                                //
////////////////////////////////////////////////////////////////////
function MPS_ValidateTarget takes unit u, integer Node returns boolean

    //Checks if the passed unit can be used as a valid target for damage
    return ((IsUnitEnemy(u, udg_MPS_ProjectilePlayer[Node])) and (GetUnitTypeId(u) != MPS_ProjectileDummyID()) and not(IsUnitType(u, UNIT_TYPE_DEAD) or GetUnitTypeId(u) == 0))
    
endfunction

////////////////////////////////////////////////////////////////////
//  Function used to update event listeners                       //
////////////////////////////////////////////////////////////////////
function MPS_FireEvent takes real EventID returns nothing
    set udg_MPS_ACTIVE_EVENT = EventID
    set udg_MPS_ACTIVE_EVENT = udg_MPS_NO_EVENT
endfunction

////////////////////////////////////////////////////////////////////
//  Function used to recycle instances, so that they can used     //
//  again later, keeping the total array sizes smaller            //
////////////////////////////////////////////////////////////////////
function MPS_ProjectileRecycle takes integer Node returns nothing
           
        //Recycles the node
        set udg_MPS_ProjectileRecycleNodes[udg_MPS_ProjectileRecyclableNodes] = Node
        set udg_MPS_ProjectileRecyclableNodes = udg_MPS_ProjectileRecyclableNodes + 1
        set udg_MPS_ProjectileNextNode[udg_MPS_ProjectilePrevNode[Node]] = udg_MPS_ProjectileNextNode[Node]
        set udg_MPS_ProjectilePrevNode[udg_MPS_ProjectileNextNode[Node]] = udg_MPS_ProjectilePrevNode[Node]

        //Stops the timer if this is the only remaining Node
        if (udg_MPS_ProjectilePrevNode[0] == 0) then
            call PauseTimer(udg_MPS_ProjectileTimer)
        endif

endfunction

////////////////////////////////////////////////////////////////////
//  Function used to create new Nodes for the system whenever a   //
//  unit or effect is added to run in the loop function           //
////////////////////////////////////////////////////////////////////
function MPS_ProjectileCreateNode takes nothing returns integer
    //set up local
    local integer Node = 0
   
    //Checking for recycleable Nodes
    if (udg_MPS_ProjectileRecyclableNodes == 0) then
        set udg_MPS_ProjectileNodeNumber = udg_MPS_ProjectileNodeNumber + 1
        set Node = udg_MPS_ProjectileNodeNumber
    else
        set udg_MPS_ProjectileRecyclableNodes = udg_MPS_ProjectileRecyclableNodes - 1
        set Node = udg_MPS_ProjectileRecycleNodes[udg_MPS_ProjectileRecyclableNodes]
    endif

    //Sets up this Node
    set udg_MPS_ProjectileNextNode[Node] = 0
    set udg_MPS_ProjectileNextNode[udg_MPS_ProjectilePrevNode[0]] = Node
    set udg_MPS_ProjectilePrevNode[Node] = udg_MPS_ProjectilePrevNode[0]
    set udg_MPS_ProjectilePrevNode[0] = Node
   
    return Node
endfunction

///////////////////////////////////////////////////////////////////
//  Function used to recycle instances, so that they can used     //
//  again later, keeping the total array sizes smaller            //
////////////////////////////////////////////////////////////////////
function MPS_SlaveRecycle takes integer Node returns nothing
           
        //Recycles the node
        set udg_MPS_SlaveRecycleNodes[udg_MPS_SlaveRecyclableNodes] = Node
        set udg_MPS_SlaveRecyclableNodes = udg_MPS_SlaveRecyclableNodes + 1
        set udg_MPS_SlaveNextNode[udg_MPS_SlavePrevNode[Node]] = udg_MPS_SlaveNextNode[Node]
        set udg_MPS_SlavePrevNode[udg_MPS_SlaveNextNode[Node]] = udg_MPS_SlavePrevNode[Node]

endfunction

////////////////////////////////////////////////////////////////////
//  Function used to create new Nodes for the system whenever a   //
//  unit or effect is added to run in the loop function           //
////////////////////////////////////////////////////////////////////
function MPS_SlaveCreateNode takes nothing returns integer
    //set up local
    local integer Node = 0
   
    //Checking for recycleable Nodes
    if (udg_MPS_SlaveRecyclableNodes == 0) then
        set udg_MPS_SlaveNodeNumber = udg_MPS_SlaveNodeNumber + 1
        set Node = udg_MPS_SlaveNodeNumber
    else
        set udg_MPS_SlaveRecyclableNodes = udg_MPS_SlaveRecyclableNodes - 1
        set Node = udg_MPS_SlaveRecycleNodes[udg_MPS_SlaveRecyclableNodes]
    endif

    //Sets up this Node
    set udg_MPS_SlaveNextNode[Node] = 0
    set udg_MPS_SlaveNextNode[udg_MPS_SlavePrevNode[0]] = Node
    set udg_MPS_SlavePrevNode[Node] = udg_MPS_SlavePrevNode[0]
    set udg_MPS_SlavePrevNode[0] = Node
   
    return Node
endfunction

////////////////////////////////////////////////////////////////////
//  Function used to move all projectile-type entities in the     //
//  abiity - the physics engine of the spell                      //
////////////////////////////////////////////////////////////////////
function MPS_Move takes integer Node returns boolean
    //set up locals
    local real dy = udg_MPS_TargetY[Node] - udg_MPS_ProjectileY[Node]
    local real dx = udg_MPS_TargetX[Node]- udg_MPS_ProjectileX[Node]
    local real x
    local real y
    local real Angle = Atan2(dy, dx)
    local real Angle2 = Atan2((MPS_GetZ(udg_MPS_TargetX[Node], udg_MPS_TargetY[Node]) + udg_MPS_TargetZ[Node]) - (MPS_GetZ(udg_MPS_ProjectileX[Node], udg_MPS_ProjectileY[Node]) + GetUnitFlyHeight(udg_MPS_Projectile[Node])), Pow(dx * dx + dy * dy, 0.5))
    local real Angle3 = Atan2(udg_MPS_YVel[Node], udg_MPS_XVel[Node])
    local real Angle4 = Atan(udg_MPS_ZVel[Node])
    local real TempReal = Pow((udg_MPS_ZVel[Node] * udg_MPS_ZVel[Node]) + (udg_MPS_XVel[Node] * udg_MPS_XVel[Node]) + (udg_MPS_YVel[Node] * udg_MPS_YVel[Node]), 0.5) * udg_MPS_ProjectileTurnEfficiency[udg_MPS_AmmoType[Node]]
    local real TempReal2 = 1/(1 + udg_MPS_ProjectileTurnEfficiency[udg_MPS_AmmoType[Node]]) 
    
    //Calculate new velocities
    set udg_MPS_ZVel[Node] = ((udg_MPS_ZVel[Node] + (TempReal + udg_MPS_ProjectileTurnRate[udg_MPS_AmmoType[Node]]) * Sin(Angle2)) * udg_MPS_ProjectileAcceleration[udg_MPS_AmmoType[Node]]) * TempReal2
    set udg_MPS_XVel[Node] = ((udg_MPS_XVel[Node] + (TempReal + udg_MPS_ProjectileTurnRate[udg_MPS_AmmoType[Node]]) * Cos(Angle) * Cos(Angle2)) * udg_MPS_ProjectileAcceleration[udg_MPS_AmmoType[Node]]) * TempReal2
    set udg_MPS_YVel[Node] = ((udg_MPS_YVel[Node] + (TempReal + udg_MPS_ProjectileTurnRate[udg_MPS_AmmoType[Node]]) * Sin(Angle) * Cos(Angle2)) * udg_MPS_ProjectileAcceleration[udg_MPS_AmmoType[Node]]) * TempReal2
    set udg_MPS_ProjectileHeight[Node] = udg_MPS_ProjectileHeight[Node] + udg_MPS_ZVel[Node] - MPS_Gravity()
    
    set x = udg_MPS_ProjectileX[Node] + udg_MPS_XVel[Node]
    set y = udg_MPS_ProjectileY[Node] + udg_MPS_YVel[Node]
    
    //Make sure the location is within the map bounds
    if MPS_ValidateLocation(x, y) then
        set udg_MPS_ProjectileX[Node] = x
        set udg_MPS_ProjectileY[Node] = y
        call SetUnitX(udg_MPS_Projectile[Node], x)
        call SetUnitY(udg_MPS_Projectile[Node], y)
    endif
    
    //Update target unit information if applicable
    if not(udg_MPS_ProjectileTargetUnit[Node] == null) then
        set udg_MPS_TargetX[Node] = GetUnitX(udg_MPS_ProjectileTargetUnit[Node])
        set udg_MPS_TargetY[Node] = GetUnitY(udg_MPS_ProjectileTargetUnit[Node])
        set udg_MPS_TargetZ[Node] = GetUnitFlyHeight(udg_MPS_ProjectileTargetUnit[Node]) + udg_MPS_ProjectileTargetAimOffset[udg_MPS_AmmoType[Node]]
    endif
    
    //Apply visuals
    call SetUnitFlyHeight(udg_MPS_Projectile[Node], udg_MPS_ProjectileHeight[Node] - MPS_GetZ(x, y), 0.)
    call SetUnitFacing(udg_MPS_Projectile[Node], bj_RADTODEG * Atan2(udg_MPS_YVel[Node], udg_MPS_XVel[Node]))
    set udg_MPS_ProjectileAnimation[Node] = R2I(Atan2((udg_MPS_ZVel[Node]), Pow((udg_MPS_XVel[Node] * udg_MPS_XVel[Node]) + (udg_MPS_YVel[Node] * udg_MPS_YVel[Node]), 0.5) * bj_RADTODEG + 0.5)) + 70
    call SetUnitAnimationByIndex(udg_MPS_Projectile[Node], udg_MPS_ProjectileAnimation[Node])
    
    //Check if the unit has crashed into the ground
    if (GetUnitFlyHeight(udg_MPS_Projectile[Node]) <= MPS_HeightLet()) then
        call MPS_FireEvent(udg_MPS_MASTER_CRASH)
        return true
    endif
    
    call MPS_FireEvent(udg_MPS_MASTER_UPDATE)
    return false
endfunction

////////////////////////////////////////////////////////////////////
//  Function used to identify when a projectile has hit its       //
//  target unit so that damage can be dealt & stun can be         //
//  applied if applicable                                         //
////////////////////////////////////////////////////////////////////
function MPS_HitTarget takes integer Node returns boolean
    //Set up locals
    local real dx = udg_MPS_ProjectileX[Node] - udg_MPS_TargetX[Node]
    local real dy = udg_MPS_ProjectileY[Node] - udg_MPS_TargetY[Node]
    local real dz = (MPS_GetZ(udg_MPS_ProjectileX[Node], udg_MPS_ProjectileY[Node]) + GetUnitFlyHeight(udg_MPS_Projectile[Node])) - (MPS_GetZ(udg_MPS_TargetX[Node], udg_MPS_TargetY[Node]) + GetUnitFlyHeight(udg_MPS_ProjectileTargetUnit[Node]))
    
    //Measure distance between the Unit and its Target and return if it's close enough
    if (dx * dx + dy * dy + dz * dz <= udg_MPS_ProjectileAOE[udg_MPS_AmmoType[Node]] * udg_MPS_ProjectileAOE[udg_MPS_AmmoType[Node]]) then
        call MPS_FireEvent(udg_MPS_MASTER_HIT)
        
        return true
    endif
    
    return false
endfunction

////////////////////////////////////////////////////////////////////
//  Function used to damage all valid units within the impact     //
//  area of the projectile once it has crashed or hit the target  //
////////////////////////////////////////////////////////////////////
function MPS_ProjectileDamageArea takes integer Node returns nothing
    //Set up locals
    local unit u
    local real x
    local real y
    local real z = GetUnitFlyHeight(udg_MPS_Projectile[Node])
    local real dx
    local real dy
    local real dz
    
    //Get units in range
    call GroupEnumUnitsInRange(udg_MPS_TempGroup, udg_MPS_ProjectileX[Node], udg_MPS_ProjectileY[Node], udg_MPS_ProjectileAOE[udg_MPS_AmmoType[Node]], null)
    
    loop
        set u = FirstOfGroup(udg_MPS_TempGroup)
        exitwhen (u == null)
        set x = GetUnitX(u)
        set y = GetUnitY(u)
        set dx = udg_MPS_ProjectileX[Node] - x
        set dy = udg_MPS_ProjectileY[Node] - y
        set dz = (MPS_GetZ(udg_MPS_ProjectileX[Node], udg_MPS_ProjectileY[Node]) + z) - (MPS_GetZ(x, y) + GetUnitFlyHeight(u))
        
        //Check if they are close enough and are valid as a damage target
        if (dx * dx + dy * dy + dz * dz <= udg_MPS_ProjectileAOE[udg_MPS_AmmoType[Node]] * udg_MPS_ProjectileAOE[udg_MPS_AmmoType[Node]]) and (MPS_ValidateTarget(u, Node)) then
            //Apply Damage
            call UnitDamageTarget(udg_MPS_ProjectileLauncher[Node], u, udg_MPS_ProjectileHealthDamage[udg_MPS_AmmoType[Node]], false, false, udg_MPS_ProjectileAttackType[udg_MPS_AmmoType[Node]], udg_MPS_ProjectileDamageType[udg_MPS_AmmoType[Node]], null)
            call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MANA) - udg_MPS_ProjectileManaDamage[udg_MPS_AmmoType[Node]])
        endif
        
        call GroupRemoveUnit(udg_MPS_TempGroup, u)
    endloop
    
endfunction

////////////////////////////////////////////////////////////////////
//  Function used to keep all the Slave projectiles attached      //
//  to their masters, runs once at the end of the loop to keep    //
//  system efficiency up                                          //
////////////////////////////////////////////////////////////////////
function MPS_UpdateSlaveProjectiles takes nothing returns nothing
    //Set up locals
    local integer TempInt = 0
    local integer Node = 0
    local real x
    local real y
    local real z
    local real x2
    local real y2
    local real Incline
    local real facing
    local real correction
    local real dx
    local real dy
    local real dz
    local unit u
    
    loop
        set Node = udg_MPS_SlaveNextNode[Node]
        exitwhen Node == 0
        set udg_MPS_ACTIVE_NODE = Node
        
        //Find the incline of the Orbit and position on the Orbit
        set Incline = Atan2(udg_MPS_ZVel[udg_MPS_MasterNode[Node]] * udg_MPS_ZVel[udg_MPS_MasterNode[Node]], udg_MPS_XVel[udg_MPS_MasterNode[Node]] * udg_MPS_XVel[udg_MPS_MasterNode[Node]] + udg_MPS_YVel[udg_MPS_MasterNode[Node]] * udg_MPS_YVel[udg_MPS_MasterNode[Node]]) + 1.570800
        set udg_MPS_SlaveAngle[Node] = udg_MPS_SlaveAngle[Node] + udg_MPS_SlaveSpin[udg_MPS_SlaveType[Node]] 
        set facing = GetUnitFacing(udg_MPS_Projectile[udg_MPS_MasterNode[Node]]) * bj_DEGTORAD
        
        //Apply Pull in correct direction
        if (udg_MPS_SlaveCurrentOffset[Node] > 0.) then
            set udg_MPS_SlaveVel[Node] = udg_MPS_SlaveVel[Node] - udg_MPS_SlavePull[udg_MPS_SlaveType[Node]]
            set correction = udg_MPS_SlaveAngle[Node]
        else
            set udg_MPS_SlaveVel[Node] = udg_MPS_SlaveVel[Node] + udg_MPS_SlavePull[udg_MPS_SlaveType[Node]]
            set correction = udg_MPS_SlaveAngle[Node] * - 1
        endif
        
        //Find new location
        set udg_MPS_SlaveCurrentOffset[Node] = udg_MPS_SlaveCurrentOffset[Node] + udg_MPS_SlaveVel[Node]
        set x = udg_MPS_ProjectileX[udg_MPS_MasterNode[Node]] + (udg_MPS_SlaveCurrentOffset[Node] * Cos(correction))
        set y = udg_MPS_ProjectileY[udg_MPS_MasterNode[Node]] + (udg_MPS_SlaveCurrentOffset[Node] * Sin(correction))
        set z = udg_MPS_ProjectileHeight[udg_MPS_MasterNode[Node]] + (udg_MPS_SlaveCurrentOffset[Node] * Sin(Incline) * Cos(facing) * Cos(correction)) + (MPS_GetZ(x, y) - MPS_GetZ(udg_MPS_ProjectileX[udg_MPS_MasterNode[Node]], udg_MPS_ProjectileY[udg_MPS_MasterNode[Node]])) - MPS_GetZ(x, y)
        
        //Apply new location
        call SetUnitX(udg_MPS_Slave[Node], x)
        call SetUnitY(udg_MPS_Slave[Node], y)
        call SetUnitFlyHeight(udg_MPS_Slave[Node], z, 0.)
        call SetUnitAnimationByIndex(udg_MPS_Slave[Node], udg_MPS_ProjectileAnimation[udg_MPS_MasterNode[Node]])
        call SetUnitFacing(udg_MPS_Slave[Node], GetUnitFacing(udg_MPS_Projectile[udg_MPS_MasterNode[Node]]))
        
        if (udg_MPS_ProjectileStageID[udg_MPS_MasterNode[Node]] == MPS_ProjectileRecycleStageID() or z <= MPS_HeightLet()) then
            //Slave killed
            call MPS_FireEvent(udg_MPS_SLAVE_DEATH)
            //Destroy effects on crash or Master death
            call DestroyEffect(udg_MPS_SlaveCurrentEffect[Node])
            call DestroyEffect(AddSpecialEffectTarget(udg_MPS_SlaveDeathModel[udg_MPS_SlaveType[Node]], udg_MPS_Slave[Node], MPS_ProjectileAttachmentPoint()))
            //Select units to damage
            call GroupEnumUnitsInRange(udg_MPS_TempGroup, x, y, udg_MPS_SlaveAOE[udg_MPS_SlaveType[Node]], null)

            loop
                set u = FirstOfGroup(udg_MPS_TempGroup)
                exitwhen (u == null)
                set x2 = GetUnitX(u)
                set y2 = GetUnitY(u)
                set dx = x - x2
                set dy = y - y2
                set dz = (MPS_GetZ(x, y) + z) - (MPS_GetZ(x2, y2) + GetUnitFlyHeight(u))
                
                //Check they are within range and are a valid target
                if (dx * dx + dy * dy + dz * dz <= udg_MPS_SlaveAOE[udg_MPS_SlaveType[Node]] * udg_MPS_SlaveAOE[udg_MPS_SlaveType[Node]]) and (MPS_ValidateTarget(u, udg_MPS_MasterNode[Node])) then
                    //Deal Damage
                    call UnitDamageTarget(udg_MPS_ProjectileLauncher[udg_MPS_MasterNode[Node]], u, udg_MPS_SlaveHealthDamage[udg_MPS_SlaveType[Node]], false, false, udg_MPS_SlaveAttackType[udg_MPS_SlaveType[Node]], udg_MPS_SlaveDamageType[udg_MPS_SlaveType[Node]], null)
                    call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MANA) - udg_MPS_SlaveManaDamage[udg_MPS_SlaveType[Node]])
                endif
                
                call GroupRemoveUnit(udg_MPS_TempGroup, u)
            endloop
            
            //Start Recycling
            call KillUnit(udg_MPS_Slave[Node])
            call MPS_SlaveRecycle(Node)
        else
            call MPS_FireEvent(udg_MPS_SLAVE_UPDATE)
        endif
 
    endloop
    
endfunction

////////////////////////////////////////////////////////////////////
//  The main function which is used to handle the control of all  //
//  of the projectiles handled by the system                      //
////////////////////////////////////////////////////////////////////
function MPS_ProjectileLoop takes nothing returns nothing
    //Sets up locals
    local integer Node = 0
    
    loop
        set Node = udg_MPS_ProjectileNextNode[Node]
        exitwhen Node == 0
        set udg_MPS_ACTIVE_NODE = Node
        
        //Projectiles
        if (udg_MPS_ProjectileStageID[Node] == MPS_ProjectileStageID()) then
            //Move the projectile
            if (MPS_Move(Node)) or (MPS_HitTarget(Node)) then
                //Destroy on crash
                call DestroyEffect(udg_MPS_ProjectileCurrentEffect[Node])
                call DestroyEffect(AddSpecialEffectTarget(udg_MPS_ProjectileDeathModel[udg_MPS_AmmoType[Node]], udg_MPS_Projectile[Node], MPS_ProjectileAttachmentPoint()))
                //Deal Damage
                call MPS_ProjectileDamageArea(Node)
                //Start Recycling
                set udg_MPS_ProjectileStageID[Node] = MPS_ProjectileRecycleStageID()
            endif
        
        //Recycling
        else
            //Remove and Recycle Unit
            call KillUnit(udg_MPS_Projectile[Node])
            call MPS_ProjectileRecycle(Node)
        endif
        
    endloop
    
    //If you're not using the slave projectile system remove these lines of Code
    //************************************************************************
    if (udg_MPS_SlaveNextNode[0] > 0) then
        call MPS_UpdateSlaveProjectiles()
    endif
    //************************************************************************
endfunction

///////////////////////////////////////////////////////////////////
//  Function used to add Slave Projectiles to their Masters      //
//  only needs to know the Node number of the Master, what       //
//  Slave Ammo to use and how much of it to make                 //
///////////////////////////////////////////////////////////////////
function MPS_AddSlaveProjectiles takes integer Master, integer Ammo, integer Amount returns nothing
    //Set up locals
    local integer iLoop = 0
    local integer Node
    local real Angle = 0
    local real Increment
    
    //Make sure there's at least one to put on before dividing
    if (Amount > 0) then
        set Increment = 2 * bj_PI / Amount
    
        //Create projectiles
        loop
            set iLoop = iLoop + 1
            exitwhen (iLoop > Amount)
            
            //Set up Slave Data
            set Angle = Angle + Increment
            set Node = MPS_SlaveCreateNode()
            set udg_MPS_SlaveAngle[Node] = Angle
            set udg_MPS_SlaveCurrentOffset[Node] = udg_MPS_SlaveOffset[Ammo]
            set udg_MPS_SlaveVel[Node] = 0
            set udg_MPS_MasterNode[Node] = Master
            set udg_MPS_SlaveType[Node] = Ammo
            
            //Create Slave Projectile and Apply Aesthetics
            set udg_MPS_Slave[Node] = CreateUnit(MPS_DummyPlayer(), MPS_ProjectileDummyID(), udg_MPS_ProjectileX[Master], udg_MPS_ProjectileY[Master], GetUnitFacing(udg_MPS_Projectile[Master]))
            set udg_MPS_SlaveCurrentEffect[Node] = AddSpecialEffectTarget(udg_MPS_SlaveModel[Ammo], udg_MPS_Slave[Node], MPS_ProjectileAttachmentPoint())
            if UnitAddAbility(udg_MPS_Slave[Node], 'Amrf') and UnitRemoveAbility(udg_MPS_Slave[Node], 'Amrf') then
            endif
            call SetUnitScale(udg_MPS_Slave[Node], udg_MPS_SlaveModelScale[Ammo], 0., 0.)
            call SetUnitFlyHeight(udg_MPS_Slave[Node], GetUnitFlyHeight(udg_MPS_Projectile[Master]), 0.)
            call PauseUnit(udg_MPS_Slave[Node], true)
            
            //Fire create event
            set udg_MPS_ACTIVE_NODE = Node
            call MPS_FireEvent(udg_MPS_SLAVE_CREATE)
        endloop
        
    endif
endfunction
////////////////////////////////////////////////////////////////////
//  Function used to create the projectiles in the system, it     //
//  must be given an Ammo type, strength of launch, the angles    //
//  for the launch (Pitch and Angle), starting co-ordinates       //
//  and a target (if a unit is passed as a target then target     //
//  X and Y need not be passed (pass a value of 0 for both)       //
////////////////////////////////////////////////////////////////////
function MPS_CreateProjectile takes unit u, integer Ammo, real Power, real Pitch, real Angle, real x, real y, real z, unit Target, real TargetX, real TargetY returns integer
    local integer Node = MPS_ProjectileCreateNode()
    
    //Setup up Master Projectile Data
    set udg_MPS_AmmoType[Node] = Ammo
    set udg_MPS_ProjectileX[Node] = x
    set udg_MPS_ProjectileY[Node] = y
    set udg_MPS_TargetX[Node] = TargetX
    set udg_MPS_TargetY[Node] = TargetY
    set udg_MPS_ZVel[Node] = Power * Sin(Pitch)
    set udg_MPS_XVel[Node] = Power * Cos(Angle) * Cos(Pitch)
    set udg_MPS_YVel[Node] = Power * Sin(Angle) * Cos(Pitch)
    set udg_MPS_ProjectileHeight[Node] = z + MPS_GetZ(x, y)
    set udg_MPS_ProjectileStageID[Node] = MPS_ProjectileStageID()
    set udg_MPS_ProjectileTargetUnit[Node] = Target
    set udg_MPS_ProjectileLauncher[Node] = u
    set udg_MPS_ProjectilePlayer[Node] = GetTriggerPlayer()
    
    //Check if it has a target
    if not(Target == null) then
        set udg_MPS_TargetZ[Node] = GetUnitFlyHeight(udg_MPS_ProjectileTargetUnit[Node]) + udg_MPS_ProjectileTargetAimOffset[udg_MPS_AmmoType[Node]]
    else
        set udg_MPS_TargetZ[Node] = 0.
    endif
    
    //Create Projectile and Apply Aesthetics
    set udg_MPS_Projectile[Node] = CreateUnit(MPS_DummyPlayer(), MPS_ProjectileDummyID(), x, y, Angle * bj_RADTODEG)
    if UnitAddAbility(udg_MPS_Projectile[Node], 'Amrf') and UnitRemoveAbility(udg_MPS_Projectile[Node], 'Amrf') then
    endif
    set udg_MPS_ProjectileCurrentEffect[Node] = AddSpecialEffectTarget(udg_MPS_ProjectileModel[Ammo], udg_MPS_Projectile[Node], MPS_ProjectileAttachmentPoint())
    call SetUnitScale(udg_MPS_Projectile[Node], udg_MPS_ProjectileModelScale[Ammo], 0., 0.)
    call SetUnitFlyHeight(udg_MPS_Projectile[Node], z, 0.)
    call PauseUnit(udg_MPS_Projectile[Node], true)

    //Fire create event
    set udg_MPS_ACTIVE_NODE = Node
    call MPS_FireEvent(udg_MPS_MASTER_CREATE)
    
    //Start Timer
    if udg_MPS_ProjectilePrevNode[Node] == 0 then
        call TimerStart(udg_MPS_ProjectileTimer, MPS_ProjectileTimerSpeed(), true, function MPS_ProjectileLoop)
    endif
    
    //Pass back node number for Slave projectiles
    return Node
endfunction

////////////////////////////////////////////////////////////////////
//  Initialisation trigger, applies the conditions to triggers    //
//  and sets up the global location used to get location Z's      //
//  as well as the map bounds and event variable values           //
////////////////////////////////////////////////////////////////////
function InitTrig_Master_Projectile_System takes nothing returns nothing
    //Set up the location used to find Z heights
    set udg_MPS_Loc = Location(0,0)
    //Set up the Timer used to run the loop
    set udg_MPS_ProjectileTimer = CreateTimer()

    //Set up event variables
    set udg_MPS_NO_EVENT = 0.
    set udg_MPS_MASTER_CREATE = 1.
    set udg_MPS_MASTER_UPDATE = 2.
    set udg_MPS_MASTER_CRASH = 3.
    set udg_MPS_MASTER_HIT = 4.
    set udg_MPS_SLAVE_CREATE = 5.
    set udg_MPS_SLAVE_UPDATE = 6.
    set udg_MPS_SLAVE_DEATH = 7.
    //Set up Amount of active slaves and ammo
    set udg_MPS_AmmoNumber = 0
    set udg_MPS_SlaveNumber = 0
    
    //Sets up the variables used to make sure a point is within the map area
    set udg_MPS_MapMaxX = GetRectMaxX(bj_mapInitialPlayableArea)
    set udg_MPS_MapMinX = GetRectMinX(bj_mapInitialPlayableArea)
    set udg_MPS_MapMaxY = GetRectMaxY(bj_mapInitialPlayableArea)
    set udg_MPS_MapMinY = GetRectMinY(bj_mapInitialPlayableArea)
endfunction
////////////////////////////////////////////////////////////////////
// End of the system                                              //
////////////////////////////////////////////////////////////////////


JASS:
////////////////////////////////////////////////////////////////////
//                  MASTER SALVO SYSTEM V1.02                     //
//  Author: Tank-Commander                                        //
//  Requires: MASTER PROJECTILE SYSTEM                            //
//  Purpose: Create Impressive and Dynamic Salvos of projectiles  //
//                                                                //
//  Notes:                                                        //
//    -  Read the readme before you try modifying the config      //
//    -  Use the "Helpful files" to help you import the spell     //
//                                                                //
//  If you have used this system in your map, you are required    //
//  to give credits to Tank-Commander for the creation of it      //
//  If you would like to use snippets of code from this for       //
//  whatever, getting permission and crediting the source/linking //
//  would be much appreciated.                                    //
//                                                                //
//  This system does not have a configuration (as it does not     //
//  have any use of one)                                          //
////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////
//  Function used for registering new salvo types into the system //
//  call this function after setting up a new salvo type will     //
//  allow it to be used as a salvo type for spells/turrets/etc.   //
////////////////////////////////////////////////////////////////////
function MPS_RegisterSalvo takes integer RegisterNumber returns nothing
    local integer Id
    
    if (RegisterNumber == 0) then
        set udg_MPS_SalvoNumber = ( udg_MPS_SalvoNumber + 1 )
        set Id = udg_MPS_SalvoNumber
    else
        set Id = RegisterNumber
    endif
    
    set udg_MPS_SalvoName[Id] = StringCase(udg_MPS_SalvoName[0], true)
    set udg_MPS_SalvoMasterAmmoAmount[Id] = udg_MPS_SalvoMasterAmmoAmount[0]
    set udg_MPS_SalvoSlaveAmmoAmount[Id] = udg_MPS_SalvoSlaveAmmoAmount[0]
    set udg_MPS_SalvoAngles[Id] = udg_MPS_SalvoAngles[0]
    set udg_MPS_SalvoPower[Id] = udg_MPS_SalvoPower[0]
    set udg_MPS_SalvoHeightOffset[Id] = udg_MPS_SalvoHeightOffset[0]
endfunction

////////////////////////////////////////////////////////////////////
//  This function returns the Id number of the salvo              //
//  which shares the name with the passed parameter, it can be    //
//  used to consistently refer to a salvo without knowing the     //
//  order by which it was declared (allowing dynamic introduction //
//  of salvo types                                                //
////////////////////////////////////////////////////////////////////
function MPS_GetSalvoByName takes string name returns integer
    local integer iLoop = 0
   
    set name = StringCase(name, true)
   
    loop
        set iLoop = iLoop + 1
        exitwhen iLoop > udg_MPS_SalvoNumber
       
        if (name == udg_MPS_SalvoName[iLoop]) then
            return iLoop
        endif
       
    endloop
   
    return 0
endfunction

////////////////////////////////////////////////////////////////////
//  Function used for registering new attack types into the system//
//  call this function after setting up a new attack type will    //
//  allow it to be used as an attack type for spells/turrets/etc. //
////////////////////////////////////////////////////////////////////
function MPS_RegisterAttack takes integer RegisterNumber returns nothing
    local integer Id
    
    if (RegisterNumber == 0) then
        set udg_MPS_AttackNumber = ( udg_MPS_AttackNumber + 1 )
        set Id = udg_MPS_AttackNumber
    else
        set Id = RegisterNumber
    endif
    
    set udg_MPS_AttackName[Id] = StringCase(udg_MPS_AttackName[0], true)
    set udg_MPS_AttackSalvo[Id] = udg_MPS_AttackSalvo[0]
    set udg_MPS_AttackMasterAmmo[Id] = udg_MPS_AttackMasterAmmo[0]
    set udg_MPS_AttackSlaveAmmo[Id] = udg_MPS_AttackSlaveAmmo[0]
endfunction

////////////////////////////////////////////////////////////////////
//  This function returns the Id number of the attack             //
//  which shares the name with the passed parameter, it can be    //
//  used to consistently refer to a attack without knowing the    //
//  order by which it was declared (allowing dynamic introduction //
//  of attack types                                               //
////////////////////////////////////////////////////////////////////
function MPS_GetAttackByName takes string name returns integer
    local integer iLoop = 0
   
    set name = StringCase(name, true)
   
    loop
        set iLoop = iLoop + 1
        exitwhen iLoop > udg_MPS_AttackNumber
       
        if (name == udg_MPS_AttackName[iLoop]) then
            return iLoop
        endif
       
    endloop
   
    return 0
endfunction

////////////////////////////////////////////////////////////////////
//  Function used to Launch a Salvo from a specific unit to a     //
//  specific target in accordance to what it was passed           //
////////////////////////////////////////////////////////////////////
function MPS_LaunchSalvo takes unit u, integer Salvo, integer MasterAmmo, integer SlaveAmmo, unit Target, real TargetX, real TargetY returns nothing
    //Set up locals
    local real x = GetUnitX(u)
    local real y = GetUnitY(u)
    local real z = GetUnitFlyHeight(u) + udg_MPS_SalvoHeightOffset[Salvo]
    local real Facing = GetUnitFacing(u) * bj_DEGTORAD
    local real Increment = 0
    local real Offsetting = 0
    local real Angle
    local real Pitch
    local integer iLoop = 0
    
    //Make sure there's at least more than one master before dividing
    if (udg_MPS_SalvoMasterAmmoAmount[Salvo] > 1) then
        set Increment = udg_MPS_SalvoAngles[Salvo] / (udg_MPS_SalvoMasterAmmoAmount[Salvo] - 1)
        set Offsetting = (udg_MPS_SalvoAngles[Salvo] / 2)
    endif
    
    //trigger event
    set udg_MPS_ACTIVE_NODE = Salvo
    call MPS_FireEvent(udg_MPS_SALVO_FIRED)
    
    //Set up the Angle
    set Angle =  Facing - Offsetting
    
    //Create Master and Slave Projectiles
    loop
        set iLoop = iLoop + 1
        exitwhen iLoop > udg_MPS_SalvoMasterAmmoAmount[Salvo]
        set Pitch = Cos((Facing - Angle))
        
        if (SlaveAmmo > 0) then
            call MPS_AddSlaveProjectiles(MPS_CreateProjectile(u, MasterAmmo, udg_MPS_SalvoPower[Salvo], Pitch, Angle, x, y, z, Target, TargetX, TargetY), SlaveAmmo, udg_MPS_SalvoSlaveAmmoAmount[Salvo])
        else
            call MPS_CreateProjectile(u, MasterAmmo, udg_MPS_SalvoPower[Salvo], Pitch, Angle, x, y, z, Target, TargetX, TargetY)
        endif

        set Angle = Angle + Increment
    endloop
    
endfunction

////////////////////////////////////////////////////////////////////
//  Function used to fire projectiles based on a defined Attack   //
//  using a specific Salvo, Master and Slave Ammo for ease of     //
//  use                                                           //
////////////////////////////////////////////////////////////////////
function MPS_ActivateAttack takes integer Attack, unit Launcher, unit Target, real x, real y returns nothing
    set udg_MPS_ACTIVE_NODE = Attack
    call MPS_FireEvent(udg_MPS_ATTACK_USED)
    
    call MPS_LaunchSalvo(Launcher, udg_MPS_AttackSalvo[Attack], udg_MPS_AttackMasterAmmo[Attack], udg_MPS_AttackSlaveAmmo[Attack], Target, x, y)
endfunction

////////////////////////////////////////////////////////////////////
//  Function which only exists to allow for compilation and set   //
//  up event variable values                                      //
////////////////////////////////////////////////////////////////////
function InitTrig_Master_Salvo_System takes nothing returns nothing
    set udg_MPS_SALVO_FIRED = 8.
    set udg_MPS_ATTACK_USED = 9.
endfunction
////////////////////////////////////////////////////////////////////
//  END OF THE SYSTEM                                             //
////////////////////////////////////////////////////////////////////

System Information

- Produced over 1 day on a whim
- Coded in Vanilla JASS
- Features 2 advanced systems with 2 sub systems
- Comes with Enchanted Fire Bolt adapted to use the system to show you how to use the system to make your own abilities
- Supports as many Projectiles as you want
- Supports as many Salvos as you want
- Attack System provides ease-of-access
- Seperated registeries for each component of the system to break it down
- Vector Physics giving a realistic feeling
- Allows complex Arcs for projectiles
- 3D orbiting Projectiles that stay in the correct place regardless of the vector (Good lord the Maths!)
- Salvos can fire multiple projectiles at once cleanly and evenly
- Efficiently designed to allow maximum missile spam
- Comes with helpful guides on how to utilise the system including how to replace regular attacks with them (while still allowing A-Moving)
- Comes with omnipresent Mr. Fluffy and his entourage of Tanks
Setup Files


JASS:
The setup triggers are divided into categories (they do not need to be like this
but it is more practical this way), The names of the triggers are fairly
self-explanatory.

------------------------------------------------------------------------
The Master Ammo Setup

    The fundamental setup - without this you have no projectiles to work with
    and the other systems cannot do anything at all, it may be a bit difficult
    to get the hang of but with a good understanding you can make really
    beautiful looking projectiles and arcs.
    
 - ProjectileName: This is the name given to the ammo so you can refer to it later
                   via the "MPS_GetAmmoByName(string Ammo)" function (pass this
                   value to get the index number of the ammo)
                   
 - ProjectileModel: This is the filepath of the model used for the projectile
                    while it is moving
                    
 - ProjectileDeathModel: This is the filepath of the model used for the projectile
                         while it is blowing up
                         
 - ProjectileScale: This is the size of the projectile and its death model,
                    1.00 = 100% (normal scale)
                    
 - ProjectileAOE: This is how close to its target the projectile needs to get
                  before it will detonate, it is also how large the area around
                  the target of the projectile which will also be affected is
                  
 - ProjectileHealthDamage: The damage dealt to the health of targets hit
 
 - ProjectileManaDamage: The damage dealt to the mana of targets hit
 
 - ProjectileAttackType: This is the Attack Type used by the projectile, it decides
                         what kind of damage buffs/reductions are applied to the
                         damage dealt by the projectile
                         
 - ProjectileWeaponType: This is the Weapon Type used by the projectile, it doesn't
                         mean much however unless you're using another external
                         system to make it do something useful
                         
 - ProjectileAcceleration: This is how much the net Speed of the projectile is
                           multiplied by every timer iteration (don't set it too
                           high or the projectile will fly out of control, too low
                           and it will barely move without a high turn rate
                           
 - ProjectileTurnRate: This is the amount of momentum the projectile gains each timer
                       iteration directly in the direction of the target (causing it
                       to turn)
                       
 - ProjectileTurnEfficiency: This is the portion of the net speed of the projectile
                             which is converted to be in the direction of the target
                             each step, note though that this results in a fractional
                             amount where 1.00 = 1/2, 0.5 = 2/3, 1.5 = 3/5.
                             (essetially TurnEfficiency / (1 + TurnEfficiency) with 
                             this value being high, the projectile will always face
                             the target
                             
 - ProjectileTargetAimOffset: This is the extra Z height offset that the projectile
                              will aim at (this prevents the projectile from hitting
                              the ground too early)

------------------------------------------------------------------------
The Slave Ammo Setup

    The Slave Projectile setup is a lot like the Master Ammo setup except with 
    some small differences: There's no physics controls (Accel/Turn/etc.)
    instead you're given three different parameters.
    
 - SlaveOffset: This is how far away from the Master projectile the Slave starts

 - SlavePull: This is the strength at which the Slave projectile is pulled toward
              The master projectile (this forms a sort of bouncing effect)
              
 - SlaveSpin: This is how fast the projectile spins around the master projectile
              The value is in radians so be weary when setting it
------------------------------------------------------------------------
The Salvos Setup

    The Salvos Setup is where things start getting very fancy, this setup allows
    you to launch multiple projectiles at once with different Arcs with each having
    their own Slave projectiles.
    
 - SalvoMasterAmmoAmount: This is how many Master projectiles that will be launched
                          by the salvo
                          
 - SalvoSlaveAmmoAmount: This is how many Slave projectiles each Master projectile
                         will ve given
                         
 - SalvoAngles: This is the area in which projectiles can be launched (a measure in
                radians) a value of PI/2 results in an even spread in a |- shape
                (where "-" is the facing angle of the launcher) Master projectiles
                will be distributed evenly within the specified area of angles
                
 - SalvoPower: This is the amount of force each Master Projectile is launched with
               giving them an extra push as they are fired
               
 - SalvoHeightOffset: This is the added amount of height given to Master Projectiles
                      fired by this salvo from the height of the launcher in order
                      to prevent them from hitting into the ground on launch
------------------------------------------------------------------------  
The Attack Setup

    By far the simplest setup, the Attack setup brings everything together in
    an easy-to-use package once everything has been specified, as such it's
    pretty simple to use.
    
 - AttackSalvo: What Salvo Type this Attack shall be using
 
 - AttackMasterAmmo: What Master Projectiles shall the Attack be using
 
 - AttackSlaveAmmo: What Slave Projectiles shall the Attack by using
 ------------------------------------------------------------------------  
 
 Please use this document for referral if you are having difficulty or getting
 confused by the setups for each area. Each Setup also has dividing markers
 to show where one setup ends and each begins (and it's encouraged to
 continue the structure in order to avoid confusion later on).



  • Master Ammo Setup
    • Events
      • Time - Elapsed game time is 1.00 seconds
    • Conditions
    • Actions
      • -------- Start of Setup Block --------
      • Set MPS_ProjectileName[0] = Rocket
      • Set MPS_ProjectileModel[0] = Abilities\Weapons\RocketMissile\RocketMissile.mdl
      • Set MPS_ProjectileDeathModel[0] = Abilities\Weapons\SteamTank\SteamTankImpact.mdl
      • Set MPS_ProjectileModelScale[0] = 0.80
      • Set MPS_ProjectileAOE[0] = 100.00
      • Set MPS_ProjectileHealthDamage[0] = 100.00
      • Set MPS_ProjectileManaDamage[0] = 0.00
      • Set MPS_ProjectileAttackType[0] = Spells
      • Set MPS_ProjectileDamageType[0] = Normal
      • Set MPS_ProjectileAcceleration[0] = 0.75
      • Set MPS_ProjectileTurnRate[0] = 15.00
      • Set MPS_ProjectileTurnEfficiency[0] = 0.00
      • Set MPS_ProjectileTargetAimOffset[0] = 50.00
      • Custom script: call MPS_RegisterAmmo(0)
      • -------- End of Setup Block --------
      • Set MPS_ProjectileName[0] = Empty Core
      • Set MPS_ProjectileModel[0] = war3mapImported\dummy.mdl
      • Set MPS_ProjectileDeathModel[0] = war3mapImported\dummy.mdl
      • Set MPS_ProjectileModelScale[0] = 1.50
      • Set MPS_ProjectileAOE[0] = 30.00
      • Set MPS_ProjectileHealthDamage[0] = 0.00
      • Set MPS_ProjectileManaDamage[0] = 0.00
      • Set MPS_ProjectileAttackType[0] = Spells
      • Set MPS_ProjectileDamageType[0] = Normal
      • Set MPS_ProjectileAcceleration[0] = 0.80
      • Set MPS_ProjectileTurnRate[0] = 7.50
      • Set MPS_ProjectileTurnEfficiency[0] = 0.00
      • Set MPS_ProjectileTargetAimOffset[0] = 30.00
      • Custom script: call MPS_RegisterAmmo(0)
      • -------- ------------------------------- --------
      • Set MPS_ProjectileName[0] = Chaos Core
      • Set MPS_ProjectileModel[0] = war3mapImported\dummy.mdl
      • Set MPS_ProjectileDeathModel[0] = war3mapImported\dummy.mdl
      • Set MPS_ProjectileModelScale[0] = 1.50
      • Set MPS_ProjectileAOE[0] = 30.00
      • Set MPS_ProjectileHealthDamage[0] = 0.00
      • Set MPS_ProjectileManaDamage[0] = 0.00
      • Set MPS_ProjectileAttackType[0] = Chaos
      • Set MPS_ProjectileDamageType[0] = Normal
      • Set MPS_ProjectileAcceleration[0] = 0.80
      • Set MPS_ProjectileTurnRate[0] = 11.00
      • Set MPS_ProjectileTurnEfficiency[0] = -0.01
      • Set MPS_ProjectileTargetAimOffset[0] = 30.00
      • Custom script: call MPS_RegisterAmmo(0)
      • -------- ------------------------------- --------
      • Set MPS_ProjectileName[0] = Freezing Energy
      • Set MPS_ProjectileModel[0] = Abilities\Spells\Undead\FrostNova\FrostNovaTarget.mdl
      • Set MPS_ProjectileDeathModel[0] = Abilities\Spells\Undead\FrostNova\FrostNovaTarget.mdl
      • Set MPS_ProjectileModelScale[0] = 0.75
      • Set MPS_ProjectileAOE[0] = 80.00
      • Set MPS_ProjectileHealthDamage[0] = 100.00
      • Set MPS_ProjectileManaDamage[0] = 0.00
      • Set MPS_ProjectileAttackType[0] = Spells
      • Set MPS_ProjectileDamageType[0] = Normal
      • Set MPS_ProjectileAcceleration[0] = 1.01
      • Set MPS_ProjectileTurnRate[0] = 2.00
      • Set MPS_ProjectileTurnEfficiency[0] = 0.25
      • Set MPS_ProjectileTargetAimOffset[0] = 30.00
      • Custom script: call MPS_RegisterAmmo(0)


  • Slave Ammo Setup
    • Events
      • Time - Elapsed game time is 1.10 seconds
    • Conditions
    • Actions
      • -------- Start of Setup Block --------
      • Set MPS_SlaveName[0] = Fire Streak
      • Set MPS_SlaveModel[0] = Abilities\Weapons\PhoenixMissile\Phoenix_Missile.mdl
      • Set MPS_SlaveDeathModel[0] = Abilities\Spells\Other\Incinerate\FireLordDeathExplode.mdl
      • Set MPS_SlaveModelScale[0] = 0.60
      • Set MPS_SlaveAOE[0] = 100.00
      • Set MPS_SlaveHealthDamage[0] = 40.00
      • Set MPS_SlaveManaDamage[0] = 0.00
      • Set MPS_SlaveAttackType[0] = Spells
      • Set MPS_SlaveDamageType[0] = Normal
      • Set MPS_SlaveOffset[0] = 90.00
      • Set MPS_SlavePull[0] = 1.50
      • Set MPS_SlaveSpin[0] = 0.20
      • Custom script: call MPS_RegisterSlave(0)
      • -------- End of Setup Block --------
      • Set MPS_SlaveName[0] = Ice Breath
      • Set MPS_SlaveModel[0] = Abilities\Weapons\FrostWyrmMissile\FrostWyrmMissile.mdl
      • Set MPS_SlaveDeathModel[0] = Abilities\Spells\Items\AIob\AIobSpecialArt.mdl
      • Set MPS_SlaveModelScale[0] = 0.40
      • Set MPS_SlaveAOE[0] = 60.00
      • Set MPS_SlaveHealthDamage[0] = 75.00
      • Set MPS_SlaveManaDamage[0] = 0.00
      • Set MPS_SlaveAttackType[0] = Spells
      • Set MPS_SlaveDamageType[0] = Normal
      • Set MPS_SlaveOffset[0] = 90.00
      • Set MPS_SlavePull[0] = 0.00
      • Set MPS_SlaveSpin[0] = 0.60
      • Custom script: call MPS_RegisterSlave(0)
      • -------- ------------------------------- --------
      • Set MPS_SlaveName[0] = Frozen Heart
      • Set MPS_SlaveModel[0] = Abilities\Weapons\SpiritOfVengeanceMissile\SpiritOfVengeanceMissile.mdl
      • Set MPS_SlaveDeathModel[0] = war3mapImported\dummy.mdl
      • Set MPS_SlaveModelScale[0] = 1.50
      • Set MPS_SlaveAOE[0] = 30.00
      • Set MPS_SlaveHealthDamage[0] = 20.00
      • Set MPS_SlaveManaDamage[0] = 0.00
      • Set MPS_SlaveAttackType[0] = Spells
      • Set MPS_SlaveDamageType[0] = Normal
      • Set MPS_SlaveOffset[0] = 75.00
      • Set MPS_SlavePull[0] = 0.00
      • Set MPS_SlaveSpin[0] = 0.00
      • Custom script: call MPS_RegisterSlave(0)
      • -------- ------------------------------- --------
      • Set MPS_SlaveName[0] = Chaos Energy
      • Set MPS_SlaveModel[0] = Abilities\Spells\Undead\OrbOfDeath\AnnihilationMissile.mdl
      • Set MPS_SlaveDeathModel[0] = war3mapImported\dummy.mdl
      • Set MPS_SlaveModelScale[0] = 0.40
      • Set MPS_SlaveAOE[0] = 70.00
      • Set MPS_SlaveHealthDamage[0] = 15.00
      • Set MPS_SlaveManaDamage[0] = 0.00
      • Set MPS_SlaveAttackType[0] = Chaos
      • Set MPS_SlaveDamageType[0] = Normal
      • Set MPS_SlaveOffset[0] = 90.00
      • Set MPS_SlavePull[0] = 0.00
      • Set MPS_SlaveSpin[0] = 0.15
      • Custom script: call MPS_RegisterSlave(0)


  • Salvos Setup
    • Events
      • Time - Elapsed game time is 1.20 seconds
    • Conditions
    • Actions
      • -------- Start of Setup Block --------
      • Set MPS_SalvoName[0] = Arch
      • Set MPS_SalvoMasterAmmoAmount[0] = 5
      • Set MPS_SalvoSlaveAmmoAmount[0] = 0
      • Set MPS_SalvoAngles[0] = 1.57
      • Set MPS_SalvoPower[0] = 140.00
      • Set MPS_SalvoHeightOffset[0] = 100.00
      • Custom script: call MPS_RegisterSalvo(0)
      • -------- End of Setup Block --------
      • Set MPS_SalvoName[0] = Single Up
      • Set MPS_SalvoMasterAmmoAmount[0] = 1
      • Set MPS_SalvoSlaveAmmoAmount[0] = 6
      • Set MPS_SalvoAngles[0] = 0.00
      • Set MPS_SalvoPower[0] = 70.00
      • Set MPS_SalvoHeightOffset[0] = 50.00
      • Custom script: call MPS_RegisterSalvo(0)
      • -------- ------------------------------- --------
      • Set MPS_SalvoName[0] = Single Across
      • Set MPS_SalvoMasterAmmoAmount[0] = 1
      • Set MPS_SalvoSlaveAmmoAmount[0] = 0
      • Set MPS_SalvoAngles[0] = 0.00
      • Set MPS_SalvoPower[0] = 0.00
      • Set MPS_SalvoHeightOffset[0] = 50.00
      • Custom script: call MPS_RegisterSalvo(0)
      • -------- ------------------------------- --------
      • Set MPS_SalvoName[0] = Circle
      • Set MPS_SalvoMasterAmmoAmount[0] = 8
      • Set MPS_SalvoSlaveAmmoAmount[0] = 0
      • Set MPS_SalvoAngles[0] = 3.14
      • Set MPS_SalvoPower[0] = 110.00
      • Set MPS_SalvoHeightOffset[0] = 150.00
      • Custom script: call MPS_RegisterSalvo(0)
      • -------- ------------------------------- --------
      • Set MPS_SalvoName[0] = Dual Spiral
      • Set MPS_SalvoMasterAmmoAmount[0] = 2
      • Set MPS_SalvoSlaveAmmoAmount[0] = 20
      • Set MPS_SalvoAngles[0] = 2.00
      • Set MPS_SalvoPower[0] = 90.00
      • Set MPS_SalvoHeightOffset[0] = 50.00
      • Custom script: call MPS_RegisterSalvo(0)
      • -------- ------------------------------- --------
      • Set MPS_SalvoName[0] = Double
      • Set MPS_SalvoMasterAmmoAmount[0] = 2
      • Set MPS_SalvoSlaveAmmoAmount[0] = 5
      • Set MPS_SalvoAngles[0] = 0.80
      • Set MPS_SalvoPower[0] = 200.00
      • Set MPS_SalvoHeightOffset[0] = 50.00
      • Custom script: call MPS_RegisterSalvo(0)
      • -------- ------------------------------- --------
      • Set MPS_SalvoName[0] = Heavy Artillery
      • Set MPS_SalvoMasterAmmoAmount[0] = 3
      • Set MPS_SalvoSlaveAmmoAmount[0] = 9
      • Set MPS_SalvoAngles[0] = 1.80
      • Set MPS_SalvoPower[0] = 140.00
      • Set MPS_SalvoHeightOffset[0] = 25.00
      • Custom script: call MPS_RegisterSalvo(0)
      • -------- ------------------------------- --------
      • Set MPS_SalvoName[0] = Effortless
      • Set MPS_SalvoMasterAmmoAmount[0] = 1
      • Set MPS_SalvoSlaveAmmoAmount[0] = 3
      • Set MPS_SalvoAngles[0] = 1.50
      • Set MPS_SalvoPower[0] = 0.00
      • Set MPS_SalvoHeightOffset[0] = 150.00
      • Custom script: call MPS_RegisterSalvo(0)


  • Attack Setup
    • Events
      • Time - Elapsed game time is 1.30 seconds
    • Conditions
    • Actions
      • -------- Start of Setup Block --------
      • Set MPS_AttackName[0] = Flame Burst
      • Custom script: set udg_MPS_AttackSalvo[0] = MPS_GetSalvoByName("Heavy Artillery")
      • Custom script: set udg_MPS_AttackMasterAmmo[0] = MPS_GetAmmoByName("Empty Core")
      • Custom script: set udg_MPS_AttackSlaveAmmo[0] = MPS_GetSlaveByName("Fire Streak")
      • Custom script: call MPS_RegisterAttack(0)
      • -------- End of Setup Block --------
      • Set MPS_AttackName[0] = Ice Strike
      • Custom script: set udg_MPS_AttackSalvo[0] = MPS_GetSalvoByName("Effortless")
      • Custom script: set udg_MPS_AttackMasterAmmo[0] = MPS_GetAmmoByName("Empty Core")
      • Custom script: set udg_MPS_AttackSlaveAmmo[0] = MPS_GetSlaveByName("Ice Breath")
      • Custom script: call MPS_RegisterAttack(0)
      • -------- ------------------------------- --------
      • Set MPS_AttackName[0] = Barrage
      • Custom script: set udg_MPS_AttackSalvo[0] = MPS_GetSalvoByName("Arch")
      • Custom script: set udg_MPS_AttackMasterAmmo[0] = MPS_GetAmmoByName("Rocket")
      • Custom script: set udg_MPS_AttackSlaveAmmo[0] = 1
      • Custom script: call MPS_RegisterAttack(0)
      • -------- ------------------------------- --------
      • Set MPS_AttackName[0] = Freezing Blast
      • Custom script: set udg_MPS_AttackSalvo[0] = MPS_GetSalvoByName("Single up")
      • Custom script: set udg_MPS_AttackMasterAmmo[0] = MPS_GetAmmoByName("Freezing Energy")
      • Custom script: set udg_MPS_AttackSlaveAmmo[0] = MPS_GetSlaveByName("Frozen Heart")
      • Custom script: call MPS_RegisterAttack(0)
      • -------- ------------------------------- --------
      • Set MPS_AttackName[0] = Chaos Flare
      • Custom script: set udg_MPS_AttackSalvo[0] = MPS_GetSalvoByName("Dual Spiral")
      • Custom script: set udg_MPS_AttackMasterAmmo[0] = MPS_GetAmmoByName("Chaos Core")
      • Custom script: set udg_MPS_AttackSlaveAmmo[0] = MPS_GetSlaveByName("Chaos Energy")
      • Custom script: call MPS_RegisterAttack(0)


Helpful Files

- Variable Creator: Copy and paste into your map, you now have all the variables needed for the spell to run
- Explaining The Events: Tells you how to use the Event Variables to create your own extension and abilities to the system
- Not Using Salvo?: A short guide on what to do if you just want to use the Masters Projectile System and not the Masters Salvo System
- Guide on replacing regular attacks with the Attack System
- An example of an ability made using the system A recreation of Enchanted Fire Bolt using the system
  • Variable Creator Projectile
    • Events
    • Conditions
    • Actions
      • Set MPS_ACTIVE_EVENT = 0.00
      • Set MPS_ACTIVE_NODE = 0
      • Set MPS_MASTER_CRASH = 0.00
      • Set MPS_MASTER_CREATE = 0.00
      • Set MPS_MASTER_HIT = 0.00
      • Set MPS_MASTER_UPDATE = 0.00
      • Set MPS_SLAVE_CREATE = 0.00
      • Set MPS_SLAVE_DEATH = 0.00
      • Set MPS_SLAVE_UPDATE = 0.00
      • Set MPS_NO_EVENT = 0.00
      • Set MPS_AmmoNumber = 0
      • Set MPS_AmmoType[0] = 0
      • Set MPS_Loc = MPS_Loc
      • Set MPS_MapMaxX = 0.00
      • Set MPS_MapMaxY = 0.00
      • Set MPS_MapMinX = 0.00
      • Set MPS_MapMinY = 0.00
      • Set MPS_MasterNode[0] = 0
      • Set MPS_Projectile[0] = MPS_Projectile[0]
      • Set MPS_ProjectileAOE[0] = 0.00
      • Set MPS_ProjectileAcceleration[0] = 0.00
      • Set MPS_ProjectileAnimation[0] = 0
      • Set MPS_ProjectileAttackType[0] = MPS_ProjectileAttackType[0]
      • Set MPS_ProjectileCurrentEffect[0] = MPS_ProjectileCurrentEffect[0]
      • Set MPS_ProjectileDamageType[0] = MPS_ProjectileDamageType[0]
      • Set MPS_ProjectileHealthDamage[0] = 0.00
      • Set MPS_ProjectileHeight[0] = 0.00
      • Set MPS_ProjectileLauncher[0] = No unit
      • Set MPS_ProjectileManaDamage[0] = 0.00
      • Set MPS_ProjectileModel[0] = <Empty String>
      • Set MPS_ProjectileModelScale[0] = 0.00
      • Set MPS_ProjectileName[0] = <Empty String>
      • Set MPS_ProjectileNextNode[0] = 0
      • Set MPS_ProjectileNodeNumber = 0
      • Set MPS_ProjectilePlayer[0] = MPS_ProjectilePlayer[0]
      • Set MPS_ProjectilePrevNode[0] = 0
      • Set MPS_ProjectileRecyclableNodes = 0
      • Set MPS_ProjectileRecycleNodes[0] = 0
      • Set MPS_ProjectileStageID[0] = 0
      • Set MPS_ProjectileTargetAimOffset[0] = 0.00
      • Set MPS_ProjectileTargetUnit[0] = MPS_ProjectileTargetUnit[0]
      • Set MPS_ProjectileTimer = MPS_ProjectileTimer
      • Set MPS_ProjectileTurnEfficiency[0] = 0.00
      • Set MPS_ProjectileTurnRate[0] = 0.00
      • Set MPS_ProjectileX[0] = 0.00
      • Set MPS_ProjectileY[0] = 0.00
      • Set MPS_ProjectileRemoveTimer[0] = 0.00
      • Set MPS_Slave[0] = MPS_Slave[0]
      • Set MPS_SlaveAOE[0] = 0.00
      • Set MPS_SlaveAngle[0] = 0.00
      • Set MPS_SlaveAttackType[0] = Normal
      • Set MPS_SlaveCurrentEffect[0] = MPS_SlaveCurrentEffect[0]
      • Set MPS_SlaveCurrentOffset[0] = 0.00
      • Set MPS_SlaveDamageType[0] = Normal
      • Set MPS_SlaveDeathModel[0] = <Empty String>
      • Set MPS_SlaveHealthDamage[0] = 0.00
      • Set MPS_SlaveManaDamage[0] = 0.00
      • Set MPS_SlaveModel[0] = <Empty String>
      • Set MPS_SlaveModelScale[0] = 0.00
      • Set MPS_SlaveName[0] = <Empty String>
      • Set MPS_SlaveNextNode[0] = 0
      • Set MPS_SlaveNodeNumber = 0
      • Set MPS_SlaveNumber = 0
      • Set MPS_SlaveOffset[0] = 0.00
      • Set MPS_SlavePrevNode[0] = 0
      • Set MPS_SlavePull[0] = 0.00
      • Set MPS_SlaveRecyclableNodes = 0
      • Set MPS_SlaveRecycleNodes[0] = 0
      • Set MPS_SlaveSpin[0] = 0.00
      • Set MPS_SlaveType[0] = 0
      • Set MPS_SlaveVel[0] = 0.00
      • Set MPS_TargetX[0] = 0.00
      • Set MPS_TargetY[0] = 0.00
      • Set MPS_TargetZ[0] = 0.00
      • Set MPS_TempGroup = MPS_TempGroup
      • Set MPS_XVel[0] = 0.00
      • Set MPS_YVel[0] = 0.00
      • Set MPS_ZVel[0] = 0.00


  • Variable Creator Salvo
    • Events
    • Conditions
    • Actions
      • Set MPS_ATTACK_USED = 0.00
      • Set MPS_SALVO_FIRED = 0.00
      • Set MPS_AttackMasterAmmo[0] = 0
      • Set MPS_AttackName[0] = <Empty String>
      • Set MPS_AttackNumber = 0
      • Set MPS_AttackSalvo[0] = 0
      • Set MPS_AttackSlaveAmmo[0] = 0
      • Set MPS_SalvoAngles[0] = 0.00
      • Set MPS_SalvoAngles[0] = 0.00
      • Set MPS_SalvoHeightOffset[0] = 0.00
      • Set MPS_SalvoMasterAmmoAmount[0] = 0
      • Set MPS_SalvoName[0] = <Empty String>
      • Set MPS_SalvoNumber = 0
      • Set MPS_SalvoPower[0] = 0.00
      • Set MPS_SalvoSlaveAmmoAmount[0] = 0


JASS:
Events in this context are variables that reach a certain value when something occurs
you can respond to them via using the event of a Real becoming a certain value.

Event variable: udg_MPS_ACTIVE_EVENT
 - this is the variable that is set to these other values listed below when an
   event occurs so it is the one you need to be checking the value of

when any event occurs you can use the MPS_ACTIVE_NODE variable to get the index
value of the activating component (or in the case of the salvo system, the salvo
or attack type that was used, for projectiles you can access what type of ammo or
slave was used by using the following:

 - udg_MPS_AmmoType[udg_MPS_ACTIVE_NODE] for master projectiles
 - udg_MPS_SlaveType[udg_MPS_ACTIVE_NODE] for slave projectiles
 - udg_MPS_AmmoType[udg_MPS_MasterNode[udg_MPS_ACTIVE_NODE]] to get the master projecile 
                                                     type for any given slave projectile
                                                     
WARNING: As slave projectiles and master projectiles are stored in seperate linked lists
it is possible and even likely that a master and slave projectile share an index number
when using this system to make abilities or other systems please use different variables
if you are performing actions on both slaves and masters

The values these systems has built-in for you to respond to:
------------------------------------------------------------------------
MASTER PROJECTILE SYSTEM:

 MASTER RESPONCES
 - udg_MPS_MASTER_CREATE: Fires when a master projectile is made
 - udg_MPS_MASTER_UPDATE: Fires whenever a master projectile is updated (moved)
 - udg_MPS_MASTER_CRASH: Fires when a master projectile hits the ground
 - udg_MPS_MASTER_HIT: Fires when a master projectile hits a target
 
Depending on what kind of ability you are attempting to make/use you will either
be using CRASH or HIT but probably not both (unless you want something to happen
regardless of how the projectile meets its end)

If you want something to be happening all the time while the projectile is moving
UPDATE is the event for you

CREATE is probably the least used event of these unless you are making multiple
master projectiles and need them to behave independantly but you may still find
use for it in other scenarios

ADDITIONAL:
 - Whenever you create a projectile using call MPS_CreateProjectile() 
   MPS_ACTIVE_NODE is set to that projectile index, so you can latch on to
   the linked list of the projectiles instead of making your own structure for
   your ability

 SLAVE RESPONCES
 - udg_MPS_SLAVE_CREATE: Fires when a slave projectile is made
 - udg_MPS_MASTER_UPDATE: Fires whenever a slave projectile is updated (moved)
 - udg_MPS_SLAVE_DEATH: Fires whenever a slave projectile is destroyed
 
Unlike master projectiles they do not distinguish between hitting their target
and crashing into the floor as they are tied to their master (although they
may still crash into the floor and they do not technically have a target)
this is why they only have the DEATH event

the other two are exactly the same as the Master projectile counterparts
------------------------------------------------------------------------
SALVO PROJECTILE SYSTEM

 - udg_MPS_SALVO_FIRED: Fires whenever a salvo is fired
 - udg_MPS_ATTACK_USED: Fires whenever an attack is used
 
both pretty self-explanatory


JASS:
If you are not using the Master Salvo System (or the Attack system within it)
then you are going to need to know a few things in order to maximise the
usage out of the master projectile system

1) How do I create projectiles without Slave Projectiles?
function MPS_CreateProjectile takes unit u, integer Ammo, real Power, real Pitch, real Angle, real x, real y, real z, unit Target, real TargetX, real TargetY returns integer
the function returns an integer (which is the Node Number of the projectile created)

 - MPS_CreateProjectile(u, Ammo, Power, Pitch, Angle, x, y, z, Target, TargetX, Target Y)
        There are a lot of parameters that the function asks for
        u - The unit creating the projectile
        Ammo - What Master Projectile you're using
        Power - How strong is the launch of the projectile
        Pitch - What is the Angle upwards that you're firing with (Radians)
        Angle - What is the Angle across the X/Y you're firing with (Radians)
        x - x co-ordinate to place the projectile
        y - y co-ordinate to place the projectile
        z - z co-ordinate to place the projectile
        Target - Target unit of the projectile (can be null)
        TargetX - Only used if Target is null, Target X co-ordinate
        TargetY - Only used if Target is null, Target Y co-ordinate
        
2) So now you know how to make a Master projectile, but how do you give a Master Projectile its slaves?
Well you directly nest the MPS_CreateProjectile function into the MPS_AddSlaveProjectiles function
function MPS_AddSlaveProjectiles takes integer Master, integer Ammo, integer Amount returns nothing

 - MPS_AddSlaveProjectiles(Master, Ammo, Amount)
        This one is significantly simpler than creating Master Projectiles
        Master - The Node Number of the Master Projectile
        Ammo - What Slave Projectile you're using
        Amount - How many Slave projectiles do you want
        
So when combined they look like this:
call MPS_AddSlaveProjectiles(MPS_CreateProjectile(u, Ammo, Power, Pitch, Angle, x, y, z, Target, TargetX, Target Y), Ammo, Amount)
compact but it works

3)' By now it is looking like you ought to be using the Salvo system which makes doing this much easier
but you chose not to, so what else is important to remember?

Without having the attack system to help you either (inbuilt to the Salvo system) you are going to need to code up
each of the attacks as well as all the things the salvo does for you this includes:
 - The Angle needs to take into account of the direction between launcher and caster (or the facing angle)
 - Pitch should not be so high as to fire projectiles backwards (unless that's what you wanted)
 - It becomes much harder to differentiate between different attacks without the inbuilt system
All things considered it shouldn't be too difficult to code up your own methods of utilising the system


JASS:
While this example is rather crude is proves very effective at replacing all unit
attacks with projectiles generated through this system.

First you will need the Force Attack Trigger - this will order a unit to attack
whenever it attempts to attack. While this is weird at first it is essential
to the trick done to complete this task

Next, go into the object editor and create an ability based off channel
this will need as many levels as you have attacks.

change the Base order ID of every level to "attack", set duration to 0
set the disabling of other abilities to false and the follow through time
to 0 as well

the remaining parameters you have are Target Type, Cast Range and Cooldown
These are now your attacks, Target Type works best with "Unit Target"
as it still allows you to attack move successfully (this will however
also make your projectiles homing unless you make sure not to pass a unit
when activating the attack)

Cast Range now becomes your attack range (note: the unit the ability is
attached to will still need the correct/same attack range) and Cooldown
is now your attack cooldown (set the unit attack speed to be about twice
as fast as this cooldown, e.g. if the cooldown is 1.5 seconds set the
attack speed to 0.75, it's also recommended to change the animation
to "attack" if applicable, but in this case it may be a good idea to set
the attack speed to a value to at least match if not be more than the
cooldown

Now give all units with projectile attacks the ability (this can also be
achieved by having different channel abilities for each attack which may
be preferable to the next part if you're willing to do it)

You now need to make sure that all of your units has the right ability
which means setting all of them to have the right level (setting the level
of the ability to the right thing when they enter the map bounds and running
a trigger setting all exisiting units to the right level as well)

Finally you need a trigger to run the right attack for each level, the
example trigger here is rather crude and quite inefficient but serves the
purpose, a better method would be to cycle through each level and tie a
name or attack index number to each to then automatically run them
(some extra work if you wanted to occassionally pass null for a unit target
to avoid homing projectiles)

You can set the attack of units with the abilities to whatever you want
though it'd be helpful if it gave some indication of purpose
such as DPS - it may also be an idea to use gameplay constants to make
their attack type always deal 0 damage incase of any worries



  • EFB Setup
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set EFB_Ability = Enchanted Fire Bolt
      • Set EFB_LaunchHeight = 50.00
      • -------- Set up values that are constant throughought levelling --------
      • -------- Use an new loop for every set of Masters/Slaves you need --------
      • -------- This keeps them all in a linear order --------
      • -------- Master 1 --------
      • Set MPS_ProjectileAttackType[0] = Spells
      • Set MPS_ProjectileDamageType[0] = Normal
      • Set MPS_ProjectileAcceleration[0] = 0.75
      • Set MPS_ProjectileTurnRate[0] = 15.00
      • Set MPS_ProjectileTurnEfficiency[0] = 0.00
      • Set MPS_ProjectileTargetAimOffset[0] = 0.00
      • Set MPS_ProjectileManaDamage[0] = 0.00
      • Set MPS_ProjectileModel[0] = war3mapImported\dummy.mdl
      • Set MPS_ProjectileDeathModel[0] = war3mapImported\dummy.mdl
      • Set MPS_ProjectileModelScale[0] = 1.00
      • Set MPS_ProjectileAOE[0] = 0.00
      • Set MPS_ProjectileHealthDamage[0] = 0.00
      • -------- Slaves --------
      • Set MPS_SlaveModel[0] = Abilities\Weapons\PhoenixMissile\Phoenix_Missile.mdl
      • Set MPS_SlaveDeathModel[0] = war3mapImported\dummy.mdl
      • Set MPS_SlaveModelScale[0] = 0.40
      • Set MPS_SlaveAOE[0] = 100.00
      • Set MPS_SlaveAttackType[0] = Spells
      • Set MPS_SlaveDamageType[0] = Normal
      • Set MPS_SlaveManaDamage[0] = 0.00
      • Set MPS_SlaveOffset[0] = 100.00
      • Set MPS_SlavePull[0] = 0.00
      • Set MPS_SlaveSpin[0] = 0.00
      • -------- Set up all values that change based on level and register the Ammo used by the ability --------
      • For each (Integer EFB_Loop) from 1 to 3, do (Actions)
        • Loop - Actions
          • Set EFB_Real = (Real(EFB_Loop))
          • -------- First Master Projectile --------
          • Set MPS_ProjectileName[0] = (EFBMF + (String(EFB_Loop)))
          • Custom script: call MPS_RegisterAmmo(0)
          • -------- Slave projectiles --------
          • Set MPS_SlaveName[0] = (EFBS + (String(EFB_Loop)))
          • Set MPS_SlaveHealthDamage[0] = (75.00 x (Real(EFB_Loop)))
          • Custom script: call MPS_RegisterSlave(0)
          • -------- Spell Level Data --------
          • Set EFB_SlaveCount[EFB_Loop] = 6
          • Set EFB_ExplosionCount[EFB_Loop] = 18
          • Set EFB_RingCount[EFB_Loop] = EFB_Loop
          • Set EFB_RingIncrement[EFB_Loop] = 150.00
          • Set EFB_Pitch[EFB_Loop] = 0.80
          • Set EFB_LaunchPower[EFB_Loop] = 140.00
      • -------- Master 2 --------
      • Set MPS_ProjectileModel[0] = Abilities\Weapons\PhoenixMissile\Phoenix_Missile.mdl
      • Set MPS_ProjectileDeathModel[0] = Abilities\Spells\Other\Incinerate\FireLordDeathExplode.mdl
      • Set MPS_ProjectileModelScale[0] = 0.40
      • Set MPS_ProjectileAOE[0] = 100.00
      • -------- Set up level data for the second master --------
      • For each (Integer EFB_Loop) from 1 to 3, do (Actions)
        • Loop - Actions
          • -------- Second Master Projectile --------
          • Set MPS_ProjectileName[0] = (EFBMS + (String(EFB_Loop)))
          • Set MPS_ProjectileHealthDamage[0] = (75.00 x (Real(EFB_Loop)))
          • Custom script: call MPS_RegisterAmmo(0)
      • -------- Find the Index number of the registered Ammo --------
      • -------- Doing this allows multiple abilities to be used --------
      • -------- in one map without worrying about index numbers --------
      • Custom script: set udg_EFB_Ammo[0] = MPS_GetAmmoByName("EFBMF1") - 1
      • Custom script: set udg_EFB_Ammo[1] = MPS_GetAmmoByName("EFBMS1") - 1
      • Custom script: set udg_EFB_Ammo[2] = MPS_GetSlaveByName("EFBS1") - 1
      • -------- Set up event listener --------
      • Trigger - Add to EFB Impact <gen> the event (Game - MPS_ACTIVE_EVENT becomes Equal to MPS_MASTER_CRASH)


  • EFB Launch
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to EFB_Ability
    • Actions
      • -------- Gather data for projectiles --------
      • Set EFB_u = (Triggering unit)
      • Set EFB_Int = (Level of Enchanted Fire Bolt for EFB_u)
      • Set EFB_Real = ((Current flying height of EFB_u) + EFB_LaunchHeight)
      • Custom script: set udg_EFB_X = GetSpellTargetX()
      • Custom script: set udg_EFB_Y = GetSpellTargetY()
      • Custom script: set udg_EFB_X2 = GetUnitX(udg_EFB_u)
      • Custom script: set udg_EFB_Y2 = GetUnitY(udg_EFB_u)
      • Custom script: set udg_EFB_Angle = Atan2(udg_EFB_Y - udg_EFB_Y2, udg_EFB_X - udg_EFB_X2)
      • -------- Create Projectiles --------
      • Custom script: call MPS_CreateProjectile(udg_EFB_u, udg_EFB_Ammo[0] + udg_EFB_Int, udg_EFB_LaunchPower[udg_EFB_Int], udg_EFB_Pitch[udg_EFB_Int], udg_EFB_Angle, udg_EFB_X2, udg_EFB_Y2, udg_EFB_Real, null, udg_EFB_X, udg_EFB_Y)
      • -------- After a projectile has been made with MPS_CreateProjectile --------
      • -------- MPS_ACTIVE_NODE is set to the index of the new projectile --------
      • Set EFB_AbilityLevel[MPS_ACTIVE_NODE] = EFB_Int
      • Set EFB_Unit[MPS_ACTIVE_NODE] = EFB_u
      • Custom script: call MPS_AddSlaveProjectiles(udg_MPS_ACTIVE_NODE, udg_EFB_Ammo[2] + udg_EFB_Int, udg_EFB_SlaveCount[udg_EFB_Int])
      • -------- The same applies to slaves but will only get the last slave --------
      • -------- So use the Slave Create event for this --------


  • EFB Impact
    • Events
    • Conditions
    • Actions
      • -------- Check the ability level of the dying projectile --------
      • Set EFB_Int = EFB_AbilityLevel[MPS_ACTIVE_NODE]
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • MPS_AmmoType[MPS_ACTIVE_NODE] Equal to (EFB_Ammo[0] + EFB_Int)
        • Then - Actions
          • -------- Ammo type matches up - it's the one belonging to this ability --------
          • Set EFB_X2 = MPS_ProjectileX[MPS_ACTIVE_NODE]
          • Set EFB_Y2 = MPS_ProjectileY[MPS_ACTIVE_NODE]
          • Custom script: set udg_EFB_Real2 = (bj_PI * 2) / udg_EFB_ExplosionCount[udg_EFB_Int]
          • Set EFB_Real = 0.00
          • For each (Integer EFB_Loop2) from 1 to EFB_RingCount[EFB_Int], do (Actions)
            • Loop - Actions
              • Set EFB_Real = (EFB_Real + EFB_RingIncrement[EFB_Int])
              • Set EFB_Angle = 0.00
              • For each (Integer EFB_Loop) from 1 to EFB_ExplosionCount[EFB_Int], do (Actions)
                • Loop - Actions
                  • Set EFB_Angle = (EFB_Angle + EFB_Real2)
                  • Custom script: set udg_EFB_X = udg_MPS_ProjectileX[udg_MPS_ACTIVE_NODE] + udg_EFB_Real * Cos(udg_EFB_Angle)
                  • Custom script: set udg_EFB_Y = udg_MPS_ProjectileY[udg_MPS_ACTIVE_NODE] + udg_EFB_Real * Sin(udg_EFB_Angle)
                  • Custom script: call MPS_CreateProjectile(udg_EFB_Unit[udg_MPS_ACTIVE_NODE], udg_EFB_Ammo[1] + udg_EFB_Int, udg_EFB_LaunchPower[udg_EFB_Int], udg_EFB_Pitch[udg_EFB_Int], udg_EFB_Angle, udg_EFB_X2, udg_EFB_Y2, udg_EFB_LaunchHeight, null, udg_EFB_X, udg_EFB_Y)
        • Else - Actions


  • Variable Creator
    • Events
    • Conditions
    • Actions
      • Set EFB_Ability = Enchanted Fire Bolt
      • Set EFB_AbilityLevel[0] = 0
      • Set EFB_Ammo[0] = 0
      • Set EFB_Angle = 0.00
      • Set EFB_ExplosionCount[0] = 0
      • Set EFB_Int = 0
      • Set EFB_LaunchPower[0] = 0.00
      • Set EFB_Loop2 = 0
      • Set EFB_Pitch[0] = 0.00
      • Set EFB_Real2 = 0.00
      • Set EFB_RingCount[0] = 0
      • Set EFB_RingIncrement[0] = 0.00
      • Set EFB_SlaveCount[0] = 0
      • Set EFB_Unit[0] = No unit
      • Set EFB_X = 0.00
      • Set EFB_X2 = 0.00
      • Set EFB_Y = 0.00
      • Set EFB_Y2 = 0.00
      • Set EFB_u = No unit


Images

Demo1

Demo2

Demo3

Demo4

Demo5

Enchanted Fire Bolt


Demo1.jpg


Demo2.jpg


Demo3.jpg


Demo4.jpg


Demo5.jpg


Enchanted%20Fire%20Bolt.jpg


Changelog


-=V1.02b=-
- Restructured linked list to reduce variable usage and improve efficiency
-=V1.02=-
- Added Event Variables so that the system now properly supports making your own abilities and is GUI-Friendly Huzzah!
- Added a recreation of Enchanted Fire Bolt as an example spell for this system (Still GUI)
- Fixed a physics bug by throwing more Maths at the problem until it worked
-=V1.01=-
- Made the program more efficient when no slave projectiles are presently in use
-=V1.00=-
- Initial Upload

[tr]

Keywords:
Master, Slave, Projectile, System, Salvo, Attack, Replace, Physics, Eye Candy, Missile, Dynamic, 3D, Orbit, Projectile.
Contents

Master Projectile System V1.02b (Map)

Reviews
20:20, 11th Feb 2016 Criticism: Great projectile system, which I honestly while reading didn't fully understand from an aspect of math. Particularly when it comes to multiple arctangent calculations. The test map works great. Good job on...

Moderator

M

Moderator

20:20, 11th Feb 2016

Criticism:

Great projectile system, which I honestly while reading didn't fully understand from an aspect of math.
Particularly when it comes to multiple arctangent calculations.

The test map works great. Good job on documentation and presentation.
I neither found obvious flaws nor handle leaks.
You could null the array handles upon removing nodes from the linked list.
Approved with 5/5.

 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Just seeing the screenshot, my first thought is that will kill my netbook in performance due to the eye candy. That's a lot of sexiness in one picture.

Moving on, I can testify I have no idea about the formula you're using (so many Atan2 calls), so I wouldn't know if any of the math operators would be better calculated at launch and remain static throughout the flight. However, I do know that factoring in Z for collision is prone to cause desyncs with Mac users or those with different graphics settings. Just compare the 2D axis, and at the very least it will help performance.

I'm not sure why you're using strings to lookup projectiles and then doing an O(n) search to find the right one. How often are those searches done? If it's only done once on launch, maybe it's not so bad. However, you usually want to target best performance with these systems so using a hashtable with StringHash could work best.
 
Bribe said:
However, I do know that factoring in Z for collision is prone to cause desyncs with Mac users or those with different graphics settings. Just compare the 2D axis, and at the very least it will help performance.
I'll add in an optional version which does this - I'm assuiming this is applied for collision with units or would it also apply to when a unit is moving through the terrain? (Never used a Mac)
Bribe said:
I'm not sure why you're using strings to lookup projectiles and then doing an O(n) search to find the right one. How often are those searches done? If it's only done once on launch, maybe it's not so bad. However, you usually want to target best performance with these systems so using a hashtable with StringHash could work best.
The string lookups are mostly for usability when setting up attacks - which in the majority of cases would run at the start of the map and never run again, the only exception would be possibly MPS_GetAttackByName() which I run once each launch in the test map but with good usage that too could run at the start by swapping the values out for index numbers at map initialization - I'll probably make the change to do that at some point or simply write up an explanation of how to do it since it's really simple (essentially setting up an array of attacks udg_Attack[0] = GetAttackByName("Attack1"), udg_Attack[1] = GetAttackbyName("Name2") and then using those index numbers to refer to them instead for each launch (It'd also be harder to perform other searches given that there's no guarantee it's arranged in any kind of order and assuring that introduces other inefficiencies, it also allows you to register things via the appropriate register function e.g. "MPS_RegisterAmmo(GetAmmoByName("ExistingAmmo"))" in order to update existing records or make it if it hasn't been yet though I imagine that'd see negligible use - Done

Flux said:
Why use Pow(base, exponent) though?
Mostly readability - I avoid using it in critical locations but some of the lines would get absurdly long (again this is something I may change once I've rested a bit more) - Done
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
KillUnit comes with one big downside. Units which have 'Aloc'
start to appear in GroupEnumUnitsInRange loops once you used KillUnit on them.
Basically you start to enumerate your own dummies.
Applying timed life should solve that problem.

set udg_MPS_ProjectilePlayer[Node] = GetTriggerPlayer() is that line correct?
The projectile system runs as action function to a player unit event trigger?
 
Originally it was designed that way - though complications ensued with doing that when considering I was attempting to make it work for the vanilla editor (duplication of (constant) functions as both systems needed the same ones among other things) The system can still be split (simply remove all functions with Slave in their name as well as the "call UpdateSlaves()" line at the end of the projectile loop and you keep the core projectile system minus the slaves (which is necessarily at the end of the loop of the base projectile system in order to allow the slaves to work without generating lag)

You can technically mount the slave system onto any vector based projectile system by doing as described above except move them (though a lot of variable renaming would be in order as well as bringing across some functions)
 
If you don't use any slave projectiles then the loss is one function call, the 14 local variables being created (2 of them are assigned values) and a loop that's exited after 2 lines per iteration of the loop function, similarly when creating the projectile a similar things occurs (though fewer lines) - though now that I think about it I can easily reduce both losses when there are no slave projectiles in exchange for slowing the system for when there are slave projectiles by introducing if statements before the function calls (1 extra line for each loop cycle and created projectile when there are slaves) granted I've marked the slave update for destruction in the event that you -aren't- using slaves since it's kind of the point of the system but I may update this to be the case anyway

eitherway to answer your question, I've tested it with 250 projectiles made simultaneously (partially off-screen to avoid render lag) them firing and in-flight generates no lag (though hitting the ground 250 times at the same time does due to all of them damaging and being recycled at once, and rendering lag is inevitable with that many projectiles), I've yet to locate the exact limit

Edit:
- 400 now generates lag on launch, still no lag while moving off screen though
- 800 same result
Should be noted though this depends on your CPU, so I'm not sure what the results would be for anybody else

Edit2: Changes made, there's now very little (almost negligible) performance difference if you aren't using any slave projectiles
 
Oh, wow. 800? Phew, that's impressive. I'm definitely going to try using this system for my missile needs. As awesome as BPower's Missile is (sorry BP), I would love to have a system that handles homing missiles with sideways curves and arcs. With that said, Master Projectile System can handle point-target missiles... right?

Out of curiosity, is it at all possible to have multiple delayed missile launches integrated in the system without [much] performance loss?

Also about the Force Attack trigger, it doesn't catch the damage, does it? Otherwise that would be a great way to completely customise basic attacks.

PS: How do you catch when the missile reaches destination and/or collision? Wait... does it have a collision system?
 
- Yes it handles point targets however it assumes point targets are on the ground while giving a unit target gives 3D homing (the system functions via implementing homing projectiles for all projectiles, so allowing 3D aerial homing could result in them floating about forever), this is how it's able to handle so many projectiles while still permitting the type of arcs it can generate
- Yes you can fire multiple delayed launches without any significant performance loss
- You can catch the damage via using gameplay constants and setting the attack type to deal 0 damage to all armour types, then use that attack type for all units
- It has a 3D collision system, if you'd like to implement crash detection (hitting target or the ground for spells) you need to modify MPS_HitTarget and MPS_Move functions to set an event variable to the projectile type ID (set the event to udg_MPS_AmmoType[Node] to get the ID) in the event that they would return true. I'll probably tie this into the system at some point
 
- Wait, I'm confused - does that mean missiles cannot home in on air targets? What about point targets that end in the air (like for an aoe aerial barrage)?

- is the multiple delayed launch integrated into the system? As in, is there a function call specifically for multiple delayed launches or is it just a matter of calling the missile launch multiple times by myself?

- It's kind of the same deal as using a DDS to catch the initial damage and nullifying it then?

- I would mostly use collision to imitate line-attacks. Something I would like to do with BPower's Missile is to create a line attack with an expanding collision size to create some sort of cone attack.
 
- It means missiles must home in on air targets and can't be fired at aerial points - that is to say if they homed in on a point in the air they'd fly around it forever because there may be nothing to hit at the target point
- You'd have to call the launch multiple times by yourself
- The way I did it in the test map was using channel and giving it the same order ID as attacking - whenever a unit attacks it tries to cast the spell instead (unless the spell is on cool down) it's not the most elegant of methods but it functions well (it's still possible for the unit to attack normally if ordered more often but by changing gameplay constants that damage is zero - it was mostly done for demonstration purposes rather than being an optimal solution, implementing a DDS designed for it would be better)
- Not sure if this is relevant to what you ment but with the ammo type all other properties of the ammo could be fetched including AOE and things like that (setting a second variable to the current node would also make all projectile-specific properties accessible
 
- Maybe you could get around that by giving missiles with aerial points as destination a timed life so they don't fly around it forever? Dunno.

- I see

- Guess i'll stick with a DDS then :p

- What do you mean by Ammo, btw? I see this used in your documentation but I'd not sure if it means the missile itself or something else (number of missiles per salvo maybe?). Is that jargon for why 'type' a missile is? Could I perhaps trouble you for an example of what a line/cone attack would be setup using Master Projectile System?
 
- Potentially but it'd be a bit redundant for most uses other than I suppose a sort of air mine

- Ammo (ammunition) are master projectiles which slaves are centered around - when you set up the properties for a master projectile you register it as a type of ammo storing properties like flight pattern (physics), damage types, AOE, etc. (see the sample setups for master projectiles to see what I mean and follow the explanations to follow what each property does if you have trouble) - I called it "Ammo" because it's what Salvos Fire (while salvos can use any amount of this ammunition)

You can disable homing by modifying the physics properties (though it then becomes more difficult to get the projectiles to land in controllable points) though whether or not you needed to do that would depend on what you wanted, I'm not entirely sure what is meant by cone or line, but to get projectiles to land in a sort of u<| shape where u is the origin unit, and | is a line representing the landing points of projectiles you'd fire either multiple salvos of homing missiles simultaneously (multiple missiles hitting the same point) or if in single projectiles you'd not use the salvo system and essentially write a piece of code resembling the salvo's launch projectile function except move the target point as appropriate after each projectile (I apologise if my explanation is a bit vague, I'm not terribly good at conveying these things in words and lack the time to write a full code example)
 
Ah I see what you mean, well you'd add the previously mentioned events code, (putting another one in MPS_Move which would simply occur every time the projectile was moved, then from your trigger with the event becomes equal to the movement value use the node number to the apply your own components (This being the current AOE, e.g. AOE[TempNode] = AOE[TempNode] + growth rate) like with any other system functioning the same way.

I'll add the event values soon in what is probably the most efficient way and add some text to explain this more in an in depth way (also possibly give a spell example) but it'll take some time - it'll probably be more clear then (I'll do this tomorrow after my lectures roughly around 4pm (it's 9:45pm right now)
 
Am I very confused by how to use this system xD

I kinda understand the setups, like you set a string as the name, then setup the rest of they values, and then do call MPS_RegisterAmmo(0) and I guess it takes all those variables setup before it and 'links' them to the last string that was setup? Assuming I'm not entirely wrong, that's where my understanding of the system ends.

So I have a few questions. Some of those I think I know, but I'll ask anyway:
1. What's the difference b/w ActivateAttack and CreateProjectile?
2. Is udg_MPS_MASTER_HIT for collision or when a missile applies damage?
3. How do I check for collision and setup the size of the collision?
4. How do I prematurely terminate a missile? Do I just kill it?
5. TurnRate and TurnEfficiency - does this affect the missile's path curvature or just it's facing angle?
 
MPS_RegisterAmmo(0) takes the setup variables (with index[0]) and saves it to a new index (so the first time you call it they're given the index 1, the nth time they have index n+1, you can call MPS_RegisterAmmo(n) where n is the index to replace - the purpose of names is so you can update specific masters, slaves, salvos, attacks by using their Get[type]ByName("name") functions to locate and update that specific index and also so you can create abilities via saving this index number and referencing it when using that ability (as in my EFB Example)

1) Activate attack uses the Master Salvo System to launch a specific salvo using a specific master projectile and a specific slave, Create Projectile creates 1 Master projectile using the passed Master (Ammo) type

2) as explained in the events explanation trigger
- udg_MPS_MASTER_CRASH: Fires when a master projectile hits the ground
- udg_MPS_MASTER_HIT: Fires when a master projectile hits a target
the projectile always applies damage regardless of how it dies, HIT means it hit the intended target of the missile (and thus damaged it), CRASH means it hit the terrain on the way to the intended target (and may have damaged nearby enemies)

3) the AOE of the projectile is its collision size (3d collision)

4) killing the missile will do so, however it will not damage anything if you do it, you'd use the udg_MPS_MASTER_UPDATE event to select the missile in-transit

5) The missile facing angle always matches its current curvature (it'd be silly for this to not be the case), anything that affects the facing angle always effects the flight path

- ProjectileTurnRate: This is the amount of momentum the projectile gains each timer
iteration directly in the direction of the target (causing it
to turn)

- ProjectileTurnEfficiency: This is the portion of the net speed of the projectile
which is converted to be in the direction of the target
each step, note though that this results in a fractional
amount where 1.00 = 1/2, 0.5 = 2/3, 1.5 = 3/5.
(essetially TurnEfficiency / (1 + TurnEfficiency) with
this value being high, the projectile will always face
the target

A high TurnEfficiency will result in a straighter Arc (less curvy) and (if accel => 1.00) a fast projectile (it will not change speed as it turns if accel == 1.00) A high Turn rate gives a minimum speed (if accel is < 1.00) and a more curvy arc, it will also cause the projectile to get faster over time (if facing the target and accel => 1.00 or turn rate is very high)

Accel/TurnEfficiency/TurnRate/LaunchPitch/LaunchPower are the main controls for your arc (Pitch and Power given from CreateProjectile or through a salvo)
 
Level 7
Joined
Aug 11, 2010
Messages
269
I feel stupid for asking; but I've read the documentation but it's only served to confuse me further. Perhaps it's due to the fact there isn't any simpler examples in the demo map; but how exactly do I just make a singular projectile with this system? Say for example; I wanted to make a Death Coil projectile that travelled a distance, if it connected with another target it would kill them? I don't quite understand the Servos, the Master, The Slaves, or the Ammo components. Sorry again for asking, but I am just generally lost. I'm really needing a versatile projectile system for GUI (or at least GUI compatible) and this looks REALLY solid, and really customizable. It's just a bit of a steep learning curve since the examples you gave are all very complex in nature.
 
Well the closest example to what you're asking for is the second demo - which fires 5 simple projectiles without any slaves (so 5 basic projectiles) the things you need if you're using all the parts of the system including the salvo system:
A defined master ammo (this being the singular projectile you want to fire), this Ammo in your case should not have any turning-related abilities since you want it to travel in a straight line (all those values should be 0, if you don't want it to change speed then acceleration should also be 0)
A defined salvo where MPS_SalvoMasterAmmoAmount[0] = 1 and MPS_SalvoSlaveAmmoAmount[0] = 0 (this is a salvo where only 1 master projectile is launched and each master projectile has 0 slaves, so just a master projectile) the launch pitch should also be 0, as this will cause the projectile to be fired horizontally
A defined attack which combines the two - where the AttackSalvo is the salvo you defined and the Master ammo is the master ammo you defined, since you have no slaves for that salvo type you can leave the slave type as 0 since none will ever be created.

in simple terms, master ammo defines your central projectiles, slave ammo defines your projectiles that may be around a master, the salvo defines how many masters are launched,in what pattern they are launched and how many slave projectiles surround each master and an attack defines a particular setup of each of those things.
If you want a series of units to all have a singular projectile attack you will only need a singular salvo defined for all of them, if you wanted a series of units that all fired the same master projectile but maybe in different amounts then you'd have one master projectile defined but several different salvos, every unit with a different type of attack will need their attack defined, but units with the exact same attack can share attacks

The event system is fairly simple but I think takes some getting used to: whenever an event is the system occurs a variable (MPS_ACTIVE_EVENT) is set to a certain value, these certain values are defined by several constants such as MPS_MASTER_HIT, this can be detected by real events (sadly GUI requires that you define these events in a different initialization function), in my EFB examples this event is
  • Trigger - Add to EFB Impact <gen> the event (Game - MPS_ACTIVE_EVENT becomes Equal to MPS_MASTER_CRASH)
Which basically reads "When a master projectile crashes (hits the ground) run the trigger "EFB Impact" now of course you don't want to just detect any and all projectiles crashing or hitting their targets, whatever it may be - you want to be able to be sure that the ammo is of the correct type, in you case a death coil type projectile, to that end you can use a condition like this:
  • MPS_AmmoType[MPS_ACTIVE_NODE] Equal to (MY_SPELL_DEATH_COIL_AMMO)
(setting the constant "MY_SPELL_DEATH_COIL_AMMO" to the integer value assigned to your projectile when created (you can find this via MPS_GetAmmoByName("Death Coil") which finds Master Ammo given by a certain name (case sensitive), assuming in this case you named you master Ammo "Death Coil" (for slave ammo this is MPS_GetSlaveByName(String))
MPS_ACTIVE_NODE is the index of the system of the projectile that fired the event which is how you're able to find anything out about it, such as current position, the type of ammo, how fast it was travelling, how much damage it would deal - anything really, this allows you to do other interesting things having triggers run on MPS_MASTER_UPDATE, which would allow you to have individual projectiles deal damage based on say, distance travelled before crashing/hitting

Anyway in the event of an instant kill death coil which travels in a straight line you'd want to run on MPS_MASTER_UPDATE, then check that the type of MPS_ACTIVE_NODE is equal to your death coil projectile type index number, and then check if any units are too close to the projectile, it should be noted that the system is designed primarily for realistic homing-style projectiles so while it can do what you're asking it's a bit of extra work. if any units are too close to the projectile you select them all and then run a kill command, there are multiple ways to then destroy the projectile but there isn't a dedicated MPS_DestroyProjectile(int) function since normally a projectile is only destroyed by crashing or hitting a target (note for something for me to add at somepoint), the simplest and easiest way to kill it is to set the projectile's target to itself at the moment (Set MPS_ProjectileTargetUnit[MPS_ACTIVE NODE] = MPS_Projectile[MPS_ACTIVE_NODE]) which should cause it to instantly detonate except maybe when the projectile is traveling very quickly due to technical reasons, if the projectile doesn't die you increment a distance counter for the projectile on its index (something like DC_TotalDistance[MPS_ACTIVE_NODE] = DC_TotalDistance[MPS_ACITVE_NODE] + distance from where it last was) and if that reaches a certain value or goes over it, kill the projectile anyway.

It'd essentially be an initialization trigger which defines and registers all your components (see the Enchanted fire bolt included as an example) a trigger that runs on the cast of your ability (the same way you do any other spell), use MPS_ActivateAttack() to use your defined attack setup (you need to pass it the attacker, the target (if none then null) and the target point (can be 0,0 if target is not null)) and a trigger that runs on the HIT event (as this only runs when the projectile hits its target), projectiles which only have a target point always run the CRASH event

The system in general has a steep learning curve but offers a lot and is fairly simple after some hands on experience, but it is definitely more suited for homing projectiles rather than projectiles that are supposed to be fired off into the aether, I apologize for the long response but it's somewhat necessary given the complexity of the system
 
Level 7
Joined
Aug 11, 2010
Messages
269
Well the closest example to what you're asking for is the second demo - which fires 5 simple projectiles without any slaves (so 5 basic projectiles) the things you need if you're using all the parts of the system including the salvo system:
A defined master ammo (this being the singular projectile you want to fire), this Ammo in your case should not have any turning-related abilities since you want it to travel in a straight line (all those values should be 0, if you don't want it to change speed then acceleration should also be 0)
A defined salvo where MPS_SalvoMasterAmmoAmount[0] = 1 and MPS_SalvoSlaveAmmoAmount[0] = 0 (this is a salvo where only 1 master projectile is launched and each master projectile has 0 slaves, so just a master projectile) the launch pitch should also be 0, as this will cause the projectile to be fired horizontally
A defined attack which combines the two - where the AttackSalvo is the salvo you defined and the Master ammo is the master ammo you defined, since you have no slaves for that salvo type you can leave the slave type as 0 since none will ever be created.

in simple terms, master ammo defines your central projectiles, slave ammo defines your projectiles that may be around a master, the salvo defines how many masters are launched,in what pattern they are launched and how many slave projectiles surround each master and an attack defines a particular setup of each of those things.
If you want a series of units to all have a singular projectile attack you will only need a singular salvo defined for all of them, if you wanted a series of units that all fired the same master projectile but maybe in different amounts then you'd have one master projectile defined but several different salvos, every unit with a different type of attack will need their attack defined, but units with the exact same attack can share attacks

The event system is fairly simple but I think takes some getting used to: whenever an event is the system occurs a variable (MPS_ACTIVE_EVENT) is set to a certain value, these certain values are defined by several constants such as MPS_MASTER_HIT, this can be detected by real events (sadly GUI requires that you define these events in a different initialization function), in my EFB examples this event is
  • Trigger - Add to EFB Impact <gen> the event (Game - MPS_ACTIVE_EVENT becomes Equal to MPS_MASTER_CRASH)
Which basically reads "When a master projectile crashes (hits the ground) run the trigger "EFB Impact" now of course you don't want to just detect any and all projectiles crashing or hitting their targets, whatever it may be - you want to be able to be sure that the ammo is of the correct type, in you case a death coil type projectile, to that end you can use a condition like this:
  • MPS_AmmoType[MPS_ACTIVE_NODE] Equal to (MY_SPELL_DEATH_COIL_AMMO)
(setting the constant "MY_SPELL_DEATH_COIL_AMMO" to the integer value assigned to your projectile when created (you can find this via MPS_GetAmmoByName("Death Coil") which finds Master Ammo given by a certain name (case sensitive), assuming in this case you named you master Ammo "Death Coil" (for slave ammo this is MPS_GetSlaveByName(String))
MPS_ACTIVE_NODE is the index of the system of the projectile that fired the event which is how you're able to find anything out about it, such as current position, the type of ammo, how fast it was travelling, how much damage it would deal - anything really, this allows you to do other interesting things having triggers run on MPS_MASTER_UPDATE, which would allow you to have individual projectiles deal damage based on say, distance travelled before crashing/hitting

Anyway in the event of an instant kill death coil which travels in a straight line you'd want to run on MPS_MASTER_UPDATE, then check that the type of MPS_ACTIVE_NODE is equal to your death coil projectile type index number, and then check if any units are too close to the projectile, it should be noted that the system is designed primarily for realistic homing-style projectiles so while it can do what you're asking it's a bit of extra work. if any units are too close to the projectile you select them all and then run a kill command, there are multiple ways to then destroy the projectile but there isn't a dedicated MPS_DestroyProjectile(int) function since normally a projectile is only destroyed by crashing or hitting a target (note for something for me to add at somepoint), the simplest and easiest way to kill it is to set the projectile's target to itself at the moment (Set MPS_ProjectileTargetUnit[MPS_ACTIVE NODE] = MPS_Projectile[MPS_ACTIVE_NODE]) which should cause it to instantly detonate except maybe when the projectile is traveling very quickly due to technical reasons, if the projectile doesn't die you increment a distance counter for the projectile on its index (something like DC_TotalDistance[MPS_ACTIVE_NODE] = DC_TotalDistance[MPS_ACITVE_NODE] + distance from where it last was) and if that reaches a certain value or goes over it, kill the projectile anyway.

It'd essentially be an initialization trigger which defines and registers all your components (see the Enchanted fire bolt included as an example) a trigger that runs on the cast of your ability (the same way you do any other spell), use MPS_ActivateAttack() to use your defined attack setup (you need to pass it the attacker, the target (if none then null) and the target point (can be 0,0 if target is not null)) and a trigger that runs on the HIT event (as this only runs when the projectile hits its target), projectiles which only have a target point always run the CRASH event

The system in general has a steep learning curve but offers a lot and is fairly simple after some hands on experience, but it is definitely more suited for homing projectiles rather than projectiles that are supposed to be fired off into the aether, I apologize for the long response but it's somewhat necessary given the complexity of the system

Don't apologize, I appreciate the short response time and the in-depth comment; I'm gonna do some tinkering and see if I can't take what you've said and learn from it, as you've laid it out pretty well I feel. Like I said prior, I've been looking for a semi-versatile missile system since one of my weaknesses with triggers has been dealing with creating my own missiles; I never quite learned how to make them from scratch. (Which is pretty shameful to admit; seeing as it's such a simple and high-in-demand feature)

Idealistically, I'd like to have a system that covers both point-target and unit-target functionality, which as you said -- your system is capable of, with a little bit of extra work. Anywho, I've rambled enough -- thanks again for the hasty and helpful comment. Enjoy yourself a little bit of rep!
 
Level 4
Joined
Jun 21, 2009
Messages
107
Is it possible to use this just for spells and not auto attacks? From my understanding on the documentation, the auto attacks are built into the slavo system. How would I go about using this system exclusively for spells and not for auto attacks?

EDIT: Forgot to mention a few things

1) I tried removing the 3 triggers for attacks (Setup, Force, and Activate)
This stopped the projectiles from firing but there units with Hero attack type are unable to do damage.

2) I also removed the Replace Attack spell but this didn't change anything.

3) I looked into the code to see where Hero type attack is used but I only see normal/spell/chaos attack types so I'm not sure why heroes cannot do damage.

4) Non-hero damage type units can still do damage.
 
Auto attacks are not built into the system, the attack system combines Salvo types, master types and slave types together to form a single "attack" type, the auto-attacks are replaced in the triggers you listed (the triggers in the "replacing regular attacks" section. The Tanks in the test map have "no attack" in that their projectile speed is 0 while not having instant projectiles meaning that they attack but nothing happens every time

For an example of how to use the system for spells look at the example given for "Enchanted fire bolt" - though this example doesn't use the attack system, it'd be a matter of registering Attacks (much like how it is done in the "Setup Attacks" trigger) and then using MPS_ActivateAttack() to use the registered attack in your spell rather than individually creating the projectile and adding slaves to it

The system structure is as follows:

Master Projectile <- Basic Projectile (e.g. "Ice Lance")
Slave Projectile <- Needs to be tied to a master (e.g. "Ice shards")
Salvo System <- Creates multiple Master and Slave projectiles tied to those masters (works purely on numbers) (e.g."4 masters, 5 slaves per master - called "Flurry" or something)
Attack System <- Ties specific Master's and Slaves to a Salvo type (e.g. "Ice Lance" + "Ice Shards" arranged as a "Flurry" -> Attack called "Ice Flurry")
then using the attack by calling (MPS_ActivateAttack(MPS_GetAttackByName("Ice Flurry"), TriggeringUnit, TargetUnit, TargetX, TargetY) and it does the rest for you, creates the master and slave projectiles, arranges them to match the salvo pattern, fires them at the target unit or point (this last line is the only thing that would be in your spell, the other parts would be set up in your spell initialization/configuration)

The auto attack replacement in the test map was just a demonstration of how somebody might want to use the system (i.e. replacing all attacks in their map with fancy 3D projectiles instead of boring normal WC3 ones)
 
Level 4
Joined
Jun 21, 2009
Messages
107
Ah okay, so what I'm understanding is that in the test map, the only thing making the Auto Attacks projectile are those 3 triggers that I mentioned. Every other trigger is there to just build the Master, Slave, and Salvo projectiles which CAN be used for either spells or Auto Attacks.

I guess where I got confused was that when I read the word "attack" I thought it referred to Auto Attacks and not necessarily the Projectile/Slave Ability.

There is still one thing I don't understand: Why none of the Hero Units do no damage. Now, I know you said the tanks and dragon have their projectiles speed set to 0, so they go through the motions of attacking but an attack is never made. This explains why they do no damage. However, the Priestess of the Moon hero already in the test map also does no damage, even though she does fire a projectile. Furthermore, I added melee heroes from the Unit Palette (Paladin Hero and Mountain King) and they do not do any damage. I tried this before and after removing the 3 attack triggers I mentioned in my previous post. Am I doing something incorrectly for this to happen?
 
Level 2
Joined
Aug 30, 2017
Messages
15
after i imported the spell. Theres no damage or maybe its just my error.
 
Last edited:
Top