• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Chain Ability System - v.06

  • Like
Reactions: Nyxohant and Vercas
Just a simple System that allows you to use Chain Abilities like Chainlightning with custom effects.

Credits goes to:
- Vexorian (Table)
- Cohader (TextTag)
- N-a-z-g-u-l (DummyModel

v.01 - Release
v.02 - Changed all integer values from hexadecimal to normal
v.03 - Rewrote the code abit
v.04 - Completly recoded the system
v.05 - Fixed some code related Stuff that could make the system malfunction
v.06 - Now uses a non-argument constructor and added lightning support


[jass=Documentation] -
-- ## ## --
-- ## -- # -- ## --
-- ## -- ## --- ## -- ## --
-- ## -- ## ## -- ## --
-- ## -- ChainAbilitySystem v.06 -- ## --
-- ## -- ## ## -- ## --
-- ## -- ## --- ## -- ## --
-- ## -- # -- ## --
-- ## ## --
-

-- # -- by Inferior -- # --

Credits:

Vexorian

Cohader

N-a-z-g-u-l

-- # -- Requirements: -- # --

Table

MissileSystem

-- # -- Documentation: -- # --

- Q: How to import this system?

A: First of all make sure you copy the ChainAbilitySystems code into your map. Then copy all required libraries
into your map. The next step is optional. Since this system uses the MissileSystem you also have to include the
Dummy library. There is a dummy model used in this library. If you want you can use this model. But you can also
use your own dummy unit. The only advantage with the model included in this map is, that you can change the
dummys Z-Facing.

- Q: How does this system work?

A: This System uses a projectile generated by the MissileSystem, which travels from one unit to another.
It allows the user to easily manipulate the projectiles behaviour, easy handling of target enumeration,
ensures leakfree and realistic physics in the projectiles movement and auto-recycling of all
Instances.

- Q: Which features are provided?

A: This System allows you to easily create your own Chaining Abilities like the "Far Seers" Chainlightning.
It allows declaring custom targeting Filters and custom target-manipulation functions.

- Q: How do i create my own Chaining Ability?

A: The best way to answer this question is to provide an example:
First of all you have to declare a new struct like this:

struct YourChainAbilityStruct

The next step is to add 2 methods.
The First method is the target enumeration Filter:

method targetFilter takes nothing returns boolean

Here you can declare a custom enumeration Filter for the Chaining Ability to filter out the correct targets.
I will give further informations after we declared our second method.

! Hint ! : This method has not to be declared, it is optional. If you do not have any Filters just do not declare this method.

The Second method is the target-manipulation function:

method onTargetHit takes nothing returns nothing

Here you can declare whatever shall happen, when a unit is hit by your Chaining Ability.

! Hint ! : This method has to be declared, otherwise your projectile will just move from unit to unit, but have no effect on them.

The next step is the code decleration in those 2 methods.
There are 3 values available to detect the current Target, the casting Unit and the allready hit targets.
All three variables use the prefix: "ChainAbility."

- To detect the current Target you can use this variable: ChainAbility.CurrentTarget
- To detect the casting Unit you can use this variable: ChainAbility.CastingUnit
- To detect the allready hit target amount you can use this variable: ChainAbility.CurrentHit

! Hint ! : These variables only work correctly when used in those 2 methods.
! Hint ! : In the targetFilter method, the ChainAbility.CurrentTarget functions as the GetFilterUnit(),
so use ChainAbility.CurrentTarget instead of GetFilterUnit()

You may add any other struct variables or methods you need for your Chaining Ability, but this has no effect on the main system.

Now that you added the 2 methods you can create your Chaining Ability:
To create a new Chaining Ability Instance you have to use the .create() method:

local YourChainAbilityStruct instance=YourChainAbilityStruct.create()

Now that you have created a new instance of your Chaining Ability, you can manipulate its behaviour:

set instance.missileSpeed = 900 - This changes the Chains projectile speed. Use values like in common Object Editor.

set instance.pickRange = 750 - This sets the AoE in which the projectile will check for new possible targets.
If this is not assigned the instance will be destroyed, when it reachs the first target.

set instance.missileModel = "Units\\...." - This assigns the Chain projectiles Model to the model Path entered.
The Chains projectile has no model by default.

set instance.lightningModel = "MBUR" - This enables the usage of lightnings in the Chain. The entered Path is the
lightnings model type. Lightning usage is disabled by default. Use this to enable it.

set instance.hitCount = 6 - This sets the number of targets that can be hit by the Chaining Ability.

set instance.useZArc = 0.5 - This sets the projectiles zArc, means the parabola curve of the projectile.
Use values like in common Object Editor, e.g. values between 0.0 and 1.0.

set instance.hitMoreThanOnce = true - Assigns whether a Unit can be hit more than one time by the same Chaining Ability.
This is set to false by default.

set instance.moveBackToSource = true - Assigns whether the Chains projectile shall move back to the source Unit when the last target is hit.
This is set to false by default.

When you are done with the movement behaviour manipulation you can start the Chaining Ability.
To start the Chaining Ability you have to use the .Cast() method:

call instance.Cast("for the arguments see 1 line below")

The .Cast() method requires 2 Arguments. The first Argument is the "source". The "source" argument specifies the Chains
Start and Casting/Source Unit (The Unit that sends out the Chain). The second Argument is the "target". The "target" argument
specifies the Unit that gets hit first.

And thats all you have to do...
If there is still something unclear, check the Samples to better understand this system.

- Q: How do lightning and missile work toegher?

A: As you know this system uses a Missile System. The model you assign to the Chains projectile will be created on the Missile
Systems Dummy. A lightning is created as soon as the Missile reaches the currently targeted Unit. If you do not define the Missiles
model, you do not have to worry that lightnings won´t work. A non-assigned Missile model does not mean there is no Missile.

If you want your Chain to only have a lightning, you still have to specify the speed value. The higher the speed value is,
the faster the lightning appears between the last and the new targeted Unit![/code]


[jass="ChainAbility"]library ChainAbilSystem requires MissileSystem, Table
//*************************************************************
//* ChainAbilSystem v.06 by Inferior
//*
//* Requirements:
//*
//* - MissileSystem by Inferior
//*
//* - Table by Vexorian
//*
//* The purpose of this library is to allow easy handling and creation
//* of Custom ChainAbilities like a Chainlightning with your own Effects.
//*
//* The best to understand this is an example:
//*
//* struct YourOwnChainAbility extends ChainAbility
//* "your Structvariables go here".......
//*
//* method targetFilter takes nothing returns boolean
//* return "your Filter".....
//* ! Use ChainAbility.CurrentTarget instead of GetFilterUnit()
//* ! Use ChainAbility.CastingUnit to get the Chains Source
//* ! Use ChainAbility.CurrentHit to get the count of allready hit units
//*
//* !!!! If this method cannot be found in the Childstruct the standard Filter will apply !!!!
//* endmethod
//*
//* method onTargetHit takes nothing returns nothing
//* ! Use ChainAbility.CurrentTarget to get the Unit hit by the Chain
//* ! Use ChainAbility.CastingUnit to get the Chains Source
//* ! Use ChainAbility.CurrentHit to get the count of allready hit units
//* "your code goes here"
//*
//* !!!! If this method cannot be found in the Childstruct nothing will happen when the Chain hits an Unit !!!!
//* endmethod
//*
//* "your other methods go here".......
//* endstruct
//*
//* To create a new Chain use this:
//* !your extending struct!
//* local YourOwnChainAbility data=YourOwnChainAbiltiy.create()
//*
//* To manipulate the Chains behaviour use this:
//* set temp.hitCount="yourHitCount" - Sets the maximal targets the Chain can have
//*
//* set temp.pickRange="yourDetectionRange" - Sets the AoE where the Chain searchs for new Targets
//*
//* set temp.missileModel="yourMissileModelPath" - Sets the missiles Model
//*
//* set temp.lightningModel="yourLightningModelPath" - Sets the Chains Lightning type model
//*
//* set temp.missileSpeed="yourChainSpeed" - Sets the Chains movespeed. Uses common Wc3 object editor speeds. (e.g. 900)
//*
//* set temp.useZArc="yourZArc" - Change the Chains fly behaviour (parabola movement).
//* Uses common Wc3 object editor arcs (e.g. 0.0 to 1.0)
//*
//* set temp.hitMoreThanOnce="yourFlag" - Change the Chains flag, whether the Chain can hit one Unit more than once.
//*
//* set temp.moveBackToSource="yourFlag" - Change the Chains flag, whether the Chain shall move back to the Source, after
//* it hit the last possible Target
//*
//* To Start a Chain use this:
//*
//* call temp.Cast("YourSourceUnit","YourTargetUnit") - This starts the Chain with "YourSourceUnit" as the Chains source and
//* "YourTargetUnit" as the Chains first Target
//*
//***********************************************************

globals
// - # - Lightning Type Model Paths - # - //
constant string AerialShackles = "LEAS"
constant string ChainLightning_Primary = "CLPB"
constant string ChainLightning_Secondary = "CLSB"
constant string DrainLife = "DRAL"
constant string DrainMana = "DRAM"
constant string DrainLifeAndMana = "DRAB"
constant string FingerOfDeath = "AFOD"
constant string ForkedLightning = "FORK"
constant string LightningAttack = "CHIM"
constant string HealingWave_Primary = "HWPB"
constant string HealingWave_Secondary = "HWSB"
constant string ManaBurn = "MBUR"
constant string ManaFlare = "MFPB"
constant string SpiritLink = "SPLK"
// - # - - - - - - - - - - - - - - - - # - //
endglobals

private struct Lightning
// - # - Lightning Data - # - //

//* The lightning variable
private lightning light

// - # - Lightning Targets - # - //
private unit firstEnd //* The First Unit attached to the Lightning
private unit secondEnd //* The Second Unit attached to the Lightning

//* The Height-Offset for each unit
private real fly1=0.
private real fly2=0.
// - # - - - - - - - - - - - # - //

// - # - Fade and Instance - # - //
private boolean visible
private real alpha=1.
// - # - - - - - - - - - - - # - //

// - - - - - - - - - - - - # - //

// - # - Struct Data - # - //
private static integer lightCount=1
private static timer lightTimer=CreateTimer()
// - # - - - - - - - - - - //

// - # - After Lightning completly faded - # - //
private method clear takes nothing returns nothing
call DestroyLightning(.light)
set .firstEnd=null
set .secondEnd=null
set .visible=false
set .alpha=1.
set .fly1=0.
set .fly2=0.
call .destroy()
endmethod
// - # - - - - - - - - - - - - - - - - - - # - //

// - # - Attach Fading Lightning Method - # - //
static method attachBetweenUnits takes unit firstEnd, unit secondEnd, string path returns nothing
local thistype data=thistype.allocate()
if data==thistype.lightCount then
set thistype.lightCount=thistype.lightCount+1
endif

// - # - Check z-coordinate - # - //
if GetUnitFlyHeight(firstEnd)==0 then
set data.fly1=50.
else
set data.fly1=GetUnitFlyHeight(firstEnd)
endif

if GetUnitFlyHeight(secondEnd)==0 then
set data.fly2=50.
else
set data.fly2=GetUnitFlyHeight(secondEnd)
endif
// - # - - - - - - - - - - - - # - //

set data.firstEnd=firstEnd
set data.secondEnd=secondEnd
set data.visible=true
set data.light=AddLightningEx(path,true,GetUnitX(firstEnd),GetUnitY(firstEnd),data.fly1,GetUnitX(secondEnd),GetUnitY(secondEnd),data.fly2)
endmethod
// - # - - - - - - - - - - - - - - - - - - - # - //

// - # - Loop through Lightning Instances - # - //
private static method fadeLightning takes nothing returns nothing
local integer i=1
loop
exitwhen i==thistype.lightCount
if thistype(i).visible then
call MoveLightningEx(thistype(i).light,true,GetUnitX(thistype(i).firstEnd),GetUnitY(thistype(i).firstEnd),thistype(i).fly1,GetUnitX(thistype(i).secondEnd),GetUnitY(thistype(i).secondEnd),thistype(i).fly2)

// - # - Fading Process - # - //
set thistype(i).alpha=thistype(i).alpha-0.03
if thistype(i).alpha<=0. then
call thistype(i).clear()
else
call SetLightningColor(thistype(i).light,1.,1.,1.,thistype(i).alpha)
endif
// - # - - - - - - - - - - # - //

endif
set i=i+1
endloop
endmethod
// - # - - - - - - - - - - - - - - - - - - - - # - //

private static method onInit takes nothing returns nothing
call TimerStart(thistype.lightTimer,0.03,true,function thistype.fadeLightning)
endmethod

endstruct

struct ChainAbility extends Missile

// - # - Chain Ability Instance Variables - # - //
//* the maximal jump count
private integer maxCount = 1
//* the current jump count
private integer curCount = 1
//* the unit that sends out the ChainAbility
private unit sourceUnit = null
//* the range in which possible targets get picked
private real pickUpRange = 0.0
//* the speed the chains missile has
private real mSpeed = 0.0
//* the zArc the chains missile has
private real arc = 0.0
//* the model the chains missile has
private string mdlPath = ""
//* the lightnings path
private string lightPath = ""
//* checks if a unit can be hit another time (requires multiHit enabled)
private HandleTable targetChecker
//* the flag whether a target can be hit more than once
private boolean multiHit = false
//* the flag whether the chain shall move back to the source
private boolean moveBack = false
//* the flag whether the chain uses a lightning or not
private boolean usesLightning = false
//* the temp unit for lightnings
private unit tempLastUnit = null

// - # - - - - - - - - - - - - - - - - - - # - //

// - # - Public Chain Ability Variables - # - //

//* The Unit that sends out the Chain (use this variable in your onTargetHit and targetFilter method)
static unit CastingUnit = null
//* The Unit that currently gets hit by the Chain (use this variable in your onTargetHit and targetFilter method)
static unit CurrentTarget = null
//* This returns how much targets have been hit allready (use this variable in your onTargetHit method)
static integer CurrentHit = 0

// - # - - - - - - - - - - - - - - - - - # - //


// - # - Enumeration Variables - # - //

//* The Enumeration Group
private static group enumGroup = CreateGroup()
//* The Enumeration Instance
private static thistype currentInstance = 0

// - # - - - - - - - - - - - - - # - //

// - # - on Destruction - # - //
private method onDestroy takes nothing returns nothing
set .usesLightning=false
set .moveBack=false
set .multiHit=false
set .mdlPath=""
set .lightPath=""
endmethod


// - # - Initialization Methods - # - //

// - # - With this function you can start a Chain.
// - # - Requires 2 arguments:
// - # - - unit source: the Unit that sends out the Chain
// - # - - unit target: the Unit that gets targeted by the Chain
method Cast takes unit source, unit target returns nothing
call .setMissileStartPosition(GetUnitX(source),GetUnitY(source),GetUnitFlyHeight(source)+50.)
set .modelPath=.mdlPath
set .setZArc=.arc
set .setSpeed=.mSpeed
set .sourceUnit=source
set .tempLastUnit=source
set .targetChecker=HandleTable.create()
call .assignTargetUnit(target)
call .disableOnTargetDestruction()
call .startMotion()
endmethod

// - # - - - - - - - - - - - - - - # - //


// - # - Manipulation Operators - # - //

// - # - With this function you can enable the usage of a lightning
// - # - Requires 1 argument:
// - # - - string path: the lightnings model path
method operator setLightningModel= takes string path returns nothing
set .usesLightning=true
set .lightPath=path
endmethod

// - # - With this function you can set the Chains hitcount.
// - # - Requires 1 argument:
// - # - - integer hits: the new hitcount amount.
method operator hitCount= takes integer hits returns nothing
set .maxCount=hits
set .curCount=hits
endmethod

// - # - With this function you can set the Chains detection range.
// - # - Requires 1 argument:
// - # - - real AoE: the new detection range.
method operator pickRange= takes real AoE returns nothing
set .pickUpRange=AoE
endmethod

// - # - With this function you can set the Chains speed.
// - # - Requires 1 argument:
// - # - - real speed: the new Chain speed.
method operator missileSpeed= takes real speed returns nothing
set .mSpeed=speed
endmethod

// - # - With this function you can set the Chains model.
// - # - Requires 1 argument:
// - # - - string path: the new Chain model.
method operator missileModel= takes string path returns nothing
set .mdlPath=path
endmethod

// - # - With this function you can set the Chains zArc. (makes the Chain fly a parabola curve, use a value between 0.0 and 1.0)
// - # - Requires 1 argument:
// - # - - real arc: the new zArc.
method operator useZArc= takes real arc returns nothing
set .arc=arc
endmethod

// - # - With this function you can assign whether the Chain can hit one target more than once.
// - # - Requires 1 argument:
// - # - - boolean flag: hit one target more than once or not.
method operator hitMoreThanOnce= takes boolean flag returns nothing
set .multiHit=flag
endmethod


// - # - With this function you can assign whether the Chain shall move back to the source.
// - # - Requires 1 argument:
// - # - - boolean flag: move back to source or not.
method operator moveBackToSource= takes boolean flag returns nothing
set .moveBack=flag
endmethod

// - # - - - - - - - - - - - - - - - - - # - //


// - # - Extending Childstruct Methods - # - //

// - # - With this method you can assign your own enumeration Filter.
// - # - ! This method will be replaced by the Extending Childstruct !
// - # - !! If there is no targetFilter method found in the Childstruct only the standard Filter will apply !!
stub method targetFilter takes nothing returns boolean
return true
endmethod

// - # - With this method you can assign what shall happen when a unit gets hit by the Chain
// - # - ! This method will be replaced by the Extending Childstruct !
// - # - !! If there is no onTargetHit method found in the Childstruct there won't happen anything !!
stub method onTargetHit takes nothing returns nothing
endmethod

// - # - !! For more Informations how this works check the samples !! - # - //

// - # - - - - - - - - - - - - - - - - - - # - //


// - # - This function removes a ChainAbility Instance.
// - # - ! A ChainAbility Instance is automatically destroyed when the hitcount expires or no more possible targets can be found !
private method remove takes nothing returns nothing
call .targetChecker.destroy()
call .clear()
call .destroy()
endmethod

// - # - The enumeration function that checks for possible targets.
private static method enumFilter takes nothing returns boolean
local thistype temp=.currentInstance
local unit old=thistype.CurrentTarget
local boolean b
set thistype.CurrentTarget=GetFilterUnit()
set b= temp.targetFilter() and old!=thistype.CurrentTarget and not temp.targetChecker.exists(thistype.CurrentTarget) and GetUnitTypeId(thistype.CurrentTarget)!='face'
set thistype.CurrentTarget=old
return b
endmethod

// - # - The method that will fire when a missile hits a unit.
private method onTargetReach takes nothing returns nothing
set thistype.CastingUnit=.sourceUnit
set thistype.CurrentTarget=.targetUnit
set thistype.CurrentHit=.maxCount-.curCount
set thistype.currentInstance=this
call .onTargetHit()

if .usesLightning then
call Lightning.attachBetweenUnits(.tempLastUnit,.targetUnit,.lightPath)
set .tempLastUnit=.targetUnit
endif

if .sourceUnit==.targetUnit and .moveBack then
call .remove()
return
endif

if not .multiHit then
set .targetChecker[.targetUnit]=this
endif

set .curCount=.curCount-1

if .curCount==0 then
if .moveBack then
call .assignNewTargetUnit(thistype.CastingUnit)
return
else
call .remove()
return
endif
endif

call GroupEnumUnitsInRange(thistype.enumGroup,GetUnitX(.targetUnit),GetUnitY(.targetUnit),.pickUpRange,Condition(function thistype.enumFilter))
set thistype.CurrentTarget=GroupPickRandomUnit(thistype.enumGroup)
if thistype.CurrentTarget==null then
if .moveBack then
set thistype.CurrentTarget=thistype.CastingUnit
else
call .remove()
return
endif
endif
call .assignNewTargetUnit(thistype.CurrentTarget)
call GroupClear(thistype.enumGroup)
endmethod

endstruct

endlibrary[/code]


[jass="Chain Manaburn"]scope ChainManaBurn initializer Init

globals
private constant integer AbilID = 'A000' //* The Spells MainAbility
private constant real reduction = 0.07 //* The reduction per hit
private real array ManaBurned[4]
endglobals

//* Extend your struct with the ChainAbility
struct ChainManaBurn extends ChainAbility

//* Assign your own TargetFilter
//* Use ChainAbility.CurrentTarget instead of FilterUnit()
//* Use ChainAbility.CastingUnit as the Chains source unit
//* ! If this method is not declared in the Childstruct the standard filter will apply !
method targetFilter takes nothing returns boolean
return GetUnitState(ChainAbility.CurrentTarget,UNIT_STATE_MANA)>0. /*
*/ and GetWidgetLife(ChainAbility.CurrentTarget)>0.405 /*
*/ and IsUnitEnemy(ChainAbility.CurrentTarget,GetOwningPlayer(ChainAbility.CastingUnit)) /*
*/ and not IsUnitType(ChainAbility.CurrentTarget,UNIT_TYPE_MAGIC_IMMUNE)
endmethod

//* Assign what shall happen when a unit gets hit by a Chain
//* Use ChainAbility.CurrentTarget as the Unit that gets hit
//* Use ChainAbility.CastingUnit as the Chains source Unit
//* Use ChainAbility.CurrentHit to get the number of allready hit targets
//* ! If this method is not declared in the Childstruct nothing will happen when a unit is hit !
method onTargetHit takes nothing returns nothing
local real mana=GetUnitState(ChainAbility.CurrentTarget,UNIT_STATE_MANA)
local integer level=GetUnitAbilityLevel(ChainAbility.CastingUnit,AbilID)-1
local real burn=ManaBurned[level] - ManaBurned[level]*(1-Pow(1-reduction,ChainAbility.CurrentHit))
if burn>mana then
set burn=mana
endif
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl",ChainAbility.CurrentTarget,"origin"))
call Manaburn(ChainAbility.CurrentTarget,R2I(burn))
call SetUnitState(ChainAbility.CurrentTarget,UNIT_STATE_MANA,mana-burn)
call UnitDamageTarget(ChainAbility.CastingUnit,ChainAbility.CurrentTarget,burn,true,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_UNIVERSAL,WEAPON_TYPE_WHOKNOWS)
endmethod

//* The Mana burned per level
static method manaBurnSetup takes nothing returns nothing
set ManaBurned[0]=75
set ManaBurned[1]=125
set ManaBurned[2]=175
set ManaBurned[3]=225
endmethod

endstruct

//* The function that fires on SpellCast
private function SpellCast takes nothing returns nothing
local ChainManaBurn object
if GetSpellAbilityId()==AbilID then
//* Your Chain Setup:
//* Create the Chain with the source Unit, the target Unit and the Chains missile model path
set object=ChainManaBurn.create()
//* Change the Chains zArc to your value (makes the chain fly a parabola curve)
set object.useZArc=0.1
//* Change the Chains model
set object.missileModel="Abilities\\Weapons\\BlackKeeperMissile\\BlackKeeperMissile.mdl"
//* Change the Chains Lightning model
set object.setLightningModel=ManaBurn
//* Change the Chains movespeed
set object.missileSpeed=1200
//* Change the Chains detectionrange
set object.pickRange=500
//* Change the flag whether a unit gets hit more than once
set object.hitMoreThanOnce=false
//* Change the flag whether the chain shall move back to the source
set object.moveBackToSource=false
//* Change the Chains hitcount
set object.hitCount=GetUnitAbilityLevel(GetSpellAbilityUnit(),AbilID)+3
//* Start the Chain
call object.Cast(GetSpellAbilityUnit(),GetSpellTargetUnit())
//* End Chain Setup
endif
endfunction

private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
call ChainManaBurn.manaBurnSetup()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddAction(t,function SpellCast)
endfunction

endscope[/code]


[jass="Life Absorb"]scope LifeAbsorb initializer Init

globals
private constant integer AbilID = 'A001' //* The Spells MainAbility
private real array absorbLife[4]
endglobals

//* Extend your struct with the ChainAbility
struct LifeAbsorb extends ChainAbility
private real lifeAbsorbed=0.
integer level=0

//* Assign your own TargetFilter
//* Use ChainAbility.CurrentTarget instead of FilterUnit()
//* Use ChainAbility.CastingUnit as the Chains source unit
//* ! If this method is not declared in the Childstruct the standard filter will apply !
method targetFilter takes nothing returns boolean
return IsUnitEnemy(ChainAbility.CurrentTarget,GetOwningPlayer(ChainAbility.CastingUnit)) and GetWidgetLife(ChainAbility.CurrentTarget)>0.405
endmethod

//* Assign what shall happen when a unit gets hit by a Chain
//* Use ChainAbility.CurrentTarget as the Unit that gets hit
//* Use ChainAbility.CastingUnit as the Chains source Unit
//* Use ChainAbility.CurrentHit to get the number of allready hit targets
//* ! If this method is not declared in the Childstruct nothing will happen when a unit is hit !
method onTargetHit takes nothing returns nothing
if ChainAbility.CurrentTarget==ChainAbility.CastingUnit then
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl",ChainAbility.CastingUnit,"origin"))
call SetWidgetLife(ChainAbility.CastingUnit,GetWidgetLife(ChainAbility.CastingUnit)+.lifeAbsorbed)
else
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Undead\\DeathandDecay\\DeathandDecayDamage.mdl",ChainAbility.CurrentTarget,"chest"))
call UnitDamageTarget(ChainAbility.CastingUnit,ChainAbility.CurrentTarget,absorbLife[.level],true,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_UNIVERSAL,WEAPON_TYPE_WHOKNOWS)
set .lifeAbsorbed=.lifeAbsorbed+absorbLife[.level]
endif
endmethod

//* The Life absorbed per level
static method lifeAbsorbSetup takes nothing returns nothing
set absorbLife[0]=25.
set absorbLife[1]=35.
set absorbLife[2]=45.
set absorbLife[3]=60.
endmethod

endstruct

//* The function that fires on SpellCast
private function SpellCast takes nothing returns nothing
local LifeAbsorb object
if GetSpellAbilityId()==AbilID then
//* Your Chain Setup:
//* Create the Chain with the source Unit, the target Unit and the Chains missile model path
set object=LifeAbsorb.create()
//* Change the Chains zArc to your value (makes the chain fly a parabola curve)
set object.useZArc=1
//* Change the Chains model
set object.missileModel="Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilMissile.mdl"
//* Change the Chains Lightning model
set object.setLightningModel=DrainLife
//* Change the Chains movespeed
set object.missileSpeed=1400
//* Change the Chains detectionrange
set object.pickRange=750
//* Change the flag whether a unit gets hit more than once
set object.hitMoreThanOnce=false
//* Change the flag whether the chain shall move back to the source
set object.moveBackToSource=true
//* Change the Chains hitcount
set object.hitCount=16
//* Start the Chain
set object.level=GetUnitAbilityLevel(GetSpellAbilityUnit(),AbilID)-1
call object.Cast(GetSpellAbilityUnit(),GetSpellTargetUnit())
//* End Chain Setup
endif
endfunction

private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
call LifeAbsorb.lifeAbsorbSetup()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddAction(t,function SpellCast)
endfunction
endscope[/code]



Keywords:
Chain, Ability, System, vJass, Damage, Detection, Mana, Burn, Life, Absorb, Missile, Dummy, Vector, Text, Tag, Table, Lightning
Contents

ChainAbilSystem - v.06 (Map)

Reviews
10:23, 29th Jul 2010 The_Reborn_Devil: The coding looks good and this is very useful. Status: Approved Rating: Recommended

Moderator

M

Moderator

10:23, 29th Jul 2010
The_Reborn_Devil:

The coding looks good and this is very useful.


Status: Approved
Rating: Recommended
 
code:
JASS:
library ChainAbilSystem initializer Init needs Array, GroupUtils, DamageDetection
//*******************************************************************************
//* ChainAbilSystem - v.01 by Inferior
//*
//* requires:
//*   - Array by Inferior
//*   - GroupUtils by Rising_Dusk
//*   - DamageDetection by Inferior ( But can use any other DamageDetectionSystem aswell )
//*
//* The purpose of this System is to easily create Chain Abilities
//* like ChainLightning or HealingWave, with your own effects.
//* First of all you have to create the very important buff.
//* Therefor you can use the ObjectMerger call below. Just remove the exclamations,
//* Save the map, close and reopen your map and put exclamations again.
//*
//* //! external ObjectMerger w3h BNab ChAB fnam "ChainAbilBuff" fart "ReplaceableTextures\CommandButtons\BTNScatterRockets.blp" ftat "" ftip "" fube ""
//*
//* Befor you can use a Chain Ability you have to specify its type:
//* Example:
//* local ChainAbil c=ChainAbil.create("TheCasterAbility","TheDummyAbility","WhatHappensWhenTheChainHits","PickTargetAoE","HitsMoreThanOnce?","FilterMeOut")
//*   - "TheCasterAbility"  : This Specifies the Chain Type. A Chain can only be assigned to one Ability and vice versa.
//*                          It is aswell required to check for the Chains Level.
//*   - "TheDummyAbility"   : This is like the Chains core. This is allways required. It makes the Chain have an Eyecandy.
//*   - "WhatHappensWhenTheChainHits" : The Function that shall be executed when the Chain hits a target:
//*                                     This Function must follow the function interface below!
//*   - "PickTargetAoE"     : The Area in which possible Targets shall be picked.
//*   - "HitsMoreThanOnce?" : Can a Target be hit more than one time.
//*   - "FilterMeOut"       : To Filter the possible Targets. Better check the sample for that.
//*
//* c.hitCount[0]=4
//* c.hitCount[1]=6          - Assigns the number of targets per Level. Be carefull the counter starts at 0 
//* ...........                not at 1!!
//* c.hitCount["how much levels you like"]=9
//*
//* To create the Dummy Ability just use the ObjectMerger below.
//! textmacro AbilGen takes id,missile,arc,speed
        //! external ObjectMerger w3a ANab $id$ anam "ChainAbil$id$" Nab4 1 0.0 Nab3 1 0 Nab6 1 100. Nab5 1 0.0 aani "" amat "$missile$" amac $arc$ amsp $speed$ acdn 1 0 ahdu 1 1.0 adur 1 1.0 amcs 1 0 alev 1 aher 0 aare 1 0.0 aran 1 9999.0 abuf 1 ChAB
//! endtextmacro
//* Example: //! runtextmacro AbilGen("WhatEverRawCode","WhatEverMissileArt","WhateverArc (between 0.0 and 1.0)","WhatEverSpeed")
//*
//* Now you are done. To use a chain simply use the function:
//*
//* call CastChainOnUnit("AtWhichUnit","WhichUnitUsesIt","OneOfYourChainTypes")
//* 
//* For more Information check out the samples!
//****************************************************************************************************

    globals
        private constant integer dummyID   = 'e000' //* Set this to the Dummy Rawcode of your map
                                                    //* If you do not have a dummy unit create one and
                                                    //* Import the model included in this map!
       
        private constant integer maxLevel  = 4      //* The maximum Level that the Chains can have
                                                    //* The limit is at 8190!
        
        private constant integer checkBuff ='ChAB'  //* If You have used the ObjectMerger to create the buff
                                                    //* you don't have to change this. Else you have to set this
                                                    //* to the correct RawCode!
                                                    
        //* DO NOT TOUCH ANYTHING BELOW, UNLESS YOU KNOW WHAT YOU DO!!
        
                         unit    ChainAbil_TempUnit   
                         unit    ChainAbil_TempTarg
    endglobals
    
    function interface onChainHit takes unit whichTarget, unit whichSource, integer hitCount returns nothing
    
    struct ChainData
        integer hitCount=0
        unit caster
        unit dummy
        integer index
        IntegerArray DataArray
        
        static IntegerArray cData
        
        method clear takes nothing returns nothing
            set this.hitCount=0
            call RemoveUnit(this.dummy)
            set this.index=0
            call this.DataArray.clear()
        endmethod
        
        static method operator [] takes unit whichUnit returns ChainData
            return thistype.cData[whichUnit]
        endmethod
        
        static method create takes nothing returns ChainData
            return ChainData.allocate()
        endmethod
    endstruct
    
    struct ChainAbil
        private integer AbilID
        private integer ChainID
        private onChainHit response
        private real AoE
        private boolean multiHit
        private boolexpr filter
        integer array hitCount[maxLevel]
        
        static InfiniteIntegerArray AbilData
        
        method onCast takes unit whichTarget, unit whichSource returns nothing
            local ChainData Data=ChainData.create()
            set Data.hitCount=.hitCount[GetUnitAbilityLevel(whichSource,.AbilID)-1]
            set Data.caster=whichSource
            set Data.DataArray=IntegerArray.newArray()
            set Data.index=this
            set Data.dummy=CreateUnit(GetOwningPlayer(Data.caster),dummyID,0,0,0)
            call UnitAddAbility(Data.dummy,'Amrf')
            call UnitRemoveAbility(Data.dummy,'Amrf')
            call SetUnitX(Data.dummy,GetUnitX(Data.caster))
            call SetUnitY(Data.dummy,GetUnitY(Data.caster))
            set ChainData.cData[Data.dummy]=Data
            call UnitAddAbility(Data.dummy,.ChainID)
            call IssueTargetOrder(Data.dummy,"acidbomb",whichTarget)
        endmethod
        
        static method onAbilHit takes unit whichTarget, unit whichSource, real damage returns nothing
            local ChainAbil data
            local ChainData Data
            local group g
            if GetUnitAbilityLevel(whichTarget,checkBuff)>0 then
                call UnitRemoveAbility(whichTarget,checkBuff)
                set Data=ChainData.cData[whichSource]
                set data=Data.index
                call data.response.evaluate(whichTarget,Data.caster,Data.hitCount)
                set Data.hitCount=Data.hitCount-1
                if Data.hitCount<=0 then
                    call Data.clear()
                    return
                endif
                call SetUnitX(Data.dummy,GetUnitX(whichTarget))
                call SetUnitY(Data.dummy,GetUnitY(whichTarget))
                set g=NewGroup()
                set ChainAbil_TempUnit=Data.dummy
                set ChainAbil_TempTarg=whichTarget
                call GroupEnumUnitsInArea(g,GetUnitX(Data.dummy),GetUnitY(Data.dummy),data.AoE,data.filter)
                set ChainAbil_TempTarg=GroupPickRandomUnit(g)
                call ReleaseGroup(g)
                if ChainAbil_TempTarg==null then
                    call Data.clear()
                    return
                endif
                call IssueTargetOrder(Data.dummy,"acidbomb",ChainAbil_TempTarg)
                if not data.multiHit then
                    set Data.DataArray[whichTarget]=Data
                endif
                set ChainAbil_TempUnit=null
                set ChainAbil_TempTarg=null
            endif
        endmethod
        
        static method create takes integer abilID, integer chainID, onChainHit response, real AoE, boolean flag, boolexpr filter returns ChainAbil
            local ChainAbil data
            if not ChainAbil.AbilData.assigned(abilID) then
                set data=ChainAbil.allocate()
                set data.AbilID=abilID
                set data.ChainID=chainID
                set data.response=response
                set data.AoE=AoE
                set data.filter=filter
                set data.multiHit=flag
                set ChainAbil.AbilData[abilID]=data
                return data
            else
                debug call BJDebugMsg("Warning: There allready exists a ChainType for this Ability!!")
                return 0x00
            endif
        endmethod
        
    endstruct
    
    function CastChainOnUnit takes unit whichTarget, unit whichSource, ChainAbil whichChain returns nothing
        call whichChain.onCast(whichTarget,whichSource)
    endfunction

    private function Init takes nothing returns nothing
        set ChainAbil.AbilData=IntegerArray.newArray()
        set ChainData.cData=IntegerArray.newArray()
        call AddSpellResponse(ChainAbil.onAbilHit)
    endfunction
    
endlibrary
 
Level 21
Joined
Dec 9, 2007
Messages
3,096
I finally get a chance to test it.

I will edit this post with my opinion!

Edit:
I've never EVER seen a code snippet bugging so much.

WC3ScrnShot_073110_135644_01.jpg


Ability max level is 100.

Use
JASS:
thistype
in the struct methods (both static and instance)!
If the ability is already assigned, that's a serious error and the user might not have debug mode turned on!
So you should remove the
JASS:
debug
keyword before the
JASS:
call BJDebugMsg("Warning: There allready exists a ChainType for this Ability!!")
__________________
JASS:
static method create takes integer abilID, integer chainID, real AoE, boolean flag returns thistype
            local thistype data
            if not thistype.AbilData.assigned(abilID) then
                set data=ChainAbil.allocate()
                set data.AbilID=abilID
                set data.ChainID=chainID
                set data.AoE=AoE
                set data.multiHit=flag
                set thistype.AbilData[abilID]=data
                return data
            else
                call BJDebugMsg("Warning: There allready exists a ChainType for this Ability!!")
                return 0
            endif
        endmethod
__________________

Edit 2:
Let me give a try in making a chain system... o.o
I am bored to hell ATM.
 
Last edited:
Level 21
Joined
Dec 9, 2007
Messages
3,096
JASS:
private function SpellCast takes nothing returns nothing
        local ChainManaBurn object
        if GetSpellAbilityId()==AbilID then
            //* Your Chain Setup:
                //* Create the Chain with the source Unit, the target Unit and the Chains missile model path
                set object=object.create(GetSpellAbilityUnit(),GetSpellTargetUnit(),"Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl")
I think you mean:
JASS:
private function SpellCast takes nothing returns nothing
        local ChainManaBurn object
        if GetSpellAbilityId()==AbilID then
            //* Your Chain Setup:
                //* Create the Chain with the source Unit, the target Unit and the Chains missile model path
                set object=ChainManaBurn.create(GetSpellAbilityUnit(),GetSpellTargetUnit(),"Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl")

Other than that, nice work!

Edit:
If you add support for lightning effects, I'll give you a medal!
 
Level 21
Joined
Dec 9, 2007
Messages
3,096
I really suggest you use a constructor with no arguments...
And take the source and target units in the "Cast" method for clarity's sake.
For the missile's string... How about making it another field/property/variable? (Call it however you like.)
This can spare the user of some bloody memory and processing when the chain is supposed to be cast more than once. (Especially on a crappy PC like mine...)
 
Level 6
Joined
Nov 3, 2008
Messages
117
JASS:
private function SpellCast takes nothing returns nothing
        local ChainManaBurn object
        if GetSpellAbilityId()==AbilID then
            //* Your Chain Setup:
                //* Create the Chain with the source Unit, the target Unit and the Chains missile model path
                set object=object.create(GetSpellAbilityUnit(),GetSpellTargetUnit(),"Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl")
I think you mean:
JASS:
private function SpellCast takes nothing returns nothing
        local ChainManaBurn object
        if GetSpellAbilityId()==AbilID then
            //* Your Chain Setup:
                //* Create the Chain with the source Unit, the target Unit and the Chains missile model path
                set object=ChainManaBurn.create(GetSpellAbilityUnit(),GetSpellTargetUnit(),"Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl")

This has been fixed in the last update. ( Just forgot to update the posted script!)

I really suggest you use a constructor with no arguments...
And take the source and target units in the "Cast" method for clarity's sake.
For the missile's string... How about making it another field/property/variable? (Call it however you like.)
This can spare the user of some bloody memory and processing when the chain is supposed to be cast more than once. (Especially on a crappy PC like mine...)

will fix it
 
Level 21
Joined
Dec 9, 2007
Messages
3,096
:smile:
Thanks!
It's just that... You seem to take my suggestions as bugs or something similar. But it's good to hear that it's not like that.

Edit: Are we the only ones posting??!!
Edit2: I forgot to mention that I changed my rating to 5/5 because it deserves it now.
 
Level 6
Joined
Nov 3, 2008
Messages
117
Edit: Are we the only ones posting??!!

seems like....

Edit2: I forgot to mention that I changed my rating to 5/5 because it deserves it now.

thx ^^.

actually i'm on your "constructor with no arguments suggestion" and i guess i have to recode the missile system for it abit.

Edit2:
actually i'm on your "constructor with no arguments suggestion" and i guess i have to recode the missile system for it abit.

done.

Will upload the map with finished lightning support and "non-argument constructor" tomorrow

Edit:
Now uses a non-argument constructor and I added lightning support !!!!
 
Last edited:
Level 21
Joined
Dec 9, 2007
Messages
3,096
Damn I'll check it! :D

Edit:
Sick!
This works perfectly now!
Congratulations, man! :)

