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

[JASS] Using Timer to Freeze/Unfreeze Multiple Units

Status
Not open for further replies.
Level 3
Joined
Jul 15, 2009
Messages
40
Hey there, all.

I am trying to learn JASS, and so I'm working on a spell to get some practice. However, I am having trouble figuring out exactly what I need to do to complete it.

The spell should work as follows:
Flash-Freeze

Whenever the caster is attacked, there is a chance that the attacking unit will be frozen instantly for several seconds. If the attacking unit is frozen, there will be a chance that any enemy units next to it will also become frozen, possibly triggering other units next to them to become frozen. This process continues until a unit does not freeze.

Level 1: 10% chance to freeze attacker for 3 seconds; 5% chance to freeze subsequent units for 3 seconds.

Level 2: 15% chance to freeze attacker for 5 seconds; 10% chance to freeze subsequent units for 5 seconds.

Level 3: 20% chance to freeze attacker for 7 seconds; 15% chance to freeze subsequent units for 7 seconds.

Here is the code:
JASS:
function Flash_Freeze_Condition takes nothing returns boolean
    return (GetUnitTypeId(GetTriggerUnit()) == 'H000') and (GetUnitAbilityLevel(GetTriggerUnit(),'A000')) > 0
endfunction                                                                                     

function UnfreezeAttacker takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit frozen_unit = GetHandleUnit(t,"frozen_unit")
    local effect sfx = GetHandleEffect(t,"sfx")
    call UnitRemoveAbility(frozen_unit, 'A001')
    call DestroyEffect(sfx)
    call PauseUnit(frozen_unit, false)
    call SetUnitTimeScalePercent(frozen_unit,100)
    call DestroyTimer(t)
    set frozen_unit = null
    set sfx = null
    set t = null
endfunction

function UnfreezeSub takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local unit frozen_unit = GetHandleUnit(t,"frozen_unit2")
    local effect sfx = GetHandleEffect(t,"sfx2")
    call UnitRemoveAbility(frozen_unit, 'A001')
    call DestroyEffect(sfx)
    call PauseUnit(frozen_unit, false)
    call SetUnitTimeScalePercent(frozen_unit,100)
    call DestroyTimer(t)
    set frozen_unit = null
    set sfx = null
    set t = null
endfunction


function FilterUnit takes unit source, unit temp returns boolean
    if (IsUnitAlly(temp, GetOwningPlayer(source))) and (GetUnitAbilityLevel(temp, 'A001') == 0) and not (IsUnitType(temp,UNIT_TYPE_STRUCTURE)) then
        return true
    else 
        return false
    endif
endfunction


function FreezeAdjacent takes unit source returns nothing
    local location source_loc = GetUnitLoc(source)
    local group enemies = CreateGroup()
    local unit temp
    local effect sfx
    local timer t = CreateTimer()
    call GroupEnumUnitsInRangeOfLoc(enemies, source_loc, 100.0, null)
    loop
        set temp = FirstOfGroup(enemies)
        exitwhen temp == null
        if (FilterUnit(source,temp) == true) then
            if GetRandomInt(1,100) <= 5 * (GetUnitAbilityLevel(GetTriggerUnit(),'A000') then
                set sfx = AddSpecialEffectTarget("Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl",temp,"origin")
                call UnitAddAbility(temp, 'A001')
                call PauseUnit(temp, true)
                call SetUnitTimeScalePercent(temp, 0)
                call SetHandleHandle(t,"frozen_unit2",temp)
                call SetHandleHandle(t,"sfx2",sfx)
                call TimerStart(t,(1. + 2 * (GetUnitAbilityLevel(GetTriggerUnit(), 'A000'))), false, function UnfreezeSub)
                call FreezeAdjacent(temp)
            endif
        endif
        call GroupRemoveUnit(enemies, temp)
    endloop
    call RemoveLocation(source_loc)
    call DestroyGroup(enemies)
    set temp = null
    set source_loc = null
    set enemies = null
endfunction


function Flash_Freeze_Actions takes nothing returns nothing
    local unit attacker = GetAttacker()
    local effect sfx
    local timer t = CreateTimer()
    
    if GetRandomInt(1,100) <= (10 + 5*(GetUnitAbilityLevel(GetTriggerUnit(),'A000') - 1)) then
        set sfx = AddSpecialEffectTarget("Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl",attacker,"origin")
        call UnitAddAbility(attacker, 'A001')
        call PauseUnit(attacker, true)
        call SetUnitTimeScalePercent(attacker,0)
        call SetHandleHandle(t,"frozen_unit",attacker)
        call SetHandleHandle(t,"sfx",sfx)
        call TimerStart(t,(1. + 2 * (GetUnitAbilityLevel(GetTriggerUnit(),'A000'))), false, function UnfreezeAttacker)
        call FreezeAdjacent(attacker)
    endif
    set attacker = null
endfunction

                                                                
function InitTrig_Flash_Freeze takes nothing returns nothing
 set gg_trg_Flash_Freeze = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Flash_Freeze, EVENT_PLAYER_UNIT_ATTACKED)
    call TriggerAddCondition(gg_trg_Flash_Freeze, Condition(function Flash_Freeze_Condition))
    call TriggerAddAction(gg_trg_Flash_Freeze, function Flash_Freeze_Actions)
