• 🏆 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!

Dynamic Indexing - Submission

Status
Not open for further replies.
Good day, reader. This mission is about dynamic indexing.

As we all know, dynamic indexing is one method used to achieve MUI. This snippet will help you understand how it works.

As our demonstration, we will have an active ability that damages a targeted enemy unit.
Per demonstration, the rawcode of the passive ability is 'A000'

Unnamed tab 1

Changelog


JASS:
scope HealUnits initializer Init

    globals
        // This acts as our control variable
        private integer index = 0

        // This will come into play later...
        private constant timer DAMAGE_TIMER = CreateTimer()

        // This is a unit array variable used for those who will get healed.
        private unit array target

        private real array ticks
    endglobals

    private function onTickHeal takes nothing returns nothing
        local integer i = 1
 
        if index == 0 then
            call PauseTimer(DAMAGE_TIMER)
            return
        endif

        loop
            exitwhen i > index
 
            if ticks[i] <= 0 or GetWidgetLife(target[i]) < 0.405 then
                // Unit must be dead, so we remove the unit from our array list by
                // jamming the values of the last index into our to-be-removed index...
                set target[i] = target[index]
                set ticks[i] = ticks[index]
                set caster[index] = null

                set index = index - 1
            else
                call SetWidgetLife(target[i], GetWidgetLife(target[i]) + 2.5)
         
                set ticks[i] = ticks[i] - 1/32.
                set i = i + 1
            endif

        endloop
    endfunction

    private function Cond takes nothing returns nothing
        if GetSpellAbilityId() == 'A000' then
            // We check if the index was 0 (index == 0). If it is, we start the timer.
            // To be sure, I just use (index <= 0)
            if index <= 0 then
                // set index = 0
                call TimerStart(DAMAGE_TIMER, 1/32., true, function onTickHeal)
            endif
 
            // We increment the index to allow for a new target.
            set index = index + 1

            set target[index] = GetSpellTargetUnit()
     
            set ticks[index] = 2/(1/32.)
        endif
    endfunction

    // This function will be called when the game initializes
    // Thank the initializer keyword for that.
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Filter(function Cond))
        set t = null
    endfunction

- v.1.0 - Creation of thread.
- v.1.1 - Changed the example from an AoE damage aura to a concentrated blast of damage dealt to a specific unit over 2.00 seconds.
- v.1.2 - Changed the example from damage to heal. Also removed the caster variable.
- v.1.2.1 - Added the Submission tag as it did not have one before...
 
Last edited:
Level 37
Joined
Jul 22, 2015
Messages
3,485
  • Your naming convention for globals should be LIKE_THIS
  • I personally don't like using natives inside globals block because of this quote from JASSHelper:
    ...you can't make them initialize with any function call or with a native (although it is possible to use a native, most natives tend to crash the thread when used in globals declaration).
  • Not sure why you are doing this:
    JASS:
    set caster[index + 1] = GetTriggerUnit()
    set index = index + 1
    It logically makes more sense to do this:
    JASS:
    set index = index + 1
    set caster[index] = GetTriggerUnit()
  • This should be inside the deindex block, not outside of the loop:
    JASS:
    if index == 0 then
        call PauseTimer(DAMAGE_TIMER)
        return
    endif
  • The return statement also feels redundant. The loop is going to end anyway since the index is 0
  • Not sure how I feel about this: if GetWidgetLife(caster[i]) <= 0.405 then. Given that you are using vJASS, UnitAlive() is a much better alternative.
  • enum_unit doesn't have to be a global.
 
Your naming convention for globals should be LIKE_THIS

I think those apply to constant variables.

I personally don't like using natives inside globals block because of this quote from JASSHelper:
...you can't make them initialize with any function call or with a native (although it is possible to use a native, most natives tend to crash the thread when used in globals declaration).

It's okay. I tested some of those natives, one of which is GetWorldBounds. That function tends to crash the game on start.
CreateTrigger(), CreateGroup() seem to work in the declaration of globals...

  • Not sure why you are doing this:
    JASS:
    set caster[index + 1] = GetTriggerUnit()
    set index = index + 1
    It logically makes more sense to do this:
    JASS:
    set index = index + 1
    set caster[index] = GetTriggerUnit()
  • This should be inside the deindex block, not outside of the loop:
    JASS:
    if index == 0 then
        call PauseTimer(DAMAGE_TIMER)
        return
    endif

