Trample System (BFME - Inspired) v2.01

This bundle is marked as pending. It has not been reviewed by a staff member yet.
Cavalry Charge System (Trample) v2.0


Aura‑based charge mechanics for cavalry and monster units.
Deals heavy directional damage, knocks back targets, stuns them, and applies a deceleration after a configurable number of hits.
Fully MUI, leakless, event‑driven; designed for large battles with minimal overhead.



### How It Works


  • An invisible passive aura is added to your charger units.
  • When the charger moves and an enemy is inside the aura, a small “ping” damage triggers the trample.
  • The system checks if the enemy is in front of the charger, applies scaled damage, knockback, stun, and a dust impact effect.
  • If the charger hits enough enemies (per‑unit‑type threshold), it gains a deceleration buff (slow) and can’t trample again until it stops or the buff is removed.



### Features


  • Frontal cone check (configurable angle)
  • Density‑based knockback (slows down in crowds)
  • Revenge ability: some units reflect damage back at the charger
  • Per‑unit‑type hit‑thresholds before deceleration
  • Anti‑spam (targets can’t be hit repeatedly in quick succession)
  • Dummy recycling pool (no dummy leaks, performance friendly)
  • Fully MUI
  • Easy import: just one trigger to configure, plus four required systems



### Requirements


You must have the following systems imported into your map:

1. Unit Indexer by Bribe
2. Damage Engine 5.A.0.0+ by Bribe
3. Knockback 2.5D by Bribe
4. Is Unit Moving by Bribe

All credit goes to Bribe for those systems.



### Import Instructions


1. Copy the required systems into your map (triggers + their variables).
2. Copy the Dummy unit 'o000' into your Object Editor:
3. Copy the Trample System library (this vJASS code) into your map script.
4. Copy the Trample_Config with the Map Initialization event, and set the configuration globals (see below).
5. Set up your abilities
6. **Don't forget to enable vJass



### Configuration Trigger:


In your Trample_Config trigger, use Custom Script to set the following:

  • Trample Config
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- /////////////////////// --------
      • Custom script: set TrampleSystem_AuraBuff = 'B000'
      • Custom script: set TrampleSystem_DecelBuff = 'B001'
      • Custom script: set TrampleSystem_StunAbility = 'A000'
      • Custom script: set TrampleSystem_DecelAbility = 'A002'
      • Custom script: set TrampleSystem_RevengeAbility = 'A003'
      • -------- /////////////////////// --------
      • Custom script: set TrampleSystem_AttackType = ATTACK_TYPE_SIEGE
      • Custom script: set TrampleSystem_DamageType = DAMAGE_TYPE_UNIVERSAL
      • Custom script: set TrampleSystem_RevengeAttack = ATTACK_TYPE_PIERCE
      • -------- /////////////////////// --------
      • Custom script: set TrampleSystem_RevengeMult = 3.00
      • Custom script: set TrampleSystem_NormalMult = 3.00
      • Custom script: set TrampleSystem_RevengeBaseMult = 1.00
      • -------- /////////////////////// --------
      • Custom script: set TrampleSystem_AngleLimit = 90.00
      • Custom script: set TrampleSystem_KB_DistMul = 1.00
      • Custom script: set TrampleSystem_KB_Time = 0.50
      • -------- /////////////////////// --------
      • Custom script: set TrampleSystem_DensityRadius = 200.00
      • Custom script: set TrampleSystem_DensityFactor = 0.15
      • Custom script: set TrampleSystem_KB_MinDistance = 64.00
      • -------- /////////////////////// --------
      • Custom script: call Tramp_SetThreshold('hkni', 2)
      • Custom script: call Tramp_SetThreshold('orai', 4)
      • -------- /////////////////////// --------
      • Custom script: set TrampleSystem_ImpactEffect = "Objects\\Spawnmodels\\Undead\\ImpaleTargetDust\\ImpaleTargetDust.mdl"
      • -------- /////////////////////// --------
      • Custom script: if udg_GameTimer == null then
      • Custom script: set udg_GameTimer = CreateTimer()
      • Custom script: endif
      • Custom script: call TimerStart(udg_GameTimer, 100000.00, false, null)

Trample System V2

