• 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.

[vJASS] Making own damage engine

Status
Not open for further replies.
Level 7
Joined
Apr 5, 2011
Messages
245
Updated.

Unit Indexer:
JASS:
//======================
//=======[ vGUI ]=======
//======= v0.201 =======

library vGUI requires IndexedData, NExt, WorldBounds, optional vGDC, Temple

//=== Settings ===
globals
    private constant boolean REGISTER_START_UNITS = true
//----------------
    private constant boolean TEST_MODE = false
endglobals
//================

    globals
        indexedUnit Unit
    endglobals

    struct vGUI
        readonly static unit triggerUnit
        readonly static boolean enabled = false
        private static trigger t1
        private static trigger t2

        private static method register takes nothing returns boolean
            set triggerUnit = GetFilterUnit()
            call Unit.register(triggerUnit)
            static if TEST_MODE then
                call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Unit[" + I2S(Unit.indexOf(triggerUnit)) + "] has been registered.")
            endif
            static if LIBRARY_vGDC then
                call vGDC.register()
            endif
            static if LIBRARY_Temple then
                call onUnitRegister()
            endif
            return false
        endmethod

        private static method unregister takes nothing returns boolean
            set triggerUnit = GetTriggerUnit()
            static if TEST_MODE then
                call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Unit[" + I2S(Unit.indexOf(triggerUnit)) + "] has been unregistered.")
            endif
            static if LIBRARY_vGDC then
                call vGDC.unregister()
            endif
            static if LIBRARY_Temple then
                call onUnitUnregister()
            endif
            call Unit.unregister(triggerUnit)
            return false
        endmethod

        //=== Register units present at this moment ===
        static method snapshot takes nothing returns nothing
            local group g = CreateGroup()
            call GroupEnumUnits(g, Condition(function thistype.register))
            call DestroyGroup(g)
            set g = null
        endmethod        

        //=== Enable/disable triggers ===
        static method enable takes boolean flag returns nothing
            if flag then
                call EnableTrigger(t1)
                call EnableTrigger(t2)
                set enabled = true
            else
                call DisableTrigger(t1)
                call DisableTrigger(t2)
                set enabled = false
            endif
        endmethod

        //=== Starts work with it ===
        static method start takes nothing returns nothing
            static if REGISTER_START_UNITS then
                call snapshot()
            endif
            call TriggerRegisterEnterRegion(t1, WorldBounds.worldRegion, Condition(function thistype.register))
            call TriggerRegisterAnyUnitEvent(t2, EVENT_PLAYER_UNIT_DEATH, null)
            call TriggerAddCondition(t2, Condition(function thistype.unregister))
            set enabled = true
        endmethod
        
        private static method onInit takes nothing returns nothing
            set Unit = Unit.create()
            set t1 = CreateTrigger()
            set t2 = CreateTrigger()
        endmethod
    endstruct
endlibrary
Damage Controller:
JASS:
//======================
//=======[ vGDC ]=======
//======= v0.203 =======

library vGDC requires optional Temple

//=== Settings ===
globals
    private constant boolean DETECT_MAGIC = true
    private constant integer MAX_INSTANCES = 24
    private constant integer REFRESH_COUNT = 25
    //When <REFRESH_COUNT> units are unregistered trigger refreshes itself.
    private constant real SAFE_LIFE = 500000
    //<SAFE_LIFE> should be out of game values.
//----------------
    private constant integer SURVIVAL_ABILITY = 'Z000'
endglobals
static if DETECT_MAGIC then
globals
    private constant integer DETECT_MAGIC_SPELLBOOK = 'Z001'
endglobals
endif
globals
    private constant boolean TEST_MODE = true
endglobals
//================

//Damage type interface
static if DETECT_MAGIC then
globals
    constant boolean PHYSICAL = false
    constant boolean MAGIC = true
    constant boolean SPELL = true
