```
////////////////////////////////////////////////////////////////////
// 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 //
////////////////////////////////////////////////////////////////////
```