• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[JASS] My 1 armor = 1 damage reduction system

Status
Not open for further replies.
Level 37
Joined
Mar 6, 2006
Messages
9,240
I'm looking for some feedback on this system. How to improve it or are there some things I've missed. I'm going to use it in a project.

The system makes each point of armor reduce damage by one point.

Here's how it works:


All unit types are save into an array. There's also an array for attack- and defence types and base armor. There's a hidden dummy ability that is leveled, it keeps track of the armor value.

All items are set to an array, and there's an array for item armor values. When an item is acquired/lost, , I loop through the item types and then find the armor value. The level of the armor ability is changed.

There's a boolean that checks whether the damage is from a normal attack or from an ability. When "unit is attacked" the boolean is saved as true so the next damage the unit does by normal attack. When a "unit begins casting an ability", the booleas is set to false since the next damage will be from spells.

I have two damage modifier arrays. One is for default Blizzard values and another for custom modifiers.

When a unit takes damage, I save the damage to a variable. Then I load the attack- and defence types and get the damage modifier based on them.

I divide the damage done with the Blizzard modifier to get unmodified damage. Then that damage is modified by custom modifier and armor.

I set the damaged unit's life to max life of the unit. That prevents it from dying if armor is high enough to soak enough damage.

Then I calculate what the unit's health should be after the real damage is applied.

I set the life to correct value after a timer with 0 expiration time expires.

So normal attacks can be configured to use custom modifiers, spells use default Blizz modifiers.