vJASS:
library TrampleSystem requires DamageEngine

/*
* ==============================================================================
*  Trample System 2.0
*  by Artaros13 & the.Axolotl
* ==============================================================================
*  REQUIREMENTS (all must be in your map):
*   - Unit Indexer (Bribe)
*   - Damage Engine 5.A.0.0+ (Bribe)
*   - Knockback 2.5D (Bribe)
*   - Is Unit Moving (Bribe)
* ==============================================================================
*/

globals
    public  integer AuraBuff        = 0
    public  integer DecelBuff       = 0
    public  integer StunAbility     = 0
    public  integer DecelAbility    = 0
    public  integer RevengeAbility  = 0

    public  attacktype AttackType   = ATTACK_TYPE_SIEGE
    public  damagetype DamageType   = DAMAGE_TYPE_UNIVERSAL
    public  attacktype RevengeAttack = ATTACK_TYPE_PIERCE

    public  real RevengeMult        = 3.0
    public  real NormalMult         = 3.0
    public  real RevengeBaseMult    = 1.0

    public  real AngleLimit         = 90.0

    public  real KB_DistMul         = 1.0
    public  real KB_Time            = 0.50

    public  real DensityRadius      = 200.0
    public  real DensityFactor      = 0.15
    public  real KB_MinDistance     = 64.0

    // Dust / impact effect  set in Trample_Config
    public  string ImpactEffect     = ""

    private hashtable hash          = InitHashtable()
    private unit array pool
    private integer poolSize        = 0

    private constant integer DUMMY_ID = 'o000'
    private constant integer MAX_POOL_SIZE = 12
    private constant real ANTISPAM_COOLDOWN = 0.80
endglobals

//==============================================================================
//  API: per‑unit‑type threshold
//==============================================================================
function Tramp_SetThreshold takes integer unitType, integer maxHits returns nothing
    call SaveInteger(hash, unitType, 0, maxHits)
endfunction

//==============================================================================
//  Helper: full weapon damage
//==============================================================================
private function GetUnitWeaponDamage takes unit u returns real
    return BlzGetUnitBaseDamage(u, 0) + I2R(BlzGetUnitDiceNumber(u, 0)) * I2R(BlzGetUnitDiceSides(u, 0))
endfunction

//==============================================================================
//  Frontal cone check
//==============================================================================
private function IsInFrontalCone takes unit source, unit target returns boolean
    local real angle = bj_RADTODEG * Atan2(GetUnitY(target) - GetUnitY(source), GetUnitX(target) - GetUnitX(source))
    local real diff  = RAbsBJ(angle - GetUnitFacing(source))
    if diff > 180.0 then
        set diff = 360.0 - diff
    endif
    return diff <= (AngleLimit * 0.5)
endfunction

//==============================================================================
//  Dummy recycling (fully MUI‑safe)
//==============================================================================
private function AcquireDummy takes player p, real x, real y, real face returns unit
    local unit u
    if poolSize > 0 then
        set poolSize = poolSize - 1
        set u = pool[poolSize]
        call SetUnitOwner(u, p, false)
        call SetUnitX(u, x)
        call SetUnitY(u, y)
        call SetUnitFacing(u, face)
        call ShowUnit(u, true)
    else
        set u = CreateUnit(p, DUMMY_ID, x, y, face)
        call UnitAddAbility(u, 'Aloc')
        call SetUnitPathing(u, false)
    endif
    return u
endfunction

private function ReleaseDummy takes unit u returns nothing
    call ShowUnit(u, false)
    call SetUnitX(u, 0.0)
    call SetUnitY(u, 0.0)
    if poolSize < MAX_POOL_SIZE then
        set pool[poolSize] = u
        set poolSize = poolSize + 1
    else
        call RemoveUnit(u)
    endif
endfunction

private function OnReleaseCallback takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit u = LoadUnitHandle(hash, GetHandleId(t), 2)
    if u != null then
        call UnitRemoveAbility(u, StunAbility)
        call UnitRemoveAbility(u, DecelAbility)
        call ReleaseDummy(u)
    endif
    call DestroyTimer(t)
    set t = null
endfunction