I agree to those statements. I wrote that (the code) from scratch, so I did not delve into detail in optimizing it.


  • The return statement also feels redundant. The loop is going to end anyway since the index is 0
  • Not sure how I feel about this: if GetWidgetLife(caster[i]) <= 0.405 then. Given that you are using vJASS, UnitAlive() is a much better alternative.
  • enum_unit doesn't have to be a global.

I actually wrote the entire code without looking at the TESH Compiler.
Sorry for the previously broken tags, I wrote those in haste.
 
Last edited:
Huh?

Never mind my previous exclamation. I understood it at the second try.

For this example, it would have made more sense to use a linked list instead of dynamic indexing as the behavior of the code suggests that it behave as such. (It runs permanently, thus precluding any possibility of removal of any instance).

Anywho, I used dynamic indexing such that those who learn a certain passive ability will have a triggered aura that deals damage to enemy units around 900 AoE. However, I didn't think about the example and just went on and on, gradually writing it up until it was made apparent that it was not to be so.

So to put it in figures, this is the demonstration of the code:

Learn Ability ('A000')
Make new instance (Index learning unit if level of ability is 1) (Got lazy about this approach)
If timer is paused, activate timer

Timer ticks
Enumerate all units that are enemies
Deal damage to them

If unit is dead, deindex (Definitely not appropriate for this scenario).
 
Last edited:
I would have also suggested a doubly linked list for such task, but I wanted to understand your idea, too. But then, we probably think alike.

The main task is just to understand how dynamic indexing works, and to implement the tequinique in a senseful example. But as we both probably say, this example would fit for expliitly using a doubly linked list. You are't forced to write explainations and stand alone libraries, but just a small written and working example would be enough for the mission.
 
My example before was that of continuous checking of an instance which would not be expected to expire, thus defeating the purpose of dynamic indexing.

Why would it not be expected to expire? The timer will continuously run certain actions without ever having a condition for deindexing. What I mean by this is that no ticking variable exists which would otherwise provide a condition for deindexing.

In short, before:
no ticks, run forever, except when died...
when died, deindex...
(faulty, but was example only)

I have changed it so that it now damages a certain target per tick (by instance) and once the number of ticks reaches 0, the instance is deindexed.

ticks, timed expire, deindex on expiry or target death,
when target is death, deindex or
when timer is expires, deindex.
 
Last edited:
I don't understand the reasoning.

I understand at which Position the deindex occurs. But what if I would say that a doubly linked list makes more sense in your case; how do you defend your stance why you used dynamic indexing?

Again, I want to understand that you understand when to use dynamic indexing and when not, and not simply how to use it.
 
I actually prefer doubly linked lists for almost everything that I code, but in this mission, the goal is to demonstrate dynamic indexing and when to use it. A temporal scenario with no priorities would be the ideal case for dynamic indexing, while the doubly linked list is good for every scenario that dynamic indexing encounters.

In the sample code, the scenario is:


- A target is damaged per tick. Ticks will persist 64 times.

(This is a temporal scenario)

- Does order of the instances matter? No, because the interaction of a variable array with different indices are nonexistent.

(This is a temporal scenario without any priority)

Since the scenario is temporal and without any priority, and I have taken it that a temporal scenario with no priorities would be the ideal case for dynamic indexing, (ergo), the scenario that I have presented now would have dynamic indexing as a recommended implementation.
 
In case the caster is removed from game, or any other real case scenario occurs, with possible buff removal or, what ever,- a spell might end it's effect. Or just any scnerario that instanes do not end at same duration, might it only be dynamic durations.

When this should ever happen, then the order of the registered data inside arrays might mix up, and some units might deal and receive damage in a different order it initialy was registered.

Such cases make the system sensitive towards vulnerability, and so systems where a mixed order might have bad influence towards the in game result are not recommended to use dynamic indexing.
More instanes might influnece the same target even, and then things have potential to get even worse.

If I display messages to certain players, for example, then the order makes no differene, as I don't care if first Player(1), or first Player(2) receives his message at first, as long it happens in the same tick, because it has no effect on the gameplay. But when operations like kill/damage etc get mixed up, the it gets dangerous, and should be prevented.
Of course you can argue now that your code does not have included logics for it that backup dynamic duration, or other checks that might occur, but the point is that with this sensitive damage operations a system can have the potential to not fit dynamic indexing, and so it not a perfect example. Such operations are placed better in a linked list example.

I know, it might seem a bit nit picky, but that's the details what the mission is about, just to get sure one understood the principles.
 
Status
Not open for further replies.
Top