HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Elemental damage modifier system

04-14-2010, 12:04 PM#1
MasterofSickness
Hey Anitarf,
my JassNewGenPack (JNGP) works now. The problem was that I used WIII:TFT in V1.21b.
Still I think Vexorian should add this in his instruction for using 2 versions of W3,
that vjass with hashtables won't work then by using JNGP with WIII-V1.21b.

Now I can ask my core question :)
I want to use your system with DamageEvent & DamageModifiers.
My purpose is quite easy, but I reread your ABuffSystem1.5 again and again and I come to estimated 1 dubious line of code per hour.
I studied the RetributionBarrier-Trigger, but without success till now.
If you could give me code for following situation so I can understand it and work it out to my desires?

I don't need buffs with effects.
I just have to detect different damage and armor types.
I think I need abilities and buffs.
(The abilities can already be added to the appropriate units in the object editor,
the buffs are needed for your damage system?)

We have fire and ice classes.

There is a unit A1, a tower.
There is a unit B1, a creep.
A1 has an ability (& buff?) for ice-damage-detection.
B1 has an ability (& buff?) for ice-armor-detection.
A1 makes 0.2 * X dmg to B1. (both are ice)

Then we have another unit B2, a creep.
B2 has an ability (& buff?) for fire-armor-detection.
A1 makes 2.0 * X dmg to B2.

That would be all.
If you are so nice to give my a sample code so I can work it out to 8 different damage and armor types...
04-14-2010, 11:10 PM#2
Anitarf
Dummy abilities as proxies for armour types are one way to go. However, this might not be the fastest way because in order to find out which elemental armour a unit has, you would need to check for N abilities, where N is equal to the number of different elements you have. Actually, N is only equal to the number of elements an attack gets modified against, so if you have 8 elements but an elemental attack only has bonuses/penalties against 2 of them, then you would only have to check for two abilities for that attack.

The alternative would be to use a hashtable to associate unit types with armour types with a hashtable. In that case, finding out a unit's armour type would only require a GetUnitTypeId call and a hashtable read call.

Once you have one of these two systems in place, you can easily tell what a unit's armour (and weapon) element is. Now for the actual damage modifiers:

Whenever a unit that can deal damage is created in game (if these are towers in a TD, you would use the "finishes construction" event, otherwise use "unit enters region" or whatever), create a damage modifier for it. If this unit can never be destroyed, such as towers in some TDs, then you don't have to worry about cleanup, otherwise you should somehow attach the damage modifier to your unit so when the unit dies, you can destroy the modifier. One way to do this is with ABuffs, but I won't go into more detail unless you need this, because if you don't then you can easily do this without ABuff alltogether, just with Damage Modifiers.

There are two ways to approach the damage modifier itself. One way is to have a different type of modifier for each element: then, in each damage modifier's onDamageDealt method, you can hardcode what elements to check for in the target and what the modifier factors are for those elements. This is useful if you are using dummy abilities since hardcoding it means there are fewer abilities you need to check; you just need to look for those abilities on the damaged unit for which the damage source gets a damage bonus or penalty.

The second approach is to have a single generic damage modifier applied to all units that deal damage. This modifier then checks the attack element of the damage source, the defense element of the damaged unit and then looks up what the damage modifier for this combination is, for example in a hashtable. This approach is useful if you are already using hashtables for getting a unit type's element and it allows you to very easily create and maintain complicated damage bonus tables.
04-15-2010, 11:40 AM#3
MasterofSickness
This is my damage and defense spreadsheet:
Click image for larger version

Name:	spread.JPG
Views:	17
Size:	29.1 KB
ID:	48714
So I have to check for four abilities for each of the 8 attack-types.

