• 🏆 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!

SilenceEx - Everything you don't know about Silence.

Status
Not open for further replies.
Level 9
Joined
Jul 30, 2012
Messages
156

SilenceEx - Complete guide about Silence

This is not the silence you're looking for.

Introduction

This is a complete guide about Silence - the mechanic that disables a unit's abilities. Not just the ordinary Silence that we all know about, but all forms of Silence present in the game, and how they work internally.

General Information

Just like everything in the game, Silence is handled internally with counters. Every ability object has two counters for that: the Silence counter, and the Hide counter. These two things are handled by the same functions in game.dll:
JASS:
void CAbility::Disable(int Count, bool Hide);
void CAbility::Enable(int Count, bool Hide);
They both take two arguments: the Count argument is the number of "silence charges" that will be placed on that ability. The Disable function will add that number to the Silence counter of the ability, and the Enable function will subtract that number.

The Hide boolean determines if the ability will not only be silenced but also hidden from the command card. If it's true, the integer Count will also be added or subtracted from the Hide counter of the ability.

If, at any moment in the game, the Silence counter of an ability is greater than zero, that ability is silenced. And if the Hide counter is greater than zero, the icon will be hidden from the command card.

What are the effects of Silence?
  • An active ability cannot be cast while silenced, the icon gets disabled and cannot be clicked, and you can't issue the casting order with triggers.
  • If an ability is being channeled when it gets silenced the channeling will stop. If a unit that has been issued the casting order gets silenced before the casting it will either stop, or resume its next order.
  • A passive ability will lose all its effects when silenced. Chance-based passives will not trigger at all.
Also you should notice that silenced abilities do not display their cooldown indicator.

What abilities can be silenced? - Almost everything can be silenced, active abilities, passives, even Move and Attack can be silenced/hidden. There's a small number of abilities that can never be silenced, CAbility::Disable will simply do nothing and return when it encounters these abilities. I haven't found all of them, but I know that auras and Reincarnation fall into this category.

What happens if an ability is hidden? - If an ability is both silenced and hidden there is no difference. But there are things in the game that will hide an ability without silencing it (those things will be discussed later).

If an ability is hidden but not silenced:
  • You cannot cast the ability nor issue the casting order with triggers, but momentary casts of that ability will not be interrupted.
  • Passive abilities will still have their effects if they are hidden and not silenced.
Remarks
  • Disabling an ability for a player (either with a requirement or with SetPlayerAbilityAvailable) is not related to Silence at all
  • Orb of Slow is not related to silence as well. The ability inside OoS is NOT silenced, although the icon is disabled, it still has all of its effects, and it does display the cooldown indicator.

Regular Silence

This is the normal Silence that we all know about. It's used only by 4 abilities in the game: Silence, Drunken Haze, Soul Burn and Cloud. All of these abilities stack with each other, but multiple instances of the same ability do not stack.

Regular Silence is handled by the following function:
JASS:
CUnit::Silence(bool Silence)
The boolean argument determines if Silence is being applied or removed. The function will enumerate all abilities of a unit and call a function to "ask" that ability if it wants to be silenced. As you may expect, all passives and item abilities will say false and will not be silenced by this function. For the active abilities, there's a special condition that they check:
An active ability will not be silenced if it has only 1 level and 0 manacost.
Most active abilities will check for that condition, but there are some abilities that will always return true regardless of that. It's also worth noting that, in addition to this condition, the Channel ability will also check for the Universal Spell flag: if it's not Universal, it will be silenced.

The Silence function will then call CAbility::Disable or CAbility::Enable, depending on the bool argument, for all abilities that were eligible for silence. After that, it will also increase or decrease the Silence counter of the unit by 1. Whenever an ability is added to a unit, its Silence counter is set to the value of the unit. This ensures that new abilities will be automatically silenced.

This function will never hide the abilities, only silence them.

SilenceEx - The special Silence
JASS:
CUnit::SilenceEx(bool Silence, bool Hide, bool Physical)
This function is used by a very large number of things in the game, like Hex, Doom, PauseUnit, Staff of Teleportation and many others.

It doesn't check any condition: actives, passives, Move, Attack and everything else will be disabled. The only exception is the Physical argument, if it's true, it will silence only "Physical" abilities. (This is used by the Ethereal state to prevent you from attacking and doing things like building a structure)

SilenceEx will call CAbility::Enable/Disable, passing the Hide boolean to them, then it will increase/decrease the Silence counter of the unit by 1, and will do the same to the Hide counter if Hide is true. This way, every new ability will be automatically silenced/hidden.

However, many spells that make use of this function will explictly set exceptions to it: they do this by directly calling CAbility::Enable before the silence. This makes the Silence counter of that ability drop to -1, after calling SilenceEx, the counter will go back to 0, and the ability will not be silenced.

