//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
Attribute | real | No | |
Level | real | No | |
SpellCache | gamecache | No | |
SystemCache | gamecache | No | |
Target | unit | No |
//TESH.scrollpos=72
//TESH.alwaysfold=0
library InvokerConfig initializer Init
// Requires nothing.
//===============================================================================
// C O N F I G U R E E V E R Y T H I N G H E R E I F N E C E S S A R Y
//===============================================================================
globals
// Raw code of your Hero that is going to have all of the Invoker's spells.
constant integer UID_INVOKER = 'H000'
// Raw code of your generic dummy unit.
constant integer UID_DUMMY = 'e000'
// The EMP unit that is created at the target point of an EMP cast.
constant integer UID_EMP = 'e001'
// The EMP Blast unit that is created when the ball explodes, drains mana and deals damage.
// It uses an unit because this way it can get scaled to a much bigger size.
constant integer UID_EMP_BLAST = 'e002'
// Raw code of the Chaos Meteor unit (the unit that is supposed to "roll").
constant integer UID_CHAOS_METEOR = 'e003'
// Raw code of the Chaos Meteor Falling unit (the meteor that falls from the sky).
constant integer UID_CHAOS_METEOR_FALLING = 'e004'
// Raw code of the Tornado unit.
constant integer UID_TORNADO = 'e005'
// Raw code of the Deafening Blast unit.
constant integer UID_DEAFENING_BLAST = 'e006'
// Raw code of the Ice Wall unit.
constant integer UID_ICE_WALL = 'e007'
//===============================================================================
// Order string of casting the ability Cold Snap Ministun.
constant string OID_COLD_SNAP_MINISTUN = "thunderbolt"
// Order string of casting the ability Tornado Cyclone.
constant string OID_TORNADO_CYCLONE = "cyclone"
// Order string for casting the ability Deafening Blast Silence.
constant string OID_DEAFENING_BLAST_SILENCE = "soulburn"
// Order string for casting the ability Ghost Walk Self Slow.
constant string OID_GHOST_WALK_SELF_SLOW = "slow"
//===============================================================================
// These are the raw codes of the abilities that give the sphere effect.
// Reference them as Quas Sphere First/Second/Third and fill in their raw code accordingly.
// Raw code of the spell Quas Sphere First
constant integer AID_QUAS_FIRST = 'A008'
// Raw code of Quas Sphere Second... etc.
constant integer AID_QUAS_SECOND = 'A009'
constant integer AID_QUAS_THIRD = 'A00A'
// Reference these as Wex Sphere First/Second/Third and fill in their raw codes accordingly.
constant integer AID_WEX_FIRST = 'A005'
constant integer AID_WEX_SECOND = 'A006'
constant integer AID_WEX_THIRD = 'A007'
// Reference these as Exort Sphere First/Second/Third and fill their raw codes accordingly.
constant integer AID_EXORT_FIRST = 'A002'
constant integer AID_EXORT_SECOND = 'A003'
constant integer AID_EXORT_THIRD = 'A004'
// Raw code of the Hero ability Quas.
constant integer AID_QUAS = 'A00D'
// Raw code of the Hero ability Wex.
constant integer AID_WEX = 'A00C'
// Raw code of the Hero ability Exort.
constant integer AID_EXORT = 'A00B'
// Raw code of the Hero ability Invoke.
constant integer AID_INVOKE = 'A00E'
//===============================================================================
// Buff raw code of the ability Ghost Walk.
constant integer BID_GHOST_WALK = 'B004'
// Buff raw code of the ability Ghost Walk Self Slow.
constant integer BID_GHOST_WALK_SELF_SLOW = 'B003'
//===============================================================================
// Raw codes of the abilities that gives movement/attack speed bonuses when Wex is in use.
constant integer AID_WEX_MOVEMENT_SPEED_BONUS = 'A00I'
constant integer AID_WEX_ATTACK_SPEED_BONUS = 'A00J'
// Raw code of the spellbook that contains the movement speed bonus by Wex.
constant integer AID_WEX_SPELLBOOK = 'A00K'
// Raw code of the ability that gives damage bonuses when Exort is in use.
constant integer AID_EXORT_BONUS = 'A00F'
// Raw code of the Cold Snap ability.
constant integer AID_COLD_SNAP = 'A00G'
// Raw code of the dummy ability that is used to ministun and deal damage (Cold Snap Ministun).
constant integer AID_COLD_SNAP_MINISTUN = 'A00H'
// Raw code of the EMP ability.
constant integer AID_EMP = 'A00L'
// Raw code of the Sun Strike ability.
constant integer AID_SUN_STRIKE = 'A00N'
// Raw code of the Chaos Meteor ability.
constant integer AID_CHAOS_METEOR = 'A00M'
// Raw code of the Alacrity ability.
constant integer AID_ALACRITY = 'A00O'
// Raw code of the ability that gives damage (Alacrity Damage Bonus).
constant integer AID_ALACRITY_DAMAGE_BONUS = 'A00P'
// Raw code of the ability that gives attack speed (Alacrity Speed Bonus).
constant integer AID_ALACRITY_SPEED_BONUS = 'A00Q'
// Raw code of the ability Tornado.
constant integer AID_TORNADO = 'A00R'
// Raw code of the ability Tornado Cyclone that cyclones affected units.
constant integer AID_TORNADO_CYCLONE = 'A00S'
// Raw code of the ability Deafening Blast.
constant integer AID_DEAFENING_BLAST = 'A00W'
// Raw code of the ability Deafening Blast Disable Attack that is supposed to disable a unit's attack.
// Based on Cargo Hold (Orc Burrow).
constant integer AID_DEAFENING_BLAST_DISABLE_ATTACK = 'A00T'
// Raw code of the ability Deafening Blast Silence.
constant integer AID_DEAFENING_BLAST_SILENCE = 'A00V'
// Raw code of the Ice Wall ability.
constant integer AID_ICE_WALL = 'A00X'
// Raw code of the Ice Wall Slow Aura ability.
constant integer AID_ICE_WALL_SLOW_AURA = 'A00Y'
// Raw code of the Ghost Walk ability.
constant integer AID_GHOST_WALK = 'A00Z'
// Raw code of the Ghost Walk Aura ability.
constant integer AID_GHOST_WALK_AURA = 'A010'
// Raw code of the ability Ghost Walk Self Slow.
constant integer AID_GHOST_WALK_SELF_SLOW = 'A011'
// Raw code of the ability Forge Spirit.
constant integer AID_FORGE_SPIRIT = 'A012'
// Raw code of the ability Forge Spirit Armor Melt.
constant integer AID_FORGE_SPIRIT_ARMOR_MELT = 'A013'
// Raw code of the ability Forge Spirit Armor Bonus.
constant integer AID_FORGE_SPIRIT_ARMOR_BONUS = 'A014'
// Raw code of the ability Forge Spirit Mana Bonus.
constant integer AID_FORGE_SPIRIT_MANA_BONUS = 'A015'
endglobals
//===============================================================================
// Since real numbers cannot be used in life regeneration spells, Quas' regeneration
// has to be triggered for special cases like these.
public constant function QUAS_REGENERATION takes integer level returns real
return 0.75 * level
// Each level gives 0.75 regeneration PER ONE INSTANCE.
// If you have 3 instances you would get 2.25 hp/sec.
// And 15.75 for 3 instances and maximum level.
endfunction
// Choose a limitation to which levels to make it invoke only 1 spell.
// If you, for example, returned 3 it will allow 2 invokes only at level 4 and above.
public constant function LIMIT takes integer level returns integer
return 1
endfunction
globals
// DO NOT TOUCH OR CHANGE THIS INTEGER ARRAY!
integer array SPIRIT
endglobals
// Edit this by adding or changing raw codes and array indexes.
// REMEMBER TO ALWAYS START FROM SPIRIT[1] AND INCREASE BY ONE WITHOUT SKIPPING AN INDEX!
// For example: SPIRIT[1], SPIRIT[3], SPIRIT[4] <---- WRONG.
// The index represents what spirit will be spawned at that level.
// For example: Spirit[3] - A spirit with raw code 'n002' will be spawned.
private function SetForgeSpiritData takes nothing returns nothing
// Level 1.
set SPIRIT[1] = 'n000'
// Level 2.
set SPIRIT[2] = 'n001'
// Level 3.
set SPIRIT[3] = 'n002'
// etc.
set SPIRIT[4] = 'n003'
set SPIRIT[5] = 'n004'
set SPIRIT[6] = 'n005'
set SPIRIT[7] = 'n006'
endfunction
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
private function Init takes nothing returns nothing
local integer a = 0
// Make the spellbook hidden so the bonuses' icons can't be seen.
loop
exitwhen a > 11
call SetPlayerAbilityAvailable(Player(a), AID_WEX_SPELLBOOK, false)
set a = a + 1
endloop
call SetForgeSpiritData()
endfunction
endlibrary
//TESH.scrollpos=18
//TESH.alwaysfold=0
library InvokerPreload initializer Init requires InvokerConfig, AbilityPreload
// Look above what it requires.
//===============================================================================
// THIS IS AN OPTIONAL LIBRARY THAT WILL PRELOAD ALL OF THE INVOKER'S RESOURCES
// AT MAP INITIALIZATION. IF YOU WANT YOU CAN COPY IT IN YOUR MAP HAVING IN MIND
// THAT IT WOULD CAUSE A SPIKE JUST AT THE START DUE TO THE BIG AMOUNTS OF DATA.
// IT IS OPTIONAL BECAUSE YOU MAY WANT TO PRELOAD YOUR STUFF IN A DIFFERENT WAY.
//===============================================================================
private function Init takes nothing returns nothing
local integer a = 0
call AbilityPreload(AID_QUAS_FIRST)
call AbilityPreload(AID_QUAS_SECOND)
call AbilityPreload(AID_QUAS_THIRD)
call AbilityPreload(AID_WEX_FIRST)
call AbilityPreload(AID_WEX_SECOND)
call AbilityPreload(AID_WEX_THIRD)
call AbilityPreload(AID_EXORT_FIRST)
call AbilityPreload(AID_EXORT_SECOND)
call AbilityPreload(AID_EXORT_THIRD)
call AbilityPreload(AID_QUAS)
call AbilityPreload(AID_WEX)
call AbilityPreload(AID_EXORT)
call AbilityPreload(AID_INVOKE)
call AbilityPreload(AID_WEX_ATTACK_SPEED_BONUS)
// Uknown why but preloading these at Map Initialization crashes with fatal error.
//call AbilityPreload(AID_WEX_SPELLBOOK) <--- Causes Fatal Error.
//call AbilityPreload(AID_WEX_MOVEMENT_SPEED_BONUS) <--- Causes Fatal Error.
call AbilityPreload(AID_EXORT_BONUS)
call AbilityPreload(AID_COLD_SNAP)
call AbilityPreload(AID_COLD_SNAP_MINISTUN)
call AbilityPreload(AID_EMP)
call AbilityPreload(AID_SUN_STRIKE)
call AbilityPreload(AID_CHAOS_METEOR)
call AbilityPreload(AID_TORNADO)
call AbilityPreload(AID_TORNADO_CYCLONE)
call AbilityPreload(AID_DEAFENING_BLAST)
call AbilityPreload(AID_DEAFENING_BLAST_DISABLE_ATTACK)
call AbilityPreload(AID_ICE_WALL)
call AbilityPreload(AID_ICE_WALL_SLOW_AURA)
call AbilityPreload(AID_GHOST_WALK)
call AbilityPreload(AID_GHOST_WALK_AURA)
call AbilityPreload(AID_FORGE_SPIRIT)
call AbilityPreload(AID_FORGE_SPIRIT_ARMOR_MELT)
call AbilityPreload(AID_FORGE_SPIRIT_ARMOR_BONUS)
call AbilityPreload(AID_FORGE_SPIRIT_MANA_BONUS)
call UnitApplyTimedLife(CreateUnit(Player(13), UID_INVOKER, 9999999., 999999., 0.), 'BTLF', 2.)
call UnitApplyTimedLife(CreateUnit(Player(13), UID_DUMMY, 9999999., 999999., 0.), 'BTLF', 2.)
call UnitApplyTimedLife(CreateUnit(Player(13), UID_EMP, 9999999., 999999., 0.), 'BTLF', 2.)
call UnitApplyTimedLife(CreateUnit(Player(13), UID_EMP_BLAST, 9999999., 999999., 0.), 'BTLF', 2.)
call UnitApplyTimedLife(CreateUnit(Player(13), UID_CHAOS_METEOR, 9999999., 999999., 0.), 'BTLF', 2.)
call UnitApplyTimedLife(CreateUnit(Player(13), UID_CHAOS_METEOR_FALLING, 9999999., 999999., 0.), 'BTLF', 2.)
call UnitApplyTimedLife(CreateUnit(Player(13), UID_TORNADO, 9999999., 999999., 0.), 'BTLF', 2.)
call UnitApplyTimedLife(CreateUnit(Player(13), UID_ICE_WALL, 9999999., 999999., 0.), 'BTLF', 2.)
loop
exitwhen SPIRIT[a] == null
call UnitApplyTimedLife(CreateUnit(Player(13), SPIRIT[a], 9999999., 999999., 0.), 'BTLF', 2.)
set a = a + 1
endloop
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope Invokelist initializer Init
// Requires nothing.
//===============================================================================
// C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Should a message appear at Map Initialization reminding what is the command?
private constant boolean REMINDER = true
// If yes, then this message will be displayed:
private constant string REMINDER_MESSAGE = "Type |cffffcc00-invokelist|r to see what spells you can invoke."
// Should previous text messages be cleared so only the list be visible for the player?
// True or false.
private constant boolean CLEAR_MESSAGES = true
// Duration of the list that is going to be displayed for the player (in seconds).
private constant real LIST_DURATION = 30.
// Don't touch this string array!!!
private string array SPELL
endglobals
// You can add your list here by filling the value SPELL[x] (x is a number) from 0 to
// as much as you like. Then all these will be displayed after the user types -invokelist.
// Remember to always start at 0 and NEVER have a missing index inbetween
// (for example SPELL[0], SPELL[1], SPELL[3] <--- that is WRONG and will not display the whole list).
private function Set takes nothing returns nothing
// All those FF03535 etc. symbols are colour codes.
set SPELL[0] = "Sun Strike [|CFFFF0303EEE|r] - [|cffffcc00T|r]"
set SPELL[1] = "Chaos Meteor [|CFFFF0303EE|r|cff93ffc9W|r] - [|cffffcc00D|r]"
set SPELL[2] = "Forge Spirit [|CFFFF0303EE|r|CFF0042FFQ|r] - [|cffffcc00F|r]"
set SPELL[3] = "Cold Snap [|CFF0042FFQQQ|r] - [|cffffcc00Y|r]"
set SPELL[4] = "Ghost Walk [|CFF0042FFQQ|r|cff93ffc9W|r] - [|cffffcc00V|r]"
set SPELL[5] = "Ice Wall [|CFF0042FFQQ|r|cff93ffc9E|r] - [|cffffcc00G|r]"
set SPELL[6] = "Alacrity [|cff93ffc9WW|r|CFFFF0303E|r] - [|cffffcc00Z|r]"
set SPELL[7] = "Tornado [|cff93ffc9WW|r|CFF0042FFQ|r] - [|cffffcc00X|r]"
set SPELL[8] = "EMP [|cff93ffc9WWW|r] - [|cffffcc00C|r]"
set SPELL[9] = "Deafening Blast [|CFF0042FFQ|r|cff93ffc9W|r|CFFFF0303E|r] - [|cffffcc00B|r]"
endfunction
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
private function Actions takes nothing returns nothing
local integer a = 0
if CLEAR_MESSAGES then
if GetLocalPlayer() == GetTriggerPlayer() then
call ClearTextMessages()
endif
endif
loop
exitwhen SPELL[a] == null
call DisplayTimedTextToPlayer(GetTriggerPlayer(), 0., 0., LIST_DURATION, SPELL[a])
set a = a + 1
endloop
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
local integer a = 0
loop
exitwhen a > 11
call TriggerRegisterPlayerChatEvent(trig, Player(a), "-invokelist", true)
set a = a + 1
endloop
call TriggerAddAction(trig, function Actions)
if REMINDER then
call BJDebugMsg(REMINDER_MESSAGE)
endif
call Set()
endfunction
endscope
//TESH.scrollpos=210
//TESH.alwaysfold=0
scope Spheres initializer Init
// Requires PUI and InvokerConfig.
//===============================================================================
// * This script handles all orb combination and changes and keeps track of them.
// * It also takes care of the bonuses given by Quas/Wex/Exort and of invoked spells.
//===============================================================================
//========================
// No configuration here.=
//========================
globals
// Group for enumerating.
private group gr = CreateGroup()
// Unit used for FirstOfGroup().
private unit fir = null
endglobals
// Even if this isn't private, it shouldn't be touched.
// Do not edit it unless you know what you are doing!
struct Spheres
//! runtextmacro PUI()
integer quas = 0
integer wex = 0
integer exort = 0
integer count = 0
integer invokes = 0
integer invoke1 = 0
integer invoke2 = 0
endstruct
private function Conditions takes nothing returns boolean
// One of the three spells should be casted.
return GetSpellAbilityId() == AID_QUAS or GetSpellAbilityId() == AID_WEX or GetSpellAbilityId() == AID_EXORT
endfunction
// The bonuses given by the orbs should be updated when a new level of Exort/Wex is learned.
// There is no need for Quas since it's triggered.
private function FixBonuses takes nothing returns boolean
local unit cast = GetTriggerUnit()
local Spheres d = Spheres[cast]
if GetLearnedSkill() == AID_EXORT and GetUnitAbilityLevel(cast, AID_EXORT_BONUS) > 0 then
call SetUnitAbilityLevel(cast, AID_EXORT_BONUS, GetUnitAbilityLevel(cast, AID_EXORT) * d.exort)
elseif GetLearnedSkill() == AID_WEX and GetUnitAbilityLevel(cast, AID_WEX_MOVEMENT_SPEED_BONUS) > 0 then
call SetUnitAbilityLevel(cast, AID_WEX_MOVEMENT_SPEED_BONUS, GetUnitAbilityLevel(cast, AID_WEX) * d.wex)
call SetUnitAbilityLevel(cast, AID_WEX_ATTACK_SPEED_BONUS, GetUnitAbilityLevel(cast, AID_WEX) * d.wex)
endif
set cast = null
return false
endfunction
private function GetInvoker takes nothing returns boolean
return GetUnitTypeId(GetFilterUnit()) == UID_INVOKER and GetUnitAbilityLevel(GetFilterUnit(), AID_QUAS) > 0 and GetWidgetLife(GetFilterUnit()) > 0.405 and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false
// Double check.
endfunction
private function Callback takes nothing returns nothing
local real heal
local Spheres d
// Get the Invokers on the map (there can be more than one of them).
call GroupEnumUnitsInRange(gr, 0., 0., 999999., Condition(function GetInvoker))
loop
set fir = FirstOfGroup(gr)
exitwhen fir == null
set d = Spheres[fir]
// Is the struct created and attached?
if d != 0 then
// Is a Quas orb activated?
if d.quas > 0 then
set heal = InvokerConfig_QUAS_REGENERATION(GetUnitAbilityLevel(fir, AID_QUAS)) * d.quas
call SetWidgetLife(fir, GetWidgetLife(fir) + heal)
endif
endif
call GroupRemoveUnit(gr, fir)
endloop
endfunction
private function Actions takes nothing returns nothing
local unit cast = GetTriggerUnit()
local Spheres d = Spheres[cast]
// Was the struct already created? If no, create it and "attach" with PUI.
if d == 0 then
set d = Spheres.create()
set Spheres[cast] = d
endif
// Used for counting the spheres... If it gets over 3 then return it back
// to 1 so the cycle can begin once again and orbs can get replaced.
if d.count < 3 then
set d.count = d.count + 1
else
set d.count = 1
endif
// Was Quas casted?
if GetSpellAbilityId() == AID_QUAS then
// Maybe the first orb must be replaced?
if d.count == 1 then
// Remove all attached orb effects.
call UnitRemoveAbility(cast, AID_QUAS_FIRST)
call UnitRemoveAbility(cast, AID_WEX_FIRST)
call UnitRemoveAbility(cast, AID_EXORT_FIRST)
// And add the one that is really needed.
call UnitAddAbility(cast, AID_QUAS_FIRST)
// Or maybe the second sphere needs to be replaced>
elseif d.count == 2 then
// Remove again.
call UnitRemoveAbility(cast, AID_QUAS_SECOND)
call UnitRemoveAbility(cast, AID_WEX_SECOND)
call UnitRemoveAbility(cast, AID_EXORT_SECOND)
// Add the necessary one.
call UnitAddAbility(cast, AID_QUAS_SECOND)
// Nothing left, it must be the 3rd orb.
elseif d.count == 3 then
// Remove.
call UnitRemoveAbility(cast, AID_QUAS_THIRD)
call UnitRemoveAbility(cast, AID_WEX_THIRD)
call UnitRemoveAbility(cast, AID_EXORT_THIRD)
// Add.
call UnitAddAbility(cast, AID_QUAS_THIRD)
endif
// Was Wex casted instead?
elseif GetSpellAbilityId() == AID_WEX then
// Same cycle as in Exort.
if d.count == 1 then
// Remove.
call UnitRemoveAbility(cast, AID_QUAS_FIRST)
call UnitRemoveAbility(cast, AID_WEX_FIRST)
call UnitRemoveAbility(cast, AID_EXORT_FIRST)
// Add.
call UnitAddAbility(cast, AID_WEX_FIRST)
// Is it second sphere?
elseif d.count == 2 then
// Remove.
call UnitRemoveAbility(cast, AID_QUAS_SECOND)
call UnitRemoveAbility(cast, AID_WEX_SECOND)
call UnitRemoveAbility(cast, AID_EXORT_SECOND)
// Add.
call UnitAddAbility(cast, AID_WEX_SECOND)
elseif d.count == 3 then
call UnitRemoveAbility(cast, AID_QUAS_THIRD)
call UnitRemoveAbility(cast, AID_WEX_THIRD)
call UnitRemoveAbility(cast, AID_EXORT_THIRD)
call UnitAddAbility(cast, AID_WEX_THIRD)
endif
// Exort was casted if it wasn't the other two:
else
if d.count == 1 then
call UnitRemoveAbility(cast, AID_QUAS_FIRST)
call UnitRemoveAbility(cast, AID_WEX_FIRST)
call UnitRemoveAbility(cast, AID_EXORT_FIRST)
// Add first exort orb effect.
call UnitAddAbility(cast, AID_EXORT_FIRST)
elseif d.count == 2 then
call UnitRemoveAbility(cast, AID_QUAS_SECOND)
call UnitRemoveAbility(cast, AID_WEX_SECOND)
call UnitRemoveAbility(cast, AID_EXORT_SECOND)
// Add second exort orb effect.
call UnitAddAbility(cast, AID_EXORT_SECOND)
elseif d.count == 3 then
call UnitRemoveAbility(cast, AID_QUAS_THIRD)
call UnitRemoveAbility(cast, AID_WEX_THIRD)
call UnitRemoveAbility(cast, AID_EXORT_THIRD)
// Add third exort orb effect.
call UnitAddAbility(cast, AID_EXORT_THIRD)
endif
endif
// Set the counting for the orbs to 0.
set d.wex = 0
set d.exort = 0
set d.quas = 0
// And start checking one by one the number of activated instances.
if GetUnitAbilityLevel(cast, AID_WEX_FIRST) > 0 then
set d.wex = d.wex + 1
endif
if GetUnitAbilityLevel(cast, AID_WEX_SECOND) > 0 then
set d.wex = d.wex + 1
endif
if GetUnitAbilityLevel(cast, AID_WEX_THIRD) > 0 then
set d.wex = d.wex + 1
endif
if GetUnitAbilityLevel(cast, AID_EXORT_FIRST) > 0 then
set d.exort = d.exort + 1
endif
if GetUnitAbilityLevel(cast, AID_EXORT_SECOND) > 0 then
set d.exort = d.exort + 1
endif
if GetUnitAbilityLevel(cast, AID_EXORT_THIRD) > 0 then
set d.exort = d.exort + 1
endif
if GetUnitAbilityLevel(cast, AID_QUAS_FIRST) > 0 then
set d.quas = d.quas + 1
endif
if GetUnitAbilityLevel(cast, AID_QUAS_SECOND) > 0 then
set d.quas = d.quas + 1
endif
if GetUnitAbilityLevel(cast, AID_QUAS_THIRD) > 0 then
set d.quas = d.quas + 1
endif
// First remove the abilities that give bonuses.
call UnitRemoveAbility(cast, AID_WEX_SPELLBOOK)
call UnitRemoveAbility(cast, AID_WEX_ATTACK_SPEED_BONUS)
call UnitRemoveAbility(cast, AID_EXORT_BONUS)
// And re-add them if it's necessary.
if d.wex > 0 then
call UnitAddAbility(cast, AID_WEX_SPELLBOOK)
call UnitAddAbility(cast, AID_WEX_ATTACK_SPEED_BONUS)
endif
if d.exort > 0 then
call UnitAddAbility(cast, AID_EXORT_BONUS)
endif
// Modify their level depending on the main abilities' level and the number of activated orbs.
call SetUnitAbilityLevel(cast, AID_WEX_MOVEMENT_SPEED_BONUS, GetUnitAbilityLevel(cast, AID_WEX) * d.wex)
call SetUnitAbilityLevel(cast, AID_WEX_ATTACK_SPEED_BONUS, GetUnitAbilityLevel(cast, AID_WEX) * d.wex)
call SetUnitAbilityLevel(cast, AID_EXORT_BONUS, GetUnitAbilityLevel(cast, AID_EXORT) * d.exort)
set cast = null
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
set trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_HERO_SKILL)
call TriggerAddCondition(trig, Condition(function FixBonuses))
call TimerStart(CreateTimer(), 1., true, function Callback)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope Invoke initializer Init
// Requires PUI, Spheres, InvokerConfig.
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID_INVOKE
endfunction
private function Actions takes nothing returns nothing
local unit cast = GetTriggerUnit()
local integer temp
local integer lvl = GetUnitAbilityLevel(cast, AID_INVOKE)
local Spheres d = Spheres[cast]
// Some checks to prevent more than 2 invokes (and a limit check, ofc).
if d.invokes < 2 and lvl > InvokerConfig_LIMIT(lvl) then
set d.invokes = d.invokes + 1
else
set d.invokes = 1
endif
// Next are some checks to see what combinations were made:
// 3 times Exort - Sun Strike.
if d.exort == 3 then
set temp = AID_SUN_STRIKE
// 3 times Wex - EMP.
elseif d.wex == 3 then
set temp = AID_EMP
// 3 times Quas - Cold Snap.
elseif d.quas == 3 then
set temp = AID_COLD_SNAP
// 2 times Exort and 1 time Wex - Chaos Meteor.
elseif d.exort == 2 and d.wex == 1 then
set temp = AID_CHAOS_METEOR
// 2 times Wex and 1 time Exort - Alacrity.
elseif d.wex == 2 and d.exort == 1 then
set temp = AID_ALACRITY
// 2 times Wex and 1 time Quas - Tornado.
elseif d.wex == 2 and d.quas == 1 then
set temp = AID_TORNADO
// 1 time Quas, 1 time Wex and 1 time Exort - Deafening Blast.
elseif d.quas == 1 and d.wex == 1 and d.exort == 1 then
set temp = AID_DEAFENING_BLAST
// 2 times Quas, 1 time Exort - Ice Wall.
elseif d.quas == 2 and d.exort == 1 then
set temp = AID_ICE_WALL
// 2 times Quas, 1 time Wex - Ghost Walk.
elseif d.quas == 2 and d.wex == 1 then
set temp = AID_GHOST_WALK
// 2 times Exort, 1 time Quas - Forge Spirit.
elseif d.exort == 2 and d.quas == 1 then
set temp = AID_FORGE_SPIRIT
endif
// Other checks to see which ability needs to be replaced.
// Of course, it shouldn't be the previous invoked one because that would
// make you have one invoked spell forever. A cycle is needed.
// And a check must be done to see if the spell that is going to be replaced
// is the same (if it just replaced then cooldown could be reseted and thus abused).
if d.invokes == 1 and temp != d.invoke1 then
call UnitRemoveAbility(cast, d.invoke1)
set d.invoke1 = temp
call UnitAddAbility(cast, d.invoke1)
elseif d.invokes == 2 and temp != d.invoke2 then
call UnitRemoveAbility(cast, d.invoke2)
set d.invoke2 = temp
call UnitAddAbility(cast, d.invoke2)
endif
set cast = null
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope ColdSnap initializer Init
// Requires PUI, TimerUtils, LightLeaklessDamageDetect, InvokerConfig.
//===============================================================================
// C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Special effect attached to the target while Cold Snap is active.
private constant string SFX_ONE = "Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl"
// Attachment point of the special effect (SFX_ONE).
private constant string AP_ONE = "overhead"
endglobals
// Duration of the ability.
private constant function DURATION takes integer level returns real
return 2.5 + (level * 0.5)
endfunction
// Interval in which affected enemies cannot get ministunned.
private constant function MINISTUN_INTERVAL takes integer level returns real
return 0.8 - (0.028 * level)
endfunction
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
// PUI properties for checking whether a unit was hit and prevent endless
// ministuns and passing the level of Quas.
//! runtextmacro PUI_PROPERTY("private", "boolean", "AFFECTED", "false")
//! runtextmacro PUI_PROPERTY("private", "integer", "LEVEL", "0")
globals
// Group that contains all units that have Cold Snap on them.
private group gr = CreateGroup()
// A pointer for a dummy unit.
private unit dum = null
endglobals
private struct Data
unit targ
timer tim
effect sfx
static method create takes unit targ returns Data
local Data d = Data.allocate()
set d.targ = targ
set d.tim = NewTimer()
call SetTimerData(d.tim, d)
return d
endmethod
method onDestroy takes nothing returns nothing
call DestroyEffect(.sfx)
call ReleaseTimer(.tim)
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID_COLD_SNAP
endfunction
private function Refresh takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
// Allow the target to be affected by a ministun next time it gets damaged.
set AFFECTED[d.targ] = false
call d.destroy()
set tim = null
endfunction
private function OnDamage takes nothing returns boolean
local unit cast = GetEventDamageSource()
local unit targ = GetTriggerUnit()
local Data d
// Is the unit allowed to get ministunned? Does it have Cold Snap on it?
if AFFECTED[targ] == false and IsUnitInGroup(targ, gr) and GetEventDamage() > 1. then
// No more ministun activating until the timer expires.
set AFFECTED[targ] = true
set d = Data.create(targ)
call TimerStart(d.tim, MINISTUN_INTERVAL(LEVEL[targ]), false, function Refresh)
// Creating the dummy and cause it to cast the ministun.
set dum = CreateUnit(GetOwningPlayer(cast), UID_DUMMY, GetUnitX(targ), GetUnitY(targ), 0.)
call UnitAddAbility(dum, 'Aloc')
call UnitAddAbility(dum, AID_COLD_SNAP_MINISTUN)
call IssueTargetOrder(dum, OID_COLD_SNAP_MINISTUN, targ)
call UnitApplyTimedLife(dum, 'BTLF', 0.5)
set dum = null
endif
set cast = null
set targ = null
return false
endfunction
private function Callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
// Remove the affected unit from the Cold Snap group because the duration has ended.
call GroupRemoveUnit(gr, d.targ)
call d.destroy()
set tim = null
endfunction
private function Actions takes nothing returns nothing
local unit cast = GetTriggerUnit()
local integer lvl = GetUnitAbilityLevel(cast, AID_QUAS)
local Data d = Data.create(GetSpellTargetUnit())
// Pass the level of Quas for the casting unit to the target.
// This value gets accessed when the target gets damaged.
set LEVEL[d.targ] = lvl
// Add an effect.
set d.sfx = AddSpecialEffectTarget(SFX_ONE, d.targ, AP_ONE)
// Add the unit to the Cold Snap group.
call GroupAddUnit(gr, d.targ)
call TimerStart(d.tim, DURATION(lvl), false, function Callback)
set cast = null
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
call AddOnDamageFunc(Condition(function OnDamage))
call Preload(SFX_ONE)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope EMP initializer Init
// Requires TimerUtils, InvokerConfig.
//===============================================================================
// C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Period used for changing the size of the EMP unit.
// Keep it in range 0.10 - 0.20.
private constant real PERIOD = 0.1
// Attack type of the damaging when EMP explodes.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_CHAOS
// Damage type of the damaging when EMP explodes.
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL
// Weapon type of the damaging when EMP explodes.
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
endglobals
// The duration of the wait between casting EMP and the explosion.
private constant function DELAY takes integer level returns real
return 4. - (0.29 * level)
endfunction
// Mana drained when the ball explodes.
private constant function MANA_DRAINED takes integer level returns real
return 57. * level
endfunction
// Damage dealt when the ball explodes (in this case - 50% of mana drained).
private constant function DAMAGE takes integer level returns real
return 0.5 * (MANA_DRAINED(level))
endfunction
// Area of effect in which enemies get damaged/mana drained.
private constant function AREA_OF_EFFECT takes integer level returns real
return 700.
endfunction
// Scaling of the EMP unit when it's created.
private constant function STARTING_SCALING takes integer level returns real
return 2.5
endfunction
// The maximum scaling the EMP unit reaches (it grows) before it explodes.
private constant function MAXIMUM_SCALING takes integer level returns real
return 5.
endfunction
// Animation speed of the EMP unit.
private constant function BALL_ANIMATION_SPEED takes integer level returns real
return 0.35
endfunction
// Animation speed of the EMP Blast unit (might want to be a little slower?).
private constant function BLAST_ANIMATION_SPEED takes integer level returns real
return 0.25
endfunction
// Duration of the EMP Blast unit (if you chose a faster effect you could decrease this
// so it doesn't get created multiple times).
private constant function BLAST_DURATION takes integer level returns real
return 4.
endfunction
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Group used for enumerations.
private group gr = CreateGroup()
// Unit used for FirstOfGroup() and global caster passing for filters.
private unit fir = null
endglobals
private struct Data
unit cast
unit emp
integer lvl
location loc
timer tim
integer ticks
real change
real scale
static method create takes unit cast returns Data
local Data d = Data.allocate()
set d.cast = cast
set d.lvl = GetUnitAbilityLevel(d.cast, AID_WEX)
set d.loc = GetSpellTargetLoc()
set d.emp = CreateUnitAtLoc(GetOwningPlayer(cast), UID_EMP, d.loc, GetRandomReal(0., 359.))
set d.tim = NewTimer()
set d.ticks = R2I(DELAY(d.lvl) / PERIOD)
set d.change = (MAXIMUM_SCALING(d.lvl) - STARTING_SCALING(d.lvl)) / d.ticks
set d.scale = STARTING_SCALING(d.lvl)
call SetUnitScale(d.emp, d.scale, d.scale, d.scale)
call SetUnitTimeScale(d.emp, BALL_ANIMATION_SPEED(d.lvl))
//call UnitApplyTimedLife(d.emp, 'BTLF', DELAY(d.lvl))
call SetTimerData(d.tim, d)
return d
endmethod
method onDestroy takes nothing returns nothing
call RemoveLocation(.loc)
call ReleaseTimer(.tim)
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID_EMP
endfunction
private function GetEnemies takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(fir)) and GetUnitState(GetFilterUnit(), UNIT_STATE_MANA) > 0. and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false
endfunction
private function Callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
local unit blast
set d.scale = d.scale + d.change
call SetUnitScale(d.emp, d.scale, d.scale, d.scale)
set d.ticks = d.ticks - 1
if d.ticks <= 0 then
call RemoveUnit(d.emp) // Please don't bug. :)
set blast = CreateUnitAtLoc(GetOwningPlayer(d.cast), UID_EMP_BLAST, d.loc, GetRandomReal(0., 359.))
call SetUnitTimeScale(blast, BLAST_ANIMATION_SPEED(d.lvl))
call UnitApplyTimedLife(blast, 'BTLF', BLAST_DURATION(d.lvl))
set blast = null
set fir = d.cast
call GroupEnumUnitsInRangeOfLoc(gr, d.loc, AREA_OF_EFFECT(d.lvl), Condition(function GetEnemies))
loop
set fir = FirstOfGroup(gr)
exitwhen fir == null
if GetWidgetLife(fir) > DAMAGE(d.lvl) then
// It should "damage" through Tornado and other spells.
call SetWidgetLife(fir, GetWidgetLife(fir) - DAMAGE(d.lvl))
else
call UnitDamageTarget(d.cast, fir, DAMAGE(d.lvl), true, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
endif
call SetUnitState(fir, UNIT_STATE_MANA, GetUnitState(fir, UNIT_STATE_MANA) - MANA_DRAINED(d.lvl))
call GroupRemoveUnit(gr, fir)
endloop
call d.destroy()
endif
set tim = null
endfunction
private function Actions takes nothing returns nothing
local Data d = Data.create(GetTriggerUnit())
call TimerStart(d.tim, PERIOD, true, function Callback)
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope SunStrike initializer Init
// Requires TimerUtils, InvokerConfig.
//===============================================================================
// C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Attack type of the damaging.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
// Damage type of the damaging.
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
// Weapon type of the damaging.
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
// Special effect created at the targeted location.
private constant string SFX_ONE = "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl"
endglobals
// Time to wait before Sun Strike damages the targeted area.
private constant function DELAY takes integer level returns real
return 1.7
endfunction
// Damage dealt in the area (which is spread amongst affected foes).
private constant function DAMAGE takes integer level returns real
return 12.5 + (level * 62.5)
endfunction
// Area of effect of the damaging.
private constant function AREA_OF_EFFECT takes integer level returns real
return 200.
endfunction
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Group used for enumerations.
private group gr = CreateGroup()
// Unit used for FirstOfGroup() and global passing of the caster for the filter.
private unit fir = null
endglobals
private struct Data
unit cast
location loc
timer tim
integer lvl
static method create takes unit cast returns Data
local Data d = Data.allocate()
set d.cast = cast
set d.loc = GetSpellTargetLoc()
set d.tim = NewTimer()
set d.lvl = GetUnitAbilityLevel(d.cast, AID_EXORT)
call SetTimerData(d.tim, d)
return d
endmethod
method onDestroy takes nothing returns nothing
call ReleaseTimer(.tim)
call RemoveLocation(.loc)
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID_SUN_STRIKE
endfunction
private function GetEnemies takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(fir)) and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL) == false
endfunction
private function Callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
local integer temp
call GroupEnumUnitsInRangeOfLoc(gr, d.loc, AREA_OF_EFFECT(d.lvl), Condition(function GetEnemies))
set temp = CountUnitsInGroup(gr)
if temp > 0 then
loop
set fir = FirstOfGroup(gr)
exitwhen fir == null
call UnitDamageTarget(d.cast, fir, DAMAGE(d.lvl) / temp, true, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call GroupRemoveUnit(gr, fir)
endloop
endif
call DestroyEffect(AddSpecialEffectLoc(SFX_ONE, d.loc))
call d.destroy()
set tim = null
endfunction
private function Actions takes nothing returns nothing
local Data d = Data.create(GetTriggerUnit())
call TimerStart(d.tim, DELAY(d.lvl), false, function Callback)
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
call Preload(SFX_ONE)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope ChaosMeteor initializer Init
// Requires PUI, TimerUtils, BoundSentinel (optional but recommended), InvokerConfig.
//===============================================================================
// C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// This period is used for moving the meteor so keep it in range of 0.02 to 0.05,
// otherwise the movement would look quite choppy and ugly!
private constant real PERIOD = 0.03125
// Special effect created bellow the meteor while it's moving.
// It is NOT created each PERIOD!
private constant string SFX_ONE = "Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl"
// Special effect created on enemies affected by the dps effect of the meteor.
private constant string SFX_TWO = "Environment\\LargeBuildingFire\\LargeBuildingFire1.mdl"
// Attachment point of the above effect (SFX_TWO).
private constant string AP_TWO = "chest"
// Attack type of the damaging.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
// Damage type of the damaging.
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
// Weapon type of the damaging.
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
endglobals
// Animation speed of the unit Chaos Meteor Falling.
private constant function METEOR_FALLING_ANIMATION_SPEED takes integer wexLvl, integer exortLvl returns real
return 0.7
endfunction
// This DELAY time is waited so the Chaos Meteor Falling unit can play it's animation.
// Of course, it shouldn't just be a random number because it has to be high or low enough
// so the Chaos Meteor Falling can play it's full animation. In this case, we wait 1.05 seconds
// so the meteor can hit the ground and THEN create the rolling meteor with all its damaging and such.
private constant function DELAY takes integer wexLvl, integer exortLvl returns real
return 1.05
endfunction
// Speed of the normal meteor (Chaos Meteor). This isn't "Movement Speed" (as in Object Editor)!
private constant function METEOR_SPEED takes integer wexLvl, integer exortLvl returns real
return 8.5
endfunction
// How much range should the rolling meteor travel until it gets destroyed.
private constant function METEOR_TRAVEL_DISTANCE takes integer wexLvl, integer exortLvl returns real
return 7.5 * wexLvl * wexLvl + 127.500 * wexLvl + 330 // Lol, formula finder.
endfunction
// Period of damaging of the main meteor (in this case every half second).
private constant function DAMAGE_PERIOD takes integer wexLvl, integer exortLvl returns real
return 0.5
endfunction
// Primary damage dealt by the rolling meteor. I multiply it by the DAMAGE_PERIOD (cut it in half)
// on purpouse because this way it will deal the half damage the first 0.5 of the second and the
// other half at the last 0.5 second.
private constant function METEOR_DAMAGE takes integer wexLvl, integer exortLvl returns real
return (30 * exortLvl + 50) * DAMAGE_PERIOD(wexLvl, exortLvl)
// This is supposed to deal the full damage every second.
// That's why I multiply it by the period because it shouldn't deal damage every 0.5 second.
endfunction
// Area of effect of the Chaos Meteor (in this AoE enemies will get damaged).
private constant function AREA_OF_EFFECT takes integer wexLvl, integer exortLvl returns real
return 300.
endfunction
// Damage per second that is dealt to enemies as a secondary damage. Cannot be stacked.
private constant function DAMAGE_PER_SECOND takes integer wexLvl, integer exortLvl returns real
return (30 * exortLvl + 50) * 0.2 // 20% of the main damage.
endfunction
// Duration of the dps damaging. Staying in the meteor after 3 seconds will renew the damaging.
private constant function DPS_DURATION takes integer wexLvl, integer exortLvl returns real
return 3.
endfunction
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Group used for enumerating.
private group gr = CreateGroup()
// Unit used for FirstOfGroup() and global passing of the caster for the filter.
private unit fir = null
// Group containing all units affected by the dps of the meteor.
private group dmgGroup = CreateGroup()
endglobals
// Properties attached to affected units.
// Dps.
//! runtextmacro PUI_PROPERTY("private", "real", "DAMAGE", "0.00")
// Counter for the duration.
//! runtextmacro PUI_PROPERTY("private", "integer", "TIME_LEFT", "0")
// Damaging unit... No other way to pass it and still be MUI. Or maybe there is another way.
//! runtextmacro PUI_PROPERTY("private", "unit", "DAMAGER", "null")
// Effect attached on the damaged unit.
//! runtextmacro PUI_PROPERTY("private", "effect", "EFFECT", "null")
private struct Data
unit cast
unit meteor
location loc
timer tim
real angle
integer ticks
integer dmgTicks
integer wex // Level of Wex.
integer exort // Level of Exort.
static method create takes unit cast returns Data
local Data d = Data.allocate()
local unit meteorFalling
set d.cast = cast
set d.wex = GetUnitAbilityLevel(d.cast, AID_WEX)
set d.exort = GetUnitAbilityLevel(d.cast, AID_EXORT)
set d.loc = GetSpellTargetLoc()
// I can't work with radians, sorry. Not so high level of maths at school. :(
set d.angle = bj_RADTODEG * Atan2(GetLocationY(d.loc) - GetUnitY(d.cast), GetLocationX(d.loc) - GetUnitX(d.cast))
set meteorFalling = CreateUnitAtLoc(GetOwningPlayer(d.cast), UID_CHAOS_METEOR_FALLING, d.loc, d.angle)
call SetUnitTimeScale(meteorFalling, METEOR_FALLING_ANIMATION_SPEED(d.wex, d.exort))
call UnitApplyTimedLife(meteorFalling, 'BTLF', DELAY(d.wex, d.exort))
set d.tim = NewTimer()
set d.ticks = R2I(METEOR_TRAVEL_DISTANCE(d.wex, d.exort) / METEOR_SPEED(d.wex, d.exort))
set d.dmgTicks = R2I(DAMAGE_PERIOD(d.wex, d.exort) / PERIOD)
call SetTimerData(d.tim, d)
set meteorFalling = null
return d
endmethod
method onDestroy takes nothing returns nothing
call RemoveLocation(.loc)
call ReleaseTimer(.tim)
call KillUnit(.meteor)
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID_CHAOS_METEOR
endfunction
private function ForDPSGroup takes nothing returns nothing
local unit first = GetEnumUnit()
call UnitDamageTarget(DAMAGER[first], first, DAMAGE[first], true, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
set TIME_LEFT[first] = TIME_LEFT[first] - 1
if TIME_LEFT[first] <= 0 then
call GroupRemoveUnit(dmgGroup, first)
call DestroyEffect(EFFECT[first])
set EFFECT[first] = null
set DAMAGER[first] = null
// The other properties don't need changing since they'll be
// replaced next time the unit is hit by the meteor.
endif
set first = null
endfunction
private function DamagePerSecond takes nothing returns nothing
call ForGroup(dmgGroup, function ForDPSGroup)
endfunction
private function GetEnemies takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(fir)) and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) == true and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false
endfunction
private function MoveMeteor takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
local real x = GetUnitX(d.meteor) + METEOR_SPEED(d.wex, d.exort) * Cos(d.angle * bj_DEGTORAD)
local real y = GetUnitY(d.meteor) + METEOR_SPEED(d.wex, d.exort) * Sin(d.angle * bj_DEGTORAD)
call SetUnitX(d.meteor, x)
call SetUnitY(d.meteor, y)
set d.dmgTicks = d.dmgTicks - 1
if d.dmgTicks <= 0 then
set d.dmgTicks = R2I(DAMAGE_PERIOD(d.wex, d.exort) / PERIOD)
call DestroyEffect(AddSpecialEffect(SFX_ONE, x, y))
// Just for passing, we don't need another global variable just for that.
set fir = d.cast
call GroupEnumUnitsInRange(gr, x, y, AREA_OF_EFFECT(d.wex, d.exort), Condition(function GetEnemies))
loop
set fir = FirstOfGroup(gr)
exitwhen fir == null
call UnitDamageTarget(d.cast, fir, METEOR_DAMAGE(d.wex, d.exort), true, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
if not IsUnitInGroup(fir, dmgGroup) then
call GroupAddUnit(dmgGroup, fir)
set TIME_LEFT[fir] = R2I(DPS_DURATION(d.wex, d.exort))
set DAMAGE[fir] = DAMAGE_PER_SECOND(d.wex, d.exort)
set DAMAGER[fir] = d.cast
set EFFECT[fir] = AddSpecialEffectTarget(SFX_TWO, fir, AP_TWO)
endif
call GroupRemoveUnit(gr, fir)
endloop
endif
set d.ticks = d.ticks - 1
if d.ticks <= 0 then
// Okay, the meteor travelled it's maximum range.
call d.destroy()
endif
set tim = null
endfunction
private function Callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
set d.meteor = CreateUnitAtLoc(GetOwningPlayer(d.cast), UID_CHAOS_METEOR, d.loc, d.angle)
call DestroyEffect(AddSpecialEffectLoc(SFX_ONE, d.loc))
call TimerStart(d.tim, PERIOD, true, function MoveMeteor)
set tim = null
endfunction
private function Actions takes nothing returns nothing
local Data d = Data.create(GetTriggerUnit())
call TimerStart(d.tim, DELAY(d.wex, d.exort), false, function Callback)
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
call TimerStart(CreateTimer(), 1., true, function DamagePerSecond)
call Preload(SFX_ONE)
call Preload(SFX_TWO)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope Alacrity initializer Init
// Requires TimerUtils, InvokerConfig.
//===============================================================================
// C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Special effect attached on unit affected with Alacrity.
private constant string SFX_ONE = "Abilities\\Spells\\Items\\AIsp\\SpeedTarget.mdl"
// Attachment point of the above effect (SFX_ONE)
private constant string AP_ONE = "overhead"
endglobals
// Duration of the bonuses from Alacrity.
private constant function DURATION takes integer wexLvl, integer exortLvl returns real
return 6.
endfunction
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
private struct Data
unit targ
effect sfx
timer tim
integer wex
integer exort
static method create takes unit targ returns Data
local Data d = Data.allocate()
local unit cast = GetTriggerUnit()
// Get Exort and Wex's levels.
set d.wex = GetUnitAbilityLevel(cast, AID_WEX)
set d.exort = GetUnitAbilityLevel(cast, AID_EXORT)
set d.targ = targ
set d.sfx = AddSpecialEffectTarget(SFX_ONE, d.targ, AP_ONE)
// Add the abilities that give bonuses and set their level depending
// on the level of Wex and Exort.
call UnitAddAbility(d.targ, AID_ALACRITY_DAMAGE_BONUS)
call UnitAddAbility(d.targ, AID_ALACRITY_SPEED_BONUS)
call SetUnitAbilityLevel(d.targ, AID_ALACRITY_DAMAGE_BONUS, d.exort)
call SetUnitAbilityLevel(d.targ, AID_ALACRITY_SPEED_BONUS, d.wex)
set d.tim = NewTimer()
call SetTimerData(d.tim, d)
set cast = null
return d
endmethod
method onDestroy takes nothing returns nothing
// Remove the bonuses when the spell ends.
call UnitRemoveAbility(.targ, AID_ALACRITY_DAMAGE_BONUS)
call UnitRemoveAbility(.targ, AID_ALACRITY_SPEED_BONUS)
call DestroyEffect(.sfx)
call ReleaseTimer(.tim)
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID_ALACRITY
endfunction
private function Callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
call d.destroy()
set tim = null
endfunction
private function Actions takes nothing returns nothing
local Data d = Data.create(GetSpellTargetUnit())
call TimerStart(d.tim, DURATION(d.wex, d.exort), false, function Callback)
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
call Preload(SFX_ONE)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope Tornado initializer Init
// Requires TimerUtils, GroupUtils, BoundSentinel (optional but recommended), InvokerConfig.
//===============================================================================
// C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// This period is used for moving the tornado so keep it in range of 0.02 to 0.04,
// otherwise the movement would look quite choppy and ugly!
private constant real PERIOD = 0.03125
// Attack type of the damaging.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
// Damage type of the damaging.
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
// Weapon type of the damaging.
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
endglobals
// Speed of the tornado (this is NOT "Movement Speed" as in Object Editor).
private constant function SPEED takes integer quasLvl, integer wexLvl returns real
return 28.
endfunction
// Distance travelled by the tornado.
private constant function DISTANCE takes integer quasLvl, integer wexLvl returns real
return 410. * wexLvl + 230.
endfunction
// Area of effect of the tornado.
private constant function AREA_OF_EFFECT takes integer quasLvl, integer wexLvl returns real
return 200.
endfunction
// Duration of the cycloning. REMEMBER, IT MUST MATCH THE DURATION FROM THE ABILITY
// TORNADO CYCLONE, OTHERWISE IT WOULDN'T DEAL DAMAGE AT ALL.
private constant function CYCLONE_DURATION takes integer quasLvl, integer wexLvl returns real
return 0.000 * quasLvl * quasLvl + 0.250 * quasLvl + 0.350 // Lol, formula finder.
endfunction
// Damage dealt by the tornado when the units get back on the ground.
private constant function DAMAGE takes integer quasLvl, integer wexLvl returns real
return 42.5 * wexLvl + 77.5
endfunction
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Unit used for FirstOfGroup() and global passing of the caster for the filter.
private unit fir = null
endglobals
private struct Damage
unit cast
unit targ
real dmg
timer tim
static method create takes unit cast, unit targ, real dmg returns Damage
local Damage g = Damage.allocate()
set g.cast = cast
set g.targ = targ
set g.dmg = dmg
set g.tim = NewTimer()
call SetTimerData(g.tim, g)
return g
endmethod
method onDestroy takes nothing returns nothing
call ReleaseTimer(.tim)
endmethod
endstruct
private struct Data
unit cast
unit tornado
real angle
integer ticks
timer tim
group gr
integer quas
integer wex
static method create takes unit cast returns Data
local Data d = Data.allocate()
local location loc = GetSpellTargetLoc()
set d.cast = cast
set d.quas = GetUnitAbilityLevel(d.cast, AID_QUAS)
set d.wex = GetUnitAbilityLevel(d.cast, AID_WEX)
// I can't work with radians, sorry. Not so high level of maths at school. :(
set d.angle = bj_RADTODEG * Atan2(GetLocationY(loc) - GetUnitY(d.cast), GetLocationX(loc) - GetUnitX(d.cast))
set d.tornado = CreateUnit(GetOwningPlayer(d.cast), UID_TORNADO, GetUnitX(d.cast), GetUnitY(d.cast), d.angle)
set d.ticks = R2I(DISTANCE(d.quas, d.wex) / SPEED(d.quas, d.wex))
set d.tim = NewTimer()
set d.gr = NewGroup()
call SetTimerData(d.tim, d)
call RemoveLocation(loc)
set loc = null
return d
endmethod
method onDestroy takes nothing returns nothing
call ReleaseGroup(.gr)
call ReleaseTimer(.tim)
call SetUnitTimeScale(.tornado, 3.) // <-- Because the tornado dies too slow...
call KillUnit(.tornado)
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID_TORNADO
endfunction
private function GetEnemies takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(fir)) and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) == true and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false
endfunction
private function DamageAfterCyclone takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Damage g = GetTimerData(tim)
call UnitDamageTarget(g.cast, g.targ, g.dmg, true, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call g.destroy()
set tim = null
endfunction
private function Callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
local real x = GetUnitX(d.tornado) + SPEED(d.quas, d.wex) * Cos(d.angle * bj_DEGTORAD)
local real y = GetUnitY(d.tornado) + SPEED(d.quas, d.wex) * Sin(d.angle * bj_DEGTORAD)
local unit dum
local Damage g
call SetUnitX(d.tornado, x)
call SetUnitY(d.tornado, y)
set fir = d.cast
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, AREA_OF_EFFECT(d.quas, d.wex), Condition(function GetEnemies))
loop
set fir = FirstOfGroup(ENUM_GROUP)
exitwhen fir == null
if not IsUnitInGroup(fir, d.gr) then
call GroupAddUnit(d.gr, fir)
set dum = CreateUnit(GetOwningPlayer(d.cast), UID_DUMMY, x, y, 0.)
call UnitAddAbility(dum, AID_TORNADO_CYCLONE)
call UnitAddAbility(dum, 'Aloc')
call SetUnitAbilityLevel(dum, AID_TORNADO_CYCLONE, d.quas)
call IssueTargetOrder(dum, OID_TORNADO_CYCLONE, fir)
call UnitApplyTimedLife(dum, 'BTLF', 0.5)
set g = Damage.create(d.cast, fir, DAMAGE(d.quas, d.wex))
// Requires a tiny little wait so the cyclone can fully finish.
// (Because you can't damage invulnerable units)
call TimerStart(g.tim, CYCLONE_DURATION(d.quas, d.wex) + 0.2, false, function DamageAfterCyclone)
set dum = null
endif
call GroupRemoveUnit(ENUM_GROUP, fir)
endloop
set d.ticks = d.ticks - 1
if d.ticks <= 0 then
// The tornado reached its maximum distance.
call d.destroy()
endif
set tim = null
endfunction
private function Actions takes nothing returns nothing
local Data d = Data.create(GetTriggerUnit())
call TimerStart(d.tim, PERIOD, true, function Callback)
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope DeafeningBlast initializer Init
// Requires TimerUtils, GroupUtils, BoundSentinel (optional but recommended), InvokerConfig
//===============================================================================
// C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// This period is used for moving the blast and for the knockback so keep it
// in range of 0.02 to 0.04, otherwise the movement would look quite choppy and ugly!
private constant real PERIOD = 0.03125
// Attack type of the damaging.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
// Damage type of the damaging.
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
// Weapon type of the damaging.
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
// Effect attached to units that get affected by the deafening blast.
private constant string SFX_ONE = "Abilities\\Spells\\Other\\TalkToMe\\TalkToMe.mdl"
// Attachment point of the above effect (SFX_ONE).
private constant string AP_ONE = "overhead"
endglobals
// Speed of the Deafening Blast (this is NOT "Movement Speed" as in Object Editor).
private constant function SPEED takes integer quasLvl, integer wexLvl, integer exortLvl returns real
return 30.
endfunction
// Distance travelled by the Deafening Blast.
private constant function DISTANCE takes integer quasLvl, integer wexLvl, integer exortLvl returns real
return 1000.
endfunction
// Starting area of effect of the Deafening Blast (it gets increased).
private constant function STARTING_AREA_OF_EFFECT takes integer quasLvl, integer wexLvl, integer exortLvl returns real
return 200.
endfunction
// End area of effect of the Deafening Blast (it gets increased to this value).
private constant function END_AREA_OF_EFFECT takes integer quasLvl, integer wexLvl, integer exortLvl returns real
return 250.
endfunction
// Damage dealt by the Deafening Blast to each unit hit by it.
private constant function DAMAGE takes integer quasLvl, integer wexLvl, integer exortLvl returns real
return exortLvl * 40.
endfunction
// Duration of the attack prevention of affected units.
private constant function ATTACK_PREVENTION_DURATION takes integer quasLvl, integer wexLvl, integer exortLvl returns real
return 0.5 * wexLvl + 0.5
endfunction
// Duration of the knockback when a unit is hit by the Deafening Blast.
private constant function KNOCKBACK_DURATION takes integer quasLvl, integer wexLvl, integer exortLvl returns real
return quasLvl * 0.25
endfunction
// Knockback speed of units hit by the Deafening Blast (this is NOT "Movement Speed" as in Object Editor).
private constant function KNOCKBACK_SPEED takes integer quasLvl, integer wexLvl, integer exortLvl returns real
return 6.
endfunction
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Unit used for FirstOfGroup() and global passing of the caster for the filter.
private unit fir = null
endglobals
private struct Attack
unit targ
timer tim
effect sfx
static method create takes unit targ returns Attack
local Attack a = Attack.allocate()
set a.targ = targ
set a.tim = NewTimer()
set a.sfx = AddSpecialEffectTarget(SFX_ONE, a.targ, AP_ONE)
call SetTimerData(a.tim, a)
return a
endmethod
method onDestroy takes nothing returns nothing
call DestroyEffect(.sfx)
call ReleaseTimer(.tim)
endmethod
endstruct
private struct Knockback
unit targ
real angle
timer tim
integer quas
integer wex
integer exort
integer ticks
static method create takes unit blast, unit targ, integer quas, integer wex, integer exort returns Knockback
local Knockback b = Knockback.allocate()
set b.targ = targ
set b.quas = quas
set b.wex = wex
set b.exort = exort
set b.angle = bj_RADTODEG * Atan2(GetUnitY(b.targ) - GetUnitY(blast), GetUnitX(b.targ) - GetUnitX(blast))
set b.ticks = R2I(KNOCKBACK_DURATION(b.quas, b.wex, b.exort) / PERIOD)
set b.tim = NewTimer()
call SetTimerData(b.tim, b)
return b
endmethod
method onDestroy takes nothing returns nothing
call ReleaseTimer(.tim)
endmethod
endstruct
private struct Data
unit cast
unit blast
timer tim
group gr
real angle
real currentAoe
real increment
integer ticks
integer quas
integer wex
integer exort
static method create takes unit cast returns Data
local Data d = Data.allocate()
local location loc = GetSpellTargetLoc()
set d.cast = cast
set d.quas = GetUnitAbilityLevel(d.cast, AID_QUAS)
set d.wex = GetUnitAbilityLevel(d.cast, AID_WEX)
set d.exort = GetUnitAbilityLevel(d.cast, AID_EXORT)
set d.angle = bj_RADTODEG * Atan2(GetLocationY(loc) - GetUnitY(d.cast), GetLocationX(loc) - GetUnitX(d.cast))
set d.blast = CreateUnit(GetOwningPlayer(d.cast), UID_DEAFENING_BLAST, GetUnitX(d.cast), GetUnitY(d.cast), d.angle)
set d.ticks = R2I(DISTANCE(d.quas, d.wex, d.exort) / SPEED(d.quas, d.wex, d.exort))
// Keeping track of the area of effect so it can get increased.
set d.currentAoe = STARTING_AREA_OF_EFFECT(d.quas, d.wex, d.exort)
// The growing of the area of effect each tick.
set d.increment = (END_AREA_OF_EFFECT(d.quas, d.wex, d.exort) - STARTING_AREA_OF_EFFECT(d.quas, d.wex, d.exort)) / d.ticks
set d.tim = NewTimer()
set d.gr = NewGroup()
call SetTimerData(d.tim, d)
call RemoveLocation(loc)
set loc = null
return d
endmethod
method onDestroy takes nothing returns nothing
call ReleaseTimer(.tim)
call ReleaseGroup(.gr)
call KillUnit(.blast)
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID_DEAFENING_BLAST
endfunction
private function GetEnemies takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(fir)) and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) == true and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false
endfunction
private function KnockCallback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Knockback b = GetTimerData(tim)
local real x = GetUnitX(b.targ) + KNOCKBACK_SPEED(b.quas, b.wex, b.exort) * Cos(b.angle * bj_DEGTORAD)
local real y = GetUnitY(b.targ) + KNOCKBACK_SPEED(b.quas, b.wex, b.exort) * Sin(b.angle * bj_DEGTORAD)
call SetUnitPosition(b.targ, x, y)
set b.ticks = b.ticks - 1
if b.ticks <= 0 or IsUnitType(b.targ, UNIT_TYPE_DEAD) == true then
call b.destroy()
endif
set tim = null
endfunction
private function EnableAttack takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Attack a = GetTimerData(tim)
call UnitRemoveAbility(a.targ, AID_DEAFENING_BLAST_DISABLE_ATTACK)
call a.destroy()
set tim = null
endfunction
private function Callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
local real x = GetUnitX(d.blast) + SPEED(d.quas, d.wex, d.exort) * Cos(d.angle * bj_DEGTORAD)
local real y = GetUnitY(d.blast) + SPEED(d.quas, d.wex, d.exort) * Sin(d.angle * bj_DEGTORAD)
local Attack a
local Knockback b
local unit dum
call SetUnitX(d.blast, x)
call SetUnitY(d.blast, y)
set fir = d.cast
set d.currentAoe = d.currentAoe + d.increment
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, d.currentAoe, Condition(function GetEnemies))
loop
set fir = FirstOfGroup(ENUM_GROUP)
exitwhen fir == null
if not IsUnitInGroup(fir, d.gr) then
call GroupAddUnit(d.gr, fir)
set a = Attack.create(fir)
call UnitAddAbility(fir, AID_DEAFENING_BLAST_DISABLE_ATTACK)
call TimerStart(a.tim, ATTACK_PREVENTION_DURATION(d.quas, d.wex, d.exort), false, function EnableAttack)
set b = Knockback.create(d.blast, fir, d.quas, d.wex, d.exort)
call TimerStart(b.tim, PERIOD, true, function KnockCallback)
call UnitDamageTarget(d.cast, fir, DAMAGE(d.quas, d.wex, d.exort), true, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
if IsUnitType(fir, UNIT_TYPE_DEAD) == false then
set dum = CreateUnit(GetOwningPlayer(d.cast), UID_DUMMY, x, y, 0.)
call UnitAddAbility(dum, AID_DEAFENING_BLAST_SILENCE)
call UnitAddAbility(dum, 'Aloc')
call SetUnitAbilityLevel(dum, AID_DEAFENING_BLAST_SILENCE, d.quas)
call IssueTargetOrder(dum, OID_DEAFENING_BLAST_SILENCE, fir)
call UnitApplyTimedLife(dum, 'BTLF', 0.5)
endif
endif
call GroupRemoveUnit(ENUM_GROUP, fir)
endloop
set d.ticks = d.ticks - 1
if d.ticks <= 0 then
call d.destroy()
endif
set tim = null
set dum = null
endfunction
private function Actions takes nothing returns nothing
local Data d = Data.create(GetTriggerUnit())
call TimerStart(d.tim, PERIOD, true, function Callback)
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
call Preload(SFX_ONE)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope IceWall initializer Init
// Requires TimerUtils, LineSegment, xebasic, InvokerConfig.
//===============================================================================
// C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// This period is used for checking units around the wall so use a low period around 0.1 and 0.3,
// otherwise it wouldn't work as good as you want.
private constant real PERIOD = 0.1
// Attack type of the damaging.
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
// Damage type of the damaging.
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
// Weapon type of the damaging.
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
endglobals
// How much range in front of the caster should the wall be created.
private constant function CREATION_RANGE takes integer quasLvl, integer exortLvl returns real
return 200.
endfunction
// Area of effect of the Ice Wall. In this area units will be damaged.
// THE AREA OF EFFECT OF THE SLOW DEPENDS ON THE AREA OF EFFECT OF THE AURA IN OBJECT EDITOR
// SO THAT'S WHY YOU WOULD NEED TO MAKE BOTH THE SAME.
private constant function AREA_OF_EFFECT takes integer quasLvl, integer exortLvl returns real
return 150.
endfunction
// Number of Ice Wall units created (ice blocks, since that is their current model).
private constant function NUMBER_OF_WALL_UNITS takes integer quasLvl, integer exortLvl returns integer
return 9
endfunction
// Distance between the wall units (shouldn't be really high so it could be visually good).
private constant function DISTANCE_BETWEEN_WALL_UNITS takes integer quasLvl, integer exortLvl returns real
return 95.
endfunction
// Duration of the wall.
private constant function WALL_DURATION takes integer quasLvl, integer exortLvl returns real
return 1.5 * quasLvl + 1.5
endfunction
// Period between damaging units in the wall.
private constant function DAMAGE_PERIOD takes integer quasLvl, integer exortLvl returns real
// DPS - damage per second. You can change it to something like 0.65
// and it will damage units every 0.65 second.
return 1.
endfunction
// Damage dealt every DAMAGE_PERIOD().
private constant function DAMAGE takes integer quasLvl, integer exortLvl returns real
return exortLvl * 6.
endfunction
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Group used for enumerating untis.
private group gr = CreateGroup()
// Unit used for FirstOfGroup() and global passing of the caster for the filter.
private unit fir = null
endglobals
private struct Data
unit cast
real x1
real y1
real x2
real y2
timer tim
integer ticks
integer dmgTicks
integer quas
integer exort
static method create takes unit cast returns Data
local Data d = Data.allocate()
set d.cast = cast
set d.quas = GetUnitAbilityLevel(d.cast, AID_QUAS)
set d.exort = GetUnitAbilityLevel(d.cast, AID_EXORT)
set d.ticks = R2I(WALL_DURATION(d.quas, d.exort) / PERIOD)
set d.dmgTicks = R2I(DAMAGE_PERIOD(d.quas, d.exort) / PERIOD)
set d.tim = NewTimer()
call SetTimerData(d.tim, d)
return d
endmethod
method onDestroy takes nothing returns nothing
call ReleaseTimer(.tim)
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID_ICE_WALL
endfunction
private function GetEnemies takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(fir)) and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) == true and IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false
endfunction
private function Callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
local real dd = NUMBER_OF_WALL_UNITS(d.quas, d.exort) * DISTANCE_BETWEEN_WALL_UNITS(d.quas, d.exort)
set d.dmgTicks = d.dmgTicks - 1
if d.dmgTicks <= 0 then
set d.dmgTicks = R2I(DAMAGE_PERIOD(d.quas, d.exort) / PERIOD)
set fir = d.cast
// Get units around the wall.
call GroupEnumUnitsInRangeOfSegment(gr, d.x1, d.y1, d.x2, d.y2, AREA_OF_EFFECT(d.quas, d.exort), Condition(function GetEnemies))
loop
set fir = FirstOfGroup(gr)
exitwhen fir == null
call UnitDamageTarget(d.cast, fir, 50., true, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call GroupRemoveUnit(gr, fir)
endloop
endif
set d.ticks = d.ticks - 1
if d.ticks <= 0 then
call d.destroy()
endif
set tim = null
endfunction
private function Actions takes nothing returns nothing
local unit cast = GetTriggerUnit()
local Data d = Data.create(cast)
local real x = GetUnitX(cast)
local real y = GetUnitY(cast)
local real face = GetUnitFacing(cast)
local real tx = x + CREATION_RANGE(d.quas, d.exort) * Cos(face * bj_DEGTORAD)
local real ty = y + CREATION_RANGE(d.quas, d.exort) * Sin(face * bj_DEGTORAD)
local real dd = NUMBER_OF_WALL_UNITS(d.quas, d.exort) * DISTANCE_BETWEEN_WALL_UNITS(d.quas, d.exort)
local real angle = Atan2(ty - y, tx - x)
local integer a = 0
local unit block
set x = Cos(angle + bj_PI / 2) * dd
set y = Sin(angle + bj_PI / 2) * dd
set tx = tx + x / 2
set ty = ty + y / 2
set x = x / NUMBER_OF_WALL_UNITS(d.quas, d.exort)
set y = y / NUMBER_OF_WALL_UNITS(d.quas, d.exort)
loop
//exitwhen a > NUMBER_OF_WALL_UNITS(d.quas, d.exort)
set tx = tx - x
set ty = ty - y
set block = CreateUnit(GetOwningPlayer(cast), UID_ICE_WALL, tx, ty, GetRandomReal(0., 359.))
if a == 0 then
set d.x1 = tx
set d.y1 = ty
endif
// I think it's useless but I've seen other people do it in their wall spells.
// Maybe it has something to do with pathing, I don't know exactly. Still, it doesn't hurt.
call SetUnitX(block, tx)
call SetUnitY(block, ty)
call UnitAddAbility(block, AID_ICE_WALL_SLOW_AURA)
call SetUnitAbilityLevel(block, AID_ICE_WALL_SLOW_AURA, d.quas)
call UnitApplyTimedLife(block, 'BTLF', WALL_DURATION(d.quas, d.exort))
exitwhen a == NUMBER_OF_WALL_UNITS(d.quas, d.exort)
set a = a + 1
endloop
set d.x2 = tx
set d.y2 = ty
call TimerStart(d.tim, PERIOD, true, function Callback)
set block = null
set cast = null
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope GhostWalk initializer Init
// Requires TimerUtils, InvokerConfig.
//===============================================================================
// C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// This period is used for checking whether the Hero is in Ghost Walk
// So keep it in range 0.02-0.10 for maximum accuracies.
private constant real PERIOD = 0.03125
endglobals
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
private struct Data
unit cast
unit dum
integer quas
integer wex
timer tim
static method create takes unit cast returns Data
local Data d = Data.allocate()
set d.cast = cast
set d.quas = GetUnitAbilityLevel(d.cast, AID_QUAS)
set d.wex = GetUnitAbilityLevel(d.cast, AID_WEX)
set d.dum = CreateUnit(GetOwningPlayer(d.cast), UID_DUMMY, GetUnitX(d.cast), GetUnitY(d.cast), 0.)
set d.tim = NewTimer()
call UnitAddAbility(d.dum, 'Aloc')
call UnitAddAbility(d.dum, AID_GHOST_WALK_AURA)
call UnitAddAbility(d.dum, AID_GHOST_WALK_SELF_SLOW)
call SetUnitAbilityLevel(d.dum, AID_GHOST_WALK_AURA, d.quas)
call SetUnitAbilityLevel(d.dum, AID_GHOST_WALK_SELF_SLOW, d.wex)
call IssueTargetOrder(d.dum, OID_GHOST_WALK_SELF_SLOW, d.cast)
call SetTimerData(d.tim, d)
return d
endmethod
method onDestroy takes nothing returns nothing
// Remove the self-slow.
call UnitRemoveAbility(.cast, BID_GHOST_WALK_SELF_SLOW)
call KillUnit(.dum)
call ReleaseTimer(.tim)
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID_GHOST_WALK
endfunction
private function Callback takes nothing returns nothing
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
call SetUnitX(d.dum, GetUnitX(d.cast))
call SetUnitY(d.dum, GetUnitY(d.cast))
// Did caster break invisibility or did he die?
if GetUnitAbilityLevel(d.cast, BID_GHOST_WALK) < 1 or IsUnitType(d.cast, UNIT_TYPE_DEAD) == true then
call d.destroy()
endif
set tim = null
endfunction
private function Actions takes nothing returns nothing
local Data d = Data.create(GetTriggerUnit())
call TimerStart(d.tim, PERIOD, true, function Callback)
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope ForgeSpirit initializer Init
// Requires PUI, LightLeaklessDamageDetect, InvokerConfig.
//===============================================================================
// C O N F I G U R A T I O N M E N U
//===============================================================================
globals
// Effect created upon forged spirits when they are spawned.
private constant string SFX_ONE = "Abilities\\Spells\\Orc\\FeralSpirit\\feralspiritdone.mdl"
// Maximum armor reduction EVER by the melting armor ability. MUST MATCH OBJECT EDITOR!
private constant integer MAX_ARMOR_MELT_LEVEL = 10
endglobals
// Level needed for Quas and for Exort so 2 Spirits can be forged.
// For example: quas = 3 and exort = 4 --> 1 spirit.
// quas = 4 (and above) and exort = 4 (and above) --> 2 spirits.
private constant function LEVEL_NEEDED takes integer quasLvl, integer exortLvl returns integer
return 4
endfunction
// Duration of the spirits.
private constant function SPIRIT_DURATION takes integer quasLvl, integer exortLvl returns real
return 10. + (quasLvl * 10.)
endfunction
//===============================================================================
// E N D O F C O N F I G U R A T I O N M E N U
//===============================================================================
//! runtextmacro PUI_PROPERTY("private", "integer", "TIME", "0")
globals
// Group that contains all units
private group gr = CreateGroup()
// Group used for enumerating units.
private group temp = CreateGroup()
// Unit used for FirstOfGroup() loops.
private unit fir = null
endglobals
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID_FORGE_SPIRIT
endfunction
private function ForTimeHandlerGroup takes nothing returns nothing
local unit fir = GetEnumUnit()
if TIME[fir] > 0 then
set TIME[fir] = TIME[fir] - 1
else
call GroupRemoveUnit(gr, fir)
call UnitRemoveAbility(fir, AID_FORGE_SPIRIT_ARMOR_MELT)
endif
set fir = null
endfunction
private function TimeHandler takes nothing returns nothing
call ForGroup(gr, function ForTimeHandlerGroup)
endfunction
private function OnDamage takes nothing returns boolean
local unit spirit = GetEventDamageSource()
local unit targ = GetTriggerUnit()
local integer a = 1
loop
exitwhen SPIRIT[a] == null
if GetUnitTypeId(spirit) == SPIRIT[a] and IsUnitType(targ, UNIT_TYPE_HERO) == true then
set TIME[targ] = 5
if not IsUnitInGroup(targ, gr) then
call GroupAddUnit(gr, targ)
endif
// Don't have the spell that reduces armor?
if GetUnitAbilityLevel(targ, AID_FORGE_SPIRIT_ARMOR_MELT) < 1 then
call UnitAddAbility(targ, AID_FORGE_SPIRIT_ARMOR_MELT)
else
// If the unit has it, then check
if GetUnitAbilityLevel(targ, AID_FORGE_SPIRIT_ARMOR_MELT) < MAX_ARMOR_MELT_LEVEL then
call IncUnitAbilityLevel(targ, AID_FORGE_SPIRIT_ARMOR_MELT)
endif
endif
endif
set a = a + 1
endloop
set spirit = null
set targ = null
return false
endfunction
// Funny filter, but that's the easiest way to check if the enumed unit is a Spirit,
// otherwise that line would be come a little long with all those OR operations...
private function GetSpirits takes nothing returns boolean
local integer a = 1
if GetOwningPlayer(GetFilterUnit()) == GetOwningPlayer(GetTriggerUnit()) then
loop
exitwhen SPIRIT[a] == null
if GetUnitTypeId(GetFilterUnit()) == SPIRIT[a] then
return true
endif
set a = a + 1
endloop
endif
return false
endfunction
private function Actions takes nothing returns nothing
local unit cast = GetTriggerUnit()
local integer exort = GetUnitAbilityLevel(cast, AID_EXORT)
local integer quas = GetUnitAbilityLevel(cast, AID_QUAS)
// Get and remove all currently existing spirits.
call GroupEnumUnitsInRange(temp, GetUnitX(cast), GetUnitY(cast), 999999., Condition(function GetSpirits))
loop
set fir = FirstOfGroup(temp)
exitwhen fir == null
call KillUnit(fir)
call GroupRemoveUnit(temp, fir)
endloop
// Create a spirit.
set bj_lastCreatedUnit = CreateUnit(GetOwningPlayer(cast), SPIRIT[exort], GetUnitX(cast), GetUnitY(cast), GetRandomReal(0., 359.))
call UnitApplyTimedLife(bj_lastCreatedUnit, 'BTLF', SPIRIT_DURATION(quas, exort))
call UnitAddAbility(bj_lastCreatedUnit, AID_FORGE_SPIRIT_ARMOR_BONUS)
call UnitAddAbility(bj_lastCreatedUnit, AID_FORGE_SPIRIT_MANA_BONUS)
call SetUnitAbilityLevel(bj_lastCreatedUnit, AID_FORGE_SPIRIT_MANA_BONUS, quas)
call SetUnitAbilityLevel(bj_lastCreatedUnit, AID_FORGE_SPIRIT_ARMOR_BONUS, quas)
call DestroyEffect(AddSpecialEffect(SFX_ONE, GetUnitX(bj_lastCreatedUnit), GetUnitY(bj_lastCreatedUnit)))
// If Quas and Exort are both at/above the level needed for 2 spirits, then create another spirit.
if exort >= LEVEL_NEEDED(quas, exort) and quas >= LEVEL_NEEDED(quas, exort) then
set bj_lastCreatedUnit = CreateUnit(GetOwningPlayer(cast), SPIRIT[exort], GetUnitX(cast), GetUnitY(cast), GetRandomReal(0., 359.))
call UnitApplyTimedLife(bj_lastCreatedUnit, 'BTLF', SPIRIT_DURATION(quas, exort))
call UnitAddAbility(bj_lastCreatedUnit, AID_FORGE_SPIRIT_ARMOR_BONUS)
call UnitAddAbility(bj_lastCreatedUnit, AID_FORGE_SPIRIT_MANA_BONUS)
call SetUnitAbilityLevel(bj_lastCreatedUnit, AID_FORGE_SPIRIT_MANA_BONUS, quas)
call SetUnitAbilityLevel(bj_lastCreatedUnit, AID_FORGE_SPIRIT_ARMOR_BONUS, quas)
call DestroyEffect(AddSpecialEffect(SFX_ONE, GetUnitX(bj_lastCreatedUnit), GetUnitY(bj_lastCreatedUnit)))
endif
set cast = null
endfunction
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig, Condition(function Conditions))
call TriggerAddAction(trig, function Actions)
call AddOnDamageFunc(Condition(function OnDamage))
call TimerStart(CreateTimer(), 1., true, function TimeHandler)
call Preload(SFX_ONE)
endfunction
endscope
//TESH.scrollpos=9
//TESH.alwaysfold=0
library xebasic
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITID : Rawcode of the dummy unit in your map. It should
// use the dummy.mdx model, so remember to import it as
// well, just use copy&paste to copy the dummy from the
// xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLER: Medivh's raven form ability, you may need to change
// this rawcode to another spell that morphs into a flier
// in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITION: The ancients' Eat tree ability, same as with medivh
// raven form, you might have to change it.
//
// XE_ANIMATION_PERIOD: The global period of animation used by whatever
// timer that depends on it, if you put a low value
// the movement will look good but it may hurt your
// performance, if instead you use a high value it
// will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZE: The maximum unit collision size in your map, if
// you got a unit bigger than 197.0 it would be
// a good idea to update this constant, since some
// enums will not find it. Likewise, if none of
// your units can go bellow X and X is much smaller
// than 197.0, it would be a good idea to update
// as well, since it will improve the performance
// those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************
//===========================================================================
globals
constant integer XE_DUMMY_UNITID = 'e000'
constant integer XE_HEIGHT_ENABLER = 'Amrf'
constant integer XE_TREE_RECOGNITION = 'Aeat'
constant real XE_ANIMATION_PERIOD = 0.025
constant real XE_MAX_COLLISION_SIZE = 197.0
endglobals
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
// ==================================================================================
// LineSegment - by Ammorth, April 13, 2009 - rev4
// Please give credit when using this in your map
// This, and myself, can be found at wc3c.net
//
// Used to determine certain things about segments, involving units and geometry.
//
// Requirements:
// - vJass
// - xebasic - by Vexorian ( [url]http://www.wc3c.net/showthread.php?t=101150[/url] )
//
// Installation:
// - Create a new trigger called LineSegments and convert it to custom text
// - Copy the code to the new trigger, replacing everything
//
// How To Use:
// - call GetNearestPointOnSegment(Ax, Ay, Bx, By, Cx, Cy) to get the nearest point on
// the line segment AB to point C (returns a location)
// - call GetDistanceFromSegment(Ax, Ay, Bx, By, Cx, Cy) to get the distance from the
// line segment AB to a given point C
// - call GroupEnumUnitsInRangeOfSegment(whichgroup, Ax, Ay, Bx, By, distance, filter) to
// add all of the units within distance of segment AB to whichgroup, according to the filter.
// - call IsUnitInRangeOfSegment(unit, Ax, Ay, Bx, By, distance) to see if the unit givin is
// within distance of segment AB
//
// Notes:
// - All functions have a location version wrappers incase you would rather pass locations
// They are named as follows:
// > GetNearestPointOnSegmentLoc()
// > GetDistanceFromSegmentLoc()
// > GroupEnumUnitsInRangeOfSegmentLoc()
// > IsUnitInRangeOfSegmentLoc()
//
// ==================================================================================
library LineSegment requires xebasic
globals
private group udg_LineTempGroup = CreateGroup()
endglobals
function GetNearestPointOnSegment takes real Ax, real Ay, real Bx, real By, real Cx, real Cy returns location
local real r
// could use a point struct here if you really wanted, instead of a location.
local real dx = Bx-Ax
local real dy = By-Ay
local real L = ((dx)*(dx) + (dy)*(dy)) // Get quasi length
if L == 0 then // seg is actually a point so lets return the point
return Location(Ax, Ay)
endif
set r = ((Cx-Ax)*(dx) + (Cy-Ay)*(dy))/(L) // get the ratio
if r > 1 then // closests point is past seg, so return end point B
return Location(Bx, By)
elseif r < 0 then // same as B, but at A instead
return Location(Ax, Ay)
endif // In the middle of A and B so use the ratio to find the point
return Location(Ax+r*(dx), Ay+r*(dy))
endfunction
function GetNearestPointOnSegmentLoc takes location A, location B, location C returns location
return GetNearestPointOnSegment(GetLocationX(A), GetLocationY(A), GetLocationX(B), GetLocationY(B), GetLocationX(C), GetLocationY(C))
endfunction
function GetDistanceFromSegment takes real Ax, real Ay, real Bx, real By, real Cx, real Cy returns real
local real r
local real dx = Bx-Ax
local real dy = By-Ay
local real L = ((dx)*(dx) + (dy)*(dy)) // Get quasi length
if L == 0 then // seg is actually a point so lets return the distance to the point
return SquareRoot((Cx-Ax)*(Cx-Ax)+(Cy-Ay)*(Cy-Ay))
endif
set r = ((Cx-Ax)*(dx) + (Cy-Ay)*(dy))/(L) // get the ratio
if r > 1 then // closests point is past seg, so return distance to point B
return SquareRoot((Cx-Bx)*(Cx-Bx)+(Cy-By)*(Cy-By))
elseif r < 0 then // same as B, but at A instead
return SquareRoot((Cx-Ax)*(Cx-Ax)+(Cy-Ay)*(Cy-Ay))
endif // In the middle of A and B so use the ratio to find the point
set Ax = Ax+r*(dx)
set Ay = Ay+r*(dy)
return SquareRoot((Cx-Ax)*(Cx-Ax)+(Cy-Ay)*(Cy-Ay))
endfunction
function GetDistanceFromSegmentLoc takes location A, location B, location C returns real
return GetDistanceFromSegment(GetLocationX(A), GetLocationY(A), GetLocationX(B), GetLocationY(B), GetLocationX(C), GetLocationY(C))
endfunction
function GroupEnumUnitsInRangeOfSegment takes group whichgroup, real Ax, real Ay, real Bx, real By, real distance, boolexpr filter returns nothing
local real dx = Bx-Ax
local real dy = By-Ay
local real L = ((dx)*(dx) + (dy)*(dy)) // Get quasi length
local real r = SquareRoot(dx*dx+dy*dy)/2+distance + XE_MAX_COLLISION_SIZE // double-purpose for r
local unit u
call GroupClear(udg_LineTempGroup)
call GroupEnumUnitsInRange(udg_LineTempGroup, Ax+(dx/2), Ay+(dy/2), r, filter)
loop
set u = FirstOfGroup(udg_LineTempGroup)
exitwhen u == null
if L == 0 and IsUnitInRangeXY(u, Ax, Ay, distance) then // seg is actually a point so lets return the point
call GroupAddUnit(whichgroup, u)
else
set r = ((GetUnitX(u)-Ax)*(dx) + (GetUnitY(u)-Ay)*(dy))/(L) // get the ratio
if r > 1 then // split if/thens so that it exists properly
if IsUnitInRangeXY(u, Bx, By, distance) then // closests point is past seg, so return end point B
call GroupAddUnit(whichgroup, u)
endif
elseif r < 0 then
if IsUnitInRangeXY(u, Ax, Ay, distance) then // same as B, but at A instead
call GroupAddUnit(whichgroup, u)
endif
elseif IsUnitInRangeXY(u, Ax+r*(dx), Ay+r*(dy), distance) then // In the middle of A and B so use the ratio to find the point
call GroupAddUnit(whichgroup, u)
endif
endif
call GroupRemoveUnit(udg_LineTempGroup, u)
endloop
set u = null
endfunction
function GroupEnumUnitsInRangeOfSegmentLoc takes group whichgroup, location A, location B, real distance, boolexpr filter returns nothing
call GroupEnumUnitsInRangeOfSegment(whichgroup, GetLocationX(A), GetLocationY(A), GetLocationX(B), GetLocationY(B), distance, filter)
endfunction
function IsUnitInRangeOfSegment takes unit u, real Ax, real Ay, real Bx, real By, real distance returns boolean
local real r
local real dx = Bx-Ax
local real dy = By-Ay
local real L = ((dx)*(dx) + (dy)*(dy)) // Get quasi length
if L == 0 then // seg is actually a point so lets return the point
return IsUnitInRangeXY(u, Ax, Ay, distance)
endif
set r = ((GetUnitX(u)-Ax)*(dx) + (GetUnitY(u)-Ay)*(dy))/(L) // get the ratio
if r > 1 then // closests point is past seg, so return end point B
return IsUnitInRangeXY(u, Bx, By, distance)
elseif r < 0 then // same as B, but at A instead
return IsUnitInRangeXY(u, Ax, Ay, distance)
endif // In the middle of A and B so use the ratio to find the point
return IsUnitInRangeXY(u, Ax+r*(dx), Ay+r*(dy), distance)
endfunction
function IsUnitInRangeOfSegmentLoc takes unit u, location A, location B, real distance returns boolean
return IsUnitInRangeOfSegment(u, GetLocationX(A), GetLocationY(A), GetLocationX(B), GetLocationY(B), distance)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library AbilityPreload initializer Init
// Information:
//==============
//
// Preloading removes the noticeable delay the first time an ability is
// loaded in a game. It is suggested that you preload every ability that
// is not already on a unit that is placed on the map or created at init.
//
// How to install PreloadAbility:
//================================
//
// Just copy the script into your map, no further tweaking needed.
//
// How to use PreloadAbility:
//============================
//
// Just call PreloadAbility(abilityid) during an init function. There is no
// point calling it after init, since it will do nothing, and it would not
// be "pre"-loading anyway.
//
//===========================================================================
globals
private boolean InitDone = false
private unit PreloadUnit
endglobals
function AbilityPreload takes integer abilityid returns nothing
if not InitDone then
call BJDebugMsg("AbilityPreload error: Library has not been initialized yet")
return
elseif GetUnitTypeId(PreloadUnit) == 0 then
call BJDebugMsg("AbilityPreload error: Can't preload an ability after initialization")
return
endif
call UnitAddAbility(PreloadUnit, abilityid)
endfunction
//===========================================================================
private function Init takes nothing returns nothing
set PreloadUnit = CreateUnit(Player(15), 'zsmc', 0., 0., 0.)
call UnitApplyTimedLife(PreloadUnit, 0, .001)
call ShowUnit(PreloadUnit, false)
call UnitAddAbility(PreloadUnit, 'Aloc')
set InitDone = true
endfunction
endlibrary
//TESH.scrollpos=320
//TESH.alwaysfold=0
//==============================================================================
// PUI -- Perfect Unit Indexing by Cohadar -- v5.3
//==============================================================================
//
// PURPOUSE:
// * Extending UnitUserData()
// * This is basically perfect hashing algorithm for units
//
// HOW TO USE:
// * You have only one function at your disposal GetUnitIndex(unit)
// It will return a unique index for each unit in range 1..8190
//
// * What you will do with that index is all up to you
// Of course using global arrays is the most obvious choice
// Advanced jassers will think of a couple of more clever ones ofc.
//
// * There are also 2 textmacros available at the end of library code
// They can be used for easier attaching to units
// PUI for structs
// PUI_PROPERTY for unit, integer, real, boolean and string variables
//
// PROS:
// * You can use any number of systems that previously could not work together
// because they all required exclusive access of UnitUserData()
//
// * You can also use this to attach spell data structs to casters
//
// * There are no SetUnitIndex() or ClearUnitIndex() functions here
// Each unit gets assigned one index that cannot be changed
// That index will be automatically recycled when unit is removed from the game.
//
// CONS:
// * This system uses UnitUserData() itself
// That means that if you want to use PUI you must recode
// any other system that uses UnitUserData() to use GetUnitIndex() instead
//
// * If you use UnitIndex for arrays of non-native types (timers, effects and similar)
// you must check if timer on that index already exists before you create a new one.
// This can happen if GetUnitIndex() assigns a recycled index (index of some dead and removed unit)
// to the newly created unit for which you intended to use timer for
//
// * All in all this is not a sys for newbies, it gives great power,
// but it requires knowledge and carefull handling
//
// DETAILS:
// * System is using unit array to keep track of all units with an index.
// Array is periodically checked for removed units,
// when removed unit is found, index is recycled.
// Indexes have "decay time" to prevent bugs
// caused by attaching to "Can't Raise, Does not decay" type units,
// or by using RemoveUnit() function
//
// SYSTEM COMMANDS: (debug mode only, red player only)
//
// * type -pui to display indexes of currently selected units
// * type -puistats to display some stats
// * type -puitest to verify that all indexes are valid and unique
//
// THANKS TO:
// * Vexorian - for his help with PUI textmacro
// * Builder Bob - for testing and bugfinding
// * Joker(Div) - bugfinding
//
// HOW TO IMPORT:
// * Just create a trigger named PUI
// * convert it to text and replace the whole trigger text with this one
//
//==============================================================================
library PUI initializer Init
//==============================================================================
globals
//-----------------------------------------------
private constant real INDEX_DECAY_TIME = 5. // seconds
//-----------------------------------------------
private constant real PERIOD = 0.03125 // 32 fps
//-----------------------------------------------
private constant integer DECAY_TICKS = R2I(INDEX_DECAY_TIME/PERIOD)
//-----------------------------------------------
private integer maxIndex = 0
private integer usedIndexCount = 0
private integer checker = 0
private integer array Indexz
private unit array Unitz
//-----------------------------------------------
private integer freeIndexCount = 0
private integer array Freez
//-----------------------------------------------
private integer decayIndexCount = 0
private integer decayer = 0
private integer array Decayz
private integer array TimeEndz
private integer tick = 0
endglobals
//==============================================================================
private function PeriodicRecycler takes nothing returns boolean
set tick = tick + 1
// unit recycler
if usedIndexCount > 0 then
set checker = checker + 1
if checker > usedIndexCount then
set checker = 1
endif
if (GetUnitUserData(Unitz[checker]) == 0) then
set decayIndexCount = decayIndexCount + 1
set Decayz[decayIndexCount] = Indexz[checker]
set TimeEndz[decayIndexCount] = tick + DECAY_TICKS
set Indexz[checker] = Indexz[usedIndexCount]
set Unitz[checker] = Unitz[usedIndexCount]
set usedIndexCount = usedIndexCount - 1
endif
endif
// index recycler
if decayIndexCount > 0 then
set decayer = decayer + 1
if decayer > decayIndexCount then
set decayer = 1
endif
if TimeEndz[decayer] <= tick then
set freeIndexCount = freeIndexCount + 1
set Freez[freeIndexCount] = Decayz[decayer]
set Decayz[decayer] = Decayz[decayIndexCount]
set TimeEndz[decayer] = TimeEndz[decayIndexCount]
set decayIndexCount = decayIndexCount - 1
endif
endif
// for debugging
//if ModuloInteger(tick, 8) == 0 then
// call ClearTextMessages()
// call BJDebugMsg("Used/Free/Decaying: " + I2S(usedIndexCount) + "/" + I2S(freeIndexCount) + "/" + I2S(decayIndexCount))
//endif
return false
endfunction
//==============================================================================
// Main and only function exported by this library
//==============================================================================
function GetUnitIndex takes unit whichUnit returns integer
local integer index
debug if whichUnit == null then
debug call BJDebugMsg("|c00FF0000ERROR: PUI - Index requested for null unit")
debug return 0
debug endif
set index = GetUnitUserData(whichUnit)
if index == 0 then
set usedIndexCount = usedIndexCount + 1
if freeIndexCount > 0 then
set Indexz[usedIndexCount] = Freez[freeIndexCount]
set freeIndexCount = freeIndexCount - 1
else
set maxIndex = maxIndex + 1
set Indexz[usedIndexCount] = maxIndex
endif
set Unitz[usedIndexCount] = whichUnit
call SetUnitUserData(whichUnit, Indexz[usedIndexCount])
set index = GetUnitUserData(whichUnit)
// this happens when requesting unit index for removed unit
debug if index == 0 then
debug call BJDebugMsg("|c00FFCC00WARNING: PUI - Bad unit handle")
debug endif
endif
return index
endfunction
//==============================================================================
private function DisplayStats takes nothing returns nothing
call BJDebugMsg("=============================================")
call BJDebugMsg("Biggest index ever = " + I2S(maxIndex))
call BJDebugMsg("Indexes in use = " + I2S(usedIndexCount))
call BJDebugMsg("Decaying indexes = " + I2S(decayIndexCount))
call BJDebugMsg("Released indexes = " + I2S(freeIndexCount))
call BJDebugMsg("=============================================")
endfunction
//===========================================================================
private function DisplaySelectedEnum takes nothing returns nothing
call BJDebugMsg( "PUI(" + ( GetUnitName(GetEnumUnit()) + ( ") = " + I2S(GetUnitUserData(GetEnumUnit())) ) ) )
endfunction
//===========================================================================
private function DisplaySelected takes nothing returns nothing
local group g = CreateGroup()
call SyncSelections()
call GroupEnumUnitsSelected(g, Player(0), null)
call ForGroup(g, function DisplaySelectedEnum)
call DestroyGroup(g)
set g = null
endfunction
//==============================================================================
globals
private integer testCounter = 0
private integer array TestCountz
endglobals
//==============================================================================
private function Test takes nothing returns nothing
local integer i
set testCounter = testCounter + 1
set i = 1
loop
exitwhen i > usedIndexCount
// happens when you abuse UnitUserData outside PUI
if GetUnitTypeId(Unitz[i]) != 0 and GetUnitUserData(Unitz[i]) != Indexz[i] then
call BJDebugMsg("|c00FF0000ERROR: PUI - Invalid index detected")
return
endif
// if this error happens it means PUI is bugged
if TestCountz[Indexz[i]] != testCounter then
set TestCountz[Indexz[i]] = testCounter
else
call BJDebugMsg("|c00FF0000ERROR: PUI - Double index detected")
return
endif
set i = i + 1
endloop
call BJDebugMsg("|c0000ff00OK: PUI - All indexes were valid and unique")
endfunction
//==============================================================================
private function Init takes nothing returns nothing
local trigger trig
set trig = CreateTrigger()
call TriggerRegisterTimerEvent( trig, PERIOD, true )
call TriggerAddCondition( trig, Condition(function PeriodicRecycler) )
debug set trig = CreateTrigger()
debug call TriggerRegisterPlayerChatEvent( trig, Player(0), "-pui", true )
debug call TriggerAddAction( trig, function DisplaySelected )
debug set trig = CreateTrigger()
debug call TriggerRegisterPlayerChatEvent( trig, Player(0), "-puistats", true )
debug call TriggerAddAction( trig, function DisplayStats )
debug set trig = CreateTrigger()
debug call TriggerRegisterPlayerChatEvent( trig, Player(0), "-puitest", true )
debug call TriggerAddAction( trig, function Test )
endfunction
endlibrary
//===========================================================================
// Allowed PUI_PROPERTY TYPES are: unit, integer, real, boolean, string
// Do NOT put handles that need to be destroyed here (timer, trigger, ...)
// Instead put them in a struct and use PUI textmacro
//===========================================================================
//! textmacro PUI_PROPERTY takes VISIBILITY, TYPE, NAME, DEFAULT
$VISIBILITY$ struct $NAME$
private static unit array pui_unit
private static $TYPE$ array pui_data
//-----------------------------------------------------------------------
// Returns default value when first time used
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns $TYPE$
local integer pui = GetUnitIndex(whichUnit)
if .pui_unit[pui] != whichUnit then
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = $DEFAULT$
endif
return .pui_data[pui]
endmethod
//-----------------------------------------------------------------------
static method operator[]= takes unit whichUnit, $TYPE$ whichData returns nothing
local integer pui = GetUnitIndex(whichUnit)
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = whichData
endmethod
endstruct
//! endtextmacro
//===========================================================================
// Never destroy PUI structs directly.
// Use .release() instead, will call .destroy()
//===========================================================================
//! textmacro PUI
private static unit array pui_unit
private static integer array pui_data
private static integer array pui_id
//-----------------------------------------------------------------------
// Returns zero if no struct is attached to unit
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns integer
local integer pui = GetUnitIndex(whichUnit)
if .pui_data[pui] != 0 then
if .pui_unit[pui] != whichUnit then
// recycled handle detected
call .destroy(.pui_data[pui])
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endif
endif
return .pui_data[pui]
endmethod
//-----------------------------------------------------------------------
// This will overwrite already attached struct if any
//-----------------------------------------------------------------------
static method operator[]= takes unit whichUnit, integer whichData returns nothing
local integer pui = GetUnitIndex(whichUnit)
if .pui_data[pui] != 0 then
call .destroy(.pui_data[pui])
endif
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = whichData
set .pui_id[whichData] = pui
endmethod
//-----------------------------------------------------------------------
// If you do not call release struct will be destroyed when unit handle gets recycled
//-----------------------------------------------------------------------
method release takes nothing returns nothing
local integer pui= .pui_id[integer(this)]
call .destroy()
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endmethod
//! endtextmacro
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library LightLeaklessDamageDetect initializer Init
// Creating threads off of this that last longer than the timeout below will likely cause issues, like everything blowing up (handle stack corruption)
// It seems that threads created by timers, rather than executefunc / .evaluate / .execute are not affected. Any threads created from the timer thread are fine.
// This being safe with even the usage laid out above isn't guarenteed. Use at own risk.
// If you start getting random bugs, see if commenting out the timer line below (see comments) helps
// If it does, report it in the thread for this script at [url]www.wc3campaigns.net[/url]
globals
private constant real SWAP_TIMEOUT = 600. // keep high; 600 should be about the right balance.
endglobals
globals
private conditionfunc array func
private integer funcNext = 0
private trigger current = null
private trigger toDestroy = null
private group swapGroup
private rect mapRect
endglobals
// One of the only accessible functions. Use it to add a condition. Must return boolean type, and then have return false at the end.
// Note that it's technically a condition, so if you put a wait in there, it'll die. But waits are lame anyway.
function AddOnDamageFunc takes conditionfunc cf returns nothing
call TriggerAddCondition(current, cf)
set func[funcNext] = cf
set funcNext = funcNext + 1
endfunction
// These inline. For avoiding feedback loops. Feel free to make your own wrapper function for damage functions using this.
function DisableDamageDetect takes nothing returns nothing
call DisableTrigger(current)
endfunction
function EnableDamageDetect takes nothing returns nothing
call EnableTrigger(current)
endfunction
// no more accessible functions, folks.
//! textmacro CGLeaklessDamageDetectAddFilter takes UNIT
// add here any conditions to add the unit to the trigger, example below, commented out:
// if GetUnitTypeId($UNIT$) != 'h000' then // where 'h000' is a dummy unit
call TriggerRegisterUnitEvent(current, $UNIT$, EVENT_UNIT_DAMAGED)
// endif
//! endtextmacro
private function AddEx takes nothing returns boolean
//! runtextmacro CGLeaklessDamageDetectAddFilter("GetFilterUnit()")
return false
endfunction
private function Enters takes nothing returns boolean
//! runtextmacro CGLeaklessDamageDetectAddFilter("GetTriggerUnit()")
return false
endfunction
private function Swap takes nothing returns nothing
local integer i = 0
local boolean b = IsTriggerEnabled(current)
call DisableTrigger(current)
if toDestroy != null then
call DestroyTrigger(toDestroy)
endif
set toDestroy = current
set current = CreateTrigger()
if not(b) then
call DisableTrigger(current)
endif
call GroupEnumUnitsInRect(swapGroup, mapRect, Filter(function AddEx))
loop
exitwhen i >= funcNext
call TriggerAddCondition(current, func[i])
set i = i + 1
endloop
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
local region r = CreateRegion()
local integer i = 0
set mapRect = GetWorldBounds()
call RegionAddRect(r, mapRect)
call TriggerRegisterEnterRegion(t, r, null)
call TriggerAddCondition(t, Condition(function Enters))
set swapGroup = CreateGroup()
set current = CreateTrigger()
loop
exitwhen i >= funcNext
call TriggerAddCondition(current, func[i])
set i = i + 1
endloop
call GroupEnumUnitsInRect(swapGroup, GetWorldBounds(), Filter(function AddEx))
// Commenting out the next line will make the system leak indexes and events, but should make it safer.
call TimerStart(CreateTimer(), SWAP_TIMEOUT, true, function Swap)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library BoundSentinel initializer init
//*************************************************
//* BoundSentinel
//* -------------
//* Don't leave your units unsupervised, naughty
//* them may try to get out of the map bounds and
//* crash your game.
//*
//* To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//*************************************************
//==================================================
globals
// High enough so the unit is no longer visible, low enough so the
// game doesn't crash...
//
// I think you need 0.0 or soemthing negative prior to patch 1.22
//
private constant real EXTRA = 500.0
endglobals
//=========================================================================================
globals
private real maxx
private real maxy
private real minx
private real miny
endglobals
//=======================================================================
private function dis takes nothing returns nothing
local unit u=GetTriggerUnit()
local real x=GetUnitX(u)
local real y=GetUnitY(u)
if(x>maxx) then
set x=maxx
elseif(x<minx) then
set x=minx
endif
if(y>maxy) then
set y=maxy
elseif(y<miny) then
set y=miny
endif
call SetUnitX(u,x)
call SetUnitY(u,y)
set u=null
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
local region r=CreateRegion()
local rect rc
set minx=GetCameraBoundMinX() - EXTRA
set miny=GetCameraBoundMinY() - EXTRA
set maxx=GetCameraBoundMaxX() + EXTRA
set maxy=GetCameraBoundMaxY() + EXTRA
set rc=Rect(minx,miny,maxx,maxy)
call RegionAddRect(r, rc)
call RemoveRect(rc)
call TriggerRegisterLeaveRegion(t,r, null)
call TriggerAddAction(t, function dis)
//this is not necessary but I'll do it anyway:
set t=null
set r=null
set rc=null
endfunction
endlibrary