[Trigger] Issues with dynamic index spell

Level 28
Joined
Jun 20, 2013
Messages
769
Hello, i'm using Hanky dynamic index template to learn how to create MUI spells and got a problem with one of the examples.

Example Spell

  • StartMissile
    • Acontecimientos
      • Unidad - A unit Inicia el efecto de una habilidad
    • Condiciones
      • (Ability being cast) Igual a Test-It (Enjambre Carroñero)
    • Acciones
      • -------- IMPORTANT SYSTEM PART START --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • Si: Condiciones
          • SPELL_Index_Size Igual a 0
        • Entonces: Acciones
          • Detonador - Turn on MissileLoop <gen>
        • Otros: Acciones
      • -------- Increase the index size --------
      • Set SPELL_Index_Size = (SPELL_Index_Size + 1)
      • -------- Dynamic Index --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • Si: Condiciones
          • SPELL_Index_Size Mayor que SPELL_Index_maxSize
        • Entonces: Acciones
          • Set SPELL_Index[SPELL_Index_Size] = SPELL_Index_Size
          • Set SPELL_Index_maxSize = SPELL_Index_Size
        • Otros: Acciones
      • -------- Dynamic Index End --------
      • -------- IMPORTANT SYSTEM PART END --------
      • Set TempInt = SPELL_Index[SPELL_Index_Size]
      • -------- Setup those arrays --------
      • Set TempLoc = (Position of (Triggering unit))
      • Set TempLoc2 = (Target point of ability being cast)
      • Set SPELL_CASTER[TempInt] = (Triggering unit)
      • Unidad - Create 1 Dummy (Misiile 1) for Neutral pasivo at TempLoc facing (Angle from TempLoc to TempLoc2) degrees
      • Unidad - Turn collision for (Last created unit) Apagado
      • Unidad - Move (Last created unit) instantly to TempLoc
      • Set SPELL_MISSILE[TempInt] = (Last created unit)
      • Set SPELL_ANGLE[TempInt] = (Angle from TempLoc to TempLoc2)
      • Set SPELL_SPEED[TempInt] = 5.00
      • Set SPELL_DUR[TempInt] = 10.00
      • Custom script: call RemoveLocation(udg_TempLoc)
      • Custom script: call RemoveLocation(udg_TempLoc2)


  • MissileLoop
    • Acontecimientos
      • Tiempo - Every 0.03 seconds of game time
    • Condiciones
    • Acciones
      • For each (Integer SPELL_LOOP) from 1 to SPELL_Index_Size, do (Actions)
        • Bucle: Acciones
          • -------- This let look everything cleaner. --------
          • Set TempInt = SPELL_Index[SPELL_LOOP]
          • -------- Condition... --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • Si: Condiciones
              • SPELL_DUR[TempInt] Mayor que 0.00
            • Entonces: Acciones
              • -------- Actions --------
              • Set TempLoc = (Position of SPELL_MISSILE[TempInt])
              • Set TempLoc2 = (TempLoc offset by SPELL_SPEED[TempInt] towards SPELL_ANGLE[TempInt] degrees)
              • Unidad - Move SPELL_MISSILE[TempInt] instantly to TempLoc2, facing SPELL_ANGLE[TempInt] degrees
              • Custom script: set bj_wantDestroyGroup=true
              • Grupo de unidad - Pick every unit in (Units within 200.00 of TempLoc2) and do (Actions)
                • Bucle: Acciones
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • Si: Condiciones
                      • (SPELL_CASTER[TempInt] belongs to an enemy of (Owner of (Picked unit))) Igual a True
                    • Entonces: Acciones
                      • Unidad - Cause SPELL_CASTER[TempInt] to damage (Picked unit), dealing 75.00 damage of attack type Conjuros and damage type Normal
                      • Set SPELL_DUR[TempInt] = 0.00
                    • Otros: Acciones
              • Set SPELL_DUR[TempInt] = (SPELL_DUR[TempInt] - 0.03)
              • Custom script: call RemoveLocation(udg_TempLoc)
              • Custom script: call RemoveLocation(udg_TempLoc2)
            • Otros: Acciones
              • -------- Destroy handles --------
              • Unidad - Kill SPELL_MISSILE[TempInt]
              • -------- IMPORTANT SYSTEM PART START --------
              • -------- RecycleIndex --------
              • Set SPELL_Index[SPELL_LOOP] = SPELL_Index[SPELL_Index_Size]
              • Set SPELL_Index[SPELL_Index_Size] = TempInt
              • Set SPELL_Index_Size = (SPELL_Index_Size - 1)
              • Set SPELL_LOOP = (SPELL_LOOP - 1)
              • -------- Turn the trigger again off if the index_size is below 0... --------
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • Si: Condiciones
                  • SPELL_Index_Size Igual a 0
                • Entonces: Acciones
                  • Detonador - Turn off (This trigger)
                  • Skip remaining actions
                • Otros: Acciones
              • -------- IMPORTANT SYSTEM PART END --------