Now I have created this table-trigger:
Collapse JASS:
function InitTrig_InitCache takes nothing returns nothing
 call FlushGameCache(InitGameCache("cache.w3v"))
 set udg_cache = InitGameCache("cache.w3v")
 //Initialize: Table for Damagetype, Defendtype, Amount
 call StoreReal(udg_cache, "DmgDef_1", "DmgDef_1", 0.2) // 1
 call StoreReal(udg_cache, "DmgDef_1", "DmgDef_2", 1.0)
 call StoreReal(udg_cache, "DmgDef_1", "DmgDef_3", 1.0)
 call StoreReal(udg_cache, "DmgDef_1", "DmgDef_4", 2.0)
 call StoreReal(udg_cache, "DmgDef_1", "DmgDef_5", 0.5)
 call StoreReal(udg_cache, "DmgDef_1", "DmgDef_6", 1.5)
 call StoreReal(udg_cache, "DmgDef_1", "DmgDef_7", 1.0)
 call StoreReal(udg_cache, "DmgDef_1", "DmgDef_8", 1.0)
 call StoreReal(udg_cache, "DmgDef_2", "DmgDef_1", 1.0) // 2
 call StoreReal(udg_cache, "DmgDef_2", "DmgDef_2", 0.2)
 call StoreReal(udg_cache, "DmgDef_2", "DmgDef_3", 2.0)
 call StoreReal(udg_cache, "DmgDef_2", "DmgDef_4", 1.0)
 call StoreReal(udg_cache, "DmgDef_2", "DmgDef_5", 0.5)
 call StoreReal(udg_cache, "DmgDef_2", "DmgDef_6", 1.0)
 call StoreReal(udg_cache, "DmgDef_2", "DmgDef_7", 1.5)
 call StoreReal(udg_cache, "DmgDef_2", "DmgDef_8", 1.0)
 call StoreReal(udg_cache, "DmgDef_3", "DmgDef_1", 1.0) // 3
 call StoreReal(udg_cache, "DmgDef_3", "DmgDef_2", 1.0)
 call StoreReal(udg_cache, "DmgDef_3", "DmgDef_3", 0.2)
 call StoreReal(udg_cache, "DmgDef_3", "DmgDef_4", 1.0)
 call StoreReal(udg_cache, "DmgDef_3", "DmgDef_5", 1.5)
 call StoreReal(udg_cache, "DmgDef_3", "DmgDef_6", 0.5)
 call StoreReal(udg_cache, "DmgDef_3", "DmgDef_7", 2.0)
 call StoreReal(udg_cache, "DmgDef_3", "DmgDef_8", 1.0)
 call StoreReal(udg_cache, "DmgDef_4", "DmgDef_1", 1.5) // 4
 call StoreReal(udg_cache, "DmgDef_4", "DmgDef_2", 1.0)
 call StoreReal(udg_cache, "DmgDef_4", "DmgDef_3", 1.0)
 call StoreReal(udg_cache, "DmgDef_4", "DmgDef_4", 0.2)
 call StoreReal(udg_cache, "DmgDef_4", "DmgDef_5", 1.0)
 call StoreReal(udg_cache, "DmgDef_4", "DmgDef_6", 0.5)
 call StoreReal(udg_cache, "DmgDef_4", "DmgDef_7", 1.0)
 call StoreReal(udg_cache, "DmgDef_4", "DmgDef_8", 2.0)
 call StoreReal(udg_cache, "DmgDef_5", "DmgDef_1", 1.0) // 5
 call StoreReal(udg_cache, "DmgDef_5", "DmgDef_2", 0.5)
 call StoreReal(udg_cache, "DmgDef_5", "DmgDef_3", 1.0)
 call StoreReal(udg_cache, "DmgDef_5", "DmgDef_4", 1.0)
 call StoreReal(udg_cache, "DmgDef_5", "DmgDef_5", 0.2)
 call StoreReal(udg_cache, "DmgDef_5", "DmgDef_6", 2.0)
 call StoreReal(udg_cache, "DmgDef_5", "DmgDef_7", 1.5)
 call StoreReal(udg_cache, "DmgDef_5", "DmgDef_8", 1.0)
 call StoreReal(udg_cache, "DmgDef_6", "DmgDef_1", 2.0) // 6
 call StoreReal(udg_cache, "DmgDef_6", "DmgDef_2", 1.0)
 call StoreReal(udg_cache, "DmgDef_6", "DmgDef_3", 1.0)
 call StoreReal(udg_cache, "DmgDef_6", "DmgDef_4", 0.5)
 call StoreReal(udg_cache, "DmgDef_6", "DmgDef_5", 1.5)
 call StoreReal(udg_cache, "DmgDef_6", "DmgDef_6", 0.2)
 call StoreReal(udg_cache, "DmgDef_6", "DmgDef_7", 1.0)
 call StoreReal(udg_cache, "DmgDef_6", "DmgDef_8", 1.0)
 call StoreReal(udg_cache, "DmgDef_7", "DmgDef_1", 1.0) // 7
 call StoreReal(udg_cache, "DmgDef_7", "DmgDef_2", 2.0)
 call StoreReal(udg_cache, "DmgDef_7", "DmgDef_3", 1.5)
 call StoreReal(udg_cache, "DmgDef_7", "DmgDef_4", 1.0)
 call StoreReal(udg_cache, "DmgDef_7", "DmgDef_5", 1.0)
 call StoreReal(udg_cache, "DmgDef_7", "DmgDef_6", 1.0)
 call StoreReal(udg_cache, "DmgDef_7", "DmgDef_7", 0.2)
 call StoreReal(udg_cache, "DmgDef_7", "DmgDef_8", 0.5)
 call StoreReal(udg_cache, "DmgDef_8", "DmgDef_1", 1.0) // 8
 call StoreReal(udg_cache, "DmgDef_8", "DmgDef_2", 1.0)
 call StoreReal(udg_cache, "DmgDef_8", "DmgDef_3", 1.0)
 call StoreReal(udg_cache, "DmgDef_8", "DmgDef_4", 1.0)
 call StoreReal(udg_cache, "DmgDef_8", "DmgDef_5", 2.5)
 call StoreReal(udg_cache, "DmgDef_8", "DmgDef_6", 0.5)
 call StoreReal(udg_cache, "DmgDef_8", "DmgDef_7", 1.0)
 call StoreReal(udg_cache, "DmgDef_8", "DmgDef_8", 0.2)