endglobals
endif

    struct vGDC
        //General
        readonly static boolean enabled = true
        private static trigger t
        private static integer count = 0
        private static boolean flag = false
        private static timer tick
        private static integer instances

        //Damage instance's interface
        readonly static unit damageTarget
        readonly static real targetLife
        readonly static integer damageSources
        readonly static unit array damageSource[MAX_INSTANCES]
        readonly static real array damageAmount[MAX_INSTANCES]
    static if DETECT_MAGIC then
        readonly static boolean array damageType[MAX_INSTANCES]
    endif
        readonly static real damageTotalAmount
        readonly static boolean deathEvent

        //Damage instances' data
        private static unit array damaged[MAX_INSTANCES]
        private static real array life[MAX_INSTANCES]
        private static integer array sources[MAX_INSTANCES]
        private static unit array damager[MAX_INSTANCES][MAX_INSTANCES]
        private static real array damage[MAX_INSTANCES][MAX_INSTANCES]
    static if DETECT_MAGIC then
        private static boolean array type[MAX_INSTANCES][MAX_INSTANCES]
    endif
        private static real array damageTotal[MAX_INSTANCES]
        private static boolean array death[MAX_INSTANCES]

        static method register takes nothing returns nothing
            static if DETECT_MAGIC then
                call UnitAddAbility(vGUI.triggerUnit, DETECT_MAGIC_SPELLBOOK)
            endif
            call TriggerRegisterUnitEvent(t, vGUI.triggerUnit, EVENT_UNIT_DAMAGED)
        endmethod
        //It is called by vGUI register.

        static method unregister takes nothing returns nothing
            set count = count + 1
            if count == REFRESH_COUNT then
                set count = 0
                call refresh()
            endif
        endmethod
        //It is called by vGUI unregister.

        //=== Refresh trigger ===
        static method refresh takes nothing returns nothing
            local integer i = 0
            call DestroyTrigger(t)
            set t = CreateTrigger()
            call TriggerAddCondition(t, Condition(function thistype.beforeDamage))
            loop
                exitwhen i == Unit.size
                if Unit[i] != null then
                    call TriggerRegisterUnitEvent(t, Unit[i], EVENT_UNIT_DAMAGED)
                endif
                set i = i + 1
            endloop
            static if TEST_MODE then
                call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Damage trigger has been refreshed.")
            endif
        endmethod

        //=== Enable/disable trigger ===
        static method enable takes boolean flag returns nothing
            if flag then
                call EnableTrigger(t)
                set enabled = true
            else
                call DisableTrigger(t)
                set enabled = false
            endif
        endmethod
        
        private static method beforeDamage takes nothing returns boolean
            local integer i = 0
            local boolean b = false
            //First damage in session
            if not flag then
                set flag = true
                call TimerStart(tick, 0, false, function thistype.afterDamage)
                set instances = 0
            endif
            //Save data for instance
            set damaged[instances] = GetTriggerUnit()
            set life[instances] = GetWidgetLife(damaged[instances])
            set sources[instances] = 1
            set damager[instances][0] = GetEventDamageSource()
            set damage[instances][0] = GetEventDamage()
        static if DETECT_MAGIC then
            //Determine damage type
            if damage[instances][0] < 0 then
                set damage[instances][0] = -damage[instances][0]
                set type[instances][0] = MAGIC
            else
                set type[instances][0] = PHYSICAL
            endif
        endif
            set damageTotal[instances] = damage[instances][0]
            set death[instances] = false
            //Prevent unit from early death
            if life[instances] <= damageTotal[instances] + 1 then
                set death[instances] = true
                call UnitAddAbility(damaged[instances], SURVIVAL_ABILITY)
                call SetWidgetLife(damaged[instances], SAFE_LIFE)
            endif
            //<SAFE_LIFE> is out of game values, thaty's why no need of check (<SURVIVAL_ABILITY> existense)/(death event).
            //Merge this instance with another one with the same source
            loop
                exitwhen i == instances or b
                if damaged[instances] == damaged[i] then
                    set b = true
                    set damager[i][sources[i]] = damager[instances][0]
                    set damage[i][sources[i]] = damage[instances][0]
                static if DETECT_MAGIC then
                    set type[i][sources[i]] = type[instances][0]
                endif
                    set damageTotal[i] = damageTotal[i] + damage[instances][0]
                    set sources[i] = sources[i] + 1
                    //Prevent unit from early death
                    if life[i] <= damageTotal[i] + 1 and not death[i] then
                        set death[i] = true
                        call UnitAddAbility(damaged[i], SURVIVAL_ABILITY)
                        call SetWidgetLife(damaged[i], SAFE_LIFE)
                    endif
                endif
                set i = i + 1
            endloop
            if not b then
                set instances = instances + 1
            endif
            return false
        endmethod

        private static method afterDamage takes nothing returns nothing
            local integer i = 0
            local integer j
            local real regen
            set flag = false
            loop
                //Prepare interface
                set damageTarget = damaged[i]
                set targetLife = life[i]
                set damageSources = sources[i]
                set j = 0
                loop
                    set damageSource[j] = damager[i][j]
                    set damageAmount[j] = damage[i][j]
                static if DETECT_MAGIC then
                    set damageType[j] = type[i][j]
                endif
                    set j = j + 1
                    exitwhen j == damageSources
                endloop
                set damageTotalAmount = damageTotal[i]
                set deathEvent = death[i]
                static if TEST_MODE then
                    if damageSources == 1 then
                        static if DETECT_MAGIC then
                            if damageType[0] == MAGIC then
                                call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Unit[" + I2S(Unit.indexOf(damageTarget)) + "] is gonna take " + R2S(damageTotalAmount) + " magic damage from Unit[" + I2S(Unit.indexOf(damageSource[0])) + "].")
                            else
                                call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Unit[" + I2S(Unit.indexOf(damageTarget)) + "] is gonna take " + R2S(damageTotalAmount) + " physical damage from Unit[" + I2S(Unit.indexOf(damageSource[0])) + "].")
                            endif
                        else
                            call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Unit[" + I2S(Unit.indexOf(damageTarget)) + "] is gonna take " + R2S(damageTotalAmount) + " damage from Unit[" + I2S(Unit.indexOf(damageSource[0])) + "].")
                        endif
                    else
                        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Unit[" + I2S(Unit.indexOf(damageTarget)) + "] is gonna take " + R2S(damageTotalAmount) + " damage from " + I2S(damageSources) + " sources.")
                    endif
                endif
                //Nullify incoming damage
                if deathEvent then
                    call UnitRemoveAbility(damageTarget, SURVIVAL_ABILITY)
                endif
                call SetWidgetLife(damageTarget, targetLife)
                //Modify damage
                static if LIBRARY_Temple then
                    call onUnitTakesDamage()
                endif
                static if TEST_MODE then
                    static if DETECT_MAGIC then
                        if damageSources == 1 then
                            if damageType[0] == MAGIC then
                                call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Unit[" + I2S(Unit.indexOf(damageTarget)) + "] takes " + R2S(damageTotalAmount) + " magic damage.")
                            else
                                call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Unit[" + I2S(Unit.indexOf(damageTarget)) + "] takes " + R2S(damageTotalAmount) + " physical damage.")
                            endif
                        else
                            call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Unit[" + I2S(Unit.indexOf(damageTarget)) + "] takes " + R2S(damageTotalAmount) + " damage.")
                        endif
                    else
                        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Unit[" + I2S(Unit.indexOf(damageTarget)) + "] takes " + R2S(damageTotalAmount) + " damage.")
                    endif
                endif
                //Deal modified damage
                call SetWidgetLife(damageTarget, GetWidgetLife(damageTarget) - damageTotalAmount)
                set i = i + 1
                exitwhen i == instances
            endloop
        endmethod
        
        //=== Modify damage total amount ===
        static method setDamageAmount takes integer i, real value returns nothing
            static if LIBRARY_ErrorStater then
                if CheckArrayIndexError(i, MAX_INSTANCES, damageSources) then
                    return
                endif
            endif
            set damageTotalAmount = damageTotalAmount - damageAmount[i] + value
            set damageAmount[i] = value
        endmethod
        
    static if DETECT_MAGIC then
        //=== Set damage type ===
        static method setDamageType takes integer i, boolean value returns nothing
            set damageType[i] = value
        endmethod
    endif
        
        private static method onInit takes nothing returns nothing
            static if DETECT_MAGIC then
                local integer i = 0
                loop
                    call SetPlayerAbilityAvailable(Player(i), DETECT_MAGIC_SPELLBOOK, false)
                    exitwhen i == MAX_PLAYER_INDEX
                    set i = i + 1
                endloop
            endif
            set t = CreateTrigger()
            call TriggerAddCondition(t, Condition(function thistype.beforeDamage))
            set tick = CreateTimer()
        endmethod
    endstruct
