• Check out the results of the Techtree Contest #19!
  • Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • Create a void inspired texture for Warcraft 3 and enter Hive's 34th Texturing Contest: Void! Click here to enter!
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!

Shield Engine 1.4.1

This bundle is marked as high quality. It exceeds standards and is highly desirable.

General

Import & Config

How to use

Code

Changelog

Credits


Purpose

So... Here I was implementing shields in various ways and I thought I might as well do a whole engine with events :grin:

This system is meant to be (or become) a complete engine for shielding that can manage shields logic and events.
This engine avoids recursive calls and will postpone any shield created during events, avoiding variables conflicts.

I'm pretty there are more things that could be added to this system, so feel free to make suggestions.

How does this works ?

Shields can
  • Specify what type of damage they block:
    • By source:
      • all sources
      • block only spells
      • block only attacks
    • By attack type (Hero, Normal, Pierce ...)
    • By damage type (universal, magic, spiritual link, enhanced, defensive...)
  • block an entire instance of damage or block a set amount of damage
  • block damage before or after damage reduction
  • have a visual effect
  • regen over time

With this engine, a unit can have as many shields as you want. Whenever a unit get a shield, a shield bar will appear.
A shield bar is created per type of damage it can block.

When a unit is taking damage while being shielded, shields will apply in the following order:
1/ Shield that can only take specific damage source (only spell/attack) before shields that can absorb all
2/ Shield that nullify one instance of damage before those blocking amount of damage
3/ Order by timeLeft

Events List

GUI

vJass



Game - PreShieldCreationEvent Becomes Equal to 1.00 - This event fires when a shield is created, but not yet applied. Use this event to apply any modifiers you want. Depending of your config, PreShieldCreationEvent can be 2.00, 3.00 etc., several distinct steps before applying the heal to have a clear separation of modifiers.
Game - ShieldCreatedEvent Becomes Equal to 1.00 - This event runs when a shield has been applied to a unit.
Game - ShieldAbsorbEvent Becomes Equal to 1.00 - This event runs when a shield absorb damage.
Game - ShieldBreakEvent Becomes Equal to 1.00 - This event fires when a shield is broken (its amount reached 0 when taking damage).
Game - ShieldExpiredEvent Becomes Equal to 1.00 - This event runs when a timed shield runs out of time.
Game - ShieldRefreshedEvent Becomes Equal to 1.00 - This event runs when a non stackable shield is applied again to the same target.
Game - ShieldStackedEvent Becomes Equal to 1.00 - This event runs when a stackable shield is applied again to the same target.



You can register callbacks functions to events by using the following:

ShieldEvent.AddOnPreCreation takes function callback that will be called on event and integer index to specify which PreShieldCreation step.
ShieldEvent.AddOnCreation takes function callback that will be called on event
ShieldEvent.AddOnAbsorb takes function callback that will be called on event
ShieldEvent.AddOnExpired takes function callback that will be called on event
ShieldEvent.AddOnBreak takes function callback that will be called on event
ShieldEvent.AddOnRefresh takes function callback that will be called on event
ShieldEvent.AddOnStack takes function callback that will be called on event



Available variables

GUI

vJass


ShieldName - string - Name of the shield

ShieldSource - unit - Who casted the shield
ShieldWielder - unit - Who is affected by the shield

ShieldProcType - integer - When does the shield apply (before or after damage reduction).
ShieldType - integer - How does the shield block damage (set amount of damage or number of damage instance).
ShieldHpType - integer - What type of damage does the shield absorb (all, only spell, only attack).

ShieldIsStackable - boolean - Can the shield stack with itself

ShieldIsTimed - boolean - Does the shield expires after a set amount of time
ShieldDuration - real - When shield is timed, this is its duration
ShieldTimeLeft - real - When shield is timed, this is the time left before expiration

ShieldHpMax - real - How much damage can the shield absorb. If ShieldType is SHIELD_TYPE_INSTANCE, this represents how much damage instance this shield can block instead.
ShieldCurrentHp - real - How much HPs left
ShieldBreakAtZero - boolean - Does the shield break when its HP reaches 0

ShieldAbsorbedDamage - real - Last amount of damage blocked by this shield.
ShieldAbsorbedDamageSource - unit - Who did the last damage absorbed by this shield

ShieldCanRegen - boolean - Can the shield regen its HP ?
ShieldTimeBeforeRegen - real - How much time without taking damage is needed before starting regen. If set to 0, the shield will always regen
ShieldRegenTime - real - How much time is need for the shield to regen to full (from 0% to 100%).

ShieldBaseMaxHP - real - Initial max HP when shield was casted, before any modification
ShieldPrvMaxHP - real - Max HP at the end of previous step
ShieldBaseDuration - real - Initial duration when shield was casted, before any modification
ShieldPrvDuration - real - Duration at the end of previous step


To get shield info in your callback functions, get the ShieldEvent.eventShield


How to shield using this system ?

See the Import & config tab to install this engine in your map.
See the How to use tab to know how to call this engine.


Requirements

Mandatory - Bribe's GUI Unit Event, Bribe's Damage Engine 5.A.0, Anachron's CustomBar

Import

Make sure to have option "File > Preference > Automatically create unknown variables while pasting trigger data" checked.
If you do not have the requirements in your map yet, copy and paste the "Requirements" folder.
Copy the folder "Shield Engine" and paste it in your map.

Configuration

The following variables can be config:

USE_GUI - boolean, default is false - If set to true, it will enable the use of GUI variables and event firing.

SHIELD_TICK_INTERVAL - real, default is 0.1 - Set the frequency of update of shield (for regen/duration).

MAX_PRESHIELD_CREATION_EVENTS - integer, default is 4 - How much PreShieldCreationEvent are fired.

SHIELD_DEFAULT_SFX_PATH - string, default points to mana shield effect - Will be used on simplified interface for shield creation as a default models for all shields
SHIELD_DEFAULT_ATTACH_POINT - string, default is "origin" - Attach point of above effect.

NUMBER_OF_ATTACK_TYPE - integer, default is 8 - How much attack types your maps has.
NUMBER_OF_DAMAGE_TYPE - integer, default is 23 - How much damage types your map has.

The 6 variables below need to be config in ShieldWielder struct onInit method.

SHIELD_COLOR_LEFT - ARGB, default is dark green - Left color of shield bar for the shields absorbing all type of damage
SHIELD_COLOR_RIGHT - ARGB, default is light green - Right color of shield bar for the shields absorbing all type of damage

SPELL_SHIELD_COLOR_LEFT - ARGB, default is dark purple - Left color of shield bar for the shields absorbing spell damage
SPELL_SHIELD_COLOR_RIGHT - ARGB, default is light purple - Right color of shield bar for the shields absorbing spell damage

ATTACK_SHIELD_COLOR_LEFT - ARGB, default is dark brown - Left color of shield bar for the shields absorbing attack damage
ATTACK_SHIELD_COLOR_RIGHT - ARGB, default is light brown - Right color of shield bar for the shields absorbing attack damage



Jass API

GUI Triggers



  • Shield Creation:
    • ShieldParameters.create gives you a struct with default values of parameters needed for the direct function. You'll have to set at least the name and target of the shield, but then can call the method CreateShield of your instance which will call the function described at the next point.
      How to use:
      JASS:
      set parameters = ShieldParameters.create()
      set parameters.name = "YourName"
      set parameters.target = yourTarget
      
      call parameters.CreateShield()
    • ShieldEngine.CreateShield
      Create a shield on given unit.
      unit source - Unit that casted the shield
      unit target - Unit that received the shield
      string name - Name of Shield, allows search/stack etc
      string sfxPath - Path to the model of visual effect
      string attachPoint - Where is the visual effect attached to the unit
      integer shieldProcType - When does the shield proc (before or after damage reduction).
      integer shieldType - Does the shield block a complete instance of damage or does it block a damage amount.
      integer shieldHpType - What kind of damage does the shield block.
      boolean isStackable - Does the shield stack with itself (if not, shield will only be refreshed when reapplied).
      boolean isTimed - Does the shield expire after a set time.
      real duration - If shield is timed, how much time does the shield last.
      real HP - How many instance/damage can the shield block.
      boolean breakAtZero - Does the shield break when it HP reaches 0.
      boolean canRegen - Can the shield regen over time.
      real timeBeforeRegen - How many seconds without taking damage before the shield start regenerating.
      real regenTime - How much time the shield would take to regen from 0% to 100%.
    • ShieldEngine.CreateTimedShield
      Simplified version of CreateShield for timed shield, with a lot less parameters.
      unit source,
      unit target,
      string name,
      real duration,
      real HP
    • ShieldEngine.CreateInstanceShield
      Simplified version of CreateShield for instance shield, with a lot less parameters.
      unit source,
      unit target,
      string name,
      real HP
  • Shield Removal/Destruction. Destroying a shield will fire a ShieldBreakEvent. Removing a shield will do it silently.
    • ShieldEngine.RemoveShieldByName
      Remove a given shield on a given unit.
      unit u,
      string name
    • ShieldEngine.RemoveAllShields
      Remove all shields on a given unit.
      unit u
    • ShieldEngine.RemoveShieldsByType
      Remove all shields protecting a given unit based on what type of damage they can absorb.
      unit u,
      integer hpType
    • ShieldEngine.DestroyShieldByName
      Destroy a given shield on a given unit.
      unit u,
      string name
    • ShieldEngine.DestroyShieldsByType
      Destroy all shields on a given unit.
      unit u,
      integer hpType
    • ShieldEngine.DestroyAllShields
      Destroy all shields protecting a given unit based on what type of damage they can absorb.
      unit u
  • Getting unit informations.
    • ShieldEngine.IsUnitShielded
      Tells if a given unit has at least one shield.
      unit u
    • ShieldEngine.UnitHasShield
      Tells if a given unit has a specific shield.
      unit u,
      string name
    • ShieldEngine.UnitHasActiveShield
      Tells if a unit has at least one shield with current HP >= 1.0
      unit u
    • ShieldEngine.UnitHasActiveShieldOfType
      Tells if a unit has at least one shield of given type with current HP >= 1.0
      unit u,
      integer hpType
  • Specifying attack/damage types absorbed by a shield
    • Shield.AddAttackType
      Add an attack type to the list of absorbed attack type. If never called, the shield will absorb all attack types.
      integer attType
    • Shield.ExcludeAttackType
      Make the shield unenabled to absorb attack type. Exclusion takes priority over inclusion if same attType had been added using AddAttackType.
      integer attType
    • Shield.AddDamageType
      Add an attack type to the list of absorbed damage type. If never called, the shield will absorb all damage types.
      integer dmgType
    • Shield.ExcludeDamageType
      Make the shield unenabled to absorb damage type. Exclusion takes priority over inclusion if same dmgType had been added using AddDamageType.
      integer dmgType


General rule : When calling a GUI trigger, you need to set some variables that it will use. Those variables are always reset at the end of the trigger, so just check there to know which one are used.

  • Shield Creation.
    To create a shield, run the "Create Shield Complete" trigger.
    This requires all 16 variables to be set. Might create a bunch of simplified triggers in the future.
    327756-0aee04450f19331ece6518e7fe69fdf1.png

  • Remove Shield.
  • Destroy Shield.





FAQ

Why would you have so much different shields list ?
It can look a bit messy, and might complicate things a bit, but this avoid a lot of operations during damage blocking.
With this implementation, you only check for the needed shields on the trigger that will occur the most (except for the ShieldTickLoop that MUST check all shields).

Feel to correct me if I'm wrong and/or you see a more efficient way to implement this.

Complete engine code

vJASS:
/*
    vJass ShieldEngine 1.4.0
    by Marchombre

    Requirements:
    -   GUI Unit Event (by Bribe)
    -   Damage Engine 5.A.0.0 (by Bribe)
    -   CustomBar 1.0 (by dhk_undead_lord / aka Anachron)
*/

/*
    Apply shield in the following order:
    1/ Shield that can only take specific damage (only spell/attack) before shields that can absorb all
    2/ Shield that nullify one instance of damage before those blocking amount of damage
    3/ Order by timeLeft
*/

/*
    ================================================
                JASS API DESCRIPTION     
    ================================================
*/
/*
    These are the functions you can use to interact with the engine:

    ================================================
                    SHIELD CREATION    
    ================================================
  
    ==================================    
            PARAMETER STRUCTURE
 
    struct ShieldParameters
    |-- Members:
        public unit source
        public unit target
        public string name
        public string sfxPath
        public string attachPoint
        public integer shieldProcType
        public integer shieldType
        public integer shieldHpType
        public boolean isStackable
        public boolean isTimed
        public real duration
        public real HP
        public boolean breakAtZero
        public boolean canRegen
        public real timeBeforeRegen
        public real regenTime
    |-- Methods:
        public static method create takes nothing returns ShieldParameters  // create a new paramater struct instance with default values
        public method CreateShield takes nothing returns boolean            // from the parameter struct instance, creates a shield. Returns true if shield creation has been called, false if missing mandatory fields (name & target)


    ==================================    
            DIRECT FUNCTIONS

    ShieldEngine.CreateShield
    |-- Parameters:
        unit source,
        unit target,
        string name,
        string sfxPath,
        string attachPoint,
        integer shieldProcType,
        integer shieldType,
        integer shieldHpType,
        boolean isStackable,
        boolean isTimed,
        real duration,
        real HP,
        boolean breakAtZero,
        boolean canRegen,
        real timeBeforeRegen,
        real regenTime
    |-- Returns: nothing

    ShieldEngine.CreateTimedShield
    |-- Parameters:
        unit source,
        unit target,
        string name,
        real duration,
        real HP
    |-- Returns: nothing

    ShieldEngine.CreateInstanceShield
    |-- Parameters:
        unit source,
        unit target,
        string name,
        real HP
    |-- Returns: nothing

    ================================================
                    SHIELD DESTRUCTION    
    ================================================

    There are two ways of destroying a shield manually. Methods with Remove will do it silently. Methods with destroy will fire a BreakEvent.

    ShieldEngine.RemoveShieldByName
    |-- Parameters:
        unit u,
        string name
    |-- Returns: nothing
  
    ShieldEngine.RemoveAllShields
    |-- Parameters:
        unit u
    |-- Returns: nothing

    ShieldEngine.RemoveShieldsByType
    |-- Parameters:
        unit u,
        integer hpType
    |-- Returns: nothing

    ShieldEngine.DestroyShieldByName
    |-- Parameters:
        unit u,
        string name
    |-- Returns: nothing

    ShieldEngine.DestroyShieldsByType
    |-- Parameters:
        unit u,
        integer hpType
    |-- Returns: nothing

    ShieldEngine.DestroyAllShields
    |-- Parameters:
        unit u
    |-- Returns: nothing

    ================================================
                    UNIT INFORMATION    
    ================================================

    ShieldEngine.IsUnitShielded
    |-- Parameters:
        unit u
    |-- Returns: boolean

    ShieldEngine.UnitHasShield
    |-- Parameters:
        unit u,
        string name
    |-- Returns: boolean

    ShieldEngine.UnitHasActiveShield
    |-- Parameters:
        unit u
    |-- Returns: boolean

    ShieldEngine.UnitHasActiveShieldOfType
    |-- Parameters:
        unit u,
        integer shieldHpType
    |-- Returns: boolean

  
*/
/*
    ================================================
                GUI API DESCRIPTION     
    ================================================
*/
/*
    ================================================
                EVENTS TRIGGER VARIABLES    
    ================================================

    udg_PreShieldCreationEvent
    udg_ShieldCreatedEvent
    udg_ShieldAbsorbEvent
    udg_ShieldExpiredEvent
    udg_ShieldBreakEvent
    udg_ShieldRefreshedEvent
    udg_ShieldStackedEvent

    ================================================
                    EVENTS GETTERS    
    ================================================
 
    // This variables should be used as readonly in GUI
    udg_ShieldName
    udg_ShieldAbsorbedDamage
    udg_ShieldAbsorbedDamageSource
    udg_ShieldProcType
    udg_ShieldHpType
    udg_ShieldBaseMaxHP
    udg_ShieldPrvMaxHP
    udg_ShieldBaseDuration
    udg_ShieldPrvDuration


    ================================================
                    EVENTS VARIABLES
    ================================================

    // This Variables can be read and/or modified during events
    udg_ShieldSource
    udg_ShieldWielder
    udg_ShieldIsStackable
    udg_ShieldIsTimed
    udg_ShieldDuration
    udg_ShieldMaxHP
    udg_ShieldCurrentHP
    udg_ShieldBreakAtZero
    udg_ShieldCanRegen
    udg_ShieldTimeBeforeRegen
    udg_ShieldRegenTime

    ================================================
                    GUI CONSTANTS
    ================================================

    // This variables should be used as readonly in GUI
    udg_SHIELD_PROC_PREREDIC
    udg_SHIELD_PROC_POSTREDUC
    udg_SHIELD_TYPE_HP_BASED
    udg_SHIELD_TYPE_INSTANCE
    udg_SHIELD_HP_TYPE_ALL
    udg_SHIELD_HP_TYPE_SPELL
    udg_SHIELD_HP_TYPE_ATTACK

    ================================================
                    GUI API TRIGGERS
    ================================================

    // This variables are used in GUI triggers to call API

    udg_NextShieldSource
    udg_NextShieldTarget
    udg_NextShieldName
    udg_NextShieldSfxPath
    udg_NextShieldSfxAttachPoint
    udg_NextShieldProcType
    udg_NextShieldType
    udg_NextShieldHpType
    udg_NextShieldIsStackable
    udg_NextShieldIsTimed
    udg_NextShieldDuration
    udg_NextShieldHP
    udg_NextShieldBreakAtZero
    udg_NextShieldCanRegen
    udg_NextShieldTimeBeforeRegen
    udg_NextShieldRegenTime

*/

