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

[Trigger] Strange things with Stasis Spell

Level 6
Joined
Sep 10, 2022
Messages
68
Hello again, I am trying to make a new Stasis spell that will work similarly as in SC/SC2.

I know that someone managed to do it a long time ago: Simple Problem with Stasis ability
There are even more requests, I glanced. But what I am creating is something that is more trigger-based.

What I did:
1. Created a custom blizzard spell just to select the target area.
2. Every time I cast custom blizzard, triggers does the following:

1) It counts the number of targets.
2) Puts each target unit into an array. The special effect of "Lich Nova" is created and destroyed.
3) Makes units invulnerable and paused, then creates another special effect on each unit. The Special Effect is a decoration called a crystal (from the Ice Crown landscape set). It has no collision, so unit can literally be in it (or it looks so)...
4) Stores the created effects in the effect array.
5) After 5 seconds returns each unit to its initial state, destroying special effects also.
6) Cleans everything.

The mentioned trigger:
  • Stasis
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Стазис
    • Actions
      • Set VariableSet spell_target_area_3 = (Target point of ability being cast)
      • Set VariableSet spell_target_units_2 = (Units within 200.00 of spell_target_area_3.)
      • Unit Group - Pick every unit in spell_target_units_2 and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) is Magic Immune) Equal to Нет
              • ((Picked unit) is A structure) Equal to Нет
              • ((Picked unit) is alive) Equal to Да
            • Then - Actions
              • Set VariableSet spell_target_2 = (Picked unit)
              • Set VariableSet units_array[number_of_units] = spell_target_2
              • Special Effect - Create a special effect attached to the origin of spell_target_2 using Abilities\Spells\Undead\FrostNova\FrostNovaTarget.mdl
              • Set VariableSet stasis_effect = (Last created special effect)
              • Special Effect - Destroy stasis_effect
              • Unit - Pause spell_target_2
              • Unit - Make spell_target_2 Invulnerable
              • Special Effect - Create a special effect attached to the origin of spell_target_2 using Doodads\Icecrown\Rocks\Icecrown_Crystal\Icecrown_Crystal5.mdl
              • Set VariableSet effect_array[number_of_units] = (Last created special effect)
              • Set VariableSet number_of_units = (number_of_units + 1)
            • Else - Actions
      • Wait 5.00 seconds
      • For each (Integer A) from 0 to (number_of_units - 1), do (Actions)
        • Loop - Actions
          • Special Effect - Destroy effect_array[(Integer A)]
          • Unit - Unpause units_array[(Integer A)]
          • Unit - Make units_array[(Integer A)] Vulnerable
          • Custom script: set udg_effect_array[bj_forLoopAIndex] = null
          • Custom script: set udg_units_array[bj_forLoopAIndex] = null
      • Custom script: call DestroyGroup(udg_spell_target_units_2)
      • Custom script: call RemoveLocation(udg_spell_target_area_3)
      • Custom script: set udg_stasis_effect = null
      • Custom script: set udg_spell_target_2 = null
      • Custom script: set udg_number_of_units = 0
It seems to work correctly, BUT each time the unit comes to the initial state, the decoration of the crystal always remains two seconds longer and only then dissapears.

Screenshot.

1693076951181.png


I don't know what's going on exaclty, so I have few questions:
1) Do arrays work too slow?
2) Is there way to know how many units are affected by the spell without using loop?
3) Does the decoration special effect (basically any) really leak if I don't destroy it immediately?
4) What actually causes the delay of destroying of special effect ?
 
Level 23
Joined
Apr 3, 2018
Messages
460
Often the issue is that the model doesn't have a proper death animation.
When it's so, when "destroyed" it will just hang there for the effect decay time specified in advanced -> game constants, and only then disappear.
You could edit the model and add a death animation.
 
Last edited:
Level 6
Joined
Sep 10, 2022
Messages
68
Often the issue is that the model doesn't have a proper death animation.
When it's so, when "destroyed" it will just hang there for the effect decay time specified in advanced -> game constants, and only then disappear.
You could edit the model and add a death animation.
Yeah, it may be the problem. I set effect decay time -> 0 and now the crystal disappears exactly when needed. While the effect of the lich nova is no longer visible, it disappears too quickly. I also thought about a possible way out, instead of changing the constants, I'll just add a wait for 5 seconds to the loop, though now the units will come out of stasis one at a time, and not all together.
I will also try to add a death animation...

UPD: Thanks for the model. I will try.

UPD 2: Almost exactly what I need. A dissipation of the Ice looks good, but could it be a bit faster?
 
