scope FrozenSpellpack
/*
* BANSGEE QUEEN SPELLPACK
* v2.0
*
* This spellpack consists of 4 active spells
* They are Cold Addict, Frostwave, Frost
* Phase, and Frozen Fate (ultimate).
* Their codes are packed into one library
* means that they are requiring each other.
*
* !This spellpack requires JNGP to work!
*
* External Dependencies:
* (Required)
* - CTL
* - IsTerrainWalkable
* - IsUnitCorpse
* - WorldBounds
* - UnitIndexer
*
* (Optional)
* - SpellEffectEvent
* - AutoFly
*
* How to install:
* - Install and configure all external
* dependecies properly.
* - Copy all abilities (UD), units (UD), and
* buffs at Object Editor into your map.
* - Copy this trigger into your map.
* - Configure the spells correctly.
*
* Credits:
* - Nestharus
* - Bribe
* - Cokemonkey11
* - Anitarf
* - Vexorian
* - Magtheridon96
*
*
* CONFIGURATION
*
* Configuration is separated into several
* parts. Each part is reserved for one spell.
*/
/* A. Global */
globals
/* Dummy unit's rawcode at Object Editor */
private constant integer DUMMY_ID = 'h000'
/* General decay time for all created sfx */
private constant real SFX_DECAY_TIME = 2.0
endglobals
/* B. Slow Effect */
private module SlowData
/* Slow ability's rawcode at Object Editor */
static constant integer SPELL_ID = 'A001'
/* Slow buff's rawcode at Object Editor */
static constant integer BUFF_ID = 'B001'
/* Slow order id */
static constant integer SLOW_ORDER = 852096 /* thunderclap */
/* Attached sfx on slow targets */
static constant string SLOW_SFX = "Abilities\\Spells\\Other\\FrostDamage\\FrostDamage.mdl"
static constant string SLOW_SFX_PT = "chest"
/* Duration is stackable */
static constant boolean STACKABLE = true
/* Will reset the duration if not stacked */
static constant boolean RESETABLE = false
/* The higher, the safer. But just leave it */
static constant real BUFF_SAFETY = 100.0
endmodule
/* C. Freeze Effect */
private module FreezeData
/* Freeze ability's rawcode at Object Editor */
static constant integer SPELL_ID = 'A000'
/* Freeze buff's rawcode at Object Editor */
static constant integer BUFF_ID = 'B000'
/* Freeze order id */
static constant integer FREEZE_ORDER = 852127 /* stomp */
/* Attached sfx on freeze targets */
static constant string FREEZE_SFX = "Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl"
static constant string FREEZE_SFX_PT = "origin"
/* Duration is stackable */
static constant boolean STACKABLE = false
/* Will reset the duration if not stacked */
static constant boolean RESETABLE = true
/* The higher, the safer. But just leave it */
static constant real BUFF_SAFETY = 100.0
endmodule
/*
* COLD ADDICT
* Inflicts frost magic at a target, slowing
* it's movement and attack rate, and stopping
* any mana regeneration. All damages taken
* from caster's spell will be amplified.
*/
private module ColdAddictData
/* Main ability's rawcode at Object Editor */
static constant integer SPELL_ID = 'A003'
/* Attached sfx on targets */
static constant string TARGET_SFX = "Abilities\\Spells\\Items\\AIob\\AIobTarget.mdl"
static constant string TARGET_SFX_PT = "overhead"
/* Damage amplification amount is stackable */
static constant boolean STACKABLE_BUFF = true
/* Duration is stackable */
static constant boolean STACKABLE_DUR = false
/* Will reset the duration if not stacked */
static constant boolean RESETABLE = true
/* Damage amplification amount */
static constant method damage takes integer l returns real
return -0.05 + 0.15 * l
endmethod
/* Buff duration on normal units */
static constant method durationNormal takes integer l returns real
return 15.0
endmethod
/* Buff duration on hero units */
static constant method durationHero takes integer l returns real
return 5.0
endmethod
endmodule
/*
* FROSTWAVE
* Sends a lance of ice shards wave to the
* target point, dealing damage to land units
* in a line and slowing them for a short
* duration.
*/
private module FrostwaveData
/* Main ability's rawcode at Object Editor */
static constant integer SPELL_ID = 'A005'
/* Total of created missiles on every cast */
static constant integer SHARD_COUNT = 23
/* Normal height z for every missile */
static constant real LAUNCH_Z = 64.0
/* Initial speed of every missile */
static constant real SPEED_MIN = 20.0
/* Maximum speed of every missile */
static constant real SPEED_MAX = 40.0
/* Acceleration rate of every missile */
static constant real ACCELERATION = 0.25
/* Turning rate of every missile */
static constant real TURN_RATE = 12.5 * bj_DEGTORAD
/* Missile model path */
static constant string MISSILE_PATH = "Abilities\\Weapons\\LichMissile\\LichMissile.mdl"
/* Attached sfx on targets */
static constant string TARGET_SFX = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl"
static constant string TARGET_SFX_PT = "origin"
/* Dealt damage configuration */
static constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO
static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_COLD
static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
/* Max distance from missile to hits a target */
static constant method aoe takes integer l returns real
return 128.0
endmethod
/* Dealt damage amount */
static constant method damage takes integer l returns real
return 20.0 + 60.0 * l
endmethod
/* Travel distance of every missile */
static constant method distance takes integer l returns real
return 1000.0
endmethod
/* Slow duration for normal units */
static constant method durationNormal takes integer l returns real
return 4.0
endmethod
/* Slow duration for hero units */
static constant method durationHero takes integer l returns real
return 1.325
endmethod
/* Configure the targets, by default it's unable to hit structures and magic immune */
static constant method filter takes unit u, player p returns boolean
return not(IsUnitAlly(u, p) or IsUnitType(u, UNIT_TYPE_FLYING) or IsUnitType(u, UNIT_TYPE_MECHANICAL))
endmethod
endmodule
/*
* FROST PHASE
* Instantly vanishes to the target point.
* Leaving ice explosion behind and at the
* target point. Freezes nearby enemy units
* for a short duration and deals minor damage
*/
private module FrostPhaseData
/* Main ability's rawcode at Object Editor */
static constant integer SPELL_ID = 'A004'
/* How often the spell will check for terrain's pathability */
static constant real ACCURACY = 20.0
/* Explosion sfx */
static constant real EXPLODE_SIZE = 2.0
static constant string EXPLODE_SFX = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl"
/* Only damage once for each target */
static constant boolean DAMAGE_ONCE = false
/* Dealt damage configuration */
static constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO
static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_COLD
static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
/* Dealt damage amount */
static constant method amount takes integer l returns real
return 20.0 + 20.0 * l
endmethod
/* Max range from the center of explosion to hit a target */
static constant method AoE takes integer l returns real
return 300.0
endmethod
/* Max teleport distance */
static constant method range takes integer l returns real
return 200.0 + 200.0 * l
endmethod
/* Freeze duration for normal units */
static constant method durationNormal takes integer l returns real
return 1.7 + 0.65 * l
endmethod
/* Freeze duration for hero units */
static constant method durationHero takes integer l returns real
return 0.9 + 0.5 * l
endmethod
/* Configure the targets, by default it's unable to hit structures and magic immune */
static constant method filter takes unit u, player p returns boolean
return not(IsUnitAlly(u, p) or IsUnitType(u, UNIT_TYPE_MECHANICAL))
endmethod
endmodule
/*
* FROZEN FATE
* Brings nearby corpses back to life as a
* muntant. Mutant has Frost Attack ability
* which will slows their victim down on
* every attack.
* Behind the scene, this ability is not
* actually reviving unit as other unit but
* removing old unit and creating new unit
* So keep warned.
*/
private module FrozenFateData
/* Main ability's rawcode at Object Editor */
static constant integer SPELL_ID = 'A002'
/* Summoned unit's rawcode at Object Editor */
static constant integer SUMMON_ID = 'u001'
/* Timed life buff's rawcode at Object Editor */
static constant integer BUFF_ID = 'BTLF'
/* Created sfx on missile impact */
static constant string IMPACT_SFX = "Abilities\\Spells\\Undead\\RaiseSkeletonWarrior\\RaiseSkeleton.mdl"
/* Missile model's file path */
static constant string MISSILE_SFX = "Abilities\\Spells\\Undead\\Possession\\PossessionMissile.mdl"
static constant real MISSILE_SIZE = 1.0
/* Movement rate of each missile */
static constant real MISSILE_SPEED = 15.0
/* Normal height z of each missile */
static constant real MISSILE_Z = 0.0
/* Vertical arc of missiles */
static constant real V_ARC_MIN = 0.1
static constant real V_ARC_MAX = 0.4
/* Horizontal arc of missiles */
static constant real H_ARC_MIN = 0.1
static constant real H_ARC_MAX = 0.4
/* Maximum distance to the corpse to successfully revive it */
static constant real TOLERATION = 100.0
/* Maximum summoned unit count */
static constant method count takes integer l returns integer
return 12
endmethod
/* Summoned unit lifespan */
static constant method lifespan takes integer l returns real
return 60.0
endmethod
/* Maximum distance from caster for corpses to be revived */
static constant method aoe takes integer l returns real
return 900.0
endmethod
/* Configure the targeted corpse */
static constant method filter takes unit u, player p returns boolean
return not(IsUnitType(u, UNIT_TYPE_STRUCTURE) or IsUnitType(u, UNIT_TYPE_MECHANICAL) or IsUnitType(u, UNIT_TYPE_HERO) or IsUnitType(u, UNIT_TYPE_SUMMONED))
endmethod
endmodule
/*
* END OF CONFIGURATION
* ================================================== */
native UnitAlive takes unit id returns boolean
globals
private player PASSIVE = Player(PLAYER_NEUTRAL_PASSIVE)
endglobals
/* Function that returns distance between coordinates */
private function getDistance takes real x, real y, real xt, real yt returns real
return SquareRoot((xt - x) * (xt - x) + (yt - y) * (yt - y))
endfunction
/* Function that returns angle between coordinates */
private function getAngle takes real x, real y, real xt, real yt returns real
return Atan2(yt - y, xt - x)
endfunction
/* Function that checks whether given coordinate is in map bound or not */
private constant function isInBound takes real x, real y returns boolean
return x > WorldBounds.minX and x < WorldBounds.maxX and y > WorldBounds.minY and y < WorldBounds.maxY
endfunction
private struct Slow extends array
implement SlowData
unit target
real duration
effect sfx
static unit dummy
static group group = CreateGroup()
static boolean array isAffected
static thistype array index
implement CTLExpire
set .duration = .duration - .03125
if .duration <= 0 or not UnitAlive(.target) then
call DestroyEffect(.sfx)
call UnitRemoveAbility(.target, BUFF_ID)
set isAffected[GetUnitUserData(.target)] = false
call destroy()
set .sfx = null
set .target = null
endif
implement CTLEnd
static method apply takes unit t, real d returns boolean
local thistype this
local integer i = GetUnitUserData(t)
if isAffected[i] then
set this = index[i]
/* Stack */
static if thistype.STACKABLE then
set .duration = .duration + d
else
/* Reset */
static if thistype.RESETABLE then
if .duration < d then
set .duration = d
endif
endif
endif
return true
else
// Basically, the buff doesn't work only for magic immune and structures
if not(IsUnitType(t, UNIT_TYPE_MAGIC_IMMUNE) or IsUnitType(t, UNIT_TYPE_STRUCTURE)) then
set this = create()
set .target = t
set .duration = d
set .sfx = AddSpecialEffectTarget(SLOW_SFX, t, SLOW_SFX_PT)
set isAffected[i] = true
set index[i] = this
// If unit don't have the buff yet
if GetUnitAbilityLevel(t, BUFF_ID) == 0 then
call SetUnitX(dummy, GetUnitX(t))
call SetUnitY(dummy, GetUnitY(t))
call IssueImmediateOrderById(dummy, SLOW_ORDER)
call SetUnitPosition(dummy, WorldBounds.maxX, WorldBounds.maxY)
endif
// Remove unwanted buff
call GroupEnumUnitsInRange(group, GetUnitX(.target), GetUnitY(.target), BUFF_SAFETY, null)
loop
set t = FirstOfGroup(group)
exitwhen t == null
call GroupRemoveUnit(group, t)
if GetUnitAbilityLevel(t, BUFF_ID) > 0 and not isAffected[GetUnitUserData(t)] then
call UnitRemoveAbility(t, BUFF_ID)
endif
endloop
return true
debug else
debug call BJDebugMsg("Error occured: failed to attach buff")
endif
endif
return false
endmethod
static method onInit takes nothing returns nothing
set dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, 0)
call UnitAddAbility(dummy, SPELL_ID)
endmethod
endstruct
private struct Freeze extends array
implement FreezeData
unit target
real duration
effect sfx
static unit dummy
static group group = CreateGroup()
static boolean array isAffected
static thistype array index
implement CTLExpire
set .duration = .duration - .03125
if .duration <= 0 or not UnitAlive(.target) then
call DestroyEffect(.sfx)
call UnitRemoveAbility(.target, BUFF_ID)
set isAffected[GetUnitUserData(.target)] = false
call destroy()
set .sfx = null
set .target = null
endif
implement CTLEnd
static method apply takes unit t, real d returns boolean
local thistype this
local integer i = GetUnitUserData(t)
if isAffected[i] then
set this = index[i]
/* Stack */
static if thistype.STACKABLE then
set .duration = .duration + d
else
/* Reset */
static if thistype.RESETABLE then
if .duration < d then
set .duration = d
endif
endif
endif
return true
else
/* Basically, the buff doesn't work for magic immune and structures only */
if not(IsUnitType(t, UNIT_TYPE_MAGIC_IMMUNE) or IsUnitType(t, UNIT_TYPE_STRUCTURE)) then
set this = create()
set .target = t
set .duration = d
set .sfx = AddSpecialEffectTarget(FREEZE_SFX, t, FREEZE_SFX_PT)
set isAffected[i] = true
set index[i] = this
/* If unit don't have the buff yet */
if GetUnitAbilityLevel(t, BUFF_ID) == 0 then
call SetUnitX(dummy, GetUnitX(t))
call SetUnitY(dummy, GetUnitY(t))
call IssueImmediateOrderById(dummy, FREEZE_ORDER)
call SetUnitPosition(dummy, WorldBounds.maxX, WorldBounds.maxY)
endif
/* Remove unwanted buff */
call GroupEnumUnitsInRange(group, GetUnitX(.target), GetUnitY(.target), BUFF_SAFETY, null)
loop
set t = FirstOfGroup(group)
exitwhen t == null
call GroupRemoveUnit(group, t)
if GetUnitAbilityLevel(t, BUFF_ID) > 0 and not isAffected[GetUnitUserData(t)] then
call UnitRemoveAbility(t, BUFF_ID)
endif
endloop
return true
debug else
debug call BJDebugMsg("Error occured: failed to attach buff")
endif
endif
return false
endmethod
private static method onInit takes nothing returns nothing
set dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, 0)
call UnitAddAbility(dummy, SPELL_ID)
endmethod
endstruct
/* Cold Addict spell*/
scope ColdAddict
struct ColdAddict extends array
implement ColdAddictData
unit target
real duration
real mana
effect sfx
static real array amount
static boolean array isAffected
static thistype array index
implement CTL
local integer i
local real m
implement CTLExpire
set .duration = .duration - .03125
/* Stops mana regeneration */
set m = GetUnitState(.target, UNIT_STATE_MANA)
if m > .mana then
call SetUnitState(.target, UNIT_STATE_MANA, .mana)
elseif m < .mana then
set .mana = m
endif
if .duration <= 0 or not UnitAlive(.target) then
set i = GetUnitUserData(.target)
call DestroyEffect(.sfx)
set amount[i] = 0
set isAffected[i] = false
call destroy()
set .target = null
set .sfx = null
endif
implement CTLEnd
static method onCast takes nothing returns nothing
local thistype this = create()
local unit t = GetSpellTargetUnit()
local integer i = GetUnitUserData(t)
local integer l = GetUnitAbilityLevel(GetTriggerUnit(), SPELL_ID)
local real d
if isAffected[i] then
set this = index[i]
static if thistype.STACKABLE_DUR then
if IsUnitType(t, UNIT_TYPE_HERO) then
set .duration = .duration + durationHero(l)
else
set .duration = .duration + durationNormal(l)
endif
else
static if thistype.RESETABLE then
if IsUnitType(t, UNIT_TYPE_HERO) then
set .duration = durationHero(l)
else
set .duration = durationNormal(l)
endif
endif
endif
/* Stack damage amplification amount */
static if thistype.STACKABLE_BUFF then
set amount[i] = amount[i] + damage(l)
endif
/*
* Prevent slow buff from being removed before
* cold addict has expired
*/
if Slow.index[i].duration < .duration then
set Slow.index[i].duration = .duration
endif
else
if IsUnitType(t, UNIT_TYPE_HERO) then
set d = durationHero(l)
else
set d = durationNormal(l)
endif
if Slow.apply(t, duration) then
set this = create()
set .target = t
set .duration = d
set .mana = GetUnitState(.target, UNIT_STATE_MANA)
set amount[i] = damage(l)
set isAffected[i] = true
set index[i] = this
set .sfx = AddSpecialEffectTarget(TARGET_SFX, .target, TARGET_SFX_PT)
endif
endif
set t = null
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method check takes nothing returns isAffectedean
if GetSpellAbilityId() == SPELL_ID then
call thistype.onCast()
endif
return false
endmethod
endif
static method onInit takes nothing returns nothing
local trigger t
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
else
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.check))
endif
endmethod
endstruct
endscope
/* Frostwave spell*/
scope Frostwave
private struct data extends array
implement FrostwaveData
static constant real ANGLE_ADD = (bj_PI * 2)/data.SHARD_COUNT
endstruct
private keyword Frostwave
private struct IceShard
integer count
real array angle[data.SHARD_COUNT]
real array speed[data.SHARD_COUNT]
real array x[data.SHARD_COUNT]
real array y[data.SHARD_COUNT]
boolean array bool[data.SHARD_COUNT]
effect array sfx[data.SHARD_COUNT]
unit array dummy[data.SHARD_COUNT]
/* For FoG looping */
static group tempGroup = CreateGroup()
/* Function to compare two distances */
static constant method inRadius takes real x, real y, real xt, real yt, real r returns boolean
return (x - xt) * (x - xt) + (y - yt) * (y - yt) <= r * r
endmethod
static method move takes Frostwave i returns boolean
local boolean b = false
local integer j = 0
local real a
local real d
local unit u
loop
exitwhen j > data.SHARD_COUNT - 1
if i.missile.bool[j] then
set i.missile.x[j] = i.missile.x[j] + i.missile.speed[j] * Cos(i.missile.angle[j])
set i.missile.y[j] = i.missile.y[j] + i.missile.speed[j] * Sin(i.missile.angle[j])
/* If has reached the destination */
if isInBound(i.missile.x[j], i.missile.y[j]) and not inRadius(i.missile.x[j], i.missile.y[j], i.tX, i.tY, i.missile.speed[j]) then
set a = getAngle(i.missile.x[j], i.missile.y[j], i.tX, i.tY)
call SetUnitX(i.missile.dummy[j], i.missile.x[j])
call SetUnitY(i.missile.dummy[j], i.missile.y[j])
call SetUnitFacing(i.missile.dummy[j], i.missile.angle[j] * bj_RADTODEG)
call GroupEnumUnitsInRange(tempGroup, i.missile.x[j], i.missile.y[j], i.angle, null)
loop
set u = FirstOfGroup(tempGroup)
exitwhen u == null
if UnitAlive(u) and not IsUnitInGroup(u, i.targets) and data.filter(u, i.owner) then
if IsUnitType(u, UNIT_TYPE_HERO) then
set d = data.durationHero(i.level)
else
set d = data.durationNormal(i.level)
endif
if Slow.apply(u, d) then
call UnitDamageTarget(i.count, u, i.damage + i.damage * ColdAddict.amount[GetUnitUserData(u)], false, false, data.ATTACK_TYPE, data.DAMAGE_TYPE, data.WEAPON_TYPE)
call DestroyEffect(AddSpecialEffectTarget(data.TARGET_SFX, u, data.TARGET_SFX_PT))
endif
call GroupAddUnit(i.targets, u)
endif
call GroupRemoveUnit(tempGroup, u)
endloop
/*
* Turn the missile slowly to face the target
* point based on TURN_RATE
*/
if data.TURN_RATE != 0 and Cos(i.missile.angle[j] - a) < Cos(data.TURN_RATE) then
if Sin(a - i.missile.angle[j]) >= 0 then
set i.missile.angle[j] = i.missile.angle[j] + data.TURN_RATE
else
set i.missile.angle[j] = i.missile.angle[j] - data.TURN_RATE
endif
else
set i.missile.angle[j] = a
/* Accelerate if has faced the target point correctly */
set i.missile.speed[j] = i.missile.speed[j] + data.ACCELERATION
if i.missile.speed[j] > data.SPEED_MAX then
set i.missile.speed[j] = data.SPEED_MAX
endif
endif
else
call SetUnitX(i.missile.dummy[j], i.tX)
call SetUnitY(i.missile.dummy[j], i.tY)
call DestroyEffect(i.missile.sfx[j])
call UnitApplyTimedLife(i.missile.dummy[j], 'BTLF', SFX_DECAY_TIME)
set i.missile.sfx[j] = null
set i.missile.bool[j] = false
set i.missile.count = i.missile.count - 1
if i.missile.count < 0 then
call i.missile.destroy()
set b = true
endif
endif
endif
set j = j + 1
endloop
return b
endmethod
static method create takes real x, real y, real f returns thistype
local thistype this = allocate()
local integer i = 0
/* Determine the lowest angle */
set f = f - data.ANGLE_ADD * (data.SHARD_COUNT/2)
set .count = data.SHARD_COUNT - 1
loop
exitwhen i > .count
set .speed[i] = data.SPEED_MIN
set .angle[i] = f
set .bool[i] = true
set .x[i] = x
set .y[i] = y
set .dummy[i] = CreateUnit(PASSIVE, DUMMY_ID, x, y, f * bj_RADTODEG)
set .sfx[i] = AddSpecialEffectTarget(data.MISSILE_PATH, .dummy[i], "origin")
static if not LIBRARY_AutoFly then
if UnitAddAbility(.dummy[i], 'Amrf') and UnitRemoveAbility(.dummy[i], 'Amrf') then
endif
endif
call SetUnitFlyHeight(.dummy[i], data.LAUNCH_Z, 0)
set f = f + data.ANGLE_ADD
set i = i + 1
endloop
return this
endmethod
endstruct
private struct Frostwave extends array
real tX
real tY
real angle
real damage
unit count
group targets
player owner
integer level
IceShard missile
implement CTLExpire
if IceShard.move(this) then
call DestroyGroup(.targets)
call destroy()
set .count = null
set .targets = null
endif
implement CTLEnd
static method onCast takes nothing returns nothing
local thistype this = create()
local real a
local real x
local real y
set .count = GetTriggerUnit()
set .owner = GetTriggerPlayer()
set .targets = CreateGroup()
set x = GetUnitX(.count)
set y = GetUnitY(.count)
set .tX = GetSpellTargetX()
set .tY = GetSpellTargetY()
set .level = GetUnitAbilityLevel(.count, data.SPELL_ID)
/* If target point is the same as cast point */
if x == .tX and y == .tY then
set a = GetUnitFacing(.count) * bj_DEGTORAD
else
set a = getAngle(x, y, .tX, .tY)
endif
set .missile = IceShard.create(x, y, a)
set .tX = x + data.distance(.level) * Cos(a)
set .tY = y + data.distance(.level) * Sin(a)
set .damage = data.damage(.level)
set .angle = data.aoe(.level)
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method check takes nothing returns boolean
if GetSpellAbilityId() == data.SPELL_ID then
call thistype.onCast()
endif
return false
endmethod
endif
static method onInit takes nothing returns nothing
local trigger t
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(data.SPELL_ID, function thistype.onCast)
else
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.check))
endif
endmethod
endstruct
endscope
/* Frost Phase spell*/
scope FrostPhase
private struct FrostPhase extends array
implement FrostPhaseData
real aoe
real angle
real distance
real damage
real x
real y
unit source
group targets
player owner
integer level
/* For FoG looping */
static group tempGroup = CreateGroup()
implement CTL
local unit u
local unit f
local real d
local real c = 0
implement CTLExpire
/*
* Check terrain pathability between cast and
* target point
*/
set c = 0
loop
exitwhen c >= .distance or not IsTerrainWalkable(.x, .y)
set .x = .x + ACCURACY * Cos(.angle)
set .y = .y + ACCURACY * Sin(.angle)
set c = c + ACCURACY
endloop
call SetUnitPosition(.source, .x, .y)
set .x = GetUnitX(.source)
set .y = GetUnitY(.source)
call GroupEnumUnitsInRange(tempGroup, .x, .y, .aoe, null)
loop
set u = FirstOfGroup(tempGroup)
exitwhen u == null
if UnitAlive(u) and filter(u, .owner) then
if not IsUnitInGroup(u, .targets) then
if IsUnitType(u, UNIT_TYPE_HERO) then
set d = durationHero(.level)
else
set d = durationNormal(.level)
endif
if Freeze.apply(u, d) then
call UnitDamageTarget(.source, u, .damage + .damage * ColdAddict.amount[GetUnitUserData(u)], false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
/* If only deal damage for once per unit*/
static if DAMAGE_ONCE then
call GroupAddUnit(.targets, u)
endif
endif
endif
endif
call GroupRemoveUnit(tempGroup, u)
endloop
/* Create scaled sfx at target point */
set f = CreateUnit(.owner, DUMMY_ID, .x, .y, 0)
call SetUnitScale(f, EXPLODE_SIZE, 1, 1)
call DestroyEffect(AddSpecialEffectTarget(EXPLODE_SFX, f, "origin"))
call UnitApplyTimedLife(f, 'BTLF', SFX_DECAY_TIME)
call DestroyGroup(.targets)
call destroy()
set .source = null
set .targets = null
set f = null
implement CTLEnd
static method onCast takes nothing returns nothing
local thistype this = create()
local real xt
local real yt
local real d
local unit u
set .source = GetTriggerUnit()
set .owner = GetTriggerPlayer()
set .targets = CreateGroup()
set .x = GetUnitX(.source)
set .y = GetUnitY(.source)
set xt = GetSpellTargetX()
set yt = GetSpellTargetY()
set .level = GetUnitAbilityLevel(.source, SPELL_ID)
set .angle = getAngle(.x, .y, xt, yt)
set .distance = getDistance(.x, .y, xt, yt)
/* If surpass max distance */
if .distance > range(.level) then
set .distance = range(.level)
endif
set .damage = amount(.level)
set .aoe = AoE(.level)
call GroupEnumUnitsInRange(tempGroup, .x, .y, .aoe, null)
loop
set u = FirstOfGroup(tempGroup)
exitwhen u == null
if UnitAlive(u) and filter(u, .owner) then
if not IsUnitInGroup(u, .targets) then
if IsUnitType(u, UNIT_TYPE_HERO) then
set d = durationHero(.level)
else
set d = durationNormal(.level)
endif
if Freeze.apply(u, d) then
call UnitDamageTarget(.source, u, .damage + .damage * ColdAddict.amount[GetUnitUserData(u)], false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
/* If only deal damage for once per unit*/
static if DAMAGE_ONCE then
call GroupAddUnit(.targets, u)
endif
endif
endif
endif
call GroupRemoveUnit(tempGroup, u)
endloop
/* Create scaled sfx at cast point */
set u = CreateUnit(.owner, DUMMY_ID, .x, .y, 0)
call SetUnitScale(u, EXPLODE_SIZE, 1, 1)
call DestroyEffect(AddSpecialEffectTarget(EXPLODE_SFX, u, "origin"))
call UnitApplyTimedLife(u, 'BTLF', SFX_DECAY_TIME)
set u = null
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method check takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call thistype.onCast()
endif
return false
endmethod
endif
static method onInit takes nothing returns nothing
local trigger t
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
else
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.check))
endif
endmethod
endstruct
endscope
/* Frozen Fate spell*/
scope FrozenFate
private struct FrozenFate extends array
implement FrozenFateData
real x
real y
real cDistance
real tDistance
real vertical
real horizontal
real angle
real side
real duration
unit target
player owner
effect sfx
unit dummy
static group Group = CreateGroup()
static constant real HP = bj_PI/2
implement CTL
local real v
local real h
local real x
local real y
local real xt
local real yt
implement CTLExpire
set .cDistance = .cDistance + MISSILE_SPEED
/* Calculate vertical and horizontal offset */
set h = (4 * .horizontal/.tDistance) * (.tDistance - .cDistance) * (.cDistance/.tDistance)
set v = (4 * .horizontal/.tDistance) * (.tDistance - .cDistance) * (.cDistance/.tDistance) + MISSILE_Z
set .x = .x + MISSILE_SPEED * Cos(.angle)
set .y = .y + MISSILE_SPEED * Sin(.angle)
set x = .x + h * Cos(.angle + HP * .side)
set y = .y + h * Sin(.angle + HP * .side)
if isInBound(x, y) then
set xt = GetUnitX(.target)
set yt = GetUnitY(.target)
call SetUnitX(.dummy, x)
call SetUnitY(.dummy, y)
call SetUnitFlyHeight(.dummy, v, 0)
call SetUnitFacing(.dummy, getAngle(x, y, xt, yt) * bj_RADTODEG)
if .cDistance >= .tDistance then
/* If the targeted corpse is still available and reachable */
if IsUnitIndexed(.target) and getDistance(x, y, xt, yt) <= TOLERATION then
call DestroyEffect(AddSpecialEffect(IMPACT_SFX, xt, yt))
call UnitApplyTimedLife(CreateUnit(.owner, SUMMON_ID, xt, yt, GetUnitFacing(.target)), BUFF_ID, .duration)
call RemoveUnit(.target)
endif
call kill()
call destroy()
endif
else
call kill()
call destroy()
endif
implement CTLEnd
method kill takes nothing returns nothing
call DestroyEffect(.sfx)
call RemoveUnit(.dummy)
set .sfx = null
set .target = null
set .dummy = null
endmethod
static method onCast takes nothing returns nothing
local thistype this
local unit f
local unit u = GetTriggerUnit()
local player p = GetTriggerPlayer()
local integer l = GetUnitAbilityLevel(u, SPELL_ID)
local integer c = count(l)
local real d = lifespan(l)
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real xt
local real yt
call GroupEnumUnitsInRange(Group, x, y, aoe(l), null)
loop
set f = FirstOfGroup(Group)
exitwhen f == null
if c > 0 and IsUnitCorpse(f) and filter(f, p) then
set this = create()
set .target = f
set .duration = d
set .owner = p
set .x = x
set .y = y
set xt = GetUnitX(f)
set yt = GetUnitY(f)
set .cDistance = 0
set .tDistance = getDistance(.x, .y, xt, yt)
set .angle = getAngle(.x, .y, xt, yt)
set .vertical = .tDistance * GetRandomReal(V_ARC_MIN, V_ARC_MAX)
set .horizontal = .tDistance * GetRandomReal(H_ARC_MIN, H_ARC_MAX)
set .dummy = CreateUnit(PASSIVE, DUMMY_ID, .x, .y, .angle * bj_RADTODEG)
set .sfx = AddSpecialEffectTarget(MISSILE_SFX, .dummy, "origin")
static if not LIBRARY_AutoFly then
if UnitAddAbility(.dummy, 'Amrf') and UnitRemoveAbility(.dummy, 'Amrf') then
endif
endif
/* Set the horizontal offset side */
if GetRandomInt(1, 100) <= 50 then
set .side = 1
else
set .side = -1
endif
call SetUnitScale(.dummy, MISSILE_SIZE, 1, 1)
call SetUnitFlyHeight(.dummy, MISSILE_Z, 0)
set c = c - 1
endif
call GroupRemoveUnit(Group, f)
endloop
set u = null
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method check takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call thistype.onCast()
endif
return false
endmethod
endif
static method onInit takes nothing returns nothing
local trigger t
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
else
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.check))
endif
endmethod
endstruct
endscope
endscope