library ShieldEngine requires DamageEngine, ARGB, CustomBar
    globals
        /*
            ================================================
                            CONFIG VARIABLES      
            ================================================
        */
        private constant boolean USE_GUI                        = false     // Set to false if you call this system only in Jass.
        private constant real SHIELD_TICK_INTERVAL              = 0.1       // Interval (in second) at which duration is checked and regen is applied.
        private constant integer MAX_PRESHIELD_CREATION_EVENTS  = 4

        private constant integer NUMBER_OF_ATTACK_TYPE          = 8         // Number of Attack types in your map. Default are types in Damage Engine.
        private constant integer NUMBER_OF_DAMAGE_TYPE          = 23        // Number of Damage types in your map. Default are types in Damage Engine.
      
        /*
            Shield aesthetic
        */

        private constant string SHIELD_DEFAULT_SFX_PATH         = "Abilities\\Spells\\Human\\ManaShield\\ManaShieldCaster.mdl"
        private constant string SHIELD_DEFAULT_ATTACH_POINT     = "origin"

        // Shield Bar colors config is done in ShieldWieder.OnInit (can't be done in globals)
        // SHIELD_HP_TYPE_ALL => Green by default
        private ARGB SHIELD_COLOR_LEFT
        private ARGB SHIELD_COLOR_RIGHT

        // SHIELD_HP_TYPE_SPELL => Purple by default
        private ARGB SPELL_SHIELD_COLOR_LEFT
        private ARGB SPELL_SHIELD_COLOR_RIGHT

        // SHIELD_HP_TYPE_ATTACK => Brown by default
        private ARGB ATTACK_SHIELD_COLOR_LEFT
        private ARGB ATTACK_SHIELD_COLOR_RIGHT
      
        /*
            ================================================
                    SHOULD NOT TOUCH THESE CONSTANTS      
            ================================================
        */
        constant integer SHIELD_PROC_PREREDUC                   = 0     // Shield absorb damage before reductions like armor are applied.
        constant integer SHIELD_PROC_POSTREDUC                  = 1     // Shield absorb damage after reductions are applied.

        constant integer SHIELD_TYPE_HP_BASED                   = 0     // Shield that expire after set amount of damage.
        constant integer SHIELD_TYPE_INSTANCE                   = 1     // Shield that expires after a number of damage instance

        constant integer SHIELD_HP_TYPE_ALL                     = 0     // Absorb all type of damage
        constant integer SHIELD_HP_TYPE_SPELL                   = 1     // Absorb only spell damage
        constant integer SHIELD_HP_TYPE_ATTACK                  = 2     // Absorb only attack damage

        // Used for Firing events
        private constant integer SHIELD_EVENT_CREATED           = 0   
        private constant integer SHIELD_EVENT_ABSORB            = 1
        private constant integer SHIELD_EVENT_BREAK             = 2
        private constant integer SHIELD_EVENT_EXPIRE            = 3
        private constant integer SHIELD_EVENT_REFRESHED         = 4
        private constant integer SHIELD_EVENT_STACKED           = 5
    endglobals

    public function interface iChange takes CustomBar cb returns nothing

    struct Shield
        /*
            Per instance variables
        */
        public string name                          // Name of Shield, allows search/stack etc.

        public unit source                          // Unit that casted the shield
        public unit wielder                         // Unit that received the shield

        private string sfxPath                      // Path to the model of visual effect
        public effect visualEffect                  // Visual Effect attached to the unit
        private string attachPoint                  // Visual Effect attach point to the wielder

        public integer shieldProcType               // When does the shield apply
        public integer shieldType                   // How the shield block damage
        public integer shieldHpType                 // What type of damage does the shield absorb
        public boolean isStackable                  // Can this shield stack with itself

        public boolean isTimed                      // Is the shield limited in duration
        public real duration                        // If timed: number of seconds before expiration.
        public real timeLeft                        // If timed: how much time is left before expiration.
      
        public real maxHP                           // If shieldType = SHIELD_TYPE_HP_BASED: Amount of damage the shield can absorb. If shieldType = SHIELD_TYPE_INSTANCE: how many instance of damage does the shield absorb
        public real currentHP                       // Remaining HP
        public boolean breakAtZero                  // Does the shield break when its HPs reach 0
        public real lastAbsorbedDamage              // Store the last amount of damage blocked
        public unit lastAbsorbedDamageSource        // Store the unit who dealt last damage

        public boolean canRegen                     // Can the shield regenerate itself
        public real timeBeforeRegen                 // How long without taking damage before shield regen (in seconds)
        public real regenTime                       // How long should it take for a full regen (in seconds)
        public real regenPerTick                    // How much HP regen per tick

        private integer array attackTypesIncluded[NUMBER_OF_ATTACK_TYPE]
        private integer nbOfAttTypesIncluded
        private integer array attackTypesExcluded[NUMBER_OF_ATTACK_TYPE]
        private integer nbOfAttTypesExcluded

        private integer array damageTypesIncluded[NUMBER_OF_DAMAGE_TYPE]
        private integer nbOfDmgTypesIncluded
        private integer array damageTypesExcluded[NUMBER_OF_DAMAGE_TYPE]
        private integer nbOfDmgTypesExcluded

        // Following are to keep track of modifications during Events
        public real baseMaxHP                       // Initial Hp Amount when shield was casted
        public real prvMaxHP                        // Hp amount at the end of last step
        public real baseDuration                    // Initial duration when shield was casted
        public real prvDuration                     // Duration at the end of last step

        public Shield next
        public Shield prev

        public static method create takes /*
            */ unit source, /*
            */ unit target, /*
            */ string name, /*
            */ string sfxPath, /*
            */ string attachPoint, /*
            */ integer shieldProcType, /*
            */ integer shieldType, /*
            */ integer shieldHpType, /*
            */ boolean isStackable, /*
            */ boolean isTimed, /*
            */ real duration, /*
            */ real HP, /*
            */ boolean breakAtZero, /*
            */ boolean canRegen, /*
            */ real timeBeforeRegen, /*
            */ real regenTime /*
        */ returns Shield
            local Shield this = Shield.allocate()

            set this.source = source
            set this.wielder = target

            set this.name = name

            set this.sfxPath = sfxPath
            set this.attachPoint = attachPoint
            set this.visualEffect = null

            set this.shieldProcType = shieldProcType
            set this.shieldType = shieldType
            set this.shieldHpType = shieldHpType
            set this.isStackable = isStackable

            set this.isTimed = isTimed
            set this.duration = duration
            set this.timeLeft = this.duration

            // Set times to a very high amount that will not change to make it easier when sorting shields by duration
            if not this.isTimed then
                set this.duration = 1000000.00
                set this.timeLeft = 1000000.00
            endif

            set this.maxHP = HP
            set this.currentHP = HP
            set this.breakAtZero = breakAtZero

            set this.canRegen = canRegen
            set this.timeBeforeRegen = timeBeforeRegen
            set this.regenTime = regenTime

            set this.nbOfDmgTypesIncluded = 0
            set this.nbOfDmgTypesExcluded = 0

            if this.canRegen then
                set this.regenPerTick = this.maxHP / (this.regenTime / SHIELD_TICK_INTERVAL)
            else
                set this.regenPerTick = 0.00
            endif

            // Following are to keep track of modifications during Events
            set this.baseMaxHP = this.maxHP
            set this.prvMaxHP = this.maxHP
            set this.baseDuration = this.duration
            set this.prvDuration = this.duration

            set this.next = 0
            set this.next = 0
          
            return this
        endmethod

        public method destroy takes nothing returns nothing
            call DestroyEffect(this.visualEffect)
            call this.deallocate()
        endmethod

        public method CreateVisualEffet takes nothing returns nothing
            if this.visualEffect == null then
               set this.visualEffect = AddSpecialEffectTarget(this.sfxPath, this.wielder, this.attachPoint)
            endif
        endmethod

        public method Refresh takes real duration, real HP returns nothing
            local real prevMaxHP
            local real prevCurrentHP
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(this.wielder)

            set this.duration = RMaxBJ(this.duration, duration)
            set this.timeLeft = RMinBJ(this.duration, this.timeLeft + duration)
          
            set prevMaxHP = this.maxHP
            set prevCurrentHP = this.currentHP
      
            set this.maxHP = RMaxBJ(this.maxHP, HP)
            set this.currentHP = RMinBJ(this.maxHP, this.currentHP + HP)
          
            if this.shieldHpType == SHIELD_HP_TYPE_ALL then
                set wielder.totalMaxShield = wielder.totalMaxShield + (this.maxHP - prevMaxHP)
                set wielder.totalCurrentShield = wielder.totalCurrentShield + (this.currentHP - prevCurrentHP)
            elseif this.shieldHpType == SHIELD_HP_TYPE_SPELL then
                set wielder.totalMaxSpellShield = wielder.totalMaxSpellShield + (this.maxHP - prevMaxHP)
                set wielder.totalCurrentSpellShield = wielder.totalCurrentSpellShield + (this.currentHP - prevCurrentHP)
            elseif this.shieldHpType == SHIELD_HP_TYPE_ATTACK then
                set wielder.totalMaxAttackShield = wielder.totalMaxAttackShield + (this.maxHP - prevMaxHP)
                set wielder.totalCurrentAttackShield = wielder.totalCurrentAttackShield + (this.currentHP - prevCurrentHP)
            endif
          
            call ShieldEvent.FireEvent(SHIELD_EVENT_REFRESHED, this)
        endmethod

        public method Stack takes real duration, real HP returns nothing
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(this.wielder)

            set this.duration = RMaxBJ(this.duration, duration)
            set this.timeLeft = RMinBJ(this.duration, this.timeLeft + duration)

            set this.maxHP = this.maxHP + HP
            set this.currentHP = this.currentHP + HP

            if this.shieldHpType == SHIELD_HP_TYPE_ALL then
                set wielder.totalMaxShield = wielder.totalMaxShield + HP
                set wielder.totalCurrentShield = wielder.totalCurrentShield + HP
            elseif this.shieldHpType == SHIELD_HP_TYPE_SPELL then
                set wielder.totalMaxSpellShield = wielder.totalMaxSpellShield + HP
                set wielder.totalCurrentSpellShield = wielder.totalCurrentSpellShield + HP
            elseif this.shieldHpType == SHIELD_HP_TYPE_ATTACK then
                set wielder.totalMaxAttackShield = wielder.totalMaxAttackShield + HP
                set wielder.totalCurrentAttackShield = wielder.totalCurrentAttackShield + HP
            endif

            call ShieldEvent.FireEvent(SHIELD_EVENT_STACKED, this)
        endmethod

        public method AddAttackType takes integer attType returns nothing
            set this.attackTypesIncluded[this.nbOfAttTypesIncluded] = attType
            set this.nbOfAttTypesIncluded = this.nbOfAttTypesIncluded +1
        endmethod

        public method ExcludeAttackType takes integer attType returns nothing
            set this.attackTypesExcluded[this.nbOfAttTypesExcluded] = attType
            set this.nbOfAttTypesExcluded = this.nbOfAttTypesExcluded +1
        endmethod

        public method IsAttackTypeAbsorbed takes integer attType returns boolean
            local boolean included = false
            local boolean excluded = false
            local integer i

            if this.nbOfAttTypesIncluded > 0 then
                set i = 0
                loop
                    exitwhen included or i > this.nbOfAttTypesIncluded -1

                    set included = this.attackTypesIncluded[i] == attType

                    set i = i +1
                endloop
            else
                set included = true
            endif

            if this.nbOfAttTypesExcluded > 0 then
                set i = 0
                loop
                    exitwhen excluded or i > this.nbOfAttTypesExcluded -1

                    set excluded = this.attackTypesExcluded[i] == attType

                    set i = i +1
                endloop
            endif

            return included and not excluded
        endmethod

        public method AddDamageType takes integer dmgType returns nothing
            set this.damageTypesIncluded[this.nbOfDmgTypesIncluded] = dmgType
            set this.nbOfDmgTypesIncluded = this.nbOfDmgTypesIncluded +1
        endmethod

        public method ExcludeDamageType takes integer dmgType returns nothing
            set this.damageTypesExcluded[this.nbOfDmgTypesExcluded] = dmgType
            set this.nbOfDmgTypesExcluded = this.nbOfDmgTypesExcluded +1
        endmethod

        public method IsDamageTypeAbsorbed takes integer dmgType returns boolean
            local boolean included = false
            local boolean excluded = false
            local integer i

            if this.nbOfDmgTypesIncluded > 0 then
                set i = 0
                loop
                    exitwhen included or i > this.nbOfDmgTypesIncluded -1

                    set included = this.damageTypesIncluded[i] == dmgType

                    set i = i +1
                endloop
            else
                set included = true
            endif

            if this.nbOfDmgTypesExcluded > 0 then
                set i = 0
                loop
                    exitwhen excluded or i > this.nbOfDmgTypesExcluded -1

                    set excluded = this.damageTypesExcluded[i] == dmgType

                    set i = i +1
                endloop
            endif

            return included and not excluded
        endmethod
    endstruct


    struct ShieldWielder
        /*
            Per instance variables
        */
        public unit wielder
        private CustomBar shieldBar
        private CustomBar spellShieldBar
        private CustomBar attackShieldBar

        public Shield firstPreReduc
        public Shield lastPreReduc

        public Shield firstPreReducSpell
        public Shield lastPreReducSpell

        public Shield firstPreReducAttack
        public Shield lastPreReducAttack

        public Shield firstPostReduc
        public Shield lastPostReduc

        public Shield firstPostReducSpell
        public Shield lastPostReducSpell

        public Shield firstPostReducAttack
        public Shield lastPostReducAttack

        public real lastDamageTaken

        public real totalMaxShield
        public real totalCurrentShield

        public real totalMaxSpellShield
        public real totalCurrentSpellShield

        public real totalMaxAttackShield
        public real totalCurrentAttackShield

        public ShieldWielder prev
        public ShieldWielder next

        public static method create takes unit wielder returns ShieldWielder
            local ShieldWielder this = ShieldWielder.allocate()

            set this.wielder                        = wielder
            set this.shieldBar                      = 0
            set this.spellShieldBar                 = 0
            set this.attackShieldBar                = 0

            set this.firstPreReduc                  = 0
            set this.lastPreReduc                   = 0
            set this.firstPreReducSpell             = 0
            set this.lastPreReducSpell              = 0
            set this.firstPreReducAttack            = 0
            set this.lastPreReducAttack             = 0

            set this.firstPostReduc                 = 0
            set this.lastPostReduc                  = 0
            set this.firstPostReducSpell            = 0
            set this.lastPostReducSpell             = 0
            set this.firstPostReducAttack           = 0
            set this.lastPostReducAttack            = 0

            set this.lastDamageTaken                = 0.00

            set this.totalMaxShield                 = 0.00
            set this.totalCurrentShield             = 0.00
            set this.totalMaxSpellShield            = 0.00
            set this.totalCurrentSpellShield        = 0.00
            set this.totalMaxAttackShield           = 0.00
            set this.totalCurrentAttackShield       = 0.00

            set this.prev = 0
            set this.next = 0

            return this
        endmethod

        public method destroy takes nothing returns nothing
            // Making sure all shield bars are destroyed
            if this.shieldBar != 0 then
                call this.shieldBar.destroy()
                set this.shieldBar = 0
            endif

            if this.spellShieldBar != 0 then
                call this.spellShieldBar.destroy()
                set this.spellShieldBar = 0
            endif

            if this.attackShieldBar != 0 then
                call this.attackShieldBar.destroy()
                set this.attackShieldBar = 0
            endif

            call this.deallocate()
        endmethod

        private static method UpdateShieldBar takes CustomBar cb returns nothing
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(cb.target)

            set cb.txtBar.percentage = 100.0 * wielder.totalCurrentShield / wielder.totalMaxShield
        endmethod

        private static method UpdateSpellShieldBar takes CustomBar cb returns nothing
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(cb.target)

            set cb.txtBar.percentage = 100.0 * wielder.totalCurrentSpellShield / wielder.totalMaxSpellShield
        endmethod

        private static method UpdateAttackShieldBar takes CustomBar cb returns nothing
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(cb.target)

            set cb.txtBar.percentage = 100.0 * wielder.totalCurrentAttackShield / wielder.totalMaxAttackShield
        endmethod

        private static method ShowBarCheck takes player owner, player cur returns boolean
            return IsPlayerAlly(owner, cur) or owner == cur
        endmethod

        private method CreateShieldBar takes integer shieldHpType returns nothing
            local CustomBar cb
            local iChange updateFunction
            local real offsetX
            local real offsetY
            local ARGB colorLeft
            local ARGB colorRight

            if shieldHpType == SHIELD_HP_TYPE_SPELL then
                set cb = this.spellShieldBar
                set updateFunction = ShieldWielder.UpdateSpellShieldBar

                set offsetX = -90.0
                set offsetY = -70.0

                set colorLeft = SPELL_SHIELD_COLOR_LEFT
                set colorRight = SPELL_SHIELD_COLOR_RIGHT
            elseif shieldHpType == SHIELD_HP_TYPE_ATTACK then
                set cb = this.attackShieldBar
                set updateFunction = ShieldWielder.UpdateAttackShieldBar

                set offsetX = -90.0
                set offsetY = -40.0

                set colorLeft = ATTACK_SHIELD_COLOR_LEFT
                set colorRight = ATTACK_SHIELD_COLOR_RIGHT
            else
                set cb = this.shieldBar
                set updateFunction = ShieldWielder.UpdateShieldBar
              
                set offsetX = -90.0
                set offsetY = -10.0

                set colorLeft = SHIELD_COLOR_LEFT
                set colorRight = SHIELD_COLOR_RIGHT
            endif

            // create new bar only if there isn't already one
            if cb == 0 then
                set cb = CustomBar.create(this.wielder, updateFunction)
              
                set cb.txtTag.offsetX = offsetX
                set cb.txtTag.offsetY = offsetY

                call cb.txtBar.addGradient(colorLeft, colorRight, 1, 25)

                call cb.Show(bj_FORCE_ALL_PLAYERS, ShieldWielder.ShowBarCheck)
                call cb.showCB()

                if shieldHpType == SHIELD_HP_TYPE_SPELL then
                    set this.spellShieldBar = cb
                elseif shieldHpType == SHIELD_HP_TYPE_ATTACK then
                    set this.attackShieldBar = cb
                else
                    set this.shieldBar = cb
                endif
            endif

        endmethod

        public method AdjustBarAfterDamage takes integer shieldHpType, real dmgAmount returns nothing
            if shieldHpType == SHIELD_HP_TYPE_ALL then
                set this.totalCurrentShield = this.totalCurrentShield - dmgAmount
            elseif shieldHpType == SHIELD_HP_TYPE_SPELL then
                set this.totalCurrentSpellShield = this.totalCurrentSpellShield - dmgAmount
            elseif shieldHpType == SHIELD_HP_TYPE_ATTACK then
                set this.totalCurrentAttackShield = this.totalCurrentAttackShield - dmgAmount
            endif
        endmethod

        private method AdjustShieldBars takes string how, Shield shield returns nothing
            local CustomBar cb
          
            if shield.shieldHpType == SHIELD_HP_TYPE_SPELL then
                if how =="add" then
                    set this.totalMaxSpellShield        = this.totalMaxSpellShield + shield.maxHP
                    set this.totalCurrentSpellShield    = this.totalCurrentSpellShield + shield.currentHP

                    if this.spellShieldBar == 0 then
                        call this.CreateShieldBar(shield.shieldHpType)
                    endif
                else
                    set this.totalMaxSpellShield        = this.totalMaxSpellShield - shield.maxHP
                    set this.totalCurrentSpellShield    = this.totalCurrentSpellShield - shield.currentHP

                    if this.totalMaxSpellShield <= 0.00 then
                        call this.spellShieldBar.destroy()
                        set this.spellShieldBar = 0
                    endif
                endif
            elseif shield.shieldHpType == SHIELD_HP_TYPE_ATTACK then
                if how =="add" then
                    set this.totalMaxAttackShield       = this.totalMaxAttackShield + shield.maxHP
                    set this.totalCurrentAttackShield   = this.totalCurrentAttackShield + shield.currentHP

                    if this.attackShieldBar <= 0 then
                        call this.CreateShieldBar(shield.shieldHpType)
                    endif
                else
                    set this.totalMaxAttackShield       = this.totalMaxAttackShield - shield.maxHP
                    set this.totalCurrentAttackShield   = this.totalCurrentAttackShield - shield.currentHP

                    if this.totalMaxAttackShield <= 0.00 then
                        call this.attackShieldBar.destroy()
                        set this.attackShieldBar = 0
                    endif
                endif
            else
                if how =="add" then
                    set this.totalMaxShield             = this.totalMaxShield + shield.maxHP
                    set this.totalCurrentShield         = this.totalCurrentShield + shield.currentHP

                    if this.shieldBar == 0 then
                        call this.CreateShieldBar(shield.shieldHpType)
                    endif
                else
                    set this.totalMaxShield             = this.totalMaxShield - shield.maxHP
                    set this.totalCurrentShield         = this.totalCurrentShield - shield.currentHP

                    if this.totalMaxShield <= 0.00 then
                        call this.shieldBar.destroy()
                        set this.shieldBar = 0
                    endif
                endif
            endif
        endmethod

        private method AddFirstShield takes Shield shield returns nothing
            if shield.shieldProcType == SHIELD_PROC_PREREDUC and shield.shieldHpType == SHIELD_HP_TYPE_ALL then
                set this.firstPreReduc = shield
                set this.lastPreReduc = shield
            elseif shield.shieldProcType == SHIELD_PROC_PREREDUC and shield.shieldHpType == SHIELD_HP_TYPE_SPELL then
                set this.firstPreReducSpell = shield
                set this.lastPreReducSpell = shield
            elseif shield.shieldProcType == SHIELD_PROC_PREREDUC and shield.shieldHpType == SHIELD_HP_TYPE_ATTACK then
                set this.firstPreReducAttack = shield
                set this.lastPreReducAttack = shield
            elseif shield.shieldProcType == SHIELD_PROC_POSTREDUC and shield.shieldHpType == SHIELD_HP_TYPE_ALL then
                set this.firstPostReduc = shield
                set this.lastPostReduc = shield
            elseif shield.shieldProcType == SHIELD_PROC_POSTREDUC and shield.shieldHpType == SHIELD_HP_TYPE_SPELL then
                set this.firstPostReducSpell = shield
                set this.lastPostReducSpell = shield
            elseif shield.shieldProcType == SHIELD_PROC_POSTREDUC and shield.shieldHpType == SHIELD_HP_TYPE_ATTACK then
                set this.firstPostReducAttack = shield
                set this.lastPostReducAttack = shield
            endif
        endmethod

        public method AddShield takes Shield shield returns nothing
            local Shield current
            local Shield last
            local Shield array listOfFirst
            local integer array listOfProcType
            local integer array listOfHpType
            local integer i

            set listOfFirst[0]      = this.firstPreReduc
            set listOfFirst[1]      = this.firstPostReduc
            set listOfFirst[2]      = this.firstPreReducAttack
            set listOfFirst[3]      = this.firstPostReducAttack
            set listOfFirst[4]      = this.firstPreReducSpell
            set listOfFirst[5]      = this.firstPostReducSpell

            set listOfProcType[0]   = SHIELD_PROC_PREREDUC
            set listOfProcType[1]   = SHIELD_PROC_POSTREDUC
            set listOfProcType[2]   = SHIELD_PROC_PREREDUC
            set listOfProcType[3]   = SHIELD_PROC_POSTREDUC
            set listOfProcType[4]   = SHIELD_PROC_PREREDUC
            set listOfProcType[5]   = SHIELD_PROC_POSTREDUC

            set listOfHpType[0]     = SHIELD_HP_TYPE_ALL
            set listOfHpType[1]     = SHIELD_HP_TYPE_ALL
            set listOfHpType[2]     = SHIELD_HP_TYPE_ATTACK
            set listOfHpType[3]     = SHIELD_HP_TYPE_ATTACK
            set listOfHpType[4]     = SHIELD_HP_TYPE_SPELL
            set listOfHpType[5]     = SHIELD_HP_TYPE_SPELL

            call shield.CreateVisualEffet()
            call this.AdjustShieldBars("add", shield)

            set i = 0

            loop
                exitwhen i > 5
              
                set current = listOfFirst[i]

                // check if the correct list is empty
                if current == 0 and shield.shieldProcType == listOfProcType[i] and shield.shieldHpType == listOfHpType[i] then
                    call this.AddFirstShield(shield)

                    return
                endif

                loop
                    exitwhen current == 0 or shield.shieldProcType != listOfProcType[i] or shield.shieldHpType != listOfHpType[i]
  
                    if shield.shieldType == current.shieldType then
                        if shield.timeLeft < current.timeLeft then
                            set shield.prev = current.prev
                            set current.prev.next = shield
  
                            set shield.next = current
                            set current.prev = shield
                          
                            return
                        endif
                    elseif shield.shieldType == SHIELD_TYPE_INSTANCE and current.shieldType == SHIELD_TYPE_HP_BASED then
                        set shield.prev = current.prev
                        set current.prev.next = shield
  
                        set shield.next = current
                        set current.prev = shield
  
                        return
                    endif
                  
                    set last = current
                    set current = current.next
                endloop

                // check if it should be inserted at the end of this list
                if shield.shieldProcType == listOfProcType[i] and shield.shieldHpType == listOfHpType[i] then
                    set last.next = shield
                endif
              
                set i = i +1
            endloop

        endmethod

        public method RemoveShield takes Shield shield returns nothing
            if shield == this.firstPreReduc then
                set this.firstPreReduc = shield.next
            elseif shield == this.firstPostReduc then
                set this.firstPostReduc = shield.next
            elseif shield == this.firstPreReducAttack then
                set this.firstPreReducAttack = shield.next
            elseif shield == this.firstPostReducAttack then
                set this.firstPostReducAttack = shield.next
            elseif shield == this.firstPreReducSpell then
                set this.firstPreReducSpell = shield.next
            elseif shield == this.firstPostReducSpell then
                set this.firstPostReducSpell = shield.next
            endif

            if shield == this.lastPreReduc then
                set this.lastPreReduc = shield.prev
            elseif shield == this.lastPostReduc then
                set this.lastPostReduc = shield.prev
            elseif shield == this.lastPreReducAttack then
                set this.lastPreReducAttack = shield.prev
            elseif shield == this.lastPostReducAttack then
                set this.lastPostReducAttack = shield.prev
            elseif shield == this.lastPreReducSpell then
                set this.lastPreReducSpell = shield.prev
            elseif shield == this.lastPostReducSpell then
                set this.lastPostReduc = shield.prev
            endif

            if shield.prev != 0 then
                set shield.prev.next = shield.next
            endif
            if shield.next != 0 then
                set shield.next.prev = shield.prev
            endif
          
            call this.AdjustShieldBars("remove", shield)
            call ShieldEngine.CheckRemoveWielder(this)
        endmethod

        public method FindShieldByNameAndTypes takes string name, integer shieldProcType, integer shieldType, integer shieldHpType returns Shield
            local Shield shield = 0

            if shieldHpType == SHIELD_HP_TYPE_ALL then
                if shieldProcType == SHIELD_PROC_PREREDUC then
                    set shield = this.firstPreReduc
                else
                    set shield = this.firstPostReduc
                endif
            elseif shieldHpType == SHIELD_HP_TYPE_SPELL then
                if shieldProcType == SHIELD_PROC_PREREDUC then
                    set shield = this.firstPreReducSpell
                else
                    set shield = this.firstPostReducSpell
                endif
            elseif shieldHpType == SHIELD_HP_TYPE_ATTACK then
                if shieldProcType == SHIELD_PROC_PREREDUC then
                    set shield = this.firstPreReducAttack
                else
                    set shield = this.firstPostReducAttack
                endif
            endif
          
            loop
                exitwhen shield == 0

                if shield.name == name and shield.shieldType == shieldType then
                    return shield
                endif

                set shield = shield.next
            endloop

            return 0
        endmethod

        public method RemoveShieldsByType takes integer hpType returns nothing
            local Shield shield
  
            if hpType == SHIELD_HP_TYPE_ALL then
                set shield = this.firstPreReduc
            elseif hpType == SHIELD_HP_TYPE_ATTACK then
                set shield = this.firstPreReducAttack
            elseif hpType == SHIELD_HP_TYPE_SPELL then
                set shield = this.firstPreReducSpell
            endif
  
            loop
                exitwhen shield == 0
  
                call this.RemoveShield(shield)
                call shield.destroy()
  
                set shield = shield.next
            endloop
  
            if hpType == SHIELD_HP_TYPE_ALL then
                set shield = this.firstPostReduc
            elseif hpType == SHIELD_HP_TYPE_ATTACK then
                set shield = this.firstPostReducAttack
            elseif hpType == SHIELD_HP_TYPE_SPELL then
                set shield = this.firstPostReducSpell
            endif
  
            loop
                exitwhen shield == 0
  
                call this.RemoveShield(shield)
                call shield.destroy()
  
                set shield = shield.next
            endloop
    endmethod

        public method DestroyShieldsByType takes integer hpType returns nothing
            local Shield shield

            if hpType == SHIELD_HP_TYPE_ALL then
                set shield = this.firstPreReduc
            elseif hpType == SHIELD_HP_TYPE_ATTACK then
                set shield = this.firstPreReducAttack
            elseif hpType == SHIELD_HP_TYPE_SPELL then
                set shield = this.firstPreReducSpell
            endif

            loop
                exitwhen shield == 0

                call this.RemoveShield(shield)
                call ShieldEvent.FireEvent(SHIELD_EVENT_BREAK, shield)

                set shield = shield.next
            endloop

            if hpType == SHIELD_HP_TYPE_ALL then
                set shield = this.firstPostReduc
            elseif hpType == SHIELD_HP_TYPE_ATTACK then
                set shield = this.firstPostReducAttack
            elseif hpType == SHIELD_HP_TYPE_SPELL then
                set shield = this.firstPostReducSpell
            endif

            loop
                exitwhen shield == 0

                call this.RemoveShield(shield)
                call ShieldEvent.FireEvent(SHIELD_EVENT_BREAK, shield)

                set shield = shield.next
            endloop
        endmethod

        public method RemoveShieldByName takes string name returns nothing
            local Shield array listOfFirst
            local Shield shield
            local integer i = 0

            set listOfFirst[0] = this.firstPreReduc
            set listOfFirst[1] = this.firstPostReduc
            set listOfFirst[2] = this.firstPreReducAttack
            set listOfFirst[3] = this.firstPostReducAttack
            set listOfFirst[4] = this.firstPreReducSpell
            set listOfFirst[5] = this.firstPostReducSpell

            loop
                exitwhen i > 5

                set shield = listOfFirst[i]

                loop
                    exitwhen shield == 0

                    if shield.name == name then
                        call this.RemoveShield(shield)
                    endif

                    set shield = shield.next
                endloop

                set i = i +1
            endloop
        endmethod

        public method DestroyShieldByName takes string name returns nothing
            local Shield array listOfFirst
            local Shield shield
            local integer i = 0

            set listOfFirst[0] = this.firstPreReduc
            set listOfFirst[1] = this.firstPostReduc
            set listOfFirst[2] = this.firstPreReducAttack
            set listOfFirst[3] = this.firstPostReducAttack
            set listOfFirst[4] = this.firstPreReducSpell
            set listOfFirst[5] = this.firstPostReducSpell

            loop
                exitwhen i > 5

                set shield = listOfFirst[i]

                loop
                    exitwhen shield == 0

                    if shield.name == name then
                        call this.RemoveShield(shield)
                        call ShieldEvent.FireEvent(SHIELD_EVENT_BREAK, shield)
                    endif

                    set shield = shield.next
                endloop

                set i = i +1
            endloop
        endmethod

        public method HasShield takes string name returns boolean
            local Shield array listOfFirst
            local Shield shield
            local integer i = 0

            set listOfFirst[0] = this.firstPreReduc
            set listOfFirst[1] = this.firstPostReduc
            set listOfFirst[2] = this.firstPreReducAttack
            set listOfFirst[3] = this.firstPostReducAttack
            set listOfFirst[4] = this.firstPreReducSpell
            set listOfFirst[5] = this.firstPostReducSpell

            loop
                exitwhen i > 5

                set shield = listOfFirst[i]

                loop
                    exitwhen shield == 0

                    if shield.name == name then
                        return true
                    endif

                    set shield = shield.next
                endloop

                set i = i +1
            endloop

            return false
        endmethod

        private static method onInit takes nothing returns nothing
            // Shield Bar color, gradient from left to right. Parameters: Alpha, red, green, blue
            // SHIELD_HP_TYPE_ALL => Green by default
            set SHIELD_COLOR_LEFT                          = ARGB.create(255, 15, 75, 15)
            set SHIELD_COLOR_RIGHT                         = ARGB.create(255, 15, 220, 15)

            // SHIELD_HP_TYPE_SPELL => Purple by default
            set SPELL_SHIELD_COLOR_LEFT                    = ARGB.create(255, 75, 10, 75)
            set SPELL_SHIELD_COLOR_RIGHT                   = ARGB.create(255, 220, 30, 220)

            // SHIELD_HP_TYPE_ATTACK => Brown by default
            set ATTACK_SHIELD_COLOR_LEFT                   = ARGB.create(255, 220, 150, 25)
            set ATTACK_SHIELD_COLOR_RIGHT                  = ARGB.create(255, 150, 75, 25)
        endmethod
    endstruct

    struct ShieldEvent
        /*
            Per instance variables
        */
        private integer eventType
        private Shield shield

        private ShieldEvent next
      
        /*
            Shared variables
        */
        private static ShieldEvent first
        private static ShieldEvent last

        private static trigger array trg_PreCreationEvent
        private static trigger trg_CreatedEvent
        private static trigger trg_AbsorbEvent
        private static trigger trg_ExpiredEvent
        private static trigger trg_BreakEvent
        private static trigger trg_RefreshedEvent
        private static trigger trg_StackedEvent

        static Shield eventShield

        public static method create takes integer eventType, Shield shield returns ShieldEvent
            local ShieldEvent this = ShieldEvent.allocate()

            set this.eventType = eventType
            set this.shield = shield

            set this.next = 0

            return this
        endmethod

        private method destroy takes nothing returns nothing
            call this.deallocate()
        endmethod

        private static method resetEventVariables takes nothing returns nothing
            static if USE_GUI then
                set udg_ShieldName              = null
              
                set udg_ShieldSource            = null
                set udg_ShieldWielder           = null

                set udg_ShieldProcType          = -1
                set udg_ShieldType              = -1
                set udg_ShieldHpType            = -1

                set udg_ShieldIsStackable       = false
              
                set udg_ShieldIsTimed           = false
                set udg_ShieldDuration          = 0.00
                set udg_ShieldTimeLeft          = 0.00

                set udg_ShieldMaxHP             = 0.00
                set udg_ShieldCurrentHP         = 0.00
                set udg_ShieldBreakAtZero       = false

                set udg_ShieldAbsorbedDamage    = 0.00

                set udg_ShieldCanRegen          = false
                set udg_ShieldTimeBeforeRegen   = 0.00
                set udg_ShieldRegenTime         = 0.00

                set udg_ShieldBaseMaxHP         = 0.00
                set udg_ShieldPrvMaxHP          = 0.00
                set udg_ShieldBaseDuration      = 0.00
                set udg_ShieldPrvDuration       = 0.00
            endif
        endmethod

        private method setEventVariables takes nothing returns nothing
            static if USE_GUI then
                set udg_ShieldName                  = this.shield.name
              
                set udg_ShieldSource                = this.shield.source
                set udg_ShieldWielder               = this.shield.wielder

                set udg_ShieldProcType              = this.shield.shieldProcType
                set udg_ShieldType                  = this.shield.shieldType
                set udg_ShieldHpType                = this.shield.shieldHpType

                set udg_ShieldIsStackable           = this.shield.isStackable
              
                set udg_ShieldIsTimed               = this.shield.isTimed
                set udg_ShieldDuration              = this.shield.duration
                set udg_ShieldTimeLeft              = this.shield.timeLeft

                set udg_ShieldMaxHP                 = this.shield.maxHP
                set udg_ShieldCurrentHP             = this.shield.currentHP
                set udg_ShieldBreakAtZero           = this.shield.breakAtZero

                set udg_ShieldAbsorbedDamage        = this.shield.lastAbsorbedDamage
                set udg_ShieldAbsorbedDamageSource  = this.shield.lastAbsorbedDamageSource

                set udg_ShieldCanRegen              = this.shield.canRegen
                set udg_ShieldTimeBeforeRegen       = this.shield.timeBeforeRegen
                set udg_ShieldRegenTime             = this.shield.regenTime

                set udg_ShieldBaseMaxHP             = this.shield.baseMaxHP
                set udg_ShieldPrvMaxHP              = this.shield.prvMaxHP
                set udg_ShieldBaseDuration          = this.shield.baseDuration
                set udg_ShieldPrvDuration           = this.shield.prvDuration
            endif
        endmethod

        private method getEventVariables takes nothing returns nothing
            static if USE_GUI then
                set this.shield.source              = udg_ShieldSource
                set this.shield.wielder             = udg_ShieldWielder

                set this.shield.isStackable         = udg_ShieldIsStackable
              
                set this.shield.isTimed             = udg_ShieldIsTimed
                set this.shield.duration            = udg_ShieldDuration
                set this.shield.timeLeft            = udg_ShieldTimeLeft

                set this.shield.maxHP               = udg_ShieldMaxHP
                set this.shield.currentHP           = udg_ShieldCurrentHP
                set this.shield.breakAtZero         = udg_ShieldBreakAtZero

                set this.shield.canRegen            = udg_ShieldCanRegen
                set this.shield.timeBeforeRegen     = udg_ShieldTimeBeforeRegen
                set this.shield.regenTime           = udg_ShieldRegenTime

                if this.shield.canRegen then
                    set this.shield.regenPerTick = this.shield.maxHP / (this.shield.regenTime / SHIELD_TICK_INTERVAL)
                else
                    set this.shield.regenPerTick = 0.00
                endif

                // Stores this step result for reference in next step if needed.
                set this.shield.prvMaxHP            = udg_ShieldMaxHP
                set this.shield.prvDuration         = udg_ShieldDuration         
            endif
        endmethod

        public static method AddOnPreCreation takes code callback, integer index returns nothing
            if index >= 0 and index <= MAX_PRESHIELD_CREATION_EVENTS then
                call TriggerAddCondition(trg_PreCreationEvent[index], Filter(callback))
            else
                call BJDebugMsg("|cffff0000Warning:|r invalid index for OnPreCreation. Must be between 1 and " + I2S(MAX_PRESHIELD_CREATION_EVENTS))
            endif
        endmethod

        public static method AddOnCreation takes code callback returns nothing
            call TriggerAddCondition(trg_CreatedEvent, Filter(callback))
        endmethod

        public static method AddOnAbsorb takes code callback returns nothing
            call TriggerAddCondition(trg_AbsorbEvent, Filter(callback))
        endmethod

        public static method AddOnExpired takes code callback returns nothing
            call TriggerAddCondition(trg_ExpiredEvent, Filter(callback))
        endmethod

        public static method AddOnBreak takes code callback returns nothing
            call TriggerAddCondition(trg_BreakEvent, Filter(callback))
        endmethod

        public static method AddOnRefresh takes code callback returns nothing
            call TriggerAddCondition(trg_RefreshedEvent, Filter(callback))
        endmethod

        public static method AddOnStack takes code callback returns nothing
            call TriggerAddCondition(trg_StackedEvent, Filter(callback))
        endmethod

        public static method FireEvent takes integer eventType, Shield shield returns nothing
            local ShieldEvent shieldEvent = ShieldEvent.create(eventType, shield)

            if first == 0 then
                set first = shieldEvent
                set last = shieldEvent
                call ShieldEvent.Run()
            else
                set last.next = shieldEvent
                set last = last.next
            endif

        endmethod

        private static method Run takes nothing returns nothing
            local ShieldEvent shEvent
            local real ev
            local ShieldWielder wielder

            loop
                exitwhen first == 0
              
                set eventShield = first.shield
                set shEvent = first
                set wielder = ShieldEngine.GetUnitWielder(shEvent.shield.wielder)
              
                if shEvent.eventType == SHIELD_EVENT_CREATED then
                    set ev = 1.00
                    loop
                        exitwhen ev > MAX_PRESHIELD_CREATION_EVENTS

                        // execute callbacks set in jass
                        call TriggerEvaluate(trg_PreCreationEvent[R2I(ev)])

                        static if USE_GUI then
                            // Set GUI vars
                            call shEvent.setEventVariables()

                            // Fire PreCreation event
                            set udg_PreShieldCreationEvent  = 0.00
                            set udg_PreShieldCreationEvent  = ev
                            set udg_PreShieldCreationEvent  = 0.00

                            // Update fields in case event was caught and variables changed.
                            call shEvent.getEventVariables()
                        endif

                        set ev = ev + 1.00
                    endloop

                    if (not shEvent.shield.isTimed or shEvent.shield.timeLeft > 0.00) /*
                        */ and (not shEvent.shield.breakAtZero or shEvent.shield.currentHP > 0.00) /*
                    */ then
                        // execute callbacks set in jass
                        call TriggerEvaluate(trg_CreatedEvent)

                        // Attach shield to wielder
                        call wielder.AddShield(shEvent.shield)

                        static if USE_GUI then
                            // Set GUI vars
                            call shEvent.setEventVariables()

                            // Fire ShieldCreated event
                            set udg_ShieldCreatedEvent          = 0.00
                            set udg_ShieldCreatedEvent          = 1.00
                            set udg_ShieldCreatedEvent          = 0.00
                        endif
                    endif
                elseif shEvent.eventType == SHIELD_EVENT_ABSORB then
                    // execute callbacks set in jass
                    call TriggerEvaluate(trg_AbsorbEvent)

                    static if USE_GUI then
                        // Set GUI vars
                        call shEvent.setEventVariables()

                        set udg_ShieldAbsorbEvent           = 0.00
                        set udg_ShieldAbsorbEvent           = 1.00
                        set udg_ShieldAbsorbEvent           = 0.00

                        // Update fields in case event was caught and variables changed.
                        call shEvent.getEventVariables()
                    endif
                elseif shEvent.eventType == SHIELD_EVENT_EXPIRE then
                    // execute callbacks set in jass
                    call TriggerEvaluate(trg_ExpiredEvent)

                    static if USE_GUI then
                        // Set GUI vars
                        call shEvent.setEventVariables()

                        set udg_ShieldExpiredEvent          = 0.00
                        set udg_ShieldExpiredEvent          = 1.00
                        set udg_ShieldExpiredEvent          = 0.00

                        call shEvent.shield.destroy()
                    endif
                elseif shEvent.eventType == SHIELD_EVENT_BREAK then
                    // execute callbacks set in jass
                    call TriggerEvaluate(trg_BreakEvent)

                    static if USE_GUI then
                        // Set GUI vars
                        call shEvent.setEventVariables()

                        set udg_ShieldBreakEvent            = 0.00
                        set udg_ShieldBreakEvent            = 1.00
                        set udg_ShieldBreakEvent            = 0.00

                        call shEvent.shield.destroy()
                    endif
                elseif shEvent.eventType == SHIELD_EVENT_REFRESHED then
                    // execute callbacks set in jass
                    call TriggerEvaluate(trg_RefreshedEvent)

                    static if USE_GUI then
                        // Set GUI vars
                        call shEvent.setEventVariables()

                        set udg_ShieldRefreshedEvent        = 0.00
                        set udg_ShieldRefreshedEvent        = 1.00
                        set udg_ShieldRefreshedEvent        = 0.00

                        // Update fields in case event was caught and variables changed.
                        call shEvent.getEventVariables()
                    endif
                elseif shEvent.eventType == SHIELD_EVENT_STACKED then
                    // execute callbacks set in jass
                    call TriggerEvaluate(trg_StackedEvent)

                    static if USE_GUI then
                        // Set GUI vars
                        call shEvent.setEventVariables()

                        set udg_ShieldStackedEvent          = 0.00
                        set udg_ShieldStackedEvent          = 1.00
                        set udg_ShieldStackedEvent          = 0.00

                        // Update fields in case event was caught and variables changed.
                        call shEvent.getEventVariables()
                    endif
                endif
                  
                static if USE_GUI then
                    // Resets variables
                    call resetEventVariables()
                endif

                // Set the next ShieldEvent that will be run
                set first = shEvent.next
              
                // Free resources
                call shEvent.destroy()
            endloop
        endmethod

        private static method onInit takes nothing returns nothing
            local integer i

            set first   = 0
            set last    = 0

            set i = 1
            loop
                exitwhen i > MAX_PRESHIELD_CREATION_EVENTS
                set trg_PreCreationEvent[i] = CreateTrigger()

                set i = i +1
            endloop

            set trg_CreatedEvent        = CreateTrigger()
            set trg_AbsorbEvent         = CreateTrigger()
            set trg_ExpiredEvent        = CreateTrigger()
            set trg_BreakEvent          = CreateTrigger()
            set trg_RefreshedEvent      = CreateTrigger()
            set trg_StackedEvent        = CreateTrigger()
        endmethod
    endstruct

    struct ShieldEngine
        /*
            Shared variables
        */
        private static timer clock
        private static timer tickTimer
        private static trigger preReducTrigger
        private static trigger postReducTrigger
        private static trigger tickTrigger
        private static trigger unregisterTrigger

        private static boolean array isInSystem
        private static ShieldWielder array unitToWielder
        private static ShieldWielder firstWielder
        private static ShieldWielder lastWielder
      
        private static method ApplyShields takes integer procType returns nothing
            local Shield shield = 0
            local real blockedDmg

            local unit dmgTarget = udg_DamageEventTarget
            local integer uId = GetUnitUserData(dmgTarget)
            local ShieldWielder wielder = unitToWielder[uId]

            local boolean isDmgSpell = udg_IsDamageSpell
            local boolean isDmgAtt = udg_IsDamageAttack
            local integer dmgType = udg_DamageEventDamageT
            local integer attType = udg_DamageEventAttackT

            set wielder.lastDamageTaken = TimerGetElapsed(clock)

            /*
                Loop through shields, applying one by one until either no shield left or no damage left
                Shields order criterias
                1/ Shield that can only take specific damage (only spell/attack) before shields that can absorb all
                2/ Shield that nullify one instance of damage before those blocking amount of damage
                3/ Order by timeLeft
            */

            if procType == SHIELD_PROC_PREREDUC then
                if isDmgSpell then
                    set shield = wielder.firstPreReducSpell
                elseif isDmgAtt then
                    set shield = wielder.firstPreReducAttack
                endif
            elseif procType == SHIELD_PROC_POSTREDUC then
                if isDmgSpell then
                    set shield = wielder.firstPostReducSpell
                elseif isDmgAtt then
                    set shield = wielder.firstPostReducAttack
                endif
            endif

            // Loop through shields against specific damage
            loop
                exitwhen shield == 0 or udg_DamageEventAmount == 0.00

                if shield.IsAttackTypeAbsorbed(attType) and shield.IsDamageTypeAbsorbed(dmgType) then
                    if shield.shieldType == SHIELD_TYPE_INSTANCE and shield.currentHP >= 1.00 then
                        set shield.lastAbsorbedDamage = udg_DamageEventAmount
                        set shield.lastAbsorbedDamageSource = udg_DamageEventSource
                        set udg_DamageEventAmount = 0.00
                      
                        set shield.currentHP = shield.currentHP - 1.00
                        call wielder.AdjustBarAfterDamage(shield.shieldHpType, 1.00)

                        call ShieldEvent.FireEvent(SHIELD_EVENT_ABSORB, shield)

                        if shield.currentHP == 0 and shield.breakAtZero then
                            call wielder.RemoveShield(shield)
                            call ShieldEvent.FireEvent(SHIELD_EVENT_BREAK, shield)
                        endif
                    elseif shield.shieldType == SHIELD_TYPE_HP_BASED and shield.currentHP >= 0.00 then
                        if shield.currentHP > udg_DamageEventAmount then
                            set shield.currentHP = shield.currentHP - udg_DamageEventAmount
                            call wielder.AdjustBarAfterDamage(shield.shieldHpType, udg_DamageEventAmount)

                            set shield.lastAbsorbedDamage = udg_DamageEventAmount
                            set shield.lastAbsorbedDamageSource = udg_DamageEventSource
                            set udg_DamageEventAmount = 0.00

                            call ShieldEvent.FireEvent(SHIELD_EVENT_ABSORB, shield)
                        else
                            set udg_DamageEventAmount = udg_DamageEventAmount - shield.currentHP
                            call wielder.AdjustBarAfterDamage(shield.shieldHpType, shield.currentHP)

                            set shield.lastAbsorbedDamage = shield.currentHP
                            set shield.lastAbsorbedDamageSource = udg_DamageEventSource
                            set shield.currentHP = 0.00

                            call ShieldEvent.FireEvent(SHIELD_EVENT_ABSORB, shield)

                            if shield.breakAtZero then
                                call wielder.RemoveShield(shield)
                                call ShieldEvent.FireEvent(SHIELD_EVENT_BREAK, shield)
                            endif
                        endif
                    endif

                endif
                set shield = shield.next
            endloop

            if procType == SHIELD_PROC_PREREDUC then
                set shield = wielder.firstPreReduc
            elseif procType == SHIELD_PROC_POSTREDUC then
                set shield = wielder.firstPostReduc
            endif

            // Loop through shields against all type of damage
            loop
                exitwhen shield == 0 or udg_DamageEventAmount == 0.00
              
                if shield.IsAttackTypeAbsorbed(attType) and shield.IsDamageTypeAbsorbed(dmgType) then

                    if shield.shieldType == SHIELD_TYPE_INSTANCE and shield.currentHP >= 1.00 then
                        set shield.lastAbsorbedDamage = udg_DamageEventAmount
                        set shield.lastAbsorbedDamageSource = udg_DamageEventSource
                        set udg_DamageEventAmount = 0.00
                      
                        set shield.currentHP = shield.currentHP - 1.00
                        call wielder.AdjustBarAfterDamage(shield.shieldHpType, 1.00)

                        call ShieldEvent.FireEvent(SHIELD_EVENT_ABSORB, shield)

                        if shield.currentHP == 0 and shield.breakAtZero then
                            call wielder.RemoveShield(shield)
                            call ShieldEvent.FireEvent(SHIELD_EVENT_BREAK, shield)
                        endif
                    elseif shield.shieldType == SHIELD_TYPE_HP_BASED and shield.currentHP >= 0.00 then
                        if shield.currentHP > udg_DamageEventAmount then
                            set shield.currentHP = shield.currentHP - udg_DamageEventAmount
                            call wielder.AdjustBarAfterDamage(shield.shieldHpType, udg_DamageEventAmount)

                            set shield.lastAbsorbedDamage = udg_DamageEventAmount
                            set shield.lastAbsorbedDamageSource = udg_DamageEventSource
                            set udg_DamageEventAmount = 0.00

                            call ShieldEvent.FireEvent(SHIELD_EVENT_ABSORB, shield)
                        else
                            set udg_DamageEventAmount = udg_DamageEventAmount - shield.currentHP
                            call wielder.AdjustBarAfterDamage(shield.shieldHpType, shield.currentHP)

                            set shield.lastAbsorbedDamage = shield.currentHP
                            set shield.lastAbsorbedDamageSource = udg_DamageEventSource
                            set shield.currentHP = 0.00

                            call ShieldEvent.FireEvent(SHIELD_EVENT_ABSORB, shield)

                            if shield.breakAtZero then
                                call wielder.RemoveShield(shield)
                                call ShieldEvent.FireEvent(SHIELD_EVENT_BREAK, shield)
                            endif
                        endif
                      
                    endif
                endif

                set shield = shield.next
            endloop
        endmethod

        private static method ApplyShieldPreReduc takes nothing returns nothing
            call ShieldEngine.ApplyShields(SHIELD_PROC_PREREDUC)
        endmethod

        private static method ApplyShieldPostReduc takes nothing returns nothing
            call ShieldEngine.ApplyShields(SHIELD_PROC_POSTREDUC)
        endmethod

        private static method ShieldTickLoop takes nothing returns nothing
            local ShieldWielder wielder = firstWielder
            local Shield shield
            local Shield array listOfFirst
            local integer i

          
            // Update every shield of every shield wielder
            loop
                exitwhen wielder == 0

                set listOfFirst[0] = wielder.firstPreReduc
                set listOfFirst[1] = wielder.firstPostReduc
                set listOfFirst[2] = wielder.firstPreReducAttack
                set listOfFirst[3] = wielder.firstPostReducAttack
                set listOfFirst[4] = wielder.firstPreReducSpell
                set listOfFirst[5] = wielder.firstPostReducSpell
              
                set i = 0

                loop
                    exitwhen i > 5

                    set shield = listOfFirst[i]

                    loop
                        exitwhen shield == 0

                        if shield.canRegen and shield.currentHP != shield.maxHP and (TimerGetElapsed(clock) - wielder.lastDamageTaken > shield.timeBeforeRegen) then
                            set shield.currentHP = RMinBJ(shield.maxHP, shield.currentHP + shield.regenPerTick)

                            call wielder.AdjustBarAfterDamage(shield.shieldHpType, -1.00 * shield.regenPerTick)
                        endif

                        if shield.isTimed then
                            set shield.timeLeft = shield.timeLeft - SHIELD_TICK_INTERVAL
                          
                            if shield.timeLeft <= 0.00 then
                                call wielder.RemoveShield(shield)
                                call ShieldEvent.FireEvent(SHIELD_EVENT_EXPIRE, shield)
                            endif
                        endif

                        set shield = shield.next
                    endloop

                    set i = i +1
                endloop

                set wielder = wielder.next
            endloop
        endmethod

        public static method GetUnitWielder takes unit u returns ShieldWielder
            local integer uId = GetUnitUserData(u)

            if isInSystem[uId] then
                return unitToWielder[uId]
            endif

            return 0
        endmethod

        private static method IsUnitInSytem takes nothing returns boolean
            local integer uId = GetUnitUserData(udg_DamageEventTarget)

            return isInSystem[uId]
        endmethod

        private static method onInit takes nothing returns nothing
            /*
                ================================================
                                    TRIGGERS     
                ================================================
            */

            set clock                   = CreateTimer()
            set tickTimer               = CreateTimer()
            set preReducTrigger         = CreateTrigger()
            set postReducTrigger        = CreateTrigger()
            set tickTrigger             = CreateTrigger()
            set unregisterTrigger       = CreateTrigger()

            call StartTimerBJ(clock, true, 99999.00)
            call StartTimerBJ(tickTimer, true, SHIELD_TICK_INTERVAL)
          
            // Check shields before damage reduction
            call TriggerRegisterVariableEvent(preReducTrigger, "udg_PreDamageEvent", EQUAL, 1.00)
            call TriggerAddAction(preReducTrigger, function ShieldEngine.ApplyShieldPreReduc)
            call TriggerAddCondition(preReducTrigger, function ShieldEngine.IsUnitInSytem)

            // Check shields after damage reduction
            call TriggerRegisterVariableEvent(postReducTrigger, "udg_ArmorDamageEvent", EQUAL, 1.00)
            call TriggerAddAction(postReducTrigger, function ShieldEngine.ApplyShieldPostReduc)
            call TriggerAddCondition(preReducTrigger, function ShieldEngine.IsUnitInSytem)

            // Loop Trigger for duration and regeneration
            call TriggerRegisterTimerExpireEvent(tickTrigger, tickTimer)
            call TriggerAddAction(tickTrigger, function ShieldEngine.ShieldTickLoop)

            // Remove a unit from system if it is unindex by UnitEvent
            call TriggerRegisterVariableEvent(unregisterTrigger, "udg_UnitIndexEvent", EQUAL, 2.00)
            call TriggerRegisterVariableEvent(unregisterTrigger, "udg_DeathEvent", EQUAL, 0.50)
            call TriggerRegisterVariableEvent(unregisterTrigger, "udg_DeathEvent", EQUAL, 1.00)
            call TriggerRegisterVariableEvent(unregisterTrigger, "udg_DeathEvent", EQUAL, 3.00)
            call TriggerAddAction(unregisterTrigger, function ShieldEngine.RemoveUnitFromSystem)

            // Disable triggers until a unit is registered
            call DisableTrigger(preReducTrigger)
            call DisableTrigger(postReducTrigger)
            call DisableTrigger(tickTrigger)

            /*
                ================================================
                            Struct members init      
                ================================================
            */

            set firstWielder = 0
            set lastWielder = 0
          
            /*
                ================================================
                                GUI VARIABLES      
                ================================================
            */
            static if USE_GUI then
                set udg_SHIELD_PROC_PREREDUC = SHIELD_PROC_PREREDUC
                set udg_SHIELD_PROC_POSTREDUC = SHIELD_PROC_POSTREDUC
      
                set udg_SHIELD_TYPE_HP_BASED = SHIELD_TYPE_HP_BASED
                set udg_SHIELD_TYPE_INSTANCE = SHIELD_TYPE_INSTANCE
      
                set udg_SHIELD_HP_TYPE_ALL = SHIELD_HP_TYPE_ALL
                set udg_SHIELD_HP_TYPE_SPELL = SHIELD_HP_TYPE_SPELL
                set udg_SHIELD_HP_TYPE_ATTACK = SHIELD_HP_TYPE_ATTACK
            endif
        endmethod

        public static method RemoveUnitFromSystem takes nothing returns nothing
            local integer uId = udg_UDex
            local ShieldWielder wielder

            if isInSystem[uId] then
                set wielder = unitToWielder[uId]

                call ShieldEngine.RemoveAllShields(wielder.wielder)
            endif
        endmethod

        public static method CheckRemoveWielder takes ShieldWielder wielder returns nothing
          
            if wielder.totalMaxShield == 0.00 /*
                */ and wielder.totalMaxSpellShield == 0.00 /*
                */ and wielder.totalMaxAttackShield == 0.00 /*
            */ then
                if wielder == firstWielder then
                    set firstWielder = wielder.next
                  
                    set isInSystem[GetUnitUserData(wielder.wielder)] = false
                    call wielder.destroy()

                    if firstWielder == 0 then
                        call DisableTrigger(preReducTrigger)
                        call DisableTrigger(postReducTrigger)
                        call DisableTrigger(tickTrigger)
                    endif
                else
                    set wielder.prev.next = wielder.next
                    set wielder.next.prev = wielder.prev

                    set isInSystem[GetUnitUserData(wielder.wielder)] = false
                    call wielder.destroy()

                endif
            endif
        endmethod

        /*
            ================================================
                            SYSTEM API       
            ================================================
        */

        // Tells if a unit is shielded
        public static method IsUnitShielded takes unit u returns boolean
            local integer uId = GetUnitUserData(u)

            return isInSystem[uId]
        endmethod

        // Tells if a unit is shielded AND if current total shield is greater than 0.
        public static method UnitHasActiveShield takes unit u returns boolean
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(u)

            return ShieldEngine.UnitHasActiveShieldOfType(u, SHIELD_HP_TYPE_ALL) /*
                */ or ShieldEngine.UnitHasActiveShieldOfType(u, SHIELD_HP_TYPE_ATTACK) /*
                */ or ShieldEngine.UnitHasActiveShieldOfType(u, SHIELD_HP_TYPE_SPELL)
        endmethod

        public static method UnitHasActiveShieldOfType takes unit u, integer shieldHpType returns boolean
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(u)

            if wielder == 0 then
                return false
            endif

            if shieldHpType == SHIELD_HP_TYPE_ALL then
                return wielder.totalCurrentShield >= 1.0
            elseif shieldHpType == SHIELD_HP_TYPE_ATTACK then
                return wielder.totalCurrentAttackShield >= 1.0
            else
                return wielder.totalCurrentSpellShield >= 1.0
            endif
        endmethod

        // Tells if unit has a specific shield
        public static method UnitHasShield takes unit u, string name returns boolean
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(u)

            return wielder.HasShield(name)
        endmethod

        /*
            The following methods remove/destroy shields.
            Removing: No Event if fired, shield disappear without any trace.
            Destroying: Shield Break Event is fired when destroying a shield.
        */
        public static method RemoveShieldByName takes unit u, string name returns nothing
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(u)

            call wielder.RemoveShieldByName(name)
        endmethod

        public static method DestroyShieldByName takes unit u, string name returns nothing
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(u)

            call wielder.DestroyShieldByName(name)
        endmethod

        public static method RemoveShieldsByType takes unit u, integer hpType returns nothing
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(u)

            call wielder.RemoveShieldsByType(hpType)
        endmethod

        public static method RemoveAllShields takes unit u returns nothing
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(u)

            call wielder.RemoveShieldsByType(SHIELD_HP_TYPE_ALL)
            call wielder.RemoveShieldsByType(SHIELD_HP_TYPE_SPELL)
            call wielder.RemoveShieldsByType(SHIELD_HP_TYPE_ATTACK)
        endmethod

        public static method DestroyShieldsByType takes unit u, integer hpType returns nothing
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(u)
          
            call wielder.DestroyShieldsByType(hpType)
        endmethod

        public static method DestroyAllShields takes unit u returns nothing
            local ShieldWielder wielder = ShieldEngine.GetUnitWielder(u)

            call wielder.DestroyShieldsByType(SHIELD_HP_TYPE_ALL)
            call wielder.DestroyShieldsByType(SHIELD_HP_TYPE_SPELL)
            call wielder.DestroyShieldsByType(SHIELD_HP_TYPE_ATTACK)
        endmethod

        /*
            ================================================
                            ENTRY POINT      
            ================================================
        */

        public static method CreateShield takes /*
            */ unit source, /*
            */ unit target, /*
            */ string name, /*
            */ string sfxPath, /*
            */ string attachPoint, /*
            */ integer shieldProcType, /*
            */ integer shieldType, /*
            */ integer shieldHpType, /*
            */ boolean isStackable, /*
            */ boolean isTimed, /*
            */ real duration, /*
            */ real HP, /*
            */ boolean breakAtZero, /*
            */ boolean canRegen, /*
            */ real timeBeforeRegen, /*
            */ real regenTime /*
        */ returns nothing
            local integer uId = GetUnitUserData(target)
            local ShieldWielder wielder
            local Shield shield

            if isInSystem[uId] then
                set wielder = unitToWielder[uId]
            else
                set wielder = ShieldWielder.create(target)
                set unitToWielder[uId] = wielder
                set isInSystem[uId] = true
                if firstWielder == 0 then
                    set firstWielder = wielder
                    set lastWielder = wielder

                    call EnableTrigger(preReducTrigger)
                    call EnableTrigger(postReducTrigger)
                    call EnableTrigger(tickTrigger)
                else
                    set wielder.prev = lastWielder
                    set lastWielder.next = wielder
                    set lastWielder = lastWielder.next
                endif
            endif

            // Check if shield with same name and types exist
            set shield = wielder.FindShieldByNameAndTypes(name, shieldProcType, shieldType, shieldHpType)

            // If didn't exist, create new one
            if shield == 0 then
                set shield = Shield.create(source, target, name, sfxPath, attachPoint, shieldProcType, shieldType, shieldHpType, isStackable, isTimed, duration, HP, breakAtZero, canRegen, timeBeforeRegen, regenTime)
              
                call ShieldEvent.FireEvent(SHIELD_EVENT_CREATED, shield)
            else
                // if both previous shield and new are stackable, then stack, else refresh
                if shield.isStackable and isStackable then
                    call shield.Stack(duration, HP)
                else
                    call shield.Refresh(duration, HP)
                endif
            endif

        endmethod

        /*
            ================================================
                        SIMPLIFIED FUNCTIONS
            ================================================
        */

        public static method CreateTimedShield takes unit source, unit target, string name, real duration, real HP returns nothing
            call ShieldEngine.CreateShield(source, target, name, SHIELD_DEFAULT_SFX_PATH, SHIELD_DEFAULT_ATTACH_POINT, SHIELD_PROC_POSTREDUC, SHIELD_TYPE_HP_BASED, SHIELD_HP_TYPE_ALL, false, true, duration, HP, true, false, 0.0, 0.0)
        endmethod

        public static method CreateInstanceShield takes unit source, unit target, string name, real HP returns nothing
            call ShieldEngine.CreateShield(source, target, name, SHIELD_DEFAULT_SFX_PATH, SHIELD_DEFAULT_ATTACH_POINT, SHIELD_PROC_PREREDUC, SHIELD_TYPE_INSTANCE, SHIELD_HP_TYPE_ALL, false, false, 0.0, HP, true, false, 0.0, 0.0)
        endmethod

    endstruct

    struct ShieldParameters
        public unit source
        public unit target
        public string name
        public string sfxPath
        public string attachPoint
        public integer shieldProcType
        public integer shieldType
        public integer shieldHpType
        public boolean isStackable
        public boolean isTimed
        public real duration
        public real HP
        public boolean breakAtZero
        public boolean canRegen
        public real timeBeforeRegen
        public real regenTime
  
        public method CreateShield takes nothing returns boolean
            if this.target != null and this.name != null then
                call ShieldEngine.CreateShield(this.source, this.target, this.name, this.sfxPath, this.attachPoint, this.shieldProcType, this.shieldType, this.shieldHpType, this.isStackable, this.isTimed, this.duration, this.HP, this.breakAtZero, this.canRegen, this.timeBeforeRegen, this.regenTime)
              
                return true
            endif

            return false
        endmethod
          
          
        public static method create takes nothing returns ShieldParameters
            local ShieldParameters this = ShieldParameters.allocate()

            set this.source             = null
            set this.target             = null
            set this.name               = null
            set this.sfxPath            = SHIELD_DEFAULT_SFX_PATH
            set this.attachPoint        = SHIELD_DEFAULT_ATTACH_POINT
            set this.shieldProcType     = SHIELD_PROC_POSTREDUC
            set this.shieldType         = SHIELD_TYPE_HP_BASED
            set this.shieldHpType       = SHIELD_HP_TYPE_ALL
            set this.isStackable        = false
            set this.isTimed            = false
            set this.duration           = 0.00
            set this.HP                 = 0.00
            set this.breakAtZero        = true
            set this.canRegen           = false
            set this.timeBeforeRegen    = 0.00
            set this.regenTime          = 0.00

            return this
        endmethod
    endstruct
