• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Attack Indexing

Status
Not open for further replies.
God forsaken my comprehensive skills:

API (current)
JASS:
library AttackIndexer requires AllocFast UnitDex DamageEvent
    globals
        private constant real ATTACK_DURATION = 90
        
        private constant integer MAX_ATTACKS = 64
    endglobals
    
    private module Init
        private static method onInit takes nothing returns nothing
            call init()
        endmethod
    endmodule
    
    private struct AttackQueue extends array
        implement AllocFast
        
        private boolean indexed
        
        private real time
        
        private thistype attacks
        private thistype front
        private thistype back
        
        private static thistype array next
        
        static method operator [] takes unit u returns thistype
            local thistype this = GetUnitId(u)
            if not indexed and this != 0 then
                set indexed = true
                set attacks = 0
            endif
            return this
        endmethod
        
        method push takes real gameTime returns thistype
            local thistype newNode = 0
            if indexed then
                /*
                *   Check if:
                *   - the oldest node has expired
                *   - the number of attack instances has reached the max attacks
                */
                if gameTime - front.time > ATTACK_DURATION or attacks >= MAX_ATTACKS then
                    /*
                    *   If true, use the front node as the new node
                    */
                    set newNode = front
                    set front = next[front]
                else
                    /* 
                    *   if not, allocate an attack
                    */
                    set newNode = allocate()
                    set attacks = attacks + 1
                endif
                /*
                *   if the queue has only one instance, we make sure that the new node
                *   also becomes the front node
                */
                if front == 0 then
                    set front = newNode
                endif
                /*
                *   push the node to the back
                */
                set next[back] = newNode
                set next[newNode] = 0
                set back = newNode
                /*
                *   set the current game time
                */
                set time = gameTime
            endif
            return newNode
        endmethod
        
        method pop takes nothing returns thistype
            local thistype node = 0
            if indexed and attacks > 0 then
                call front.deallocate()
                set node = front
                set front = next[front]
                set attacks = attacks - 1
            endif
            return node
        endmethod
        
        method has takes thistype node returns boolean
            local thistype n = front
            loop
                exitwhen 0 == n
                if n == node then
                    return true
                endif
                set n = next[n]
            endloop
            return false
        endmethod
        
        method empty takes nothing returns boolean
            return front == 0 or attacks == 0
        endmethod
        
        method destroy takes nothing returns nothing
            loop
                exitwhen 0 == front
                call front.deallocate()
                set time = 0
                set front = next[front]
            endloop
            set attacks = 0
        endmethod
    endstruct
    
    struct Attack extends array
        readonly static integer indexed
        readonly static integer deindexed
        
        private static trigger indexHandler = CreateTrigger()
        private static trigger deindexHandler = CreateTrigger()
        
        private static constant timer gameTime = CreateTimer()
        
        static method register takes code c, boolean onIndex returns nothing
            if onIndex then
                call TriggerAddCondition(indexHandler, Condition(c))
            else
                call TriggerAddCondition(deindexHandler, Condition(c))
            endif
        endmethod
        
        private static method fire takes boolean onIndex returns nothing
            if onIndex then
                call TriggerEvaluate(indexHandler)
            else
                call TriggerEvaluate(deindexHandler)
            endif
        endmethod
        
        
        static method isValid takes unit u, integer attackId returns boolean
            return AttackQueue[u].has(attackId)
        endmethod
        
        static method isEmpty takes unit u returns boolean
            return AttackQueue[u].empty()
        endmethod
        
        static method pop takes unit u returns integer
            return AttackQueue[u].pop()
        endmethod
        
        private static method attack takes nothing returns boolean
            local unit u = GetAttacker()
            if GetUnitId(u) != 0 then
                set indexed = AttackQueue[u].push(TimerGetElapsed(gameTime))
                call fire(true)
            endif
            return false
        endmethod
        
        private static method damaged takes nothing returns boolean
            if PDDS.damageType == PHYSICAL then
                if AttackQueue[PDDS.source].empty() then
                    set PDDS.amount = 0
                else
                    set deindexed = AttackQueue[PDDS.source].pop()
                    call fire(false)
                endif
            endif
            return false
        endmethod
        
        private static method init takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerAddCondition(t, Condition(function thistype.attack))
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ATTACKED)
            
            call AddDamageHandler(function thistype.damaged)
            
            call TimerStart(gameTime, 31557600, false, null)
        endmethod
        
        implement Init
    endstruct
endlibrary

Here is a demo that attaches a lifesteal amount to an indexed attack if a unit has the item 'I000'
JASS:
library LifeSteal requires AttackIndexer
    private struct T extends array
        private static real array factor
        static method attacked takes nothing returns boolean
            if UnitHasItemOfTypeBJ(GetAttacker(), 'I000') then
                set factor[Attack.indexed] = GetRandomReal(0, 1)
                call BJDebugMsg("Attack indexed! :" + I2S(Attack.indexed))
                call BJDebugMsg("Attached lifesteal factor: " + R2S(factor[Attack.indexed]))
            endif
            return false
        endmethod
        
        static method damaged takes nothing returns boolean
            if PDDS.amount != 0 then
                call SetWidgetLife(PDDS.source, GetWidgetLife(PDDS.source) + PDDS.amount*factor[Attack.deindexed])
                call BJDebugMsg("Attack deindexed! :" + I2S(Attack.deindexed))
                call BJDebugMsg("Attached lifesteal factor: " + R2S(factor[Attack.deindexed]))
            endif
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            call Attack.register(function thistype.attacked, true)
            call Attack.register(function thistype.damaged, false)
        endmethod
    endstruct