endfunction

Hmm...
Is gamecache or hashtables better?
Hashtables are new to me.

EDIT
Well, I think hashtables are better, otherwise current threads would still deal with gamecache. So I will search for them.
Got a good start with "jEdit" (used Jasscraft all the time with old common and blizzard.j so there also were no hashtables declared )
You said hashtables with GetUnitTypeId...
I will start with an Init_trigger:
Collapse JASS:
globals
 hashtable ht = InitHashtable()
endglobals


function InitTrig_InitHash takes nothing returns nothing
 //call SaveReal(ht, parentkey?, childkey?, 0.2)
 //parentKey Usage by UnitId
 //childKey Usage
 //1=Water
 //2=Earth
 //3=Wind
 //4=Fire
 //5=Darkness
 //6=Light
 //7=Moon
 //8=Tree

 //WaterTowers
 call SaveReal(ht, 'hgtw', 1, 0.2)
 call SaveReal(ht, 'hgtw', 4, 2.0)
 call SaveReal(ht, 'hgtw', 5, 0.5)
 call SaveReal(ht, 'hgtw', 6, 1.5)
 
 //WaterCreeps c100 - c199 (small c not big C, because when "C", then automatically categorie "Hero")
 call SaveInteger(ht, 'c100', 1, 1)
 
 //EarthCreeps c200 - c299
 call SaveInteger(ht, 'c200', 1, 2)
 
 //...
endfunction

I just copied the triggers "DamageEvent", "DamageModifiers", "Table" and "RetributionBarrier"(for comparing) from your "ABuffSystem1.5".
I worked out a trigger with very much patience which does not work. I already had this predicted feeling, that I will not get this to work, but set it up in the best logic way I can think of. Finally I have done this all for you, so that you can see my big problems with structs and so on. Once I read all about structs and vjass in Vexorians brilliant description from "jasshelpermanual.html". It was so much that I read it one time and now after a few years I forgot almost all and it is hard for me to bring up so much time to study the whole syntax again, although I only want this small system...
(To unveil you my current constitution: I feel a heavy rage coming up, but I stand it, in the hope that your next post brings me a big step forward...)
Hope you can clarify this puzzling code from me. Here it is finally:
Collapse JASS:
scope UnitFinishConstruction
   private struct barrier extends DamageModifier
        real blockFactor
        unit affected
        unit caster

        integer damagedUnitArmor
        
        static method create takes unit u, unit caster returns barrier
            local barrier this = barrier.allocate(u, BARRIER_PRIORITY)
            set this.damagedUnitArmor = LoadInteger(ht, u, 1)
            set this.blockFactor = LoadReal(ht, caster, this.damagedUnitArmor)
            set this.caster = caster
            set this.affected = u
            return this
        endmethod

    endstruct