endlibrary


Version 1.4.1 (2026-06-09)
Bug fixes:
  • Fix an issue where shield effect would not disappear when USE_GUI was set to false.

Version 1.4.0 (2026-06-08)
Bug fixes:
  • Fixed an issue of some damage instance not being absorbed by shields when the corresponding DamageEvent didn't have IsdamageAttack = true or IsDamageSpell = true.
  • The shield visual bar will now update when the shield regenerate its HPs.
  • Added a fail safe to make sure every shield bar is destroyed properly when a wielder is destroy.
New features:
  • Added support for pure vJass use of this system. You can now add callbacks functions to events using the following functions:
    • ShieldEvent.AddOnPreCreation
    • ShieldEvent.AddOnCreation
    • ShieldEvent.AddOnAbsorb
    • ShieldEvent.AddOnExpired
    • ShieldEvent.AddOnBreak
    • ShieldEvent.AddOnRefresh
    • ShieldEvent.AddOnStack
  • Added parameter USE_GUI (boolean), default is false. Set to true to enabled usage of GUI variables in event firing. If kept to false, feel free to delete the whole folder GUI Management folder.
  • Shield can now filter absorbed damage using attack types and damage types.
    • ShieldEvent.AddAttackType will add given attack type to the list of absorbed attack types. If never called, the shield will absorb all attack types.
    • ShieldEvent.ExcludeAttackType will make the shield unabled to absorb the given attack type.
    • ShieldEvent.AddDamageType will add given attack type to the list of absorbed damage types. If never called, the shield will absorb all damage types.
    • ShieldEvent.ExcludeDamageType will make the shield unabled to absorb the given damage type.
  • Added parameters NUMBER_OF_ATTACK_TYPE and NUMBER_OF_DAMAGE_TYPE. These are used to define the max size of arrays for attack and damage inclusion/exclusion. Default are the number of types in the DamageEngine.

