HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

[script] PhysicalAttack

01-25-2011, 11:10 AM#1
Deaod
A library to detect physical attacks on units using buff-placing attack modifiers.

This library uses vJass, AutoIndex, DamageModifiers and LinkedList. Additionally, you need a library that supports DamageModifiers, and DamageEvent is the only one known to me that does that.

Changelog

Version 1.0.0 - 25/01/2011
- initial release



Collapse JASS:
/*******************************************************************************
 *                       PhysicalAttack -- Version 1.0.0
 *                                 by Deaod
 *
 * A library that detects physical attacks of units using attack modifiers.
 *
 * KNOWN BUGS:
 *  This library cant detect usage of Attack Ground.
 * 
 * API:
 *  You have to register any buff thats placed by an attack on the attacked unit
 *  using
 *
 *  function RegisterAttackBuff takes integer buffid, integer priority returns nothing
 *  
 *  The priority determines in which order these buffs will be checked for, so
 *  its probably a good idea to give more frequently occurring buffs a higher
 *  priority.
 *  
 *  To actually respond to physical attacks, you have to create a struct that
 *  extends PhysicalAttack and implement methods onAttack and/or onAttacked.
 *
 * EXAMPLE:
 *
 *  // simple attack modifier that makes a unit deal 50% more damage 50% of the
 *  // time on the first level, 75% more damage 75% of the time on the second
 *  // level and 100% more damage 100% of the time on the third level.
 *  struct Demonstration extends PhysicalAttack
 *      integer level
 *
 *      // the source of this attack is the unit you created this struct for
 *      // the target is passed to this method, as well as the damage
 *      // and so is the ID of the buff that was detected
 *      method onAttack takes unit target, real damage, integer buffid returns real
 *          if GetRandomReal(0,1)<=(0.25+(level*0.25)) then
 *              // positive return value increases damage dealt
 *              return damage*(0.25+(level*0.25))
 *          else
 *              // negative return value decreases damage dealt
 *              return -damage*(0.25+(level*0.25))
 *          endif
 *      endmethod
 *
 *      // no onAttacked method, so this struct only responds to when a unit
 *      // attacks another unit.
 *
 *      // you are free to implement a create method in your struct, and if
 *      // you do, you have to pass a unit and an integer to the allocator.
 *      static method create takes unit u, integer level returns thistype
 *      // dont forget to pass the unit you want attack detection for to the 
 *      // allocator along with the priority
 *      // the priority determines in which order the structs are processed.
 *      // Higher priority means earlier execution. If there are multiple 
 *      // structs with the same priority, the ones of the attacking unit get
 *      // evaluated first and the first one of those to be evaluated is the
 *      // one created first.
 *      local thistype s=allocate(u, 0)
 *          set s.level=level
 *          return s
 *      endmethod
 *  endstruct
 *
 * CREDITS:
 *  Anitarf (DamageModifiers)
 *  Ammorth (LinkedList)
 *  grim001 (AutoIndex)
 *  Vexorian (JassHelper)
 *  MindWorX (JassNewGenPack)
 *  PitzerMike (JassNewGenPack)
 *  Pipedream (Grimoire)
 *  SFilip (TESH)
 *
 ******************************************************************************/