endfunction
(Please note that I left out the references for the Handle Vars functions.)

The easy thing to do was have the attacker become frozen and then unfreeze. The hard part is making it so that both the attacker and the subsequent units freeze and then unfreeze after the appropriate amount of time.

The problem I am having right now is that some of the units do not unfreeze. I believe this problem is due to the fact that the data I'm storing (the special effect and the unit) are not always unique.


Help I Need
_______________________________________________________
  • First, I'd like to fix the problem with the units not unfreezing. Should I use a queue structure to keep track of units that are frozen? Are there other methods of doing this?
  • I'd like to make this spell MUI, but I'm not sure what would need changing. The majority of the spell is instant, and only the timers controlling unfreezing will linger. If I correctly create a structure to make the units unfreeze properly, would that, essentially, render the spell MUI?
  • Are there any leaks that I'm failing to catch?
  • Any other constructive criticism on the spell's functionality?


As I said, this will be my first JASS spell, so be gentle.
 
Last edited:
Level 2
Joined
Jul 17, 2009
Messages
27
Hey there.

Thankyou for your in-depth explanation regarding the freezing problems.


  • The reason some units are not freezing is because you don't have a unique way of referring to the frozen units. Every time you freeze an adjacent unit, you're overwriting the data you need to unfreeze the previous frozen adjacent unit. When the timer expires, it will only freeze the last frozen adjacent unit.

  • The spell is practically MUI. There will be problems if the same unit is frozen multiple times, but once that is fixed (applicable to the current version of the spell) you should be fine.

  • GroupEnumUnits causes a script leak when it is passed a null value, IIRC.


Quick question - what ability does 'A001' refer to? Since you are pausing effected units, 'A001' might not be needed (depending on functionality).


Examing your code:

Looking at your Unfreeze functions, logically and functionally they're almost identical. Ideally, you should be able to call one function or action when you have to unfreeze a unit, since all units are frozen in similar manner.

Furthermore, you can probably unify the freezing process into a single function, as well. This again, has a nice logical flow. There are two potential issues - freeze chance, and freeze duration. The freeze chance should be calculated before calling any function to freeze a unit - duration currently is the same for both direct and adjacent freezing, but you can pass duration to the freeze function in case you change it in future.

Finally, you're creating a timer for each unit that is frozen. This is fine for the purposes of this spell, but I'd recommend looking at a timer system that recycles timers if you would like to code further timer-intensive spells.


Problems:

You need a way to keep track of and uniquely identify the handles of each effect created and each unit frozen.

You need a method to prevent new freezes from interfering with frozen units.


Keeping track of frozen units.

As you guessed, the core problem is keeping track of frozen units. There are several ways to handle this.

You could use arrays with a single, low-duration timer. It would iterate through every frozen unit, prolonging or removing the freeze effect when needed. This timer would be paused when no units are frozen.

You could do something similar with handlevars, and attach an integer to each timer called when a unit is frozen. The integer references an index that is used on the effect and unit arrays, and recycled when needed. Structs make this process easier, but require vJASS (vJASS is a Good Thing, though).

You could also just attach the unit and effect handles to the timer, loading them when the timer expires. You only need to deal with two handles, so this shouldn't be a problem. I have no experience with handlevars, myself, though.

You could use a (hash)table. 1.24 supplies an easy way to do this, you'd use the GetHandleId of the timer as the parent key, and just use integers as the child keys (0 and 1, for Unit and Spell Effect).


Dealing with frozen units.


Finally, you need a way to handle the freezing of frozen units. A unit that is frozen, and is then refrozen, needs to either ignore the refreezing or have the freeze duration extended. This is easiest with the low-duration timer, because you can reset the iteration count for each individual unit (the thing that controls duration) when it is frozen for a second (or third, fourth, etc...) time.

You could do something similar with the index recycling method, or use handlevars to attach the freeze timer to a unit's handle. When a unit is frozen, you see of a timer is attached and, if so, pause and delete that timer (after dealing with anything attached to it first, of course).

You could do almost the same thing with the hastable, too, or use a global stack to check if a unit has a timer associated with it. Basically, you subtract the unit's GetHandleId from the stack value (some large integer) in order to create a unique reference.
 
Level 3
Joined
Jul 15, 2009
Messages
40
Sophismata, thank you for your excellent response.

I suppose I didn't do very well in explaining what the abilities were. 'A000' refers to the ability carried by the unit being attacked, i.e. the Flash-Freeze ability. When another unit attacks the first, the ability 'A001' is added to it. (This ability is a remnant from an earlier point, before I decided to make the spell completely triggered. It uses the Immolation effect, but only targets itself; this is so units will take damage over time while under this effect. I didn't mention it because it is handled separately.)

Essentially, I can keep track of units that are already frozen by checking whether or not they have that ability.


I have the two separate Unfreeze functions for testing purposes. As you stated, I don't see the need in having two once I get things straightened out, either.


Forgive me for asking, but could you provide more information on using the low-duration timers? This is my first time working with timers, so I'm not very sure of how to use them properly. If you could explain how that process works, I'd appreciate it.