Last edited:
Level 19
Joined
Feb 27, 2019
Messages
590
1. Arrays are very fast.
2. The other way I know of is to count units in group using the integer function number of units in group.
3. My understanding is that all special effects leak if theyre not destroyed after the completion of their usage. If its used instantly or for 1 hour doesnt matter as long as its destroyed when no longer used. Some special effects without animations will not play if destroyed instantly, in those cases its preferable to delay the destruction by as long as the effect requires to play.
A dissipation of the Ice looks good, but could it be a bit faster?
If its not possible to edit the model itself one can with most effects change their time scale which is how fast they animate.
A time scale of 1.00 is 100%.
  • Special Effect - Set Time Scale of (Last created special effect) to 1.00
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,543
Here's a somewhat cleaned up version of your trigger.

I removed everything I deemed as unnecessary and fixed any bugs I found:
  • Stasis
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Стазис
    • Actions
      • Set VariableSet spell_target_area_3 = (Target point of ability being cast)
      • Set VariableSet spell_target_units_2 = (Units within 200.00 of spell_target_area_3.)
      • Set VariableSet number_of_units = 0
      • Unit Group - Pick every unit in spell_target_units_2 and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) is Magic Immune) Equal to Нет
              • ((Picked unit) is A structure) Equal to Нет
              • ((Picked unit) is alive) Equal to Да
            • Then - Actions
              • Set VariableSet number_of_units = (number_of_units + 1)
              • Set VariableSet spell_target_2 = (Picked unit)
              • Set VariableSet units_array[number_of_units] = spell_target_2
              • Special Effect - Create a special effect attached to the origin of spell_target_2 using Abilities\Spells\Undead\FrostNova\FrostNovaTarget.mdl
              • Special Effect - Destroy (Last created special effect)
              • Unit - Pause spell_target_2
              • Unit - Make spell_target_2 Invulnerable
              • Special Effect - Create a special effect attached to the origin of spell_target_2 using Doodads\Icecrown\Rocks\Icecrown_Crystal\Icecrown_Crystal5.mdl
              • Set VariableSet effect_array[number_of_units] = (Last created special effect)
            • Else - Actions
      • Custom script: call DestroyGroup(udg_spell_target_units_2)
      • Custom script: call RemoveLocation(udg_spell_target_area_3)
      • Wait 5.00 seconds
      • For each (Integer A) from 1 to number_of_units, do (Actions)
        • Loop - Actions
          • Special Effect - Destroy effect_array[(Integer A)]
          • Unit - Unpause units_array[(Integer A)]
          • Unit - Make units_array[(Integer A)] Vulnerable
Note that this trigger is not MUI or MPI. If this spell is cast multiple times within 5.00 seconds of one another it will cause problems. This is because you're using a Wait action as well as global variables that are referenced after it. These global variables will be shared between all triggers and all instances of this trigger and will be modified globally. So for example, if you were to cast the spell multiple times in a row with short intervals in between each cast (less than 5.00 seconds) you would see the issue at hand. Once the first instance of the trigger finishes it's Wait it will ruin every other instance since it'll make the unit Vulnerable/Destroy the effects/modify the shared variables.

My preferred solution is to take advantage of Unit Indexing and/or Dynamic Indexing. In this case Unit Indexing would work great for a spell like this since we simply want to track some basic data that is attached to our Units. I attached a map (Unit Indexing Spell Example 1) with an example of this. There's also Dynamic Indexing which is a flexible technique that is very similar to what you're doing now. I often take advantage of the Unit Indexer's ability to attach data to a Unit inside of my Dynamic Indexing triggers. There's also Hashtables if you're not interested in Unit Indexing but I find those are often overcomplicated.

Here's my general rule of thumb for when to use these two techniques in a custom spell:

Unit Indexing:
If the custom spell applies a Buff (or uses the same concept of a Buff like your spell does) then use Unit Indexing. It lends itself well to effects that get replaced.

Dynamic Indexing:
If the custom spell is meant to be able to run any number of times from any source at any moment then use Dynamic Indexing. Note that this method struggles with the Buff concept and is better at "stacking" effects, however, it can also be combined with Unit Indexing to do both.

Note that both of these techniques can be used outside of custom triggered spells. If you look at my Unit Indexing Spell Example map you can see that there's really no reason why the triggers have to be tied to a "A unit Starts the effect of an ability" Event. You could use these techniques with any Event/Event Response.

I also attached a system that allows you to easily destroy a special effect after a specified number of seconds. It's a lazy approach and uses Waits which are imprecise but it gets the job done.
 