The idea is to create a Missile to ground spell using a dummy to cast Cluster of Rockets in the main trigger, then move the dummy and apply two different types of damage and debuff, based on being units or buildings on the second trigger.

Here is the modified spell, it works mostly well. It created a dummy, it shots the ground at the proper place, it applies different damages to units and buildings.

The problems are that the AOE of 200, in the triggers don't hit anything, it need to increased to like 1000 to be able to hit all units inside a 200 selection circle. This also creates lag during the first firing, bigger the number, bigger the jump. Another problem is that the dummy don't attack buildings to apply freezing breath, i've pasted the attack data of the Wyrm on the dummy to not avail.

  • MUINade Start
    • Acontecimientos
      • Unidad - A unit Inicia el efecto de una habilidad
    • Condiciones
      • (Ability being cast) Igual a Storm Bolt Grenade (Caster)
    • Acciones
      • -------- IMPORTANT SYSTEM PART START --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • Si: Condiciones
          • MUINade_Index_Size Igual a 0
        • Entonces: Acciones
          • Detonador - Turn on MUINade Loop <gen>
        • Otros: Acciones
      • -------- Increase the index size --------
      • Set MUINade_Index_Size = (MUINade_Index_Size + 1)
      • -------- Dynamic Index --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • Si: Condiciones
          • MUINade_Index_Size Mayor que MUINade_Index_MaxSize
        • Entonces: Acciones
          • Set MUINade_Index[MUINade_Index_Size] = MUINade_Index_Size
          • Set MUINade_Index_MaxSize = MUINade_Index_Size
        • Otros: Acciones
      • -------- Dynamic Index End --------
      • -------- IMPORTANT SYSTEM PART END --------
      • Set TempInt = MUINade_Index[MUINade_Index_Size]
      • -------- Setup those arrays --------
      • Set TempLoc = (Position of (Triggering unit))
      • Set TempLoc2 = (Target point of ability being cast)
      • Set MUINade_Caster[TempInt] = (Triggering unit)
      • -------- Dummy Variables --------
      • Unidad - Create 1 Dummy (Caster Size 2) for Neutral pasivo at TempLoc facing (Angle from TempLoc to TempLoc2) degrees
      • Set MUINade_Missile[TempInt] = (Last created unit)
      • -------- Missile Graphic --------
      • Unidad - Add Storm Bolt Grenade (Dummy Missile) to MUINade_Missile[TempInt]
      • Unidad - Order MUINade_Missile[TempInt] to Hojalatero neutral: Grupo de cohetes TempLoc2
      • -------- Stun For Ground Units --------
      • Unidad - Add Stun Bolt Ground (Dummy Stun) to MUINade_Missile[TempInt]
      • Unidad - Set level of Stun Bolt Ground (Dummy Stun) for MUINade_Missile[TempInt] to (Level of Storm Bolt Grenade (Caster) for MUINade_Caster[TempInt])
      • -------- Stun For Buildings --------
      • Unidad - Add Stun Building (Dummy Stun) to MUINade_Missile[TempInt]
      • Unidad - Set level of Stun Building (Dummy Stun) for MUINade_Missile[TempInt] to (Level of Storm Bolt Grenade (Caster) for MUINade_Caster[TempInt])
      • -------- Dummy Movement --------
      • Set MUINade_Angle[TempInt] = (Angle from TempLoc to TempLoc2)
      • Set MUINade_Speed[TempInt] = 50.00
      • Set MUINade_Dur[TempInt] = 10.00
      • -------- Damage for Units --------
      • Set MUINade_DamageUnit[TempInt] = (50.00 x (Real((Level of Storm Bolt Grenade (Caster) for MUINade_Caster[TempInt]))))
      • -------- Damage for Buildings --------
      • Set MUINade_DamageBuilding[TempInt] = (25.00 x (Real((Level of Storm Bolt Grenade (Caster) for MUINade_Caster[TempInt]))))
      • -------- Leak Cleaning --------
      • Custom script: call RemoveLocation(udg_TempLoc)
      • Custom script: call RemoveLocation(udg_TempLoc2)
  • MUINade Loop
    • Acontecimientos
      • Tiempo - Every 0.10 seconds of game time
    • Condiciones
    • Acciones
      • For each (Integer MUINade_Loop) from 1 to MUINade_Index_Size, do (Actions)
        • Bucle: Acciones
          • -------- This let look everything cleaner. --------
          • Set TempInt = MUINade_Index[MUINade_Loop]
          • -------- Condition... --------
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • Si: Condiciones
              • MUINade_Dur[TempInt] Mayor que 0.00
            • Entonces: Acciones
              • -------- Actions --------
              • Set TempLoc = (Position of MUINade_Missile[TempInt])
              • Set TempLoc2 = (TempLoc offset by MUINade_Speed[TempInt] towards MUINade_Angle[TempInt] degrees)
              • Unidad - Move MUINade_Missile[TempInt] instantly to TempLoc2, facing MUINade_Angle[TempInt] degrees
              • Custom script: set bj_wantDestroyGroup=true
              • Grupo de unidad - Pick every unit in (Units within 1000.00 of TempLoc2) and do (Actions)
                • Bucle: Acciones
                  • Set MUINade_PicketUnit[TempInt] = (Picked unit)
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • Si: Condiciones
                      • (MUINade_Caster[TempInt] belongs to an enemy of (Owner of MUINade_PicketUnit[TempInt])) Igual a True
                      • (MUINade_PicketUnit[TempInt] is Una unidad de tierra) Igual a True
                      • (MUINade_PicketUnit[TempInt] is Una unidad voladora) Igual a False
                      • (MUINade_PicketUnit[TempInt] is Una estructura) Igual a False
                    • Entonces: Acciones
                      • Unidad - Cause MUINade_Caster[TempInt] to damage MUINade_PicketUnit[TempInt], dealing MUINade_DamageUnit[TempInt] damage of attack type Conjuros and damage type Normal
                      • Unidad - Order MUINade_Missile[TempInt] to Humano Rey de la Montaña: Rayo de tormenta MUINade_PicketUnit[TempInt]
                    • Otros: Acciones
                      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                        • Si: Condiciones
                          • (MUINade_Caster[TempInt] belongs to an enemy of (Owner of MUINade_PicketUnit[TempInt])) Igual a True
                          • (MUINade_PicketUnit[TempInt] is Una estructura) Igual a True
                        • Entonces: Acciones
                          • Unidad - Cause MUINade_Caster[TempInt] to damage MUINade_PicketUnit[TempInt], dealing MUINade_DamageBuilding[TempInt] damage of attack type Conjuros and damage type Normal
                          • Unidad - Order MUINade_Missile[TempInt] to Atacar MUINade_PicketUnit[TempInt]
                          • Partida - Display to (All players) for 15.00 seconds the text: Building Stun
                          • Set MUINade_Dur[TempInt] = 0.00
                        • Otros: Acciones
              • Set MUINade_Dur[TempInt] = (MUINade_Dur[TempInt] - 0.03)
              • Custom script: call RemoveLocation(udg_TempLoc)
              • Custom script: call RemoveLocation(udg_TempLoc2)
            • Otros: Acciones
              • -------- Destroy handles --------
              • Unidad - Kill MUINade_Missile[TempInt]
              • -------- IMPORTANT SYSTEM PART START --------
              • -------- RecycleIndex --------
              • Set MUINade_Index[MUINade_Loop] = MUINade_Index[MUINade_Index_Size]
              • Set MUINade_Index[MUINade_Index_Size] = TempInt
              • Set MUINade_Index_Size = (MUINade_Index_Size - 1)
              • Set MUINade_Loop = (MUINade_Loop - 1)
              • -------- Turn the trigger again off if the index_size is below 0... --------
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • Si: Condiciones
                  • MUINade_Index_Size Igual a 0
                • Entonces: Acciones
                  • Detonador - Turn off (This trigger)
                  • Skip remaining actions
                • Otros: Acciones
              • -------- IMPORTANT SYSTEM PART END --------
 