private function DelayedRelease takes unit u returns nothing
    local timer t = CreateTimer()
    call SaveUnitHandle(hash, GetHandleId(t), 2, u)
    call TimerStart(t, 0.00, false, function OnReleaseCallback)
    set t = null
endfunction

//==============================================================================
//  Core trample hit
//==============================================================================
private function PerformHit takes unit source, unit target, boolean revenge returns nothing
    local integer srcId     = GetUnitUserData(source)
    local integer targId    = GetUnitUserData(target)
    local integer hits      = LoadInteger(hash, srcId, 0) + 1
    local integer threshold = LoadInteger(hash, GetUnitTypeId(source), 0)
    local real srcX = GetUnitX(source)
    local real srcY = GetUnitY(source)
    local real face = GetUnitFacing(source)
    local real dmg
    local unit dummy
    local unit u
    local group g
    local integer nearbyCount
    local real kbDist

    if GetUnitState(target, UNIT_STATE_LIFE) <= 0.405 then
        return
    endif

    call SaveInteger(hash, srcId, 0, hits)

    // --- Damage ---
    if revenge then
        set dmg = GetUnitWeaponDamage(target) * RevengeMult
        call UnitDamageTarget(target, source, dmg, false, false, RevengeAttack, DamageType, null)
        set dmg = GetUnitWeaponDamage(source) * RevengeBaseMult
    else
        set dmg = GetUnitWeaponDamage(source) * NormalMult
    endif
    call UnitDamageTarget(source, target, dmg, false, false, AttackType, DamageType, null)

    // --- Density‑based knockback (manual enemy count) ---
    set g = CreateGroup()
    call GroupEnumUnitsInRange(g, srcX, srcY, DensityRadius, null)
    call GroupRemoveUnit(g, source)

    set nearbyCount = 0
    loop
        set u = FirstOfGroup(g)
        exitwhen u == null
        call GroupRemoveUnit(g, u)

        if IsUnitEnemy(u, GetOwningPlayer(source)) and GetUnitState(u, UNIT_STATE_LIFE) > 0.405 and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
            set nearbyCount = nearbyCount + 1
        endif
    endloop

    call DestroyGroup(g)
    set g = null
    set u = null

    set kbDist = GetUnitMoveSpeed(source) * KB_DistMul / (1.0 + DensityFactor * nearbyCount)
    if kbDist < KB_MinDistance then
        set kbDist = KB_MinDistance
    endif

    // Fully reset all knockback globals
    set udg_Knockback2DAngle       = face
    set udg_Knockback2DDistance    = kbDist
    set udg_Knockback2DTime        = KB_Time
    set udg_Knockback2DUnit        = target
    set udg_Knockback2DAmphibious  = false
    set udg_Knockback2DBounces     = false
    set udg_Knockback2DCollision   = 16.00
    set udg_Knockback2DDestRadius  = 128.00
    set udg_Knockback2DKillTrees   = false
    set udg_Knockback2DPause       = true
    set udg_Knockback2DSimple      = true
    set udg_Knockback2DOverride    = true
    call TriggerExecute(gg_trg_Knockback_2D)

    // --- Impact effect (dust) ---
    if ImpactEffect != "" then
        call DestroyEffect(AddSpecialEffectTarget(ImpactEffect, target, "origin"))
    endif

    // --- Stun ---
    set dummy = AcquireDummy(GetOwningPlayer(source), srcX, srcY, face)
    call UnitAddAbility(dummy, StunAbility)
    call IssueTargetOrder(dummy, "thunderbolt", target)
    call DelayedRelease(dummy)

    // --- Deceleration ---
    if threshold > 0 and hits >= threshold then
        set dummy = AcquireDummy(GetOwningPlayer(source), srcX, srcY, face)
        call UnitAddAbility(dummy, DecelAbility)
        call IssueTargetOrder(dummy, "slow", source)
        call DelayedRelease(dummy)
        call RemoveSavedInteger(hash, srcId, 0)
    endif

    // --- Anti‑spam timestamp ---
    call SaveReal(hash, targId, 1, TimerGetElapsed(udg_GameTimer) + ANTISPAM_COOLDOWN)
endfunction