endscope

function UnitFinishConstruction_damage takes nothing returns nothing
call barrier.create(GetAttackedUnitBJ(),GetAttacker())//error: barrier doesn't allow .syntax
endfunction

function Trig_UnitFinishConstruction_Actions takes nothing returns nothing

local unit u = GetConstructedStructure()
local trigger t = CreateTrigger()

call SaveTriggerHandle(ht, 1, u, t)// Another trigger with event EVENT_PLAYER_UNIT_SELL should delete the trigger later when the tower is sold...

call TriggerRegisterUnitEvent( t, u, EVENT_UNIT_ACQUIRED_TARGET )// I think this is the wrong event, in fact there is no real event for attacking, therefor I should use your ABuffSystem(?)
call TriggerAddAction( t, function UnitFinishConstruction_damage )

endfunction

//===========================================================================
function InitTrig_UnitFinishConstruction takes nothing returns nothing
    set gg_trg_UnitFinishConstruction = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_UnitFinishConstruction, EVENT_PLAYER_UNIT_CONSTRUCT_FINISH )
    call TriggerAddAction( gg_trg_UnitFinishConstruction, function Trig_UnitFinishConstruction_Actions )
endfunction

Theory
DamageEvent gives every unit who can take damage and enters the map an own trigger which fires when the unit takes damage.
Damager unit, damaged unit and damage amount get executed by the response function. Right before that, the damage amount is set to the DamageModifiers.
So, I just have to copy the trigger DamageEvent. Units get registered with damage event triggers automatically...
Every time a unit gets damage, the DamageModifiers Trigger will be launched...
Hmm... And then I only need one trigger (one scope) which fires one time on Init so that a new Modifier is registered (extend the DamageModifier).
There I use onDamageDealt or onDamageTaken (what makes the difference? I don't get it) with the saved real values from the hashtables with UnitId...

I think I don't want DamageEvent. It already creates a trigger for every unit. And every trigger launches the DamageModifiers, but what's in it for me? Nothing happens...
Ahhh... On the other side DamageEvent so nicely creates triggers with the function Damage, but I only can follow till static method RunModifiers and then?

Here I have big problems:
Quote:
Whenever a unit that can deal damage is created in game (if these are towers in a TD, you would use the "finishes construction" event, otherwise use "unit enters region" or whatever), create a damage modifier for it.
Ok, I seriously want to do this, but how?
How do I create a damageModifier for a special unit?
The event is apparent, but the damageModifier is a struct...
I don't know how I can refer between the function and the struct without using ABuffs SpellEvent.
And I don't want to use SpellEvent because it certainly is for heros, but not for normal units.

If you will show me how, then here is the next thing that will come up:
Quote:
If this unit can never be destroyed, such as towers in some TDs, then you don't have to worry about cleanup, otherwise you should somehow attach the damage modifier to your unit so when the unit dies, you can destroy the modifier. One way to do this is with ABuffs, but I won't go into more detail unless you need this, because if you don't then you can easily do this without ABuff alltogether, just with Damage Modifiers.
My towers can be sold, so the damage modifiers has be destroyed at that time.

I give up.
I tried several hours to understand, but the system is just to big for me.
Have to wait for your helping hand.


EDIT 2

So, I have a little time again...
Tried to understand the DamageEvent first.
I see now, that I need DamageModifiers as base.
If I use DamageEvent too, then the advantage would be, that I don't have to look for damage events, as the DamageEvent-system already assumes this and runs DamageModifiers automatically everytime a unit gets damage.

Ok, then the DamageModifier-System is what I have to learn now...
I will read again and again depending on sparetime, since I seriously want your system!
Attached Images
File type: jpgspread.JPG (29.1 KB)
05-13-2010, 11:47 PM#4
MasterofSickness
I have finally made progress

I'm using DamageEvent, DamageModifiers and required Table now.

Here is my current working hashtable library
Collapse JASS:
library InitHashtable initializer Init

globals
 hashtable ht_SoM = InitHashtable()
endglobals

private function Init takes nothing returns nothing // 1 function must be named "Init" for library to run
  //call SaveReal(ht, parentkey?, childkey?, 0.2)
 //parentKey Usage by UnitId
 //childKey Usage
 //1=Water
 //2=Earth
 //3=Wind
 //4=Fire
 //5=Darkness
 //6=Light
 //7=Moon
 //8=Tree
 
 // List of Damager Towers
 // Water
 call SaveReal(ht_SoM, 'hgtw', 1, 0.2) //native  SaveReal - takes hashtable table, integer parentKey, integer childKey, real value returns nothing
 call SaveReal(ht_SoM, 'hgtw', 4, 12.0)
 call SaveReal(ht_SoM, 'hgtw', 5, 0.5)
 call SaveReal(ht_SoM, 'hgtw', 6, 1.5)
 
endfunction

endlibrary

And here is the current WORKING DamageSystem library
Collapse JASS:
library DamageSystem initializer Init requires DamageModifiers

struct Test extends DamageModifier

 static integer PRIORITY=0 // Default priority
 real power // This lets us give different units differently strong armour.
 unit damager

    // create method is optional, if you don't declare one then you must use
    // the .allocate parameters (unit, integer) when creating a modifier of this kind.
 static method create takes unit u returns Test //, real power
    // Note the parameters for .allocate, this is because this struct extends
    // DamageModifier which asks for these parameters in its create method:
  local Test this = Test.allocate(u, Test.PRIORITY)
  set this.damager = u
  //set this.power = power
  return this
 endmethod

    // This is the method that runs when damage is dealt from the unit with the modifier.
    // The damage parameter tells how much damage got to this modifier past any modifiers
    // with a higher priority that the unit may have.
    // The value that the method returns tells the system by how much to modify the damage,
    // a positive return value increases damage while a negative value reduces it.
 method onDamageDealt takes unit damagedUnit, real damage returns real
  
  local string s = SubString(UnitId2String(GetUnitTypeId(damagedUnit)),8,9) // cut "custom_c" + "00" from "custom_cX00" (X = 1-8)
  local integer i = S2I(s)
  local real r = LoadReal(ht_SoM, GetUnitTypeId(this.damager), i) //native  LoadReal - takes hashtable table, integer parentKey, integer childKey returns real
  return -((1-r) * damage)
   
 endmethod
    // onDestroy method is missing...

endstruct




function Well takes nothing returns nothing

local unit u = GetConstructedStructure()
local Test A = Test.create(u)


endfunction

private function Init takes nothing returns nothing // 1 function must be named "Init" for library to run
 local trigger t = CreateTrigger()
 call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_CONSTRUCT_FINISH ) // b.j-function for choosing all players
 call TriggerAddAction( t, function Well )
