• 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 faction for Warcraft 3 and enter Hive's 19th Techtree Contest: Co-Op Commanders! Click here to enter!
  • Get your art tools and paintbrushes ready and enter Hive's 34th Texturing Contest: Void! Click here to enter!

PDMS 1.0 (Periodic Damage / Damage Over Time / DOT)

This bundle is marked as pending. It has not been reviewed by a staff member yet.

P.D.M.S.

Installation

Documentation

Code

FAQ

Change Log


Overview

Periodic Damage Management System (P.D.M.S.) is a lightweight and flexible system that allows you to apply both stacking and non-stacking periodic damage to units. It also lets you define custom periodic damage types, giving you full control over how damage is applied over time. P.D.M.S. is designed to closely mimic Warcraft 3’s native periodic damage behavior, ensuring your implementation feels natural, intuitive, and seamlessly integrates into your mapmaking workflow.

P.D.M.S. is beginner-friendly and fully functional in GUI. You can start applying poison-like or burn effects with just a few lines — no JASS knowledge required. While you may want to register your custom periodic damage type first, the process is very straightforward: simply set the damage interval, special effect, whether the damage stacks, and whether it can kill — then run the registration trigger.

Prefer a system that’s ready to go out of the box? P.D.M.S. comes with several example periodic damage types in the test map. These serve as a helpful starting point and show how to define your own types. Applying a periodic damage effect is just as easy — specify the source, target, duration, damage amount, and type, and the system handles the rest.

Best of all, it's compatible with Warcraft 3 version 1.26!

How does it work?

Before diving into how P.D.M.S functions, it helps to understand how Warcraft 3's native periodic damage works — especially with poison-based abilities like Envenomed Spears or Envenomed Weapons, which showcase both stacking and non-stacking behaviors.

For non-stacking behavior, this one's simple. If a unit is already affected by a periodic damage effect, and a new one is applied:
  • The effect with the higher duration and higher damage will override the weaker one.
  • Only one instance of the effect is active at a time.
For stacking behavior, however, it gets a bit more nuanced:
  • Longer duration effects still override shorter ones.
  • Each new application of damage becomes a separate "stack", with its own damage value and timer.
  • The stack count only increases when the target is attacked by a different unit (just like in vanilla Warcraft 3 poison effects).
  • As time passes and stacks expire, their individual damage is removed from the total.
This native behavior is what inspired the design of P.D.M.S. — to stay faithful to how Warcraft 3 works, while giving you fine control over how damage is applied in your own map.

In some cases, you may want stacking behavior where each application increases the stack count—even if the source is the same unit. P.D.M.S. supports this alternative stacking mode as well, allowing multiple instances from the same source to coexist and accumulate independently.

Features

P.D.M.S. uses a hashtable to store and manage all the data it needs to mimic Warcraft 3’s native periodic damage behavior. Each time a periodic damage effect is applied, relevant values like damage, duration, etc. are saved and updated efficiently. Thanks to this, P.D.M.S. gives you access to powerful and flexible features:
  • Create custom periodic damage types — Configure everything from the attack type, damage type, interval, and special effect to whether the damage stacks or can kill the target.
  • Apply or remove periodic damage easily using simple trigger calls — no JASS knowledge required.
  • Check if a unit is affected by a specific type of periodic damage.
  • Custom Events which allow you to detect and respond to specific moments in the periodic damage lifecycle — such as when an effect is first applied, refreshed, stacked, or expires.
  • Compatible with damage detection system, such as Damage Engine 3.8 — with built-in support to avoid recursive triggers. (Experimental but tested)

Motivation

I was looking for a Damage Over Time (DoT) system that I could easily integrate into my map but I couldn't find any that satisfies me. This dissatisfaction comes from the fact that most of the DoT systems I found are either lacking features for being too simple or too many features for having a scope beyond than just DoT. I aimed to strike a balance — a system that’s powerful but focused, flexible but easy to use. Hopefully, I’ve managed to hit that sweet spot.

Also, I don't like how most don't even support non-stacking behavior.

Requirements

  • None! P.D.M.S. does not have any dependencies!

Installation

  • Make sure your world editor has this option enabled:
    • Go to File,
    • Go to Preferences,
    • Check "Automatically create unknown variables while pasting trigger data"
  • Simply copy the P.D.M.S. folder and paste it into your map
  • You are ready to roll!

Compatibility

P.D.M.S. has been tested and confirmed to work on:
  • Warcraft 3 version 1.26a
  • Warcraft 3 version 1.31.1
Support for other versions may vary and is untested.
But in theory, it should work fine with any newer version.


Registration

Application

Removal

Check

Immunity

Custom Events

Damage Detection Exclusion


Getting Started

First, you need to install P.D.M.S. in your map. Please follow the installation guide to do so. Once you are done with the installation, you need to register your own Periodic Damage Type. It is very easy to do; First, you need an integer variable to store the periodic damage type index, so you can easily reference it later. For this example, I want to create a poison type, so let's call it PDMS_Type_Poison.

For GUI User:

  • PDMS Registration
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- - --------
      • -------- REGISTER POISON TYPE --------
      • -------- - --------
      • -------- Attack Type and Damage Type of your periodic damage --------
      • -------- - --------
      • Set PDMS_Register_AttackType = Chaos
      • Set PDMS_Register_DamageType = Universal
      • -------- - --------
      • -------- The special effect attached on affected unit --------
      • -------- - --------
      • Set PDMS_Register_SfxModel = Abilities\Weapons\PoisonSting\PoisonStingTarget.mdl
      • -------- - --------
      • -------- Attachment point for the special effect --------
      • -------- - --------
      • Set PDMS_Register_AttachPoint = chest
      • -------- - --------
      • -------- Should it stack? --------
      • -------- - --------
      • Set PDMS_Register_StackDamage = True
      • -------- - --------
      • -------- Should it stack like how it does in Wc3? If it does, set this to false --------
      • -------- Note: --------
      • -------- For Wc3 style, you can only add stack from a different source --------
      • -------- For Non-Wc3 style, you can add stack from the same source --------
      • -------- - --------
      • Set PDMS_Register_NonWc3Style = False
      • -------- - --------
      • -------- StackCap only matters if NonWc3Style is true as Wc3Style does not have a StackCap --------
      • -------- The value must be greater than 0 --------
      • -------- Set to -1 for infinite stacks (not recommended) --------
      • -------- - --------
      • Set PDMS_Register_StackCap = 0
      • -------- - --------
      • -------- Can it kill? --------
      • -------- - --------
      • Set PDMS_Register_CanKill = False
      • -------- - --------
      • -------- The damage interval --------
      • -------- - --------
      • Set PDMS_Register_Interval = 1.00
      • -------- - --------
      • -------- Run PDMS Register trigger to register your new periodic damage type --------
      • -------- You can only use Run PDMS_Register (checking conditions) --------
      • -------- PDMS_Register (ignoring conditions) will not work here --------
      • -------- - --------
      • Trigger - Run PDMS_Register (checking conditions)
      • -------- - --------
      • -------- This will store the index which you will use as reference --------
      • -------- - --------
      • Set PDMS_Type_Poison = PDMS_LastRegisteredType

For JASS User:

JASS:
//-- You can call this function to register your own periodic damage type
function PDMS_RegisterType takes attacktype AT, damagetype DT, boolean damageStack, boolean nonWc3Style, integer stackCap, boolean canKill, real interval, string sfxModel, string attachPoint returns integer

And just like that, Periodic Damage Type has been successfully registered. You can use PDMS_Type_Poison to reference the type whenever you want to check / apply / remove a periodic damage effect on a unit.
While it is possible to dynamically register a new type during the runtime, it is highly recommended to register a new type as soon as the map loads to avoid any possible complications.

How to Apply

To apply a Periodic Damage Effect on a unit is also very easy; you just need to configure the variables with "Param" middle namespace. Please follow the example below:

For GUI User:

  • PDMS Apply
    • Events
      • Unit - A unit Is attacked
    • Conditions
    • Actions
      • Set PDMS_Param_Source = (Attacking unit)
      • Set PDMS_Param_Target = (Attacked unit)
      • Set PDMS_Param_Damage = 10.00
      • Set PDMS_Param_Duration = 10.00
      • Set PDMS_Param_Type = PDMS_Type_Poison
      • Trigger - Run PDMS_Apply (checking conditions)

For JASS User:

JASS:
//-- You can simply call this function to apply a non-stacking Periodic Damage Effect
function PDMS_ApplyNonStack takes unit source, real damage, unit target, real duration, integer pdType returns boolean

//-- You can simply call this function to Apply a stacking PDMS Effect on a unit
//-- with Wc3 Stacking Behavior (Stack can only be increased once per source)
function PDMS_ApplyWc3Stack takes unit source, real damage, unit target, real duration, integer pdType returns boolean

//-- You can simply call this function to Apply a stacking PDMS Effect on a unit
//-- with Custom Stacking Behavior (Stack can be increased no matter what)
function PDMS_ApplyCustomStack takes unit source, real damage, unit target, real duration, integer pdType returns boolean

