This system has not been extensively tested, so please report any problems you have.
==========
--Description--
==========
Chain spells are a huge part of blizzard games. They are in Diablo 2, Diablo 3, Warcraft 2, Warcraft 3, World of Warcraft, and will likely play a role in some abilities of their NewGen MMO. Many map-makers like to use the chain lightning and healing wave abilities built into the object editor of Warcraft 3. However, using the object editor means no extensive control over the ability in-game. With this system, you can create chain spells with just one function call (GUI-friendly). This system should be SUMI and leakless.
==================================================
--Why use this over chain lightning/healing wave in object editor?--
==================================================
Things you can do if you create your chain spells using this system that you couldn't if you use the object editor:
- change the damage/heal amount to anything at anytime.
- use any lightning types that you want
- show floating text that displays the amount damage/healed over each unit.
- apply custom stats you may have used in your map (such as spellpower or magic defense)
- apply custom actions to units hit by the chain (such as stun, slow, or inner fire)
- modify the color of the lightning
- modify the spawn interval and lifespan of each bolt
==============
--Implementation--
==============
All you got to do is copy and paste the code below (and the required library) into your map header. You DO NOT need to touch or modify the code at all. After that you can use the call statement (outlined in the documentation below) to create your chains with ease.
==========
--Requires--
==========
GroupUtils by Rising_Dusk
=============
--Authors Note--
=============
I know the call function is ridiculously long, but it is a necessary evil that gives the user more control over the system. Please report any problems you have or errors you find. Further documentation can be found in the system code.
[jass=Code]//=====================================================================================================
//
// ===== = = ===== ===== = =
// == = = = = = == =
// == ===== ===== = = = =
// == = = = = = = ==
// ===== = = = = ===== = =
//
// = = ===== ===== = ====
// = = = = = =
// = = = = = ===
// = = = = = =
// ===== = ===== ===== ====
// By Raven0 V2.0
//
//
// --Description--
// Allows for easy creation and modification of chain effects
//
//
// --Capabilities--
// You can:
//
// - Create a chain of any lightning type
// - Set the number of bounces, unit detection radius, damage, and damage reduction of the chain
// - Set the lightning color of the chain
// - Create a "handler function" for the chain (which runs whenever a unit is hit by a chain effect)
// - Display damage text
// - Modify the creation interval, duration, and z-offset of the lightning bolts
// - Cause the chain to heal instead of damage
//
//
// --Functions--
// Can be found easily at the bottom of the library
//
//
// --Lightning ID's--
//
// (thanks to PurgeandFire111):
//
// LIGHTNING_CHAIN_LIGHTNING_PRIMARY = "CLPB";
// LIGHTNING_CHAIN_LIGHTNING_SECONDARY = "CLSB";
// LIGHTNING_DRAIN = "DRAB";
// LIGHTNING_DRAIN_LIFE = "DRAL";
// LIGHTNING_DRAIN_MANA = "DRAM";
// LIGHTNING_FINGER_OF_DEATH = "AFOD";
// LIGHTNING_FORKED_LIGHTNING = "FORK";
// LIGHTNING_HEALING_WAVE_PRIMARY = "HWPB";
// LIGHTNING_HEALING_WAVE_SECONDARY = "HWSB";
// LIGHTNING_LIGHTNING_ATTACK = "CHIM";
// LIGHTNING_MAGIC_LEASH = "LEAS";
// LIGHTNING_MANA_BURN = "MBUR";
// LIGHTNING_MANA_FLARE = "MFPB";
// LIGHTNING_SPIRIT_LINK = "SPLK";
//
//=====================================================================================================
//! zinc
library ChainSystem requires GroupUtils
{
private
{
constant real Bolt_CreationInterval = .2; //How often do 'bounces' occur
constant real Bolt_MoveInterval = .05; //How often do bolts compensate for unit movement
constant real Bolt_Lifespan = .8; //After how long are bolts destroyed
constant real Bolt_ZOffset = 65; //At what height off the ground are bolts created
}
//============================================================================================//
//============================================================================================//
//==========================Do Not Modify Any of the Code Below===============================//
//============================================================================================//
//============================================================================================//
private
{
//==========================================//
constant timer CHAIN_TIMER = CreateTimer();
constant timer BOLT_TIMER = CreateTimer();
group POTENTIAL_TARGETS = CreateGroup();
//==========================================//
integer MaxChainIndex = 0;
Chain Chains[];
boolean ChainIndexUsed[];
Chain LastCreatedChain;
//==========================================//
integer MaxBoltIndex = 0;
Bolt Bolts[];
boolean BoltIndexUsed[];
//==========================================//
location Loc1 = Location(0, 0);
location Loc2 = Location(0, 0);
//==========================================//
}
public
{
unit Chain_LastHitUnit;
}
function DestroyChain(integer index)
{
ReleaseGroup(Chains[index].UnitsHit);
ChainIndexUsed[index] = false;
Chains[index].destroy();
}
function GetNextAvailableChainIndex() -> integer
{
integer count = 0;
while (ChainIndexUsed[count] == true)
{
count = count + 1;
}
if (count > MaxChainIndex) {MaxChainIndex = count;}
ChainIndexUsed[count] = true;
return count;
}
function GetNextAvailableBoltIndex() -> integer
{
integer count = 0;
while (BoltIndexUsed[count] == true)
{
count = count + 1;
}
if (count > MaxBoltIndex) {MaxBoltIndex = count;}
BoltIndexUsed[count] = true;
return count;
}
struct Bolt
{
unit Origin,Target;
real CurrentLife,DecayPoint;
lightning Lightning;
integer Index;
method Initialize(integer index, unit origin, unit target, real duration, lightning whichlightning)
{
this.Origin = origin;
this.Target = target;
this.Lightning = whichlightning;
this.Index = index;
this.DecayPoint = duration;
this.CurrentLife = 0;
}
method Tick()
{
real Origin_X = GetUnitX(this.Origin);
real Origin_Y = GetUnitY(this.Origin);
real Target_X = GetUnitX(this.Target);
real Target_Y = GetUnitY(this.Target);
MoveLocation(Loc1, Origin_X, Origin_Y);
MoveLocation(Loc2, Target_X, Target_Y);
if (this.Target != null)
{
MoveLightningEx(this.Lightning, true, Origin_X, Origin_Y, GetLocationZ(Loc1) + Bolt_ZOffset, Target_X, Target_Y, GetLocationZ(Loc2) + Bolt_ZOffset);
}
this.CurrentLife = this.CurrentLife + Bolt_MoveInterval;
if (this.CurrentLife > this.DecayPoint)
{
DestroyLightning(this.Lightning);
BoltIndexUsed[this.Index] = false;
this.destroy();
}
}
}
struct Chain
{
unit Caster,Target;
string PrimaryLightning,SecondaryLightning,HitEffect,AttachmentPoint;
integer MaxJumps,CurrentJumps;
real Radius,CurrentDamage,Reduction,X,Y,BoltLife,Red,Green,Blue;
boolean ShowText,HitsFriendly,HitsEnemy,HitsStructure;
attacktype AtkType;
damagetype DmgType;
string UserDefinedFunction;
group UnitsHit;
method GetNextAvailableTarget() -> unit
{
player CastingPlayer = GetOwningPlayer(this.Caster);
unit picked;
GroupEnumUnitsInRange(POTENTIAL_TARGETS, this.X, this.Y, this.Radius, null);
picked = FirstOfGroup(POTENTIAL_TARGETS);
while (picked != null)
{
if ((GetUnitState(picked, UNIT_STATE_LIFE) > 0) && (IsUnitInGroup(picked, this.UnitsHit) == false) && ((this.HitsStructure) || (IsUnitType(picked, UNIT_TYPE_STRUCTURE) == false)))
{
if (((this.HitsFriendly) && (IsUnitAlly(picked, CastingPlayer))) || ((this.HitsEnemy) && (IsUnitEnemy(picked, CastingPlayer))))
{
break;
}
}
GroupRemoveUnit(POTENTIAL_TARGETS, picked);
picked = FirstOfGroup(POTENTIAL_TARGETS);
}
return picked;
}
static method DamageUnit(unit caster, unit target, real damage, boolean Text, attacktype AtkType, damagetype DmgType)
{
texttag dmgtxt;
string txtoutput;
if (damage > 0)
{
UnitDamageTarget(caster, target, damage, true, false, AtkType, DmgType, WEAPON_TYPE_WHOKNOWS);
txtoutput = ("|cffCD0000" + I2S(R2I(damage)) + "|r");
}
else
{
SetWidgetLife(target, (GetWidgetLife(target) + damage));
txtoutput = ("|cff00CD00" + I2S(R2I(damage)) + "|r");
}
if (Text)
{
dmgtxt = CreateTextTag();
SetTextTagPermanent(dmgtxt, false);
SetTextTagVisibility(dmgtxt, true);
SetTextTagPosUnit(dmgtxt, target, 0);
SetTextTagText(dmgtxt, txtoutput, .023);
SetTextTagFadepoint(dmgtxt, 1);
SetTextTagLifespan(dmgtxt, 1.5);
SetTextTagVelocity(dmgtxt, 0, .05);
SetTextTagColor(dmgtxt, 255, 255, 255, 255);
}
}
method SpawnBolt(unit origin, unit target, string whichkind)
{
integer x = GetNextAvailableBoltIndex();
real BoltLifespan = this.BoltLife;
lightning Lightning;
if (BoltLifespan == 0)
{
BoltLifespan = Bolt_Lifespan;
}
MoveLocation(Loc1, GetUnitX(origin), GetUnitY(origin));
MoveLocation(Loc2, GetUnitX(target), GetUnitY(target));
Bolts[x] = Bolt.create();
if (whichkind == "primary")
{
Lightning = AddLightningEx(this.PrimaryLightning, true, GetUnitX(origin), GetUnitY(origin), (GetLocationZ(Loc1) + Bolt_ZOffset), GetUnitX(target), GetUnitY(target), (GetLocationZ(Loc2) + Bolt_ZOffset));
}
else
{
Lightning = AddLightningEx(this.SecondaryLightning, true, GetUnitX(origin), GetUnitY(origin), (GetLocationZ(Loc1) + Bolt_ZOffset), GetUnitX(target), GetUnitY(target), (GetLocationZ(Loc2) + Bolt_ZOffset));
}
SetLightningColor(Lightning, this.Red, this.Green, this.Blue, 1);
Bolts[x].Initialize(x, origin, target, BoltLifespan, Lightning);
DestroyEffect(AddSpecialEffectTarget(this.HitEffect, target, this.AttachmentPoint));
this.DamageUnit(this.Caster, target, this.CurrentDamage, this.ShowText, this.AtkType, this.DmgType);
this.CurrentDamage = this.CurrentDamage * this.Reduction;
GroupAddUnit(this.UnitsHit, target);
if (this.UserDefinedFunction != null)
{
Chain_LastHitUnit = target;
ExecuteFunc(this.UserDefinedFunction);
}
}
method Jump()
{
unit picked = this.GetNextAvailableTarget();
if (picked == null)
{
this.destroy();
}
else
{
this.CurrentJumps = this.CurrentJumps + 1;
this.SpawnBolt(this.Target, picked, "secondary");
this.Target = picked;
}
picked = null;
}
method Initialize(unit caster, unit target, string p_lightning, string s_lightning, string hiteffect, string attachmentpoint, integer jumps, real radius, real dmg, real reduct, boolean showtext, boolean affects_allies, boolean affects_enemies, boolean affects_structures, attacktype atktype, damagetype dmgtype) -> thistype
{
this.Caster = caster;
this.Target = target;
this.X = GetUnitX(target);
this.Y = GetUnitY(target);
this.PrimaryLightning = p_lightning;
this.SecondaryLightning = s_lightning;
this.HitEffect = hiteffect;
this.AttachmentPoint = attachmentpoint;
this.CurrentJumps = 0;
this.Red = 1;
this.Green = 1;
this.Blue = 1;
this.MaxJumps = jumps;
this.Radius = radius;
this.CurrentDamage = dmg;
this.BoltLife = 0;
this.Reduction = (100 - reduct) / 100;
this.ShowText = showtext;
this.HitsFriendly = affects_allies;
this.HitsEnemy= affects_enemies;
this.HitsStructure = affects_structures;
this.AtkType = atktype;
this.DmgType = dmgtype;
this.UnitsHit = NewGroup();
this.SpawnBolt(caster, target, "primary");
return this;
}
}
function ChainPeriodic()
{
integer x;
for (0 <= x <= MaxChainIndex)
{
if (ChainIndexUsed[x] == true)
{
if (Chains[x].CurrentJumps <= Chains[x].MaxJumps)
{
Chains[x].Jump();
}
else
{
DestroyChain(x);
}
}
}
}
function BoltPeriodic()
{
integer x;
for (0 <= x <= MaxBoltIndex)
{
if (BoltIndexUsed[x] == true)
{
Bolts[x].Tick();
}
}
}
function onInit()
{
TimerStart(CHAIN_TIMER, Bolt_CreationInterval, true, function ChainPeriodic);
TimerStart(BOLT_TIMER, Bolt_MoveInterval, true, function BoltPeriodic);
}
//********************************************************************************************//
//********************************************************************************************//
////========================================================================================////
//////====================================================================================//////
////////===============================CALL FUNCTIONS===================================////////
//////====================================================================================//////
////========================================================================================////
//********************************************************************************************//
//********************************************************************************************//
//===============================
//=====Primary Call Function=====
//===============================
public function CreateChain(unit Caster, unit Target, string PrimaryLightningId, string SecondaryLightningId, string HitEffect, string HitEffectAttachmentPoint, integer Jumps, real Radius, real Damage, real PercentReduction, boolean ShowText, boolean AffectsAllies, boolean AffectsEnemies, boolean AffectsStructures, attacktype WhichAttackType, damagetype WhichDamageType)
{
integer x = GetNextAvailableChainIndex();
Chains[x] = Chain.create();
LastCreatedChain = Chains[x];
Chains[x].Initialize(Caster, Target, PrimaryLightningId, SecondaryLightningId, HitEffect, HitEffectAttachmentPoint, Jumps, Radius, Damage, PercentReduction, ShowText, AffectsAllies, AffectsEnemies, AffectsStructures, WhichAttackType, WhichDamageType);
}
//===============================
//======Advanced Modifiables=====
//===============================
public function SetLastCreatedChain_CustomBoltDuration(real BoltDuration)
{
//Bolts will disappear after this whatever duration you set
LastCreatedChain.BoltLife = BoltDuration;
}
public function SetLastCreatedChain_HandlerFunction(string HandlerFunctionName)
{
//This function will be called whenever a unit is hit with a bolt
//Useful for making chain spells that stun or cripple enemies
//Use "Chain_LastHitUnit" to refer to the unit being hit by the bolt
LastCreatedChain.UserDefinedFunction = HandlerFunctionName;
}
public function SetLastCreatedChain_Color(real Red, real Green, real Blue)
{
//Bolts of this chain object will be set to these RGB values
//Setting custom RGB values may effect visibility of the lighting
if ((Red < 0) || (Red > 1)) {LastCreatedChain.Red = 1;} else {LastCreatedChain.Red = Red;}
if ((Green < 0) || (Green > 1)) {LastCreatedChain.Green = 1;} else {LastCreatedChain.Green = Green;}
if ((Blue < 0) || (Blue > 1)) {LastCreatedChain.Blue = 1;} else {LastCreatedChain.Blue = Blue;}
}
//===============================
//=========Simple Chains=========
//===============================
public function CreateChainLightning(unit Caster, unit Target, integer Jumps, real Radius, real Damage, real PercentReduction, boolean ShowText)
{
//Creates a basic chain lightning spell (similar to object editor one)
integer x = GetNextAvailableChainIndex();
Chains[x] = Chain.create();
LastCreatedChain = Chains[x];
Chains[x].Initialize(Caster, Target, "CLPB", "CLSB", "Abilities\\Weapons\\Bolt\\BoltImpact.mdl", "Origin", Jumps, Radius, Damage, PercentReduction, ShowText, false, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL);
}
public function CreateHealingWave(unit Caster, unit Target, integer Jumps, real Radius, real HealAmount, real PercentReduction, boolean ShowText)
{
//Creates a basic healing wave spell (similar to object editor one)
integer x = GetNextAvailableChainIndex();
Chains[x] = Chain.create();
LastCreatedChain = Chains[x];
Chains[x].Initialize(Caster, Target, "HWPB", "HWSB", "Abilities\\Spells\\Orc\\HealingWave\\HealingWaveTarget.mdl", "Origin", Jumps, Radius, (HealAmount * -1), PercentReduction, ShowText, true, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL);
}
}
//! endzinc[/code]
[jass=Changelog]
//[2-27-2011]
//More global configuration implemented
//Extended function call implemented
//Textag leak fixed
//Now recycles unit groups
//Some modifications made for efficiency
//[3-2-2011]
//Modified code to make more efficient
//Lightning now follows units
//Effect is now attached to units (rather than created at their position
//"simple" function calls included
//You can now add a "handler function" to each chain that will run whenever a unit is hit by that chain
//You can now modify the color of each chain
[/code]
Keywords:
Chain, Chain Lightning, Lightning, System, ZINC, zinc, Raven0, SUMI, Chain System, epic