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

Crowd Control & Tenacity

title-png.418055
intro-png.418054

Crowd Control and Tenacity are 2 systems that allows for fine control over stuns, silences, slows, fear, taunt, knockbacks and many other types of disables and their duration. With these systems you will be able to detect when a unit is disabled as well as change parameters of the disable being applied.

howto-png.418052

Crowd Control
Crowd Control uses a set of abilities and buffs to apply and detect when a unit is disabled. Many other systems do implement ways to stun or slow a target, but only this one provides events to detect and modify the parameters of a crowd control effect. See the API section for a full overview of all available functions.

Tenacity
You might have heard this word from League of Legends and it basically means crowd control resistance. There are 3 basic types of tenacity implemented. The first is the default one, and it stacks multiplicatively with each bonus. The second is a flat bonus, and it stacks additively. Finally there is an offset bonus where a fixed amount of duration can be either add or subtracted from the disable duration. Tenacity is implemented natively inside the Crowd Control library.

import-png.418053

Importing those 2 systems is pretty simple. Copy all the 14 buffs with the CC prefix over (BU00 to BU13), the 15 abilities with the CC prefix as well (U000 to U014) and finally copy the dummy unit. After that just copy the the scripts to the Trigger Editor and match the raw codes with necessary. The dummy unit raw code must be matched inside the Utilities library, one of the requirements.

cooltext426617741956022-png.418051

vJASS:
CROWD_CONTROL_SILENCE
CROWD_CONTROL_STUN
CROWD_CONTROL_SLOW
CROWD_CONTROL_SLOW_ATTACK
CROWD_CONTROL_BANISH
CROWD_CONTROL_ENSNARE
CROWD_CONTROL_PURGE
CROWD_CONTROL_HEX
CROWD_CONTROL_SLEEP
CROWD_CONTROL_CYCLONE
CROWD_CONTROL_ENTANGLE
CROWD_CONTROL_DISARM
CROWD_CONTROL_FEAR
CROWD_CONTROL_TAUNT
CROWD_CONTROL_KNOCKBACK
CROWD_CONTROL_KNOCKUP
vJASS:
/* ------------------------------------------- Disarm ------------------------------------------- */
function DisarmUnit takes unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitDisarmed takes unit target returns boolean

/* -------------------------------------------- Fear -------------------------------------------- */
function FearUnit takes unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitFeared takes unit target returns boolean

/* -------------------------------------------- Taunt ------------------------------------------- */
function TauntUnit takes unit source, unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitTaunted takes unit target returns boolean

/* ------------------------------------------ Knockback ----------------------------------------- */
function KnockbackUnit takes unit target, real angle, real distance, real duration, string model, string point, boolean onCliff, boolean onDestructable, boolean onUnit, boolean stack returns nothing

function IsUnitKnockedBack takes unit target returns boolean

/* ------------------------------------------- Knockup ------------------------------------------ */
function KnockupUnit takes unit target, real maxHeight, real duration, string model, string point, boolean stack returns nothing

function IsUnitKnockedUp takes unit target returns boolean

/* ------------------------------------------- Silence ------------------------------------------ */
function SilenceUnit takes unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitSilenced takes unit target returns boolean

/* -------------------------------------------- Stun -------------------------------------------- */
function StunUnit takes unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitStunned takes unit target returns boolean

/* ---------------------------------------- Movement Slow --------------------------------------- */
function SlowUnit takes unit target, real amount, real duration, string model, string point, boolean stack returns nothing

function IsUnitSlowed takes unit target returns boolean

/* ----------------------------------------- Attack Slow ---------------------------------------- */
function SlowUnitAttack takes unit target, real amount, real duration, string model, string point, boolean stack returns nothing

function IsUnitAttackSlowed takes unit target returns boolean

/* ------------------------------------------- Banish ------------------------------------------- */
function BanishUnit takes unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitBanished takes unit target returns boolean

