Name | Type | is_array | initial_value |
hero | unit | No |
//TESH.scrollpos=22
//TESH.alwaysfold=0
/*
Polarisation
============
Coded by: Mr_Bean
Idea by: Sage Chow
Requirements:
-------------
> JASS NewGen Pack (http://www.hiveworkshop.com/forums/miscellaneous-tutorials-456/how-download-install-confige-jngp-160547)
> The "TimerUtils" system by Vexorian (included).
Installation:
-------------
> Copy the following from the Trigger Editor:
- "TimerUtils" trigger, if you don't have it already.
- "Polarisation" trigger.
- This trigger; it may be helpful in the future.
> Copy the "Polarisation" hero ability. Update SPELL_ID in the Polarisation trigger.
> Configure the configurables section in the Polarisation trigger as desired.
> If you want the custom icons, export the .blp files in the Import Editor and import them as follows:
- BTNCUST_Polarisation.blp -> ReplaceableTextures\CommandButtons\BTNCUST_Polarisation.blp
- DISBTNCUST_Polarisation.blp -> ReplaceableTextures\CommandButtonsDisabled\DISBTNCUST_Polarisation.blp
> If you want the custom model used, export the .mdx file from the Import Manager of the demo map. Import it into your map with
the same path as the one in the demo map. Be sure to credit DonDustin if you use the model.
Contact:
--------
> [email protected]
> Mr_Bean987 on the HiveWorkshop.com
*/
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
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* 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)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* 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.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//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")
set tT[0]=CreateTimer()
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")
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
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
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
if ( didinit ) then
return
else
set didinit = true
endif
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