How to Remove

To remove a Periodic Damage Effect from a unit, there are two ways of doing it. You can remove all Periodic Damage Effect from a unit or you can remove only a certain type of Periodic Damage Effect from a unit. We will cover both cases in examples down below:

For GUI User:

  • PDMS RemoveByType
    • Events
      • Unit - A unit enters Cleansing Zone 001 <gen>
    • Conditions
    • Actions
      • -------- To remove only a certain type of periodic damage effect --------
      • -------- You simply need to specify the target AND the periodic damage type itself --------
      • Set PDMS_Param_Target = (Triggering unit)
      • Set PDMS_Param_Type = PDMS_Type_Poison
      • -------- Then run PDMS_Remove trigger --------
      • Trigger - Run PDMS_Remove (checking conditions)
  • PDMS RemoveAll
    • Events
      • Unit - A unit enters Cleansing Zone 002 <gen>
    • Conditions
    • Actions
      • -------- To remove all periodic damage effect --------
      • -------- You simply need to specify the target --------
      • Set PDMS_Param_Target = (Triggering unit)
      • -------- Then run PDMS_Remove trigger --------
      • Trigger - Run PDMS_Remove (checking conditions)

For JASS User:

JASS:
//-- You can simply call this function to remove a specific type of Periodic Damage Effect
function PDMS_RemoveByType takes unit target, integer pdType returns nothing

//-- You can simply call this function to remove all kind of Periodic Damage Effect
function PDMS_RemoveAll takes unit target returns nothing

How to Check

You can check whether a unit is affected by a specific type of Periodic Damage Effect. To do this, you simply need to specify the target and the type of Periodic Damage Effect you want to check. Please take a look at the example down below:

For GUI User:

  • PDMS Check
    • Events
      • Player - Player 1 (Red) Selects a unit
    • Conditions
    • Actions
      • Set PDMS_Param_Target = (Triggering unit)
      • Set PDMS_Param_Type = PDMS_Type_Poison
      • Trigger - Run PDMS_Check (checking conditions)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • PDMS_IsUnitInEffect Equal to True
        • Then - Actions
          • Game - Display to (All players) for 10.00 seconds the text: ((Name of (Triggering unit)) + is affected by poison!)
        • Else - Actions
          • Game - Display to (All players) for 10.00 seconds the text: ((Name of (Triggering unit)) + is healthy and well!)

For JASS User:

JASS:
//-- You can call this function to check whether a unit is affected by a specific type of Periodic Damage Effect
function PDMS_IsUnitInEffect takes unit target, integer pdType returns boolean

How to Apply or Remove Immunity?

You can apply or remove immunity from a unit very easily but you must specify which unit, which type of periodic damage it is immune to, and finally adjust the flag accordingly. Please take a look at example below:

For GUI User:

  • PDMS Immunity
    • Events
      • Player - Player 1 (Red) types a chat message containing -i as A substring
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Entered chat string) Equal to -i true
        • Then - Actions
          • Custom script: set bj_wantDestroyGroup = true
          • Unit Group - Pick every unit in (Units currently selected by (Triggering player)) and do (Actions)
            • Loop - Actions
              • -------- - --------
              • -------- Apply Immunity against Poison --------
              • -------- - --------
              • Set PDMS_Param_Target = (Picked unit)
              • Set PDMS_Param_Type = PDMS_Type_Poison
              • Set PDMS_Param_Flag = True
              • Trigger - Run PDMS_SetImmunity (checking conditions)
              • -------- - --------
              • Game - Display to (All players) for 10.00 seconds the text: ((Name of (Picked unit)) + ( is now immune to + Poison!))
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Entered chat string) Equal to -i false
            • Then - Actions
              • Custom script: set bj_wantDestroyGroup = true
              • Unit Group - Pick every unit in (Units currently selected by (Triggering player)) and do (Actions)
                • Loop - Actions
                  • -------- - --------
                  • -------- Remove Immunity against Poison --------
                  • -------- - --------
                  • Set PDMS_Param_Target = (Picked unit)
                  • Set PDMS_Param_Type = PDMS_Type_Poison
                  • Set PDMS_Param_Flag = False
                  • Trigger - Run PDMS_SetImmunity (checking conditions)
                  • -------- - --------
                  • Game - Display to (All players) for 10.00 seconds the text: ((Name of (Picked unit)) + ( is no longer immune to + Poison!))
            • Else - Actions

For JASS User:

JASS:
//-- Simply call this function to Apply/Remove immunity towards specific periodic damage type from a unit
function PDMS_SetImmunity takes unit target, integer pdType, boolean flag returns nothing

//-- As a bonus, you also have access to this function to check whether a unit is immune or not
function PDMS_IsImmune takes unit target, integer pdType returns boolean

What are Custom Events?

Custom events in P.D.M.S. allow you to detect and respond to specific moments in the periodic damage lifecycle — such as when an effect is first applied, refreshed, stacked, or expires. These events make it easier to trigger additional actions or visuals based on what's happening in the system.

The following is a list of custom events currently available in P.D.M.S.:
  • Game - PDMS_AppliedEvent becomes equal to 1.00
    • This event fires when PDMS Effect is applied for the first time on a unit.
  • Game - PDMS_StackedEvent becomes equal to 1.00
    • This event fires when a stackable PDMS Effect is re-applied on the same unit.
  • Game - PDMS_RefreshedEvent becomes equal to 1.00
    • This event fires when a non-stackable PDMS Effect is re-applied on the same unit.
  • Game - PDMS_ExpiredEvent becomes equal to 1.00
    • This event fires when PDMS Effect wears off
Each of them has their own variables that serve as Event Responses. To get a better understanding, let's take a look at some examples right away.
  • AppliedEvent Example
    • Events
      • Game - PDMS_AppliedEvent becomes Equal to 1.00
    • Conditions
      • PDMS_LastAppliedType Equal to PDMS_Type_Poison
    • Actions
      • -------- - --------
      • -------- Available variables that serve as Event Responses are as following: --------
      • -------- [*] PDMS_LastAppliedType --------
      • -------- [*] PDMS_LastAppliedSource --------
      • -------- [*] PDMS_LastAppliedTarget --------
      • -------- - --------
      • -------- This will add a custom ability based on Slow Aura (Tornado) to the target --------
      • Unit - Add Poison (Custom Aura) to PDMS_LastAppliedTarget
  • ExpiredEvent Example
    • Events
      • Game - PDMS_ExpiredEvent becomes Equal to 1.00
    • Conditions
      • PDMS_LastExpiredType Equal to PDMS_Type_Poison
    • Actions
      • -------- - --------
      • -------- Available variables that serve as Event Responses are as following: --------
      • -------- [*] PDMS_LastExpiredType --------
      • -------- [*] PDMS_LastExpiredSource --------
      • -------- [*] PDMS_LastExpiredTarget --------
      • -------- - --------
      • -------- This will remove a custom ability based on Slow Aura (Tornado) from the target --------
      • Unit - Remove Poison (Custom Aura) from PDMS_LastExpiredTarget
      • -------- This will remove the buff immediately --------
      • Unit - Remove Poison (Custom Buff) buff from PDMS_LastExpiredTarget
  • StackedEvent Example
    • Events
      • Game - PDMS_StackedEvent becomes Equal to 1.00
    • Conditions
      • PDMS_LastStackedType Equal to PDMS_Type_Poison
    • Actions
      • -------- - --------
      • -------- Available variables that serve as Event Responses are as following: --------
      • -------- [*] PDMS_LastStackedType --------
      • -------- [*] PDMS_LastStackedSource --------
      • -------- [*] PDMS_LastStackedTarget --------
      • -------- - --------
      • Game - Display to (All players) for 10.00 seconds the text: ((Name of PDMS_LastStackedSource) + ( has added a new stack on + (Name of PDMS_LastStackedTarget)))
  • RefreshedEvent Example
    • Events
      • Game - PDMS_RefreshedEvent becomes Equal to 1.00
    • Conditions
      • PDMS_LastRefreshedType Equal to PDMS_Type_Poison
    • Actions
      • -------- - --------
      • -------- Available variables that serve as Event Responses are as following: --------
      • -------- [*] PDMS_LastRefreshedType --------
      • -------- [*] PDMS_LastRefreshedSource --------
      • -------- [*] PDMS_LastRefreshedTarget --------
      • -------- - --------
      • Game - Display to (All players) for 10.00 seconds the text: ((Name of PDMS_LastRefreshedSource) + ( has refreshed the effect on + (Name of PDMS_LastRefreshedTarget)))

Problem with Damage Detection Event

As you may know, Damage Detection Systems (DDS) like Damage Engine, Physical Damage Detection, or GUI Damage Detection all share a common trait: they detect all instances of damage. This becomes an issue when using them to apply periodic effects, such as those handled by P.D.M.S., because the system itself deals damage periodically.
This can lead to an unintended recursive loop:
  1. DDS detects incoming damage and triggers P.D.M.S.
  2. P.D.M.S. applies periodic damage, which is again picked up by the DDS.
  3. DDS runs again, triggering P.D.M.S. again...
  4. …and the cycle repeats — resulting in an infinite loop.