JASS:
library ArmorSystem initializer InitTrig_Armor_System

    globals
        
        // How many item types the system uses
        private constant integer ITEMS = 3
        // How many unit types the system uses
        private constant integer UNITS = 5
        // Raw code of armor AbilityId
        private constant integer ARMORABILITY = 'A000'
        // Is floating text displayed
        private constant boolean FLOAT = true
        
        
        private integer itype
        private integer armor
        private boolean melee
        private string string1
        private string string2
        private real modifier
        private real finalDamage
        
        private group damageGroup = CreateGroup()
        trigger damageDetect = CreateTrigger()
        private timer damageDeal = CreateTimer()
        private constant integer array unitTypes
        private constant integer array baseArmor
        private constant integer array itemTypes
        private constant integer array armorValues
        private constant string array attackTypes
        private constant string array defenceTypes
        private constant real array damageModifiersB
        private constant real array damageModifiersC
        private hashtable Unit_Hash = InitHashtable()
    
    endglobals
    
    
    private function init_Items takes nothing returns nothing
        
        // Set item type raw codes here. Don't use index 0.
        // Also set armor values for each item.
        
        set itemTypes[1] = 'I000'
        set armorValues[1] = 2
        
        set itemTypes[2] = 'I001'
        set armorValues[2] = 3
        
        set itemTypes[3] = 'I002'
        set armorValues[3] = 15
    
    endfunction
    
    
    private function init_Unit_Data takes nothing returns nothing
        
        // Set unit types, defence- and attack types and base armor.
        
        set unitTypes[1] = 'Hpal'
        set defenceTypes[1] = "5"
        set attackTypes[1] = "7"
        set baseArmor[1] = 3
        
        set unitTypes[2] = 'Hamg'
        set defenceTypes[2] = "1"
        set attackTypes[2] = "4"
        set baseArmor[2] = 1
        
        set unitTypes[3] = 'Hmkg'
        set defenceTypes[3] = "3"
        set attackTypes[3] = "3"
        set baseArmor[3] = 2
        
        set unitTypes[4] = 'Hblm'
        set defenceTypes[4] = "6"
        set attackTypes[4] = "2"
        set baseArmor[4] = 0
        
        set unitTypes[5] = 'hkni'
        set defenceTypes[5] = "3"
        set attackTypes[5] = "1"
        set baseArmor[5] = 5
    
    endfunction
    
    
    private function init_Custom_Values takes nothing returns nothing
        // --------------------------------------------------
        // YOU CAN FREELY EDIT THESE VALUES
        // Example: [11] = Normal damage to light armor
        //          [43] = Magic damage to heavy armor
        // --------------------------------------------------
        // Attack Types        Armor Types
        //      1 = Normal      1 = Light
        //      2 = Pierce      2 = Medium
        //      3 = Siege       3 = Heavy
        //      4 = Magic       4 = Fortified
        //      5 = Chaos       5 = Hero
        //      6 = Spells      6 = Unarmored
        //      7 = Hero
        
        // --Normal damage--
        set damageModifiersC[11] = 1.00
        set damageModifiersC[12] = 1.50
        set damageModifiersC[13] = 1.00
        set damageModifiersC[14] = 0.70
        set damageModifiersC[15] = 1.00
        set damageModifiersC[16] = 1.00
        // --Pierce damage--
        set damageModifiersC[21] = 2.00
        set damageModifiersC[22] = 0.75
        set damageModifiersC[23] = 1.00
        set damageModifiersC[24] = 0.35
        set damageModifiersC[25] = 0.50
        set damageModifiersC[26] = 1.50
        // --Siege damage--
        set damageModifiersC[31] = 1.00
        set damageModifiersC[32] = 0.50
        set damageModifiersC[33] = 1.00
        set damageModifiersC[34] = 1.50
        set damageModifiersC[35] = 0.50
        set damageModifiersC[36] = 1.50
        // --Magic damage--
        set damageModifiersC[41] = 1.25
        set damageModifiersC[42] = 0.75
        set damageModifiersC[43] = 2.00
        set damageModifiersC[44] = 0.35
        set damageModifiersC[45] = 0.50
        set damageModifiersC[46] = 1.00
        // --Chaos damage--
        set damageModifiersC[51] = 1.00
        set damageModifiersC[52] = 1.00
        set damageModifiersC[53] = 1.00
        set damageModifiersC[54] = 1.00
        set damageModifiersC[55] = 1.00
        set damageModifiersC[56] = 1.00
        // --Spells damage--
        set damageModifiersC[61] = 1.00
        set damageModifiersC[62] = 1.00
        set damageModifiersC[63] = 1.00
        set damageModifiersC[64] = 1.00
        set damageModifiersC[65] = 0.70
        set damageModifiersC[66] = 1.00
        // --Hero damage--
        set damageModifiersC[71] = 1.00
        set damageModifiersC[72] = 1.00
        set damageModifiersC[73] = 1.00
        set damageModifiersC[74] = 0.50
        set damageModifiersC[75] = 1.00
        set damageModifiersC[76] = 1.00
    
    endfunction
    
    
    //*************************************//
    //      END OF CONFIGURABLE SECTION    //
    //*************************************//
    
    
    private function init_Blizz_Values takes nothing returns nothing
        // --------------------------------------------------
        // DON'T CHANGE THESE VALUES !
        // The are default Blizzard values to get unmodified damage.
        // Example: [11] = Normal damage to light armor
        // --------------------------------------------------
        
        // --Normal damage--
        set damageModifiersB[11] = 1.00
        set damageModifiersB[12] = 1.50
        set damageModifiersB[13] = 1.00
        set damageModifiersB[14] = 0.70
        set damageModifiersB[15] = 1.00
        set damageModifiersB[16] = 1.00
        // --Pierce damage--
        set damageModifiersB[21] = 2.00
        set damageModifiersB[22] = 0.75
        set damageModifiersB[23] = 1.00
        set damageModifiersB[24] = 0.35
        set damageModifiersB[25] = 0.50
        set damageModifiersB[26] = 1.50
        // --Siege damage--
        set damageModifiersB[31] = 1.00
        set damageModifiersB[32] = 0.50
        set damageModifiersB[33] = 1.00
        set damageModifiersB[34] = 1.50
        set damageModifiersB[35] = 0.50
        set damageModifiersB[36] = 1.50
        // --Magic damage--
        set damageModifiersB[41] = 1.25
        set damageModifiersB[42] = 0.75
        set damageModifiersB[43] = 2.00
        set damageModifiersB[44] = 0.35
        set damageModifiersB[45] = 0.50
        set damageModifiersB[46] = 1.00
        // --Chaos damage--
        set damageModifiersB[51] = 1.00
        set damageModifiersB[52] = 1.00
        set damageModifiersB[53] = 1.00
        set damageModifiersB[54] = 1.00
        set damageModifiersB[55] = 1.00
        set damageModifiersB[56] = 1.00
        // --Spells damage--
        set damageModifiersB[61] = 1.00
        set damageModifiersB[62] = 1.00
        set damageModifiersB[63] = 1.00
        set damageModifiersB[64] = 1.00
        set damageModifiersB[65] = 0.70
        set damageModifiersB[66] = 1.00
        // --Hero damage--
        set damageModifiersB[71] = 1.00
        set damageModifiersB[72] = 1.00
        set damageModifiersB[73] = 1.00
        set damageModifiersB[74] = 0.50
        set damageModifiersB[75] = 1.00
        set damageModifiersB[76] = 1.00
        
    endfunction
    
    
    // Updates armor value when acquiring an item.
    private function Acquire_Item takes nothing returns nothing
        local unit u = GetTriggerUnit()
        local integer id = GetHandleId(u)
        local integer i = 1
        
        set itype = GetItemTypeId(GetManipulatedItem())
        
        loop
            exitwhen i > ITEMS
            if itype == itemTypes[i] then
                call SetUnitAbilityLevel( u , ARMORABILITY , GetUnitAbilityLevel( u , ARMORABILITY ) + armorValues[i] )
                set i = 10000
            endif
            set i = i + 1
        endloop        
        
        set u = null
    endfunction
    
    
    // Updates armor value when losing an item
    private function Lose_Item takes nothing returns nothing
        local unit u = GetTriggerUnit()
        local integer id = GetHandleId(u)
        local integer i = 1
        
        set itype = GetItemTypeId(GetManipulatedItem())
        
        loop
            exitwhen i > ITEMS
            if itype == itemTypes[i] then
                call SetUnitAbilityLevel( u , ARMORABILITY , GetUnitAbilityLevel( u , ARMORABILITY ) - armorValues[i] )
                set i = 10000
            endif
            set i = i + 1
        endloop        
        
        set u = null
    endfunction
    
    
    // Creates floating text above the damaged unit
    private function damageDisplay takes unit u , integer damage , integer armor returns nothing
        set string1 = I2S(damage) + " |c0080FF80(-" + I2S(armor) + ")"
        call CreateTextTagUnitBJ( string1 , u , 50 , 10 , 100 , 0 , 0 , 0 )
        call SetTextTagVelocity( bj_lastCreatedTextTag , 0, 0.05 )
        call SetTextTagPermanent( bj_lastCreatedTextTag , false )
        call SetTextTagLifespan( bj_lastCreatedTextTag , 2.00 )
        call SetTextTagFadepoint( bj_lastCreatedTextTag , 1.70 )
    endfunction
    
    
    // Deals damage to the damaged unit
    private function dealDamage takes nothing returns nothing
        local unit u = GetEnumUnit()
        local integer id = GetHandleId(u)
        local real life = LoadReal( Unit_Hash , id , StringHash("life") )
        
        call SetUnitState( u , UNIT_STATE_LIFE , life )
        call GroupRemoveUnit( damageGroup , u )
        
        set u = null
    endfunction
    
    
    // Handles the expiring timer
    private function timerExpires takes nothing returns nothing
        call ForGroup( damageGroup , function dealDamage )
    endfunction
    
    
    // Damage detection, calculation
    private function Damage_Actions takes nothing returns nothing
        local unit u1 = GetTriggerUnit()
        local unit u2 = GetEventDamageSource()
        local integer id1 = GetHandleId(u1)
        local integer id2 = GetHandleId(u2)
        local integer index
        local real damage = GetEventDamage()
        local real life = GetUnitState( u1 , UNIT_STATE_LIFE )
        local real max_life = GetUnitState( u1 , UNIT_STATE_MAX_LIFE )
        
        // Get armor value
        set armor = GetUnitAbilityLevel( u1 , ARMORABILITY )
        
        // This boolean checks whether the damage is deal with normal attack
        if LoadBoolean( Unit_Hash , id2 , StringHash("boolean") ) == true then
            // If it is then load defence- and attack types of the attacker
            set string1 = LoadStringBJ( StringHash("defence") , id1 , Unit_Hash )
            set string2 = LoadStringBJ( StringHash("attack") , id2 , Unit_Hash )
            // Converts the types to an index
            set index = S2I( string2 + string1 )
            // Calculates the damage unmodified by Blizzard's values
            set damage = damage / damageModifiersB[index]
            // Loads custom damage modifier
            set modifier = damageModifiersC[index]
        else
            // If damage done by spell or ability, then custom modifier
            // is 1, standard Blizzard modifier is used.
            set modifier = 1.00
        endif
        
        // Calculate how much damage should be dealt
        set finalDamage = damage * modifier - I2R(armor)
        
        // If armor soaks all damage, set damage to 0
        // and set armor to damage done, so damage done is displayed correctly
        if finalDamage <= 0 then
            set finalDamage = 0
            set armor = R2I(damage)
        endif
        
        // Prevents the unit from dying before armor is applied.
        // Unit that has 10 hp, 5 armor and takes 10 damage would die without this.
        if life <= damage then
            call SetUnitState( u1 , UNIT_STATE_LIFE , damage + 1 )
        endif
        
        // Saves the value that the unit's life should be set to
        call SaveReal( Unit_Hash , id1 , StringHash("life") , life - finalDamage )
        // Add the damaged unit to a group and starts the timer which sets the life to correct value
        call GroupAddUnit( damageGroup , u1 )
        call TimerStart( damageDeal , 0 , false , function timerExpires )

        // Displays damage
        if FLOAT == true then
            call damageDisplay( u1 , R2I(finalDamage) , armor )
        endif
                
        set u1 = null
        set u2 = null
    endfunction
    
    
    // Configures units that are in the map at map initialization
    // or that enter playable map area during the game
    private function Set_Unit_Data takes nothing returns boolean
        local integer i = 1
        local integer j
        local integer k
        local integer id
        local integer utype
        local unit u = GetEnumUnit()
        
        // GetEnumUnit is used to add units at map initialization
        // GetTriggerUnit is used to add units that enter the map later
        if u == null then
            set u = GetTriggerUnit()
        endif
        
        set id = GetHandleId(u)
        set utype = GetUnitTypeId(u)
        
        // Loops through unit types to find a matching unit type
        loop
            if utype == unitTypes[i] then
                // Stores the attack- and defence types
                call SaveStringBJ( attackTypes[i] , StringHash("attack") , id , Unit_Hash )
                call SaveStringBJ( defenceTypes[i] , StringHash("defence") , id , Unit_Hash )
                // Enables damage detection for this unit
                call TriggerRegisterUnitEvent( damageDetect , u , EVENT_UNIT_DAMAGED )
                // Sets base armor for the unit
                if baseArmor[i] > 0 then
                    call UnitAddAbility( u , ARMORABILITY )
                    call SetUnitAbilityLevel( u , ARMORABILITY , baseArmor[i] )
                endif
                // Loops through items, adds armor value
                set j = 0
                loop
                    set itype = GetItemTypeId(UnitItemInSlot( u , j ))
                    if itype != null then
                        set k = 1
                        loop
                            if itype == itemTypes[k] then
                                call SetUnitAbilityLevel( u , ARMORABILITY , GetUnitAbilityLevel( u , ARMORABILITY ) + armorValues[k] )
                                set k = ITEMS + 1
                            endif
                            set k = k + 1
                            exitwhen k > ITEMS
                        endloop
                    endif
                    set j = j + 1
                    exitwhen j > 5
                endloop
                // Breaks out of the loop when unit type is found
                set i = 10000
            endif
            set i = i + 1
            exitwhen i > UNITS
        endloop
        
        set u = null
        return true
    endfunction
    
    
    // Picks all units in the playable map area at map initialization
    // and enables the system for them
    private function Init_Units takes nothing returns nothing
        local group g = CreateGroup()
        
        call GroupEnumUnitsInRect( g , bj_mapInitialPlayableArea , null )
        call ForGroup( g , function Set_Unit_Data )
        
        call DestroyGroup(g)
        set g = null
    endfunction
    
    
    // If a unit begins casting an ability, then the damage it deals after that won't be from it's normal attack
    private function Spell_Effect_Actions takes nothing returns nothing
        call SaveBoolean( Unit_Hash , GetHandleId(GetTriggerUnit()) , StringHash("boolean") , false )
    endfunction
    
    
    // If a unit attacks a unit, then the next damage it deals will be from it's normal attack
    private function Attacked_Actions takes nothing returns nothing
        call SaveBoolean( Unit_Hash , GetHandleId(GetAttacker()) , StringHash("boolean") , true )
    endfunction

    
    private function InitTrig_Armor_System takes nothing returns nothing
        local region mapArea = CreateRegion()
        
        local trigger t1 = CreateTrigger()
        local trigger t2 = CreateTrigger()
        local trigger t3 = CreateTrigger()
        local trigger t4 = CreateTrigger()
        local trigger t5 = CreateTrigger()
        
        call RegionAddRect( mapArea , bj_mapInitialPlayableArea )
        
        call TriggerAddAction( t1 , function Attacked_Actions )
        call TriggerAddAction( t2 , function Spell_Effect_Actions )
        call TriggerAddAction( t3 , function Set_Unit_Data )
        call TriggerAddAction( t4 , function Acquire_Item )
        call TriggerAddAction( t5 , function Lose_Item )
        call TriggerAddAction( damageDetect , function Damage_Actions )
        
        call TriggerRegisterAnyUnitEventBJ( t1 , EVENT_PLAYER_UNIT_ATTACKED )
        call TriggerRegisterAnyUnitEventBJ( t2 , EVENT_PLAYER_UNIT_SPELL_CAST )
        //call TriggerRegisterAnyUnitEventBJ( t2 , EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerRegisterEnterRegion( t3 , mapArea , null )
        call TriggerRegisterAnyUnitEventBJ( t4 , EVENT_PLAYER_UNIT_PICKUP_ITEM )
        call TriggerRegisterAnyUnitEventBJ( t5 , EVENT_PLAYER_UNIT_DROP_ITEM )
        
        call init_Items()
        call init_Unit_Data()
        call Init_Units()
        call init_Blizz_Values()
        call init_Custom_Values()
        
    endfunction
    
    
