library Polarisation initializer onInit requires TimerUtils
//**************************************************\\
//*** START OF CONFIGURABLES ***\\
//**************************************************\\
globals
// Raw ID of the "Polarisation" hero ability:
private constant integer SPELL_ID = 'A000'
// Enable preloading to prevent in-game lag:
private constant boolean ENABLE_PRELOAD = true
// How long the charge lasts on each unit:
private constant real DURATION = 90.0
// Area of effect of the spell:
private constant real SPELL_AOE = 300.0
// How often negative units zap positive units:
private constant real ZAP_FREQUENCY = 1.0
// How close a positive unit needs to be to a negative one to get zapped:
private constant real ZAP_AOE = 500.0
// How often the system updates each unit's remaining time (shouldn't need to be changed):
private constant real UPDATE_FREQUENCY = 0.1
// How long the lightning bolt lasts:
private constant real BOLT_DURATION = 0.5
// Code of the desired lightning:
private constant string BOLT_CODE = "CLPB"
// Effect created on charged untis:
private constant string EFFECT = "Abilities\\Spells\\Orc\\Purge\\PurgeBuffTarget.mdl"
// Attachment point of EFFECT:
private constant string EFFECT_ATTACHMENT = "origin"
// Effect created on units when they get zapped:
private constant string ZAP_EFFECT = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
// Attachment point for ZAP_EFFECT:
private constant string ZAP_EFFECT_ATTACH = "origin"
// Effect created at the target point:
private constant string AREA_EFFECT = "war3mapImported\\RollingStormSFX.mdx"
// Colour of the floating text showing a +:
private constant string POSITIVE_COLOUR = "|cff00ff00"
// Colour of the floating text showing a -:
private constant string NEGATIVE_COLOUR = "|cffff0000"
endglobals
// Configure how the damage per level is calculated:
private function GetDamage takes integer level returns real
return (25.0 * level) + 25.0
endfunction
//**************************************************\\
//*** ADVANCED CONFIGURABLES ***\\
//**************************************************\\
globals
private constant real LIGHTNING_UPDATE_FREQUENCY = 0.03 // How often lightnings are updated.
private constant real TEXTTAG_UPDATE_FREQUENCY = 0.03 // How often texttags are updated.
private constant real TEXTTAG_HEIGHT_OFFSET = 25.0 // Height offset of texttags.
private constant real TEXTTAG_HEIGHT = 0.035 // Height of texttags when created.
private constant string POSITIVE = "positive" // Name for positive charge.
private constant string NEGATIVE = "negative" // Name for negative charge.
private constant key CHARGE_KEY // The unit's current charge (positive/negative).
private constant key TIME_KEY // How long the charge has left on the unit.
private constant key LEVEL_KEY // The level of the spell cast on the unit.
private constant key TEXT_TAG_KEY // The unit's texttag showing it's charge.
private constant key EFFECT_KEY // The unit's effect.
private constant key CASTER_KEY // Who cast the spell on the unit.
endglobals
//**************************************************\\
//*** END OF CONFIGURABLES ***\\
//**************************************************\\
globals
private hashtable ht = InitHashtable() // Stores all the data.
private group positive = CreateGroup() // Stores all positive units.
private group negative = CreateGroup() // Stores all negative units.
private group enumG = CreateGroup() // For group enumeration.
private unit dummy // Temporary unit for dummy casting.
private unit enumU // For group enumeration.
private timer textTagTimer // Timer that moves all units' texttags.
private timer updateTimer // Timer that updates all units' time remaining.
private timer zapTimer // Timer that makes negatives zap positives.
private boolean isTicking = false // Saves whether the timers are ticking or not.
endglobals
//**************************************************
/*
True if unit is:
- alive
- not magic immune
- not a structure
*/
private function IsUnitTargetable takes unit u returns boolean
return not IsUnitType(u, UNIT_TYPE_DEAD) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(u, UNIT_TYPE_STRUCTURE)
endfunction
// Creates a texttag which is only visible to the specified player:
private function NewTextTagUnitVis takes string colour, string text, unit u, player p returns texttag
local texttag tt = CreateTextTag()
call SetTextTagText(tt, colour + text + "|r", TEXTTAG_HEIGHT)
call SetTextTagPosUnit(tt, u, TEXTTAG_HEIGHT_OFFSET)
call SetTextTagPermanent(tt, true)
if IsPlayerAlly(GetLocalPlayer(), p) then
call SetTextTagVisibility(tt, true)
endif
return tt
endfunction
//**************************************************
private struct Lightning
unit u1
unit u2
unit caster // Used for dealing damage.
lightning bolt
real damage
real left
private method destroy takes nothing returns nothing
call DestroyLightning(.bolt)
set .bolt = null
set .u1 = null
set .u2 = null
set .caster = null
call .deallocate()
endmethod
private static method move takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
// Move the lightning and update time left:
call MoveLightning(.bolt, false, GetUnitX(.u1), GetUnitY(.u1), GetUnitX(.u2), GetUnitY(.u2))
set .left = .left - LIGHTNING_UPDATE_FREQUENCY
// If the time has expired, damage the target and create an effect:
if .left <= 0 then
call ReleaseTimer(GetExpiredTimer())
call UnitDamageTarget(.caster, .u2, .damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
call DestroyEffect(AddSpecialEffectTarget(ZAP_EFFECT, .u2, ZAP_EFFECT_ATTACH))
call .destroy()
endif
endmethod
public static method create takes unit u1, unit u2, unit c, real dmg returns thistype
local thistype this = thistype.allocate()
// Store stuff:
set .u1 = u1
set .u2 = u2
set .caster = c
set .damage = dmg
set .left = BOLT_DURATION
// Create lightning and start timer:
set .bolt = AddLightning(BOLT_CODE, true, GetUnitX(.u1), GetUnitY(.u1), GetUnitX(.u2), GetUnitY(.u2))
call TimerStart(NewTimerEx(this), LIGHTNING_UPDATE_FREQUENCY, true, function thistype.move)
return this
endmethod
endstruct
private function MoveTextTag takes nothing returns nothing
// Move the texttag to the unit:
call SetTextTagPosUnit(LoadTextTagHandle(ht, GetHandleId(GetEnumUnit()), TEXT_TAG_KEY), GetEnumUnit(), TEXTTAG_HEIGHT_OFFSET)
endfunction
private function DisplayCharge takes nothing returns nothing
// If there are no units in either group, pause all timers:
if CountUnitsInGroup(positive) == 0 and CountUnitsInGroup(negative) == 0 then
set isTicking = false
call PauseTimer(textTagTimer)
call PauseTimer(updateTimer)
call PauseTimer(zapTimer)
// Otherwise, move all the units' texttags:
else
call ForGroup(positive, function MoveTextTag)
call ForGroup(negative, function MoveTextTag)
endif
endfunction
private function ZapPositivesEnum takes nothing returns nothing
local unit u = GetEnumUnit()
local unit temp
local boolean stop = false
// Group all unit near the target:
call GroupEnumUnitsInRange(enumG, GetUnitX(u), GetUnitY(u), ZAP_AOE, null)
loop
set temp = GroupPickRandomUnit(enumG) // Pick a random unit.
exitwhen temp == null or stop // Stop searching if the unit is alive and positive.
if not IsUnitType(temp, UNIT_TYPE_DEAD) and LoadStr(ht, GetHandleId(temp), CHARGE_KEY) == POSITIVE then
set stop = true
call Lightning.create(u, temp, LoadUnitHandle(ht, GetHandleId(temp), CASTER_KEY), GetDamage(LoadInteger(ht, GetHandleId(temp), LEVEL_KEY))) // Create a lightning (also damages).
endif
call GroupRemoveUnit(enumG, temp)
endloop
call GroupClear(enumG)
set u = null
set temp = null
endfunction
private function ZapPositives takes nothing returns nothing
// For all positive units, zap nearby negatives:
call ForGroup(negative, function ZapPositivesEnum)
endfunction
private function CheckDuration takes nothing returns nothing
local unit u = GetEnumUnit()
local integer handleId = GetHandleId(u)
local real time = LoadReal(ht, handleId, TIME_KEY)
local string status = LoadStr(ht, handleId, CHARGE_KEY)
set time = time - UPDATE_FREQUENCY
// If the unit's charge expires or it is dead:
if time <= 0 or IsUnitType(u, UNIT_TYPE_DEAD) then
// Remove from groups:
if status == POSITIVE then
call GroupRemoveUnit(positive, u)
else
call GroupRemoveUnit(negative, u)
endif
call DestroyTextTag(LoadTextTagHandle(ht, handleId, TEXT_TAG_KEY)) // Destroy texttag.
call DestroyEffect(LoadEffectHandle(ht, handleId, EFFECT_KEY)) // Destroy effect.
call FlushChildHashtable(ht, handleId) // Flush hashtable.
// Otherwise, update it's time left:
else
call SaveReal(ht, handleId, TIME_KEY, time)
endif
set u = null
endfunction
private function CheckGroups takes nothing returns nothing
// For positives and negatives, check their remaining durations and whether each unit is alive:
call ForGroup(positive, function CheckDuration)
call ForGroup(negative, function CheckDuration)
endfunction
private function RegisterUnit takes unit u, unit c, integer level, player p returns nothing
local integer handleId = GetHandleId(u)
local string status = LoadStr(ht, handleId, CHARGE_KEY)
local texttag tt = LoadTextTagHandle(ht, handleId, TEXT_TAG_KEY)
local effect eff = LoadEffectHandle(ht, handleId, EFFECT_KEY)
// If the unit already has a texttag, destroy it:
if tt != null then
call DestroyTextTag(tt)
set tt = null
endif
// If the unit already has an effect, destroy it:
if eff != null then
call DestroyEffect(eff)
set eff = null
endif
call SaveReal(ht, handleId, TIME_KEY, DURATION) // Save duration.
call SaveInteger(ht, handleId, LEVEL_KEY, level) // Save ability level.
call SaveEffectHandle(ht, handleId, EFFECT_KEY, AddSpecialEffectTarget(EFFECT, u, EFFECT_ATTACHMENT)) // Create and save effect.
call SaveUnitHandle(ht, handleId, CASTER_KEY, c) // Save caster (for dealing damage).
// If the unit has a positive charge:
if status == POSITIVE then
call SaveStr(ht, handleId, CHARGE_KEY, NEGATIVE) // Change it to negative.
call GroupRemoveUnit(positive, u) // Remove from positives.
call GroupAddUnit(negative, u) // Add to negatives.
call SaveTextTagHandle(ht, handleId, TEXT_TAG_KEY, NewTextTagUnitVis(NEGATIVE_COLOUR, "-", u, p)) // Create a texttag and save it.
// Otherwise:
else
call SaveStr(ht, handleId, CHARGE_KEY, POSITIVE) // Change it to positive.
if IsUnitInGroup(u, negative) then // Remove from negatives if it is in the group.
call GroupRemoveUnit(negative, u)
endif
call GroupAddUnit(positive, u) // Add to positives.
call SaveTextTagHandle(ht, handleId, TEXT_TAG_KEY, NewTextTagUnitVis(POSITIVE_COLOUR, "+", u, p)) // Create a texttag and save it.
endif
endfunction
private function SpellActions takes nothing returns nothing
local unit caster = GetTriggerUnit()
local player owner = GetTriggerPlayer()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
call DestroyEffect(AddSpecialEffect(AREA_EFFECT, GetSpellTargetX(), GetSpellTargetY()))
// Group all units within range of the point:
call GroupEnumUnitsInRange(enumG, GetSpellTargetX(), GetSpellTargetY(), SPELL_AOE, null)
for enumU in enumG
if not IsUnitType(enumU, UNIT_TYPE_DEAD) and IsUnitTargetable(enumU) then
// Register the target in the 'system':
call RegisterUnit(enumU, caster, level, owner)
// Start the timers if they aren't already going:
if not isTicking then
set isTicking = true
call TimerStart(textTagTimer, TEXTTAG_UPDATE_FREQUENCY, true, function DisplayCharge)
call TimerStart(updateTimer, UPDATE_FREQUENCY, true, function CheckGroups)
call TimerStart(zapTimer, ZAP_FREQUENCY, true, function ZapPositives)
endif
endif
endfor
set caster = null
set owner = null
endfunction
private function CheckSpell takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call SpellActions()
endif
return false
endfunction
private function onInit takes nothing returns nothing
local trigger t = CreateTrigger()
// Preloading:
static if ENABLE_PRELOAD then
local unit u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'Hpal', 0, 0, 0)
call UnitAddAbility(u, SPELL_ID)
call RemoveUnit(u)
call Preload(ZAP_EFFECT)
call Preload(EFFECT)
set u = null
endif
// Timers:
set textTagTimer = NewTimer()
set zapTimer = NewTimer()
set updateTimer = NewTimer()
// Register the spell:
call TriggerAddCondition(t, Condition(function CheckSpell))
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
set t = null
endfunction
endlibrary