Attachments

  • TargetZone.png
    TargetZone.png
    1.1 MB · Views: 7
Last edited:
It would be helpful to attach your test map if possible (that way we can also read the triggers more easily since the ones in the post are in spanish).

The first-time lag issue typically occurs when the engine has to load an asset/unit/effect/ability/etc. for the first time. It can usually be resolved by preloading things on map initialization:
  • For units (e.g. the dummy) => create the dummy and then remove it from the map on init
  • For effects => create the effect and destroy it on init
  • For abilities => add an ability to a dummy and then remove the ability on init
See this thread for reference (but you can do it manually pretty easily): [Snippet] Resource Preloader

The range issue has to do with how enumeration is done in wc3. When you grab all units within 200.0 of a point, it'll only enumerate the units whose center coordinates are within that radius (ignoring collision size). This is particularly noticeable for buildings and large units since they have a large collision size. If you want it to be accurate (accounting for collision size), you'll want to increase your enumeration range by a bit (typically whatever the largest collision size is on your map), and then you'll want to use the IsUnitInRange native to narrow them down. Here is an example of how you can do it:
  • Actions
    • Set VariableSet TempUnit = Paladin 0004 <gen>
    • Set VariableSet TempLoc = (Position of TempUnit)
    • Custom script: set bj_wantDestroyGroup = true
    • Unit Group - Pick every unit in (Units within 512.00 of TempLoc matching (((Matching unit) is A structure) Equal to True).) and do (Actions)
      • Loop - Actions
        • Custom script: set udg_TempBoolean = IsUnitInRange(GetEnumUnit(), udg_TempUnit, 200)
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • TempBoolean Equal to True
          • Then - Actions
            • Game - Display to (All players) the text: (Name of (Picked unit))
          • Else - Actions
    • Custom script: call RemoveLocation(udg_TempLoc)
