scope MagnumBarrage
/* Magnum Barrage v1.1
Copy:
Imports
- dummy.mdx
Objects
- dummy unit
- pause ability
- magnum barrage ability
Requires:
Magtheridon
- RegisterPlayerUnitEvent http://hiveworkshop.com/forums/showthread.php?t=203338
Nestharus
- Alloc https://github.com/nestharus/JASS/tree/master/jass/Systems/Alloc
- SharedList https://github.com/nestharus/JASS/tree/master/jass/Data%20Structures/SharedList
- ErrorMessage https://github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
Cohadar
- TT http://www.thehelper.net/threads/timer-ticker.68189/
TriggerHappy
- TimedHandles http://www.wc3c.net/showthread.php?t=105456
Bribe
- SpellEffectEvent http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
- Table http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
Changelog:
v1.0
- Initial upload
v1.1
- Code minor cleanups
- Code Documentation
- Now Requires TimedHandles to make sure missiles don't bug out groupEnum
- Custom FilterUnit function for customizable filter
Configure:
ABIL_ID to magnum barrage ability id
PAUSE_ABIL_ID to pause ability id
DUMMY_ID to dummy unit id
PAUSE_ABIL_ORDER_STRING is the order string for PAUSE_ABIL_ID
CHARGE_FX are the offsets for the charge fx, because it the fx is attached toa dummy unit, you have to custom position it with each unit.
MISSILE_Z is the height of the missile.
MISSILE_FX is the string path of the effect missiles take
CHARGE_FX is the string path of the effect used to charge up the ability
EXPLODE_FX is the string path of the explosion effect
LOAD_PER_SECOND is how many missiles the barrage loads up per second of charging
FIRE_PER_SECOND is how many missiles are discharged per second upon releasing
MISSILE_SIZE is how big the missiles are
COLLISION_AOE is how big the missile collision radius is
CAST_AOE is the aoe within missiles should be fired at
SPLASH_AOE is how much area the damage should splash within
SPLASH_DAMAGE is the ratio of damage that should be dealt
SPEED is how fast the missile moves
DAMAGE is how much damage is dealt upon collision
STATS is how much stats is factored into the damage formula
FILTER_UNIT what kind of units you want to target
** by default filters out alive enemy units, then proceeds to check your filter **
*/
globals
public constant integer ABIL_ID = 'A000'
public constant integer PAUSE_ABIL_ID = 'A001'
public constant integer DUMMY_ID = 'e001'
public constant string PAUSE_ABIL_ORDER_STRING = "carrionswarm"
public constant real CHARGE_FX_X = 60.
public constant real CHARGE_FX_Y = 60.
public constant real CHARGE_FX_Z = 60.
public constant real MISSILE_Z = 60.
public constant string MISSILE_FX = "Abilities\\Weapons\\SearingArrow\\SearingArrowMissile.mdl"
public constant string CHARGE_FX = "Abilities\\Spells\\Orc\\Bloodlust\\BloodlustTarget.mdl"
public constant string EXPLODE_FX = "abilities\\weapons\\catapult\\catapultmissile.mdl"
endglobals
private constant function LOAD_PER_SECOND takes integer i returns real
return 6 + 1.5*i
endfunction
private constant function FIRE_PER_SECOND takes integer i returns real
return 12.
endfunction
private constant function MISSILE_SIZE takes integer i returns real
return .8 + .35*i
endfunction
private constant function COLLISION_AOE takes integer i returns real
return 100. + 20*i
endfunction
private constant function CAST_AOE takes integer i returns real
return 100 + 100.*i
endfunction
private constant function SPLASH_AOE takes integer i returns real
return 100 + 70.*i
endfunction
private constant function SPLASH_DAMAGE takes integer i returns real
return .65
endfunction
private constant function SPEED takes integer i returns real
return 1000 +400.*i
endfunction
private constant function DAMAGE takes integer i, integer stats returns real
return 5.*i + stats*i*.35
endfunction
private constant function STATS takes integer str, integer agi, integer int returns integer
return agi
endfunction
private function FilterUnit takes unit u returns boolean
return IsUnitType(u, UNIT_TYPE_HERO) or IsUnitType(u, UNIT_TYPE_GROUND)
endfunction
// End of COnfigurables
// ================================================================================
public struct Magnum extends array
implement SharedList
unit cast // Casting Unit
real dmg // Damage Dealt upon Explosion
unit mis // Missile Unit
effect fx // Effect of charging model
real currentDist // Distance curently traveled
real splashAoe // Aoe of Explosion
real splashDmg // Splash Damage Factor
real speed // Speed of missile
real finalDist // Maximum Distance
real collisionSize // Collision Size
real cos // Cos of angle
real sin // Sin of angle
static group g = CreateGroup()
static unit fog = null
static player filterPlayer = null
static thistype l
static timer tmr = CreateTimer()
static real period = 0.025
// By default pre filters alive, enemy units
static method filter takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(), filterPlayer) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) and GetWidgetLife(GetFilterUnit()) > .405
endmethod
// Explode method separated to allow flexible explosions
// Can explode with or without a target
method explode takes unit hitTarget returns nothing
// Set the effect coordinates by default to the location of the missile
local real sx = GetUnitX(.mis)
local real sy = GetUnitY(.mis)
// First of Group loop to deal damage to all filtered units
set filterPlayer = GetOwningPlayer(.cast)
call GroupEnumUnitsInRange(g, GetUnitX(.mis), GetUnitY(.mis), .splashAoe, function thistype.filter)
set fog = FirstOfGroup(g)
loop
exitwhen fog == null
// For now only affect non target units
if (fog != hitTarget and FilterUnit(fog)) then
call UnitDamageTarget(.cast, fog, .dmg *.splashDmg, false, false, null, null, null)
endif
call GroupRemoveUnit(g, fog)
set fog = FirstOfGroup(g)
endloop
// If there is a target to explode on, change the effect coordinates and deal full damage to the target
if (hitTarget != null) then
call UnitDamageTarget(.cast, hitTarget, .dmg, false, false, null, null, null)
set sx = GetUnitX(hitTarget)
set sy = GetUnitY(hitTarget)
endif
// Add explosion effect, remove missile, null handles and remove node
call DestroyEffect(AddSpecialEffect(EXPLODE_FX, sx, sy))
call RemoveUnitTimed(.mis, 1.0)
call DestroyEffect(.fx)
set .fx = null
set .mis = null
set .cast = null
call .remove()
endmethod
// Separated a method to check for target for better readability - but probably could be inlined
method checkTarget takes nothing returns boolean
// First of Group to check for a valid target
set filterPlayer = GetOwningPlayer(.cast)
call GroupEnumUnitsInRange(g, GetUnitX(.mis), GetUnitY(.mis), .collisionSize, function thistype.filter)
set fog = FirstOfGroup(g)
loop
exitwhen fog == null
// Check if unit is valid, if so clear the group and exit the loop
if (fog != null and FilterUnit(fog)) then
call .explode(fog)
call GroupClear(g)
set fog = null
return true
// Else keep checking
else
call GroupRemoveUnit(g, fog)
set fog = FirstOfGroup(g)
endif
endloop
return false
endmethod
// Periodic Method
static method onLoop takes nothing returns nothing
local thistype this = l.first
local thistype nn
// Loop through every node in the list
loop
// Safety first!
exitwhen this == l.sentinel
set nn = .next
// If the next movement is the last, explode with no target
if (.currentDist + .speed > .finalDist) then
// Set unit to accurate final coordinates
call SetUnitX(.mis, GetUnitX(.mis)+ (.finalDist - .currentDist) * .cos)
call SetUnitY(.mis, GetUnitY(.mis)+ (.finalDist - .currentDist) * .sin)
// If there are no valid targets, explode without one
if (not .checkTarget()) then
call .explode(null)
endif
else
// Move coordinates and update distance traveled info
call SetUnitX(.mis, GetUnitX(.mis)+ (.speed) * .cos)
call SetUnitY(.mis, GetUnitY(.mis)+ (.speed) * .sin)
set .currentDist = .currentDist + .speed
// Also check for a valid target
call .checkTarget()
endif
set this = nn
endloop
if (l.first == l.sentinel) then
call PauseTimer(tmr)
endif
endmethod
// Method used to create, setup and launch missiles
static method fireAt takes unit caster, real dmg, real x, real y, real sx, real sy, integer lvl, /*
*/real speed, real colSize, real splDmg, real splAoe, real size returns nothing
// Some simple math
local real yy = sy - y
local real xx = sx - x
local real direction = Atan2(yy, xx)
// New node
local thistype this = l.enqueue()
// Setup variables
set .cast = caster
set .dmg = dmg
set .collisionSize = colSize
set .splashDmg = splDmg
set .splashAoe = splAoe
set .cos = Cos(direction)
set .sin = Sin(direction)
set .mis = CreateUnit(GetOwningPlayer(.cast), DUMMY_ID, x, y, direction*bj_RADTODEG)
call SetUnitFlyHeight(.mis, MISSILE_Z, 0.0)
set .fx = AddSpecialEffectTarget(MISSILE_FX, .mis, "origin")
call SetUnitScale(.mis, size, size, size)
set .finalDist = SquareRoot(xx*xx+yy*yy)
set .currentDist = 0.0
set .speed = speed
if (l.first == this) then
call TimerStart(tmr, period, true, function thistype.onLoop)
endif
endmethod
// onInit list creation
static method onInit takes nothing returns nothing
set l = create()
endmethod
endstruct
public struct Data extends array
implement Alloc
boolean stop // Stop charging, start firing
unit cast // Caster unit
unit fx // Charge effect dummy unit
effect fxx // FX model attached to dummy unit
real sx // Spell Starting LocationX
real sy // Spell Starting LocationY
real aoe // Aoe of Barrage
real dmg // Damage
real speed // Misile Speed
real missileSize // Missile Size
real collisionSize // Missile Collision Size
real splashDmg // Splash Damage Factor
real splashAoe // Splash Aoe
real lps // Load Per Second
real fps // Fire Per Second
real time // Time Charged
integer lvl // Ability Level
static Table tb
// Method for rapid firing missiles until charge duration runs out
static method rapidFire takes nothing returns boolean
// Get Index
local thistype this = TT_GetData()
local real rx
local real ry
local real rd
local real rr
// If there is enough charge time keep firing
if (.time > 0) then
set rr = GetRandomReal(0, 360) * bj_DEGTORAD
set rd = GetRandomReal(0, .aoe/2.)
set rx = .sx + rd * Cos(rr)
set ry = .sy + rd * Sin(rr)
call SetUnitAnimation(.cast, "attack")
call Magnum.fireAt(.cast, .dmg, GetUnitX(.fx),GetUnitY(.fx), rx, ry, .lvl, .speed, .collisionSize, .splashDmg, .splashAoe, .missileSize)
// Else stop firing, clear/null handles, reset everything and stop timer
else
call UnitRemoveAbility(.cast, PAUSE_ABIL_ID)
call KillUnit(.fx)
call DestroyEffect(.fxx)
call tb.integer.remove(GetHandleId(.cast))
call SetUnitPropWindow(.cast, GetUnitDefaultPropWindow(.cast) * bj_DEGTORAD)
set .fx = null
set .fxx = null
set .cast = null
call .deallocate()
return true
endif
// Remove timed based on fire rate
set .time = .time - .fps
// Set charge effect model unit scale to time, so as spell runs the unit gets smaller
call SetUnitScale(.fx, .time, .time, .time)
return false
endmethod
// Function that takes care of units while they are charging
static method charging takes nothing returns boolean
local thistype this = TT_GetData()
// If unit should stop charging, starting rapid firing at fire rate period and stop this timer
// Also the "stop" order sometimes disallows the pause ability from being added, so by adding another command here you can
// make sure the unit will be paused properly.
if (.stop) then
call IssueImmediateOrder(.cast, PAUSE_ABIL_ORDER_STRING)
call TT_StartEx(function thistype.rapidFire, this, .fps)
return true
endif
// Increase the charge duration and set the charge effect model unit scale
set .time = .time + .lps
call SetUnitScale(.fx, .time, .time, .time)
return false
endmethod
// Function fired on cast
static method onCast takes nothing returns nothing
// Allocation and some pre instance variables
local thistype this = allocate()
local unit u = GetTriggerUnit()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real dir = Atan2(GetSpellTargetY()-y,GetSpellTargetX()-x)
local integer lvl = GetUnitAbilityLevel(u, ABIL_ID)
local integer stats = STATS(GetHeroStr(u, true), GetHeroAgi(u, true), GetHeroInt(u, true))
// Setup some variables, precalculate and save all numbers so that we don't have to re calculate them later during
// missile creation
call SetUnitPropWindow(u, 0.0)
set tb.integer[GetHandleId(u)] = this
set .stop = false
set .cast = u
set .fx = CreateUnit(GetOwningPlayer(.cast), DUMMY_ID, x+60*Cos(dir), y+60*Sin(dir), dir*bj_RADTODEG)
call SetUnitFlyHeight(.fx, CHARGE_FX_Z, 0.0)
set .fxx = AddSpecialEffectTarget(CHARGE_FX, .fx, "origin")
call SetUnitScale(.fx, 0, 0, 0)
set .sx = GetSpellTargetX()
set .sy = GetSpellTargetY()
set .dmg = DAMAGE(lvl, stats)
set .aoe = CAST_AOE(lvl)
set .lps = 1./LOAD_PER_SECOND(lvl)
set .fps = 1./FIRE_PER_SECOND(lvl)
set .speed = SPEED(lvl) * Magnum.period
set .missileSize = MISSILE_SIZE(lvl)
set .collisionSize = COLLISION_AOE(lvl)
set .splashDmg = SPLASH_DAMAGE(lvl)
set .splashAoe = SPLASH_AOE(lvl)
set .time = 0.0
set .lvl = lvl
// Start the charging function loop
call TT_StartEx(function thistype.charging, this, .lps)
set u = null
endmethod
// When spell is canceled or finished
static method onCancel takes nothing returns nothing
local unit u = GetTriggerUnit()
local thistype this = tb.integer[GetHandleId(u)]
// If canceld ability is Magnum Barrage, unit has a valid instance, and said instance has not started firing yet...
if GetSpellAbilityId() == ABIL_ID or (this != 0 and not .stop) then
// Stop charging, start firing and pause unit
// Note that if you stop your caster by ordering "stop" then the pause will not work properly thus
// We re issue the pause ability command when the charging function ceases to make sure unit pauses properly
set .stop = true
call UnitAddAbility(.cast, PAUSE_ABIL_ID)
call IssueImmediateOrder(.cast, PAUSE_ABIL_ORDER_STRING)
endif
set u = null
endmethod
// OnInit
static method onInit takes nothing returns nothing
set tb = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onCast)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function thistype.onCancel)
endmethod
endstruct
endscope