HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Damage Detection problem

01-09-2009, 10:05 PM#1
Flame_Phoenix
My last unfinished spell was a simple shield that could absorb damage from enemy units. To create this shield I saw some very old codes but they were bad coded and used Kattana's which I hate by now deeply (hurray cache corruption). So I decided to post here for help, but I only managed to start a fight because I entered the world of dynamic triggers, where avoiding leaks is the price for stability.As an idiot said "just use dynamic triggers and stop crying". Ofc I didn't listen to the idiot at first .... I decided to search for damage detection system and I tried to find as many as possible, but in the end, I came only with 2options:
- IDDS by Rising_Dusk, although intuitive and easy it doesn't allow for the use of shields, which I require
- ADamage, although it has everything the system is so complex I can't even create a Crtical Strike spell (which is simple)...

So, now that I am done with system I decided to either create my own or to do the spells without any. I don't have experience to create my own system, so I decided to just use dynamic triggers.

This is the code of my spell, I made some improvements, and it works with only 1 timer however, nothing happens ! The hero doesn't get any heal and it is being damage all the time !!
If some one could help me in the new world of Damage detection I would appreciate, but please, enough systems, I want to do this without one.
If some could help I would appreciate.

Collapse JASS:
 
//===========================================================================
//A JESP spell that allows the hero to cast a shield on himself. The shield
//will absorb an amount of damage for the hero thus protecting him from harm.
//
//Requires TimerUtils and Table
//
//@author Flame_Phoenix 
//
//@credits
// - Pyrogasm, for the one time algorithm
//
//@version 1.0
//===========================================================================
scope LightShield initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
    globals
        private constant integer AID = 'A001'
        private constant integer BUFF_ID = 'BNab'
        private constant real TIMER_CYCLE = 0.1
    endglobals
    
    private constant function Duration takes integer level returns real
        return level * 90.
    endfunction
    
    private constant function ShieldAbsorb takes integer level returns real
        return level * 100.
    endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
     private keyword SpellData
    
    globals
        private group LightShieldUnits
        private HandleTable activeTable 
        
        private timer t
        private integer instancesCount
        private SpellData array datas
    endglobals


    private function onDamage takes nothing returns boolean
        local SpellData data
        local unit u = GetTriggerUnit()
        
        call BJDebugMsg("DmgCond")
        
        //we make sure that the unit that is hit is our victim
        if(IsUnitInGroup(u, LightShieldUnits)) then
            //recover that data (the struct) from the victim
            set data = activeTable[u] 
            
            call BJDebugMsg("onGroup")
            
            if (GetUnitAbilityLevel(u, BUFF_ID) > 0) then
                call BJDebugMsg("buffOn")
                call data.prevent()
            endif
            
        endif

        set u = null
        
        return false
    endfunction
    
    private struct SpellData
        unit caster
        unit target
        integer level
        real shieldLife
        real dur
        boolean done
        
        static method create takes unit caster, unit target, integer level returns SpellData
            local SpellData data = SpellData.allocate()
            local trigger damageTrg = CreateTrigger(  )
            
            set data.caster = caster
            set data.level = level
            set data.target = target
            set data.shieldLife = ShieldAbsorb(data.level)
            set data.dur = 0
            set data.done = false
            
            //put the struct in the Table, we just use the target's
            //handle adress as the key which tells us where in the 
            //Table the struct is stored
            set activeTable[data.target] = data 
            call GroupAddUnit(LightShieldUnits, data.target)

            //when the hero takes damage
            call TriggerRegisterUnitEvent(damageTrg, data.target, EVENT_UNIT_DAMAGED )
            call TriggerAddCondition(damageTrg, Condition(function onDamage))
            
            return data
        endmethod
        
        method shieldTime takes nothing returns nothing
            set .dur = .dur + TIMER_CYCLE
            
            if (.dur >= Duration(.level)) or (GetUnitAbilityLevel(.target, BUFF_ID) < 1) then
                set .done = true
            endif
        endmethod
        
        method prevent takes nothing returns nothing
            local real dmg = GetEventDamage()
            
            if (dmg >= .shieldLife) then
                //heal the unit to with the remaining hp of the shield to prevent some 
                //of teh damage. Prevention is the shield hp, the rest will pass
                call SetWidgetLife(.target, GetWidgetLife(.target) + .shieldLife)
                set .done = true
            else 
                //heal the unit to prevent the damage
                call SetWidgetLife(.target, GetWidgetLife(.target) + dmg)
                set .shieldLife = .shieldLife - dmg
                call BJDebugMsg(R2S(.shieldLife))
            endif
        endmethod
        
        method onDestroy takes nothing returns nothing
            
            //we remove the buff
            call UnitRemoveAbility(.target, BUFF_ID)
            
            //since the spell is not active anymore, we clean the Table     
            call activeTable.flush(.target) 
            
            //the units are not anymore in the active units group.
            call GroupRemoveUnit(LightShieldUnits, .target) 
        endmethod
    endstruct
    
    private function Periodic takes nothing returns nothing
        local integer currentIndex = 0
        local SpellData currentInstance
        
        loop
            set currentInstance = datas[currentIndex]
            call currentInstance.shieldTime() 
            
            //if our instance is done, we decrement the number of total instances
            //and then we check if there are any more instances being run
            if (currentInstance.done) then
                set instancesCount = instancesCount - 1
                
                //if there are, then we update our instance to the next of the array
                //and we correct the index
                if (instancesCount > 0) then
                    set datas[currentIndex] = datas[instancesCount]
                    set currentIndex = currentIndex - 1
                //else we just release the timer
                else
                    call ReleaseTimer(t)
                endif
                
                //now before we leave current instance for good, we destroy it!
                call currentInstance.destroy()
            
            endif
                
            set currentIndex = currentIndex + 1
            exitwhen currentIndex >= instancesCount
            
        endloop
    endfunction