Examples of abilities that use this trick:
  • Hex - disables everything except Move
  • Doom - disables everything except Move and Attack
  • Bladestorm - disables everything except Move, Attack, Skillmenu and item abilities
  • Burrow - when morphed disables everything except Move, Attack, Spellbook and itself

Tips and Tricks

Detecting Silence


There are 3 very special abilities in Warcraft. Their names are Defend, Magic Defense and Immolation. These abilities are known as detector abilities because they can be used to detect a variety of events.

What happens is that they fire an order event whenever they are silenced, or removed from a unit. Considering that the game will always silence an ability before removing it, that explains why the order is issued twice on removal. The game will also apply SilenceEx to a unit when it dies.

By having any of these abilities present in a unit all the time, we can detect when that unit gets silenced. Defend and Magic Defense can only detect SilenceEx, while Immolation can detect regular Silence as well.

Manipulating the Silence condition.


The Silence function is called when a debuff is applied, and again when that buff is removed. If some ability is eligible for Silence when it is applied, but not eligible when it gets removed, that ability will be silenced forever. Likewise, if it was not eligible when silenced, and eligible when unsilenced, the Silence counter of that ability will drop to -1, making it immune to all sources of silence.

The condition for many abilities to be silence-immune is to have only 1 level and 0 manacost. We can manipulate this condition in two ways:
  • With Engineering Upgrade - Engineering Upgrade can transform an ability into another at runtime, so we can just transform to an ability that fulfills the condition and transform back after the unit is silenced. The only problem is that Engineering Upgrade can not be used with non-hero units ([thread=270930]really?[/thread]).
  • With IncUnitAbilityLevel - as documented here, the function IncUnitAbilityLevel can raise an ability's level over the maximum, but those extra levels will have all data fields set to 0. So we can make an ability that has only 1 level but a non-zero manacost, and when we call this function, the manacost of the ability will become 0, fulfilling the condition.

Conclusion

This guide presented a detailed description of how the Silence mechanism works internally, as well as some tricks that take advantage of that behaviour.
This guide is NOT FINISHED YET, there are still a lot of hacks and tricks about Silence that soon I'll be writing about, together with some code snippets and a test map.
If you have any suggestions or doubts about the contents feel free to post them here.
 
Level 9
Joined
Jul 30, 2012
Messages
156
The following code demonstrates some concepts of the guide by allowing you to enable and disable a single ability of a unit, without affecting others.

This function will not interrupt the unit's orders in any way, and it will not silence any abilities except the one you want.

As I said before, not all abilities can work with this procedure. As a requirement, an ability must have only one level and a non-null manacost. And even if this condition is met, not every ability will work.

It's worth noticing that Move and Attack WILL WORK with that function if you set their manacost to something higher than 0. This can be used to make an ethereal unit able to attack!. The attached test map demonstrates this.
JASS:
//! zinc
library AbilitySilence
{
//! textmacro CreateObjects takes SB, MFL, ITEM
//! external ObjectMerger w3a ANso $SB$ arlv 2 aran 1 99999 acdn 1 0 amcs 1 0 atar 1 "vuln,invu,alive,dead"
//! external ObjectMerger w3a Amfl $MFL$ amcs 1 0
//! external ObjectMerger w3t tkno $ITEM$ iabi "$MFL$" ifil ""

    constant integer SILENCE_ORDER = 852668; //OrderId("soulburn")
    constant integer SILENCE_ID = '$SB$'; //Soul Burn ability
    constant integer DETECT_ORDER = 852479; //OrderId("magicundefense")
    constant integer DETECT_ID = 'Amdf'; //Detector ability. I'm using Magic Defense cause no one else uses it.
    constant integer MFL_ID = '$MFL$'; //Mana Flare makes the unit temporarily immune to silence.
    constant integer ITEM_ID = '$ITEM$'; //Powerup that holds the Mana Flare ability.

//! endtextmacro
//! runtextmacro CreateObjects("A000", "A001", "I000")

    unit Dummy;
    boolean flag;
    integer id;

    /*If you already have a dummy in your map you
    may modify this function to make use of it.*/
    function GetDummy() -> unit
    {
        Dummy = CreateUnit(Player(15), 'uloc', .0, .0, .0);
        SetUnitScale(Dummy, 0, 0, 0);
        UnitRemoveAbility(Dummy, 'Amov');
        UnitRemoveAbility(Dummy, 'Aatk');
        return Dummy;
    }

    function EnableAbility(unit u, integer id)
    {
        IncUnitAbilityLevel(u, id);
        IssueTargetOrderById(Dummy, SILENCE_ORDER, u);
        DecUnitAbilityLevel(u, id);
        UnitRemoveAbility(u, 'BNso');
    }

    function DisableAbility(unit u, integer id)
    {
        IssueTargetOrderById(Dummy, SILENCE_ORDER, u);
        IncUnitAbilityLevel(u, id);
        UnitRemoveAbility(u, 'BNso');
        DecUnitAbilityLevel(u, id);
    }

    function OnSilence() -> boolean
    {
        if (GetIssuedOrderId() == DETECT_ORDER && UnitRemoveAbility(GetTriggerUnit(), MFL_ID))
        {
            /*At this point our unit is immune to silence, so we can 
            silence a single ability without affecting others.*/
            if (flag) DisableAbility(GetTriggerUnit(), id);
            else EnableAbility(GetTriggerUnit(), id);
        }
        return false;
    }

    public function SetUnitAbilitySilenced(unit u, integer i, boolean silence)
    {
        boolean inventory = UnitAddAbility(u, 'AInv');
        boolean hidden = IsUnitHidden(u);
        if (hidden) ShowUnit(u, true);
        UnitAddAbility(u, DETECT_ID);
        SetUnitOwner(Dummy, GetOwningPlayer(u), false);
        id = i;
        flag = silence;
        RemoveItem(UnitAddItemById(u, ITEM_ID));
        if (!UnitRemoveAbility(u, 'Bmfl'))
        {
            /* If the unit failed to cast Mana Flare, then it's probably
            dead or already silenced. So we just ignore it and continue*/
            if (silence) DisableAbility(u, i);
            else EnableAbility(u, i);
        }
        UnitRemoveAbility(u, DETECT_ID);
        if (hidden) ShowUnit(u, false);
        if (inventory) UnitRemoveAbility(u, 'AInv');
    }

    function onInit()
    {
        trigger t = CreateTrigger();
        TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_ORDER);
        TriggerAddCondition(t, Condition(function OnSilence));
        Dummy = GetDummy();
        UnitAddAbility(Dummy, SILENCE_ID);
        t = null;
    }
}
//! endzinc