To address this, P.D.M.S. includes an experimental safeguard that prevents periodic damage applied by the system from triggering damage detection events again. While this feature is still marked experimental, it has performed reliably in my testing, including compatibility with Damage Engine 3.8.

Solution

To prevent this recursive loop, the key is to exclude P.D.M.S.-generated damage from being picked up by your Damage Detection System.
This is achieved using a simple boolean flag: PDMS_IsDealingDamage

When P.D.M.S. is about to apply damage, it sets this flag to true, and resets it to false immediately afterward. Your DDS trigger should check this flag before responding to any damage event — if the flag is true, it means the damage was caused by P.D.M.S. and should be ignored.

Here’s a simple example using Damage Engine 3.8:
  • DDS Exclusion
    • Events
      • Game - AfterDamageEvent becomes Equal to 1.00
    • Conditions
      • PDMS_IsDealingDamage Equal to False
    • Actions
      • Set PDMS_Param_Source = DamageEventSource
      • Set PDMS_Param_Target = DamageEventTarget
      • Set PDMS_Param_Damage = 10.00
      • Set PDMS_Param_Duration = 10.00
      • Set PDMS_Param_Type = PDMS_Type_Poison
      • Trigger - Run PDMS_Apply (checking conditions)
This approach ensures that P.D.M.S. can coexist with Damage Engine and other systems that listen to damage events — without causing infinite loops.

In theory, this should also work with the new Damage Event native but it has not yet been tested.


JASS:
//===========================================================================
//=
//=     ===========================================
//=     P.D.M.S. - Periodic Damage Management System
//=     Version 1.0
//=     By Rheiko
//=     ===========================================
//=
//=     Description:
//=         A lightweight and flexible system that allows map creators to
//=         define, apply, remove, stack, and refresh periodic damage on
//=         units.
//=
//===========================================================================
//=     ====
//=     API:
//=     ====
//=
//=     function PDMS_RegisterType takes attacktype AT, damagetype DT, boolean damageStack, boolean nonWc3Style, integer stackCap, boolean canKill, real interval, string sfxModel, string attachPoint returns integer
//=         - Register a new type of PDMS Effect
//=
//=     function PDMS_ApplyNonStack takes unit source, real damage, unit target, real duration, integer pdType returns boolean
//=         - Apply a non-stacking PDMS Effect on a unit
//=
//=     function PDMS_ApplyWc3Stack takes unit source, real damage, unit target, real duration, integer pdType returns boolean
//=         - Apply a stacking PDMS Effect on a unit with Wc3 Stacking Behavior (Stack can only be increased once by different source)
//=
//=     function PDMS_ApplyCustomStack takes unit source, real damage, unit target, real duration, integer pdType returns boolean
//=         - Apply a stacking PDMS Effect on a unit with Custom Stacking Behavior (Stack can be increased by single source)
//=
//=     function PDMS_RemoveByType takes unit target, integer pdType returns nothing
//=         - Remove a certain type of PDMS Effect from a unit
//=
//=     function PDMS_RemoveAll takes unit target returns nothing
//=         - Remove all kind of PDMS Effect from a unit
//=
//=     function PDMS_IsUnitInEffect takes unit target, integer pdType returns boolean
//=         - Check whether a unit is under the effect of certain type of PDMS Effect
//=
//=     function PDMS_SetImmunity takes unit target, integer pdType, boolean flag returns nothing
//=         - Apply/Remove immunity towards specific periodic damage type from a unit
//=
//=     function PDMS_IsImmune takes unit target, integer pdType returns boolean
//=         - Check whether a unit is immune to a specific periodic damage type
//=
//===========================================================================
//=     ===========
//=     Event List:
//=     ===========
//=
//=     Game - PDMS_AppliedEvent becomes equal to 1.00
//=         -> This event fires when PDMS Effect is applied for the first time on a unit.
//=
//=     Game - PDMS_StackedEvent becomes equal to 1.00
//=         -> This event fires when a stackable PDMS Effect is re-applied on the same unit.
//=
//=     Game - PDMS_RefreshedEvent becomes equal to 1.00
//=         -> This event fires when a non-stackable PDMS Effect is re-applied on the same unit.
//=
//=     Game - PDMS_ExpiredEvent becomes equal to 1.00
//=         -> This event fires when PDMS Effect wears off
//=
//===========================================================================
//=     ==================
//=     List of Variables:
//=     ==================
//=
//=     - The following variables are used to register a new type of PDMS Effect.
//=
//=     = Variable Name =                   = Data Type =       = Description =
//=     udg_PDMS_Register_AttackType        Attack Type         Assign the attack type of PDMS type
//=     udg_PDMS_Register_DamageType        Damage Type         Assign the damage type of PDMS type
//=     udg_PDMS_Register_SfxModel          String              Assign the model path used as special effect
//=     udg_PDMS_Register_AttachPoint       String              Assign the attachment point for special effect
//=     udg_PDMS_Register_StackDamage       Boolean             Enable stacking/non-stacking behavior
//=     udg_PDMS_Register_NonWc3Style       Boolean             Enable single-source stacking behavior
//=     udg_PDMS_Register_StackCap          Boolean             Assign the maximum cap of stacks
//=     udg_PDMS_Register_CanKill           Boolean             Enable kill
//=     udg_PDMS_Register_Interval          Boolean             Assign the damage interval for PDMS type
//=
//=     - The following variables are used to as a parameter to call available APIs.
//=
//=     = Variable Name =                   = Data Type =       = Description =
//=     udg_PDMS_Param_Source               Unit                Assign the source of PDMS Effect
//=     udg_PDMS_Param_Target               Unit                Assign the target of PDMS Effect
//=     udg_PDMS_Param_Damage               Real                Assign the damage value of PDMS Effect
//=     udg_PDMS_Param_Duration             Real                Assign the duration of PDMS Effect
//=     udg_PDMS_Param_Type                 Integer             Assign the Periodic Damage Type Index
//=     udg_PDMS_Param_Flag                 Boolean             Assign the immunity flag towards certain Periodic Damage Type
//=
//=     - The following variables are used to trigger a function that calls available APIs.
//=
//=     = Variable Name =                   = Data Type =       = Description =
//=     udg_PDMS_Apply                      Trigger             Allow the user to trigger Apply Function
//=     udg_PDMS_Remove                     Trigger             Allow the user to trigger RemoveByType / RemoveAll Function
//=     udg_PDMS_Check                      Trigger             Allow the user to trigger Check Function
//=     udg_PDMS_Register                   Trigger             Trigger the type registration function
//=     udg_PDMS_SetImmunity                Trigger             Allow the user to toggle the immunity flag for certain Periodic Damage Type
//=
//=     - The following variables are event getters, should be used as a read-only.
//=
//=     = Variable Name =                   = Data Type =       = Description =
//=     udg_PDMS_LastRegisteredType         Integer             Refer to the Periodic Damage Type Index when registered a new type
//=     udg_PDMS_IsUnitInEffect             Boolean             Refer to the boolean value when udg_PDMS_Check is triggered
//=     udg_PDMS_IsDealingDamage            Boolean             Refer to the state of PDMS when dealing damage
//=     udg_PDMS_LastExpiredSource          Unit                Refer to the source unit of last expired event
//=     udg_PDMS_LastExpiredTarget          Unit                Refer to the target unit of last expired event
//=     udg_PDMS_LastExpiredType            Integer             Refer to the type index of last expired event
//=     udg_PDMS_LastAppliedSource          Unit                Refer to the source unit of last applied event
//=     udg_PDMS_LastAppliedTarget          Unit                Refer to the target unit of last applied event
//=     udg_PDMS_LastAppliedType            Integer             Refer to the type index of last applied event
//=     udg_PDMS_LastStackedSource          Unit                Refer to the source unit of last stacked event
//=     udg_PDMS_LastStackedTarget          Unit                Refer to the target unit of last stacked event
//=     udg_PDMS_LastStackedType            Integer             Refer to the type index of last stacked event
//=     udg_PDMS_LastRefreshedSource        Unit                Refer to the source unit of last refreshed event
//=     udg_PDMS_LastRefreshedTarget        Unit                Refer to the target unit of last refreshed event
//=     udg_PDMS_LastRefreshedType          Integer             Refer to the type index of last refreshed event
//=
//=     - The following variables are used as the internals for the core system
//=
//=     = Variable Name =                   = Data Type =       = Description =
//=     udg_PDMS_Hashtable                  Hashtable           Main System Data Storage
//=     udg_PDMS_Timeout                    Real                Internal Clock Interval
//=     udg_PDMS_PdTypeIndex                Integer             Periodic Damage Type Index
//=     udg_PDMS_AT                         Attack Type Arr     Store the attack type of each Periodic Damage Type
//=     udg_PDMS_DT                         Damage Type Arr     Store the damage type of each Periodic Damage Type
//=     udg_PDMS_DefaultSfxModel            String Arr          Store the model file path for special effect of each Periodic Damage Type
//=     udg_PDMS_DefaultAttachPoint         String Arr          Store the attachment point for special effect of each Periodic Damage Type
//=     udg_PDMS_CanStackDamage             Boolean Arr         Enable stacking/non-stacking behavior of each Periodic Damage Type
//=     udg_PDMS_NonWc3Style                Boolean Arr         Enable single-source stacking behavior
//=     udg_PDMS_StackCap                   Boolean Arr         Assign the maximum cap of stacks
//=     udg_PDMS_CanKill                    Boolean Arr         Enable kill for each Periodic Damage Type
//=     udg_PDMS_Interval                   Real arr            Store the interval value of each Periodic Damage Type
//=
//===========================================================================
//=     ===================
//=     Hashtable Mappings:
//=     ===================
//=
//=     (targetId, pdType)                                      = isActive flag
//=     (targetId, pdType + 1000)                               = Source handle of current instance
//=     (targetId, pdType + 2000)                               = Duration value
//=     (targetId, pdType + 3000)                               = Interval value
//=     (targetId, pdType + 4000)                               = Damage value
//=     (targetId, pdType + 5000)                               = Sfx handle
//=
//=     (targetId, pdType + 6000)                               = Damage Counter
//=     (targetId, (10000 * pdType) + 7000 + damageCounter)     = Damage value
//=     (targetId, (10000 * pdType) + 8000 + damageCounter)     = Time Remaining
//=     (targetId, (10000 * pdType) + 9000 + damageCounter)     = Source ID of each instance
//=
//=     (targetId, pdType + 7000)                               = Immunity flag
//=
//=     (sourceId, targetId)                                    = isNotFirst flag
//=
//=     (timerId, 0)                                            = Target handle
//=     (timerId, 1)                                            = Periodic Damage Type
//=
//===========================================================================
function PDMS_Configuration takes nothing returns nothing
//===========================================================================
//=                         Configuration
//===========================================================================
    //-----------------------------------------------------------------------
    //-- This will show debug messages if you set it to true
    //-----------------------------------------------------------------------
    set udg_PDMS_Debug = false
    //-----------------------------------------------------------------------
    //-- This is the Internal Clock Interval
    //-----------------------------------------------------------------------
    set udg_PDMS_Timeout = 0.05