library PhysicalAttack requires DamageModifiers, AutoIndex, LinkedList
    
    globals
        // this is the priority of this library as a damage modifier.
        // damage modifiers with a higher priority will get evaluated before this one.
        // damage modifiers with a lower priority will get evaluated after this one.
        // its currently the highest possible value.
        // see also: DamageModifiers library
        private constant    integer                 PRIORITY                    = 0x7FFFFFFF
    endglobals
    
    // filter out units you dont want to detect physical attacks for.
    private function UnitFilter takes unit u returns boolean
        return true
    endfunction
    
    // Don't touch anything below.
    
    private keyword UnitData
    private keyword unitData
    private keyword priority
    
    globals
        private List AttackBuffs=0
    endglobals
    
    private struct AttackBuff
        integer abilityID
        integer priority
    endstruct
    
    function RegisterAttackBuff takes integer buffid, integer priority returns nothing
    local Link l
    local AttackBuff s=AttackBuff.create()
        set s.abilityID=buffid
        set s.priority=priority
        if AttackBuffs==0 then
            set AttackBuffs=List.create()
        endif
        loop
            exitwhen l==0 or AttackBuff(l.data).priority<priority or l.next==0
            set l=l.next
        endloop
        if l==0 then
            call Link.create(AttackBuffs, s)
        elseif l.next==0 then
            call Link.createLast(AttackBuffs, s)
        else
            call l.insertBefore(s)
        endif
    endfunction
    
    struct PhysicalAttack
        integer priority
        UnitData unitData
        
        stub method onAttack takes unit target, real damage, integer buffid returns real
            return 0.
        endmethod
        
        stub method onAttacked takes unit source, real damage, integer buffid returns real
            return 0.
        endmethod
        
        method destroy takes nothing returns nothing
            call unitData.removeCallback(this)
            call deallocate()
        endmethod
        
        static method create takes unit whichUnit, integer priority returns thistype
        local thistype s
            if UnitFilter(whichUnit) then
                set s=allocate()
                set s.priority=priority
                set s.unitData=UnitData[whichUnit]
                call s.unitData.addCallback(s)
                return s
            debug else
            debug     call BJDebugMsg("PhysicalAttack: Can't create struct for "+GetUnitName(whichUnit)+"! Unit is filtered.")
            endif
            return 0
        endmethod
    endstruct
    
    private struct UnitData extends DamageModifier
        private List structs
        
        private static method createFilter takes unit u returns boolean
            return UnitFilter(u)
        endmethod
        
        private static method create takes unit u returns thistype
        local thistype s=allocate(u, PRIORITY)
            set s.structs=List.create()
            return s
        endmethod
        
        private method onDestroy takes nothing returns nothing
            call structs.destroy()
        endmethod
        
        private method onDamageDealt takes unit target, real damage returns real
        local Link sl
        local Link tl
        local Link b
        local integer buffid
        local real res=0
            if AttackBuffs!=0 then
                set b=AttackBuffs.first
                loop
                    exitwhen b==0
                    if GetUnitAbilityLevel(target, AttackBuff(b.data).abilityID)>0 then
                        set buffid=AttackBuff(b.data).abilityID
                        set sl=structs.first
                        if UnitFilter(target) then
                            set tl=UnitData[target].structs.first
                        else
                            set tl=0
                        endif
                        loop
                            exitwhen sl==0 and tl==0
                            if sl!=0 and (tl==0 or PhysicalAttack(sl.data).priority>=PhysicalAttack(tl.data).priority) then
                                set res=res+PhysicalAttack(sl.data).onAttack(target, damage+res, buffid)
                                set sl=sl.next
                            else
                                set res=res+PhysicalAttack(tl.data).onAttacked(this.me, damage+res, buffid)
                                set tl=tl.next
                            endif
                        endloop
                        call UnitRemoveAbility(target, buffid)
                        // return now
                        return res
                    endif
                    set b=b.next
                endloop
            endif
            return 0.
        endmethod
        
        method addCallback takes PhysicalAttack which returns nothing
        local Link l=structs.first
            loop
                exitwhen l==0 or PhysicalAttack(l.data).priority<which.priority or l.next==0
                set l=l.next
            endloop
            if l==0 then
                call Link.create(structs, which)
            elseif l.next==0 then
                call Link.createLast(structs, which)
            else
                call l.insertBefore(which)
            endif
        endmethod
        
        method removeCallback takes PhysicalAttack which returns nothing
            call structs.search(which).destroy()
        endmethod
        
        implement AutoCreate
        implement AutoDestroy
    endstruct
    
endlibrary
01-25-2011, 11:29 AM#2
deolrin
Yay, new submissions!
No idea what that does, I am no coder, but the fact someone still cares enough to submit stuff to this site is very nice. :P
01-25-2011, 05:55 PM#3
Nestharus
Just use Frost Breath buff as that works for everything. Registering new buffs is also actually a good idea o-o, but Frost Breath for general attacks. I can see why this couldn't catch attack ground, but I solved that problem in AttackIndexer by creating a dummy unit at the order point and making the w/e attack that instead.

This can be further modularized by making a simple DamageEvent sort of deal that can detect physical attacks and another thing that can index those physical attacks (indexing makes onAttack (onIndex), onResolve, and onDeindex useful as that provides a unique index).

You can also go farther with this and detect JASS and spell attacks (done in AdvDamageEvent at THW).

One thing I've learned is that buffs aren't placed on a unit until after damage is dealt. Just making sure you accounted for that with a timer ;P. I'm sure you tested it and made sure it's all working, but just double checking here ;D.

Guess wc3c needs the systems being done at THW too : D.

