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

[Spell] Trigger not recognizing empty group

Status
Not open for further replies.
Level 12
Joined
May 16, 2020
Messages
660
Hi guys,

The following trigger is supposed to create X amount of illusions for every enemy hero on the map, move these illusions instantly to the enemy heroes and then force the illusions to attack the enemy heroes. The player should not be able to control these illusions or change who these illusions attack.

The spell functionally does what it's supposed to do, but the 3rd trigger with the 0.1 sec loop doesn't turn off when I want it to. I want to turn off the spell when "no more illusions with buff Haunt are found in Group Haunt_TempGroup" - but for some reason this doesn't work... I know this because the Footman 0172 who is supposed to die at this step doesn't die, nor do I receive a text "Haunt Loop OFF" that should come at this branch.

Does anyone know why?

Edit: If you find any leaks, please let me know.

***

  • Haunt
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Haunt
    • Actions
      • Set VariableSet Haunt_Caster_Position = (Position of (Triggering unit))
      • Set VariableSet Haunt_Counter = 0
      • Custom script: set bj_wantDestroyGroup = true
      • Unit Group - Pick every unit in (Units in (Playable map area) matching ((((Matching unit) is A Hero) Equal to True) and (((Matching unit) belongs to an enemy of (Triggering player).) Equal to True))) and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) is alive) Equal to True
            • Then - Actions
              • Set VariableSet Haunt_Counter = (Haunt_Counter + 1)
              • Set VariableSet Haunt_Enemy_Hero[Haunt_Counter] = (Picked unit)
            • Else - Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Haunt_Counter Not equal to 0
        • Then - Actions
          • Set VariableSet Haunt_Caster = (Triggering unit)
          • Unit - Create 1 Dummy for (Triggering player) at Haunt_Caster_Position facing Default building facing degrees
          • Unit - Add Haunt Illusion to (Last created unit)
          • Unit - Add a 2.00 second Generic expiration timer to (Last created unit)
          • Unit - Set level of Haunt Illusion for (Last created unit) to (Level of (Ability being cast) for (Triggering unit))
          • For each (Integer A) from 1 to Haunt_Counter, do (Actions)
            • Loop - Actions
              • Custom script: call IssueTargetOrderById(bj_lastCreatedUnit, 852274, udg_Haunt_Caster)
          • Trigger - Turn on Haunt Loop Copy 2 <gen>
          • Game - Display to (All players) the text: Haunt Loop ON
        • Else - Actions
      • Custom script: call RemoveLocation(udg_Haunt_Caster_Position)

  • Haunt Illusion
    • Events
      • Unit - A unit Spawns a summoned unit
    • Conditions
      • ((Summoned unit) has buff Haunt ) Equal to True
    • Actions
      • Custom script: set udg_Haunt_Counter2 = bj_forLoopAIndex
      • Unit - Set (Summoned unit) movement speed to 400.00
      • Unit - Turn collision for (Summoned unit) Off.
      • Unit - Move (Summoned unit) instantly to ((Position of Haunt_Enemy_Hero[Haunt_Counter2]) offset by 100.00 towards (Random angle) degrees.)
      • Unit - Order (Summoned unit) to Attack Haunt_Enemy_Hero[Haunt_Counter2]
      • Set VariableSet Haunt_Unit[Haunt_Counter2] = (Summoned unit)

  • Haunt Loop Copy 2
    • Events
      • Time - Every 0.10 seconds of game time
    • Conditions
    • Actions
      • Set VariableSet Haunt_TempGroup = (Units in (Playable map area) matching (((Matching unit) has buff Haunt ) Equal to True))
      • Unit Group - Pick every unit in Haunt_TempGroup and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Haunt_TempGroup is empty) Equal to True
            • Then - Actions
              • Game - Display to (All players) the text: Haunt Loop OFF
              • Unit - Kill Footman 0172 <gen>
              • Trigger - Turn off (This trigger)
            • Else - Actions
              • Unit - Kill Nature's Prophet 0039 <gen>
              • For each (Integer A) from 1 to Haunt_Counter2, do (Actions)
                • Loop - Actions
                  • Unit - Order Haunt_Unit[(Integer A)] to Attack Haunt_Enemy_Hero[(Integer A)]
      • Unit Group - Remove all units from Haunt_TempGroup.
 
Level 12
Joined
May 16, 2020
Messages
660
Hey,

Your code says "pick every unit in Haunt_TempGroup and do actions"

If Haunt_TempGroup is empty, do you expect this branch to do anything?

I think you've created a loop that executes 0 times, so you'll want to switch the order (loop, condition -> condition, loop)

