scope IceTemple initializer Init
// requires optional StunSystem :
// http://www.hiveworkshop.com/forums/jass-resources-412/system-stun-196749/
// This spell assume that the ability "Raven form" ('Amrf') and
// the destructable "Large pathing blocker both" ('YTfc') are not modified too much.
// It may be asynchronous as far as GetLocationZ is.
// However, a slight difference will unlikely trigger a real problem.
// HOW TO IMPORT :
// 1) Download the test map.
// 2) Copy/paste this scope in your map.
// 3) Export/import the 2 models "war3mapImported\IceDrop.mdx" and "war3mapImported\IceTemple.mdx" from the test map.
// 4) You can also get the icons.
// 6) Copy/paste the units "Drop" and "Ice Temple".
// 7) You can also get the "Dummy" unit and the abilities "Ice Temple" and "Frost Nova (Dummy - Ice Temple)". If you don't, make custom abilities to replace them.
// 8) Look the obtained spells and units IDs and configure the scope.
globals
private location LOCZ = Location (0,0)
private group LOCALGROUP = CreateGroup()
private hashtable THEHASH
sound SubmergeSound
private real array FrozenDamages // Over-time damages when frozen
private real array ExtraDuration // Time spent to freeze units
private real array FreezeTimeout // Delay between 2 freezing
private real array SpellRadius // Spell's base radius
endglobals
//=====================================//
// //
// CONFIGURATION BLOC //
// //
//=====================================//
globals
private constant integer SPELL_ID = 'A000' // The castable spell
private constant integer NOVA_ID = 'A001' // A Frost Nova (or any other unit-targeting spell) for dummy, dealing the ending damages ; it should have as many levels as the main spell
private constant integer DUMMY_TEMPLE_ID = 'n000' // The Id of the Temple dummy
private constant integer DUMMY_ID = 'n001' // A dummy Id for casting Frost Novas
private constant integer DUMMY_DROP_ID = 'n002' // The Id of the Drop dummy
private constant string NOVA_ORDER = "frostnova" // Adapt this field to the NOVA_ID spell.
private constant boolean MOVE_CASTER = true // Set it to false if you don't want the caster to be involved in the spell.
// If MOVE_CASTER is false, the following 3 globals are unused.
private constant string CASTER_ANIM_CAST = "stand victory"
private constant string CASTER_ANIM_CHANNEL = "spell"
private constant string CASTER_ANIM_JUMP = "attack"
// Those 4 globals shouldn't be modified in most cases.
private constant real TIMEOUT = 0.04 // A timeout value for fast periodic timers.
private constant real MAXHEIGHT = 175 // The height doesn't depend on the model's scale
private constant real MODELSCALEFACTOR = 1.18 // ModelScale = SpellRadius * MODELSCALEFACTOR / 450
private constant real NOVARADIUSFACTOR = 1.3 // NovaRadius = SpellRadius * NOVARADIUSFACTOR
endglobals
private function SetupDatas takes nothing returns nothing
// You can use a custom hashtable here instead of creating a new one.
// The spell only uses timers' Ids as parent keys.
set THEHASH = InitHashtable()
// Set up the datas per level.
// Final damage (and slow) are supposed to be in the "NOVA_ID" spell.
set FrozenDamages[1] = TIMEOUT * 20
set ExtraDuration[1] = 1.5
set FreezeTimeout[1] = 0.25
set SpellRadius[1] = 300 // The model looks bad if spell radius is too far from 380.
set FrozenDamages[2] = TIMEOUT * 25
set ExtraDuration[2] = 2.5
set FreezeTimeout[2] = 0.25
set SpellRadius[2] = 380
set FrozenDamages[3] = TIMEOUT * 30
set ExtraDuration[3] = 3
set FreezeTimeout[3] = 0.25
set SpellRadius[3] = 460
endfunction
// Additional filters for targetable units. The spell also checks if :
// - the target is alive,
// - the target's height is lower than the spell's height,
// - the target hasn't already been trapped or hit by a Frost Nova.
private function TrapFilter takes unit caster, unit target returns boolean
return IsUnitEnemy(target,GetOwningPlayer(caster)) /*
*/ and not(IsUnitType(target,UNIT_TYPE_STRUCTURE)) /*
*/ and not(IsUnitType(target,UNIT_TYPE_MAGIC_IMMUNE))
endfunction
private function EndingNovaFilter takes unit caster, unit target returns boolean
return IsUnitEnemy(target,GetOwningPlayer(caster)) /*
*/ and not(IsUnitType(target,UNIT_TYPE_STRUCTURE)) /*
*/ and not(IsUnitType(target,UNIT_TYPE_MAGIC_IMMUNE))
endfunction
//=====================================//
// //
// END OF CONFIGURATION BLOC //
// //
//=====================================//
// You can make this function public.
private function GetPointZ takes real x, real y returns real
call MoveLocation(LOCZ,x,y)
return GetLocationZ(LOCZ)
endfunction
private function RemoveUnitAfterWaitCallback takes nothing returns nothing
local timer t=GetExpiredTimer()
call RemoveUnit(LoadUnitHandle(THEHASH,GetHandleId(t),0))
call FlushChildHashtable(THEHASH,GetHandleId(t))
call DestroyTimer(t)
set t=null
endfunction
// You can make this function public.
private function RemoveUnitAfterWait takes unit whichUnit, real wait returns nothing
local timer t=CreateTimer()
call SaveUnitHandle(THEHASH,GetHandleId(t),0,whichUnit)
call TimerStart(t,wait,false,function RemoveUnitAfterWaitCallback)
set t=null
endfunction
private function PauseUnitSafeCallback takes nothing returns nothing
local timer t=GetExpiredTimer()
local unit u=LoadUnitHandle(THEHASH,GetHandleId(t),0)
static if LIBRARY_StunSystem then
call Stun.apply(u,9999,false)
else
call PauseUnit(u,true)
endif
call SetUnitX(u,LoadReal(THEHASH,GetHandleId(t),1))
call SetUnitY(u,LoadReal(THEHASH,GetHandleId(t),2))
call SetUnitAnimation(u,CASTER_ANIM_CAST)
call QueueUnitAnimation(u,CASTER_ANIM_CAST)
call FlushChildHashtable(THEHASH,GetHandleId(t))
call DestroyTimer(t)
set t=null
set u=null
endfunction
private function PauseUnitSafe takes unit whichUnit returns nothing
local timer t=CreateTimer()
call SaveUnitHandle(THEHASH,GetHandleId(t),0,whichUnit)
call SaveReal(THEHASH,GetHandleId(t),1,GetUnitX(whichUnit))
call SaveReal(THEHASH,GetHandleId(t),2,GetUnitY(whichUnit))
call TimerStart(t,0,false,function PauseUnitSafeCallback)
set t=null
endfunction
private function ClearMainDatas takes timer t returns nothing
local integer Id=GetHandleId(t)
call RemoveUnitAfterWait(LoadUnitHandle(THEHASH,Id,2),2)
call DestroyGroup(LoadGroupHandle(THEHASH,Id,5))
call DestroyGroup(LoadGroupHandle(THEHASH,Id,6))
call RemoveDestructable(LoadDestructableHandle(THEHASH,Id,10))
call RemoveDestructable(LoadDestructableHandle(THEHASH,Id,11))
call RemoveDestructable(LoadDestructableHandle(THEHASH,Id,12))
call RemoveDestructable(LoadDestructableHandle(THEHASH,Id,13))
call FlushChildHashtable(THEHASH,Id)
call DestroyTimer(t)
endfunction
private function FinalDamagesEnum takes unit caster,group targets returns nothing
local integer Id=GetHandleId(GetExpiredTimer())
local unit dummy
local unit u
local timer t
loop
set u=FirstOfGroup(targets)
exitwhen u==null
call GroupRemoveUnit(targets,u)
if IsUnitInGroup(u,LoadGroupHandle(THEHASH,Id,5)) then
set t=LoadTimerHandle(THEHASH,Id,GetHandleId(u))
static if LIBRARY_StunSystem then
call Stun.stop(u)
else
call PauseUnit(u,false)
endif
call SetUnitTimeScale(u,1)
call GroupRemoveUnit(LoadGroupHandle(THEHASH,Id,5),u)
call RemoveUnit(LoadUnitHandle(THEHASH,GetHandleId(t),2))
call DestroyEffect(LoadEffectHandle(THEHASH,GetHandleId(t),4))
call FlushChildHashtable(THEHASH,GetHandleId(t))
call DestroyTimer(t)
set t=null
endif
if not IsUnitInGroup(u,LoadGroupHandle(THEHASH,Id,6)) and GetWidgetLife(u)>0.405 and GetPointZ(GetUnitX(u),GetUnitY(u))+GetUnitFlyHeight(u)<=LoadReal(THEHASH,Id,3)+MAXHEIGHT and EndingNovaFilter(caster,u) then
set dummy=CreateUnit(GetOwningPlayer(caster),DUMMY_ID,GetUnitX(u),GetUnitY(u),0)
call UnitAddAbility(dummy,NOVA_ID)
call SetUnitAbilityLevel(dummy,NOVA_ID,LoadInteger(THEHASH,Id,1))
call IssueTargetOrder(dummy,NOVA_ORDER,u)
call RemoveUnitAfterWait(dummy,1)
call GroupAddUnit(LoadGroupHandle(THEHASH,Id,6),u)
endif
endloop
set dummy=null
endfunction
private function FinalDamages takes nothing returns nothing
local integer Id=GetHandleId(GetExpiredTimer())
local real timeleft=LoadReal(THEHASH,Id,4)-TIMEOUT
local unit caster
local unit u
call SaveReal(THEHASH,Id,4,timeleft)
call GroupEnumUnitsInRange(LOCALGROUP,GetUnitX(LoadUnitHandle(THEHASH,Id,2)),GetUnitY(LoadUnitHandle(THEHASH,Id,2)),(1-timeleft/1.4)*SpellRadius[LoadInteger(THEHASH,Id,1)]*NOVARADIUSFACTOR,null)
call FinalDamagesEnum(LoadUnitHandle(THEHASH,Id,0),LOCALGROUP)
if timeleft<=0 then
call FinalDamagesEnum(LoadUnitHandle(THEHASH,Id,0),LoadGroupHandle(THEHASH,Id,5))
call ClearMainDatas(GetExpiredTimer())
endif
endfunction
private function FinalMove takes nothing returns nothing
local integer Id=GetHandleId(GetExpiredTimer())
local unit caster=LoadUnitHandle(THEHASH,Id,0)
local real x=GetUnitX(caster)
local real y=GetUnitY(caster)
local real facing=GetUnitFacing(caster)*bj_DEGTORAD
local real speedfactor=SpellRadius[LoadInteger(THEHASH,Id,1)]*MODELSCALEFACTOR/450
local real timeleft=LoadReal(THEHASH,Id,4)-TIMEOUT
call SaveReal(THEHASH,Id,4,timeleft)
static if not MOVE_CASTER then
if timeleft>=2 then
call SaveReal(THEHASH,Id,4,-1)
call TimerStart(GetExpiredTimer(),1.44,false,function FinalMove)
return
else
call SaveReal(THEHASH,Id,4,1.4)
call SaveGroupHandle(THEHASH,Id,6,CreateGroup())
call TimerStart(GetExpiredTimer(),TIMEOUT,true,function FinalDamages)
endif
return
else
if timeleft>=0.78 then
if timeleft>=2 then
call SaveReal(THEHASH,Id,4,1.04)
call TimerStart(GetExpiredTimer(),TIMEOUT,true,function FinalMove)
return
endif
call SetUnitX(caster,x+770*speedfactor*TIMEOUT*Cos(facing))
call SetUnitY(caster,y+770*speedfactor*TIMEOUT*Sin(facing))
call SetUnitFlyHeight(caster,280+LoadReal(THEHASH,Id,3)-GetPointZ(x,y)+(1.04-timeleft)*231*speedfactor,0)
elseif timeleft>=0.52 then
call SetUnitTimeScale(caster,1)
call SetUnitX(caster,x+461*speedfactor*TIMEOUT*Cos(facing))
call SetUnitY(caster,y+461*speedfactor*TIMEOUT*Sin(facing))
call SetUnitFlyHeight(caster,280+LoadReal(THEHASH,Id,3)-GetPointZ(x,y)+(240-timeleft*231)*speedfactor,0)
elseif timeleft>=0.26 then
call SetUnitX(caster,x+309*speedfactor*TIMEOUT*Cos(facing))
call SetUnitY(caster,y+309*speedfactor*TIMEOUT*Sin(facing))
call SetUnitFlyHeight(caster,280+LoadReal(THEHASH,Id,3)-GetPointZ(x,y)+(timeleft*77+80)*speedfactor,0)
elseif timeleft>=0 then
call SetUnitX(caster,x+193*speedfactor*TIMEOUT*Cos(facing))
call SetUnitY(caster,y+193*speedfactor*TIMEOUT*Sin(facing))
call SetUnitFlyHeight(caster,280+LoadReal(THEHASH,Id,3)-GetPointZ(x,y)+(timeleft*461-20)*speedfactor,0)
elseif timeleft>-0.4 then
call SetUnitFlyHeight(caster,280+LoadReal(THEHASH,Id,3)-GetPointZ(x,y)+timeleft*650,0)
else
call SetUnitFlyHeight(caster,0,0)
static if LIBRARY_StunSystem then
call Stun.stop(caster)
else
call PauseUnit(caster,false)
endif
call SetUnitInvulnerable(caster,false)
call SetUnitPosition(caster,x,y)
call SaveReal(THEHASH,Id,4,1.4)
call SaveGroupHandle(THEHASH,Id,6,CreateGroup())
call TimerStart(GetExpiredTimer(),TIMEOUT,true,function FinalDamages)
endif
endif
set caster=null
endfunction
private function StartFinal takes nothing returns nothing
local integer Id=GetHandleId(GetExpiredTimer())
local unit caster=LoadUnitHandle(THEHASH,Id,0)
static if MOVE_CASTER then
call SetUnitAnimation(caster,CASTER_ANIM_JUMP)
call QueueUnitAnimation(caster,"stand")
call SetUnitTimeScale(caster,0.5)
endif
call SetUnitAnimation(LoadUnitHandle(THEHASH,Id,2),"death")
call FlushChildHashtable(THEHASH,GetHandleId(LoadTimerHandle(THEHASH,Id,6)))
call DestroyTimer(LoadTimerHandle(THEHASH,Id,6))
call SaveReal(THEHASH,Id,4,3)
call TimerStart(GetExpiredTimer(),1.26,false,function FinalMove)
endfunction
private function TrapUnitLoop takes nothing returns nothing
local integer Id=GetHandleId(GetExpiredTimer())
local integer Idm=LoadInteger(THEHASH,Id,0)
local unit target=LoadUnitHandle(THEHASH,Id,1)
local real x
local real y
if LoadBoolean(THEHASH,Id,3) then
set x=GetUnitX(LoadUnitHandle(THEHASH,Id,2))-GetUnitX(target)
set y=GetUnitY(LoadUnitHandle(THEHASH,Id,2))-GetUnitY(target)
// If the unit flees to far or go in transport, we cancel the freeze.
if x*x+y*y<=90000 and not IsUnitHidden(target) then
call SaveBoolean(THEHASH,Id,3,false)
static if LIBRARY_StunSystem then
call Stun.apply(target,9999,false)
else
call PauseUnit(target,true)
endif
call SetUnitTimeScale(target,0)
call TimerStart(GetExpiredTimer(),TIMEOUT,true,function TrapUnitLoop)
else
call DestroyEffect(LoadEffectHandle(THEHASH,Id,4))
call RemoveUnit(LoadUnitHandle(THEHASH,Id,2))
call GroupRemoveUnit(LoadGroupHandle(THEHASH,Idm,5),target)
call FlushChildHashtable(THEHASH,Id)
call DestroyTimer(GetExpiredTimer())
endif
elseif GetWidgetLife(target)>FrozenDamages[LoadInteger(THEHASH,Idm,1)]*2 then
call UnitDamageTarget(LoadUnitHandle(THEHASH,Idm,0),target,FrozenDamages[LoadInteger(THEHASH,Idm,1)],true,false,ATTACK_TYPE_MAGIC,DAMAGE_TYPE_UNIVERSAL,WEAPON_TYPE_WHOKNOWS)
endif
set target=null
endfunction
private function TrapUnit2 takes nothing returns nothing
call SaveEffectHandle(THEHASH,GetHandleId(GetExpiredTimer()),4,AddSpecialEffectTarget("Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdx",LoadUnitHandle(THEHASH,GetHandleId(GetExpiredTimer()),1),"origin"))
call TimerStart(GetExpiredTimer(),0.3,false,function TrapUnitLoop)
endfunction
private function TrapUnit takes nothing returns nothing
local integer Id=LoadInteger(THEHASH,GetHandleId(GetExpiredTimer()),0)
local group g=LoadGroupHandle(THEHASH,Id,5)
local real x
local real y
local unit target
local unit drop
local timer t
call GroupEnumUnitsInRange(LOCALGROUP,GetUnitX(LoadUnitHandle(THEHASH,Id,2)),GetUnitY(LoadUnitHandle(THEHASH,Id,2)),SpellRadius[LoadInteger(THEHASH,Id,1)],null)
loop
set target=FirstOfGroup(LOCALGROUP)
exitwhen target==null or (not IsUnitInGroup(target,g) and GetWidgetLife(target)>0.405 and GetPointZ(GetUnitX(target),GetUnitY(target))+GetUnitFlyHeight(target)<=LoadReal(THEHASH,Id,3)+MAXHEIGHT and TrapFilter(LoadUnitHandle(THEHASH,Id,0),target))
call GroupRemoveUnit(LOCALGROUP,target)
endloop
call GroupClear(LOCALGROUP)
if target!=null then
set x=GetUnitX(target)
set y=GetUnitY(target)
call GroupAddUnit(g,target)
call SetSoundPosition(SubmergeSound,x,y,200)
call StartSound(SubmergeSound)
set drop=CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),DUMMY_DROP_ID,x,y,0)
call UnitAddAbility(drop,'Amrf')
call UnitRemoveAbility(drop,'Amrf')
call SetUnitX(drop,x)
call SetUnitY(drop,y)
call SetUnitFlyHeight(drop,LoadReal(THEHASH,Id,3)-GetPointZ(x,y),0)
call SetUnitAnimation(drop,"death")
set t=CreateTimer()
call SaveInteger(THEHASH,GetHandleId(t),0,Id)
call SaveUnitHandle(THEHASH,GetHandleId(t),1,target)
call SaveUnitHandle(THEHASH,GetHandleId(t),2,drop)
call SaveBoolean(THEHASH,GetHandleId(t),3,true)
call SaveTimerHandle(THEHASH,Id,GetHandleId(target),t)
call TimerStart(t,0.4,false,function TrapUnit2)
set t=null
set drop=null
set target=null
endif
set g=null
endfunction
private function StartFreezing takes nothing returns nothing
local timer t=CreateTimer()
local integer Id=GetHandleId(GetExpiredTimer())
local integer level=LoadInteger(THEHASH,Id,1)
call SaveGroupHandle(THEHASH,Id,5,CreateGroup())
call SaveTimerHandle(THEHASH,Id,6,t)
call SaveInteger(THEHASH,GetHandleId(t),0,Id)
call TimerStart(t,FreezeTimeout[level],true,function TrapUnit)
call TimerStart(GetExpiredTimer(),ExtraDuration[level],false,function StartFinal)
set t=null
endfunction
private function MoveHeroTop takes nothing returns nothing
local integer Id=GetHandleId(GetExpiredTimer())
local unit caster=LoadUnitHandle(THEHASH,Id,0)
local real factor=SpellRadius[LoadInteger(THEHASH,Id,1)]*MODELSCALEFACTOR/450
local real timeleft=LoadReal(THEHASH,Id,4)-TIMEOUT
static if MOVE_CASTER then
call SetUnitFlyHeight(caster,RMaxBJ(0,280+LoadReal(THEHASH,Id,3)-GetPointZ(GetUnitX(caster),GetUnitY(caster))-timeleft*200*factor),0)
endif
if timeleft>0 then
call SaveReal(THEHASH,Id,4,timeleft)
else
static if MOVE_CASTER then
call SetUnitAnimation(caster,CASTER_ANIM_CHANNEL)
call QueueUnitAnimation(caster,CASTER_ANIM_CHANNEL)
call QueueUnitAnimation(caster,CASTER_ANIM_CHANNEL)
endif
call TimerStart(GetExpiredTimer(),1.8,false,function StartFreezing)
endif
set caster=null
endfunction
private function CreateTemple takes unit caster, integer level returns nothing
local timer t=CreateTimer()
local integer Id=GetHandleId(t)
local real ux=GetUnitX(caster)
local real uy=GetUnitY(caster)
local real radius=SpellRadius[level]*MODELSCALEFACTOR
local real angle=GetUnitFacing(caster)*bj_DEGTORAD // x, y and angle with which the temple is created.
local real x=ux+radius*Cos(angle)
local real y=uy+radius*Sin(angle)
local unit temple=CreateUnit(GetOwningPlayer(caster),DUMMY_TEMPLE_ID,x,y,angle*bj_RADTODEG+180)
call UnitAddAbility(temple,'Amrf')
call UnitRemoveAbility(temple,'Amrf')
call SetUnitX(temple,x)
call SetUnitY(temple,y)
call SetUnitFlyHeight(temple,260,0)
call SetUnitScale(temple,radius/450,1,1)
call SetUnitAnimation(temple,"birth")
static if MOVE_CASTER then
call SetUnitInvulnerable(caster,true)
call PauseUnitSafe(caster)
call UnitAddAbility(caster,'Amrf')
call UnitRemoveAbility(caster,'Amrf')
endif
call SaveUnitHandle(THEHASH,Id,0,caster)
call SaveInteger(THEHASH,Id,1,level)
call SaveUnitHandle(THEHASH,Id,2,temple)
call SaveReal(THEHASH,Id,3,GetPointZ(x,y))
call SaveReal(THEHASH,Id,4,2)
call SaveDestructableHandle(THEHASH,Id,10,CreateDestructable('YTfc',ux,uy,0,1,0))
call SaveDestructableHandle(THEHASH,Id,11,CreateDestructable('YTfc',x+radius*Cos(angle),y+radius*Sin(angle),0,1,0))
call SaveDestructableHandle(THEHASH,Id,12,CreateDestructable('YTfc',x+radius*Cos(angle+bj_PI*0.5),y+radius*Sin(angle+bj_PI*0.5),0,1,0))
call SaveDestructableHandle(THEHASH,Id,13,CreateDestructable('YTfc',x+radius*Cos(angle-bj_PI*0.5),y+radius*Sin(angle-bj_PI*0.5),0,1,0))
call TimerStart(t,TIMEOUT,true,function MoveHeroTop)
set temple=null
set t=null
endfunction
private function CastingFilter takes nothing returns boolean
if GetSpellAbilityId()==SPELL_ID then
call CreateTemple(GetTriggerUnit(),GetUnitAbilityLevel(GetTriggerUnit(),SPELL_ID))
endif
return false
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 CastingFilter) )
set SubmergeSound = CreateSound( "Abilities\\Spells\\Other\\Submerge\\Submerge1.wav", false, true, true, 10, 10, "" )
call SetSoundParamsFromLabel( SubmergeSound, "SubmergeSound" )
call SetupDatas()
endfunction
endscope