I think this is getting close to being finished (I'm thinking about changing the target of UPD to widget instead of unit). I posted the UPD library & 3 example spells that use it. Screen shots of 1 of the spells, and attached the map with the spells. There are no comments for it yet - I hate writing comments.
Description: UPD is just a library I made for projectiles to match the way projectiles already work in wc3. Not completely identical but good enough. It's pretty basic but that's what I wanted. Standard projectiles don't have sine curve flight paths or circular flight paths.
How to use:
In your spell code make a struct that inherits UPD. It has operator methods for Speed (how fast unit target projectiles move), StartLoc (to set where the projectile starts), TargetLoc (to set where the projectile should fly to), Target (to set a unit target), HangTime (how long it takes for a point target projectile to hit its target), Arc (unit target projectile arc same ass all missile arcs in wc3), MaxHeight (how high a point target projectile should move up in the air before coming back down)
You can also directly access variables uOwner (should be the caster of the spell) uTarget (same as Target operator method but Target should be used to set the target and uTarget should be used to get the target), uProjectile (the actual projectile unit), and iUnitTypeId (the unit type id of the projectile unit)
You are welcome to add any variable you want to your struct. Most spells will probably want a level variable. And your struct should implement the OnMove and OnEnd functions. They both take nothing and return nothing. OnMove is called every time the projectile moves (when the TT timer expires) and OnEnd is called after the projectile hits the target unit or point. Your OnEnd should call this.destroy() and don't forget to clean up any variables you add to it.
UPD library:
This handles all the code for projectiles
Hurl Boulder spell:
This is a spell trigger, it has 2 structs that extend UPD. The spell throws a boulder at a target location, when the boulder lands it deals damage and breaks apart into 6 smaller boulders that are thrown outwards, when each of them lands on the ground they break apart and deal a small amount of damage.
Wave of Despair spell:
This sends out a wave of despair (duh) that explodes at the end of its life dealing damage to units around it. It also does something else but it's hard to explain...
basically there are 2 kinds of projectiles, i'll call them "Wave Projectile" and "Heal Projectile"
Wave Projectile is the main projectile, the "wave of despair" if you will (and if you won't, too bad! because it's still true)
When the Wave Projectile comes in contact with an enemy unit it sends a "Heal Projectile" from the enemy unit back to the cast. The Heal Projectile deals damage to each enemy unit it passes. So this spell is *very* good against massive groups of enemy units, and it's pretty weak against single units.
This spell is kind of ridiculous but it's really just an example of the kind of shit i can make with UPD.
Venom Lance:
It's like a blink strike shadow strike. The caster vanishes, an envenomed lance is thrown from the caster to the target, when it hits the caster appears near the target and it's hit with a shadow strike.
Snooze:
Simple spell, just Sleep with a projectile. Using this method instead of the Sphere ability because sphere will make all spells have the same projectile. Using UPD I can give any instant target spell any projectile I want.
Diamond Dust: (no not shiva from Final Fantasy)
Hit 'em with the diamond dust
One breath and they're minds rust
Throws a cloud of diamond dust at a target area stunning enemy units and sapping their mana.
Description: UPD is just a library I made for projectiles to match the way projectiles already work in wc3. Not completely identical but good enough. It's pretty basic but that's what I wanted. Standard projectiles don't have sine curve flight paths or circular flight paths.
How to use:
In your spell code make a struct that inherits UPD. It has operator methods for Speed (how fast unit target projectiles move), StartLoc (to set where the projectile starts), TargetLoc (to set where the projectile should fly to), Target (to set a unit target), HangTime (how long it takes for a point target projectile to hit its target), Arc (unit target projectile arc same ass all missile arcs in wc3), MaxHeight (how high a point target projectile should move up in the air before coming back down)
You can also directly access variables uOwner (should be the caster of the spell) uTarget (same as Target operator method but Target should be used to set the target and uTarget should be used to get the target), uProjectile (the actual projectile unit), and iUnitTypeId (the unit type id of the projectile unit)
You are welcome to add any variable you want to your struct. Most spells will probably want a level variable. And your struct should implement the OnMove and OnEnd functions. They both take nothing and return nothing. OnMove is called every time the projectile moves (when the TT timer expires) and OnEnd is called after the projectile hits the target unit or point. Your OnEnd should call this.destroy() and don't forget to clean up any variables you add to it.
UPD library:
This handles all the code for projectiles
JASS:
library UPD requires TT
private interface IUPD
method OnMove takes nothing returns nothing defaults nothing
method OnEnd takes nothing returns nothing defaults nothing
endinterface
struct UPD extends IUPD //Unit Projectile Data
//The unit should have flying movement & locust ability.
integer iUnitTypeId = 0
private real rX = 0
private real rY = 0
unit uProjectile = null
unit uOwner = null
unit uTarget = null
private real rArcHeight = 0
private real rStartHeight = 0
private real rMax = 0
private real rPassed = 0
private real rInterval = 0
private real rRadAngle = 0
private real rDistance = 0
debug private boolean TargSet = false
//move to unit target
private static method MoveToUnit takes nothing returns boolean
local UPD this = TT_GetData()
local real rTX = GetUnitX(.uTarget)
local real rTY = GetUnitY(.uTarget)
set .rRadAngle = Atan2(rTY-.rY, rTX-.rX)
call SetUnitFacing(.uProjectile, .rRadAngle * bj_RADTODEG)
set .rX = .rX + Cos(.rRadAngle)*.rInterval
set .rY = .rY + Sin(.rRadAngle)*.rInterval
call SetUnitX(.uProjectile, .rX)
call SetUnitY(.uProjectile, .rY)
set .rPassed = .rPassed + .rInterval
set .rDistance = SquareRoot((rTX-.rX)*(rTX-.rX)+(rTY-.rY)*(rTY-.rY))
set .rMax = .rPassed + .rDistance
call SetUnitFlyHeight(.uProjectile, .rPassed*.rDistance*.rArcHeight*4/.rMax + .rStartHeight, 0)
if .rDistance < .rInterval then
call .OnEnd()
return true
endif
call .OnMove()
return false
endmethod
//move to point target
private static method MoveToPoint takes nothing returns boolean
local UPD this = TT_GetData()
set .rX = .rX + Cos(.rRadAngle) * .rInterval
set .rY = .rY + Sin(.rRadAngle) * .rInterval
set .rPassed = .rPassed+TT_PERIOD
call SetUnitX(.uProjectile, .rX)
call SetUnitY(.uProjectile, .rY)
call SetUnitFlyHeight(.uProjectile, .rPassed*(.rMax - .rPassed)*.rArcHeight + .rStartHeight, 0)
if .rPassed >= .rMax then
call .OnEnd()
return true
endif
call .OnMove()
return false
endmethod
//Used for unit target
method operator Speed= takes real rSpeed returns nothing
set .rInterval = rSpeed*TT_PERIOD
endmethod
//Used for point or unit target
method operator StartLoc= takes location whichLoc returns nothing
set .rX = GetLocationX(whichLoc)
set .rY = GetLocationY(whichLoc)
endmethod
//used for point target
method operator TargetLoc= takes location whichLoc returns nothing
local real rX = GetLocationX(whichLoc)
local real rY = GetLocationY(whichLoc)
set .rDistance = SquareRoot((rX-.rX)*(rX-.rX)+(rY-.rY)*(rY-.rY))
set .rRadAngle = Atan2(rY-.rY, rX-.rX)
debug set .TargSet = true
endmethod
//used for unit target
method operator Target= takes unit whichUnit returns nothing
set .uTarget = whichUnit
set .rRadAngle = Atan2(GetUnitY(whichUnit)-.rY, GetUnitX(whichUnit)-.rX)
debug set .TargSet = true
endmethod
//used for point target
method operator HangTime= takes real rTime returns nothing
if rTime - R2I(rTime/TT_PERIOD)*TT_PERIOD == 0 then
set .rMax = rTime
else
set .rMax = (R2I(rTime/TT_PERIOD)+1)*TT_PERIOD
endif
endmethod
//used for point target
method operator MaxHeight= takes real rHeight returns nothing
set .rArcHeight = rHeight
endmethod
//start the projectile
method Start takes nothing returns nothing
debug if .TargSet == false then
debug call BJDebugMsg("No target has been set.")
debug return
debug endif
debug if .iUnitTypeId == 0 then
debug call BJDebugMsg("No unit type specified.")
debug return
debug endif
debug if .uOwner == null then
debug call BJDebugMsg("No owner set.")
debug return
debug endif
set .uProjectile = CreateUnit(GetOwningPlayer(.uOwner), .iUnitTypeId, .rX, .rY, .rRadAngle * bj_RADTODEG)//CreateUnit(GetOwningPlayer(.uOwner), .iUnitTypeId, .rX, .rY, .rRadAngle * bj_RADTODEG)
set .rStartHeight = GetUnitFlyHeight(.uProjectile)
if .uTarget != null then
call TT_Start(function UPD.MoveToUnit, this)
else
set .rInterval = .rDistance * TT_PERIOD / .rMax
set .rArcHeight = (.rArcHeight * 4) / (.rMax * .rMax)
call TT_Start(function UPD.MoveToPoint, this)
endif
endmethod
endstruct
endlibrary
Hurl Boulder spell:
This is a spell trigger, it has 2 structs that extend UPD. The spell throws a boulder at a target location, when the boulder lands it deals damage and breaks apart into 6 smaller boulders that are thrown outwards, when each of them lands on the ground they break apart and deal a small amount of damage.
JASS:
scope HurlBoulder initializer Init
globals
private constant integer SPELL_ID = 039;A000039;
private constant integer BOULDER_SPAWN_BASE = 6
private constant integer BOULDER_SPAWN_PER_LEVEL = 0
private constant integer BIG_BOULDER_ID = 039;h000039;
private constant real BIG_AOE_BASE = 75.0
private constant real BIG_AOE_PER_LEVEL = 75.0
private constant real BIG_DAMAGE_BASE = 0.0
private constant real BIG_DAMAGE_PER_LEVEL = 45.0
private constant real BIG_HANG_TIME = 0.64
private constant real BIG_MAX_HEIGHT = 450.0
private constant integer SMALL_BOULDER_ID = 039;h001039;
private constant real SMALL_AOE_BASE = 35.0
private constant real SMALL_AOE_PER_LEVEL = 35.0
private constant real SMALL_DAMAGE_BASE = 0.0
private constant real SMALL_DAMAGE_PER_LEVEL = 15.0
private constant real SMALL_HANG_TIME = 0.36
private constant real SMALL_MAX_HEIGHT = 250.0
private constant real SMALL_DISTANCE_BASE = 60.0
private constant real SMALL_DISTANCE_PER_LEVEL = 40.0
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_SIEGE
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_DEMOLITION
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_ROCK_HEAVY_BASH
endglobals
private struct SmallBoulder extends UPD
integer iLevel = 0
private method operator Damage takes nothing returns real
return SMALL_DAMAGE_BASE + SMALL_DAMAGE_PER_LEVEL * .iLevel
endmethod
private method operator AoE takes nothing returns real
return SMALL_AOE_BASE + SMALL_AOE_PER_LEVEL * .iLevel
endmethod
private method ValidTarget takes unit whichUnit returns boolean
//Doesn't hit flying
if IsUnitType(whichUnit, UNIT_TYPE_FLYING) then
return false
endif
//Don't bother if it's already dead
if GetUnitState(whichUnit, UNIT_STATE_LIFE) < 0.5 then
return false
endif
//Only hits enemies
return IsUnitEnemy(whichUnit, GetOwningPlayer(.uOwner))
endmethod
private method OnEnd takes nothing returns nothing
local unit uTemp
local group gTargets = NewGroup()
call KillUnit(.uProjectile)
call GroupEnumUnitsInRange(gTargets, GetUnitX(.uProjectile), GetUnitY(.uProjectile), .AoE, null)
loop
set uTemp = FirstOfGroup(gTargets)
exitwhen uTemp == null
if .ValidTarget(uTemp) then
call UnitDamageTarget(.uOwner, uTemp, .Damage, false, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
endif
call GroupRemoveUnit(gTargets, uTemp)
endloop
call ReleaseGroup(gTargets)
set gTargets = null
set uTemp = null
call this.destroy()
endmethod
endstruct
private struct BigBoulder extends UPD
integer iLevel = 0
private method operator Damage takes nothing returns real
return BIG_DAMAGE_BASE + BIG_DAMAGE_PER_LEVEL * .iLevel
endmethod
private method operator AoE takes nothing returns real
return BIG_AOE_BASE + BIG_AOE_PER_LEVEL * .iLevel
endmethod
private method operator BoulderCount takes nothing returns integer
return BOULDER_SPAWN_BASE + BOULDER_SPAWN_PER_LEVEL * .iLevel
endmethod
private method operator Distance takes nothing returns real
return SMALL_DISTANCE_BASE + SMALL_DISTANCE_PER_LEVEL * .iLevel
endmethod
private method ValidTarget takes unit whichUnit returns boolean
//Doesn't hit flying
if IsUnitType(whichUnit, UNIT_TYPE_FLYING) then
return false
endif
//If it's already dead... Don't bother!
if GetUnitState(whichUnit, UNIT_STATE_LIFE) < 0.5 then
return false
endif
//Only hits enemies
return IsUnitEnemy(whichUnit, GetOwningPlayer(.uOwner))
endmethod
private method OnEnd takes nothing returns nothing
local unit uTemp
local group gTargets = NewGroup()
local SmallBoulder b
local integer iCount = 0
local location locStart = GetUnitLoc(.uProjectile)
local location locTarget
local real rAngle
call KillUnit(.uProjectile)
call GroupEnumUnitsInRange(gTargets, GetUnitX(.uProjectile), GetUnitY(.uProjectile), .AoE, null)
loop
set uTemp = FirstOfGroup(gTargets)
exitwhen uTemp == null
if .ValidTarget(uTemp) then
call UnitDamageTarget(.uOwner, uTemp, .Damage, false, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
endif
call GroupRemoveUnit(gTargets, uTemp)
endloop
loop
exitwhen iCount >= .BoulderCount
set rAngle = GetRandomReal(iCount, iCount+1)/.BoulderCount*2*bj_PI
set b = SmallBoulder.create()
set b.uOwner = .uOwner
set b.iLevel = .iLevel
set b.StartLoc = locStart
set locTarget = Location(GetLocationX(locStart) + Cos(rAngle) * .Distance, GetLocationY(locStart) + Sin(rAngle) * .Distance)
set b.TargetLoc = locTarget
set b.HangTime = SMALL_HANG_TIME
set b.MaxHeight = SMALL_MAX_HEIGHT
set b.iUnitTypeId = SMALL_BOULDER_ID
call b.Start()
call RemoveLocation(locTarget)
set iCount = iCount + 1
endloop
call ReleaseGroup(gTargets)
call RemoveLocation(locStart)
set gTargets = null
set uTemp = null
set locStart = null
set locTarget = null
call this.destroy()
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
private function Actions takes nothing returns nothing
local unit uCaster = GetSpellAbilityUnit()
local location locCaster = GetUnitLoc(uCaster)
local location locTarget = GetSpellTargetLoc()
local BigBoulder b = BigBoulder.create()
set b.uOwner = uCaster
set b.iLevel = GetUnitAbilityLevel(uCaster, SPELL_ID)
set b.StartLoc = locCaster
set b.TargetLoc = locTarget
set b.HangTime=BIG_HANG_TIME
set b.MaxHeight=BIG_MAX_HEIGHT
set b.iUnitTypeId = BIG_BOULDER_ID
call b.Start()
call RemoveLocation(locCaster)
call RemoveLocation(locTarget)
set uCaster = null
set locCaster = null
set locTarget = null
endfunction
private function Init takes nothing returns nothing
local trigger SpellTrigger = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(SpellTrigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(SpellTrigger, Condition(function Conditions))
call TriggerAddAction(SpellTrigger, function Actions)
endfunction
endscope
Wave of Despair spell:
This sends out a wave of despair (duh) that explodes at the end of its life dealing damage to units around it. It also does something else but it's hard to explain...
basically there are 2 kinds of projectiles, i'll call them "Wave Projectile" and "Heal Projectile"
Wave Projectile is the main projectile, the "wave of despair" if you will (and if you won't, too bad! because it's still true)
When the Wave Projectile comes in contact with an enemy unit it sends a "Heal Projectile" from the enemy unit back to the cast. The Heal Projectile deals damage to each enemy unit it passes. So this spell is *very* good against massive groups of enemy units, and it's pretty weak against single units.
This spell is kind of ridiculous but it's really just an example of the kind of shit i can make with UPD.
JASS:
scope WaveOfDespair initializer Init
globals
private constant integer SPELL_ID = 039;A002039;
//=========================================================
//WAVE PROJECTILE INFO
//=========================================================
private constant integer WAVE_ID = 039;h002039;
private constant real WAVE_RANGE_BASE = 800.0
private constant real WAVE_RANGE_PER_LEVEL = 50.0
private constant real WAVE_SPEED_BASE = 700.0
private constant real WAVE_SPEED_PER_LEVEL = 100.0
private constant real WAVE_AOE_BASE = 150.0
private constant real WAVE_AOE_PER_LEVEL = 0.0
private constant real BLAST_AOE_BASE = 210.0
private constant real BLAST_AOE_PER_LEVEL = 0.0
//Damage is dealt when the projectile poofs.
private constant real BLAST_DAMAGE_BASE = 0.0
private constant real BLAST_DAMAGE_PER_LEVEL = 50.0
//=========================================================
//HEAL PROJECTILE INFO
//=========================================================
private constant integer HEAL_ID = 039;h003039;
private constant real HEAL_SPEED_BASE = 450.0
private constant real HEAL_SPEED_PER_LEVEL = 0.0
private constant real HEAL_DAMAGE_AOE_BASE = 100.0
private constant real HEAL_DAMAGE_AOE_PER_LEVEL = 0.0
//Heal projectile deals damage to each enemy it passes
private constant real HEAL_DAMAGE_AMOUNT_BASE = 0.0
private constant real HEAL_DAMAGE_AMOUNT_PER_LEVEL = 10.0
//Heals the caster after it reaches the caster
private constant real HEAL_AMOUNT_BASE = 0.0
private constant real HEAL_AMOUNT_PER_LEVEL = 10.0
//damage types
private constant attacktype ATTACK = ATTACK_TYPE_CHAOS
private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WEAPON = WEAPON_TYPE_WHOKNOWS
//Heal effect
private constant string HEAL_EFFECT = "Abilities\\Spells\\Undead\\VampiricAura\\VampiricAuraTarget.mdl"
private constant string DAMAGE_EFFECT = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl"
private constant string EFFECT_ATTACHMENT = "origin"
endglobals
private function Range takes integer iLevel returns real
return WAVE_RANGE_BASE + WAVE_RANGE_PER_LEVEL * iLevel
endfunction
private function WaveSpeed takes integer iLevel returns real
return WAVE_SPEED_BASE + WAVE_SPEED_PER_LEVEL * iLevel
endfunction
private function HealSpeed takes integer iLevel returns real
return HEAL_SPEED_BASE + HEAL_SPEED_PER_LEVEL * iLevel
endfunction
private struct HealProjectile extends UPD
static unit uTemp = null
integer iLevel = 0
private group gHit = null
private group gInRange = null
static method create takes nothing returns HealProjectile
local HealProjectile this = HealProjectile.allocate()
set .gHit = NewGroup()
set .gInRange = NewGroup()
return this
endmethod
private method operator Damage takes nothing returns real
return HEAL_DAMAGE_AMOUNT_BASE + HEAL_DAMAGE_AMOUNT_PER_LEVEL * .iLevel
endmethod
private method operator DamageAoE takes nothing returns real
return HEAL_DAMAGE_AOE_BASE + HEAL_DAMAGE_AOE_PER_LEVEL * .iLevel
endmethod
private method operator Heal takes nothing returns real
return HEAL_AMOUNT_BASE + HEAL_AMOUNT_PER_LEVEL * .iLevel
endmethod
private method ValidTarget takes nothing returns boolean
if IsUnitInGroup(.uTemp, .gHit) then
return false //Can only be hit once!
endif
if GetUnitState(.uTemp, UNIT_STATE_LIFE) < 0.5 then
return false //Don't bother with corpses!
endif
if IsUnitType(.uTemp, UNIT_TYPE_STRUCTURE) then
return false //Don't hurt buildings
endif
if IsUnitType(.uTemp, UNIT_TYPE_MAGIC_IMMUNE) then
return false //Doesn't hurt fuckers that are immune to magic
endif
return IsUnitEnemy(.uTemp, GetOwningPlayer(.uOwner))
endmethod
private method OnMove takes nothing returns nothing
call GroupEnumUnitsInRange(.gInRange, GetUnitX(.uProjectile), GetUnitY(.uProjectile), .DamageAoE, null)
loop
set .uTemp = FirstOfGroup(.gInRange)
exitwhen .uTemp == null
if .ValidTarget() then
call UnitDamageTarget(.uOwner, .uTemp, .Damage, false, false, ATTACK, DAMAGE, WEAPON)
call GroupAddUnit(.gHit, .uTemp)
endif
call GroupRemoveUnit(.gInRange, .uTemp)
endloop
endmethod
private method OnEnd takes nothing returns nothing
call DestroyEffect(AddSpecialEffectTarget(HEAL_EFFECT, .uTarget, EFFECT_ATTACHMENT))
call SetUnitState(.uTarget, UNIT_STATE_LIFE, GetUnitState(.uTarget, UNIT_STATE_LIFE) + .Heal)
call KillUnit(.uProjectile)
call ReleaseGroup(.gHit)
call ReleaseGroup(.gInRange)
call this.destroy()
endmethod
endstruct
private struct WaveProjectile extends UPD
static unit uTemp = null
integer iLevel = 0
private group gHit = null
private group gInRange = null
static method create takes nothing returns WaveProjectile
local WaveProjectile this = WaveProjectile.allocate()
set .gHit = NewGroup()
set .gInRange = NewGroup()
return this
endmethod
private method operator WaveArea takes nothing returns real
return WAVE_AOE_BASE + WAVE_AOE_PER_LEVEL * .iLevel
endmethod
private method operator BlastArea takes nothing returns real
return BLAST_AOE_BASE + BLAST_AOE_PER_LEVEL * .iLevel
endmethod
private method operator BlastDamage takes nothing returns real
return BLAST_DAMAGE_BASE + BLAST_DAMAGE_PER_LEVEL * .iLevel
endmethod
private method ValidTarget takes nothing returns boolean
if GetUnitState(.uTemp, UNIT_STATE_LIFE) < 0.5 then
return false //Don't bother with corpses!
endif
if IsUnitType(.uTemp, UNIT_TYPE_STRUCTURE) then
return false //Don't hurt buildings
endif
if IsUnitType(.uTemp, UNIT_TYPE_MAGIC_IMMUNE) then
return false //Doesn't hurt fuckers that are immune to magic
endif
return IsUnitEnemy(.uTemp, GetOwningPlayer(.uOwner))
endmethod
private method WaveValidTarget takes nothing returns boolean
if IsUnitInGroup(.uTemp, .gHit) then
return false //Can only be hit once!
endif
return .ValidTarget()
endmethod
private method OnEnd takes nothing returns nothing
call GroupEnumUnitsInRange(.gInRange, GetUnitX(.uProjectile), GetUnitY(.uProjectile), .BlastArea, null)
call KillUnit(.uProjectile)
loop
set .uTemp = FirstOfGroup(.gInRange)
exitwhen .uTemp == null
if .ValidTarget() then
call UnitDamageTarget(.uOwner, .uTemp, .BlastDamage, false, false, ATTACK, DAMAGE, WEAPON)
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, .uTemp, EFFECT_ATTACHMENT))
endif
call GroupRemoveUnit(.gInRange, .uTemp)
endloop
call ReleaseGroup(.gHit)
call ReleaseGroup(.gInRange)
call this.destroy()
endmethod
private method OnMove takes nothing returns nothing
local HealProjectile h
local location locStart
call GroupEnumUnitsInRange(.gInRange, GetUnitX(.uProjectile), GetUnitY(.uProjectile), .WaveArea, null)
loop
set .uTemp = FirstOfGroup(.gInRange)
exitwhen .uTemp == null
call GroupRemoveUnit(.gInRange, .uTemp)
if .WaveValidTarget() then
call GroupAddUnit(.gHit, .uTemp)
set h = HealProjectile.create()
set locStart = GetUnitLoc(.uTemp)
set h.StartLoc = locStart
set h.uOwner = .uOwner
set h.iLevel = .iLevel
set h.Target = .uOwner
set h.iUnitTypeId = HEAL_ID
set h.Speed = HealSpeed(.iLevel)
call h.Start()
call RemoveLocation(locStart)
endif
endloop
set locStart = null
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
private function Actions takes nothing returns nothing
local unit uCaster = GetSpellAbilityUnit()
local location locCaster = GetUnitLoc(uCaster)
local location locTarget = GetSpellTargetLoc()
local real rAngle = Atan2(GetLocationY(locTarget)-GetLocationY(locCaster), GetLocationX(locTarget)-GetLocationX(locCaster))
local WaveProjectile w = WaveProjectile.create()
local integer iLevel = GetUnitAbilityLevel(uCaster, SPELL_ID)
call RemoveLocation(locTarget)
set locTarget = Location(GetLocationX(locCaster) + Cos(rAngle)*Range(iLevel), GetLocationY(locCaster) + Sin(rAngle)*Range(iLevel))
set w.uOwner = uCaster
set w.iLevel = iLevel
set w.StartLoc = locCaster
set w.TargetLoc = locTarget
set w.HangTime=Range(iLevel)/WaveSpeed(iLevel)
set w.iUnitTypeId = WAVE_ID
call w.Start()
call RemoveLocation(locCaster)
call RemoveLocation(locTarget)
set uCaster = null
set locCaster = null
set locTarget = null
endfunction
private function Init takes nothing returns nothing
local trigger Trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(Trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(Trig, Condition(function Conditions))
call TriggerAddAction(Trig, function Actions)
endfunction
endscope
Venom Lance:
It's like a blink strike shadow strike. The caster vanishes, an envenomed lance is thrown from the caster to the target, when it hits the caster appears near the target and it's hit with a shadow strike.
JASS:
scope VenomLance initializer Init
globals
private constant integer VL_SPELL_ID = 039;A003039;
private constant integer SS_SPELL_ID = 039;A004039;
private constant integer INVULNERABLE = 039;AInv039;
private constant integer LANCE_ID = 039;h004039;
private constant integer VENOM_ID = 039;h005039;
private constant integer DUMMY_ID = 039;hZZZ039;
private constant integer TIMED_LIFE = 039;BTLF039;
private constant real SPEED_BASE = 850.0
private constant real SPEED_PER_LEVEL = 0.0
private constant real ARC = 0.1
private constant real OFFSET = 200.0
private constant string START_EFFECT = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"
private constant string END_EFFECT = "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl"
private constant string ATTACHMENT_POINT = "origin"
endglobals
private function LanceSpeed takes integer iLevel returns real
return SPEED_BASE + SPEED_PER_LEVEL * iLevel
endfunction
private struct Lance extends UPD
integer iLevel = 0
unit uVenom = null
private method operator LanceSpeed takes nothing returns real
return SPEED_BASE + SPEED_PER_LEVEL * .iLevel
endmethod
static method create takes integer level returns Lance
local Lance l = Lance.allocate()
set l.iLevel = level
set l.Speed = l.LanceSpeed
return l
endmethod
private method OnMove takes nothing returns nothing
//Since the projectile model has no attachment points, a 2nd unit needs to be used
//for the venom effect to work.
call SetUnitX(.uVenom, GetUnitX(.uProjectile))
call SetUnitY(.uVenom, GetUnitY(.uProjectile))
call SetUnitFacing(.uVenom, GetUnitFacing(.uProjectile))
call SetUnitFlyHeight(.uVenom, GetUnitFlyHeight(.uProjectile), 0)
endmethod
private method OnEnd takes nothing returns nothing
local unit uDummy
local real rTX = GetUnitX(.uTarget)
local real rTY = GetUnitY(.uTarget)
local real rAngle = Atan2(GetUnitY(.uProjectile)-rTY, GetUnitX(.uProjectile)-rTX)
call SetUnitX(.uOwner, GetUnitX(.uTarget) + Cos(rAngle) * OFFSET)
call SetUnitY(.uOwner, GetUnitY(.uTarget) + Sin(rAngle) * OFFSET)
call ShowUnit(.uOwner, true)
call UnitRemoveAbility(.uOwner, INVULNERABLE)
call DestroyEffect(AddSpecialEffectTarget(END_EFFECT, .uOwner, ATTACHMENT_POINT))
if GetLocalPlayer() == GetOwningPlayer(.uOwner) then
call SelectUnit(.uOwner, true)
endif
//Need to make sure the target hasn't died since the spell was cast
if GetUnitState(.uTarget, UNIT_STATE_LIFE) >= .5 then
set uDummy = CreateUnit(GetOwningPlayer(.uOwner), DUMMY_ID, rTX, rTY, 0)
call UnitAddAbility(uDummy, SS_SPELL_ID)
call SetUnitAbilityLevel(uDummy, SS_SPELL_ID, .iLevel)
call IssueTargetOrder(uDummy, "shadowstrike", .uTarget)
call UnitApplyTimedLife(uDummy, TIMED_LIFE, 1.0)
call IssueTargetOrder(.uOwner, "attack", .uTarget)
endif
call KillUnit(.uProjectile)
call KillUnit(.uVenom)
set uDummy = null
call .destroy()
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == VL_SPELL_ID
endfunction
private function Actions takes nothing returns nothing
local unit uCaster = GetSpellAbilityUnit()
local location locStart = GetUnitLoc(uCaster)
local Lance l = Lance.create(GetUnitAbilityLevel(uCaster, VL_SPELL_ID))
set l.StartLoc = locStart
set l.uOwner = uCaster
set l.Target = GetSpellTargetUnit()
set l.iUnitTypeId = LANCE_ID
call l.Start()
set l.uVenom = CreateUnit(GetOwningPlayer(uCaster), VENOM_ID, GetLocationX(locStart), GetLocationY(locStart), GetUnitFacing(l.uProjectile))
call SetUnitFlyHeight(l.uVenom, GetUnitFlyHeight(l.uProjectile), 0)
call DestroyEffect(AddSpecialEffectLoc(START_EFFECT, locStart))
call UnitAddAbility(uCaster, INVULNERABLE)
call ShowUnit(uCaster, false)
call RemoveLocation(locStart)
set uCaster = null
set locStart = null
endfunction
private function Init takes nothing returns nothing
local trigger Trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(Trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(Trig, Condition(function Conditions))
call TriggerAddAction(Trig, function Actions)
endfunction
endscope
Snooze:
Simple spell, just Sleep with a projectile. Using this method instead of the Sphere ability because sphere will make all spells have the same projectile. Using UPD I can give any instant target spell any projectile I want.
JASS:
scope Snooze initializer Init
globals
private constant integer SPELL_ID = 039;A004039;
private constant integer SLEEP_SPELL = 039;A005039;
private constant integer ZZ_ID = 039;h006039;
private constant real DUMMY_ID = 039;hZZZ039;
private constant integer TIMED_LIFE = 039;BTLF039;
private constant real SPEED = 600
private constant real ARC = 0.3
endglobals
private struct SnoozeData
integer iLevel = 0
private method TargetAlive takes nothing returns boolean
return GetUnitState(.uTarget, UNIT_STATE_LIFE) >= 0.5
endmethod
private method OnEnd takes nothing returns nothing
local unit uDummy
if .TargetAlive() then
set uDummy = CreateUnit(GetOwningPlayer(.uOwner), DUMMY_ID, GetUnitX(.uTarget), GetUnitY(.uTarget), 0)
call UnitAddAbility(uDummy, SLEEP_SPELL)
call SetUnitAbilityLevel(uDummy, SLEEP_SPELL, .iLevel)
call IssueTargetOrder(uDummy, "sleep", .uTarget)
call ApplyTimedLife(uDummy, TIMED_LIFE, 1.0)
endif
call KillUnit(.uProjectile)
set uDummy = null
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
private function Actions takes nothing returns nothing
local unit uCaster = GetSpellAbilityUnit()
local unit uTarget = GetSpellTargetUnit()
local location locCaster = GetUnitLoc(uCaster)
local SnoozeData s = SnoozeData.create()
set s.iLevel = GetUnitAbilityLevel(uCaster, SPELL_ID)
set s.uOwner = uCaster
set s.StartLoc = locCaster
set s.Target = uTarget
set s.Speed = SPEED
set s.Arc = ARC
call s.Start()
call RemoveLocation(locCaster)
set locCaster = null
set uCaster = null
set uTarget = null
endfunction
private function Init takes nothing returns nothing
local trigger Trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(Trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(Trig, Condition(function Conditions))
call TriggerAddAction(Trig, function Actions)
endfunction
endscope
Diamond Dust: (no not shiva from Final Fantasy)
Hit 'em with the diamond dust
One breath and they're minds rust
Throws a cloud of diamond dust at a target area stunning enemy units and sapping their mana.
JASS:
scope DiamondDust initializer Init
//HIT 'EM WITH THE DIAMOND DUST
//ONE BREATH AND THEIR MINDS RUST
globals
private constant integer SPELL_ID = 039;A006039;
private constant integer STOMP_ID = 039;A007039;
private constant integer DUST_ID = 039;h006039;
private constant real DUMMY_ID = 039;hZZZ039;
private constant integer TIMED_LIFE = 039;BTLF039;
//Mana loss: 50/80/110/140
private constant real MANA_LOSS_BASE = 20.0
private constant real MANA_LOSS_PER_LEVEL = 30.0
private constant real SPEED = 500
private constant real HEIGHT = 300
endglobals
private struct DiamondDustData
integer iLevel = 0
private method OnEnd takes nothing returns nothing
local unit uDummy = CreateUnit(GetOwningPlayer(.uOwner), DUMMY_ID, GetUnitX(.uProjectile), GetUnitY(.uProjectile), 0)
call UnitAddAbility(uDummy, STOMP_ID)
call SetUnitAbilityLevel(uDummy, STOMP_ID, .iLevel)
call
endstruct
private function MainConditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
private function MainActions takes nothing returns nothing
local unit uCaster = GetSpellAbilityUnit()
local location locCaster = GetUnitLoc(uCaster)
local location locTarget = GetSpellTargetLoc()
local DiamondDustData dd = DiamondDustData.create()
set dd.iLevel = GetUnitAbilityLevel(uCaster, SPELL_ID)
set dd.uOwner = uCaster
set dd.StartLoc = locCaster
set dd.TargetLoc = locTarget
set dd.Speed = SPEED
set dd.MaxHeight = HEIGHT
call dd.Start()
call RemoveLocation(locCaster)
call RemoveLocation(locTarget)
set locCaster = null
set locTarget = null
set uCaster = null
set uTarget = null
endfunction
private function StompConditions takes nothing returns boolean
return GetSpellAbilityId() == STOMP_ID
endfunction
private function StompActions takes nothing returns nothing
local group gTarget = NewGroup()
local unit uTemp = null
local unit uCaster = GetSpellAbilityUnit()
local integer iLevel = GetUnitAbilityLevel(uCaster, STOMP_ID)
call GroupEnumUnitsInRange(gTarget, GetUnitX(uCaster), GetUnitY(uCaster), SpellAoE(iLevel), null)
loop
set uTemp = FirstOfGroup(gTarget)
exitwhen uTemp == null
if IsUnitEnemy(GetOwningPlayer(uCaster), uTemp) then
call SetUnitState(uTemp, UNIT_STATE_MANA, GetUnitState(uTemp, UNIT_STATE_MANA) - ManaLoss(iLevel))
endif
call GroupRemoveUnit(gTarget, uTemp)
endloop
call ReleaseGroup(gTarget)
set gTarget = null
set uCaster = null
set uTemp = null
endfunction
private function Init takes nothing returns nothing
local trigger MainTrig = CreateTrigger()
local trigger StompTrig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(MainTrig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(MainTrig, Condition(function MainConditions))
call TriggerAddAction(MainTrig, function MainActions)
call TriggerRegisterAnyUnitEventBJ(StompTrig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(StompTrig, Condition(function StompConditions))
call TriggerAddAction(StompTrig, function StompActions)
endfunction
endscope