AdvDamageEvent


And also, you should use a likely variable or w/e to cut down on iteration. For example, the last buff placed would be the most likely next buff and its next would be the second most likely. The closest buff ids to it would actually be the most likely buffs (ones that haven't resolved yet).


AdvDamageEvent at THW support damage modifiers too btw, so that's two that you know of now that does it. I think Damage by j4l supports it as well at TH.

3 forums with 3 different standards of coding and 3 different sets of systems that are all incompatible with each other, haha : D.
01-25-2011, 08:18 PM#4
Deaod
Quote:
This can be further modularized
No, it cant.
Quote:
I solved that problem in AttackIndexer by creating a dummy unit at the order point and making the w/e attack that instead.
Bad solution, unless you completely prevented users from being able to select that dummy unit.
Quote:
buffs aren't placed on a unit until after damage is dealt.
Then explain to me why all attack modifiers ive tested place their buff before the damage event is fired (the native one)?
Quote:
And also, you should use a likely variable or w/e to cut down on iteration. For example, the last buff placed would be the most likely next buff and its next would be the second most likely. The closest buff ids to it would actually be the most likely buffs (ones that haven't resolved yet).
Wat.
Quote:
AdvDamageEvent at THW support damage modifiers too btw, so that's two that you know of now that does it. I think Damage by j4l supports it as well at TH.
Either youre not talking about DamageModifiers by Anitarf, or youre wrong. Pick one.
01-26-2011, 12:03 AM#5
Nestharus
Quote:
Bad solution, unless you completely prevented users from being able to select that dummy unit.

Only efficient solution I could think of, and they wouldn't be able to select the dummy unit : ).

Quote:
Then explain to me why all attack modifiers ive tested place their buff before the damage event is fired (the native one)?

When I was working on AdvDamageEvent, the level of the buff returned 0 every time. When I added in a timer, it returned the expected 1 ; P.

Quote:
Either youre not talking about DamageModifiers by Anitarf, or youre wrong. Pick one.

Was just thinking of damage modification in general ;D. So yea, wasn't talked about DamageModifiers ;p.
01-26-2011, 08:48 AM#6
Deaod
After some test and writing of code, im back.

Quote:
Just use Frost Breath buff as that works for everything.
Shouldve thought of this earlier, but using an attack modifier that slows by default is a really bad idea. Too many attacks using that attack modifier on a unit and that unit will be frozen. Go and ask Karawasa for confirmation.

Quote:
I solved that problem in AttackIndexer by creating a dummy unit at the order point and making the w/e attack that instead.
Did that for this library as well. Not sure what its for, but what the heck.
I'm still not convinced i can prevent users from killing the dummy unit with spells (if they kill it with physical attacks, their fault).
01-26-2011, 04:17 PM#7
Nestharus
->Shouldve thought of this earlier, but using an attack modifier that slows by default is a really bad idea. Too many attacks using that attack modifier on a unit and that unit will be frozen. Go and ask Karawasa for confirmation.

But you remove the buff immediately no? It'd only have it on for a split instant, so it shouldn't even be noticeable.

Also, you should be able to tell JASS and spell attacks too with a small addition ; p. I did it using hooks. If the thing had a buff, it was physical. If the thing was hooked, it was JASS. If none of the above, it was a spell ;P. Worked brilliantly.
01-26-2011, 04:38 PM#8
Deaod
Quote:
But you remove the buff immediately no? It'd only have it on for a split instant, so it shouldn't even be noticeable.
This doesnt solve the problem. Really, using another attack modifier like Corruption is the only thing that solved this.
01-26-2011, 05:10 PM#9
Nestharus
And Corruption works on splash and etc like Frost Breath?
01-26-2011, 05:21 PM#10
Deaod
No. But what difference does it make? You can trigger splash anyway.
01-26-2011, 09:39 PM#11
Nestharus
->No. But what difference does it make? You can trigger splash anyway.
A major difference in overhead >: p, but w/e, do what you think is best ;D. Only abilities that seem to apply splash properly are the frost abilities, and Frozen Breath appeared to be the best.
01-26-2011, 10:38 PM#12
Anitarf
Personally, I prefer to trigger all my spell damage using xedamage, then I can easily identify any non-triggered damage as attack damage. However, I can see the benefits of a buff-based attack detection method as well. There is no reason that an attack detecting library would have to only support one of these methods, though. With some optional requirements, a calibration constant and some static if magic, it could easily support both.

For the buff detection method, it would be useful if the system automatically created a hidden buff-placing on-attack passive ability using the objectmerger and automatically added that ability to all units when they are indexed. This would save the users some effort when installing the library since most users will need it for generic attack damage detection where every unit needs some form of buff-placing ability.

I'm wondering if there is a way to resolve conflicts with triggered on-attack abilities that don't use this system, but instead do their own buff detection and removal. If the ability's buff is not added to the system, then attacks made with that ability active will not register as physical attacks. If the buff is added, then it will be removed and the custom ability's own code won't work any more. It should not be too difficult to modify any such abilities to use this system instead of doing the detection themselves, so this isn't such a big problem. It certainly isn't worth the overhead of only removing the buffs after a 0.0 timer expires to allow other code to detect them.

However, speaking of generic attack damage detection, there should be a way to have non-unit-specific ways to respond to attack damage events. I never aded this functionality to DamageModifiers because I already had DamageEvent, but DamageEvent didn't support priorities or modifying damage, so in hindsight I could have used non-unit-specific damage modifiers. However, I now remembered that AutoIndex allows us to easily automate the creation and destruction of unit-specific structs for all units with the AutoCreate and AutoDestroy modules, so this is kind of a moot point as well.

I guess this leaves me with only two points: one, the resource should support various methods for identifying attack damage. First, the attack buff detection. Second, damage native and BJ hooks and assuming all non-attack damage is dealt through triggers. Third, using xedamage.isInUse() and assuming all non-attack damage is dealt with xedamage. The second point is that for the buff-based attack damage detection method, an invisible base buff-applying ability should be automatically generated and given to all units to save the users the trouble of doing that.

By the way, I think PhysicalAttack is a bit awkward as a name. This is meant to detect unit attacks, whether they be physical or magical. Considering what this library expands upon, a name like AttackDamageModifier or something shorter like AttackDamage feels more appropriate.
03-14-2011, 04:46 AM#13
Nestharus
Damage isn't applied until after the damage event... just tested it : |. First the damage event runs, then the damage is applied.

Regular Damage-
1 damage event fires, buff applied before damage event

Missile Splash Damage
2 damage events fire for target unit
first damage event runs w/ dmg and w/o buff
second damage event runs w/ 0 damage and buff

2 damage event fires for rest of units
damage event runs w/ dmg and w/o buff
damage event runs w/ 0 damage and w/0 buff (wc3 bug??)

buff applied on rest of units on next period

Artillery Splash Damage
1 damage event fires
Buff applied to all units on next period


So this raises a few problems... you have to start a 0 second timer to catch all of the buffs for all possible attacks, but then you run into a problem with artillery attacks. Artillery attacks explode units, and to catch the buffs, you have to save the unit from dying before you know whether the unit will live or not. This means that artillery attacks will no longer be able to explode units as there is no good way to detect whether an attack was an artillery attack or not. Some work was done with attackground and ordering a unit to attack a tree, but that requires the unit's orders be interrupted, so they're not good solutions.

Now we run into another problem... if a unit has a non splash detection ability, the splash detection ability won't run.


edit
Solved in AdvDamageEvent >: ).

There is also an ability problem. You have to use 2 abilities to get it working, and the two that appear to work are Frost Breath and Item Frost Damage Bonus. I have tried many combos and many abilities =).