//==============================================================================
//  Event: Pre‑damage (aura tick)
//==============================================================================
private function OnPreDamage takes nothing returns boolean
    local unit src   = udg_DamageEventSource
    local unit tgt   = udg_DamageEventTarget
    local integer id = GetUnitUserData(src)

    if udg_DamageEventAmount >= 1.00 then
        return false
    endif

    if GetUnitAbilityLevel(src, AuraBuff) == 0 or not udg_UnitMoving[id] then
        return false
    endif

    if GetUnitAbilityLevel(tgt, 'BPSE') > 0 then
        return false
    endif

    if GetUnitAbilityLevel(tgt, 'A001') > 0 then
        return false
    endif

// If the target's owner cannot see the attacker (Wind Walk/Invisibility), trample fails.
 
    if IsUnitInvisible(src, GetOwningPlayer(tgt)) then
   
        return false
    endif
    if GetUnitAbilityLevel(src, DecelBuff) > 0 then
        return false
    endif

    if IsUnitType(tgt, UNIT_TYPE_STRUCTURE) or IsUnitType(tgt, UNIT_TYPE_MECHANICAL) then
        return false
    endif

    if not IsInFrontalCone(src, tgt) then
        return false
    endif

    if LoadReal(hash, GetUnitUserData(tgt), 1) > TimerGetElapsed(udg_GameTimer) then
        return false
    endif

    call PerformHit(src, tgt, GetUnitAbilityLevel(tgt, RevengeAbility) > 0)
    return false
endfunction

//==============================================================================
//  Reset hit counter when unit stops moving OR loses deceleration buff
//==============================================================================
private function OnUnitStop takes nothing returns boolean
 
    local integer id = udg_UDex
 
    local unit u = udg_UDexUnits[id]
 
// Safety check for null units to prevent thread crashes
 
    if u == null or not udg_UnitMoving[id] or GetUnitAbilityLevel(u, DecelBuff) == 0 then
   
        call RemoveSavedInteger(hash, id, 0)
 
    endif
 
    set u = null
 
    return false

endfunction

//==============================================================================
//  Initialization
//==============================================================================
private function InitTrample takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0

    call TriggerRegisterVariableEvent(t, "udg_PreDamageEvent", EQUAL, 0.00)
    call TriggerAddCondition(t, Condition(function OnPreDamage))

    set t = CreateTrigger()
    call TriggerRegisterVariableEvent(t, "udg_UnitMovingEvent", EQUAL, 2.00)
    call TriggerAddCondition(t, Condition(function OnUnitStop))

    loop
        exitwhen i >= 5
        set pool[i] = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, 0.0, 0.0, 0.0)
        call UnitAddAbility(pool[i], 'Aloc')
        call SetUnitPathing(pool[i], false)
        call ShowUnit(pool[i], false)
        set i = i + 1
    endloop
    set poolSize = 5
endfunction

private struct SafeInit extends array
    private static method onInit takes nothing returns nothing
        call TimerStart(CreateTimer(), 0.10, false, function InitTrample)
    endmethod
endstruct

endlibrary

Author's original credits:

Credits
@Bribe for the is Unit Moving, Damage Engine 5.A.0.0 And Knockback 2.5D systems.
@-Berz- for the Polearm and Charge Icons
@Mapmaster-1-bak for the Stop Icon
And last but not least :
@the.Axolotl ,whose help has been invaluable for getting this system to work, I wouldn't have done it without him.

This is my first submission to the HiveWorkshop, I hope you'll forgive the format of this page if there is something wrong with it and the poor quality of the Screenshots used.

Co-Author's note:
This update was made with the original author’s approval.
The goal was to bring the system up to modern standards—cleaner structure, safer execution, and better performance—without altering its intended mechanics.
All original design credit belongs to the author; this version focuses strictly on technical refinement and optimization.
If you encounter any bugs, unexpected behavior, or integration issues, feel free to post in the thread and tag me. I’ll do my best to look into it and provide fixes or guidance.

Changelog
v1.0:
Initial release.

v1.1:
- Fixed leaks
- Improved frontal cone logic
- Stabilized timer behavior

v2.0:
*Preserved original gameplay mechanics while improving technical implementation
- Converted system to vJASS structure
- Improved performance and execution safety
- Added density-based knockback scaling
- Implemented anti-spam protection
- Added dummy recycling system (no leaks)
- Added configurable impact effect
- Refined filtering (structures/mechanical excluded)
- General code cleanup and optimization
-Ignores targets that are invisible to the attacker

