1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. The raddest synthwave tracks were chosen - Check out our Music Contest #12 - Results and congratulate the winners!
    Dismiss Notice
  4. The poll for Hive's 12th Concept Art Contest is up! Go cast your vote for your favourite genie!
    Dismiss Notice
  5. Travel to distant realms and encounter scenes unknown to the common folk. The Greatest of Adventures is upon us with the 8th Cinematic Contest. Join in on a fun ride.
    Dismiss Notice
  6. The 18th Icon Contest is ON! Choose any ingame unit and give him/her Hero abilities. Good luck to all.
    Dismiss Notice
  7. Contestants are to create a scene set in the Stone Age. Come and see what you can come up with. We wish you the best of luck!
    Dismiss Notice
  8. Colour outside the lines! Techtree Contest #13 is a go. The contest is optionally paired.
    Dismiss Notice
  9. Greetings cerebrates, our Swarm needs new spawners that will have numerous children. Join the HIVE's 31st Modeling Contest - Spawners and Spawned! The contest is optionally paired.
    Dismiss Notice
  10. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJASS] Making own damage engine

Discussion in 'Triggers & Scripts' started by Krogoth, Oct 20, 2012.

  1. Krogoth

    Krogoth

    Joined:
    Apr 5, 2011
    Messages:
    247
    Resources:
    0
    Resources:
    0
    Updated.

    Unit Indexer:
    Code (vJASS):
    //======================
    //=======[ 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:
    Code (vJASS):
    //======================
    //=======[ 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:
    Code (vJASS):
    //==============
    //=== 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.

    Old post
    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:
    Code (vJASS):

    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.
     

    Attached Files:

    Last edited: Oct 26, 2012
  2. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Really? How can there be anything at the same time since wc3 uses a single thread that handles every event after the one before?
     
  3. Krogoth

    Krogoth

    Joined:
    Apr 5, 2011
    Messages:
    247
    Resources:
    0
    Resources:
    0
    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.
     
  4. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    So is the damage dealt twice for every source or not? I don't understand.
    One damage event = one source + one damage + one target.
     
  5. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,427
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    Are you trying to do a damage mod or something?
     
  6. Krogoth

    Krogoth

    Joined:
    Apr 5, 2011
    Messages:
    247
    Resources:
    0
    Resources:
    0
    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
     
  7. Krogoth

    Krogoth

    Joined:
    Apr 5, 2011
    Messages:
    247
    Resources:
    0
    Resources:
    0
    Updated. (Indexer also)
    There is a demo map now. Global spell that reduces each damage by 5. =]
    Test it please !! (Do you like it at all?)
    P.S. Have no rep, my bad. :p
     
  8. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Normally for a good engine you would detect unit death death by checking hitpoints - pureDmg <= 0. Then deindex since its instantly.
     
  9. Krogoth

    Krogoth

    Joined:
    Apr 5, 2011
    Messages:
    247
    Resources:
    0
    Resources:
    0
    TriggerRegisterUnitStateEvent needs a concrete unit, EVENT_DEATH does not, just a player. And:
    ?
     
  10. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,427
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    Eh before I think it had the death event with the damage event so that it was registering for each unit. But it looks like you changed it so it should be fine.
     
  11. Krogoth

    Krogoth

    Joined:
    Apr 5, 2011
    Messages:
    247
    Resources:
    0
    Resources:
    0
    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.
     
  12. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    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.
     
  13. gorillabull

    gorillabull

    Joined:
    Jul 17, 2011
    Messages:
    1,368
    Resources:
    2
    Spells:
    2
    Resources:
    2
    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
     
  14. Krogoth

    Krogoth

    Joined:
    Apr 5, 2011
    Messages:
    247
    Resources:
    0
    Resources:
    0
    Okay.
    Because I consider my better. :grin:
    But I have not looked at others much, maybe I'm wrong.)