Thanks again for your help.
 
Level 2
Joined
Jul 17, 2009
Messages
27
Excellent. That's the easiest method for me, so I'll run you through it.

Give me a moment.

(Ah, didn't see the filter you used with 'A001'. Clever.)

Okay, this requires some basic vJASS, but I assume you're using Warcraft Newgen in any case, so vJASS shouldn't be a problem.

We're going to use the global decleration feature of vJASS to declare new globals, and we'll be using scopes to define our trigger's privacy settings. This allows us to simplify names, which makes our globals easier to read.

If you can't use vJASS, this isn't a problem. You can declare global variables in the trigger editor. When you reference them in jass, you must append 'udg_' (sans quotes) to the variable name.


The approach we are using is fairly simple. We will create a one timer for the trigger. This timer has a low duration - this can be anything from 0.01 and up, you must balance between accuracy and performance. We will use constant variables (constant means that we won't change them at runtime) to make it simple to change the timer duration without mucking up our code.

Now, every time that timer expires, it will run through every frozen unit on the map. If the duration of the freeze effect is over, it will unfreeze the unit. Otherwise, it will increase a counter for the unit - this counter tells us how many times the timer has run for that unit, and therefore how long the unit has been frozen. With a 0.01 timer, for example, once we've run it 700 times on a unit, that unit has been frozen for 7 seconds.



JASS:
scope FlashFreeze

    globals
        private timer ffTimer
        private integer ffTotal = 0  // This holds the number of units we have frozen.
        private effect array ffEffect[8190]  // This holds the effect we need to destroy later.
        private unit array ffUnit[8190]  // This holds the unit we have frozen.
        private integer array ffDuration[8190]  // This holds the number of times we've run the trigger for the above unit and effect.
        private integer array ffLevel // This holds the level of the 'A000' ability cast.

The 'ff' stands for Flash Freeze. That's just to avoid confusion between the type 'effect' and the variable 'Effect', and 'unit'/'Unit'.



JASS:
   // While the above code deals with the spell, and is all we actually need,
   // it would be nice to be able to mess with duration and timer settings
   // easily. We will use constants for this.
        private constant real TIMER_PERIOD = 0.1  // What we will set our timer to.
        private constant integer DURATION_FACTOR = 1 / TIMER_PERIOD  // What me must multiply the duration by to get the number timer iterations required by the spell duration.
    endglobals


Next, we'll unify the Unfreeze functions.


JASS:
private function Unfreeze takes unit u, effect e returns boolean
    if u != null then  // We check the unit exists.
        call DestroyEffect(e)
        call UnitRemoveAbility(u, 'A001')
        call PauseUnit(u, false)
        call SetUnitTimeScalePercent(u,100)
        return true
    endif
    debug call BJDebugMsg("Tried to unfreeze a non-existant unit.")
    return false
endfunction


FilterUnit can remain as-is - we just need to add 'private' if using vJASS.


JASS:
private function FilterUnit takes unit source, unit temp returns boolean
    if (IsUnitAlly(temp, GetOwningPlayer(source))) and (GetUnitAbilityLevel(temp, 'A001') == 0) and not (IsUnitType(temp,UNIT_TYPE_STRUCTURE)) then
        return true
    else
        return false
    endif
endfunction


Okay, we're going to actually start using the arrays now. The basic idea behind the system is this - every unit and effect and number we use is stored in a global array. The number of arrays is also stored. Every time a unit is frozen, the total number of arrays in use (which is how we are indexing this) is increased, and the unit and associated effect and integers is added to each array and given the index we just used. When an effect expires, we lower the total number of arrays in use and adjust the arrays to compensate. This part can be confusing, so here's an example:

We are storing units in an array. We have a unit array called UNITARRAY, and a count variable called UNITCOUNT.

When the game starts, nothing has been stored. UNITCOUNT is 0. During the course of play, we add 5 units to the array:

1st unit: UNITCOUNT is 0, so we add the unit to UNITARRAY[UNITCOUNT], and increase UNITCOUNT. UNITCOUNT is now 1. The unit has been stored in UNITARRAY[0].

2nd unit: UNITCOUNT is 1, so we add the unit to UNITARRAY[UNITCOUNT], and increase UNITCOUNT. UNITCOUNT is now 2. The unit has been stored in UNITARRAY[1].

...

5th unit: UNITCOUNT is 4 (there are 4 units in the array before we add the fifth), so we add the unit to UNITARRAY[UNITCOUNT] and increase UNITCOUNT. UNITCOUNT is now 5.


Let's remove the 3rd unit from the array:

We decrease UNITCOUNT to 4. We set UNITARRAY[2] (the third unit) to UNITARRAY[UNITCOUNT] (the last unit). Now, all existing units are held in the array - the order doesn't matter. We can leave UNITARRAY[5] as-is, or null it.


So, here we go:


JASS:
private function TimerActions takes nothing returns nothing
    local integer iterator = 0

    loop
        exitwhen iterator == ffTotal
        if ffDuration[iterator] == (1*DURATION_FACTOR + 2*ffLevel[iterator]*DURATION_FACTOR) then // Checking the duration here.
            call Unfreeze(ffUnit[iterator], ffEffect[iterator])
            set ffTotal = ffTotal - 1
            set ffEffect[iterator] = ffEffect[ffTotal]
            set ffUnit[iterator] = ffUnit[ffTotal]
            set ffLevel[iterator] = ffLevel[ffTotal]
            set ffDuration[iterator] = ffDuration[ffTotal]
            set ffEffect[ffTotal] = null
            set ffUnit[ffTotal] = null
            set ffDuration[iterator] = ffDuration - 1
            set iterator = iterator - 1
            // We need to run this loop again for the current (iterator) unit, because
            // it is now a new unit. To prevent us increasing the duration and iterator
            // twice, we decrement them here.
        endif
        set ffDuration[iterator] = ffDuration[iterator] + 1
        set iterator = iterator + 1
    endloop
    if ffTotal == 0 then
        // pause the timer here (call PauseTimer(ffTimer), IIRC)
    endif
endfunction


I'm running short on time, so, I'll try to speed things up.

For the remaining functions, we merely specify that we must start the timer if ffTotal is 0 when a unit is first frozen, and we create the timer at init.


JASS:
private function FreezeAdjacent takes unit source returns nothing // now marked as private
    local location source_loc = GetUnitLoc(source)
    local group enemies = CreateGroup()
    local unit temp
    local timer t = CreateTimer()
    call GroupEnumUnitsInRangeOfLoc(enemies, source_loc, 100.0, null) // You should actually run this loop code as the filterfunc for the GroupEnum. You also need to check for the presence of 'A001' and make sure the unit isn't a building, etc.
    loop
        set temp = FirstOfGroup(enemies)
        exitwhen temp == null
        if (FilterUnit(source,temp) == true) then
            if GetRandomInt(1,100) <= 5 * (GetUnitAbilityLevel(GetTriggerUnit(),'A000') then
                set ffEffect[ffTotal] = AddSpecialEffectTarget("Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl",temp,"origin")
                call UnitAddAbility(temp, 'A001')
                call PauseUnit(temp, true)
                call SetUnitTimeScalePercent(temp, 0)
                // new code below
                set ffUnit[ffTotal] = temp
                set ffDuration[ffTotal] = 0
                set ffLevel[ffTotal] = GetUnitAbilityLevel(GetTriggerUnit())
                if ffTotal == 0 then
                    call TimerStart(ffTimer, TIMER_PERIOD, true, function TimerActions)
                endif
                // end new code
                call FreezeAdjacent(temp)
endif
        endif
        call GroupRemoveUnit(enemies, temp)
    endloop
    call RemoveLocation(source_loc)
    call DestroyGroup(enemies)
    set temp = null
    set source_loc = null
    set enemies = null
endfunction

You'll need to make similar changes to the aciton function.


JASS:
public function InitTrig takes nothing returns nothing //note changes for using a vJASS scope.
    local trigger trig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ATTACKED)
    call TriggerAddCondition(trig, Condition(function Flash_Freeze_Condition))
    call TriggerAddAction(trig, function Flash_Freeze_Actions) // you can rename these because you have a scope. You can make them private and simply call them Actions and Conditions.
    set ffTimer = CreateTimer()  // create the timer at init
    set trig = null  // not strictly necessary, but good habbit
endfunction


I'll go over this again later, running late atm. Good luck!
 
Last edited:
Level 3
Joined
Jul 15, 2009
Messages
40
I decided to stay away from vJass for the moment. I did, however, fit everything in and get things set up.

Unfortunately, I think something has gone seriously awry with my editor/wc3. When trying to test the map, it seems that none of the triggers in the map turn on. (This includes very simple GUI triggers that run on initialization and when the unit is attacked, respectively.)

I will continue to try to figure out what has happened, in the meantime. If you have an idea on that, it could help, as well.

As always, thanks for the help!


EDIT: It seems that (for whatever reason), the global variables are causing the problem. After some trial-and-error testing, I found that if I set all the integer arrays to size 8190, all the triggers on the map become disabled. Is there some kind of memory issue that's going on?

I avoided this problem by setting ffLevel array-size to 100 (which is more than reasonable).

EDIT2: Now that the problem is no longer an issue, I found that the code I am using currently does not unfreeze the attacking unit. I'm currently looking into a resolution.
 
Last edited:
Level 3
Joined
Jul 15, 2009
Messages
40
Okidoky! Well, After some fiddling around with the code, I seem to have got things working (almost). This has some values changed for testing purposes (it freezes 100% of the time and the timer period is 1 second so I can read the debug messages).

JASS:
function Flash_Freeze_Condition takes nothing returns boolean
    return (GetUnitTypeId(GetTriggerUnit()) == 'H000') and (GetUnitAbilityLevel(GetTriggerUnit(),'A000')) > 0
endfunction                                                                                     

function Unfreeze takes unit u, effect sfx returns nothing
    if u != null then
        call BJDebugMsg("Unfreeze unit.")
        call UnitRemoveAbility(u, 'A001')
        call DestroyEffect(sfx)
        call PauseUnit(u, false)
        call SetUnitTimeScalePercent(u,100)
    else
        call BJDebugMsg("Tried to unfreeze a non-existant unit.")
    endif
endfunction


function TimerActions takes nothing returns nothing
    local integer iterator = 0

    loop
        exitwhen iterator == udg_ffTotal
        if udg_ffDuration[iterator] == (10 + 2*udg_ffLevel[iterator]*(10)) then // Checking the duration here.
            call Unfreeze(udg_ffUnit[iterator], udg_ffEffect[iterator])
            set udg_ffTotal = udg_ffTotal - 1
            set udg_ffEffect[iterator] = udg_ffEffect[udg_ffTotal]
            set udg_ffUnit[iterator] = udg_ffUnit[udg_ffTotal]
            set udg_ffLevel[iterator] = udg_ffLevel[udg_ffTotal]
            set udg_ffDuration[iterator] = udg_ffDuration[udg_ffTotal]
            set udg_ffEffect[udg_ffTotal] = null
            set udg_ffUnit[udg_ffTotal] = null
            set iterator = iterator - 1
            // We need to run this loop again for the current (iterator) unit, because
            // it is now a new unit. To prevent us increasing the duration and iterator
            // twice, we decrement them here.
        endif
        set udg_ffDuration[iterator] = (udg_ffDuration[iterator]) + 1
        call BJDebugMsg("udg_ffDuration" + I2S(iterator) + " = " + I2S(udg_ffDuration[iterator]))
        set iterator = iterator + 1
    endloop
    if udg_ffTotal == 0 then
        // pause the timer here, IIRC)
        call PauseTimer(udg_ffTimer)
    endif
endfunction


function FilterUnit takes unit source, unit temp returns boolean
    if (IsUnitAlly(temp, GetOwningPlayer(source))) and (GetUnitAbilityLevel(temp, 'A001') == 0) and not (IsUnitType(temp,UNIT_TYPE_STRUCTURE)) then
        return true
    else 
        return false
    endif
endfunction


function FreezeAdjacent takes unit source returns nothing
    local location source_loc = GetUnitLoc(source)
    local group enemies = CreateGroup()
    local unit temp
    local effect sfx
    local timer t = CreateTimer()
    call GroupEnumUnitsInRangeOfLoc(enemies, source_loc, 100.0, null)
    loop
        set temp = FirstOfGroup(enemies)
        exitwhen temp == null
        if (FilterUnit(source,temp) == true) then
            if GetRandomInt(1,100) <= 100 then
                set sfx = AddSpecialEffectTarget("Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl",temp,"origin")
                call UnitAddAbility(temp, 'A001')
                call PauseUnit(temp, true)
                call SetUnitTimeScalePercent(temp, 0)
                set udg_ffUnit[udg_ffTotal] = temp
                set udg_ffDuration[udg_ffTotal] = 0
                set udg_ffLevel[udg_ffTotal] = GetUnitAbilityLevel(GetTriggerUnit(),'A000')
                set udg_ffTotal = udg_ffTotal + 1
                call BJDebugMsg("Total = " + I2S(udg_ffTotal))
                call FreezeAdjacent(temp)
            endif
        endif
        call GroupRemoveUnit(enemies, temp)
    endloop
    call RemoveLocation(source_loc)
    call DestroyGroup(enemies)
    set temp = null
    set source_loc = null
    set enemies = null
endfunction


function Flash_Freeze_Actions takes nothing returns nothing
    local unit attacker = GetAttacker()
    local effect sfx
    local timer t = CreateTimer()
    
    if GetRandomInt(1,100) <= (100 + 5*(GetUnitAbilityLevel(GetTriggerUnit(),'A000') - 1)) then
        set sfx = AddSpecialEffectTarget("Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl",attacker,"origin")
        call UnitAddAbility(attacker, 'A001')
        call PauseUnit(attacker, true)
        call SetUnitTimeScalePercent(attacker,0)
        if udg_ffTotal > 0 then
            set udg_ffUnit[udg_ffTotal] = attacker
            set udg_ffDuration[udg_ffTotal] = 0
            set udg_ffLevel[udg_ffTotal] = GetUnitAbilityLevel(GetTriggerUnit(),'A000')
            set udg_ffTotal = udg_ffTotal + 1
            call BJDebugMsg("Total = " + I2S(udg_ffTotal))
        elseif udg_ffTotal == 0 then
            set udg_ffUnit[udg_ffTotal] = attacker
            set udg_ffDuration[udg_ffTotal] = 0
            set udg_ffLevel[udg_ffTotal] = GetUnitAbilityLevel(GetTriggerUnit(),'A000')
            set udg_ffTotal = udg_ffTotal + 1
            call BJDebugMsg("Total = " + I2S(udg_ffTotal))
            call TimerStart(udg_ffTimer, 1, true, function TimerActions)
        endif
        call FreezeAdjacent(attacker)
    endif
    set attacker = null
endfunction

                                                                
function InitTrig_Flash_Freeze takes nothing returns nothing
    set gg_trg_Flash_Freeze = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Flash_Freeze, EVENT_PLAYER_UNIT_ATTACKED)
    call TriggerAddCondition(gg_trg_Flash_Freeze, Condition(function Flash_Freeze_Condition))
    call TriggerAddAction(gg_trg_Flash_Freeze, function Flash_Freeze_Actions)
    set gg_trg_Flash_Freeze = null
endfunction

The units now freeze/unfreeze properly, with the correct duration. But, unfortunately, I can't seem to get the effects to be destroyed.

If you want to see what is happening for yourself, I'll attach the map.
 

Attachments

  • FF_Testing.w3x
    93 KB · Views: 79
Level 3
Joined
Jul 15, 2009
Messages
40
Success! Sort of...

Haha! Well, as it turned out, I wasn't adding the effects to the ffEffect array! Now they all disappear marvelously. Well, except for a few occasional instances that don't ever unfreeze, that is.

So the trick now is to find out how these units are not being unfrozen. However, if the problem can't be found, I suppose it's not too bad. The testing I am doing is far more strenuous than the combat the spell is intended for.

In the new code, I've added another ability to the frozen units, 'Avul', which makes them invulnerable. I decided I'd rather have them just freeze instead of taking while frozen. Also, I added some floating text when the spell fires.

Here's the working code:
JASS:
function Flash_Freeze_Condition takes nothing returns boolean
    return (GetUnitTypeId(GetTriggerUnit()) == 'H000') and (GetUnitAbilityLevel(GetTriggerUnit(),'A000')) > 0
endfunction                                                                                     

function Unfreeze takes unit u, effect sfx returns nothing
    if u != null then
        call UnitRemoveAbility(u, 'A001')
        call UnitRemoveAbility(u, 'Avul')
        call DestroyEffect(sfx)
        call PauseUnit(u, false)
        call SetUnitTimeScalePercent(u,100)
    endif
endfunction


function TimerActions takes nothing returns nothing
    local integer iterator = 0

    loop
        exitwhen iterator == udg_ffTotal
        if udg_ffDuration[iterator] == (20 + 2*udg_ffLevel[iterator]*20) then // Checking the duration here.
            call Unfreeze(udg_ffUnit[iterator], udg_ffEffect[iterator])
            set udg_ffTotal = udg_ffTotal - 1
            set udg_ffEffect[iterator] = udg_ffEffect[udg_ffTotal]
            set udg_ffUnit[iterator] = udg_ffUnit[udg_ffTotal]
            set udg_ffLevel[iterator] = udg_ffLevel[udg_ffTotal]
            set udg_ffDuration[iterator] = udg_ffDuration[udg_ffTotal]
            set udg_ffEffect[udg_ffTotal] = null
            set udg_ffUnit[udg_ffTotal] = null
            set udg_ffDuration[iterator] = udg_ffDuration[iterator] - 1
            set iterator = iterator - 1
            // We need to run this loop again for the current (iterator) unit, because
            // it is now a new unit. To prevent us increasing the duration and iterator
            // twice, we decrement them here.
        endif
        set udg_ffDuration[iterator] = (udg_ffDuration[iterator]) + 1
        set iterator = iterator + 1
    endloop
    if udg_ffTotal == 0 then
        // pause the timer here, IIRC)
        call PauseTimer(udg_ffTimer)
    endif
endfunction


function FilterUnit takes unit source, unit temp returns boolean
    if (IsUnitAlly(temp, GetOwningPlayer(source))) and (GetUnitAbilityLevel(temp, 'A001') == 0) and not (IsUnitType(temp,UNIT_TYPE_STRUCTURE)) then
        return true
    else 
        return false
    endif
endfunction


function FreezeAdjacent takes unit source returns nothing
    local location source_loc = GetUnitLoc(source)
    local group enemies = CreateGroup()
    local unit temp
    local effect sfx
    local texttag text = CreateTextTag()
    call GroupEnumUnitsInRangeOfLoc(enemies, source_loc, 100.0, null)
    loop
        set temp = FirstOfGroup(enemies)
        exitwhen temp == null
        if (FilterUnit(source,temp) == true) then
            if GetRandomInt(1,100) <= (5*(GetUnitAbilityLevel(GetTriggerUnit(),'A000') - 1)) then
                set sfx = AddSpecialEffectTarget("Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl",temp,"origin")
                call SetTextTagText(text, "Flash Freeze!", .017)
                call SetTextTagPosUnit(text,GetTriggerUnit(),0.)
                call SetTextTagColor(text,255,255,0,0)
                call SetTextTagPermanent(text,false)
                call SetTextTagLifespan(text,2.0)
                call SetTextTagVisibility(text,true)
                call SetTextTagVelocity(text, 0., 0.05546875 * Sin(45*180/3.14159))
                call SetTextTagFadepoint(text,10)
                call UnitAddAbility(temp, 'A001')
                call UnitAddAbility(temp, 'Avul')
                call PauseUnit(temp, true)
                call SetUnitTimeScalePercent(temp, 0)
                if udg_ffTotal > 0 then
                    set udg_ffUnit[udg_ffTotal] = temp
                    set udg_ffDuration[udg_ffTotal] = 0
                    set udg_ffEffect[udg_ffTotal] = sfx
                    set udg_ffLevel[udg_ffTotal] = GetUnitAbilityLevel(GetTriggerUnit(),'A000')
                    set udg_ffTotal = udg_ffTotal + 1
                elseif udg_ffTotal == 0 then
                    set udg_ffUnit[udg_ffTotal] = temp
                    set udg_ffDuration[udg_ffTotal] = 0
                    set udg_ffEffect[udg_ffTotal] = sfx
                    set udg_ffLevel[udg_ffTotal] = GetUnitAbilityLevel(GetTriggerUnit(),'A000')
                    set udg_ffTotal = udg_ffTotal + 1
                    call TimerStart(udg_ffTimer, .05, true, function TimerActions)
                endif
                call FreezeAdjacent(temp)
            endif
        endif
        call GroupRemoveUnit(enemies, temp)
    endloop
    call RemoveLocation(source_loc)
    call DestroyGroup(enemies)
    set temp = null
    set source_loc = null
    set enemies = null
endfunction


function Flash_Freeze_Actions takes nothing returns nothing
    local unit attacker = GetAttacker()
    local effect sfx
    local texttag text = CreateTextTag()
    
    if GetRandomInt(1,100) <= (5 + 5*(GetUnitAbilityLevel(GetTriggerUnit(),'A000') - 1)) then
        set sfx = AddSpecialEffectTarget("Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl",attacker,"origin")
        call SetTextTagText(text, "Flash Freeze!", .017)
        call SetTextTagPosUnit(text,GetTriggerUnit(),0.)
        call SetTextTagColor(text,255,255,0,255)
        call SetTextTagPermanent(text,false)
        call SetTextTagLifespan(text,1.0)
        call SetTextTagVisibility(text,true)
        call SetTextTagVelocity(text, 0., 0.05546875 * Sin(45*180/3.14159))
        call SetTextTagFadepoint(text,10)
        call UnitAddAbility(attacker, 'A001')
        call UnitAddAbility(attacker, 'Avul')
        call PauseUnit(attacker, true)
        call SetUnitTimeScalePercent(attacker,0)
        if udg_ffTotal > 0 then
            set udg_ffUnit[udg_ffTotal] = attacker
            set udg_ffDuration[udg_ffTotal] = 0
            set udg_ffEffect[udg_ffTotal] = sfx
            set udg_ffLevel[udg_ffTotal] = GetUnitAbilityLevel(GetTriggerUnit(),'A000')
            set udg_ffTotal = udg_ffTotal + 1
        elseif udg_ffTotal == 0 then
            set udg_ffUnit[udg_ffTotal] = attacker
            set udg_ffDuration[udg_ffTotal] = 0
            set udg_ffEffect[udg_ffTotal] = sfx
            set udg_ffLevel[udg_ffTotal] = GetUnitAbilityLevel(GetTriggerUnit(),'A000')
            set udg_ffTotal = udg_ffTotal + 1
            call TimerStart(udg_ffTimer, .05, true, function TimerActions)
        endif
        call FreezeAdjacent(attacker)
    endif
    set attacker = null
endfunction

                                                                
function InitTrig_Flash_Freeze takes nothing returns nothing
    set gg_trg_Flash_Freeze = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_Flash_Freeze, EVENT_PLAYER_UNIT_ATTACKED)
    call TriggerAddCondition(gg_trg_Flash_Freeze, Condition(function Flash_Freeze_Condition))
    call TriggerAddAction(gg_trg_Flash_Freeze, function Flash_Freeze_Actions)
    set gg_trg_Flash_Freeze = null