/* ------------------------------------------- Ensnare ------------------------------------------ */
function EnsnareUnit takes unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitEnsnared takes unit target returns boolean

/* -------------------------------------------- Purge ------------------------------------------- */
function PurgeUnit takes unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitPurged takes unit target returns boolean

/* --------------------------------------------- Hex -------------------------------------------- */
function HexUnit takes unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitHexed takes unit target returns boolean

/* -------------------------------------------- Sleep ------------------------------------------- */
function SleepUnit takes unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitSleeping takes unit target returns boolean

/* ------------------------------------------- Cyclone ------------------------------------------ */
function CycloneUnit takes unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitCycloned takes unit target returns boolean

/* ------------------------------------------ Entangle ------------------------------------------ */
function EntangleUnit takes unit target, real duration, string model, string point, boolean stack returns nothing

function IsUnitEntangled takes unit target returns boolean

/* ------------------------------------------- Dispel ------------------------------------------- */
function UnitDispelCrowdControl takes unit target, integer id returns nothing

function UnitDispelAllCrowdControl takes unit target returns nothing

/* ------------------------------------------- Events ------------------------------------------- */
function RegisterCrowdControlEvent takes integer id, code c returns nothing

function RegisterAnyCrowdControlEvent takes code c returns nothing

function GetCrowdControlUnit takes nothing returns unit

function GetCrowdControlType takes nothing returns integer

function GetCrowdControlDuration takes nothing returns real

function GetCrowdControlAmount takes nothing returns real

function GetCrowdControlModel takes nothing returns string

function GetCrowdControlBone takes nothing returns string

function GetCrowdControlStack takes nothing returns boolean

function GetCrowdControlRemaining takes unit target, integer id returns real

function GetTauntSource takes nothing returns unit

function GetKnockbackAngle takes nothing returns real

function GetKnockbackDistance takes nothing returns real

function GetKnockupHeight takes nothing returns real

function GetKnockbackOnCliff takes nothing returns boolean

function GetKnockbackOnDestructable takes nothing returns boolean

function GetKnockbackOnUnit takes nothing returns boolean

function SetCrowdControlUnit takes unit u returns nothing

function SetCrowdControlType takes integer id returns nothing

function SetCrowdControlDuration takes real duration returns nothing

function SetCrowdControlAmount takes real amount returns nothing

function SetCrowdControlModel takes string model returns nothing

function SetCrowdControlBone takes string point returns nothing

function SetCrowdControlStack takes boolean stack returns nothing

function SetTauntSource takes unit u returns nothing

function SetKnockbackAngle takes real angle returns nothing

function SetKnockbackDistance takes real distance returns nothing

function SetKnockupHeight takes real height returns nothing

function SetKnockbackOnCliff takes boolean onCliff returns nothing

function SetKnockbackOnDestructable takes boolean onDestructable returns nothing

function SetKnockbackOnUnit takes boolean onUnit returns nothing
vJASS:
function GetUnitTenacity takes unit u returns real

function GetUnitTenacityFlat takes unit u returns real

function GetUnitTenacityOffset takes unit u returns real

function SetUnitTenacity takes unit u, real value returns nothing

function SetUnitTenacityFlat takes unit u, real value returns nothing

function SetUnitTenacityOffset takes unit u, real value returns nothing

function UnitAddTenacity takes unit u, real value returns nothing

function UnitAddTenacityFlat takes unit u, real value returns nothing

function UnitAddTenacityOffset takes unit u, real value returns nothing

function UnitRemoveTenacity takes unit u, real value returns nothing

function GetTenacityDuration takes unit u, real duration returns real

function RegisterTenacityUnit takes unit u returns Tenacity

function DisplayTenacityStatus takes unit u returns nothing

function UnitAddTenacityTimed takes unit u, real value, real duration returns nothing

function UnitAddTenacityFlatTimed takes unit u, real value, real duration returns nothing