//===========================================================================
//=                         Enf of Config
//===========================================================================
endfunction
//===========================================================================
//= This function registers a new type of PDMS Effect
//===========================================================================
function PDMS_RegisterType takes attacktype AT, damagetype DT, boolean damageStack, boolean nonWc3Style, integer stackCap, boolean canKill, real interval, string sfxModel, string attachPoint returns integer
    set udg_PDMS_PdTypeIndex                                = udg_PDMS_PdTypeIndex + 1
    set udg_PDMS_AT[udg_PDMS_PdTypeIndex]                   = AT
    set udg_PDMS_DT[udg_PDMS_PdTypeIndex]                   = DT
    set udg_PDMS_DefaultSfxModel[udg_PDMS_PdTypeIndex]      = sfxModel
    set udg_PDMS_DefaultAttachPoint[udg_PDMS_PdTypeIndex]   = attachPoint
    set udg_PDMS_CanKill[udg_PDMS_PdTypeIndex]              = canKill
    set udg_PDMS_CanStackDamage[udg_PDMS_PdTypeIndex]       = damageStack
    set udg_PDMS_NonWc3Style[udg_PDMS_PdTypeIndex]          = nonWc3Style
    set udg_PDMS_StackCap[udg_PDMS_PdTypeIndex]             = stackCap
    set udg_PDMS_Interval[udg_PDMS_PdTypeIndex]             = interval
    //-- Debug
    if udg_PDMS_Debug == true then
        call BJDebugMsg("[|cffffcc00System Debug|r] - " + "New Type is Registered at Index " + I2S(udg_PDMS_PdTypeIndex))
    endif
    return udg_PDMS_PdTypeIndex
endfunction
//===========================================================================
//= This function checks whether a unit is affected by specific type of PDMS effect
//===========================================================================
function PDMS_IsUnitInEffect takes unit target, integer pdType returns boolean
    local integer targetId = GetHandleId(target)
    //-- Load the value to check for active instance
    local integer isActive = LoadInteger(udg_PDMS_Table, targetId, pdType)
    if isActive == 0 then
        return false
    endif
    return true
endfunction
//===========================================================================
//= This function removes specific type of PDMS effect
//===========================================================================
function PDMS_RemoveByType takes unit target, integer pdType returns nothing
    //-- Local vars
    local integer i = 0
 
    //-- Get TargetId
    local integer targetId = GetHandleId(target)
    //-- Load Sfx and source
    local effect sfx = LoadEffectHandle(udg_PDMS_Table, targetId, pdType + 5000)
    local unit source = LoadUnitHandle(udg_PDMS_Table, targetId, pdType + 1000)
    local integer sourceId = GetHandleId(source)
    local integer damageCounter
    local integer prevSourceId
    //-- Debug
    if udg_PDMS_Debug == true then
        call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Removing Periodic Damage by Specific Type")
        call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Destroying effect for the type index " + I2S(pdType))
    endif
 
    //-- Destroy Effect
    call DestroyEffect(sfx)
    //-- Clean up hashtable
    if udg_PDMS_CanStackDamage[pdType] == true then
        set damageCounter = LoadInteger(udg_PDMS_Table, targetId, pdType + 6000)
        loop
            set i = i + 1
            exitwhen i > damageCounter
            set prevSourceId = LoadInteger(udg_PDMS_Table, targetId, (10000 * pdType) + 9000 + i)
            call RemoveSavedInteger(udg_PDMS_Table, prevSourceId, targetId)
        endloop
    else
        call RemoveSavedInteger(udg_PDMS_Table, sourceId, targetId)
    endif
    call RemoveSavedInteger(    udg_PDMS_Table, targetId, pdType)
    call RemoveSavedHandle(     udg_PDMS_Table, targetId, pdType + 1000)
    call RemoveSavedReal(       udg_PDMS_Table, targetId, pdType + 2000)
    call RemoveSavedReal(       udg_PDMS_Table, targetId, pdType + 3000)
    call RemoveSavedReal(       udg_PDMS_Table, targetId, pdType + 4000)
    call RemoveSavedHandle(     udg_PDMS_Table, targetId, pdType + 5000)
    call RemoveSavedInteger(    udg_PDMS_Table, targetId, pdType + 6000)
    set udg_PDMS_LastExpiredType = pdType
    set udg_PDMS_LastExpiredSource = source
    set udg_PDMS_LastExpiredTarget = target
    set udg_PDMS_ExpiredEvent = 1.00
    set udg_PDMS_ExpiredEvent = 0.00
    set source = null
    set sfx = null
endfunction
//===========================================================================
//= This function removes all type of PDMS effect
//===========================================================================
function PDMS_RemoveAll takes unit target returns nothing
    //-- Local vars
    local integer i = 0
    //-- Get TargetId
    local integer targetId = GetHandleId(target)
    loop
        set i = i + 1
        exitwhen i > udg_PDMS_PdTypeIndex
        call PDMS_RemoveByType(target, i)
    endloop
    //-- Debug
    if udg_PDMS_Debug == true then
        call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Removed All Periodic Damage from a unit")
    endif
endfunction
//===========================================================================
//= This function toggles the immunity flag of certain pdType for a unit
//===========================================================================
function PDMS_SetImmunity takes unit target, integer pdType, boolean flag returns nothing
    //-- Get TargetId
    local integer targetId = GetHandleId(target)
    call SaveBoolean(udg_PDMS_Table, targetId, pdType + 7000, flag)
    if flag == true then
        call PDMS_RemoveByType(target, pdType)
    endif
endfunction
//===========================================================================
//= This function checks for the immunity flag of certain pdType for a unit
//===========================================================================
function PDMS_IsImmune takes unit target, integer pdType returns boolean
    //-- Get TargetId
    local integer targetId = GetHandleId(target)
    return LoadBoolean(udg_PDMS_Table, targetId, pdType + 7000)