Attachments

  • Delayed Destroy Effect.w3m
    17.7 KB · Views: 2
  • Unit Indexing Spell Example 1.w3m
    24 KB · Views: 1
Last edited:
Level 6
Joined
Sep 10, 2022
Messages
68
1. Arrays are very fast.
2. The other way I know of is to count units in group using the integer function number of units in group.
3. My understanding is that all special effects leak if theyre not destroyed after the completion of their usage. If its used instantly or for 1 hour doesnt matter as long as its destroyed when no longer used. Some special effects without animations will not play if destroyed instantly, in those cases its preferable to delay the destruction by as long as the effect requires to play.

If its not possible to edit the model itself one can with most effects change their time scale which is how fast they animate.
A time scale of 1.00 is 100%.
  • Special Effect - Set Time Scale of (Last created special effect) to 1.00
Here's a somewhat cleaned up version of your trigger.

I removed everything I deemed as unnecessary and fixed any bugs I found:
  • Stasis
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Стазис
    • Actions
      • Set VariableSet spell_target_area_3 = (Target point of ability being cast)
      • Set VariableSet spell_target_units_2 = (Units within 200.00 of spell_target_area_3.)
      • Set VariableSet number_of_units = 0
      • Unit Group - Pick every unit in spell_target_units_2 and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) is Magic Immune) Equal to Нет
              • ((Picked unit) is A structure) Equal to Нет
              • ((Picked unit) is alive) Equal to Да
            • Then - Actions
              • Set VariableSet spell_target_2 = (Picked unit)
              • Set VariableSet units_array[number_of_units] = spell_target_2
              • Special Effect - Create a special effect attached to the origin of spell_target_2 using Abilities\Spells\Undead\FrostNova\FrostNovaTarget.mdl
              • Set VariableSet stasis_effect = (Last created special effect)
              • Special Effect - Destroy stasis_effect
              • Unit - Pause spell_target_2
              • Unit - Make spell_target_2 Invulnerable
              • Special Effect - Create a special effect attached to the origin of spell_target_2 using Doodads\Icecrown\Rocks\Icecrown_Crystal\Icecrown_Crystal5.mdl
              • Set VariableSet effect_array[number_of_units] = (Last created special effect)
              • Set VariableSet number_of_units = (number_of_units + 1)
            • Else - Actions
      • Custom script: call DestroyGroup(udg_spell_target_units_2)
      • Custom script: call RemoveLocation(udg_spell_target_area_3)
      • Wait 5.00 seconds
      • For each (Integer A) from 0 to (number_of_units - 1), do (Actions)
        • Loop - Actions
          • Special Effect - Destroy effect_array[(Integer A)]
          • Unit - Unpause units_array[(Integer A)]
          • Unit - Make units_array[(Integer A)] Vulnerable
Note that this trigger is not MUI or MPI. If this spell is cast two or more times within 5.00 seconds of one another it will cause problems. This is because you're using a Wait along with global variables that are referenced after it. These variables will be shared between all triggered instances. So all of your trigger instances will have their own Wait which doesn't care about the other instances Wait and will always do it's thing 5.00 seconds later.

My preferred solution is to take advantage of Unit Indexing and/or Dynamic Indexing. In this case Unit Indexing would work great for a spell like this since we simply want to track some basic data that is attached to our Units. I attached a map (Unit Indexing Spell Example 1) with a basic example of this. Dynamic Indexing is a flexible technique which is very similar to what you're doing now. I often take advantage of the Unit Indexer's ability to attach data to a Unit inside of my Dynamic Indexing triggers. There's also Hashtables if you're not interested in Unit Indexing but I find those unwieldy.

My general rule of thumb for when to use these two techniques in a custom spell:

Unit Indexing:
If the custom spell applies a Buff (or uses the same concept of a Buff like your spell) then use Unit Indexing. It lends itself well to effects that get replaced.

Dynamic Indexing:
If the custom spell is meant to be able to run any number of times from any source at any moment then use Dynamic Indexing. Note that this method struggles with the Buff concept and is better at "stacking" effects, however, it can also be combined with Unit Indexing to do both.

Note that both of these techniques can be used outside of custom triggered spells. If you look at my Unit Indexing Spell Example map you can see that there's really no reason why the triggers have to be tied to a "A unit Starts the effect of an ability" Event. You could use these techniques with any Event/Event Responses that you want.

I also attached a system that allows you to easily destroy a special effect after a specified number of seconds. It's a lazy approach and uses Waits which are imprecise but it gets the job done.

Thank you for your responses and help. I will try to use this knowledge to review my spell and will use it the next time I create something similar.
 
Last edited:
Top