//===========================================================================
    private function Conditions takes nothing returns boolean
        
        if (GetSpellAbilityId() == AID) then
        
            //if there are no instances of the spell then it means the timer does not
            //exist, so we create it and start it!
            if (instancesCount == 0) then
                set t = NewTimer()
                call TimerStart(t, TIMER_CYCLE, true, function Periodic)
            endif
                
            set datas[instancesCount] = SpellData.create(GetTriggerUnit(), GetSpellTargetUnit(), GetUnitAbilityLevel(GetTriggerUnit(), AID))
            set instancesCount = instancesCount + 1
        
        endif
        
        return false
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        //when the hero casts the spell
        local trigger LightShieldTrg = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( LightShieldTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(LightShieldTrg, Condition(function Conditions))
        set LightShieldTrg = null
        
        //setting our globals
        set LightShieldUnits = CreateGroup()
        set activeTable = HandleTable.create() 
        set instancesCount = 0
        
    endfunction
endscope

This is the first post I create for help in many months, I hope I get some help because I don't know when I will post another ...

According to the debugg messages I assume the problem is on the dynamic trigger. The condition is NEVER run and I don't know why.

Thx in advance, hee is the map for your joy.
Please note I didn't create the map nor the original spell. I am remaking the spell so I can change it in the future, but first I need the basics, I ned this thing working.

EDIT EDIT EDIT


Bump

Ok guys I fixed the problem, now the spell works,m however I have 2 small problems...
1 - When the shielded unit is damaged, if it is full HP, then it will lose life and I don't know how to fix this =(
2 - the trigger leaks ... The spell works good the first time, but because the trigger leaks, the second time I cast the spell, the shield will take 2x damage, because it will run same code twice. If I cast it 3 times, it runs the same code 3x so shield takes 3x extra damage, and I don't want it =(

If some one could help ...

Collapse JASS:
//===========================================================================
//A JESP spell that allows the hero to cast a shield on himself. The shield
//will absorb an amount of damage for the hero thus protecting him from harm.
//
//Requires TimerUtils and Table
//
//@author Flame_Phoenix 
//
//@credits
// - Pyrogasm, for the one time algorithm
//
//@version 1.1
//===========================================================================
scope LightShield initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
    globals
        private constant integer AID = 'A001'
        private constant integer BUFF_ID = 'BNab'
        private constant real TIMER_CYCLE = 0.1
    endglobals
    
    private constant function Duration takes integer level returns real
        return level * 90.
    endfunction
    
    private constant function ShieldAbsorb takes integer level returns real
        return level * 100.
    endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
     private keyword SpellData
    
    globals
        private group LightShieldUnits
        private HandleTable activeTable 
        
        private timer t
        private integer instancesCount
        private SpellData array datas
    endglobals


    private function onDamage takes nothing returns boolean
        local SpellData data
        local unit u = GetTriggerUnit()
        
        //we make sure that the unit that is hit is our victim
        if(IsUnitInGroup(u, LightShieldUnits)) then
            //recover that data (the struct) from the victim
            set data = activeTable[u] 
                        
            if (GetUnitAbilityLevel(u, BUFF_ID) > 0) then
                call data.prevent()
            else
                set data.done = true
            endif
            
        endif

        set u = null
        
        return false
    endfunction
    
    private struct SpellData
        unit caster
        unit target
        integer level
        real shieldLife
        real dur
        boolean done
        
        static method create takes unit caster, unit target, integer level returns SpellData
            local SpellData data = SpellData.allocate()
            local trigger damageTrg = CreateTrigger(  )
            
            set data.caster = caster
            set data.level = level
            set data.target = caster    //our target is the caster....
            set data.shieldLife = ShieldAbsorb(data.level)
            set data.dur = 0
            set data.done = false
            
            //put the struct in the Table, we just use the target's
            //handle adress as the key which tells us where in the 
            //Table the struct is stored
            set activeTable[data.target] = data 
            call GroupAddUnit(LightShieldUnits, data.target)

            //when the hero takes damage
            call TriggerRegisterUnitEvent(damageTrg, data.target, EVENT_UNIT_DAMAGED )
            call TriggerAddCondition(damageTrg, Condition(function onDamage))
        
            return data
        endmethod
        
        method shieldTime takes nothing returns nothing
            set .dur = .dur + TIMER_CYCLE
            
            if (.dur >= Duration(.level)) or (GetUnitAbilityLevel(.target, BUFF_ID) < 1) then 
                set .done = true
            endif
            
        endmethod
        
        method prevent takes nothing returns nothing
            local real dmg = GetEventDamage()
            
            if (dmg >= .shieldLife) then
                //heal the unit to with the remaining hp of the shield to prevent some 
                //of teh damage. Prevention is the shield hp, the rest will pass
                call SetWidgetLife(.target, GetWidgetLife(.target) + .shieldLife)
                set .done = true
            else 
                if GetWidgetLife(.target) == GetUnitState(.target, UNIT_STATE_MAX_LIFE) then
                    call SetWidgetLife(.target, GetUnitState(.target, UNIT_STATE_MAX_LIFE))
                    call BJDebugMsg("max me!")
                else
                    //heal the unit to prevent the damage
                    call SetWidgetLife(.target, GetWidgetLife(.target) + dmg)
                    set .shieldLife = .shieldLife - dmg
                    call BJDebugMsg(R2S(.shieldLife))
                endif
            endif
        endmethod
        
        method onDestroy takes nothing returns nothing
            
             call BJDebugMsg("============================================================")
            //we remove the buff
            call UnitRemoveAbility(.target, BUFF_ID)
            
            //since the spell is not active anymore, we clean the Table     
            call activeTable.flush(.target) 
            
            //the units are not anymore in the active units group.
            call GroupRemoveUnit(LightShieldUnits, .target) 
        endmethod
    endstruct
//===========================================================================
    private function Periodic takes nothing returns nothing
        local integer currentIndex = 0
        local SpellData currentInstance
        
        loop
            set currentInstance = datas[currentIndex]
            call currentInstance.shieldTime() 
            
            //if our instance is done, we decrement the number of total instances
            //and then we check if there are any more instances being run
            if (currentInstance.done) then
                set instancesCount = instancesCount - 1
                
                //if there are, then we update our instance to the next of the array
                //and we correct the index
                if (instancesCount > 0) then
                    set datas[currentIndex] = datas[instancesCount]
                    set currentIndex = currentIndex - 1
                //else we just release the timer
                else
                    call ReleaseTimer(t)
                endif
                
                //now before we leave current instance for good, we destroy it!
                call currentInstance.destroy()
            
            endif
                
            set currentIndex = currentIndex + 1
            exitwhen currentIndex >= instancesCount
            
        endloop
    endfunction
//===========================================================================
    private function Conditions takes nothing returns boolean
        
        if (GetSpellAbilityId() == AID) then
        
            //if there are no instances of the spell then it means the timer does not
            //exist, so we create it and start it!
            if (instancesCount == 0) then
                set t = NewTimer()
                call TimerStart(t, TIMER_CYCLE, true, function Periodic)
            endif
                
            set datas[instancesCount] = SpellData.create(GetTriggerUnit(), GetSpellTargetUnit(), GetUnitAbilityLevel(GetTriggerUnit(), AID))
            set instancesCount = instancesCount + 1

        endif
        
        return false
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        //when the hero casts the spell
        local trigger LightShieldTrg = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( LightShieldTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(LightShieldTrg, Condition(function Conditions))
        set LightShieldTrg = null
        
        //setting our globals
        set LightShieldUnits = CreateGroup()
        set activeTable = HandleTable.create() 
        set instancesCount = 0
        
    endfunction
endscope
Attached Files
File type: w3xEnergy Shield.w3x (34.0 KB)
01-10-2009, 02:06 PM#2
Flame_Phoenix
Quote:
Ok guys I fixed the problem, now the spell works,m however I have 2 small problems...
1 - When the shielded unit is damaged, if it is full HP, then it will lose life and I don't know how to fix this =(
2 - the trigger leaks ... The spell works good the first time, but because the trigger leaks, the second time I cast the spell, the shield will take 2x damage, because it will run same code twice. If I cast it 3 times, it runs the same code 3x so shield takes 3x extra damage, and I don't want it =(

Update: Ok guys I fixed problem 2 of the spell. Now the shield works properly, except in case 1, which is when the unit has full hp. I would appreciate any possible help, since I feel this thread is being ignored =(

I tried adding an ability that increases hp as I saw in a DD system, but so far that isn't working.
Collapse JASS:
//===========================================================================
//A JESP spell that allows the hero to cast a shield on himself. The shield
//will absorb an amount of damage for the hero thus protecting him from harm.
//
//Requires TimerUtils and Table
//
//@author Flame_Phoenix 
//
//@credits
// - Pyrogasm, for the one time algorithm
// -  ixtreon, the original creator of the spell gave me the idea for making my own
//
//@version 1.2
//===========================================================================
scope LightShield initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
    globals
        private constant integer AID = 'A001'
        private constant integer BUFF_ID = 'BNab'
        private constant integer LIFE_ID = '7A00'
        private constant real TIMER_CYCLE = 0.1
    endglobals
    
    private constant function Duration takes integer level returns real
        return level * 90.
    endfunction
    
    private constant function ShieldAbsorb takes integer level returns real
        return level * 100.
    endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
     private keyword SpellData
    
    globals
        private group LightShieldUnits
        private HandleTable activeTable 
        
        private timer t
        private integer instancesCount
        private SpellData array datas
    endglobals


    private function onDamage takes nothing returns boolean
        local SpellData data
        local unit u = GetTriggerUnit()
        
        //we make sure that the unit that is hit is our victim
        if(IsUnitInGroup(u, LightShieldUnits)) then
            //recover that data (the struct) from the victim
            set data = activeTable[u] 
                        
            if (GetUnitAbilityLevel(u, BUFF_ID) > 0) then
                call data.prevent()
            else
                set data.done = true
            endif
            
        endif

        set u = null
        
        return false
    endfunction
    
    private struct SpellData
        unit caster
        unit target
        integer level
        real shieldLife
        real dur
        boolean done
        trigger damageTrg
        
        static method create takes unit caster, unit target, integer level returns SpellData
            local SpellData data = SpellData.allocate()
            
            set data.caster = caster
            set data.level = level
            set data.target = caster    //our target is the caster....
            set data.shieldLife = ShieldAbsorb(data.level)
            set data.dur = 0
            set data.done = false
            
            
            
            //put the struct in the Table, we just use the target's
            //handle adress as the key which tells us where in the 
            //Table the struct is stored
            set activeTable[data.target] = data 
            call GroupAddUnit(LightShieldUnits, data.target)

           //when the hero takes damage
            if  data.damageTrg == null then
                set data.damageTrg = CreateTrigger()
                call TriggerRegisterUnitEvent(data.damageTrg, data.target, EVENT_UNIT_DAMAGED )
                call TriggerAddCondition(data.damageTrg, Condition(function onDamage))
            endif
        
            return data
        endmethod
        
        method shieldTime takes nothing returns nothing
            set .dur = .dur + TIMER_CYCLE
            
            if (.dur >= Duration(.level)) or (GetUnitAbilityLevel(.target, BUFF_ID) < 1) then 
                set .done = true
            endif
            
        endmethod
        
        method prevent takes nothing returns nothing
            local real dmg = GetEventDamage()
            
            if (dmg >= .shieldLife) then
                //heal the unit to with the remaining hp of the shield to prevent some 
                //of teh damage. Prevention is the shield hp, the rest will pass
                call SetWidgetLife(.target, GetWidgetLife(.target) + .shieldLife)
                set .done = true
            else 
                //if the damage plus unit current life exceed max life, then the unit takes damage.
                //Here we prevent the unit from taking that damage
                if (dmg + GetWidgetLife(.target) >= GetUnitState(.target, UNIT_STATE_MAX_LIFE)) then
                    call UnitAddAbility(.target, LIFE_ID)
                    call SetWidgetLife(.target, GetUnitState(.target, UNIT_STATE_MAX_LIFE))
                    call BJDebugMsg("max me!")
                    call UnitRemoveAbility(.target, LIFE_ID)
                //this is a normal case, where we heal the unit and damage the shield
                else
                    //heal the unit to prevent the damage
                    call SetWidgetLife(.target, GetWidgetLife(.target) + dmg)
                    call BJDebugMsg(R2S(.shieldLife))
                endif
                
                //update the life of the shield!
                set .shieldLife = .shieldLife - dmg
            endif
        endmethod
        
        method onDestroy takes nothing returns nothing
            
             call BJDebugMsg("============================================================")
            //we remove the buff
            call UnitRemoveAbility(.target, BUFF_ID)
            
            //since the spell is not active anymore, we clean the Table     
            call activeTable.flush(.target) 
            
            //the units are not anymore in the active units group.
            call GroupRemoveUnit(LightShieldUnits, .target) 
        endmethod
    endstruct
//===========================================================================
    private function Periodic takes nothing returns nothing
        local integer currentIndex = 0
        local SpellData currentInstance
        
        loop
            set currentInstance = datas[currentIndex]
            call currentInstance.shieldTime() 
            
            //if our instance is done, we decrement the number of total instances
            //and then we check if there are any more instances being run
            if (currentInstance.done) then
                set instancesCount = instancesCount - 1
                
                //if there are, then we update our instance to the next of the array
                //and we correct the index
                if (instancesCount > 0) then
                    set datas[currentIndex] = datas[instancesCount]
                    set currentIndex = currentIndex - 1
                //else we just release the timer
                else
                    call ReleaseTimer(t)
                endif
                
                //now before we leave current instance for good, we destroy it!
                call currentInstance.destroy()
            
            endif
                
            set currentIndex = currentIndex + 1
            exitwhen currentIndex >= instancesCount
            
        endloop
    endfunction
//===========================================================================
    private function Conditions takes nothing returns boolean
        
        if (GetSpellAbilityId() == AID) then
        
            //if there are no instances of the spell then it means the timer does not
            //exist, so we create it and start it!
            if (instancesCount == 0) then
                set t = NewTimer()
                call TimerStart(t, TIMER_CYCLE, true, function Periodic)
            endif
                
            set datas[instancesCount] = SpellData.create(GetTriggerUnit(), GetSpellTargetUnit(), GetUnitAbilityLevel(GetTriggerUnit(), AID))
            set instancesCount = instancesCount + 1

        endif
        
        return false
    endfunction
//===========================================================================
    private function Init takes nothing returns nothing
        //when the hero casts the spell
        local trigger LightShieldTrg = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( LightShieldTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(LightShieldTrg, Condition(function Conditions))
        set LightShieldTrg = null
        
        //setting our globals
        set LightShieldUnits = CreateGroup()
        set activeTable = HandleTable.create() 
        set instancesCount = 0
        
    endfunction
endscope
01-10-2009, 05:38 PM#3
Bobo_The_Kodo
It is because the event fires BEFORE the unit takes damage, so it heals them while they are already at full.

You could have:

Collapse JASS:
if damage > hp then
  //heal
elseif hp + damage > maxhp then
  //start a 0. second timer to heal
else
  //heal
endif
01-10-2009, 05:58 PM#4
Flame_Phoenix
Quote:
It is because the event fires BEFORE the unit takes damage, so it heals them while they are already at full.
Yes In deed I already saw that solution implemented many times. I was just wondering, isn't there a better way of doing that?
I tried using a Wait(0) but it didn't work (as expected...)

Anyway thx for being the only 1 to help me out. You deserve +rep and credits.
01-10-2009, 06:15 PM#5
Bobo_The_Kodo
Well TriggerSleepAction is very unstable, so you shouldn't use it anyways. The timer will fire at the same time, just after the unit takes damage. AFAIK that is the best way...
01-10-2009, 06:23 PM#6
Flame_Phoenix
Quote:
Well TriggerSleepAction is very unstable, so you shouldn't use it anyways
I know I made a huge thread about it ... I was just wondering.

PS:
How do I make a timer run a method ?

Collapse JASS:
method specialPrevent takes nothing returns nothing
endmethod

//in next method
call TimerStart(t, 0, false, function specialPrevent)

It says function does not exist =(
01-10-2009, 07:11 PM#7
chobibo
Use a static method instead
Collapse JASS:
static method specialPrevent takes nothing returns nothing
endmethod

//in next method
call TimerStart(t, 0, false, function structname.specialPrevent)
01-10-2009, 10:15 PM#8
Av3n
Use Captain Griffen's Damage Detect system and tag team it with Vexorian's Table system.

I personally use these two in combination to make shield spells, and since CG's Damage Detect system is just a additional function so you can easily link your variable to it using Vexorian Table

-Av3n
01-10-2009, 10:17 PM#9
Flame_Phoenix
Quote:
Use Captain Griffen's Damage Detect system and tag team it with Vexorian's Table system.
Hell no, I am done with searching for DD systems. Besides Griffens system is outated, the only recent systems are ADamage and IDDS and I don't like both.
Thx for your advice though, I still appreciate it.

Ok guys I fixed the spell and submited a final(?) version of it:
http://www.wc3campaigns.net/showthre...00#post1057700

Thx for all help, please comment and enjoy.
01-10-2009, 10:43 PM#10
Gwypaas
I'd suggest trying out Cohadars damage detection system called "ORBEngine" that can be found in this map: http://www.thehelper.net/forums/showthread.php?t=80046
01-10-2009, 10:44 PM#11
Captain Griffen
My system's not at all outdated, just a lightweight up to date system (where you can choose whether to have it in leaking or possibly safer modes).