//***************************************************
//Wish v1.01 by watermelon_1234
//***************************************************
//Required Libraries: TimerUtils, xebasic, xefx
//***************************************************
scope Wish
native UnitAlive takes unit id returns boolean //Remove this line if it's already implemented.
//===================================================
//CONSTANTS
//===================================================
globals
private constant integer SPELL_ID = 'A000' //The raw id of the Wish ability
private constant real HEIGHT = 1500. //The height the wish should ascend
private constant string WISH_SFX = "Abilities\\Weapons\\FaerieDragonMissile\\FaerieDragonMissile.mdl" //The sfx used for the missile that will orbit the point
private constant real WISH_SPEED = 225. //Determines the speed for the wish and the timer repitition for the Data.wishEffect. A smaller number means a faster wish missile
private constant real TIMER_LOOP = XE_ANIMATION_PERIOD //How often the timer will loop
private constant string STAR_SFX = "Abilities\\Spells\\NightElf\\Starfall\\StarfallTarget.mdl" //The star sfx that will be used for the falling star
private constant real STAR_AREA = 75. //The area for the stars to affect nearby units upon impact.
private constant real STAR_INTERVAL = .25 //The interval between each star creation
private constant real STAR_DELAY = .7 //The delay the star has after being shown before actually doing anything.
private constant string HEAL_SFX = "Abilities\\Spells\\Other\\HealingSpray\\HealBottleMissile.mdl" //The sfx that will play when a unit is healed by the wish
private constant real HEAL_INTERVAL = .4 //The interval between each heal sfx played
private constant integer MAX_ARRAY_SIZE = 6 //Number should be equal to the number of wishes created by the spell
private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL //Attack type for the star damage
private constant damagetype DMG_TYPE = DAMAGE_TYPE_UNIVERSAL //Damage type for the star damage
private constant weapontype WPN_TYPE = null //Weapon type for the star damage
private constant boolean PRELOAD = true //Determines whether or not to preload the spell sfx
endglobals
//===================================================
//Configuration for other parts of the spell
//===================================================
private constant function WishNumber takes integer lvl returns integer //The number of wishes created, purely a visual effect. I suggest that this number increases with the spell area.
return 3+1*lvl
endfunction
private constant function HeightSpeed takes integer lvl returns real //How much time should it take for the wishes to reach the height
return 5.+0*lvl
endfunction
private constant function StarRate takes integer lvl returns integer //The number of stars created depending on how many friendly people there are
return 0+1*lvl
endfunction
private constant function HealNumber takes integer lvl returns integer
return 1+1*lvl
endfunction
private constant function Area takes integer lvl returns real //Determines the area of effect of the spell
return 150.+50*lvl
endfunction
private constant function AreaHeal takes integer lvl returns real //Amount healed per second in the AoE at the beginning of the spell
return 5.+10*lvl
endfunction
private function StarHeal takes integer lvl returns real //Amount healed when friendly units are hit by the star
return 15.+5*lvl
endfunction
private constant function StarDamage takes integer lvl returns real //Damage done to enemy units if hit by the star
return 5.+5*lvl
endfunction
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//CODING OF THE SPELL. Please look at the method filter to determine which units can be healed and damaged.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
globals
private boolexpr e //Used as a filter.
private group GROUP_ENUM //This will be used if GroupUtils isn't there.
endglobals
private struct Data
unit cast //The caster of the spell
real x //Holds the x coordinate of the spell target point
real y //Holds the y coordinate of the spell target point
integer lvl //The level of the ability when it was casted
integer stars = 0 //Used to determine when to stop creating stars by counting how many stars that were created
integer which //For the filter method. 0 - acts for units affected by the wishEffect, 1 - acts for units affected by the starEffect
integer wnumber //Stores the number of wishes that will be made
integer hnumber //Stores the number of heal sfx's that will be played
integer snumber = 0 //Stores the number of stars that will be created based on how many friendly units were in that area
group sgroup = CreateGroup() //This group will be used to find out how many stars to create from the friendly units
real count = 0 //Used to determine the wish's height
real hcount = 0 //Used to determine when to play the heal sfx
real area //Stores the area of effect for the wish
real hspeed //Stores the speed it will take to reach HEIGHT
timer t //Timer used by the spell
xefx array wish[MAX_ARRAY_SIZE] //The missile for the wish effect
real array ang[MAX_ARRAY_SIZE] //The angle for the wish
private static thistype temp //Used to pass information from a specific struct to the filter method.
static method create takes unit c, real x, real y returns thistype //Used for the initial storage and creation of the struct
local thistype this = thistype.allocate()
set .cast = c
set .x = x
set .y = y
set .t = NewTimer()
return this
endmethod
static method filter takes nothing returns boolean //Determines which units will be affected by the wish and star. Also applies the effects on them.
local unit u = GetFilterUnit()
if UnitAlive(u) then //Filter only for alive units
if IsUnitAlly(u,GetOwningPlayer(temp.cast)) and not IsUnitType(u,UNIT_TYPE_MECHANICAL) then //Healing effect
if temp.which == 0 then //Wish part
call SetWidgetLife(u,GetWidgetLife(u)+AreaHeal(temp.lvl)*TIMER_LOOP)
if not IsUnitInGroup(u,temp.sgroup) then //Adds to a group so it won't be recounted
call GroupAddUnit(temp.sgroup,u)
set temp.snumber = temp.snumber + StarRate(temp.lvl)
endif
else //Star part
call SetWidgetLife(u,GetWidgetLife(u)+StarHeal(temp.lvl))
endif
elseif temp.which == 1 and IsUnitEnemy(u,GetOwningPlayer(temp.cast)) then //Star damaging effects
call UnitDamageTarget(temp.cast,u,StarDamage(temp.lvl),false,true,ATK_TYPE,DMG_TYPE,WPN_TYPE)
endif
endif
set u = null
return false //Returns false since we don't need to put the units in the group
endmethod
static method effectStar takes nothing returns nothing //Deals damage and healing after STAR_DELAY seconds
local thistype this = GetTimerData(GetExpiredTimer())
set .which = 1 //Filter will be used for star effect
set temp = this //Sets this so that the struct can be referenced from the static method
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP,.x,.y,STAR_AREA,e)
else
call GroupEnumUnitsInRange(GROUP_ENUM,.x,.y,STAR_AREA,e)
endif
call ReleaseTimer(.t)
call .destroy()
endmethod
static method createStar takes nothing returns nothing //Deals with the creation of the stars
local thistype this = GetTimerData(GetExpiredTimer())
local thistype D
if .stars < .snumber then //Used to check when to stop creating stars
set D = Data.create(.cast,.x + GetRandomReal(-.area/2,.area/2),.y + GetRandomReal(-.area/2,.area/2)) //I decided to stick with having one struct so that it could access the same filter method.
call DestroyEffect(AddSpecialEffect(STAR_SFX,D.x,D.y))
call SetTimerData(D.t,D)
call TimerStart(D.t,STAR_DELAY,false,function thistype.effectStar)
set .stars = .stars + 1
else
call ReleaseTimer(.t)
call .destroy()
endif
endmethod
static method effectWish takes nothing returns nothing //Deals with the wish movements, height, and healing.
local thistype this = GetTimerData(GetExpiredTimer())
local integer i = 0
if .count <= .hspeed then
loop
set .ang[i] = .ang[i] + WISH_SPEED*TIMER_LOOP*.017453278
set .wish[i].x = .x + (.area-.area/.hspeed*.count) * Cos(.ang[i])
set .wish[i].y = .y + (.area-.area/.hspeed*.count) * Sin(.ang[i])
set .wish[i].z = HEIGHT/.hspeed*.count
set i = i + 1
exitwhen i == .wnumber
endloop
set .count = .count + TIMER_LOOP
if .hcount >= HEAL_INTERVAL then
set .hcount = 0
endif
set .which = 0 //Filter will be used for wish effect
set temp = this //Sets this so that the struct can be referenced from the static method
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP,.x,.y,.area,e)
else
call GroupEnumUnitsInRange(GROUP_ENUM,.x,.y,.area,e)
endif
if .hcount == 0 then
set i = 0
loop
call DestroyEffect(AddSpecialEffect(HEAL_SFX,.x+GetRandomReal(-Area(.lvl)/2,Area(.lvl)/2),.y+GetRandomReal(-Area(.lvl)/2,Area(.lvl)/2)))
set i = i + 1
exitwhen i == .hnumber
endloop
endif
set .hcount = .hcount + TIMER_LOOP
else
loop
call .wish[i].destroy() //Destroy the wish missile since it's no longer needed
set i = i + 1
exitwhen i == .wnumber
endloop
call ReleaseTimer(.t)
call GroupClear(.sgroup) //No longer needed.
if .snumber > 0 then
set .t = NewTimer()
call SetTimerData(.t,this)
call TimerStart(.t,STAR_INTERVAL,true,function Data.createStar)
else
call .destroy()
endif
endif
endmethod
static method spellActions takes nothing returns boolean
local thistype this
local integer i = 0
local real wx //For readability
local real wy
if GetSpellAbilityId() == SPELL_ID then
set this = Data.create(GetTriggerUnit(),GetSpellTargetX(),GetSpellTargetY())
set .lvl = GetUnitAbilityLevel(.cast,SPELL_ID)
set .area = Area(.lvl)
set .wnumber = WishNumber(.lvl)
set .hnumber = HealNumber(.lvl)
set .hspeed = HeightSpeed(.lvl)
loop
set wx = .x + Area(.lvl)*Cos(6.28285608/WishNumber(.lvl)*i)
set wy = .y + Area(.lvl)*Sin(6.28285608/WishNumber(.lvl)*i)
set .wish[i] = xefx.create(wx,wy,0)
set .wish[i].fxpath = WISH_SFX
set .ang[i] = Atan2(.y - wy, .x - wx)
set i = i + 1
exitwhen i == WishNumber(.lvl)
endloop
call SetTimerData(.t,this)
call TimerStart(.t,TIMER_LOOP,true,function thistype.effectWish)
endif
return false
endmethod
static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t,Condition( function thistype.spellActions)) //Because trigger conditions are faster than actions
set e = Condition(function thistype.filter) //Sets the filter
//Preload effects
static if PRELOAD then
call Preload(WISH_SFX)
call Preload(STAR_SFX)
call Preload(HEAL_SFX)
endif
static if not LIBRARY_GroupUtils then
set GROUP_ENUM = CreateGroup() //Don't bother creating the group if we'll just use GroupUtils
endif
set t = null //Just to null handles.
endmethod
endstruct
endscope