endfunction


I feel that the problem is more than likely due to some sort of timer issue. Could the timer period be causing an issue?

As always, any help is greatly appreciated!
 
Level 2
Joined
Jul 17, 2009
Messages
27
I'll look at this this evening. I've just moved house and my internet access has been sketchy.

If you can attach the most recent copy of the map, I'd be much obliged.
 
Level 3
Joined
Jul 15, 2009
Messages
40
Sure as sure.

In order to see one of the glitched freezes, you'll probably need to add many more enemy units.

Thanks again!
 

Attachments

  • FF_Testing.w3x
    94.2 KB · Views: 66
Level 2
Joined
Jul 17, 2009
Messages
27
Haha, I found the problem!

JASS:
            set iterator = iterator - 1
            set udg_ffDuration[iterator] = udg_ffDuration[iterator] - 1
            // We need to run this loop again for the current (iterator) unit, because
            // it is now a new unit. To prevent us increasing the duration and iterator
            // twice, we decrement them here.
        endif
        set udg_ffDuration[iterator] = udg_ffDuration[iterator] + 1
        set iterator = iterator + 1
    endloop

I forgot that decreasing the iterator means it cannot be used to point to the currently iterating unit. Thus, the wrong unit has its duration increased, and if its duration is placed above the duration limit, it won't trigger Unfreeze.