function UnitAddTenacityOffsetTimed takes unit u, real value, real duration returns nothing
Lua:
-- ------------------------------------------- Disarm ------------------------------------------- --
function DisarmUnit(unit, duration, model, point, stack)

function IsUnitDisarmed(unit)

-- -------------------------------------------- Fear -------------------------------------------- --
function FearUnit(unit, duration, model, point, stack)

function IsUnitFeared(unit)

-- -------------------------------------------- Taunt ------------------------------------------- --
function TauntUnit(source, target, duration, model, point, stack)

function IsUnitTaunted(unit)

-- ------------------------------------------ Knockback ----------------------------------------- --
function KnockbackUnit(unit, angle, distance, duration, model, point, onCliff, onDestructable, onUnit, stack)

function IsUnitKnockedBack(unit)

-- ------------------------------------------- Knockup ------------------------------------------ --
function KnockupUnit(unit, height, duration, model, point, stack)

function IsUnitKnockedUp(unit)

-- ------------------------------------------- Silence ------------------------------------------ --
function SilenceUnit(unit, duration, model, point, stack)

function IsUnitSilenced(unit)

-- -------------------------------------------- Stun -------------------------------------------- --
function StunUnit(unit, duration, model, point, stack)

function IsUnitStunned(unit)

-- ---------------------------------------- Movement Slow --------------------------------------- --
function SlowUnit(unit, amount, duration, model, point, stack)

function IsUnitSlowed(unit)

-- ----------------------------------------- Attack Slow ---------------------------------------- --
function SlowUnitAttack(unit, amount, duration, model, point, stack)

function IsUnitAttackSlowed(unit)

-- ------------------------------------------- Banish ------------------------------------------- --
function BanishUnit(unit, duration, model, point, stack)

function IsUnitBanished(unit)

-- ------------------------------------------- Ensnare ------------------------------------------ --
function EnsnareUnit(unit, duration, model, point, stack)

function IsUnitEnsnared(unit)

-- -------------------------------------------- Purge ------------------------------------------- --
function PurgeUnit(unit, duration, model, point, stack)

function IsUnitPurged(unit)

-- --------------------------------------------- Hex -------------------------------------------- --
function HexUnit(unit, duration, model, point, stack)

function IsUnitHexed(unit)

-- -------------------------------------------- Sleep ------------------------------------------- --
function SleepUnit(unit, duration, model, point, stack)

function IsUnitSleeping(unit)

-- ------------------------------------------- Cyclone ------------------------------------------ --
function CycloneUnit(unit, duration, model, point, stack)

function IsUnitCycloned(unit)

-- ------------------------------------------ Entangle ------------------------------------------ --
function EntangleUnit(unit, duration, model, point, stack)

function IsUnitEntangled(unit)

-- ------------------------------------------- Dispel ------------------------------------------- --
function UnitDispelCrowdControl(unit, type)

function UnitDispelAllCrowdControl(unit)

-- ------------------------------------------- Events ------------------------------------------- --
function RegisterCrowdControlEvent(type, code)

function RegisterAnyCrowdControlEvent(code)

function GetCrowdControlUnit()

function GetCrowdControlType()

function GetCrowdControlDuration()

function GetCrowdControlAmount()

function GetCrowdControlModel()

function GetCrowdControlBone()

function GetCrowdControlStack()

function GetCrowdControlRemaining(unit, type)

function GetTauntSource()

function GetKnockbackAngle()

function GetKnockbackDistance()

function GetKnockupHeight()

function GetKnockbackOnCliff()

function GetKnockbackOnDestructable()

function GetKnockbackOnUnit()

function SetCrowdControlUnit(unit)

function SetCrowdControlType(type)

function SetCrowdControlDuration(duration)

function SetCrowdControlAmount(amount)

function SetCrowdControlModel(model)

function SetCrowdControlBone(point)

function SetCrowdControlStack(stack)

function SetTauntSource(unit)

function SetKnockbackAngle(angle)

function SetKnockbackDistance(distance)

function SetKnockupHeight(height)