edit
Here is the solution, cut straight out of the latest AdvDamageEvent-

Collapse JASS:
        if (UnitAlive(GetUnitById(targetId))) then
            if (GetUnitAbilityLevel(GetUnitById(targetId), DUMMY_BUFF) > 0) then
                //was a physical attack
                if (ec[eventCount]) then
                    //initial splash (runs through w/ only splash abils on splash attack)
                    loop
                        set eventType[i] = PHYSICAL
                        set es[i] = 0
                        set i = i - 1
                        exitwhen not ec[i] or es[i] != sourceId
                        set ec[i] = false
                    endloop
                else
                    //not splash (doesn't run w/ splash abil no non splash attack)
                    set eventCount = eventCount + 1
                    set eventType[eventCount] = PHYSICAL
                    set update = true
                endif
            elseif (ls == sourceId and eventType[eventCount] == PHYSICAL) then
                //this will only run with non-splash abil and splash abil on splash attack
                //first the 0 dmg runs, then the actual damage runs, alternates
                if (ec[eventCount]) then
                    set ec[eventCount] = false
                else
                    set eventCount = eventCount + 1
                    set eventType[eventCount] = PHYSICAL
                    set ec[eventCount] = true
                    set update = true
                endif
            elseif (hooked[sourceId] > 0) then
                //JASS attack
                set hooked[sourceId] = hooked[sourceId] - 1
                set eventCount = eventCount + 1
                set eventType[eventCount] = JASS
                set update = true
            elseif (overType != 0) then
                //known attack
                set eventType[eventCount] = overType
                set overType = 0
                set update = true
            else
                //unknown attack
                //will initially run for all splash attacks
                //artillery attacks won't be known until after timer
                set eventCount = eventCount + 1
                set eventType[eventCount] = 0
                set update = true
                set ec[eventCount] = true
                set es[eventCount] = sourceId
            endif
            //for chaining attacks together, last source = current source
            set ls = sourceId
            //if update (didn't skip), then add to timer stack
            if (update) then
                set eventSourceId[eventCount] = sourceId
                set eventTargetId[eventCount] = targetId
                set eventDamage[eventCount] = amount
            
                //if haven't saved the unit, add life abil to save it
                if (saved[targetId] == 0) then
                    set life = GetWidgetLife(GetUnitById(targetId))
                    call UnitAddAbility(GetUnitById(targetId), ADV_DAMAGE_EVENT_SAVE_UNIT_ABILITY)
                    call SetWidgetLife(GetUnitById(targetId), life+500000)
                endif
                //always increase the save count so life ability is removed at right time
                set saved[targetId] = saved[targetId] + 1
                
                //start the timer to actually handle (only needed because of artillery attacks)
                //artillery attacks are *never* known until after timer
                call TimerStart(eventTimer, 0, false, function thistype.handleEvent)
            endif
        endif