endfunction
//===========================================================================
//= This function handles the loop logic of non stacking version
//===========================================================================
function PDMS_LoopNonStack takes nothing returns nothing
    //-- Get the expired timer and retrieve the ID
    local timer t = GetExpiredTimer()
    local integer timerId = GetHandleId(t)
    //-- Load the previously attached data
    local unit target = LoadUnitHandle(udg_PDMS_Table, timerId, 0)
    local integer targetId = GetHandleId(target)
    local integer pdType = LoadInteger(udg_PDMS_Table, timerId, 1)
    //-- Load source unit
    local unit source
    //-- Load duration, and active flag
    local integer isActive = LoadInteger(udg_PDMS_Table, targetId, pdType)
    local real duration = LoadReal(udg_PDMS_Table, targetId, pdType + 2000)
    //-- Prepare container for interval and damage
    local real interval
    local real damage
 
    //-- Guard clause when the effect wears off
    if duration <= 0 then
        //-- Duration is over but still active; must remove
        if isActive == 1 then
            call PDMS_RemoveByType(target, pdType)
        endif
        //-- Clean up hashtable and destroy timer
        call FlushChildHashtable(udg_PDMS_Table, timerId)
        call DestroyTimer(t)
        //- Debug
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Timer is successfully destroyed")
        endif
        //-- Null local handles
        set t = null
        set source = null
        set target = null
        return
    endif
    //-- Load the interval value to be updated
    set interval = LoadReal(udg_PDMS_Table, targetId, pdType + 3000)
    //-- Decrease the duration value and update the storage
    set duration = duration - udg_PDMS_Timeout
    call SaveReal(udg_PDMS_Table, targetId, pdType + 2000, duration)
    //-- Decrease the interval value and update the storage
    set interval = interval - udg_PDMS_Timeout
    call SaveReal(udg_PDMS_Table, targetId, pdType + 3000, interval)
    //--
    if interval <= 0 then
        //-- Load source
        set source = LoadUnitHandle(udg_PDMS_Table, targetId, pdType + 1000)
        //-- Load damage value
        set damage = LoadReal(udg_PDMS_Table, targetId, pdType + 4000)
        //-- Validate the damage value
        if damage >= 0 then
            //-- Check if it allows kill
            if udg_PDMS_CanKill[pdType] == false and (GetWidgetLife(target) - damage) <= 0 then
                call SetWidgetLife(target, GetWidgetLife(target) + damage)
            endif
            set udg_PDMS_IsDealingDamage = true
            call UnitDamageTarget(source, target, damage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
            set udg_PDMS_IsDealingDamage = false
        else
            call SetWidgetLife(target, GetWidgetLife(target) + damage)
        endif
        //- Debug
        // if udg_PDMS_Debug == true then
        //     call BJDebugMsg("[|cffffcc00System Debug|r] - " + GetUnitName(source) + " deals " + R2S(damage) + " damage to " + GetUnitName(target))
        // endif
        //-- Reset the interval value back to default
        call SaveReal(udg_PDMS_Table, targetId, pdType + 3000, udg_PDMS_Interval[pdType])
    endif
    //-- Null local handles
    set t = null
    set source = null
    set target = null
endfunction
//===========================================================================
//= This function applies periodic damage of non stacking version
//===========================================================================
function PDMS_ApplyNonStack takes unit source, real damage, unit target, real duration, integer pdType returns boolean
    //-- Local variables for later
    local timer t
    local integer timerId
    local integer targetId
    //-- Variable to check for active instance
    local integer isActive
    //-- Variables to load the value of current instance if exist
    local unit currentSource
    local real currentDuration
    local real currentDamage
    //-- Variable to load effect handle
    local effect sfx
    //-- Type check!
    if pdType < 1 or pdType > udg_PDMS_PdTypeIndex then
        call BJDebugMsg("[|cffff0000PDMS Error|r] - " + "Periodic Damage Type does not exist")
        return false
    endif
    //-- Dead check
    if IsUnitType(target, UNIT_TYPE_DEAD) or GetWidgetLife(target) <= 0.405 then
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffff0000PDMS Error|r] - " + "Trying to apply PDMS on dead unit")
        endif
        return false
    endif
    //-- Immunity check
    if PDMS_IsImmune(target, pdType) == true then
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffff0000PDMS Error|r] - " + "Trying to apply PDMS on a unit that's immune")
        endif
        return false
    endif
    //-- Get the IDs of each actor
    set targetId = GetHandleId(target)
    //-- Load the value to check for active instance
    set isActive = LoadInteger(udg_PDMS_Table, targetId, pdType)
    //-- Load the value of current instance if exist
    set currentDuration  = LoadReal(udg_PDMS_Table, targetId, pdType + 2000)
    set currentDamage    = LoadReal(udg_PDMS_Table, targetId, pdType + 4000)
    //-- Load the handle to check for active effect
    set sfx = LoadEffectHandle(udg_PDMS_Table, targetId, pdType + 5000)
    //-- Create new sfx when there is still none
    if sfx == null then
        set sfx = AddSpecialEffectTarget(udg_PDMS_DefaultSfxModel[pdType], target, udg_PDMS_DefaultAttachPoint[pdType])
        call SaveEffectHandle(udg_PDMS_Table, targetId, pdType + 5000, sfx)
    endif
    //-- Refresh duration and replace source if the new one is stronger
    if duration >= currentDuration and damage >= currentDamage then
        //-- Refresh duration
        set currentDuration = duration
        call SaveReal(udg_PDMS_Table, targetId, pdType + 2000, currentDuration)
        //-- Replace source
        set currentSource = source
        call SaveUnitHandle(udg_PDMS_Table, targetId, pdType + 1000, currentSource)
        //-- Replace damage if the new one is greater
        set currentDamage = damage
        call SaveReal(udg_PDMS_Table, targetId, pdType + 4000, currentDamage)
        //-- Save Interval
        call SaveReal(udg_PDMS_Table, targetId, pdType + 3000, udg_PDMS_Interval[pdType])
        //-- RefreshedEvent
        set udg_PDMS_LastRefreshedType = pdType
        set udg_PDMS_LastRefreshedSource = source
        set udg_PDMS_LastRefreshedTarget = target
        set udg_PDMS_RefreshedEvent = 1.00
        set udg_PDMS_RefreshedEvent = 0.00
    endif
    //-- Start a timer if there is no active instances yet
    if isActive == 0 then
        //-- Create and start the timer
        set t = CreateTimer()
        call TimerStart(t, udg_PDMS_Timeout, true, function PDMS_LoopNonStack)
        //-- Debug
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Timer is successfully started")
        endif
        //-- Get its ID to attach data
        set timerId = GetHandleId(t)
        //-- Attach these data:
        //-- - target handle
        //-- - pd type
        call SaveUnitHandle(udg_PDMS_Table, timerId, 0, target)
        call SaveInteger(udg_PDMS_Table, timerId, 1, pdType)
        //-- switch active flag
        call SaveInteger(udg_PDMS_Table, targetId, pdType, 1)
        //-- AppliedEvent
        set udg_PDMS_LastAppliedType = pdType
        set udg_PDMS_LastAppliedSource = source
        set udg_PDMS_LastAppliedTarget = target
        set udg_PDMS_AppliedEvent = 1.00
        set udg_PDMS_AppliedEvent = 0.00
    endif
    //-- Null local handles
    set sfx = null
    set currentSource = null
    return true