Version 1.3.0 (2025-05-14)
Added a parameter structure with default values to make thing easier for Jass User.
How to use:
JASS:
set parameters = ShieldParameters.create()
set parameters.name = "YourName"
set parameters.target = yourTarget

call parameters.CreateShield()

Version 1.2.0 (2025-04-14)
Added:
  • GUI variables:
    • ShieldAbsorbedDamageSource - unit - Who did the last damage absorbed by this shield
    • ShieldBaseMaxHP - real - Initial max HP when shield was casted, before any modification
    • ShieldPrvMaxHP - real - Max HP at the end of previous step
    • ShieldBaseDuration - real - Initial duration when shield was casted, before any modification
    • ShieldPrvDuration - real - Duration at the end of previous step
  • Jass Functions:
    • ShieldEngine.UnitHasActiveShield - tells if a unit has at least on shield with current HP >= 1.0
    • ShieldEngine.UnitHasActiveShieldOfType - tells if a unit has at least on shield of given type with current HP >= 1.0
    • ShieldEngine.CreateInstanceShield - Simplified version for ShieldEngine.CreateShield with much less parameters for Instance shields.

Version 1.1.0 (2025-03-31)
Added:
  • API description
  • ShieldEngine.IsUnitShielded : tells if unit has at least one shield attached to them
  • ShieldEngine.UnitHasShield : tells if unit has a shield with given name
  • ShieldEngine.CreateTimedShield : Simplified version for ShieldEngine.CreateShield with much less parameters.
  • Triggers to use remove and destroy shield functions.