All units have Item Attack Frost Bonus and Frost Breath added to them at index.

Here is the final step inside of the timer method for retrieving type
Collapse JASS:
                if (type == 0) then
                    if (GetUnitAbilityLevel(target, DUMMY_BUFF) > 0) then
                        set type = PHYSICAL
                        set explode = true
                    else
                        set type = SPELL
                        set explode = false
                    endif
                else
                    set explode = false
                endif
                if (type == PHYSICAL) then
                    call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "PHYSICAL")
                    call UnitRemoveAbility(target, DUMMY_BUFF)
                endif
                call ANY.fire()
                call Event(type).fire()

And killing
Collapse JASS:
                set life = GetWidgetLife(target)-500000
                set saved[targetId] = saved[targetId] - 1
                if (life < .405) then
                    call SetUnitExploded(target, explode)
                    set saved[targetId] = 0
                    call SetWidgetLife(target, .5)
                    call disable()
                    call UnitDamageTarget(GetUnitById(sourceId), GetUnitById(targetId), 100, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
                    call enable()
                elseif (saved[targetId] == 0) then
                    call UnitRemoveAbility(GetUnitById(targetId), ADV_DAMAGE_EVENT_SAVE_UNIT_ABILITY)
                    call SetWidgetLife(target, life)
                endif



Have fun =).

Giving this up so that you guys can get the wc3c variant of PhysicalAttack approved ^)^. THW will have a version, wc3c will have a version, and TH will not =). With these, j4l's Damage at TH is kind of obsolete =D. Eventually, they'll catch up ;P.

And you are welcome for the complete solution.

You can also expand slightly to infer between physical, spell, and JASS attacks by hooking all of the BJs and natives. Also, with my solution, you can tell between artillery, splash, and non-splash attacks, lol. There is no accurate way to detect multishot : \


It took me an incredibly long time to come up with this solution... a lot of thinking. While I was testing different abilities to see if any would work with both splash and non splash, I came upon the solution when seeing how the buffs were added and what order the damage was done in ;p.
03-16-2011, 06:43 AM#14
Ignitedstar
Quote:
A library to detect physical attacks on units using buff-placing attack modifiers.

Can you elaborate more? I'm still having trouble understanding why I want something that only detects buff-placers. So... it only works with units that do a normal attack? Normal attack as in, the attack command?
03-17-2011, 03:14 AM#15
Nestharus
Quote:
Can you elaborate more? I'm still having trouble understanding why I want something that only detects buff-placers. So... it only works with units that do a normal attack? Normal attack as in, the attack command?

It actually doesn't work at the moment, but that's the general gist yea.


See my post for information on why it doesn't work ; P


I even posted the solution for how to fix ;O