endfunction
//===========================================================================
//= This function handles the loop logic for stacking periodic damage
//===========================================================================
function PDMS_LoopWc3Stack takes nothing returns nothing
    //-- Get the expired timer and retrieve the ID
    local timer t = GetExpiredTimer()
    local integer timerId = GetHandleId(t)
    local integer i = 0
    local integer prevSourceId
    local real damageStackDuration
    local real currentDamage
    local real damage
    //-- Load the previously attached data
    local unit target = LoadUnitHandle(udg_PDMS_Table, timerId, 0)
    local integer targetId = GetHandleId(target)
    local integer pdType = LoadInteger(udg_PDMS_Table, timerId, 1)
    //-- Prepare source container
    local unit source
    //-- Load duration, and active flag
    local integer isActive = LoadInteger(udg_PDMS_Table, targetId, pdType)
    local real duration = LoadReal(udg_PDMS_Table, targetId, pdType + 2000)
    //-- Prepare interval and damageCounter container
    local real interval
    local integer damageCounter
 
    //-- Guard clause when the effect wears off
    if duration <= 0 then
        //-- Duration is over but still active; must remove
        if isActive == 1 then
            call PDMS_RemoveByType(target, pdType)
        endif
        //-- Clean up hashtable and destroy timer
        call FlushChildHashtable(udg_PDMS_Table, timerId)
        call DestroyTimer(t)
        //- Debug
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Timer is successfully destroyed")
        endif
        //-- Null local handles
        set t = null
        set target = null
        return
    endif
    //-- Load source data
    set source = LoadUnitHandle(udg_PDMS_Table, targetId, pdType + 1000)
    //-- Load interval and damageCounter
    set interval = LoadReal(udg_PDMS_Table, targetId, pdType + 3000)
    set damageCounter = LoadInteger(udg_PDMS_Table, targetId, pdType + 6000)
    //-- Decrease the duration value and update the storage
    set duration = duration - udg_PDMS_Timeout
    call SaveReal(udg_PDMS_Table, targetId, pdType + 2000, duration)
    //-- Decrease the interval value and update the storage
    set interval = interval - udg_PDMS_Timeout
    call SaveReal(udg_PDMS_Table, targetId, pdType + 3000, interval)
    //-- Decrease the previous duration value and update the storage
    set currentDamage = 0
    loop
        set i = i + 1
        exitwhen i > damageCounter
        //-- Load the damage of each instance and add them to currentDamage
        set damage = LoadReal(udg_PDMS_Table, targetId, (10000 * pdType) + 7000 + i)
        set currentDamage = currentDamage + damage
        //-- Load the time remaining value of each damage instance and decrement them if they are greater than 0
        set damageStackDuration = LoadReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + i)
        if damageStackDuration > 0 then
            set damageStackDuration = damageStackDuration - udg_PDMS_Timeout
            call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + i, damageStackDuration)
        endif
                 
        //-- Check if the time remaining is over and the damage has not been dissipated
        if damageStackDuration <= 0 then
            // //-- dissipate the damage by resetting the value to 0
            // call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 7000 + i, 0)
            //-- dissipate the damage by resetting the value to 0
            set prevSourceId = LoadInteger(udg_PDMS_Table, targetId, (10000 * pdType) + 9000 + i)
            call RemoveSavedInteger(udg_PDMS_Table, prevSourceId, targetId)
            set prevSourceId = LoadInteger(udg_PDMS_Table, targetId, (10000 * pdType) + 9000 + damageCounter)
            call SaveInteger(udg_PDMS_Table, targetId, (10000 * pdType) + 9000 + i, prevSourceId)
            set damage = LoadReal(udg_PDMS_Table, targetId, (10000 * pdType) + 7000 + damageCounter)
            call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 7000 + i, damage)
            call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 7000 + damageCounter, 0)
            set damageStackDuration = LoadReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + damageCounter)
            call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + i, damageStackDuration)
            call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + damageCounter, 0)
            set i = i - 1
            set damageCounter = damageCounter - 1
            call SaveInteger(udg_PDMS_Table, targetId, pdType + 6000, damageCounter)
        endif
    endloop
    //--
    if interval <= 0 then
        //- Validate the damage value
        if currentDamage >= 0 then
            //-- Check if it allows kill
            if udg_PDMS_CanKill[pdType] == false and (GetWidgetLife(target) - currentDamage) <= 0 then
                call SetWidgetLife(target, GetWidgetLife(target) + currentDamage)
            endif
            set udg_PDMS_IsDealingDamage = true
            call UnitDamageTarget(source, target, currentDamage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
            set udg_PDMS_IsDealingDamage = false
        else
            call SetWidgetLife(target, GetWidgetLife(target) + currentDamage)
        endif
        // //- Debug
        // if udg_PDMS_Debug == true then
        //     call BJDebugMsg("[|cffffcc00System Debug|r] - " + GetUnitName(source) + " deals " + R2S(currentDamage) + " damage to " + GetUnitName(target))
        // endif
        // //- Debug
        // if udg_PDMS_Debug == true then
        //     set i = 0
        //     loop
        //         set i = i + 1
        //         exitwhen i > damageCounter
        //         call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Instance[" + I2S(i) + "]'s damage time remaining: " + R2S(LoadReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + i)))
        //     endloop
        // endif
        //-- Reset the interval value back to default
        call SaveReal(udg_PDMS_Table, targetId, pdType + 3000, udg_PDMS_Interval[pdType])
    endif
    //-- Null local handles
    set t = null
    set source = null
    set target = null
endfunction
//===========================================================================
//= This function applies stacking periodic damage wc3 style
//===========================================================================
function PDMS_ApplyWc3Stack takes unit source, real damage, unit target, real duration, integer pdType returns boolean
    //-- Local variables
    local timer t
    local integer timerId
    local real damageStackDuration      //-- Variable to store the time remaining of a damage from a new instance
    local effect sfx                    //-- Variable to load special effect
    local integer isNotFirst            //-- Variable to check for first encounter
    //-- Variables to get the IDs of each actor
    local integer sourceId
    local integer targetId
    //-- Variable to check for active instance
    local integer isActive
    //-- Variables to load the value of current instance if exist
    local real currentDuration
    local integer damageCounter
    //-- Type Check!
    if pdType < 1 or pdType > udg_PDMS_PdTypeIndex then
        call BJDebugMsg("[|cffff0000PDMS Error|r] - " + "Periodic Damage Type does not exist")
        return false
    endif
    //-- Dead check
    if IsUnitType(target, UNIT_TYPE_DEAD) or GetWidgetLife(target) <= 0.405 then
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffff0000PDMS Error|r] - " + "Trying to apply PDMS on dead unit")
        endif
        return false
    endif
    //-- Immunity check
    if PDMS_IsImmune(target, pdType) == true then
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffff0000PDMS Error|r] - " + "Trying to apply PDMS on a unit that's immune")
        endif
        return false
    endif
    //-- Get the IDs
    set sourceId = GetHandleId(source)
    set targetId = GetHandleId(target)
 
    //-- Load the value to check for active instances
    set isActive = LoadInteger(udg_PDMS_Table, targetId, pdType)
    //-- Load the value of current instance if exist
    set currentDuration  = LoadReal(udg_PDMS_Table, targetId, pdType + 2000)
    set damageCounter    = LoadInteger(udg_PDMS_Table, targetId, pdType + 6000)
    //-- Load the value to check if it is the first encounter
    set isNotFirst = LoadInteger(udg_PDMS_Table, sourceId, targetId)
    //-- Load the effect handle
    set sfx = LoadEffectHandle(udg_PDMS_Table, targetId, pdType + 5000)
    //-- Create new sfx when there is still none
    if sfx == null then
        set sfx = AddSpecialEffectTarget(udg_PDMS_DefaultSfxModel[pdType], target, udg_PDMS_DefaultAttachPoint[pdType])
        call SaveEffectHandle(udg_PDMS_Table, targetId, pdType + 5000, sfx)
    endif
    //-- Check if it is the first encounter
    if isNotFirst == 0 then
        //-- Switch the flag if it is the first encounter
        set isNotFirst = 1
        call SaveInteger(udg_PDMS_Table, sourceId, targetId, isNotFirst)
        //-- Increment damageCounter then update the storage
        set damageCounter = damageCounter + 1
        call SaveInteger(udg_PDMS_Table, targetId, pdType + 6000, damageCounter)
        //-- Save the source id of this instance
        call SaveInteger(udg_PDMS_Table, targetId, (10000 * pdType) + 9000 + damageCounter, sourceId)
        //-- Save the new damage
        call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 7000 + damageCounter, damage)
        //-- StackedEvent
        set udg_PDMS_LastStackedType = pdType
        set udg_PDMS_LastStackedSource = source
        set udg_PDMS_LastStackedTarget = target
        set udg_PDMS_StackedEvent = 1.00
        set udg_PDMS_StackedEvent = 0.00
    endif
    //-- Check duration and overwrite if it is stronger than the current one
    if duration > currentDuration then
        //-- Save the new duration of the damage from this instance
        set damageStackDuration = duration
        call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + damageCounter, damageStackDuration)
        //-- Overwrite the old duration with the new one
        set currentDuration = duration
        call SaveReal(udg_PDMS_Table, targetId, pdType + 2000, currentDuration)
    endif
    //-- Always replace source with the latest one
    call SaveUnitHandle(udg_PDMS_Table, targetId, pdType + 1000, source)
    //-- Start a timer if there is no active instances yet
    if isActive == 0 then
        //-- Save Interval
        call SaveReal(udg_PDMS_Table, targetId, pdType + 3000, udg_PDMS_Interval[pdType])
        //-- Create and start the timer
        set t = CreateTimer()
        call TimerStart(t, udg_PDMS_Timeout, true, function PDMS_LoopWc3Stack)
        //-- Debug
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Timer is successfully started")
        endif
        //-- Get its ID to attach data
        set timerId = GetHandleId(t)
        //-- Attach these data:
        //-- - target handle
        //-- - pd type
        call SaveUnitHandle(udg_PDMS_Table, timerId, 0, target)
        call SaveInteger(udg_PDMS_Table, timerId, 1, pdType)
        //-- switch active flag
        call SaveInteger(udg_PDMS_Table, targetId, pdType, 1)
        //-- AppliedEvent
        set udg_PDMS_LastAppliedType = pdType
        set udg_PDMS_LastAppliedSource = source
        set udg_PDMS_LastAppliedTarget = target
        set udg_PDMS_AppliedEvent = 1.00
        set udg_PDMS_AppliedEvent = 0.00
        //-- Null local handle
        set t = null
    endif
    //-- Null local handles
    set sfx = null
    return true