Several ways to fix this:

Change the if (in TimerActions) statement to account for durations higher or equal to what we need. This works, but I don't like it - see how we can catch things working incorrectly when we're specific with our conditions. The duration counter should never have to be higher than the duration of the spell.

We can also clean up the code a bit intead, and use an else statement instead of decreasing the iterator / duration and increasing them again later (which I probably should have done first, my apologies).

You could also just change the references to iterator like so:

JASS:
        set udg_ffDuration[iterator + 1] = udg_ffDuration[iterator + 1] + 1


Here's the preferred fix (note I was adding things to the code to help bugfix, you don't need the messages. I also changed the duration for testing purposes.):

JASS:
    loop
        exitwhen iterator == udg_ffTotal
        debug call BJDebugMsg("Iterating through: "+I2S(h2i(udg_ffUnit[iterator]))+".")
        debug call BJDebugMsg("Iterations for above unit are "+I2S(udg_ffDuration[iterator])+".")
        debug call BJDebugMsg("Above unit is in position "+I2S(iterator)+".")
        if udg_ffDuration[iterator] == (5) then // 5 seconds for this test.
            call Unfreeze(udg_ffUnit[iterator], udg_ffEffect[iterator])
            set udg_ffTotal = udg_ffTotal - 1
            set udg_ffEffect[iterator] = udg_ffEffect[udg_ffTotal]
            set udg_ffUnit[iterator] = udg_ffUnit[udg_ffTotal]
            set udg_ffLevel[iterator] = udg_ffLevel[udg_ffTotal]
            set udg_ffDuration[iterator] = udg_ffDuration[udg_ffTotal]
            set udg_ffEffect[udg_ffTotal] = null
            set udg_ffUnit[udg_ffTotal] = null
            else
            set udg_ffDuration[iterator] = udg_ffDuration[iterator] + 1
            set iterator = iterator + 1
        endif
    endloop