endlibrary

I appreciate the time taken to test it a bit. Don't mind the GUI stuff, it's incomplete. vJASS system works. Hit ESC to spawn a knight to your right.
 

Attachments

  • Armor_System_Test.w3x
    37.8 KB · Views: 57
Last edited:
Level 16
Joined
May 1, 2008
Messages
1,605
Moin moin =)

After testing it a bit, I wondering myself, this system doesn't include possible armor bonus from abilities which means, this isn't 1 armor = 1 reduced damage. When you add this with abilities, you have to use reals for that bonus.

Also somehow this system doesn't work correctly. I added in each item slot a uber helmet. Now every other hero attack the unit. Now you text shows, that all damage is 0, but the attacked hero still lose life.
bopxs1pxap4wjwzin.jpg

But at all this system is a good idea but it can be a hugh one, if this is used and all units and items with + armor have to be added...

That's all for the moment now, I must leave now for some time.

Greetings and Peace
Dr. Boom
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
After testing it a bit, I wondering myself, this system doesn't include possible armor bonus from abilities which means, this isn't 1 armor = 1 reduced damage. When you add this with abilities, you have to use reals for that bonus.

Yeah, it doesn't support +armor from abilities. I might add that.

Also somehow this system doesn't work correctly. I added in each item slot a uber helmet. Now every other hero attack the unit. Now you text shows, that all damage is 0, but the attacked hero still lose life.

That's because the max level of the armor ability is 50, and when you have more armor it bugs. I'm going to add MAX_ARMOR there and increase the max level of the ability.

EDIT: There is a bug that causes the unit to take damage if it's attacked very very quickly by several units.
 
Last edited:
JASS:
set i = 10000
You could use exitwhen true.

Also, Init_Units does not need such complication. If you do everything in the filter, instead of a null filter, just do:

JASS:
private function Init_Units takes nothing returns nothing
    call GroupEnumUnitsInRect(bj_lastCreatedGroup, bj_mapInitialPlayableArea, Filter(function Set_Unit_Data))
endfunction

bj_lastCreatedGroup never gets destroyed since one of the latest patches, so it is the perfect group to use if you only need it temporarily.
 
Status
Not open for further replies.
Top