//===========================================================================
// Chicken Burst spell
// by Garfield1337
//===========================================================================
scope ChickenBurst initializer Init
globals
private constant integer CHICKEN_ID = 'h001' //Raw code of chicken
private constant integer DUMMY_ID = 'h002' //Raw code of dummy
private constant integer EGG_ID = 'h000' //Raw code of egg
private constant integer ABILITY_ID = 'A000' //Raw code of ability
private constant real EGG_SPEED = 24.00 //Egg's flying speed
private constant real CHICKEN_SPEED = 4.00 //Chickens' flying speed
private constant real CHICKEN_HEIGHT = 600.0 //Chickens' max height when flying
private constant real CHICKEN_EXPLOSION_AOE = 160.0 //Radius of area which chickens damage
private constant real CHICKEN_RANGE_MIN = 150.0 //Minimum range chickens fly from egg
private constant real CHICKEN_RANGE_MAX = 350.0 //Maximum range chickens fly from egg
private constant string CHICKEN_EXPLOSION_SFX = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl" //Effect created upon chicken explosion
private constant boolean DAMAGE_ALLIED = false //Should the spell damage allied units?
private constant boolean DAMAGE_ENEMY = true //Should the spell damage enemy units?
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL //Explosion attack type
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL //Explosion damage type
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS //Explosion weapon type
private constant boolean SHOW_ERROR = true //Should an error message appear if target point of spell is invalid?
private constant boolean AFFECT_DESTRUCTABLES = true //Should the explosion affect destructables at all?
private constant boolean DESTROY_DESTRUCTABLES = false //Should the explosion destroy destructables?
private constant boolean TREES_ONLY = false //Should the explosion only affect trees as destructables?
private constant real DESTRUCTABLE_DAMAGE = 40.0 //Damage to destructables if DESTROY_DESTRUCTABLES is false
endglobals
private keyword INVALID_TARGETS //Don't change this line
private function InvalidTargets takes nothing returns nothing
//Which unit types shouldn't be affected by spell
set INVALID_TARGETS[0] = UNIT_TYPE_FLYING
set INVALID_TARGETS[1] = UNIT_TYPE_STRUCTURE
set INVALID_TARGETS[2] = UNIT_TYPE_MAGIC_IMMUNE
endfunction
private function Damage takes integer lvl returns real
//Damage per explosion per level
return 50.0 + 20.0 * lvl
endfunction
private function ChickenAmount takes integer lvl returns integer
//Number of chickens per level
return 5 + 5 * lvl
endfunction
private function ChickenDuration takes nothing returns real
//Time before each chicken explodes
return GetRandomReal(1.00,2.00)
endfunction
//End of configuration part
//===========================================================================
globals
private hashtable Hash = InitHashtable()
private group g = CreateGroup()
private location l = Location(0.00,0.00)
private unittype array INVALID_TARGETS
private group EGGS = CreateGroup()
private group CHICKENS = CreateGroup()
private rect r
private unit t
private real minX
private real minY
private real maxX
private real maxY
endglobals
private function Parabola takes real y0, real y1, real h, real d, real x returns real
local real A = (2*(y0+y1)-4*h)/(d*d)
local real B = (y1-y0-A*d*d)/d //Credits to moyack for the parabola function
return A*x*x + B*x + y0
endfunction
private function Check takes real x,real y returns boolean
return (x >= minX and x <= maxX) and (y >= minY and y <= maxY)
endfunction
private function Cast takes nothing returns boolean
local unit u
local real r1
local real r2
local real r3
local real r4
local real x1
local real y1
local real x2
local real y2
local integer i
if GetSpellAbilityId() == ABILITY_ID then
set x2 = GetSpellTargetX()
set y2 = GetSpellTargetY()
if Check(x2,y2) then
set x1 = GetUnitX(GetTriggerUnit())
set y1 = GetUnitY(GetTriggerUnit())
//Creating egg
set u = CreateUnit(GetOwningPlayer(GetTriggerUnit()),EGG_ID,x1,y1,GetRandomReal(0.00,360.0))
set i = GetHandleId(u)
//Moving the egg on caster's position since it is created beside
call SetUnitX(u,x1)
call SetUnitY(u,y1)
//Enabling the egg to fly by adding and removing crow form
call UnitAddAbility(u,'Amrf')
call UnitRemoveAbility(u,'Amrf')
//Adding a slight height to egg so it doesn't fly off the ground
call SetUnitFlyHeight(u,30.0,0.00)
//Saving angle between caster and target
set r1 = Atan2(y2 - y1,x2 - x1)
call SaveReal(Hash,i,0,r1)
//To prevent the egg from hitting cliff while in air before reaching it's target
//i check height of terrain on egg's way and find the peak (if any)
//then later i add the peak's height to max height of parabola in loop
set r2 = 0
set r3 = SquareRoot(Pow(x2 - x1,2) + Pow(y2 - y1,2))
set r4 = 0
loop
call MoveLocation(l,x1 + r2 * Cos(r1),y1 + r2 * Sin(r1))
if r2 == 0 then
//Saving caster's height for parabola
call SaveReal(Hash,i,1,GetLocationZ(l) + 30.0)
elseif r2 >= r3 then
//Saving target's height for parabola
call SaveReal(Hash,i,2,GetLocationZ(l))
exitwhen true
endif
if GetLocationZ(l) > r4 then
set r4 = GetLocationZ(l)
endif
set r2 = r2 + 32.0
endloop
//Saving peak's height
call SaveReal(Hash,i,3,r4)
//Saving total distance
call SaveReal(Hash,i,4,r3)
//Saving passed distance which is 0
call SaveReal(Hash,i,5,0.00)
//Saving spell level
call SaveInteger(Hash,i,6,GetUnitAbilityLevel(GetTriggerUnit(),ABILITY_ID))
//Saving a custom integer which will be used in loop
call SaveInteger(Hash,i,7,0)
call GroupAddUnit(EGGS,u)
elseif SHOW_ERROR then
call DisplayTimedTextToPlayer(GetTriggerPlayer(),0.00,0.00,5.00,"|c00FF0000Invalid Target!|r")
endif
endif
return false
endfunction
private function Loop1 takes nothing returns nothing
//Egg loop
local unit u1 = GetEnumUnit()
local unit u2
local real r
local real x1
local real y1
local real x2
local real y2
local integer i1 = GetHandleId(u1)
local integer i2 = LoadInteger(Hash,i1,7)
local integer i3 = 0
if i2 == 0 then //At first, the egg is just fly1ing
//Egg movement
set x1 = GetUnitX(u1) + EGG_SPEED * Cos(LoadReal(Hash,i1,0))
set y1 = GetUnitY(u1) + EGG_SPEED * Sin(LoadReal(Hash,i1,0))
call SetUnitX(u1,x1)
call SetUnitY(u1,y1)
call SaveReal(Hash,i1,5,LoadReal(Hash,i1,5) + EGG_SPEED)
call MoveLocation(l,x1,y1)
//Setting egg's fly1ing height with parabola
call SetUnitFlyHeight(u1,Parabola(LoadReal(Hash,i1,1),LoadReal(Hash,i1,2),LoadReal(Hash,i1,4) / 3 + LoadReal(Hash,i1,3),LoadReal(Hash,i1,4),LoadReal(Hash,i1,5)) - GetLocationZ(l),0.00)
if LoadReal(Hash,i1,5) >= LoadReal(Hash,i1,4) then
//Play1ing egg's birth animation to make it look as if it jumped off the ground
call SetUnitAnimation(u1,"birth")
//If the egg hits ground,the custom integer is set to 1 to prevent egg from moving any1more
call SaveInteger(Hash,i1,7,1)
endif
else
set x1 = GetUnitX(u1)
set y1 = GetUnitY(u1)
//The custom integer is now used as a counter to simulate wait function
call SaveInteger(Hash,i1,7,i2 + 1)
if i2 == 20 then //Upon reaching 20 the egg ex1plodes...
set u2 = CreateUnit(GetOwningPlayer(u1),DUMMY_ID,x1,y1,GetRandomReal(0.00,360.0))
call SetUnitX(u2,x1)
call SetUnitY(u2,y1)
//Mine's death spell animation is used as a small ex1plosion for egg
call SetUnitAnimation(u2,"death spell")
call UnitApplyTimedLife(u2,'BTLF',2.00)
elseif i2 == 25 then //...and after a short delay1,chickens burst
loop
set i3 = i3 + 1
//Creating chickens until it hits wanted amount
exitwhen i3 > ChickenAmount(LoadInteger(Hash,i1,6))
//Defining the facing angle of each chicken to make them form a circle
set r = 6.28318 / ChickenAmount(LoadInteger(Hash,i1,6)) * (i3 - 1) + GetRandomReal(0.00,6.28318 / ChickenAmount(LoadInteger(Hash,i1,6)))
set u2 = CreateUnit(GetOwningPlayer(u1),CHICKEN_ID,x1,y1,r * bj_RADTODEG)
call SetUnitX(u2,x1)
call SetUnitY(u2,y1)
set i2 = GetHandleId(u2)
//Enabling the chickens to fly1
call UnitAddAbility(u2,'Amrf')
call UnitRemoveAbility(u2,'Amrf')
//Defining the coordinates for each chicken to fall on
set x2 = x1 + GetRandomReal(CHICKEN_RANGE_MIN,CHICKEN_RANGE_MAX) * Cos(r)
set y2 = y1 + GetRandomReal(CHICKEN_RANGE_MIN,CHICKEN_RANGE_MAX) * Sin(r)
//Saving the egg's height for parabola
call MoveLocation(l,x1,y1)
call SaveReal(Hash,i2,0,GetLocationZ(l))
//Saving target coordinates' height
call MoveLocation(l,x2,y2)
call SaveReal(Hash,i2,1,GetLocationZ(l))
//Saving total distance
call SaveReal(Hash,i2,2,SquareRoot(Pow(x2 - x1,2) + Pow(y2 - y1,2)))
//Saving passed distance
call SaveReal(Hash,i2,3,0.00)
//Saving spell level, inherited from the egg
call SaveInteger(Hash,i2,4,LoadInteger(Hash,i1,6))
call GroupAddUnit(CHICKENS,u2)
endloop
//After chickens are created, the egg is destroy1ed
call KillUnit(u1)
call GroupRemoveUnit(EGGS,u1)
call FlushChildHashtable(Hash,i1)
endif
endif
endfunction
private function Loop2 takes nothing returns nothing
//Chicken loop
local unit u = GetEnumUnit()
local real r1
local real r2
local real x = GetUnitX(u) + CHICKEN_SPEED * Cos(GetUnitFacing(u) * bj_DEGTORAD)
local real y = GetUnitY(u) + CHICKEN_SPEED * Sin(GetUnitFacing(u) * bj_DEGTORAD)
local integer i = GetHandleId(u)
//Chicken movement
call SetUnitX(u,x)
call SetUnitY(u,y)
call MoveLocation(l,x,y)
call SaveReal(Hash,i,3,LoadReal(Hash,i,3) + CHICKEN_SPEED)
//Setting chickens' flying height with parabola
call SetUnitFlyHeight(u,Parabola(LoadReal(Hash,i,0),LoadReal(Hash,i,1),CHICKEN_HEIGHT + RMaxBJ(LoadReal(Hash,i,0),LoadReal(Hash,i,1)),LoadReal(Hash,i,2),LoadReal(Hash,i,3)) - GetLocationZ(l),0.00)
if LoadReal(Hash,i,3) >= LoadReal(Hash,i,2) then
//Upon hitting ground,each chicken is ordered to move to a random point
//The distance is defined by chickens' movement speed and time before they explode
set r1 = ChickenDuration()
set r2 = GetUnitMoveSpeed(u) * r1 * 3
set x = GetUnitX(u) + r2 * Cos(GetRandomReal(0.00,6.28318))
set y = GetUnitY(u) + r2 * Sin(GetRandomReal(0.00,6.28318))
call IssuePointOrder(u,"smart",x,y)
//Applying timed life to chickens to make them explode after the set time
call UnitApplyTimedLife(u,'BTLF',r1)
call GroupRemoveUnit(CHICKENS,u)
endif
endfunction
private function LoopInit takes nothing returns nothing
call ForGroup(EGGS,function Loop1)
call ForGroup(CHICKENS,function Loop2)
endfunction
private function ExplosionFilter takes nothing returns boolean
//Explosion's filter
local unit u1 = GetTriggerUnit()
local unit u2 = GetFilterUnit()
local integer i = 0
local boolean b = true
//Checking whether filter unit is enemy or ally
//whether the corresponding booleans are true or false
//and whether the filter unit is alive
if ((DAMAGE_ENEMY and IsUnitEnemy(u2,GetOwningPlayer(u1))) or (DAMAGE_ALLIED and IsUnitAlly(u2,GetOwningPlayer(u1)))) and IsUnitType(u2, UNIT_TYPE_DEAD) == false then
loop
//Looping through all invalid target types
//If the filter unit belongs to any,it's invalid
exitwhen INVALID_TARGETS[i] == null
if IsUnitType(u2,INVALID_TARGETS[i]) then
set b = false
endif
set i = i + 1
endloop
if b then
//If the filter unit passes all checks, it's damaged
call UnitDamageTarget(u1,u2,Damage(LoadInteger(Hash,GetHandleId(u1),4)),true,false,ATTACK_TYPE,DAMAGE_TYPE,WEAPON_TYPE)
endif
endif
return false
endfunction
static if AFFECT_DESTRUCTABLES then
private function DestructableEnum takes nothing returns nothing
local destructable d = GetFilterDestructable()
local real x
local real y
//Checking destructable life and filtering out dead ones
if GetDestructableLife(d) > 0 then
set x = GetDestructableX(d)
set y = GetDestructableX(d)
//If only trees should be damaged or destroyed, a tree-check is applied
if TREES_ONLY then
call IssueTargetOrder(t,"harvest",d)
if GetUnitCurrentOrder(t) != OrderId("harvest") then
//In case destructable is not a tree, the function ends
call IssueImmediateOrder(t,"stop")
set d = null
return
endif
call IssueImmediateOrder(t,"stop")
endif
if DESTROY_DESTRUCTABLES then
//Destroying destructable if the configurable boolean is true...
call KillDestructable(d)
else
//...if not, it's damaged instead
if DESTRUCTABLE_DAMAGE < GetDestructableLife(d) then
call SetDestructableAnimation(d,"stand hit")
endif
call SetDestructableLife(d,GetDestructableLife(d) - DESTRUCTABLE_DAMAGE)
endif
endif
set d = null
endfunction
endif
private function Death takes nothing returns boolean
local unit u = GetTriggerUnit()
local real x
local real y
if GetUnitTypeId(u) == CHICKEN_ID then
//When a chicken dies, it creates the SFX and enumerates
//the surrounding units and destructables for damage
set x = GetUnitX(u)
set y = GetUnitY(u)
call DestroyEffect(AddSpecialEffect(CHICKEN_EXPLOSION_SFX,GetUnitX(u),GetUnitY(u)))
call GroupEnumUnitsInRange(g,x,y,CHICKEN_EXPLOSION_AOE,Filter(function ExplosionFilter))
//Checking if explosion should affect destructables
static if AFFECT_DESTRUCTABLES then
call MoveRectTo(r,x,y)
call EnumDestructablesInRect(r,null,function DestructableEnum)
endif
call FlushChildHashtable(Hash,GetHandleId(u))
elseif GetUnitTypeId(u) == DUMMY_ID then
//When a dummy dies,it's instantly removed to hide the dying animation
call RemoveUnit(u)
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t1 = CreateTrigger()
local trigger t2 = CreateTrigger()
local integer i = 0
call InvalidTargets()
set t = CreateUnit(Player(15),DUMMY_ID,0.00,0.00,0.00)
call ShowUnit(t,false)
set r = Rect(-CHICKEN_EXPLOSION_AOE,-CHICKEN_EXPLOSION_AOE,CHICKEN_EXPLOSION_AOE,CHICKEN_EXPLOSION_AOE)
set minX = GetRectMinX(bj_mapInitialPlayableArea)
set minY = GetRectMinY(bj_mapInitialPlayableArea)
set maxX = GetRectMaxX(bj_mapInitialPlayableArea)
set maxY = GetRectMaxY(bj_mapInitialPlayableArea)
loop
call TriggerRegisterPlayerUnitEvent(t1,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
call TriggerRegisterPlayerUnitEvent(t2,Player(i),EVENT_PLAYER_UNIT_DEATH,null)
set i = i + 1
exitwhen i == 16
endloop
call TriggerAddCondition(t1,Condition(function Cast))
call TriggerAddCondition(t2,Condition(function Death))
call TimerStart(CreateTimer(),0.03,true,function LoopInit)
set t1 = null
set t2 = null
endfunction
endscope