Here's the updated Action code I was using, with comments and debug messages:


JASS:
function Flash_Freeze_Actions takes nothing returns nothing
    // Since we are adding the attacker to a global array, there is
    // no need to use a local attacker variable. Same with the sfx.

    // debug call BJDebugMsg("Actions executed.")
    if GetRandomInt(1,100) <= 30 then
        debug call BJDebugMsg("The chance to trigger should have been "+I2S((5 + 5*(GetUnitAbilityLevel(GetTriggerUnit(),'A000'))))+"%")
        set udg_ffUnit[udg_ffTotal] = GetAttacker()
        set udg_ffEffect[udg_ffTotal] = AddSpecialEffectTarget("Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl",udg_ffUnit[udg_ffTotal],"origin")
        set udg_ffLevel[udg_ffTotal] = GetUnitAbilityLevel(GetTriggerUnit(), 'A000')
        set udg_ffDuration[udg_ffTotal] = 0
        if udg_ffTotal == 0 then
            call TimerStart(udg_ffTimer, 1.0, true, function TimerActions)
            debug call BJDebugMsg("Timer started.")
        endif
        call Tag(udg_ffUnit[udg_ffTotal])
        call UnitAddAbility(udg_ffUnit[udg_ffTotal], 'A001')
        call UnitAddAbility(udg_ffUnit[udg_ffTotal], 'Avul')
        call PauseUnit(udg_ffUnit[udg_ffTotal], true)
        call SetUnitTimeScalePercent(udg_ffUnit[udg_ffTotal],0) // You can use the native SetUnitTimeScale here, instead.
        // if udg_ffTotal > 0 then
        //     set udg_ffUnit[udg_ffTotal] = attacker
        //     set udg_ffDuration[udg_ffTotal] = 0
        //     set udg_ffEffect[udg_ffTotal] = sfx
        //     set udg_ffLevel[udg_ffTotal] = GetUnitAbilityLevel(GetTriggerUnit(),'A000')
        //     set udg_ffTotal = udg_ffTotal + 1
        // elseif udg_ffTotal == 0 then
        //     set udg_ffUnit[udg_ffTotal] = attacker
        //     set udg_ffDuration[udg_ffTotal] = 0
        //     set udg_ffEffect[udg_ffTotal] = sfx
        //     set udg_ffLevel[udg_ffTotal] = GetUnitAbilityLevel(GetTriggerUnit(),'A000')
        //     set udg_ffTotal = udg_ffTotal + 1
        //     call TimerStart(udg_ffTimer, .02, true, function TimerActions)
        // endif
        
        // With the above code, since the only difference between them is the timerstart, you're better
        // off calling all the guaranteed lines, and then checking to see if the timer needs starting.

        call FreezeAdjacent(udg_ffUnit[udg_ffTotal])
        set udg_ffTotal = udg_ffTotal + 1
        debug call BJDebugMsg("Number of frozen units is now: "+I2S(udg_ffTotal)+".")
    endif
endfunction


Oh, the Tag(unit) call is me putting the textag in a function:


JASS:
function Tag takes unit u returns texttag
    local texttag t = CreateTextTag()
    call SetTextTagText(t, "Flash Freeze!", 0.017)
    call SetTextTagPosUnit(t, u, 0.0)
    call SetTextTagColor(t, 255, 255, 0, 255)
    call SetTextTagPermanent(t, false)
    call SetTextTagLifespan(t, 3.0)
    call SetTextTagVisibility(t,true)
    call SetTextTagVelocity(t, 0.0, 0.05546875 * Sin(45*180/3.14159))
    call SetTextTagFadepoint(t, 0.5)
    return t
    // This 'leaks' a texttag, because we cannot null the local t. That's fine for testing purposes.
    // To avoid this, you'd normally use a global variable instead of a local in functions such as
    // this, since everything is instant. Also, this function doesn't HAVE to return anything, the
    // return is there to aid in general use.
endfunction
 
Last edited:
Status
Not open for further replies.
Top