endfunction

endlibrary

Note that I don't store both, the armor and the damage type, in the hashtable. I only store the real damage modication and get the armor type from the unitIDs of the creeps themselves, set up with Grimexs Object Merger. I think thats quite fine, although I don't know how fast "UnitId2String" is...

So, Anitarf,
now I dare to ask you again about the cleanup.
How can I destroy the modifiers created?
Somehow with a onDestroy - method I guess...
05-14-2010, 10:25 AM#5
Anitarf
I think you'll need a lot of needless copy-pasting to get this to work for all towers. Also, it will be a pain to change damage percentages later on. Let's restructure the elemental armour system:

Collapse JASS:
library ElementalSystem initializer Init

    globals
        // Let us make things a bit more readable:
        public constant integer WATER    = 1
        public constant integer EARTH    = 2
        public constant integer WIND     = 3
        public constant integer FIRE     = 4
        public constant integer DARKNESS = 5
        public constant integer LIGHT    = 6
        public constant integer MOON     = 7
        public constant integer TREE     = 8

        private constant integer ARMOUR  = 9
        private constant integer WEAPON  = 10

        private hashtable ht = InitHashtable()
    endglobals


// DATA SETUP:

    private function Init takes nothing returns nothing
        //Repeat this for all elements:
        call SaveReal(ht, WATER,    WATER,     0.2)
        call SaveReal(ht, WATER,    EARTH,     1.0)
        call SaveReal(ht, WATER,    WIND,      1.0)
        call SaveReal(ht, WATER,    FIRE,     12.0)
        call SaveReal(ht, WATER,    DARKNESS,  0.5)
        call SaveReal(ht, WATER,    LIGHT,     1.5)
        call SaveReal(ht, WATER,    MOON,      1.0)
        call SaveReal(ht, WATER,    TREE,      1.0)

        //Repeat this for all towers:
        call SaveInteger(ht, WEAPON, 'hgtw', WATER)

        //Repeat this for all creeps:
        call SaveInteger(ht, ARMOUR, 'c100', WATER)
    endfunction