In the example above, we pick all structures within 512 of the "TempUnit". And then we set "TempBool" to check if the picked unit (GetEnumUnit()) is within 200 range of the TempUnit. If so, then it displays their name. You can tweak the numbers however you'd like.



As for the attack failure, probably will help to attach the test map. Usually it is just an issue with the dummy configuration (e.g. vision, attack range, etc.). It usually helps to also manually place the dummy on the map and just make sure it can attack things to begin with.
 
Thanks! After playing around with it, I found the reason for some of the issues--but how you solve it depends on how you want your spell to behave.

AoE Range Issue

The AoE range issue is pretty easy to fix. As mentioned before, you'll first want to make a boolean variable, e.g. MUINade_RangeCondition, just so you can store the result of using the IsUnitInRange custom script.

But the main question is: do you want it to pick the units in range of your missile? Or do you want it to pick units in range of the target location of the spell? Either option is fine. The first option is better if you plan on having this be a missile that hits the first thing it touches. The second option is better though if you want it to match where the player was trying to target the spell originally.

  1. Option 1 (Missile Location): If you want to pick the units within 200.0 of the missile location, you can simply add one line of custom script to check if the unit is in range of the missile location and then add an if-then-else around your damage functionality, like so:
    • Unit Group - Pick every unit in (Units within 1000.00 of TempLoc2.) and do (Actions)
      • Loop - Actions
        • Set VariableSet MUINade_PicketUnit[TempInt] = (Picked unit)
        • -------- Check that the unit is within 200.0 range of the missile (accounting for collision size) --------
        • Custom script: set udg_MUINade_RangeCondition = IsUnitInRangeLoc(GetEnumUnit(), udg_TempLoc2, 200.0)
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • MUINade_RangeCondition Equal to True
          • Then - Actions
            • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              • If - Conditions
                • (MUINade_Caster[TempInt] belongs to an enemy of (Owner of MUINade_PicketUnit[TempInt]).) Equal to True
                • (MUINade_PicketUnit[TempInt] is A ground unit) Equal to True
                • (MUINade_PicketUnit[TempInt] is A flying unit) Equal to False
                • (MUINade_PicketUnit[TempInt] is A structure) Equal to False
              • Then - Actions
                • -------- Damage units --------
              • Else - Actions
                • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                  • If - Conditions
                    • (MUINade_Caster[TempInt] belongs to an enemy of (Owner of MUINade_PicketUnit[TempInt]).) Equal to True
                    • (MUINade_PicketUnit[TempInt] is A structure) Equal to True
                  • Then - Actions
                    • -------- Damage buildings --------
                  • Else - Actions
          • Else - Actions
  2. Option 2 (Target Location of Ability Being Cast): If you want to pick the units within 200.0 of the original target location of the spell, you'll want to make another variable, e.g. MUINade_TargetLoc. Then you'll want to assign it when the spell is being cast. Afterwards, it is pretty much the same as above--you'll just want to replace TempLoc2 with MUINade_TargetLoc. And lastly, you'll want to remove the location when the spell is being cleaned-up.
    • -------- Setup those arrays --------
    • Set VariableSet TempLoc = (Position of (Triggering unit))
    • Set VariableSet TempLoc2 = (Target point of ability being cast)
    • Set VariableSet MUINade_Caster[TempInt] = (Triggering unit)
    • Set VariableSet MUINade_TargetLoc[TempInt] = (Target point of ability being cast)
    • Unit Group - Pick every unit in (Units within 1000.00 of TempLoc2.) and do (Actions)
      • Loop - Actions
        • Set VariableSet MUINade_PicketUnit[TempInt] = (Picked unit)
        • -------- Check that the unit is within 200.0 range of the missile (accounting for collision size) --------
        • Custom script: set udg_MUINade_RangeCondition = IsUnitInRangeLoc(GetEnumUnit(), udg_MUINade_TargetLoc[udg_TempInt], 200.0)
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • MUINade_RangeCondition Equal to True
          • Then - Actions
            • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              • If - Conditions
                • (MUINade_Caster[TempInt] belongs to an enemy of (Owner of MUINade_PicketUnit[TempInt]).) Equal to True
                • (MUINade_PicketUnit[TempInt] is A ground unit) Equal to True
                • (MUINade_PicketUnit[TempInt] is A flying unit) Equal to False
                • (MUINade_PicketUnit[TempInt] is A structure) Equal to False
              • Then - Actions
                • -------- Damage units --------
              • Else - Actions
                • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                  • If - Conditions
                    • (MUINade_Caster[TempInt] belongs to an enemy of (Owner of MUINade_PicketUnit[TempInt]).) Equal to True
                    • (MUINade_PicketUnit[TempInt] is A structure) Equal to True
                  • Then - Actions
                    • -------- Damage buildings --------
                  • Else - Actions
          • Else - Actions
    • -------- Later in the trigger --------
    • -------- Destroy handles --------
    • Unit - Kill MUINade_Missile[TempInt]
    • Custom script: call RemoveLocation(udg_MUINade_TargetLoc[udg_TempInt])