endlibrary
Template:
JASS:
//==============
//=== Temple ===
//==============

library Temple requires Bounty
    globals
    endglobals
    
    function onUnitRegister takes nothing returns nothing
    endfunction
    
    function onUnitUnregister takes nothing returns nothing
    endfunction
       
    function onUnitTakesDamage takes nothing returns nothing
        local player p = GetOwningPlayer(vGDC.damageSource[0])
        if vGDC.deathEvent and IsUnitEnemy(vGDC.damageTarget, p) then
            call Bounty(p, 10, GetWidgetX(vGDC.damageTarget), GetWidgetY(vGDC.damageTarget))
        endif
        //call vGDC.setDamageAmount(0, 1000)
    endfunction
endlibrary
As you see in template every damage on the map should be reduced by 5 points.
There is only 1 tool (setDamageTotalAmount) yet.
You can test my demo map in attachments below.

I noticed that more than 1 (but I have not seen more than 2) damage instances may take a place in single moment. So, I detect damage, run zero-timer to make right after damage event when target is attacked by multiple sources and have 2 instances side by side for it. Experienced here, tell me please how many instances can be (maybe I should do only with 2, it's so simple; but if no...) and how to deal with them the best? I have seen large arrays with damage data for every unit in some systems but it seems cumbersome for me.

There are few pieces of code where I could try to do it:
JASS:
globals
    private constant integer MAX_INSTANCES = 4 //(?)
endglobals
...
        //Damage instance's interface
        readonly static unit damageTarget
        readonly static unit damageSource
        readonly static real damageAmount
        readonly static boolean deathEvent

        //Damage instances' data
        private static unit array damaged[MAX_INSTANCES]
        private static unit array damager[MAX_INSTANCES]
        private static real array damage[MAX_INSTANCES]
        private static boolean array death[MAX_INSTANCES]
...
            //Save data for instance
            set damaged[instances] = damageTarget
            set damager[instances] = damageSource
            set damage[instances] = damageAmount
            set death[instances] = deathEvent
            set instances = instances + 1
...
                //Load data for interface
                set damageTarget = damaged[i]
                set damageSource = damager[i]
                set damageAmount = damage[i]
                set deathEvent = death[i]
I don't use arguments for outer functions connected with damage events, I use address to struct with interface above instead.
 

Attachments

  • KrogothSystem.w3x
    188.1 KB · Views: 69
Last edited:
Level 7
Joined
Apr 5, 2011
Messages
245
Not at the same actually, but after that 0.0 seconds they coincident (in game time). 2 different damage sources for 1 target. (And I had not more than 2, I don't know why) I meant this. I order 10 riflemans to attack and have 1-2.
 
Level 7
Joined
Apr 5, 2011
Messages
245
Mdaa...

I don't know what was wrong before but now 3, 4, 5 sources easily. I have thought there are some restrictions on that, that should not be, but it's good to use it. :O

^Yes, it's just damage modification.

So, I confused you with definitions. It's just few sources for one target. When I talk "in one moment" I just meant game time. So, I want to make data arrays, then group data instances by damage target, I got list where each damage target is unique and I can deal with each of them. (If I do not do it last hit will not be defined right, killers should be equal in their rights. And I should have no problems with preventing death for damage modifications) But I should make damage source array for each of them. Is it a good plan? :O
 
Level 7
Joined
Apr 5, 2011
Messages
245
Normally for a good engine you would detect unit death death by checking hitpoints - pureDmg <= 0. Then deindex since its instantly.
TriggerRegisterUnitStateEvent needs a concrete unit, EVENT_DEATH does not, just a player. And:
TriggerRegisterAnyUnitEventBJ() would be better. Whenever you register an event, you create a handle. So TriggerRegisterAnyUnitEventBJ() for death would lead to only 12 handles whereas registering for each units will create n unit events, where n is the number of units registered. (which often reaches hundreds or sometimes thousands)

You can't do that with the damage event but you can with the death event.
?
 
Level 7
Joined
Apr 5, 2011
Messages
245
Updated.
Now my damage engine determines damage type. There are 2 types only: PHYSICAL and MAGIC/SPELL. Type "vGDC.damageType[0] == PHYSICAL" to check physical damage type from source 1.
Demo map is much bigger since I use Dota terrain.
 
Level 7
Joined
Apr 5, 2011
Messages
245
Vexorian once said uses is depricated and should not be used.
Always use requires (with optional libraries).
Because you either have optional requirements or forced.
But use was thought for being optional, so please change it to requires.
Okay.
lol another damage system why dont you just take an older system and add more to it than make a new one with less features
Because I consider my better. :grin:
But I have not looked at others much, maybe I'm wrong.)
 
Status
Not open for further replies.
Top