Bug fixes:
  • Fixed a bug where shield effect and shield HP bar would not be destroyed for Attack & Spell shields when shield was stacked.
  • Fixed a bug where shield HP bar would not correctly update when shield was stacked.
  • Fixed an issue where some functions would check shieldType instead of shieldProcType or shieldHpType.

Version 1.1.1

Bug fixes:
- Units and their shields are now properly removed from the system if they're unindex by UnitEvent.


Version 1.0.0 (2025-03-28)
First upload.

Credits to Vinz for his Sacred Guard model used in demo map.


Keywords:
shield, shielding, shield event, shield engine, EVENT_PLAYER_UNIT_SHIELDED
Previews
Contents

Shield Engine 1.3.0 (Map)

Shield Engine 1.4.1 (Map)

Reviews
Antares
Yep, that works perfectly. Well-made system, well documented, easy to use. High Quality
Not to burst your bubble but you've got tough competition for a shield system (not mine) already GUI Friendly Shield System 1.00b
Oh, why on earth did it not come up in my search result when I tried to look for something like this ? :confused:2
All I could find was some really outdated systems. Guess I'm blind :grin:

Thanks for linking it, I'll look and see if it covers the same things (and how it's probably more optimized than mine).
 
Found a few things to fix.


Version 1.1.0