endfunction
//===========================================================================
//= This function handles the loop logic for custom stacking periodic damage
//===========================================================================
function PDMS_LoopCustomStack takes nothing returns nothing
    //-- Get the expired timer and retrieve the ID
    local timer t = GetExpiredTimer()
    local integer timerId = GetHandleId(t)
    local integer i = 0
    local real damageStackDuration
    local real currentDamage
    local real damage
    //-- Load the previously attached data
    local unit target = LoadUnitHandle(udg_PDMS_Table, timerId, 0)
    local integer targetId = GetHandleId(target)
    local integer pdType = LoadInteger(udg_PDMS_Table, timerId, 1)
    //-- Prepare source container
    local unit source
    local integer sourceId
    //-- Load duration, and active flag
    local integer isActive = LoadInteger(udg_PDMS_Table, targetId, pdType)
    local real duration = LoadReal(udg_PDMS_Table, targetId, pdType + 2000)
    //-- Prepare interval and damageCounter container
    local real interval
    local integer damageCounter
 
    //-- Guard clause when the effect wears off
    if duration <= 0 then
        //-- Duration is over but still active; must remove
        if isActive == 1 then
            call PDMS_RemoveByType(target, pdType)
        endif
        //-- Clean up hashtable and destroy timer
        call FlushChildHashtable(udg_PDMS_Table, timerId)
        call DestroyTimer(t)
        //- Debug
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Timer is successfully destroyed")
        endif
        //-- Null local handles
        set t = null
        set target = null
        return
    endif
    //-- Load source data
    set source = LoadUnitHandle(udg_PDMS_Table, targetId, pdType + 1000)
    //-- Load interval and damageCounter
    set interval = LoadReal(udg_PDMS_Table, targetId, pdType + 3000)
    set damageCounter = LoadInteger(udg_PDMS_Table, targetId, pdType + 6000)
    //-- Decrease the duration value and update the storage
    set duration = duration - udg_PDMS_Timeout
    call SaveReal(udg_PDMS_Table, targetId, pdType + 2000, duration)
    //-- Decrease the interval value and update the storage
    set interval = interval - udg_PDMS_Timeout
    call SaveReal(udg_PDMS_Table, targetId, pdType + 3000, interval)
    //-- Decrease the previous duration value and update the storage
    set currentDamage = 0
    loop
        set i = i + 1
        exitwhen i > damageCounter
        //-- Load the damage of each instance and add them to currentDamage
        set damage = LoadReal(udg_PDMS_Table, targetId, (10000 * pdType) + 7000 + i)
        set currentDamage = currentDamage + damage
        //-- Load the time remaining value of each damage instance and decrement them if they are greater than 0
        set damageStackDuration = LoadReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + i)
        if damageStackDuration > 0 then
            set damageStackDuration = damageStackDuration - udg_PDMS_Timeout
            call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + i, damageStackDuration)
        endif
                 
        //-- Check if the time remaining is over
        if damageStackDuration <= 0 then
            //-- dissipate the damage by resetting the value to 0
            set damage = LoadReal(udg_PDMS_Table, targetId, (10000 * pdType) + 7000 + damageCounter)
            call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 7000 + i, damage)
            call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 7000 + damageCounter, 0)
            set damageStackDuration = LoadReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + damageCounter)
            call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + i, damageStackDuration)
            call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + damageCounter, 0)
            set i = i - 1
            set damageCounter = damageCounter - 1
            call SaveInteger(udg_PDMS_Table, targetId, pdType + 6000, damageCounter)
        endif
    endloop
    //--
    if interval <= 0 then
        //- Validate the damage value
        if currentDamage >= 0 then
            //-- Check if it allows kill
            if udg_PDMS_CanKill[pdType] == false and (GetWidgetLife(target) - currentDamage) <= 0 then
                call SetWidgetLife(target, GetWidgetLife(target) + currentDamage)
            endif
            set udg_PDMS_IsDealingDamage = true
            call UnitDamageTarget(source, target, currentDamage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
            set udg_PDMS_IsDealingDamage = false
        else
            call SetWidgetLife(target, GetWidgetLife(target) + currentDamage)
        endif
        // //- Debug
        // if udg_PDMS_Debug == true then
        //     call BJDebugMsg("[|cffffcc00System Debug|r] - " + GetUnitName(source) + " deals " + R2S(currentDamage) + " damage to " + GetUnitName(target))
        // endif
        // //- Debug
        // if udg_PDMS_Debug == true then
        //     set i = 0
        //     loop
        //         set i = i + 1
        //         exitwhen i > damageCounter
        //         call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Instance[" + I2S(i) + "]'s damage time remaining: " + R2S(LoadReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + i)))
        //     endloop
        // endif
        //-- Reset the interval value back to default
        call SaveReal(udg_PDMS_Table, targetId, pdType + 3000, udg_PDMS_Interval[pdType])
    endif
    //-- Null local handles
    set t = null
    set source = null
    set target = null
endfunction
//===========================================================================
//= This function applies periodic damage of custom stacking version
//===========================================================================
function PDMS_ApplyCustomStack takes unit source, real damage, unit target, real duration, integer pdType returns boolean
    //-- Local vars
    local timer t
    local integer timerId
    local real damageStackDuration      //-- Variable to store the time remaining of a damage from a new instance
    local effect sfx                    //-- Variable to load special effect
    //-- Variables to get the IDs of each actor
    local integer targetId
    //-- Variable to check for active instance
    local integer isActive
    //-- Variables to load the value of current instance if exist
    local real currentDuration
    local integer damageCounter
    //-- Type Check!
    if pdType < 1 or pdType > udg_PDMS_PdTypeIndex then
        call BJDebugMsg("[|cffff0000PDMS Error|r] - " + "Periodic Damage Type does not exist")
        return false
    endif
    //-- Dead check
    if IsUnitType(target, UNIT_TYPE_DEAD) or GetWidgetLife(target) <= 0.405 then
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffff0000PDMS Error|r] - " + "Trying to apply PDMS on dead unit")
        endif
        return false
    endif
    //-- Immunity check
    if PDMS_IsImmune(target, pdType) == true then
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffff0000PDMS Error|r] - " + "Trying to apply PDMS on a unit that's immune")
        endif
        return false
    endif
    //-- Get the IDs
    set targetId = GetHandleId(target)
    set damageCounter = LoadInteger(udg_PDMS_Table, targetId, pdType + 6000)
    //-- Cap check
    if damageCounter == udg_PDMS_StackCap[pdType] then
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffff0000PDMS Error|r] - " + "Stack has reached maximum cap")
        endif
        return false
    endif
 
    //-- Load the value to check for active instances
    set isActive = LoadInteger(udg_PDMS_Table, targetId, pdType)
    //-- Load the value of current instance if exist
    set currentDuration  = LoadReal(udg_PDMS_Table, targetId, pdType + 2000)
    //-- Load the effect handle
    set sfx = LoadEffectHandle(udg_PDMS_Table, targetId, pdType + 5000)
    //-- Create new sfx when there is still none
    if sfx == null then
        set sfx = AddSpecialEffectTarget(udg_PDMS_DefaultSfxModel[pdType], target, udg_PDMS_DefaultAttachPoint[pdType])
        call SaveEffectHandle(udg_PDMS_Table, targetId, pdType + 5000, sfx)
    endif
    //-- Increment damageCounter then update the storage
    set damageCounter = damageCounter + 1
    call SaveInteger(udg_PDMS_Table, targetId, pdType + 6000, damageCounter)
    //-- Save the new damage
    call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 7000 + damageCounter, damage)
    //-- StackedEvent
    set udg_PDMS_LastStackedType = pdType
    set udg_PDMS_LastStackedSource = source
    set udg_PDMS_LastStackedTarget = target
    set udg_PDMS_StackedEvent = 1.00
    set udg_PDMS_StackedEvent = 0.00
    //-- Check duration and overwrite if it is stronger than the current one
    if duration > currentDuration then
        //-- Save the new duration of the damage from this instance
        set damageStackDuration = duration
        call SaveReal(udg_PDMS_Table, targetId, (10000 * pdType) + 8000 + damageCounter, damageStackDuration)
        //-- Overwrite the old duration with the new one
        set currentDuration = duration
        call SaveReal(udg_PDMS_Table, targetId, pdType + 2000, currentDuration)
    endif
    //-- Always replace source with the latest one
    call SaveUnitHandle(udg_PDMS_Table, targetId, pdType + 1000, source)
    //-- Start a timer if there is no active instances yet
    if isActive == 0 then
        //-- Save Interval
        call SaveReal(udg_PDMS_Table, targetId, pdType + 3000, udg_PDMS_Interval[pdType])
        //-- Create and start the timer
        set t = CreateTimer()
        call TimerStart(t, udg_PDMS_Timeout, true, function PDMS_LoopCustomStack)
        //-- Debug
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Timer is successfully started")
        endif
        //-- Get its ID to attach data
        set timerId = GetHandleId(t)
        //-- Attach these data:
        //-- - target handle
        //-- - pd type
        call SaveUnitHandle(udg_PDMS_Table, timerId, 0, target)
        call SaveInteger(udg_PDMS_Table, timerId, 1, pdType)
        //-- switch active flag
        call SaveInteger(udg_PDMS_Table, targetId, pdType, 1)
        //-- AppliedEvent
        set udg_PDMS_LastAppliedType = pdType
        set udg_PDMS_LastAppliedSource = source
        set udg_PDMS_LastAppliedTarget = target
        set udg_PDMS_AppliedEvent = 1.00
        set udg_PDMS_AppliedEvent = 0.00
        //-- Null local handle
        set t = null
    endif
    //-- Null local handles
    set sfx = null
    return true