endlibrary

The result from the demo code.
attachment.php



You might ask "what's the difference with this and PDD?" or "what's so special about this?

When writing a LifeSteal library and you use PDD, it will allow you to lifesteal from physical attacks, right? now imagine, your lifesteal system requires the unit to have an specific ability to lifeteal, you release an attack that has a missile speed of 200 but you don't have that ability then suddenly, in mid air, receives the ability, this will cause the attack you launched before to lifesteal. That is broken.

Meanwhile if you are going to write a LifeSteal library, you are going to use AttackIndexer. If we still have the scenario above but with the use of Attack Indexer + PDD, the released attack will no longer lifesteal damage if the unit doesn't have the item, regardless if you receive the item after the attack has been launched.
 

Attachments

  • screenie.png
    screenie.png
    1.7 MB · Views: 160
Last edited:
Might want to explain this better for scrubs like me who doesn't get it.

My initial guess was that it would be used to detect an attack somehow.

This is used for indexing attacks so that arggghh I want Nestharus here.


you should use an external lib for the list.
Can't. The List is like a connected Queues(which makes it a list obviously, tho that is not the point). I don't want to mess up with the list modules if I have to use one :V
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
You shouldn't have to iterate over the entire queue. The oldest attack is at the front of the queue. If the attack is not expired, no need to hit any of the other nodes.

You shouldn't need to use a .03125 seconds timer. Instead, the timer should just be the remaining time of the node at the front of the queue ; ). This means that it will expire 1 time for every N nodes instead of X times for every N nodes.

Might want to explain this better for scrubs like me who doesn't get it.

Attack indexing is the process of assigning a unique index to a unit attack. This allows you to safely attach data to a Warcraft 3 projectile. This means that you won't need to use a projectile system if all you want to do is attach data : ).


Any attack modifications can be done on the attack and any defense modifications can be done on damage.
 
You shouldn't have to iterate over the entire queue. The oldest attack is at the front of the queue. If the attack is not expired, no need to hit any of the other nodes.
But how will I remove expired attacks other than the tail?

You shouldn't need to use a .03125 seconds timer. Instead, the timer should just be the remaining time of the node at the front of the queue ; ). This means that it will expire 1 time for every N nodes instead of X times for every N nodes.
Yeah. My fault at 32 iterations per second. But how about the other old nodes?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
30 seconds ago
27 seconds ago
18 seconds ago
10 seconds ago
7 seconds ago
3 seconds ago
2 seconds ago


expiration = 15 seconds


30 seconds ago = first node
2 seconds ago = last node

When do you stop caring about the rest of the list? : )




Start timer that will expire in 30 seconds - clean node
Start timer that will expire in 3 seconds - clean node (30 - 27)
Start timer that will expire in 9 seconds - clean node (27 - 18)
 
Because some guy insisted >.<

I think this will be back to business.

30 seconds ago
27 seconds ago
18 seconds ago
10 seconds ago
7 seconds ago
3 seconds ago
2 seconds ago


expiration = 15 seconds


30 seconds ago = first node
2 seconds ago = last node

When do you stop caring about the rest of the list? : )




Start timer that will expire in 30 seconds - clean node
Start timer that will expire in 3 seconds - clean node (30 - 27)
Start timer that will expire in 9 seconds - clean node (27 - 18)

I'm still confused to this. I get that each list will only have one timer, but I don't get how will I start the timer.

[edit]
Rewriting the lib.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
So first, there should only be 1 list for the entire map.


You can create a timer that acts as a timestamp and put in a creation time for each node. From here, you can calculate how long a node has before it is going to expire.


Because these are all going to be 1-shot timers, you can easily use 1 timer for this 1 list without any performance impact.
 
My idea was whenever an attack is attempted, it is saved as a node, where a node holds the time when it was attempted (based on game time)


I updated the thread, what do you think of the contents? Pretty much it is incomplete tho.

[edit]
Updated.

Units now have an attack limit of 64 instances.

Whenever a unit tries to attack, it checks first if there is an old node or if the number of attacks has been reached, if true it moves the last node from the attack queue to the first node of the queue

[edit]
Updated.

Idk if this is what you wanted.
 
Last edited:
Updated.

Added indexing and deindexing events.
Indexing event no longer fire from units who are not indexed by the UnitIndexer

Physical damage will now be modified to 0 damage if the unit no longer have indexed attacks.

Added demo on how to attach data to attacks and how to use attached data.

If you are not going to reply, Nestharus, for the next 12 hours, I will be going to submit this in the Spell or JASS section
 
Last edited:
Status
Not open for further replies.
Top