Added:
  • API description
  • ShieldEngine.IsUnitShielded : tells if unit has at least one shield attached to them
  • ShieldEngine.UnitHasShield : tells if unit has a shield with given name
  • ShieldEngine.CreateTimedShield : Simplified version for ShieldEngine.CreateShield with much less parameters.
  • Triggers to use remove and destroy shield functions.

Bug fixes:
  • Fixed a bug where shield effect and shield HP bar would not be destroyed for Attack & Spell shields when shield was stacked.
  • Fixed a bug where shield HP bar would not correctly update when shield was stacked.
  • Fixed an issue where some functions would check shieldType instead of shieldProcType or shieldHpType.

Version 1.1.1

Bug fixes:
- Units and their shields are now properly removed from the system if they're unindex by UnitEvent.

 
Last edited:
After playing with the system a bit, I added a few things that were needed.
Updated this thread to detail Jass API.

Version 1.2.0

Added:
  • GUI variables:
    • ShieldAbsorbedDamageSource - unit - Who did the last damage absorbed by this shield
    • ShieldBaseMaxHP - real - Initial max HP when shield was casted, before any modification
    • ShieldPrvMaxHP - real - Max HP at the end of previous step
    • ShieldBaseDuration - real - Initial duration when shield was casted, before any modification
    • ShieldPrvDuration - real - Duration at the end of previous step
  • Jass Functions:
    • ShieldEngine.UnitHasActiveShield - tells if a unit has at least one shield with current HP >= 1.0
    • ShieldEngine.UnitHasActiveShieldOfType - tells if a unit has at least one shield of given type with current HP >= 1.0
    • ShieldEngine.CreateInstanceShield - Simplified version for ShieldEngine.CreateShield with much less parameters for Instance shields.
 
This is very well-written and great for GUI users. The only gripe I have is that the API for non-GUI users could be better. Instead of a function with 15 input arguments, you could make an input data struct which you can create and then set the fields and overwrite the various default values.

If you're feeling really adventurous, you could make function interface fields for onAbsorb, onBreak etc. callbacks that replace the GUI event variables.
 
This is very well-written and great for GUI users.
Well, thanks a lot.

The only gripe I have is that the API for non-GUI users could be better. Instead of a function with 15 input arguments, you could make an input data struct which you can create and then set the fields and overwrite the various default values.
API for non-GUI users bothered me a bit too, which is why I started to create some simplified versions of ShieldEngine.CreateShield
I didn't think of using a struct for parameters, and probably would be better indeed.

Is something like the following what you would be looking for ?

JASS:
struct ShieldParameters
        public unit source
        public unit target
        public string name
        public string sfxPath
        public string attachPoint
        public integer shieldProcType
        public integer shieldType
        public integer shieldHpType
        public boolean isStackable
        public boolean isTimed
        public real duration
        public real HP
        public boolean breakAtZero
        public boolean canRegen
        public real timeBeforeRegen
        public real regenTime
  
        public method CreateShield takes nothing returns boolean
            if this.target != null and this.name != null then
                call ShieldEngine.CreateShield(this.source, this.target, this.name, this.sfxPath, this.attachPoint, this.shieldProcType, this.shieldType, this.shieldHpType, this.isStackable, this.isTimed, this.duration, this.HP, this.breakAtZero, this.canRegen, this.timeBeforeRegen, this.regenTime)
              
                return true
            endif
            return false
        endmethod
          
          
        public static method create takes nothing returns ShieldParameters
            local ShieldParameters this = ShieldParameters.allocate()
            set this.source             = null
            set this.target             = null
            set this.name               = null
            set this.sfxPath            = SHIELD_DEFAULT_SFX_PATH
            set this.attachPoint        = SHIELD_DEFAULT_ATTACH_POINT
            set this.shieldProcType     = SHIELD_PROC_POSTREDUC
            set this.shieldType         = SHIELD_TYPE_HP_BASED
            set this.shieldHpType       = SHIELD_HP_TYPE_ALL
            set this.isStackable        = false
            set this.isTimed            = false
            set this.duration           = 0.00
            set this.HP                 = 0.00
            set this.breakAtZero        = true
            set this.canRegen           = false
            set this.timeBeforeRegen    = 0.00
            set this.regenTime          = 0.00
            return this
        endmethod
        public method destroy takes nothing returns nothing
            call this.deallocate()
        endmethod
    endstruct


If you're feeling really adventurous, you could make function interface fields for onAbsorb, onBreak etc. callbacks that replace the GUI event variables.

This does sound great, but also a lot more work to make it work, so not planned for now :grin:
Might look into it later on tho.
 
Last edited:
My vJASS knowledge is a bit rusty, but I think your destroy method is redundant because deallocate is the default destroy method already.
Indeed. I just took the habit to write create and destroy method on every struct so I do not forget. But I removed it there because it wasn't needed.

Version 1.3.0

Added a parameter structure with default values to make things easier for Jass User.
How to use:
JASS:
set parameters = ShieldParameters.create()
set parameters.name = "YourName"
set parameters.target = yourTarget

call parameters.CreateShield()

You can also catch the result of parameters.CreateShield().
It'll return true if the shield creation has been called, or false if name or target has not been set.
 
Last edited:
Hello! Is it possible to replace the CustomBar with a Shield Current HP / Shield Max HP number display?
 
I did some tests and found bugs. In 95% of cases they are only visual, but a couple of times I encountered the fact that the shield was not applied at all.

(what is shown in the picture never disappears, I achieved this by spamming the spell for 3 minutes)

In general, if you don't abuse it, it works well. I just always want to see an ideal system))

By the way, if you make the shield stackable, then the bugs seem to disappear.
 

Attachments

Last edited:
Hello! Is it possible to replace the CustomBar with a Shield Current HP / Shield Max HP number display?

Currently, no. But this might be a good idea indeed, so I'll probably add this in a future version.

I did some tests and found bugs. In 95% of cases they are only visual, but a couple of times I encountered the fact that the shield was not applied at all.

(what is shown in the picture never disappears, I achieved this by spamming the spell for 3 minutes)

In general, if you don't abuse it, it works well. I just always want to see an ideal system))

By the way, if you make the shield stackable, then the bugs seem to disappear.

Well, I guess I did not stress tested enough :grin:
The is weird indeed. I'll need to test this more to see how I can prevent stuff like that.
 
Currently, no. But this might be a good idea indeed, so I'll probably add this in a future version.



Well, I guess I did not stress tested enough :grin:
The is weird indeed. I'll need to test this more to see how I can prevent stuff like that.
there is a simple stress test. Place 500 units on the map, and trigger it to put a shield on each unit selected in the zone of the game map. When I did this, something happened to something.
 
Dang, i ended up doing a little shield system, how i did not see this?

lol, it's amazing, congrats!

I am posting it in case someone would need it

The shield bar is dope, i think i will implement that aswell.

JASS:
scope ShieldAdd initializer Init_ShieldAdd

globals
    hashtable ShieldTable = null
    constant string DEFAULT_SHIELD_SFX = ""
    constant string DEFAULT_SHIELD_ATTACH = "chest"
    constant real TEXTTAG_HEIGHT = 80.0
    constant real UPDATE_PERIOD = 0.06

    
    constant integer SHIELD_EXPLOSION_DMG = 10
    constant integer SHIELD_EXPLOSION_AOE = 11
    constant integer SHIELD_EXPLOSION_SFX = 12
    constant integer SHIELD_HAS_SHIELD = 20
endglobals

// ========== REMOVE SHIELD (cleanup all effects/tags/timers) ==========
function RemoveShield takes unit u returns nothing
    local integer id = GetHandleId(u)
    local texttag tag = LoadTextTagHandle(ShieldTable, id, 1)
    local timer t = LoadTimerHandle(ShieldTable, id, 2)
    local effect fx = LoadEffectHandle(ShieldTable, id, 3)
    call BJDebugMsg("RemoveShield: removing shield from " + GetUnitName(u) + " (id " + I2S(id) + ")")
    if tag != null then
        call DestroyTextTag(tag)
        call SaveTextTagHandle(ShieldTable, id, 1, null)
        call BJDebugMsg("RemoveShield: destroyed tag")
    endif
    if t != null then
        call PauseTimer(t)
        call DestroyTimer(t)
        call SaveTimerHandle(ShieldTable, id, 2, null)
        call BJDebugMsg("RemoveShield: destroyed timer")
    endif
    if fx != null then
        call DestroyEffect(fx)
        call SaveEffectHandle(ShieldTable, id, 3, null)
        call BJDebugMsg("RemoveShield: destroyed effect")
    endif
    call SaveBoolean(ShieldTable, id, SHIELD_HAS_SHIELD, false)
    call FlushChildHashtable(ShieldTable, id)
    call BJDebugMsg("RemoveShield: flushed hashtable for id " + I2S(id))