After those changes, it should perfectly match the range of the target circle.

Freezing Breath Issue

Whenever debugging dummy issues, I usually temporarily give them a model (and remove locust) just so I can see what's going on. It is also helpful to place one on the map for your player so you can control it and make sure its attack works and such. After testing, the freezing breath issue comes from a few things:
  1. Attack speed - even though the projectile range is very fast, there is still a small delay in attacks to allow the projectile to travel and such. So if you have the same dummy do storm bolt on everyone and issue an attack order on everyone and then remove it immediately, they might not have a chance to finish attacking.
  2. Freezing breath enemy requirement - freezing breath seems to only apply if the building is an enemy of the dummy. For example, if you put a neutral passive unit and order it to attack, it won't apply freezing breath. But if you make that same unit for Player 1 (Red) and order it to attack those towers, it'll apply the freezing breath debuff correctly.
  3. Splash damage - (optional) if you want the freezing breath to only apply to the structures that are exactly within range, I recommend disabling splash damage for the unit (set the dummy unit's attack type to "Missile" [not the splash one] and you can set its area of effect damage fields to 0).

So how do we solve it? One option is to keep track of the structures and just give the dummy time to attack all of them. But that can get quite tricky to coordinate reliably. I think the simplest solution is to create a dummy for each building, have them attack, and then remove the dummy. The key, though, is to make sure you remove them at the right time (after they've had enough time to attack). For this, we'll do the following:

  1. First, make a separate copy of your dummy in the object editor. I added the editor prefix (Attacker) to be clear. This will be used as a separate, unique unit-type that we can use in our triggers later.
  2. Next, instead of ordering the missile unit to attack--spawn that new dummy unit for the owner of the caster (this is critical to make sure freezing breath applies correctly). Then issue the order to attack. Don't forget to add the Stun Building ability too.
    • Unit - Cause MUINade_Caster[TempInt] to damage MUINade_PicketUnit[TempInt], dealing MUINade_DamageBuilding[TempInt] damage of attack type Spells and damage type Normal
    • Unit - Order MUINade_Missile[TempInt] to Attack MUINade_PicketUnit[TempInt]
    • Unit - Create 1 MUInade Dummy (Attacker) for (Owner of MUINade_Caster[TempInt]) at TempLoc2 facing Default building facing degrees
    • Unit - Add MUINade Stun Building (Dummy Stun) to (Last created unit)
    • Unit - Order (Last created unit) to Attack Once MUINade_PicketUnit[TempInt]
    • Set VariableSet MUINade_Dur[TempInt] = 0.00
  3. Finally, to remove the unit once the damage has been dealt, we'll add a separate trigger. This will respond to when a unit takes damage from the dummy, and then we'll kill the dummy (if you're using an older patch [pre-reforged], then you'll need to use a damage detection system for this):
    • MUINade Remove Dummies
      • Events
        • Unit - A unit About to take damage
      • Conditions
        • (Unit-type of (Damage source)) Equal to MUInade Dummy (Attacker)
      • Actions
        • Event Response - Set Damage of Unit Damaged Event to 0.00
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • ((Damage source) is alive) Equal to True
          • Then - Actions
            • Unit - Kill (Damage source)
          • Else - Actions

After those changes it should work as expected! You can see how it looks in the attached map (just note that I chose option 2 for the AoE range condition, but I'm not sure if that is what you wanted).
 

Attachments

  • MUI - Spell Template.w3x
    82.5 KB · Views: 1
Top