// PUBLIC FUNCTIONS:

    function GetElementalDamageFactor takes integer attackElement, integer defenceElement returns real
        return LoadReal(ht, attackElement, defenceElement)
    endfunction

    function GetUnitAttackElement takes unit u returns integer
        return LoadInteger(ht, WEAPON, GetUnitTypeId(u))
    endfunction
    function GetUnitDefenceElement takes unit u returns integer
        return LoadInteger(ht, ARMOUR, GetUnitTypeId(u))
    endfunction

endlibrary

Then we make the damage modifiers using the above library. As long as towers in your map can not be sold/destroyed, there is no need to worry about cleaning up the damage modifiers. In case you need that, though, I'll add support for it as well.

Collapse JASS:
library ElementalDamageSystem initializer Init requires ElementalSystem, DamageModifiers, Table

    private struct ElementalDamage extends DamageModifier
        private static integer PRIORITY=0 // Default priority
        private static HandleTable ht

        private integer element
        private unit tower

        static method onInit takes nothing returns nothing
            set ht=HandleTable.create()
        endmethod

        static method create takes unit u, integer element returns ElementalDamage
            local ElementalDamage this = ElementalDamage.allocate(u, Test.PRIORITY)
            set this.tower = u
            set this.element = element
            set ht[u]=integer(this)
            return this
        endmethod

        method onDestroy takes nothing returns nothing
            call ht.flush(this.tower)
            set this.tower=null
        endmethod

        static method clear takes unit u returns boolean
            local ElementalDamage this= ElementalDamage( ht[u] )
            if this != 0 then
                call this.destroy()
                return true
            endif
            return false
        endmethod

        method onDamageDealt takes unit damagedUnit, real damage returns real
            return -(( 1 - GetElementalDamageFactor(this.element, GetUnitDefenceElement(damagedUnit)) ) * damage)
        endmethod
    endstruct



    private function RegisterTower takes nothing returns nothing
        local unit u=GetTriggerUnit()
        local integer element = GetUnitAttackElement(u)
        if element>0 then
            call ElementalDamage.create(u, element)
        endif
        set u=null
    endfunction

    public function TowerDeath takes unit u returns nothing
        call ElementalDamage.clear(u) // Call this function when a tower dies/is sold
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_CONSTRUCT_FINISH )
        call TriggerAddAction( t, function RegisterTower )
    endfunction

endlibrary
05-14-2010, 12:28 PM#6
MasterofSickness
This is so wonderful!
I would not have been able to code such a neat script, not in 4 weeks or longer,
so thanks a lot!
Especially the clean and more useful reconstruction of the elemental armour system is something I like.
Thanks for this!

Quote:
...cleaning up the damage modifiers. In case you need that, though, I'll add support for it as well.
Yes, I definitely need this. So thanks again for adding it!

My problems are hereby done for now.
I will give you rep as soon as I can
In my credits you will be listet at the top

Apparently you had to donate a bit time for this code,
I really appreciate it.