1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  4. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  5. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  6. The results are out! Check them out.
    Dismiss Notice
  7. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  8. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  9. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Convenient Unit Group Filtering in GUI

Discussion in 'Trigger (GUI) Editor Tutorials' started by KILLCIDE, Apr 23, 2016.

  1. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,501
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    Convenient Unit Group Filtering in GUI




    Introduction


    This short tutorial will show you the best and most efficient way to filter out units enumerated from a unit group function. It will not show you what usage the function offers (excluding the example trigger), nor how to use them. With that in mind, I recommend that you have moderate knowledge and experience with the trigger editor before reading on.​

    The unit group function is probably one of the most useful functions in the Trigger Editor. A handful of the submitted spells and systems would simply not exist without it. Being an extremely common function, I can guarantee that any trigger that involves the manipulation of multiple units will use it.​

    NOTE: The example triggers you will see throughout this tutorial will all achieve to do the same thing: deal 100 damage to enemy units, that are alive, within 300 range of the triggering unit.​



    Common Structure


    As someone who lurks the Triggers & Scripts and World Editor Help Zone forums, I see a lot of people filtering units for a unit group like this:

    • Set Caster = (Triggering unit)
    • Set Player = (Triggering player)
    • Set CasterLoc = (Position of Caster)
    • -------- --------
    • Set UnitGroup = (Units within 300.00 of CasterLoc matching ((((Matching unit) is alive) Equal to True) and (((Matching unit) belongs to an enemy of Player) Equal to True)))
    • Unit Group - Pick every unit in UnitGroup and do (Actions)
      • Loop - Actions
        • Set TempUnit = (Picked unit)
        • Unit - Cause Caster to damage TempUnit, dealing 100.00 damage of attack type Spells and damage type Normal
    • Custom script: call DestroyGroup(udg_UnitGroup)
    • Custom script: call RemoveLocation(udg_CasterLoc)
    • Set Caster = (Triggering unit)
    • Set Player = (Triggering player)
    • Set CasterLoc = (Position of Caster)
    • -------- --------
    • Custom script: set bj_wantDestroyGroup = true
    • Unit Group - Pick every unit in (Units within 300.00 of CasterLoc matching ((((Matching unit) is alive) Equal to True) and (((Matching unit) belongs to an enemy of Player) Equal to True))) and do (Actions)
      • Loop - Actions
        • Set TempUnit = (Picked unit)
        • Unit - Cause Caster to damage TempUnit, dealing 100.00 damage of attack type Spells and damage type Normal
    • Custom script: call RemoveLocation(udg_CasterLoc)

    Both will achieve the same thing, as explained in the introduction. All though there is nothing entirely wrong with it, there are a few issues that I
    personally have with it:
    • (Matching unit) will constantly call the function GetFilterUnit(), which means that the execution will be slower.
    • They can get excessively long when you have multiple filters (ex: magic immune, not a structure), making it a hassle to read, and when copy-pasted as text it will actually cut off at a certain point:
      Beware: ugly unit group filtering ahead
      • Set UnitGroup = (Units within 300.00 of CasterLoc matching (((((Matching unit) is A structure) Equal to False) and (((Matching unit) is Magic Immune) Equal to False)) and ((((Matching unit) is alive) Equal to True) and (((Matching unit) belongs to an enemy of Player) Equal t
    • It is a complete pain in the a** to add, change, and or remove filters:
      Cringe worthy GIF
      [​IMG]

    Recommended Structure


    If that GIF traumatized you from ever using unit groups again, fret not, for now I will show a much better way of doing it. Instead of using the (Matching unit) function, use an If/Then/Else, Multiple Actions function that directly compares the (Picked unit) to a filter:

    • Set Caster = (Triggering unit)
    • Set Player = (Triggering player)
    • Set CasterLoc = (Position of Caster)
    • -------- --------
    • Set UnitGroup = (Units within 300.00 of CasterLoc)
    • Unit Group - Pick every unit in UnitGroup and do (Actions)
      • Loop - Actions
        • Set TempUnit = (Picked unit)
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • (TempUnit is alive) Equal to True
            • (TempUnit belongs to an enemy of Player) Equal to True
          • Then - Actions
            • Unit - Cause Caster to damage TempUnit, dealing 100.00 damage of attack type Spells and damage type Normal
          • Else - Actions
    • Custom script: call DestroyGroup(udg_UnitGroup)
    • Custom script: call RemoveLocation(udg_CasterLoc)
    • Set Caster = (Triggering unit)
    • Set Player = (Triggering player)
    • Set CasterLoc = (Position of Caster)
    • -------- --------
    • Custom script: set bj_wantDestroyGroup = true
    • Unit Group - Pick every unit in (Units within 300.00 of CasterLoc) and do (Actions)
      • Loop - Actions
        • Set TempUnit = (Picked unit)
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • (TempUnit is alive) Equal to True
            • (TempUnit belongs to an enemy of Player) Equal to True
          • Then - Actions
            • Unit - Cause Caster to damage TempUnit, dealing 100.00 damage of attack type Spells and damage type Normal
          • Else - Actions
    • Custom script: call RemoveLocation(udg_CasterLoc)



    Conclusion


    Voila! Your unit group loop is now efficient, extremely easy to read, and requires little to no effort when you want to add and or remove filters. You also get the added benefit of me not scolding you in the Triggers & Scripts or Spell Section!
     
    Last edited: Jan 9, 2018
  2. Meatmuffin

    Meatmuffin

    Joined:
    Jul 25, 2014
    Messages:
    451
    Resources:
    9
    Maps:
    2
    Spells:
    7
    Resources:
    9
    A really simple tutorial, but I see this done by amateur users all the time and it
    does make me cringe as well. Good job, would have never thought :D
     
  3. Rheiko

    Rheiko

    Joined:
    Aug 27, 2013
    Messages:
    2,936
    Resources:
    7
    Icons:
    2
    Spells:
    3
    Tutorials:
    2
    Resources:
    7
    That gif really did traumatize. =P
    And nice tutorial, nice work.
     
  4. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,426
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    That is definitely more convenient to configure.

    Approved.
     
  5. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,185
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    A proper structure is indeed very useful.
     
  6. A]mun

    A]mun

    Joined:
    Dec 4, 2007
    Messages:
    740
    Resources:
    0
    Resources:
    0
    Good to know, just something:

    Isn't it advisable, to use (matching unit) as a first and quick overall outsourcing, especially in multiple if/then/else filters?

    It would seem rather odd to use if/then/else clauses exlusively, always adding (TempUnit is alive) Equal to True, instead of outsourcing it once via matching unit?!

    Would like some insight on this.
     
  7. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,501
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    "Outsourcing" doesn't really make any sense. Whether or not you use an If/Then/Else with (Picked unit) or (Matching unit), they will all be checked the same exact way. Every single unit enumerated by the unit group will all be checked to each filter you have one at a time. There is no outsourcing.

    This is what the JASS code will look like when the GUI is compiled:
    Matching Unit
    • Set TempLoc = (Center of (Playable map area))
    • Custom script: set bj_wantDestroyGroup = true
    • Unit Group - Pick every unit in (Units within 100.00 of TempLoc matching ((((Matching unit) is A structure) Equal to True) and (((Matching unit) is alive) Equal to True))) and do (Actions)
      • Loop - Actions
        • -------- do stuff --------
    • Custom script: call RemoveLocation(udg_TempLoc)


    Code (vJASS):
    //Using (Matching unit)
    function Filter1 takes nothing returns boolean
        return ( IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == true )
    endfunction

    function Filter2 takes nothing returns boolean
        return ( IsUnitAliveBJ(GetFilterUnit()) == true )
    endfunction

    function FilterResult takes nothing returns boolean
        return GetBooleanAnd( Filter1(), Filter2() )
    endfunction

    function UnitPassesFilters takes nothing returns nothing
        // do stuff
    endfunction

    function Actions takes nothing returns nothing
        set udg_TempLoc = GetRectCenter(GetPlayableMapRect())

        set bj_wantDestroyGroup = true
        call ForGroupBJ( GetUnitsInRangeOfLocMatching(100.00, udg_TempLoc, Condition(function Filter1)), function Filter2 )
        call RemoveLocation(udg_TempLoc)
    endfunction

    If/Then/Else
    • Set TempLoc = (Center of (Playable map area))
    • Custom script: set bj_wantDestroyGroup = true
    • Unit Group - Pick every unit in (Units within 100.00 of TempLoc) and do (Actions)
      • Loop - Actions
        • Set TempUnit = (Picked unit)
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • (TempUnit is A structure) Equal to True
            • (TempUnit is alive) Equal to True
          • Then - Actions
            • -------- do stuff --------
          • Else - Actions
    • Custom script: call RemoveLocation(udg_TempLoc)


    Code (vJASS):
    //Using an If/Then/Else with (Picked unit) stored into a variable
    function Filters takes nothing returns boolean
        if ( not ( IsUnitType(udg_TempUnit, UNIT_TYPE_STRUCTURE) == true ) ) then
            return false
        endif
        if ( not ( IsUnitAliveBJ(udg_TempUnit) == true ) ) then
            return false
        endif
        return true
    endfunction

    function UnitGroupLoop takes nothing returns nothing
        set udg_TempUnit = GetEnumUnit()
        if ( Filters() ) then
            // do stuff
        else
        endif
    endfunction

    function Actions takes nothing returns nothing
        set udg_TempLoc = GetRectCenter(GetPlayableMapRect())

        set bj_wantDestroyGroup = true
        call ForGroupBJ( GetUnitsInRangeOfLocAll(100.00, udg_TempLoc), function UnitGroupLoop )
        call RemoveLocation(udg_TempLoc)
    endfunction


    To repeat, you don't magically filter out the units you want to in an instant using (Matching unit). They individually get checked by each filter you have, and if they don't pass, they are removed from the group. So it is done the same exact way as an If/Then/Else, but with ugly results as I describe in the tutorial.
     
  8. Dr Super Good

    Dr Super Good

    Spell Reviewer

    Joined:
    Jan 18, 2005
    Messages:
    25,612
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    Might be worth mentioning that some standard unit group functions leak due to the LDLHVRCLOR (Local Declared Local Handle Variable Reference Counter Leak On Return) bug.

    These include...
    Code (vJASS):

    function EnumUnitsSelected takes player whichPlayer,boolexpr enumFilter,code enumAction returns nothing
    function GetRandomSubGroup takes integer count,group sourceGroup returns group
    function GetUnitsInRangeOfLocMatching takes real radius,location whichLocation,boolexpr filter returns group
    function GetUnitsInRectMatching takes rect r,boolexpr filter returns group
    function GetUnitsInRectOfPlayer takes rect r,player whichPlayer returns group
    function GetUnitsOfPlayerAndTypeId takes player whichPlayer,integer unitid returns group
    function GetUnitsOfPlayerMatching takes player whichPlayer,boolexpr filter returns group
    function GetUnitsOfTypeIdAll takes integer unitid returns group
    function GetUnitsSelectedAll takes player whichPlayer returns group
     

    The standard implementation of the above functions can leak handle indices. Either custom implementations must be used, or alternatives. This is yet another reason why GUI in WC3 is so terrible.
     
  9. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,501
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    Well the tutorial was aimed at GUI users, so a custom implementation and or alternative is out of the question. The tutorial is also not about leaks, it's about making readable and slightly more efficient unit group loops.

    I do recall a similar topic being brought up in IcemanBo's Memory Leaks tutorial.
     
  10. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,628
    Resources:
    18
    Maps:
    1
    Spells:
    11
    Tutorials:
    6
    Resources:
    18
    Is this really better?
    Sure it's looks fancier but isn't it more ineffective as well? Somehow it feels like filtering units out before they are added to the group is better.
     
  11. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,501
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    As I explained in this post, they are filtered the same exact way. Each enumerated unit goes through each individual filter, one by one. There is no "outsourcing" as A[mun described it.

    I see no benefit in ever using (Matching unit) other than having hate to how the GUI If/Then/Else looks. A fair enough excuse to not use it, but what kind of sane person would ever think this looks any better:
    (Matching Unit) GUI
    • Set UnitGroup = (Units within 300.00 of CasterLoc matching (((((Matching unit) is A structure) Equal to False) and (((Matching unit) is Magic Immune) Equal to False)) and ((((Matching unit) is alive) Equal to True) and (((Matching unit) belongs to an enemy of Player) Equal t
     
  12. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,628
    Resources:
    18
    Maps:
    1
    Spells:
    11
    Tutorials:
    6
    Resources:
    18
    Fair enough.
    I guess I'll start to filter this way in the future then.

    Does that apply to jass as well though? Regarding ForGroup that is.
     
  13. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,501
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    If you want to group units in a range or rect via JASS, it is best to just stick to GroupEnumUnitsInRange (or InRect) and FirstOfGroup.

    I can never see ForGroup being used to filter out units. I've only seen it used when people directly add/remove units into the unit group itself.
     
  14. Dr Super Good

    Dr Super Good

    Spell Reviewer

    Joined:
    Jan 18, 2005
    Messages:
    25,612
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    If you want good looking code use JASS... WC3 GUI is absolutely terrible and hurts my eyes no matter what one tries to do with it.
     
  15. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,501
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    Tutorial Title: Proper Unit Group Filtering in GUI
    ...Proper Unit Group Filtering in GUI
    ...Unit Group Filtering in GUI
    ...Filtering in GUI
    ...in GUI


    The tutorial is for making GUI triggers look clean, not code in general. Regardless, if you wanted good looking code, you would use vJASS. JASS is too damn raw to look good.
     
  16. Dr Super Good

    Dr Super Good

    Spell Reviewer

    Joined:
    Jan 18, 2005
    Messages:
    25,612
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    Except what you are teaching might not be able to be used because a lot of the GUI group functions leak internally...

    At least add a footnote warning of the leaks. The more aware people are about them the fewer leak related problems will surface on the message boards.
     
  17. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,501
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    What you are talking about is irrelevant to what the tutorial is aimed to do.

    As far as I know, the bug is not game breaking, and no mod has ever enforced such a miniscule issue to GUI users. I will consider adding a footnote when I see it as an issue to worry about. A user would not search for and or read a tutorial called "Proper Unit Group Filtering" if they want to figure out if the unit group function leaks; they would go here, where the exact issue is addressed in the thread.
     
  18. Dr Super Good

    Dr Super Good

    Spell Reviewer

    Joined:
    Jan 18, 2005
    Messages:
    25,612
    Resources:
    3
    Maps:
    1
    Spells:
    2
    Resources:
    3
    People read roughly around what they are looking for. What you are saying is that if someone would look for "how to bake a cake" they are not at all interested in the sort of oven to use despite the sort of oven being very important to the quality of the result. All the "efficiency" in the world does not matter if it still leaks as eventually it can still lead to a crash or poor performance.
     
  19. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,501
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    Efficiency is such a small reason to do what I suggested in the tutorial. I highly doubt one will be able to recognize the faster execution time from storing (Picked unit) into a variable rather than constantly calling GetFilterUnit(). I only threw it in as a reason to not using (Matching unit) because three reasons are better than two.

    The main points I really wanted to emphasize on is readability and the ease of configuring.
     
  20. LimitingBounds

    LimitingBounds

    Joined:
    May 12, 2016
    Messages:
    39
    Resources:
    0
    Resources:
    0
    I think the right word to use to describe this tutorial is "convenient". :)