function SetKnockbackOnCliff(onCliff)

function SetKnockbackOnDestructable(onDestructable)

function SetKnockbackOnUnit(onUnit)
Lua:
function GetUnitTenacity(unit)

function GetUnitTenacityFlat(unit)

function GetUnitTenacityOffset(unit)

function SetUnitTenacity(unit, value)

function SetUnitTenacityFlat(unit, value)

function SetUnitTenacityOffset(unit, value)

function UnitAddTenacity(unit, value)

function UnitAddTenacityFlat(unit, value)

function UnitAddTenacityOffset(unit, value)

function UnitRemoveTenacity(unit, value)

function GetTenacityDuration(unit, duration)

function RegisterTenacityUnit(unit)

function DisplayTenacityStatus(unit)

function UnitAddTenacityTimed(unit, value, duration)

function UnitAddTenacityFlatTimed(unit, value, duration)

function UnitAddTenacityOffsetTimed(unit, value, duration)
01/01/2023
  • Release
02/04/2023
  • Now uses version 1.9 of the my Utilities library.
Contents

Crowd Control & Tenacity (Map)

Crowd Control & Tenacity (Map)

Reviews
MyPad
A largely compact resource that incorporates and handles common CC effects swimmingly well, drastically reducing the groundwork of the mapper. It has already seen usage in other approved spell resources (mostly Hero Concepts), so approval for this...
A largely compact resource that incorporates and handles common CC effects swimmingly well, drastically reducing the groundwork of the mapper. It has already seen usage in other approved spell resources (mostly Hero Concepts), so approval for this bundle is merely a formality.

There are certain parts of the code that could use some refactoring for clarity that I didn't notice before. Nothing too major though, so it should be easy to deal with.

Utilities library:
  • In the function GetEnemyUnitsInRange(player enemyOf, real x, real y, real aoe, boolean structures, boolean magicImmune), multiple versions of what is functionally the same loop exists. At the cost of evaluating the provided boolean values once, you can rewrite it as the following:

    JASS:
    loop
        set w = FirstOfGroup(h)
        exitwhen w == null
            if IsUnitEnemy(w, enemyOf) and UnitAlive(w) and /*
           */ (structures or (not IsUnitType(w, UNIT_TYPE_STRUCTURE))) and /*
           */ (magicImmune or (not IsUnitType(w, UNIT_TYPE_MAGIC_IMMUNE))) then
                call GroupAddUnit(g, w)
            endif
        call GroupRemoveUnit(h, w)
    endloop

  • The same applies for the function UnitDamageArea(unit source, [...], boolean structures, boolean magicImmune, boolean allies). You can rewrite it like this:

    JASS:
    loop
        set w = FirstOfGroup(h)
        exitwhen w == null
            if (UnitAlive(w) and w != source) and /*
            */ (allies or IsUnitEnemy(w, enemyOf)) and /*
            */ (structures or (not IsUnitType(w, UNIT_TYPE_STRUCTURE))) and /*
            */ (magicImmune or (not IsUnitType(w, UNIT_TYPE_MAGIC_IMMUNE))) then
                call UnitDamageTarget(source, w, damage, true, false, atkType, dmgType, null)
            endif
        call GroupRemoveUnit(h, w)
    endloop

  • Same point applies once more for the function UnitDamageCone(unit source, ...).
