Chain System [SUMI] [ZINC] [V2.0]

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
  • Like
Reactions: Garfield1337
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
Contents

Chain System [V1.0] (Map)

Reviews
12th Dec 2015 IcemanBo: For long time as NeedsFix. Rejected. Bribe: Needs an in-game screenshot.

Moderator

M

Moderator

12th Dec 2015
IcemanBo: For long time as NeedsFix. Rejected.

Bribe:

Needs an in-game screenshot.
 
Wow nice, and its in Zinc!!! been a long time since I last saw a zinc spell uploaded here... looks pretty cool too... ^_^

and oh, some of my systems have longer call functions... ^_^


question:

what will happen if we change the 200 thingy?

and why are some values hard-coded? like the 2 in this example
if (Chain[x].TickCount >= 2)

Suggestion:
Add interface methods especially one which runs upon getting hit by lightning so that we can easily add things like special buffs upon hit...

and make the texttags optional...
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
Why do you prefix the timer and the group? They're private.

Make a changeable constant for the "200" limit, should be changeable.

Why aren't attacktype, damagetype and weapontype in the function call?

Use SetWidgetLife instead of setunitstate, it's faster.

If the damage should be recognized as melee or range should also be two parameters.

doesn't this leak a texttag? I mean those texttag calls for lifespan are only used if the texttag boolean is true but the texttag is created no matter what

why do you even consider using local locations? use two static locations in the struct or two globals and move the locations instead

use GetWidgetLife

what if the user wants the hit effect to be created at the chest attachment of the target unit?

recycle groups, destroying them in vJASS/ZINC is just lame

create interface or take a function into the parameter when a unit is hit by a lightning (as Adiktuz said)
 
Level 11
Joined
Sep 30, 2009
Messages
697
The System looks nice, but it still needs some work. Yeah its easy to use, but I am missing much more customization options. What if the units hit shall get a buff instead of damage? Some sort of interface or function interfaces would be nice. Also you use FirstOfGroup(). So Heroes would get hit first always and then other units (which is good often), but what if the user wants to hit really randomly?
 
Level 15
Joined
Oct 16, 2010
Messages
941
Thanks for your comments, i'll get around to fixing this stuff eventually.

I think i'll make two call functions, one that is very simple and has only 6 or so parameters and another that has full cutomization with 13 or 14 parameters.


______________________________________

what will happen if we change the 200 thingy

It changes the max instance amount, meaning that atm you can only have 200 chains active at one time on the map. I don't see why anybody would ever have more than 10 going at once, but i've still made it a global that people can modify in the next version as base suggested.

and why are some values hard-coded? like the 2 in this example

changed to a global in the next version along with an explanation of what it does.

Suggestion:
Add interface methods especially one which runs upon getting hit by lightning so that we can easily add things like special buffs upon hit...

working on it ^-^

and make the texttags optional...

They are optional, the last parameter sent in a boolean on whether or not to show texttags or not.

Why do you prefix the timer and the group? They're private.

Because otherwise they are just called "TIMER" and "GROUP" which annoys me personally as a coder.

Make a changeable constant for the "200" limit, should be changeable.

done ^-^

Why aren't attacktype, damagetype and weapontype in the function call?

made an alternative function call that includes these things

Use SetWidgetLife instead of setunitstate, it's faster.

done

If the damage should be recognized as melee or range should also be two parameters.

Also in the alternative function call

doesn't this leak a texttag? I mean those texttag calls for lifespan are only used if the texttag boolean is true but the texttag is created no matter what

my bad. fixed.

why do you even consider using local locations? use two static locations in the struct or two globals and move the locations instead

done ^-^

use GetWidgetLife

done ^-^

what if the user wants the hit effect to be created at the chest attachment of the target unit?

working on a way to fit this in
 
Last edited:
At the top of the code, you should add this:
JASS:
    constant string LIGHTNING_CHAIN_LIGHTNING_PRIMARY   = "CLPB";
    constant string LIGHTNING_CHAIN_LIGHTNING_SECONDARY = "CLSB";
    constant string LIGHTNING_DRAIN                     = "DRAB";
    constant string LIGHTNING_DRAIN_LIFE                = "DRAL";
    constant string LIGHTNING_DRAIN_MANA                = "DRAM";
    constant string LIGHTNING_FINGER_OF_DEATH           = "AFOD";
    constant string LIGHTNING_FORKED_LIGHTNING          = "FORK";
    constant string LIGHTNING_HEALING_WAVE_PRIMARY      = "HWPB";
    constant string LIGHTNING_HEALING_WAVE_SECONDARY    = "HWSB";
    constant string LIGHTNING_LIGHTNING_ATTACK          = "CHIM";
    constant string LIGHTNING_MAGIC_LEASH               = "LEAS";
    constant string LIGHTNING_MANA_BURN                 = "MBUR";
    constant string LIGHTNING_MANA_FLARE                = "MFPB";
    constant string LIGHTNING_SPIRIT_LINK               = "SPLK";

As a reference because some people are either: a) too lazy to look them up, or b) don't know what the string is supposed to be. :)

Also, your group recycling is a bit overcomplicated. It can simply be:
JASS:
//! zinc
library GroupRecycling
{
    group groups[];
    private integer index = 0;
    
    function ReleaseGroup(group g) -> nothing
    {
        GroupClear(g)
        groups[index] = g;
        index = index + 1;
    }
    
    function GetGroup() -> group
    {
        if (index == 0) { return CreateGroup(); }
        index = index - 1;
        return groups[index];
    }
}
//! endzinc

Which is the method used in this system:
http://www.thehelper.net/forums/showthread.php/136087-Recycle
 
Last edited:
Level 15
Joined
Oct 16, 2010
Messages
941
I think the easier function call should use ATTACK_TYPE_SPELL and DAMAGE_TYPE_MAGIC instead of normal...

The ones I used are the attacktype and damagetype that normal WC3 spells use. Although, you wouldn't know it from the variable names.

Or you use GroupUtils as the rest of us does ;D

I'll pry just do that.
________________________
Thanks for all your feedback
 
Level 15
Joined
Jul 6, 2009
Messages
889
==================================================
--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)

Things the object editor abilities allow you to do:
  • the lightning follows the target correctly. This system only makes the lightning go to the unit's coordinates.
  • the lightning stems from the casting unit. This one is just strange.

sdfsf

Everything be a mess, a giant mess! MESS. May I recommend you take a look at this? This is something people should be seeing instead of your current thing. O_O. O_O. O_O.
 
== true

Please get rid of those.

Seeing as how you use ExecuteFunc, this is not compatible with Vexorian's Map Optimizer. With the extremely long variable names you use, the Optimizer would come in very handy in order to 1. reduce map file size, 2. improve in-game speed (yes, long variable names slow down the game, it's unreal)

BoltPeriodic() iterates through a linear stack instead of a circularly linked-list, which slows down performance. Storing values into next/prev nodes is faster in every way.

GetNextAvailableChainIndex and GetNextAvailableBoltIndex use an O(N) search which is the world's slowest search. There are plenty of better indexing techniques than this, even Vexorian's struct-indexing technique.

Chains[index].destroy();

You're calling this from above the struct, meaning that destroy is called via a function interface instead of a proper call.
 
Top