V2.01
-Added a filter so that units with the Charge ability can't be affected by the Trample effect

Contents

BFME-Like Charge System 2.01 (Map)

Oh damn, I always loved BFME cavalry mechanics (Still playing the AotR mod from times to times).
Will test it, hope it will give the same feeling :xxd:
To really dial in that BFME feeling, I definitely recommend playing around with the Trample Config trigger. You can adjust the DensityFactor to determine how much a crowd slows down the rider, or change the Tramp_SetThreshold for specific units—like setting a higher limit for heavy knights so they can plow through more infantry before decelerating.
 
To really dial in that BFME feeling, I definitely recommend playing around with the Trample Config trigger. You can adjust the DensityFactor to determine how much a crowd slows down the rider, or change the Tramp_SetThreshold for specific units—like setting a higher limit for heavy knights so they can plow through more infantry before decelerating.
Played a bit with it, kinda funny to watch :xxd:
May I suggest to add a knockup in addition to the knockback ?
I think having a nice bell curve would add a lot to the feel.

You could use Chopinski's Crowd Control & Tenacity (which also has stun and knockback, might also clean up a few things).
 
Played a bit with it, kinda funny to watch :xxd:
May I suggest to add a knockup in addition to the knockback ?
I think having a nice bell curve would add a lot to the feel.

You could use Chopinski's Crowd Control & Tenacity (which also has stun and knockback, might also clean up a few things).
That’s a fantastic suggestion,

That would actually allow me to replace the entire dummy pool logic I think, as it handles stuns, slows, and the knockup arc natively through code. Adding that 'bell curve' to the knockback would be cool, I totally forgot that in BFME the units actually fly a bit when knocked( ehhh.. played the game long time ago :xxd:)

Main goal was to make the system as clean as possible while keeping the author's mechanics, I'll see with him on that.

I’m going to test the Z-axis arc this weekend to see how it affects the system's weight and dependency list. Thanks for the input!
 
@the.Axolotl another neat thing i tried to add, was for the system to play the trampled unit's death animation to really give the effect of them being thrown on the ground, but I still don't have the experience for that unfortunately.
The dust is definitely our best friend here. I think if we play the death animation at a very low speed (like 10-20%) during the slide, and then time a big dust puff right as the knockback ends, we can mask that 'snap' when they 'stand back up'.

Integrating that Z-axis 'bell curve' should also help a ton—the arc should naturally distract the eye from the animation jank. I'll see what I can do.
 
I've been lurking on the development of this (just a tad). Excited to see it in production. I might experiment with adding it to my current project 😁

I would just ask that any launching/arc/other added effects like that be made optional in case a user doesn't really like all that extra flair. 🙏
 
I've been lurking on the development of this (just a tad). Excited to see it in production. I might experiment with adding it to my current project 😁

I would just ask that any launching/arc/other added effects like that be made optional in case a user doesn't really like all that extra flair. 🙏
Welcome to the party lol, I’m glad the system is catching your eye for your project.

Don't worry, I completely agree on the 'flair'. I’ll make sure that any new additions—like the Z-axis knockup arc or the death animation sliding are entirely optional toggles in the config. The goal is definitely to keep the core mechanics clean so you can use as much or as little 'AotR' juice as you want. :grin:
 
Hello,

May I suggest to implement en event system similar to the DamageEvent for this trample system ?

Suggested Event type:
  • PreTramplingEvent => Allows to change parameters of the trampling. Would make it much better to implement different type of units like anti-cavalry which could resist, strike back harder etc.
  • AfterTramplingEvent => Allows to perform actions after being trampled (something like "gains a bonus after trampling/being trampled")

Variables that could be available on a event:
  • TramplingEventSource
  • TramplingEventTarget
  • TramplingEventAttackType
  • TramplingEventDamageType
  • TramplingEventDamage
  • TramplingEventRevengeDamage
  • TramplingEventStunDuration
  • TramplingEventKnockbackDistance
  • TramplingEventImpaleEffect

That would make this much more easier to use and configure :cute:
 
Back
Top