Crowd Control library:
  • These are quite minor, so feel free to implement changes how you see fit.

  • In the static method CrowdControl.onEvent(integer key), the nested loop inside the if-then block can be moved outside, to reduce the number of indentations.

    Code:
    private static method onEvent takes integer key returns nothing
        local integer i = 0
        local integer next = -1
        local integer prev = -2
        set count = count + 1
        if count - CROWD_CONTROL_KNOCKUP >= RECURSION_LIMIT then
            set count = count - 1
            set .key = key
        endif
        loop
            exitwhen type[key] == next or (i - CROWD_CONTROL_KNOCKUP > RECURSION_LIMIT)
            set next = type[key]
       
            // The following block below might appear to be a messy implementation of a continue statement, because it is.
            // The inner loop's presence will guarantee that control will be restored to the outer loop upon exiting.
            loop
                if event[next] != null then
                    call TriggerEvaluate(event[next])
                endif
                if type[key] != next then
                    set i = i + 1
                    exitwhen true
                endif
                exitwhen next == prev
               
                call TriggerEvaluate(trigger)
                if type[key] != next then
                    set i = i + 1
                    set prev = next
                endif
                exitwhen true
            endloop
        endloop
        set count = count - 1
        set .key = key
    endmethod

Status:

  • Approved

Version Reviewed:

  • 1.0
 
Level 13
Joined
Oct 18, 2013
Messages
691
Haven't looked in the testmap yet, but does the tenacity apply to all of those debuff types? Is the offset a way of of specifying tenacity for a certain debuff type?
 
Level 39
Joined
Feb 27, 2007
Messages
5,014
Generally, no. This system heavily relies on using call BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_HERO, 0, duration) to modify the duration of CC-applying abilities right before a dummy unit casts them. The alternative would require running a bunch of timers to manually remove the buff after its appropriate duration, which is a lot more work.

You could go back as far as 1.30 when they were introduced (I believe that's the patch).
 
Generally, no. This system heavily relies on using call BlzSetAbilityRealLevelField(ability, ABILITY_RLF_DURATION_HERO, 0, duration) to modify the duration of CC-applying abilities right before a dummy unit casts them. The alternative would require running a bunch of timers to manually remove the buff after its appropriate duration, which is a lot more work.

You could go back as far as 1.30 when they were introduced (I believe that's the patch).
Wouldn't infinite duration along with timers as you mentioned do about the same thing?
 
Level 39
Joined
Feb 27, 2007
Messages
5,014
Yes... but it requires either running a timer for every instance of CC or putting all the instances of all CC into a big list that a single timer iterates over. And making sure that additional CC of the same type doesn't get removed early by another timer expiring before the subsequent one has finished (when the buff should be removed). It's not complicated, it just fundamentally changes how this system is designed. The beauty of using buffs and the same static cc-applying abilities is that the stacking and removal stuff is taken care of for you without having to do any of that.
 
Is it possible to modify this in order for it to work on older patches without losing too many features?
Only method I can think of that wont be pretty but will be handled internally without using timers, so it wont run into the issues pyro said, is using the same abilities but with many, many levels for the duration.
 
Level 8
Joined
Aug 5, 2014
Messages
194
Hello, i have an issue when try to use examples from this system along with JASS GUI version of relativistic missiles. It shows error when compile which states following:
"Member name already in use by a parent type."
It point at "private onFinish method" in the case in thunderbolt trigger.
And then point at onFinish metond in the "Missiles struct "in "Missiles" library, as "First time used here".
This problem is absent in Tenacity example map, where used, as it seems, different version of Missiles system, preharps non GUI JASS, but how i can fix this incompatibility with GUI version?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,468
Hello !

Nice system.
Does "knockback" knocks wards or units with "UnitPropWindow" equal to 0 ? Or skips them ?

Edit: I just realized that W3 doesn't allow to check if a unit is a ward or not. What a shame :S
They might either have a movement speed of 0 or may not even have the 'Amov' ability at all. Try checking for those; you will likely find either option viable.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,542
Hello !

Nice system.
Does "knockback" knocks wards or units with "UnitPropWindow" equal to 0 ? Or skips them ?

Edit: I just realized that W3 doesn't allow to check if a unit is a ward or not. What a shame :S
There's like 5 wards in Warcraft 3 by default. It wouldn't be too difficult to give each of them a hidden passive ability that you can then check in your Conditions. These pseudo classifications are very easy to work with:
  • Conditions
    • (Level of Ward (Class) for Knockback_Target) Equal to 0
 
Top