scope ArcaneBubble initializer Init
globals
private constant integer SID = 'Abbs'
//The spell rawcode
private constant integer DID = 'acbd'
//The bubble´s rawcode (the upper part)
private constant integer DID2 = 'acb2'
//Also the bubble´s rawcode but the lower part
private constant boolean AFFECT_AIR = false
//Should the bubble also pick up flying units ? (yes = true; no = false)
private constant real BUBBLE_HEIGHT = 750
//How far should the bubble raise in the air ?
private constant real BUBBLE_FLY_SPEED = 250
//How fast should the bubble raise ?
private constant real TARGET_MIN_HEIGHT = 600
//The minimal height of an enemy which is in the bubble.
//The target height is any random number between the the bubble height and this value
private constant real TARGET_FLY_SPEED = 200
//How fast should a target raise ?
private constant real TARGET_FALL_SPEED = 2300
//How fast sould a unit fall back to the ground ?
private constant real LIFT_OFF_RANGE = 400
//The AoE range of the spell
private constant real CRATER_RADIUS = 190
//When a unit falls back onto the ground it creates a small crater. How large shall the radius be ?
private constant real CRATER_DEPTH = 120
//Self explaining
private constant real CRATER_DUR = 0.55
//How long should the create being displayed ?
private constant real BUBBLE_SIZE = 4
//You may want to change the size of the bubble
private constant string IMPACT_SFX = "Abilities\\Spells\\Orc\\WarStomp\\WarStompCaster.mdl"
//The special effect being displayd when a unit hits the ground
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
//_-=-_-=-_-=-_-=-_-=-Spell Privates. Please don´t touch !!_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
private constant real UPDATE_INTER = 0.1
private constant real EFFECT_DELAY = BUBBLE_HEIGHT/BUBBLE_FLY_SPEED - 0.4
private constant real INNER_DIAMETER = 250
private constant timer TIM = CreateTimer()
private constant group SAFE_GROUP = CreateGroup()
private filterfunc LIFT_OFF
private integer TEMP_STRUCT
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
endglobals
//How long should the units stay in the air ? (based on the level ofc)
private constant function StayInAir takes real level returns real
return level * 1.5 + 0.75
endfunction
//How much damage should a unit recieve when it´s hitting the ground ? (based on the level too ofc)
private constant function GetImpactDamage takes real level returns real
return level * 60 + 40
endfunction
//The main struct
private struct Bubble
static thistype array Index
static integer Total = 0
//Static member used for the indexing method
unit caster
unit bubble
unit bubble2
group targets
boolean freeze
real timeout
//Destructor method
method onDestroy takes nothing returns nothing
call RemoveUnit(.bubble)
call RemoveUnit(.bubble2)
call DestroyGroup(.targets)
endmethod
//Creator method
static method create takes unit ca, location loc returns thistype
local thistype this = thistype.allocate()
set .caster = ca
set .bubble = CreateUnit(GetOwningPlayer(ca),DID,GetLocationX(loc),GetLocationY(loc),0)
set .bubble2 = CreateUnit(GetOwningPlayer(ca),DID2,GetLocationX(loc),GetLocationY(loc),0)
set .targets = CreateGroup()
set .timeout = StayInAir(GetUnitAbilityLevel(ca,SID)) + BUBBLE_HEIGHT/BUBBLE_FLY_SPEED
set .freeze = true
set TEMP_STRUCT = this
call GroupEnumUnitsInRange(.targets,GetLocationX(loc),GetLocationY(loc),LIFT_OFF_RANGE,LIFT_OFF)
call SetUnitFlyHeight(.bubble,BUBBLE_HEIGHT,BUBBLE_FLY_SPEED)
call SetUnitFlyHeight(.bubble2,BUBBLE_HEIGHT - INNER_DIAMETER,BUBBLE_FLY_SPEED)
call SetUnitScale(.bubble,BUBBLE_SIZE,BUBBLE_SIZE,BUBBLE_SIZE)
call SetUnitScale(.bubble2,BUBBLE_SIZE,BUBBLE_SIZE,BUBBLE_SIZE)
set thistype.Index[thistype.Total] = this
set thistype.Total = thistype.Total + 1
return this
endmethod
endstruct
//The struct holds the data which is necessary for hitting the ground
private struct Fall
static thistype array Index
static integer Total = 0
//Again some static members for struct indexing
unit target
unit caster
real timeout
//Another Destructor
method onDestroy takes nothing returns nothing
call UnitRemoveAbility(.target,'Amrf')
call PauseUnit(.target,false)
call UnitDamageTarget(.caster,.target,GetImpactDamage(GetUnitAbilityLevel(.caster,SID)),false,false,ATTACK_TYPE_MAGIC,DAMAGE_TYPE_UNKNOWN,WEAPON_TYPE_WHOKNOWS)
call DestroyEffect(AddSpecialEffect(IMPACT_SFX,GetUnitX(.target),GetUnitY(.target)))
call TerrainDeformCrater(GetUnitX(.target),GetUnitY(.target),CRATER_RADIUS,CRATER_DEPTH,R2I(CRATER_DUR * 1000),false)
call GroupRemoveUnit(SAFE_GROUP,.target)
endmethod
//...And another Creator
static method create takes unit ca, unit ta returns thistype
local thistype this = thistype.allocate()
set .target = ta
set .caster = ca
set .timeout = (GetUnitFlyHeight(ta) - GetUnitDefaultFlyHeight(ta)) / TARGET_FALL_SPEED
call SetUnitFlyHeight(ta,GetUnitDefaultFlyHeight(ta),TARGET_FALL_SPEED)
set thistype.Index[thistype.Total] = this
set thistype.Total = thistype.Total + 1
return this
endmethod
endstruct
//Functions which sends all unit (even flying ones) into the air
private function LiftOffAll takes nothing returns boolean
local unit u = GetFilterUnit()
local Bubble this = TEMP_STRUCT
if IsUnitEnemy(u,GetOwningPlayer(this.caster)) and IsUnitInGroup(u,SAFE_GROUP) != true and GetWidgetLife(u) > 0 and IsUnitType(u,UNIT_TYPE_STRUCTURE) !=true and IsUnitType(u, UNIT_TYPE_MECHANICAL) != true and IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) != true and IsUnitType(u, UNIT_TYPE_ANCIENT) != true then
call PauseUnit(u,true)
call UnitAddAbility(u,'Amrf')
call SetUnitFlyHeight(u,GetRandomReal(TARGET_MIN_HEIGHT,BUBBLE_HEIGHT),TARGET_FLY_SPEED)
call GroupAddUnit(SAFE_GROUP,u)
set u = null
return true
endif
set u = null
return false
endfunction
//This function won´t send flying units into the air
private function LiftOffGround takes nothing returns boolean
local unit u = GetFilterUnit()
local Bubble this = TEMP_STRUCT
if IsUnitEnemy(u,GetOwningPlayer(this.caster)) and IsUnitInGroup(u,SAFE_GROUP) != true and IsUnitType(u,UNIT_TYPE_FLYING) != true and GetWidgetLife(u) > 0 and IsUnitType(u,UNIT_TYPE_STRUCTURE) !=true and IsUnitType(u, UNIT_TYPE_MECHANICAL) != true and IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) != true and IsUnitType(u, UNIT_TYPE_ANCIENT) != true then
call PauseUnit(u,true)
call UnitAddAbility(u,'Amrf')
call SetUnitFlyHeight(u,GetRandomReal(TARGET_MIN_HEIGHT,BUBBLE_HEIGHT),TARGET_FLY_SPEED)
call GroupAddUnit(SAFE_GROUP,u)
set u = null
return true
endif
set u = null
return false
endfunction
//Lets all units fall down
private function InitFallDown takes nothing returns nothing
local Bubble this = TEMP_STRUCT
local Fall that = Fall.create(this.caster,GetEnumUnit())
endfunction
//This function is used for the timing...It loops through both structs
private function WaitLoop takes nothing returns nothing
local Bubble bub
local Fall fa
local integer inst = 0
//Looping through the main struct
loop
exitwhen inst == Bubble.Total
set bub = Bubble.Index[inst]
if bub.timeout > 0 then
set bub.timeout = bub.timeout - UPDATE_INTER
if bub.freeze and bub.timeout <= EFFECT_DELAY + StayInAir(GetUnitAbilityLevel(bub.caster,SID)) then
call SetUnitTimeScale(bub.bubble,0)
call SetUnitTimeScale(bub.bubble2,0)
set bub.freeze = false
endif
else
set TEMP_STRUCT = bub
call ForGroup(bub.targets,function InitFallDown)
call bub.destroy()
set Bubble.Total = Bubble.Total - 1
set Bubble.Index[inst] = Bubble.Index[Bubble.Total]
set inst = inst - 1
endif
set inst = inst + 1
endloop
//Looping through the Fall struct
set inst = 0
loop
exitwhen inst == Fall.Total
set fa = Fall.Index[inst]
if fa.timeout > 0 then
set fa.timeout = fa.timeout - UPDATE_INTER
else
call fa.destroy()
set Fall.Total = Fall.Total - 1
set Fall.Index[inst] = Fall.Index[Fall.Total]
set inst = inst -1
endif
set inst = inst + 1
endloop
//If BOTH structs are not running we pause the timer
if Bubble.Total == 0 and Fall.Total == 0 then
call PauseTimer(TIM)
endif
endfunction
//The remains of GUI
private function Actions takes nothing returns nothing
call Bubble.create(GetTriggerUnit(),GetSpellTargetLoc())
if Bubble.Total - 1 == 0 then
call TimerStart(TIM,UPDATE_INTER,true,function WaitLoop)
endif
endfunction
//Checking the spell rawcode
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SID
endfunction
//Prevents leak upon trigger creation
private constant function AntiLeak takes nothing returns boolean
return true
endfunction
//The Initializer
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
local filterfunc f = Filter(function AntiLeak)
local integer i = 0
if AFFECT_AIR then
set LIFT_OFF = Filter(function LiftOffAll)
else
set LIFT_OFF = Filter(function LiftOffGround)
endif
loop
call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,f)
exitwhen i == 15
set i = i + 1
endloop
//Preloading....
call RemoveUnit(CreateUnit(Player(15),DID,0,0,0))
call Preload(IMPACT_SFX)
call PreloadStart()
call TriggerAddCondition(t,Condition(function Conditions))
call TriggerAddAction(t,function Actions)
call DestroyFilter(f)
set t = null
set f = null
endfunction
endscope