As one last suggestion:
How about an option (boolean) to have the lightning start when the missile is thrown and start fading when the missile hit! That effect would be sick!

Edit2:
Other than the above, I can't find any room for improvements!
 
Last edited:
Level 6
Joined
Nov 3, 2008
Messages
117
Sick!
This works perfectly now!
Congratulations, man! :)

Thx, glad you like it :D

As one last suggestion:
How about an option (boolean) to have the lightning start when the missile is thrown and start fading when the missile hit! That effect would be sick!

How do you mean that. Do you want to have the lightning to be attached on the missile, re-attach it to the target when the missile hits and fade it then, or you want it directly attached to the target when the missile starts and fade it when the missile hits, or do you even want both :p
 
Level 21
Joined
Dec 9, 2007
Messages
3,096
When the missile STARTS the lightning appears (between the units, not the missile) and when the missile HITS the lightning starts fading out!

Edit: Both could be awesome!

Edit2: For missiles with Z-arc you can make two lightnings, one from the source to the missile and one from the missile to the target! :grin:
 
Level 1
Joined
Dec 31, 2009
Messages
3
@Inferior

when the demand for missile targets, it looks random, it would be bad if you add a conditional function to check the nearest unit??
 
Level 6
Joined
Nov 3, 2008
Messages
117
when the demand for missile targets, it looks random, it would be bad if you add a conditional function to check the nearest unit??

One point here. If i add a filter for the nearest unit, the randomness is lost. when one uses set instance.hitMoreThanOnce=true, one runs the risk of only 2 units out of lets say 10 getting hit, only because those 2 units are closer to each other than to any other unit. You understand me?

Edit: Next update will take some time, because im not in the mood to deal with the update....
 
Level 7
Joined
Apr 30, 2011
Messages
359
wait
can anyone help me?
when i try to save the map it result an error . . .
and the testmap also the same . .
help me . . .

note:
lightning on unit should have 60 height on ground unit and +20 to flying one
and i don't know very good on JASS
 
Top