endfunction


// ========== EXPLOSION ON EXPIRE/BREAK ==========
function ShieldAdd_Explosion takes unit u returns nothing
    local integer id = GetHandleId(u)
    local real aoe = LoadReal(ShieldTable, id, SHIELD_EXPLOSION_AOE)
    local real dmg = LoadReal(ShieldTable, id, SHIELD_EXPLOSION_DMG)
    local string sfx = LoadStr(ShieldTable, id, SHIELD_EXPLOSION_SFX)
    local group g
    local unit v
    if dmg > 0 and aoe > 0 then
        set g = CreateGroup()
       // call BJDebugMsg("ShieldAdd_Explosion: Exploding shield on unit id " + I2S(id) + ", aoe=" + R2S(aoe) + ", dmg=" + R2S(dmg))
        call GroupEnumUnitsInRange(g, GetUnitX(u), GetUnitY(u), aoe, null)
        loop
            set v = FirstOfGroup(g)
            exitwhen v == null
            call GroupRemoveUnit(g, v)
            if IsUnitEnemy(v, GetOwningPlayer(u)) and UnitAlive(v) then
              //  call BJDebugMsg("ShieldAdd_Explosion: Damaging " + GetUnitName(v) + " for " + R2S(dmg))
                call UnitDamageTarget(u, v, dmg, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
                if sfx != "" then
                    call DestroyEffect(AddSpecialEffectTarget(sfx, v, "chest"))
                endif
            endif
        endloop
        call DestroyGroup(g)
    elseif sfx != "" then
        call DestroyEffect(AddSpecialEffect(sfx, GetUnitX(u), GetUnitY(u)))
       // call BJDebugMsg("ShieldAdd_Explosion: Only played SFX (no aoe/dmg)")
    else
     //   call BJDebugMsg("ShieldAdd_Explosion: No explosion (dmg/aoe not set)")
    endif
endfunction

// ========== UPDATE SHIELD TEXT (arcing tag, updates every tick) ==========
function ShieldAdd_Shield_Update takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit u = LoadUnitHandle(ShieldTable, GetHandleId(t), 0)
    local integer id
    local real shield
    local real remaining
    local string text
    // call BJDebugMsg("UPDATE SHIELD PERIODIC RUN")
    if u == null then
        // call BJDebugMsg("UPDATE: unit is null, destroying timer")
        call DestroyTimer(t)
        return
    endif
    set id = GetHandleId(u)
    set shield = LoadReal(ShieldTable, id, 0)
    set remaining = LoadReal(ShieldTable, id, 4)
    set remaining = remaining - UPDATE_PERIOD // decrement duration
    call SaveReal(ShieldTable, id, 4, remaining)
    // call BJDebugMsg("UPDATE: shield=" + R2S(shield) + ", remaining=" + R2S(remaining))
    set text = "|c00A0FFFFShield: " + I2S(R2I(shield)) + "|r"
    if remaining > 0.0 then
        set text = text + " (" + I2S(R2I(remaining + 0.999)) + "s)"
    endif
    //call ArcingTextTag.createEx(text, u, 0.9, 1.0, GetOwningPlayer(u))
    if shield > 0.0 and remaining > 0.0 then
        call TimerStart(t, UPDATE_PERIOD, false, function ShieldAdd_Shield_Update)
    else
      //  call BJDebugMsg("ShieldAdd: shield expired or depleted on " + GetUnitName(u))
        call ArcingTextTag.createEx("|cffbbbbff[Shield expired]|r", u, 1.1, 1.2, GetOwningPlayer(u))
        call ShieldAdd_Explosion(u)
        call RemoveShield(u)
    endif
endfunction

// ========== EXPIRE SHIELD (by timer, force remove) ==========
function ShieldAdd_Shield_Expire takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit u = LoadUnitHandle(ShieldTable, GetHandleId(t), 0)
    // call BJDebugMsg("Expire function called, removing shield")
    call ShieldAdd_Explosion(u)
    call RemoveShield(u)
endfunction

// ========== GIVE SHIELD, with sfx and attach, and explosion support ==========
function GiveShieldSFXEx takes unit u, real amount, real duration, string sfx, string attach, real explosionDmg, real explosionAoe, string explosionSfx, boolean forceOverwrite returns nothing
    local integer id = GetHandleId(u)
    local timer t
    local effect fx = null

    if not forceOverwrite and LoadBoolean(ShieldTable, id, SHIELD_HAS_SHIELD) then
        //call BJDebugMsg("GiveShieldSFXEx: unit " + GetUnitName(u) + " (id " + I2S(id) + ") already has a shield, new shield not applied.")
        return
    endif

    call RemoveShield(u) // Always remove before applying
   // call BJDebugMsg("GiveShieldSFXEx: applying shield to " + GetUnitName(u) + " (id " + I2S(id) + ") (forceOverwrite=" + I2S(B2I(forceOverwrite)) + ")")
    call SaveBoolean(ShieldTable, id, SHIELD_HAS_SHIELD, true)
    call SaveReal(ShieldTable, id, 0, amount)
    call SaveUnitHandle(ShieldTable, id, 0, u)
    if sfx != "" then
        set fx = AddSpecialEffectTarget(sfx, u, attach)
        call SaveEffectHandle(ShieldTable, id, 3, fx)
    endif
    call SaveReal(ShieldTable, id, 4, duration)
    call SaveReal(ShieldTable, id, SHIELD_EXPLOSION_DMG, explosionDmg)
    call SaveReal(ShieldTable, id, SHIELD_EXPLOSION_AOE, explosionAoe)
    call SaveStr(ShieldTable, id, SHIELD_EXPLOSION_SFX, explosionSfx)
    set t = CreateTimer()
    call SaveTimerHandle(ShieldTable, id, 2, t)
    call SaveUnitHandle(ShieldTable, GetHandleId(t), 0, u)
    call TimerStart(t, UPDATE_PERIOD, false, function ShieldAdd_Shield_Update)
endfunction



// Old interface for backward compatibility (no explosion)
function GiveShieldSFX takes unit u, real amount, real duration, string sfx, string attach returns nothing
    call GiveShieldSFXEx(u, amount, duration, sfx, attach, 0.0, 0.0, "", false)
endfunction

// ========== GIVE SHIELD (direct), duration and optional SFX ==========
function GiveShield takes unit u, real amount, real duration returns nothing
    call GiveShieldSFX(u, amount, duration, DEFAULT_SHIELD_SFX, DEFAULT_SHIELD_ATTACH)
endfunction

// ========== GIVE SHIELD ONLY IF BUFF PRESENT ==========
function GiveShieldWithBuff takes unit u, real amount, real duration, integer buffid returns nothing
    if GetUnitAbilityLevel(u, buffid) > 0 then
        call GiveShield(u, amount, duration)
    endif
endfunction

// ========== DAMAGE ENGINE: SHIELD ABSORB (to be registered on udg_DamageModifierEvent) ==========
function ShieldAdd_OnShieldDamage takes nothing returns nothing
    local unit u = udg_DamageEventTarget
    local integer id = GetHandleId(u)
    local real shield = LoadReal(ShieldTable, id, 0)
    local real dmg = udg_DamageEventAmount
    // call BJDebugMsg("OnShieldDamage: shield=" + R2S(shield) + ", dmg=" + R2S(dmg))
    if shield > 0.0 then
        if dmg >= shield then
            set udg_DamageEventAmount = dmg - shield // pass overkill
            // call BJDebugMsg("OnShieldDamage: shield broken!")
            call ShieldAdd_Explosion(u)
            call RemoveShield(u)
            call ArcingTextTag.createEx("|c00FF4040[Shield Broken]|r", u, 1.0, 1.0, GetOwningPlayer(u))
        else
            set udg_DamageEventAmount = 0.0
            set shield = shield - dmg
            call SaveReal(ShieldTable, id, 0, shield)
            // call BJDebugMsg("OnShieldDamage: shield reduced, new value=" + R2S(shield))
            call ArcingTextTag.createEx("|c00A0FFFFShield: " + I2S(R2I(shield)) + "|r", u, 0.9, 1.0, GetOwningPlayer(u))
        endif
    endif
endfunction

// ========== GIVE SHIELD USING AbiDamage (all params, supports explosion/overwrite) ==========

function DOSHIELD takes unit source, unit target, real flat, real lvlScale, real statScale, real adScale, real armorScale, real hpScale, real curhpScale, real manaScale, real curmanaScale, real msScale, real asScale, real perStack, boolean showDebug, integer abilityId, real duration, string sfx, string attach, real explosionDmg, real explosionAoe, string explosionSfx, boolean forceOverwrite returns nothing
    local real shieldAmount = AbiDamage(source, target, flat, lvlScale, statScale, adScale, armorScale, hpScale, curhpScale, manaScale, curmanaScale, msScale, asScale, perStack, showDebug, abilityId)
    call GiveShieldSFXEx(target, shieldAmount, duration, sfx, attach, explosionDmg, explosionAoe, explosionSfx, forceOverwrite)
endfunction

function DOAOESHIELD takes unit source, real x, real y, real radius, real flat, real lvlScale, real statScale, real adScale, real armorScale, real hpScale, real curhpScale, real manaScale, real curmanaScale, real msScale, real asScale, real perStack, boolean showDebug, integer abilityId, real duration, string sfx, string attach returns nothing
    local group g = CreateGroup()
    local unit v
    local real amt
    call GroupEnumUnitsInRange(g, x, y, radius, null)
    loop
        set v = FirstOfGroup(g)
        exitwhen v == null
        call GroupRemoveUnit(g, v)
        if IsUnitAlly(v, GetOwningPlayer(source)) and UnitAlive(v) then
            set amt = AbiDamage(source, v, flat, lvlScale, statScale, adScale, armorScale, hpScale, curhpScale, manaScale, curmanaScale, msScale, asScale, perStack, showDebug, abilityId)
            call GiveShieldSFX(v, amt, duration, sfx, attach)
        endif
    endloop
    call DestroyGroup(g)
endfunction

// ========== INITIALIZER ==========
private function Init_ShieldAdd takes nothing returns nothing
    local trigger t = CreateTrigger()
    set ShieldTable = InitHashtable()
    call TriggerRegisterVariableEvent(t, "udg_DamageModifierEvent", EQUAL, 1)
    call TriggerAddAction(t, function ShieldAdd_OnShieldDamage)
    // call BJDebugMsg("Init_ShieldAdd called")
endfunction

private function Init takes nothing returns nothing
    call Init_ShieldAdd()
    // call BJDebugMsg("ShieldAdd: system fully initialized.")
endfunction
endscope
 
Okay, I fixed all the visual bugs by making some adjustments to the code.

I replaced:



JASS:
        public static method RemoveUnitFromSystem takes nothing returns nothing

            local integer uId = udg_UDex

            local ShieldWielder wielder



            if isInSystem[uId] then

                set wielder = unitToWielder[uId]



                call ShieldEngine.RemoveAllShields(wielder.wielder)

            endif

        endmethod



With this for confidence.



JASS:
public static method RemoveUnitFromSystem takes nothing returns nothing

       local integer uId = udg_UDex

       local ShieldWielder wielder

       local Shield shield



       if isInSystem[uId] then

           set wielder = unitToWielder[uId]

       

 

           set shield = wielder.firstPreReduc

           loop

               exitwhen shield == 0

               if shield.visualEffect != null then

                   call DestroyEffect(shield.visualEffect)

                   set shield.visualEffect = null

               endif

               set shield = shield.next

           endloop

       

    

           call ShieldEngine.RemoveAllShields(wielder.wielder)

       endif

   endmethod



And this:



JASS:
public method RemoveShield takes Shield shield returns nothing

            if shield == this.firstPreReduc then

                set this.firstPreReduc = shield.next

            elseif shield == this.firstPostReduc then

                set this.firstPostReduc = shield.next

            elseif shield == this.firstPreReducAttack then

                set this.firstPreReducAttack = shield.next

            elseif shield == this.firstPostReducAttack then

                set this.firstPostReducAttack = shield.next

            elseif shield == this.firstPreReducSpell then

                set this.firstPreReducSpell = shield.next

            elseif shield == this.firstPostReducSpell then

                set this.firstPostReducSpell = shield.next

            endif



            if shield == this.lastPreReduc then

                set this.lastPreReduc = shield.prev

            elseif shield == this.lastPostReduc then

                set this.lastPostReduc = shield.prev

            elseif shield == this.lastPreReducAttack then

                set this.lastPreReducAttack = shield.prev

            elseif shield == this.lastPostReducAttack then

                set this.lastPostReducAttack = shield.prev

            elseif shield == this.lastPreReducSpell then

                set this.lastPreReducSpell = shield.prev

            elseif shield == this.lastPostReducSpell then

                set this.lastPostReduc = shield.prev

            endif



            if shield.prev != 0 then

                set shield.prev.next = shield.next

            endif

            if shield.next != 0 then

                set shield.next.prev = shield.prev

            endif

        

            call this.AdjustShieldBars("remove", shield)

            call ShieldEngine.CheckRemoveWielder(this)

        endmethod



On this one:



JASS:
        public method RemoveShield takes Shield shield returns nothing



    if shield.visualEffect != null then

        call DestroyEffect(shield.visualEffect)

        set shield.visualEffect = null

    endif



    if shield == this.firstPreReduc then

        set this.firstPreReduc = shield.next

    elseif shield == this.firstPostReduc then

        set this.firstPostReduc = shield.next

    elseif shield == this.firstPreReducAttack then

        set this.firstPreReducAttack = shield.next

    elseif shield == this.firstPostReducAttack then

        set this.firstPostReducAttack = shield.next

    elseif shield == this.firstPreReducSpell then

        set this.firstPreReducSpell = shield.next

    elseif shield == this.firstPostReducSpell then

        set this.firstPostReducSpell = shield.next

    endif



    if shield == this.lastPreReduc then

        set this.lastPreReduc = shield.prev

    elseif shield == this.lastPostReduc then

        set this.lastPostReduc = shield.prev

    elseif shield == this.lastPreReducAttack then

        set this.lastPreReducAttack = shield.prev

    elseif shield == this.lastPostReducAttack then

        set this.lastPostReducAttack = shield.prev

    elseif shield == this.lastPreReducSpell then

        set this.lastPreReducSpell = shield.prev

    elseif shield == this.lastPostReducSpell then

        set this.lastPostReduc = shield.prev

    endif



    if shield.prev != 0 then

        set shield.prev.next = shield.next

    endif

    if shield.next != 0 then

        set shield.next.prev = shield.prev

    endif

 

    call this.AdjustShieldBars("remove", shield)



    call ShieldEngine.CheckRemoveWielder(this)

endmethod



There are still some visual bugs, but at least they disappear after the shield wears off, which is good news.
And now I can cast a shield on 100 units at once.
 
Last edited:
Well spotted, I indeed forgot to destroy shields when the shield is remove silently (without event) :jd:
Which, in turn, doesn't destroy the attached effect.
This will probably cause some memory leak too, as the shield structure is not in the system anymore and yet not destroyed.

You should only need to change the ShieldWielder.RemoveShieldsByType function tho.
Adding call shield.destroy() after removing the shield.
This should destroy the struct and the associated effect.

vJASS:
public method RemoveShieldsByType takes integer hpType returns nothing
        local Shield shield

        if hpType == SHIELD_HP_TYPE_ALL then
            set shield = this.firstPreReduc
        elseif hpType == SHIELD_HP_TYPE_ATTACK then
            set shield = this.firstPreReducAttack
        elseif hpType == SHIELD_HP_TYPE_SPELL then
            set shield = this.firstPreReducSpell
        endif

        loop
            exitwhen shield == 0

            call this.RemoveShield(shield)
            call shield.destroy()

            set shield = shield.next
        endloop

        if hpType == SHIELD_HP_TYPE_ALL then
            set shield = this.firstPostReduc
        elseif hpType == SHIELD_HP_TYPE_ATTACK then
            set shield = this.firstPostReducAttack
        elseif hpType == SHIELD_HP_TYPE_SPELL then
            set shield = this.firstPostReducSpell
        endif

        loop
            exitwhen shield == 0

            call this.RemoveShield(shield)
            call shield.destroy()

            set shield = shield.next
        endloop
endmethod

I'll post a new version when I'll have more time to test a bit more (after the techtree contest most likely).
In the meantime, feel free to point out other cases of failure / bugs :grin:
 
Well spotted, I indeed forgot to destroy shields when the shield is remove silently (without event) :jd:
Which, in turn, doesn't destroy the attached effect.
This will probably cause some memory leak too, as the shield structure is not in the system anymore and yet not destroyed.

You should only need to change the ShieldWielder.RemoveShieldsByType function tho.
Adding call shield.destroy() after removing the shield.
This should destroy the struct and the associated effect.

vJASS:
public method RemoveShieldsByType takes integer hpType returns nothing
        local Shield shield

        if hpType == SHIELD_HP_TYPE_ALL then
            set shield = this.firstPreReduc
        elseif hpType == SHIELD_HP_TYPE_ATTACK then
            set shield = this.firstPreReducAttack
        elseif hpType == SHIELD_HP_TYPE_SPELL then
            set shield = this.firstPreReducSpell
        endif

        loop
            exitwhen shield == 0

            call this.RemoveShield(shield)
            call shield.destroy()

            set shield = shield.next
        endloop

        if hpType == SHIELD_HP_TYPE_ALL then
            set shield = this.firstPostReduc
        elseif hpType == SHIELD_HP_TYPE_ATTACK then
            set shield = this.firstPostReducAttack
        elseif hpType == SHIELD_HP_TYPE_SPELL then
            set shield = this.firstPostReducSpell
        endif

        loop
            exitwhen shield == 0

            call this.RemoveShield(shield)
            call shield.destroy()

            set shield = shield.next
        endloop
endmethod

I'll post a new version when I'll have more time to test a bit more (after the techtree contest most likely).
In the meantime, feel free to point out other cases of failure / bugs :grin:
My correction = it's a crutch. So I'm waiting for a new quality version! :thumbs_up:

btw
Besides the visuals, what confuses me is that when I cast a mass shield on my army fighting another army, for some reason my hero's shield gradually gets removed, as if he is being attacked, even though he is not in combat.

Moreover, it is removed very slowly, since my army has 1000 armor. Apparently, my hero is indexed as one of the warriors in the army for some reason.
 
Last edited:
I noticed something else: the mana shield does not work with the physical shield. In short, manashit is taken into account first.
Because Damagearmor doesn't stack with Manashield. I changed it to Predamage and everything became normal, but now you also need to take armor into account, in short, yeah...
 
Last edited:
Sometimes, once every 100 uses spell "mass-shield", I get the bar to get stuck on one spot instead of sticking to the unit. I dug into the code a bit and found something. call this.shieldBar.Show and call this.shieldBar.showCB() should be on cb.Show and cb.showCB() to properly initialize and start updating the bar. I could be wrong.
 
Sometimes, once every 100 uses spell "mass-shield", I get the bar to get stuck on one spot instead of sticking to the unit. I dug into the code a bit and found something. call this.shieldBar.Show and call this.shieldBar.showCB() should be on cb.Show and cb.showCB() to properly initialize and start updating the bar. I could be wrong.

Nice catch !
Something I forgot to change when I added support for multiple shield type :hohum:


I'll add that to next version :thumbs_up:
 
By the way, I previously found another bug with the shield bar: when activating any spells, even enemy ones, the bar can suddenly appear and get stuck in the air forever (this happens very rarely). I made a few more changes for stability, after which this bug probably disappeared. Perhaps you will take note of something from this.

JASS:
public method destroy takes nothing returns nothing
    if this.shieldBar != 0 then
        call this.shieldBar.destroy()
        set this.shieldBar = 0
    endif
    if this.spellShieldBar != 0 then
        call this.spellShieldBar.destroy()
        set this.spellShieldBar = 0
    endif
    if this.attackShieldBar != 0 then
        call this.attackShieldBar.destroy()
        set this.attackShieldBar = 0
    endif
    call this.deallocate()
endmethod

The point is that this will add a "call to remove the bar" (destroy bar) exactly where the system finally clears the unit of shields. After that, the bar should not "resurrect" when casting spells, since the CustomBar resources will be completely freed.
 
By the way, I previously found another bug with the shield bar: when activating any spells, even enemy ones, the bar can suddenly appear and get stuck in the air forever (this happens very rarely). I made a few more changes for stability, after which this bug probably disappeared. Perhaps you will take note of something from this.

JASS:
public method destroy takes nothing returns nothing
    if this.shieldBar != 0 then
        call this.shieldBar.destroy()
        set this.shieldBar = 0
    endif
    if this.spellShieldBar != 0 then
        call this.spellShieldBar.destroy()
        set this.spellShieldBar = 0
    endif
    if this.attackShieldBar != 0 then
        call this.attackShieldBar.destroy()
        set this.attackShieldBar = 0
    endif
    call this.deallocate()
endmethod

The point is that this will add a "call to remove the bar" (destroy bar) exactly where the system finally clears the unit of shields. After that, the bar should not "resurrect" when casting spells, since the CustomBar resources will be completely freed.

Damn, you have been hunting bugs like crazy :grin:
I guess that destroy method is for ShieldWielder structure ?

Hmm. There shouldn't be a need to do clear that, as a ShieldWielder instance should be destroyed if and only if all of its shields are removed :con:
Guess I'll add that to the list of things to check :xxd:
 
Damn, you have been hunting bugs like crazy :grin:
I guess that destroy method is for ShieldWielder structure ?

Hmm. There shouldn't be a need to do clear that, as a ShieldWielder instance should be destroyed if and only if all of its shields are removed :con:
Guess I'll add that to the list of things to check :xxd:
Yes, this change did not fix the problem. The bars still occasionally randomly appear on units, even when the shield spell is not activated. Then, when new zits are applied to these units, the bars are forever stuck in the air. If only there was a trigger that, when a new bar is applied, checks if the old one is there and removes it... Yes, it's a crutch, but it will remove visual bugs from the game.
 
Last edited:
Bug report. Visual. Dunno if this got fixed already by any of the stuff people said above, but I didn't see anyone mention this in particular.

Looks like the bar doesn't refill properly for HP Based shields that regenerate. At first I thought that HP shields just couldn't regenerate at all, but it seems they do, just without showing it. I redownloaded the demo map and changed literally nothing except making Magic Shield be HP Based instead of Instanced, and changed its HP to be 101 instead of 3, then I cast it on a guy and hit that guy with one frost bolt, then another. The shield bar went down to almost nothing on the first hit and stayed there, then went down to literally empty on the second hit, but the shield wasn't destroyed.
1775107307074.png

1775107358578.png
 
Hey bro, I'm liking your shield system. I've tried it out, and I like that it's easy to use, but I have a question... Can I add new types of shields, bro?
 
Bug report. Visual. Dunno if this got fixed already by any of the stuff people said above, but I didn't see anyone mention this in particular.

Looks like the bar doesn't refill properly for HP Based shields that regenerate. At first I thought that HP shields just couldn't regenerate at all, but it seems they do, just without showing it. I redownloaded the demo map and changed literally nothing except making Magic Shield be HP Based instead of Instanced, and changed its HP to be 101 instead of 3, then I cast it on a guy and hit that guy with one frost bolt, then another. The shield bar went down to almost nothing on the first hit and stayed there, then went down to literally empty on the second hit, but the shield wasn't destroyed.
View attachment 582999
View attachment 583000
Will check that as soon I have time to work on this again.:thumbs_up:

Hey bro, I'm liking your shield system. I've tried it out, and I like that it's easy to use, but I have a question... Can I add new types of shields, bro?
There isn't at the moment a way to configure an other type of shield atm, but the system cover many types of shields and usages.
What kind of shield are you thinking about that can't be done with it ? :confused:2
 
Will check that as soon I have time to work on this again.:thumbs_up:


There isn't at the moment a way to configure an other type of shield atm, but the system cover many types of shields and usages.
¿En qué tipo de escudo estás pensando que no se puede hacer con él? :confused:2
I'm honestly switching everything to VAJSS. I already have the damage engine and the unitevent; I just need your shield system, but that's no problem.

But I'm delving into modifying its system, and I'm studying it to create new shields that protect against a certain type of damage, or that prevent others from reducing a certain type of damage.



Bro, I just don't like the GUI because of the GUI variables, so I'll switch your system to vJass; it's more convenient, cleaner, and more organized.
 
I'm honestly switching everything to VAJSS. I already have the damage engine and the unitevent; I just need your shield system, but that's no problem.

But I'm delving into modifying its system, and I'm studying it to create new shields that protect against a certain type of damage, or that prevent others from reducing a certain type of damage.
Hmm, I probably could add a field containing a list of damage type absorbed by the shield and functions to add/remove a damage type to a shield without making the system a complete mess. I'll look into this.

Bro, I just don't like the GUI because of the GUI variables, so I'll switch your system to vJass; it's more convenient, cleaner, and more organized.
I don't know how much you've done in you project already, but it feels like you should jump to lua. From what I have seen, it will be much cleaner for you :grin:

Anyway, I will try to make a pure vJass version where you don't need any GUI variable.
 
Hmm, I probably could add a field containing a list of damage type absorbed by the shield and functions to add/remove a damage type to a shield without making the system a complete mess. I'll look into this.


I don't know how much you've done in you project already, but it feels like you should jump to lua. From what I have seen, it will be much cleaner for you :grin:

Anyway, I will try to make a pure vJass version where you don't need any GUI variable.
Bro, there's a big problem. It doesn't reduce external damage from detonators. In this case, damage that isn't magical. I don't know if that's because your system is designed that way.


It only seems to reduce damage coming from the damage engine. The strange thing is, damage engine events should reduce all filtered damage... Yours only reduces damage from the damage engine... That's where the shield system is poorly implemented, bro. You need to allow all damage to interact with your shield system.

1779823267693.webp
 
Bro, there's a big problem. It doesn't reduce external damage from detonators. In this case, damage that isn't magical. I don't know if that's because your system is designed that way.


It only seems to reduce damage coming from the damage engine. The strange thing is, damage engine events should reduce all filtered damage... Yours only reduces damage from the damage engine... That's where the shield system is poorly implemented, bro. You need to allow all damage to interact with your shield system.

View attachment 592257
From what I see from your screen, you're calling the damage function:
  • Unit - Cause (triggering unit) to damage (Target unit of ability being cast), dealing 500.0 damage of attack type Hero and damage type Normal
BEFORE calling the shield creation
  • Trigger - Run CreateShield Complete <gen> (ignoring condition)
So I guess it is normal that it isn't absorbed ? :confused:2
 
1779830717611.webp

It still doesn't reduce external damage... Bro, the whole point of a shield system is to protect against damage. And Damage Engine filters out all types of damage, but with its system, it ignores external damage... where it shouldn't. It should absorb all damage...

It will be up to you if you want to improve that aspect of blocking types of external damage.

Unfortunately, I can't use your system in my MOBA because, by not ignoring external damage, it harms me, causing imbalance.
 
Last edited:
View attachment 592266
It still doesn't reduce external damage... Bro, the whole point of a shield system is to protect against damage. And Damage Engine filters out all types of damage, but with its system, it ignores external damage... where it shouldn't. It should absorb all damage...

It will be up to you if you want to improve that aspect of blocking types of external damage.

Unfortunately, I can't use your system in my MOBA because, by not ignoring external damage, it harms me, causing imbalance.

No need to be this aggressive, bro.



Anyway, I think I found the root cause of this behavior.
One variable not initiated, that would not be assigned a value when DamageEvent didn't flag the damage as spell nor attack.

Will do more testing this week-end before publishing a correction.
 
No need to be this aggressive, bro.



Anyway, I think I found the root cause of this behavior.
One variable not initiated, that would not be assigned a value when DamageEvent didn't flag the damage as spell nor attack.

Will do more testing this week-end before publishing a correction.
1779908401419.webp



Friend, I haven't disrespected you at any point. I haven't been rude, much less aggressive. I'm just writing to you from a logical perspective regarding a shield system.

I mentioned that the point of a shield system is that it reduces damage. Especially if it's an "all" type shield.

Look, in my MOBA, I use different types of damage, but there's a particular type called "DEGENERATIVE," which causes damage to penetrate shields.
 
Should I wait for the fix before I use this?
If you use trigger damage without setting udg_NextDamageIsAttack to true (for attacks) or you're not using the "default" pair of attack type and damage type for spells (detected by Damage Engine to set DamageIsSpell to true), it would be better to wait for the fix indeed.

I'll have much more time to work and fix this now that the tech tree contest is over :cute:
 
New version is out !
I think I included everything you guys have reported. If I forgot something, feel free to remind me :grin:


Version 1.4.0

Bug fixes:
  • Fixed an issue of some damage instance not being absorbed by shields when the corresponding DamageEvent didn't have IsdamageAttack = true or IsDamageSpell = true.
  • The shield visual bar will now update when the shield regenerate its HPs.
  • Added a fail safe to make sure every shield bar is destroyed properly when a wielder is destroy.
New features:
  • Added support for pure vJass use of this system. You can now add callbacks functions to events using the following functions:
    • ShieldEvent.AddOnPreCreation
    • ShieldEvent.AddOnCreation
    • ShieldEvent.AddOnAbsorb
    • ShieldEvent.AddOnExpired
    • ShieldEvent.AddOnBreak
    • ShieldEvent.AddOnRefresh
    • ShieldEvent.AddOnStack
  • Added parameter USE_GUI (boolean), default is false. Set to true to enabled usage of GUI variables in event firing. If kept to false, feel free to delete the whole folder GUI Management folder.
  • Shield can now filter absorbed damage using attack types and damage types.
    • ShieldEvent.AddAttackType will add given attack type to the list of absorbed attack types. If never called, the shield will absorb all attack types.
    • ShieldEvent.ExcludeAttackType will make the shield unabled to absorb the given attack type.
    • ShieldEvent.AddDamageType will add given attack type to the list of absorbed damage types. If never called, the shield will absorb all damage types.
    • ShieldEvent.ExcludeDamageType will make the shield unabled to absorb the given damage type.
  • Added parameters NUMBER_OF_ATTACK_TYPE and NUMBER_OF_DAMAGE_TYPE. These are used to define the max size of arrays for attack and damage inclusion/exclusion. Default are the number of types in the DamageEngine.
 
New version is out !
I think I included everything you guys have reported. If I forgot something, feel free to remind me :grin:


Version 1.4.0

Bug fixes:
  • Fixed an issue of some damage instance not being absorbed by shields when the corresponding DamageEvent didn't have IsdamageAttack = true or IsDamageSpell = true.
  • The shield visual bar will now update when the shield regenerate its HPs.
  • Added a fail safe to make sure every shield bar is destroyed properly when a wielder is destroy.
New features:
  • Added support for pure vJass use of this system. You can now add callbacks functions to events using the following functions:
    • ShieldEvent.AddOnPreCreation
    • ShieldEvent.AddOnCreation
    • ShieldEvent.AddOnAbsorb
    • ShieldEvent.AddOnExpired
    • ShieldEvent.AddOnBreak
    • ShieldEvent.AddOnRefresh
    • ShieldEvent.AddOnStack
  • Added parameter USE_GUI (boolean), default is false. Set to true to enabled usage of GUI variables in event firing. If kept to false, feel free to delete the whole folder GUI Management folder.
  • Shield can now filter absorbed damage using attack types and damage types.
    • ShieldEvent.AddAttackType will add given attack type to the list of absorbed attack types. If never called, the shield will absorb all attack types.
    • ShieldEvent.ExcludeAttackType will make the shield unabled to absorb the given attack type.
    • ShieldEvent.AddDamageType will add given attack type to the list of absorbed damage types. If never called, the shield will absorb all damage types.
    • ShieldEvent.ExcludeDamageType will make the shield unabled to absorb the given damage type.
  • Added parameters NUMBER_OF_ATTACK_TYPE and NUMBER_OF_DAMAGE_TYPE. These are used to define the max size of arrays for attack and damage inclusion/exclusion. Default are the number of types in the DamageEngine.
Dude, the shield disappears, but the effect continues. Take a look to see what's going on. I'm still having trouble understanding the system, but I see you've improved a lot. :,3
 
Back
Top