Cokemonkey11
Spell Reviewer
- Joined
- May 9, 2006
- Messages
- 3,575
Shield
Preface
Have you ever noticed that League of Legends contains many shield-based abilities? And DotA has very few? This isn't a coincidence. JASS simply doesn't contain an API for shields. There's no easy way to "shield a unit for up to 50 damage for up to 10 seconds" without long, boring scripts. This system aims to duplicate the functionality of league of legends shields with the easiest possible API.
Design Explanation
This library works by attaching an object to a unit which contains a special effect, a duration, and a shield amount. With some logic, we can actually display the unit's HP as being higher so that it will shield even damage that would otherwise kill it. If you attach a shield to a unit with a shield already, then the shield objects will link to each other. Checking the duration on the objects is done with a stack on a periodic timer.
Limitations:
API
The script
[/hidden]
Installation
Change Log
2014.03.04 - Improved documentation and small ambiguities.
2013.05.25 - Updated Shield to work with the new versions of StructuredDD and DamageType. Added functionality for unlimited duration and unlimited shield values.
2013.01.28 - Reduced the size of the header and included a shim to prevent thread crash if Shield is called in a module initializer function.
2013.01.25 - Initial submission to Hive: Jass Resources: Submissions
Special Thanks:
Preface
Have you ever noticed that League of Legends contains many shield-based abilities? And DotA has very few? This isn't a coincidence. JASS simply doesn't contain an API for shields. There's no easy way to "shield a unit for up to 50 damage for up to 10 seconds" without long, boring scripts. This system aims to duplicate the functionality of league of legends shields with the easiest possible API.
Design Explanation
This library works by attaching an object to a unit which contains a special effect, a duration, and a shield amount. With some logic, we can actually display the unit's HP as being higher so that it will shield even damage that would otherwise kill it. If you attach a shield to a unit with a shield already, then the shield objects will link to each other. Checking the duration on the objects is done with a stack on a periodic timer.
Limitations:
- This resource requires vJass to compile.
- Shield requires DamageType which in turn requires StructuredDD, thus it is subject to those systems' limitations. This implies that Shield is likely incompatible with other damage detection systems.
- Shield is in an awkward position where features could be taken away for a performance improvement, or added for improved functionality. Things like layers of shields aren't used in league of legends, so they're not included here.
- There were many important considerations when designing this system in regards to "features vs performance". Some of these questions I asked myself include:
- Should the system display temporary "false" hp on a shielded unit to visually show it will need more damage to kill it? Unlike league of legends, we have no way of modifying the color of different sections of the units health bar, and yet the system would be lacking in preventing damage if it were not included.
- What if a unit is shielded and the result is that it temporarily has maximum HP? This implies that the passive hit point regeneration it would otherwise incur is now prevented. (I've accepted this as a limitation of the system)
- If a unit has multiple shields, should I split the damage up into fractions for each shield member? This would surely be the most "fair" approach since for example a unit might have a shield that lasts 1 second and blocks 500 damage, and a shield that lasts 10 minutes and block 50 damage - but I have no way of knowing which one in the list is which. League of Legends implements a FILO technique (which happens to be the most efficient) - and I've done the same.
API
Shield.TIMER_PERIOD
- This value refers to how often Shield should search for instances that are expired in regards to their duration. A smaller value means that shields are less likely to block damage when their duration has passed, while a larger value is less computationally expensive. A good value lies on [1./60.,1./2.].Shield.add(unit u,real a,real t,string f,string p)
- This is the one and only important method for instanciating a Shield. It imparts a shield of size 'a' for time 't' on 'u' using effect with model 'f' on attachment point 'p'. An 'a' value of -1. will shield unlimited damage for the duration. A 't' value of -1. will shield the unit indefinitely until 'a' is reached.The script
JASS:
/**
* Shield is a simple resource which is used to add temporary health to a unit. Shield has some
* limitations which are addressed in the resource thread on hiveworshop.com. The API is as
* follows:
*
* Configurable: TIMER_PERIOD:
* How often Shield should search for expired shields. A smaller value is more accurate
* but will be more computationally expensive. Try something like 1/10 of a second to
* begin with.
*
* Public Method: Shield.add(unit target, real amount, real duration, string sfx, string attachPt):
* The only method necessary for instaniating a shield. A negatve duration lasts forever, and
* a negative amount shields an arbitrarily high value.
*/
library Shield requires DamageType
/**
* Native declaration in case it's not already done
*/
native UnitAlive takes unit u returns boolean
/**
* This struct is just a facade for a pretty API (Don't try to Shield.create())
*/
struct Shield
//< BEGIN CUSTOMIZABLE SECTION ****************************
private static constant real TIMER_PERIOD=1./10.
//> END CUSTOMIZABLE SECTION ******************************
// Values for the instance stack
private static thistype array allShields
private static integer shieldIndex=-1
private static timer clock=CreateTimer()
private static HandleTable tab
// Instance variables
private boolean useDuration=true
private boolean finiteShield=true
private unit u
private real shieldLeft
private real falseHP
private real timeLeft
private effect fx
private thistype prevShield=0
private thistype nextShield=0
/**
* The StructuredDD handler which catches damage events
*/
private static method handler takes nothing returns nothing
local thistype tempDat
local thistype destroyingDat
local unit tU=GetTriggerUnit()
local real damageLeft=GetEventDamage()
local boolean noShieldsLeft=false
if DamageType.get()!=DamageType.SPELL and UnitAlive(tU) and tab.exists(tU) then
set tempDat=thistype.tab[tU]
//we need to loop until either all the damage is mitigated
//or there's no shield left on this unit.
loop
exitwhen damageLeft<=0. or noShieldsLeft
if tempDat.shieldLeft>damageLeft then
if tempDat.finiteShield then
set tempDat.shieldLeft=tempDat.shieldLeft-damageLeft
endif
call SetWidgetLife(tempDat.u,GetWidgetLife(tempDat.u)+damageLeft)
set damageLeft=0.
else
set damageLeft=damageLeft-tempDat.shieldLeft
call SetWidgetLife(tempDat.u,GetWidgetLife(tempDat.u)+tempDat.shieldLeft)
set tempDat.shieldLeft=0.
if tempDat.falseHP>damageLeft then
set tempDat.falseHP=tempDat.falseHP-damageLeft
set damageLeft=0.
else
set damageLeft=damageLeft-tempDat.falseHP
set tempDat.falseHP=0.
set destroyingDat=tempDat
if destroyingDat.nextShield!=0 then
set tempDat=destroyingDat.nextShield
set tempDat.prevShield=0
set thistype.tab[tempDat.u]=tempDat
else
set noShieldsLeft=true
endif
set destroyingDat.nextShield=0
endif
endif
endloop
endif
set tU=null
endmethod
/**
* Checks for dead Shield instances, destroying them.
*/
private static method timeout takes nothing returns nothing
local integer index=0
local thistype tempDat
local thistype otherDat
loop
exitwhen index>thistype.shieldIndex
set tempDat=thistype.allShields[index]
set tempDat.timeLeft=tempDat.timeLeft-TIMER_PERIOD
if (tempDat.timeLeft<=0. and tempDat.useDuration) or (tempDat.shieldLeft+tempDat.falseHP)<=0. or UnitAlive(tempDat.u)==false then
//deallocate it, depending on the values of prevShield
//and nextShield
if tempDat.prevShield==0 and tempDat.nextShield==0 and thistype.tab[tempDat.u]==tempDat then
//this was the only shield left on the unit so:
call thistype.tab.flush(tempDat.u)
elseif tempDat.prevShield==0 and tempDat.nextShield!=0 then
//the shield was the first in a list so we have to
//make next into the new first!
set otherDat=tempDat.nextShield
set otherDat.prevShield=0
set thistype.tab[tempDat.u]=otherDat
elseif tempDat.prevShield!=0 and tempDat.nextShield==0 then
//the shield was the end of the list so let's do the
//proper changes to the previous member.
set otherDat=tempDat.prevShield
set otherDat.nextShield=0
elseif tempDat.prevShield!=0 and tempDat.nextShield!=0 then
//this shield was somewhere in the middle of the list
//so let's make the prev and next link to each other
set otherDat=tempDat.prevShield
set otherDat.nextShield=tempDat.nextShield
set otherDat=tempDat.nextShield
set otherDat.prevShield=tempDat.prevShield
endif
//let's reset the unit's hp if he has extra
if tempDat.falseHP>0. then
call SetWidgetLife(tempDat.u,GetWidgetLife(tempDat.u)-tempDat.falseHP)
endif
call DestroyEffect(tempDat.fx)
call tempDat.destroy()
set thistype.allShields[index]=thistype.allShields[thistype.shieldIndex]
set thistype.shieldIndex=thistype.shieldIndex-1
set index=index-1
if thistype.shieldIndex==-1 then
call PauseTimer(thistype.clock)
endif
endif
set index=index+1
endloop
endmethod
/**
* The public method for building a Shield instance
*/
public static method add takes unit u, real amount, real duration, string fx, string fxpoint returns nothing
local thistype tempDat=thistype.create()
local thistype linkedDat
local real initialLife=GetWidgetLife(u)
local real maxLife=GetUnitState(u,UNIT_STATE_MAX_LIFE)
local real diff=maxLife-initialLife
set tempDat.u=u
set tempDat.timeLeft=duration
set tempDat.fx=AddSpecialEffectTarget(fx,u,fxpoint)
if duration<0. then
set tempDat.useDuration=false
endif
if <0. then
set amount=100000.
set tempDat.finiteShield=false
endif
//figure out how to partition the damage
if diff>=amount then
//the unit has plenty of missing HP, so:
set tempDat.shieldLeft=0
set tempDat.falseHP=amount
call SetWidgetLife(u,initialLife+amount)
else
//the unit has less hp missing than the shield has value, so:
call SetWidgetLife(u,maxLife)
set tempDat.falseHP=diff
set tempDat.shieldLeft=amount-diff
endif
//Now let's figure out if the unit already has a shield - if he
//does, we add this shield to the front:
if thistype.tab.exists(u) then
set linkedDat=thistype.tab[u]
set tempDat.nextShield=linkedDat
set linkedDat.prevShield=tempDat
endif
set thistype.tab[u]=tempDat
//now let's add this shield to the stack so it can be properly
//deallocated when its time runs out:
set thistype.shieldIndex=thistype.shieldIndex+1
set thistype.allShields[thistype.shieldIndex]=tempDat
//if allShields was previously empty we have to jumpstart the timer:
if thistype.shieldIndex==0 then
call TimerStart(thistype.clock,thistype.TIMER_PERIOD,true,function thistype.timeout)
endif
endmethod
/**
* Initialization method
*/
private static method onInit takes nothing returns nothing
set thistype.tab=HandleTable.create()
call StructuredDD.addHandler(function thistype.handler)
endmethod
endstruct
endlibrary
[/hidden]
Installation
- Make sure you have an up-to-date copy of DamageType and all of its requirements: http://www.hiveworkshop.com/forums/submissions-414/system-damagetype-structureddd-extension-228883/
- Copy this script in its entirety into an empty trigger (custom script) and name it Shield.
- (If necessary) adjust the 'CUSTOMIZABLE SECTION' to your liking.
Change Log
2014.03.04 - Improved documentation and small ambiguities.
2013.05.25 - Updated Shield to work with the new versions of StructuredDD and DamageType. Added functionality for unlimited duration and unlimited shield values.
2013.01.28 - Reduced the size of the header and included a shim to prevent thread crash if Shield is called in a module initializer function.
2013.01.25 - Initial submission to Hive: Jass Resources: Submissions
Special Thanks:
- -Kobas- for creating a map submission template, which turned out useful for system submissions as well.
- Vexorian for developing JassHelper. Without vJass, I almost certainly would not still be scripting for wc3. He also created the version of Table used in this script.
- The various developers of JNGP including PitzerMike and MindworX. Integrating JassHelper and TESH is a godsend.
Last edited: