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

vJass alternative to DamageEngine

Status
Not open for further replies.

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
DamageEngine GUI has been constantly hacked at to make it what it is. Today, I was trying to locate a bug and was re-writing the script as I went through it, just to make sure my logic is sound. The result is a series of minor changes (I fixed the bug, that's what matters) but now contains full JASS efficiency and readability.

JASS:
library DamageEngineGUI initializer Init requires RealEvent //and GUI Unit Indexer

/*
API
    CreateDamageEventTrigger(string variableName, code whichCodeToRun) -> trigger
    - Provided by the library RealEvent
    A shorter amount of code so that you don't have to type everything out.
    I have provided constant strings for easier syntax:
        DAMAGE_EVENT - damage before HP was reduced. Will not fire for 0 damage.
        ZERO_DAMAGE_EVENT - if GetEventDamage() was equal to 0
        AFTER_DAMAGE_EVENT - after unit's HP has been reduced (if it had been)
        DAMAGE_MODIFIER_EVENT - modify damage amount directly
        DAMAGE_ABSORB_EVENT - runs if damage was converted to a heal
        DAMAGE_SHIELD_EVENT - subtract damage amount from a potential shield.
        - Unlike with the modification event, fires after DamageEvent has run.
        AfterDamageEvent will not fire if the shield completely blocked the damage.
        DAMAGE_BLOCK_EVENT - fires after ShieldDamageEvent has run to identify blocks.

    Damage.overrideNext()
    - Prevents internal spell modification and modifier events 1-3 from firing for
    the next UnitDamageTarget call

    Damage[real value] -> real
    - For use in a modifier event to convert values based on previous modification
    so as to remove the need for modification priorities aside from shielding.

    Damage.addAmount(real amount)
    - Add a value to the damage based on the factor of previous modification

    writeonly
    Damage.enabled = boolean -------- dis/enables DamageEventTrigger
    Damage.nextType = integer -------- set type before dealing triggered damage

    readonly
    Damage.source -> unit -------- source unit of damage dealt
    Damage.target -> unit -------- target unit of damage dealt
    Damage.isSpell -> boolean -------- true for spell damage, false for physical
    Damage.prevAmount -> real -------- absolute amount of GetEventDamage()
    
    read/write
    Damage.amount -> real -------- the amount of damage a unit will receive
    Damage.type -> integer -------- negative values cause the unit to explode
    Damage.override -> boolean -------- prevent additional modifiers from running
*/

globals
    private constant integer SPELL_ID = 'A000' // rawcode of Detect Spell Damage
    private constant integer BLOCK_ID = 'A001' // rawcode of Cheat Death Ability
    private constant integer DUMMY_ID = 'uloc' // a dummy created just for preloading abilities
    private constant boolean HAVE_VARIABLES = true // true if you already have DamageEvent's GUI variables in your map
    private constant boolean BRACERS_ABIL = false // true if you have removed Spell Reduction ability from Runed Bracers
endglobals

//What criteria must be met for a unit to have a damage event?
private function UnitFilter takes unit u returns boolean
    return GetUnitAbilityLevel(u, 'Aloc') == 0
endfunction

//Applied to spell damage before events are run.
private function GetSpellDamageMultiplier takes unit u returns real
    local real r = 1.00
    local boolean hero = IsUnitType(u, UNIT_TYPE_HERO)
    if udg_DamageEventOverride then
        return r
    endif
    if IsUnitType(u, UNIT_TYPE_ETHEREAL) and not hero then
        set r = r*1.67
    endif
    if GetUnitAbilityLevel(u, 'Aegr') > 0 then
        set r = r*0.80
    endif
    static if not BRACERS_ABIL then
      if hero and UnitHasItemOfTypeBJ(u, 'brac') then
        set r = r*0.67
      endif
    endif
    return r
endfunction

//The only GUI variables that you need for this to work:
static if not HAVE_VARIABLES then
  globals
    unit udg_DamageEventSource = null
    unit udg_DamageEventTarget = null
    real udg_DamageEventAmount = 0.00
    real udg_DamageEventPrevAmount = 0.00
    real udg_DamageEvent = 0.00
    real udg_DamageModifierEvent = 0.00
    real udg_AfterDamageEvent = 0.00
    real udg_ZeroDamageEvent = 0.00
    real udg_DamageShieldEvent = 0.00
    real udg_DamageBlockEvent = 0.00
    real udg_DamageAbsorbEvent = 0.00
    trigger udg_DamageEventTrigger = null
    trigger udg_ClearDamageEvent = null
    integer udg_DamageEventType = 0
    integer udg_NextDamageType = 0
    boolean udg_NextDamageOverride = false
    boolean udg_DamageEventOverride = false
    boolean udg_IsDamageSpell = false
  endglobals
endif

globals
    //convenient syntax
    constant string DAMAGE_EVENT = "udg_DamageEvent"
    constant string AFTER_DAMAGE_EVENT = "udg_AfterDamageEvent"
    constant string DAMAGE_MODIFIER_EVENT = "udg_DamageModifierEvent"
    constant string ZERO_DAMAGE_EVENT = "udg_ZeroDamageEvent"
    constant string DAMAGE_SHIELD_EVENT = "udg_DamageShieldEvent"
    constant string DAMAGE_BLOCK_EVENT = "udg_DamageBlockEvent"
    constant string DAMAGE_ABSORB_EVENT = "udg_DamageAbsorbEvent"
    
    private keyword onDamage
    private boolexpr checkEvent
endglobals

struct Damage extends array
    //writeonly
    static method operator enabled= takes boolean flag returns nothing
        if flag then
            call EnableTrigger(udg_DamageEventTrigger)
        else
            call DisableTrigger(udg_DamageEventTrigger)
        endif
    endmethod
    static method operator nextType= takes integer nt returns nothing
        set udg_NextDamageType = nt
    endmethod

    static method overrideNext takes nothing returns nothing
        set udg_NextDamageOverride = true
    endmethod

    static method operator [] takes real value returns real
        if udg_DamageEventPrevAmount == 0.00 then
            debug call BJDebugMsg("DamageEngine error - don't try to modify value from a Zero damage event!")
            return 0.00
        endif
        return value*udg_DamageEventAmount/udg_DamageEventPrevAmount
    endmethod

    static method addValue takes real value returns nothing
        set udg_DamageEventAmount = udg_DamageEventAmount + Damage[value]
    endmethod

    //readonly
    static method operator source takes nothing returns unit
        return udg_DamageEventSource
    endmethod
    static method operator target takes nothing returns unit
        return udg_DamageEventTarget
    endmethod
    static method operator prevAmount takes nothing returns real
        return udg_DamageEventPrevAmount
    endmethod
    static method operator isSpell takes nothing returns boolean
        return udg_IsDamageSpell
    endmethod

    //read/write
    static method operator amount takes nothing returns real
        return udg_DamageEventAmount
    endmethod
    static method operator amount= takes real value returns nothing
        set udg_DamageEventAmount = value
    endmethod

    //read/write
    static method operator type takes nothing returns integer
        return udg_DamageEventType
    endmethod
    static method operator type= takes integer value returns nothing
        set udg_DamageEventType = value
    endmethod

    //read/write
    static method operator override takes nothing returns boolean
        return udg_DamageEventOverride
    endmethod
    static method operator override= takes boolean flag returns nothing
        set udg_DamageEventOverride = flag
    endmethod

    private static integer recursionN = 0
    private static integer recursionMax = 0
    private static trigger trig = null
    private static real fixedLife = 0.00
    private unit rsource
    private unit rtarget
    private real ramount
    private real rprevAmt
    private real rtype
    private boolean rspell

    static method reset takes nothing returns nothing
        set .recursionN = .recursionN - 1

        set udg_DamageEventSource = Damage(.recursionN).rsource
        set udg_DamageEventTarget = Damage(.recursionN).rtarget
        set udg_DamageEventAmount = Damage(.recursionN).ramount
        set udg_DamageEventPrevAmount = Damage(.recursionN).rprevAmt
        set udg_DamageEventType = Damage(.recursionN).rtype
        set udg_IsDamageSpell = Damage(.recursionN).rspell
    endmethod

    private static boolean resetNext = true
    static method checkEvent takes nothing returns boolean
        if .resetNext then
            set udg_NextDamageType = 0
            set udg_NextDamageOverride = false
        else
            set .resetNext = true
        endif

        if .trig != null then
            call DestroyTrigger(.trig)
            set .trig = null
            if udg_IsDamageSpell then
                if .fixedLife <= 0.405 then
                    call SetUnitExploded(udg_DamageEventTarget, udg_DamageEventType < 0)
                    call SetWidgetLife(udg_DamageEventTarget, 0.41)
                    call DisableTrigger(udg_DamageEventTrigger)
                    call UnitDamageTarget(udg_DamageEventSource, udg_DamageEventTarget, -10.00, false, false, null, DAMAGE_TYPE_UNIVERSAL, null)
                    call EnableTrigger(udg_DamageEventTrigger)
                else
                    call SetWidgetLife(udg_DamageEventTarget, .fixedLife)
                endif
            elseif UnitRemoveAbility(udg_DamageEventTarget, BLOCK_ID) then
                call SetWidgetLife(udg_DamageEventTarget, .fixedLife)
            endif
            if udg_DamageEventAmount > 0.001 then
                set udg_AfterDamageEvent = 0.00
                set udg_AfterDamageEvent = 1.00
                set udg_AfterDamageEvent = 0.00
                set .resetNext = false
                call .checkEvent()
            endif
            if .recursionN > 0 then
                call .reset()
            endif
        endif
        return false
    endmethod
    
    private static timer zeroHour = CreateTimer()
    private static boolean started = false
    private static method onExpire takes nothing returns nothing
        set .started = .checkEvent()
    endmethod

    /*internal*/ static method onDamage takes nothing returns boolean
        local boolean override = udg_DamageEventOverride
        local boolean clear = false
        local real life
        local real maxLife

        set .resetNext = false
        call .checkEvent()

        if .running then
            set .recursionMax = .recursionMax + 1
            if .recursionMax < 16 then
                set Damage(.recursionN).rsource = udg_DamageEventSource
                set Damage(.recursionN).rtarget = udg_DamageEventTarget
                set Damage(.recursionN).ramount = udg_DamageEventAmount
                set Damage(.recursionN).rprevAmt = udg_DamageEventPrevAmount
                set Damage(.recursionN).rtype = udg_DamageEventType
                set Damage(.recursionN).rspell = udg_IsDamageSpell
                set .recursionN = .recursionN + 1
            else
                debug call BJDebugMsg("DAMAGE ENGINE INFINITE LOOP!")
                return false
            endif
        else
            set .running = true
            set clear = true
        endif

        set udg_DamageEventSource = GetEventDamageSource()
        set udg_DamageEventTarget = GetTriggerUnit()
        set udg_DamageEventAmount = GetEventDamage()
        set udg_DamageEventPrevAmount = udg_DamageEventAmount

        set udg_DamageEventType = udg_NextDamageType
        set udg_NextDamageType = 0

        set udg_DamageEventOverride = udg_NextDamageOverride
        set udg_NextDamageOverride = false

        if udg_DamageEventAmount == 0.00 then
            set udg_ZeroDamageEvent = 0.00
            set udg_ZeroDamageEvent = 1.00
            set udg_ZeroDamageEvent = 0.00
        else
            if not .started then
                set .started = true
                call TimerStart(.zeroHour, 0.00, false, function thistype.onExpire)
            endif

            set udg_IsDamageSpell = udg_DamageEventAmount < 0.00
            if udg_IsDamageSpell then
                set udg_DamageEventPrevAmount = -udg_DamageEventAmount
                set udg_DamageEventAmount = udg_DamageEventPrevAmount*GetSpellDamageMultiplier(udg_DamageEventTarget)
            endif

            if not udg_DamageEventOverride then
                set udg_DamageModifierEvent = 0.00
                set udg_DamageModifierEvent = 1.00
                set udg_DamageModifierEvent = 0.00
            endif
            set udg_DamageEventOverride = override
  
            if udg_DamageEventAmount != 0.00 then
                if udg_DamageEventAmount > 0 then
                    set udg_DamageEvent = 0.00
                    set udg_DamageEvent = 1.00
                    set udg_DamageEvent = 0.00
                    set udg_DamageShieldEvent = 0.00
                    set udg_DamageShieldEvent = 1.00
                    set udg_DamageShieldEvent = 0.00
                else
                    set udg_DamageAbsorbEvent = 0.00
                    set udg_DamageAbsorbEvent = 1.00
                    set udg_DamageAbsorbEvent = 0.00
                endif
            else
                set udg_DamageBlockEvent = 0.00
                set udg_DamageBlockEvent = 1.00
                set udg_DamageBlockEvent = 0.00
            endif

            set .trig = CreateTrigger()
            call TriggerAddCondition(.trig, checkEvent)

            set life = GetWidgetLife(udg_DamageEventTarget)
            set maxLife = GetUnitState(udg_DamageEventTarget, UNIT_STATE_MAX_LIFE)
            if udg_IsDamageSpell then
                set .fixedLife = life - (DamageEventPrevAmt - (DamageEventPrevAmt - udg_DamageEventAmount))
                if life + udg_DamageEventAmount*0.75 < maxLife then
                    set life = life + udg_DamageEventPrevAmount*0.50
                else
                    set life = RMaxBJ(1.00, maxLife - udg_DamageEventPrevAmount*0.50)
                    call SetWidgetLife(udg_DamageEventTarget, life)
                    set life = (life + maxLife)*0.50
                endif
            else
                if actualDamage != udg_DamageEventPrevAmount then
                    set life = life + udg_DamageEventPrevAmount - udg_DamageEventAmount
                    if life > maxLife then
                        set .fixedLife = life - udg_DamageEventPrevAmount
                        call UnitAddAbility(udg_DamageEventTarget, BLOCK_ID)
                    endif
                    call SetWidgetLife(udg_DamageEventTarget, RMaxBJ(0.42, life))
                endif
                set life = RMaxBJ(0.41, life - udg_DamageEventPrevAmount*0.50)
            endif

            call TriggerRegisterUnitStateEvent(.trig, udg_DamageEventTarget, UNIT_STATE_LIFE, ConvertLimitOp(IntegerTertiaryOp(udg_IsDamageSpell, 4, 0)), life)
        endif

        if clear then
            set .recursionMax = 0
            set .running = false
        elseif udg_DamageEventPrevAmount == 0.00 then
            call .reset()
        endif

        return false
    endmethod
endstruct

private function NewDamageTrigger takes nothing returns nothing
    set udg_DamageEventTrigger = CreateTrigger()
    call TriggerAddCondition(udg_DamageEventTrigger, Filter(function Damage.onDamage))
endfunction

globals
    private integer array stackRef
    private unit array stack
    private integer count = 0
    private integer wasted = 0
endglobals

private function OnCreate takes nothing returns nothing
    local integer id = udg_UDex
    local unit u = udg_UDexUnits[id]
    if UnitFilter(u) then
        set stackRef[id] = idN
        set stack[idN] = u
        set count = count + 1
        call UnitAddAbility(u, SPELL_ID)
        call UnitMakeAbilityPermanent(u, SPELL_ID, true)
        call TriggerRegisterUnitEvent(udg_DamageEventTrigger, u, EVENT_UNIT_DAMAGED)
    endif
    set u = null
endfunction

private function OnRemove takes nothing returns nothing
    local integer index = stackRef[udg_UDex]
    if n >= 0 then
        set count = count - 1
        set stackRef[GetUnitUserData(stack[count])] = index
        set stack[index] = stack[count]
        set stackRef[udg_UDex] = -1
        set wasted = wasted + 1
        if wasted == 32 then
            set wasted = 0
            call DestroyTrigger(udg_DamageEventTrigger)
            call NewDamageTrigger()
            set index = count
            loop
                exitwhen index == 0
                set index = index - 1
                call TriggerRegisterUnitEvent(udg_DamageEventTrigger, stack[index], EVENT_UNIT_DAMAGED)
            endloop
        endif
    endif
endfunction

private function Init takes nothing returns nothing
    local unit u
    local integer i = 15
    loop
        call SetPlayerAbilityAvailable(Player(i), SPELL_ID, false)
        exitwhen i == 0
        set i = i - 1
    endloop
    set checkEvent = Filter(function Damage.checkEvent)
    set udg_ClearDamageEvent = CreateTrigger()
    call TriggerAddCondition(udg_ClearDamageEvent, checkEvent)
    call NewDamageTrigger()
    set u = CreateUnit(Player(15), DUMMY_ID, 0, 0, 0)
    call UnitAddAbility(u, BLOCK_ID)
    call UnitAddAbility(u, SPELL_ID)
    call RemoveUnit(u)
    call CreateRealEventTrigger("udg_UnitIndexEvent", 1.00, function OnCreate)
    call CreateRealEventTrigger("udg_UnitIndexEvent", 2.00, function OnRemove)
    set u = null
endfunction

function CreateDamageEventTrigger takes string whichEvent, code toRun returns trigger
    return CreateRealEventTrigger(whichEvent, 1.00, toRun)
endfunction

endlibrary

library_once RealEvent
globals
    private trigger tempTrig
endglobals

function CreateRealEventTrigger takes string variable, real value, code run returns trigger
    set tempTrig = CreateTrigger()
    call TriggerRegisterVariableEvent(tempTrig, variable, EQUAL, value)
    if run != null then
        call TriggerAddCondition(tempTrig, Filter(run))
    endif
    return tempTrig
endfunction

endlibrary
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
More changes which I intend to eventually migrate to DamageEngine GUI:

- Only one modifier event. I've added a Damage[] operator so the user can work with abstract values instead of direct ones (to prevent adding to blocked damage, for example).

- Shield event now runs after the damage event so that the damage event will fire for the normal damage, but after damage event will run (or not run) based on the damage blocked by the shield. This way, the user can apply things like triggered life steal against a mana shielded target.

- ZeroDamageEvent is now its own variable instead of being a subset of DamageEvent. It will not fire based on blocked damage - only based on wc3 damage that called the event for 0 amount.

I will add a DamageBlockEvent to split up DamageEvent a bit further.
 
Level 8
Joined
Jan 23, 2015
Messages
121
Um, I somehow get such errors when C&P the code above in new empty trigger in a map with only UnitEvent installed.

Err1.JPG
Err2.JPG

They're quite numerous and I wonder if only I have them.

UPD: their count is 9 on 2nd screen really (scroll bar didn't fit in) after possible solution on error on 1st screen. Every error seems to be a simple but real mistake... And it seems really strange to me, as I don't think @Bribe would post in the entire system he has never checked for such errors. For example, .running appears only in onDamage method but is used for checking numerous parallel runs of onDamage, so it should be static boolean... but is never declared. So as any other mistakes that are about just declaration and usage.
 
Last edited:
Level 8
Joined
Jan 23, 2015
Messages
121
@Trokkin , Do you use JNGP + have the required RealEvent and GUI Unit Indexer imported into your map?
All requirements you've mentioned are met:
in a map with only UnitEvent installed
"The latest update to Unit Event makes it completely replaces GUI Unit Indexer (it's an inprovement on every level)"
RealEvent inserted in the system already:
JASS:
library_once RealEvent
globals
    private trigger tempTrig
endglobals

function CreateRealEventTrigger takes string variable, real value, code run returns trigger
    set tempTrig = CreateTrigger()
    call TriggerRegisterVariableEvent(tempTrig, variable, EQUAL, value)
    if run != null then
        call TriggerAddCondition(tempTrig, Filter(run))
    endif
    return tempTrig
endfunction
And of course I'm JNGP user. Default editor would react on other syntax mistakes that were allowed in vJass.
 
Last edited:
Status
Not open for further replies.
Top