Hey, thanks for the answer. Yes, if Haunt_TempGroup is empty, I expect for the trigger to turn off (and I would also expect the short message you see there ("Haunt Loop OFF").

Not sure what you meant by the second statement, but what currently works is the "every 0.1 sec for each integer 1 to X Haunt_Unit is forced to attack Haunt_Enemy_Hero. But I want also the other branch to work.
 

Cokemonkey11

Code Reviewer
Level 29
Joined
May 9, 2006
Messages
3,522
Hey, thanks for the answer. Yes, if Haunt_TempGroup is empty, I expect for the trigger to turn off (and I would also expect the short message you see there ("Haunt Loop OFF").

Not sure what you meant by the second statement, but what currently works is the "every 0.1 sec for each integer 1 to X Haunt_Unit is forced to attack Haunt_Enemy_Hero. But I want also the other branch to work.

Sorry, I think we're talking past each other. I'm trying to help you understand why your trigger doesn't say what you think it says.

At present, if the groups is empty, then that whole block of code is not processed at all. The following:

Code:
Unit Group - Pick every unit in Haunt_TempGroup and do (Actions)

Is for writing loops over each unit in the group. When the group is empty, the loop never executes.

So the condition should be switched to the top level, and the for-each-unit should be put inside the conditional execution
 
Level 12
Joined
May 16, 2020
Messages
660
Ahhh, you mean this?:

  • Haunt Loop
    • Events
      • Time - Every 0.10 seconds of game time
    • Conditions
    • Actions
      • Set VariableSet Haunt_TempGroup = (Units in (Playable map area) matching (((Matching unit) has buff Haunt ) Equal to True))
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Haunt_TempGroup is empty) Equal to True
        • Then - Actions
          • Trigger - Turn off (This trigger)
        • Else - Actions
          • Unit Group - Pick every unit in Haunt_TempGroup and do (Actions)
            • Loop - Actions
              • For each (Integer A) from 1 to Haunt_Counter2, do (Actions)
                • Loop - Actions
                  • Unit - Order Haunt_Unit[(Integer A)] to Attack Haunt_Enemy_Hero[(Integer A)]
      • Unit Group - Remove all units from Haunt_TempGroup.
And btw: Did you notice any leaks? I'm still not that confident into doing it 100% correctly...
 
Level 12
Joined
May 16, 2020
Messages
660
Regarding your leak question: you leak a group every 0.1 seconds.

Thanks Zwiebelchen. I corrected it like this:

  • Haunt Loop
    • Events
      • Time - Every 0.10 seconds of game time
    • Conditions
    • Actions
      • Set VariableSet Haunt_TempGroup = (Units in (Playable map area) matching (((Matching unit) has buff Haunt ) Equal to True))
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Haunt_TempGroup is empty) Equal to True
        • Then - Actions
          • Trigger - Turn off (This trigger)
        • Else - Actions
          • Unit Group - Pick every unit in Haunt_TempGroup and do (Actions)
            • Loop - Actions
              • For each (Integer A) from 1 to Haunt_Counter2, do (Actions)
                • Loop - Actions
                  • Unit - Order Haunt_Unit[(Integer A)] to Attack Haunt_Enemy_Hero[(Integer A)]
      • Custom script: call DestroyGroup (udg_Haunt_TempGroup)
This raises two questions:
1) Do I need to add the DestroyGroup also before "Turn off (This trigger)" since it would be turned off immediately? Or will "Turn off (This trigger)" still run the remaining actions before turning it off?
2) Since I destroy and apparently can set the group every 0.1 sec, why does the "set udg_xxx = CreateGroup()" exist?

Honestly you should just write wurst (or at least vjass/lua)

It will become significantly more clear what is a group and what is not

I'm trying to include some custom code into GUI to slowly get the hang of JASS, but it's not that easy to "just write vjass/lua". I know many people here write JASS, but I'm getting 1000 errors when trying to adjust a simple code and it's just bothersome when I can't find any good answers on what functions are called where. At least some help like in Excel formulas that tell you what is needed would be helpful, but JASS doesn't tell you anything.
 
Level 13
Joined
May 10, 2009
Messages
868
1) Do I need to add the DestroyGroup also before "Turn off (This trigger)" since it would be turned off immediately? Or will "Turn off (This trigger)" still run the remaining actions before turning it off?
No. It's fine that way. Turn off will just prevent events from re-running the trigger code in the future. There's another action which stops further actions from running which is "Skip Remaining Actions" (a.k.a return).

2) Since I destroy and apparently can set the group every 0.1 sec, why does the "set udg_xxx = CreateGroup()" exist?
When you assign a new value to a Unit Group variable in GUI which is within parenthesis (see them below), that value is in fact a function that calls CreateGroup() behind the scenes, then adds units to it and returns that group to whoever called it (variable or Unit Group - Pick Every... action).

Any of these guys below create a new group, and you must destroy it in case you won't use that group in the future.
  • Set group = (Random X units from (Units in (Playable map area)))
  • Set group = (Units in (Playable map area))
  • Set group = (Units in (Playable map area) owned by Player X (?))
  • Set group = (Units in (Playable map area) matching ((?) Equal to True))
  • Set group = (Units within 512.00 of (Center of (Playable map area)))
  • Set group = (Units within 512.00 of (Center of (Playable map area)) matching ((?) Equal to True))
  • Set group = (Units owned by Player X (?))
  • Set group = (Units owned by Player X (?) of type unitType)
  • Set group = (Units owned by Player X (?) matching ((?) Equal to True))
  • Set group = (Units of type unitType)
  • Set group = (Units currently selected by Player X (?))
Let's see your very own group, and what exactly happens when you run that function
  • Set VariableSet Haunt_TempGroup = (Units in (Playable map area) matching (((Matching unit) has buff Haunt ) Equal to True))
Behind the scenes
JASS:
// This is the condition which checks if a unit has a specific buff. Yes, it's a separate function.
function Trig_Untitled_Trigger_001_Func001002002 takes nothing returns boolean
    return ( UnitHasBuffBJ(GetFilterUnit(), 'B000') == true )
endfunction

//This is the "actions" block from a trigger. Of course, there's more lines of code in your trigger.
function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
    set udg_Haunt_TempGroup = GetUnitsInRectMatching(GetPlayableMapRect(), Condition(function Trig_Untitled_Trigger_001_Func001002002))
endfunction

// Now, let's take a look into "GetUnitsInRectMatching" function, below
function GetUnitsInRectMatching takes rect r, boolexpr filter returns group
    local group g = CreateGroup() // Creates a new group and assigns to local var "g"
 
    call GroupEnumUnitsInRect(g, r, filter) // Adds units within R "region" to group "G" taking into
    // account that condition which you defined above.
 
    call DestroyBoolExpr(filter)
 
    return g // Returns the group to whoever called it. In this case, "Haunt_TempGroup" var.
endfunction
 
Last edited:
Status
Not open for further replies.
Top