To use it just create a new trigger, convert to custom text, and paste the code above. You must pay attention to the IDs used in object creation, and change them if necessary. After saving the map for the first time, you can comment the three ObjectMerger lines.

Then you just need to call SetUnitAbilitySilenced, and if the ability is suitable, it will be permanently silenced for that unit. This function can also be used to make an ability "immune" to silence, as is demonstrated with the Attack ability: if you un-silence the Aatk ability, the unit will be able to attack even if it's Hexed or ethereal.
 

Attachments

  • TestMap.w3x
    23.1 KB · Views: 210
Last edited:
Level 9
Joined
Jul 30, 2012
Messages
156
just a note that abilities can have requirements and fulfilment looks totally like silence. do they share same mechanic?

As I said, having a requirement is not the same as silence. First, a requirement is global for all units of a player, while silence can be applied in a unit basis. Second, if you disable an ability with requirement while it's being cast, the casting will not be stopped. And you should also notice that abilities disabled with requirement display their cooldown indicator, while silenced abilities don't.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
As I said, having a requirement is not the same as silence. First, a requirement is global for all units of a player, while silence can be applied in a unit basis. Second, if you disable an ability with requirement while it's being cast, the casting will not be stopped. And you should also notice that abilities disabled with requirement display their cooldown indicator, while silenced abilities don't.

ah, i missed "requirement" word and only noticed SetPLayerAbilityAvailable part. I see
 
Level 13
Joined
Nov 7, 2014
Messages
571
Would you mind explaining why the SetUnitAbilitySilenced function does all the inventory/mana flare/ shenanigans when it seems that using only the Enable|DisableAbility(...) functions works as well? The only thing I noticed was that if the current order of the unit was to attack something, calling Enable|DisableAbility(...) interrupts the attack order.
JASS:
public function SetUnitAbilitySilenced(unit u, integer i, boolean silence) {
    if (silence) { DisableAbility(u, i); }
    else { EnableAbility(u, i); }
}
 
Level 9
Joined
Jul 30, 2012
Messages
156
Would you mind explaining why the SetUnitAbilitySilenced function does all the inventory/mana flare/ shenanigans when it seems that using only the Enable|DisableAbility(...) functions works as well? The only thing I noticed was that if the current order of the unit was to attack something, calling Enable|DisableAbility(...) interrupts the attack order.
JASS:
public function SetUnitAbilitySilenced(unit u, integer i, boolean silence) {
    if (silence) { DisableAbility(u, i); }
    else { EnableAbility(u, i); }
}

The mana flare shenanigan is used to raise the silence counter of all abilities to -1, preventing them from being temporarily silenced while Enable|DisableAbility(...) is called. If I don't do that, the unit's orders will be interrupted if it's attacking or casting another spell. (But if you don't mind that, you can use Enable|DisableAbility(...) just fine)
 
Status
Not open for further replies.
Top