endfunction
//===========================================================================
//= These functions below are callbacks for GUI triggers
//===========================================================================
function Trig_PDMS_OnDeath takes nothing returns boolean
    local integer i = 0
    local unit target = GetTriggerUnit()
    loop
        set i = i + 1
        exitwhen i > udg_PDMS_PdTypeIndex
        if PDMS_IsUnitInEffect(target, i) then
            call PDMS_RemoveByType(target, i)
        endif
    endloop
    set target = null
    return false
endfunction
function Trig_PDMS_SetImmunity takes nothing returns boolean
    //-- Local variables
    local unit target = udg_PDMS_Param_Target
    local integer pdType = udg_PDMS_Param_Type
    local boolean flag = udg_PDMS_Param_Flag
    //-- Make API call
    call PDMS_SetImmunity(target, pdType, flag)
    set udg_PDMS_Param_Target = null
    set udg_PDMS_Param_Type = 0
    set udg_PDMS_Param_Flag = false
    //-- Null local handle
    set target = null
    return false
endfunction
function Trig_PDMS_Apply takes nothing returns boolean
    //-- Local variables
    local unit s = udg_PDMS_Param_Source
    local unit t = udg_PDMS_Param_Target
    local real dmg = udg_PDMS_Param_Damage
    local real dur = udg_PDMS_Param_Duration
    local integer pdType = udg_PDMS_Param_Type
    //-- Make API call
    if udg_PDMS_CanStackDamage[pdType] == false then
        call PDMS_ApplyNonStack(s, dmg, t, dur, pdType)
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Applying Non-Stacking Periodic Damage")
        endif
    elseif udg_PDMS_NonWc3Style[pdType] == true then
        call PDMS_ApplyCustomStack(s, dmg, t, dur, pdType)
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Applying Custom Stacking Periodic Damage")
        endif
    elseif udg_PDMS_NonWc3Style[pdType] == false then
        call PDMS_ApplyWc3Stack(s, dmg, t, dur, pdType)
        if udg_PDMS_Debug == true then
            call BJDebugMsg("[|cffffcc00System Debug|r] - " + "Applying Wc3-Style Stacking Periodic Damage")
        endif
    endif
    //-- Null global parameters
    set udg_PDMS_Param_Source = null
    set udg_PDMS_Param_Target = null
    set udg_PDMS_Param_Damage = 0.0
    set udg_PDMS_Param_Duration = 0.0
    set udg_PDMS_Param_Type = 0
    //-- Null local handle
    set s = null
    set t = null
    return false
endfunction
function Trig_PDMS_Remove takes nothing returns boolean
    local unit target = udg_PDMS_Param_Target
    local integer pdType = udg_PDMS_Param_Type
    if pdType == 0 then
        call PDMS_RemoveAll(target)
    else
        call PDMS_RemoveByType(target, pdType)
    endif
    //-- Null global parameters
    set udg_PDMS_Param_Target = null
    set udg_PDMS_Param_Type = 0
    //-- Null local handle
    set target = null
    return false
endfunction
function Trig_PDMS_Check takes nothing returns boolean
    local unit target = udg_PDMS_Param_Target
    local integer pdType = udg_PDMS_Param_Type
    set udg_PDMS_IsUnitInEffect = PDMS_IsUnitInEffect(target, pdType)
    //-- Null global parameters
    set udg_PDMS_Param_Target = null
    set udg_PDMS_Param_Type = 0
    //-- Null local handle
    set target = null
    return false
endfunction
function Trig_PDMS_Register takes nothing returns boolean
    //-- Assign locals
    local attacktype AT = udg_PDMS_Register_AttackType
    local damagetype DT = udg_PDMS_Register_DamageType
    local boolean canKill = udg_PDMS_Register_CanKill
    local boolean canStackDamage = udg_PDMS_Register_StackDamage
    local boolean stackStyle = udg_PDMS_Register_NonWc3Style
    local integer stackCap = udg_PDMS_Register_StackCap
    local real interval = udg_PDMS_Register_Interval
    local string sfxModel = udg_PDMS_Register_SfxModel
    local string attachPoint = udg_PDMS_Register_AttachPoint
    //-- Make API call
    set udg_PDMS_LastRegisteredType = PDMS_RegisterType(AT, DT, canStackDamage, stackStyle, stackCap, canKill, interval, sfxModel, attachPoint)
    //-- Null Globals
    set udg_PDMS_Register_AttackType = null
    set udg_PDMS_Register_DamageType = null
    set udg_PDMS_Register_CanKill = false
    set udg_PDMS_Register_StackDamage = false
    set udg_PDMS_Register_NonWc3Style = false
    set udg_PDMS_Register_StackCap = 0
    set udg_PDMS_Register_Interval = 0.0
    set udg_PDMS_Register_SfxModel = ""
    set udg_PDMS_Register_AttachPoint = ""
    //-- Null local handles
    set AT = null
    set DT = null
    return false
endfunction
//===========================================================================
//= This function starts on Initialization
//===========================================================================
function InitTrig_PDMS takes nothing returns nothing
    //-- Local variables
    local integer i = 0
    local trigger deathTrg
 
    //-- Create triggers
    set deathTrg = CreateTrigger()
    set udg_PDMS_Apply = CreateTrigger()
    set udg_PDMS_Remove = CreateTrigger()
    set udg_PDMS_Check = CreateTrigger()
    set udg_PDMS_Register = CreateTrigger()
    set udg_PDMS_SetImmunity = CreateTrigger()
    //-- Register death event
    loop
        call TriggerRegisterPlayerUnitEvent(deathTrg, Player(i), EVENT_PLAYER_UNIT_DEATH, null)
        set i = i + 1
        exitwhen i == bj_MAX_PLAYER_SLOTS
    endloop
 
    //-- Register functions
    call TriggerAddCondition(deathTrg, Condition(function Trig_PDMS_OnDeath))
    call TriggerAddCondition(udg_PDMS_Apply, Condition(function Trig_PDMS_Apply))
    call TriggerAddCondition(udg_PDMS_Remove, Condition(function Trig_PDMS_Remove))
    call TriggerAddCondition(udg_PDMS_Check, Condition(function Trig_PDMS_Check))
    call TriggerAddCondition(udg_PDMS_Register, Condition(function Trig_PDMS_Register))
    call TriggerAddCondition(udg_PDMS_SetImmunity, Condition(function Trig_PDMS_SetImmunity))
    //-- Initialize hashtable
    set udg_PDMS_Table = InitHashtable()
    //-- Initialize config
    call PDMS_Configuration()
    //-- Null local handle
    set deathTrg = null
endfunction

FAQ

Q: What is P.D.M.S.?

A: P.D.M.S. stands for Periodic Damage Management System. It's a lightweight and GUI-friendly system for applying custom damage-over-time effects (e.g. poison, burn) in Warcraft 3, with support for both stacking and non-stacking behavior.

Q: Is this system compatible with GUI or do I need to learn JASS?

A: Yes! P.D.M.S. is fully compatible with GUI. You don’t need any JASS knowledge to use it — everything can be done through triggers.

Q: What versions of Warcraft 3 is this system tested on?

A: P.D.M.S. has been tested on Warcraft 3 v1.26 and v1.31.1. It may work on other versions as well, but these two are officially confirmed. Feel free to contact me if you test this on newer version, whether this works or breaks, it will be good information nonetheless.

Q: Can I create my own custom periodic damage types?

A: Yes! You can define custom damage types with parameters such as damage interval, special effect, damage type, stacking behavior, whether the damage can kill, and more.

Q: Does the system support stacking damage behavior like poison?

A: Absolutely. P.D.M.S. mimics Warcraft 3’s native stacking and non-stacking behavior. Stackable damage types accumulate damage from different sources and expire independently.

Q: Can this system detect when an effect is applied, refreshed, stacked, or expired?

A: Yes. P.D.M.S. offers custom game events like PDMS_AppliedEvent, PDMS_StackedEvent, PDMS_RefreshedEvent, and PDMS_ExpiredEvent to hook into those exact moments.

Q: Does P.D.M.S. work with damage detection system like Damage Engine?

A: Yes, with precautions. P.D.M.S. includes an internal flag PDMS_IsDealingDamage to prevent recursive loops when used alongside Damage Detection Systems. It has been tested with Damage Engine 3.8.

Q: Will this work with the new Damage Event Native?

A: In theory, yes — but it has not been officially tested yet. Use with caution and monitor behavior if you integrate it.

Q: Is this system efficient and performance-friendly?

A: Yes. It uses hashtables to efficiently manage and track instances, minimizing overhead and maintaining clean separation of logic.

Q: Do you plan to release a vJASS or Lua version?

A: Yes, but most likely not anytime soon. Probably some time in the future, I cannot promise.

Change Log

Version 1.0
  • First public release
Previews
Contents

PDMS 1.0 (Map)

Top