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

Master Projectile System V1.02b

Submitted by Tank-Commander
This bundle is marked as approved. It works and satisfies the submission rules.
Master Projectile System

Tooltip

Icon
[​IMG]



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

Master Projectile System

Code (vJASS):
////////////////////////////////////////////////////////////////////
//               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                                              //
////////////////////////////////////////////////////////////////////
 


Master Salvo System

Code (vJASS):
////////////////////////////////////////////////////////////////////
//                  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

Setup Explanation

Code (vJASS):

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


Sample Setups

Master Projectiles

  • 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 Projectiles

  • 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

  • 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)


Attacks

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

  • 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


Explaining The Events

Code (vJASS):
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
 


Not Using Salvo

Code (vJASS):
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


Replacing Regular Attacks

Code (vJASS):
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


Enchanted Fire Bolt Code

EFB Setup

  • 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

  • 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

  • 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

  • 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


Changelog

Show

-=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.[/tr]
Contents

Master Projectile System V1.02b (Map)

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

     
  2. Tank-Commander

    Tank-Commander

    Spell Reviewer

    Joined:
    May 26, 2009
    Messages:
    1,543
    Resources:
    44
    Packs:
    1
    Spells:
    41
    Tutorials:
    2
    Resources:
    44
    Since I was very tired when I finished this I won't be at all surprised if it's riddled with inefficiencies which I'll comb though at some point (would be amazing if there weren't any)

    Think this is probably one of my shortest submissions in recent times
     
  3. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    Oh my. That's quite the eye-candy *_*
    I like the fact that it's vanilla JASS. Now, about how performant it is... will keep an eye out for this.
     
  4. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,033
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    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.
     
  5. Arad MNK

    Arad MNK

    Joined:
    Dec 11, 2014
    Messages:
    1,889
    Resources:
    3
    Maps:
    2
    Spells:
    1
    Resources:
    3
    This one is really good.
     
  6. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,334
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Why use Pow(base, exponent) though?
     
  7. Dat-C3

    Dat-C3

    Joined:
    Mar 15, 2012
    Messages:
    2,470
    Resources:
    10
    Models:
    1
    Maps:
    5
    Spells:
    3
    Tutorials:
    1
    Resources:
    10
    Wow, by far one of the best projectile systems I have seen made public in WC3 history... =) Thanks for sharing this and awesome work.

    Though is there options to make it 2D/Mac friendly? More options couldn't hurt, however it's already really good as it is. One question, how many projectiles/dummy units can it handle?
     
  8. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,033
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    The only objective answer is "as many until the thread crashes". Subjectively, it depends on the CPU.
     
  9. Tank-Commander

    Tank-Commander

    Spell Reviewer

    Joined:
    May 26, 2009
    Messages:
    1,543
    Resources:
    44
    Packs:
    1
    Spells:
    41
    Tutorials:
    2
    Resources:
    44
    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)
    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

    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: Jan 25, 2016
  10. Almia

    Almia

    Joined:
    Apr 24, 2012
    Messages:
    4,861
    Resources:
    35
    Spells:
    30
    Tutorials:
    4
    JASS:
    1
    Resources:
    35
    I hate you and your maths
     
  11. BPower

    BPower

    Joined:
    Mar 18, 2012
    Messages:
    1,741
    Resources:
    21
    Spells:
    15
    Tutorials:
    1
    JASS:
    5
    Resources:
    21
    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?
     
  12. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,149
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    Is there a reason that this can't be two separate resources, a lightweight projectile system as the core and then a master-slave system riding on top of it?

    Call me a fan of modularity.
     
  13. Tank-Commander

    Tank-Commander

    Spell Reviewer

    Joined:
    May 26, 2009
    Messages:
    1,543
    Resources:
    44
    Packs:
    1
    Spells:
    41
    Tutorials:
    2
    Resources:
    44
    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)
     
  14. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    So what's the performance hit with this system? Assuming I don't use any slave missiles, how many can it handle at once?
     
  15. Tank-Commander

    Tank-Commander

    Spell Reviewer

    Joined:
    May 26, 2009
    Messages:
    1,543
    Resources:
    44
    Packs:
    1
    Spells:
    41
    Tutorials:
    2
    Resources:
    44
    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
     
  16. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    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?
     
  17. Tank-Commander

    Tank-Commander

    Spell Reviewer

    Joined:
    May 26, 2009
    Messages:
    1,543
    Resources:
    44
    Packs:
    1
    Spells:
    41
    Tutorials:
    2
    Resources:
    44
    - 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
     
  18. Spellbound

    Spellbound

    Joined:
    Jan 9, 2005
    Messages:
    1,953
    Resources:
    15
    Skins:
    5
    Spells:
    9
    JASS:
    1
    Resources:
    15
    - 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.
     
  19. Tank-Commander

    Tank-Commander

    Spell Reviewer

    Joined:
    May 26, 2009
    Messages:
    1,543
    Resources:
    44
    Packs:
    1
    Spells:
    41
    Tutorials:
    2
    Resources:
    44
    - 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