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

[vJASS] Reselect

Reselect makes deselection of your units less painful.
It saves the Units a player had selected when deselecting them.
After the deselection, when not selecting an new orderable Unit, a simple mouseclick on the map (not the Interface) will reselect them. A righclick will order the reselected group to do "smart" to the wanted position.

In the game this means one can select an enemy/shop inspect it, then simply left click on the gameScreen and the units are reselected.

I posted a gui version in the Lab, this version is in vjass, 100% mpi save and also fixes a "bug" the gui version had.
In the GUI version reselection was triggered by selecting multiple uncontrolable units in a row (multiple shops, enemies etc). In the vjass Version reselection won't trigger when selecting new units at all.

JASS:
library Reselect initializer Init
//ReselctV4
//Reselect saves the orderable Units a player had selected when deselecting them.
//When now pressing somewhere on the game screen, the saved deselected units will be reselected.
//UI clicks and another unit-selection won't trigger reselection.

//   function ReselectEnable takes boolean flag returns nothing
   // Disable/Enable Reselect Event Triggers

globals
  
   private group array Group         //Valid Reselect Targets
   private group array TempSave   //Possible Reselect Targets
   private unit array CurrentUnit   //Last Selected Unit of [playerId]
  
   private timer DeSelectTimer = CreateTimer()  
   private force DeselectForce = CreateForce()  
  
   private boolean array orderAsGroup           //(true) rightclick reselection order, will use formation for that player
   private boolean array Allowed                 //MouseClicks can Reselect, that will be calculated inside ActionDeSelectTimer
   private timer MouseTimer    = CreateTimer()   //This Timer allows to run Selection Events of a MouseClick which would execute Reselect, before reselecting.
   private force MouseForce    = CreateForce()   //That allows to select multiple other non controlable units before Reselection is applied. shop -> shop -> enemy -> press Some where -> reselect
   private unit array MouseUnit                 //CurrentUnit when Mouse Click
   private real array MouseX
   private real array MouseY
   private mousebuttontype array MouseButton
  
   public boolean array Enabled               //Enable Reselct for [playerId]; Reselect_Enabled = true, Reselect does only work for Users
   public trigger Select        = CreateTrigger()
   public trigger DeSelect    = CreateTrigger()
   public trigger Mouse        = CreateTrigger()
  
endglobals

private function ActionSelect takes nothing returns nothing
   local player eventPlayer   = GetTriggerPlayer()
   local integer index        = GetPlayerId(eventPlayer)
   if Enabled[index] then
       set CurrentUnit[index] = GetTriggerUnit()
       if GetPlayerAlliance(GetOwningPlayer(CurrentUnit[index]), eventPlayer, ALLIANCE_SHARED_CONTROL) then
           set Allowed[index] = false
           call GroupClear(Group[index])
       endif
   endif
   set eventPlayer = null
endfunction


private function ActionTimerMouseForce takes nothing returns nothing
   local player p        = GetEnumPlayer()
   local integer index   = GetPlayerId(p)
   local effect eff
   local unit fog
   //Did this player select another Unit with this MousePress?
   if CurrentUnit[index] == MouseUnit[index] then
       //No new unit, Reselect!
       call ClearSelectionForPlayer(p)
      
       if MouseButton[index] == MOUSE_BUTTON_TYPE_RIGHT then //Right click -> Order
           set eff = AddSpecialEffect("UI\\Feedback\\Confirmation\\Confirmation.mdl", MouseX[index], MouseY[index])
           call BlzSetSpecialEffectColor( eff, 0, 255, 0 )
           if GetLocalPlayer() != p then
               call BlzSetSpecialEffectScale( eff, 0.00 )
           endif
           call DestroyEffect(eff)
           set eff = null
          
           if orderAsGroup[index] then //Order together?
               call GroupPointOrder( Group[index], "smart", MouseX[index], MouseY[index])
               loop
                   set fog = FirstOfGroup(Group[index])
                   exitwhen fog == null
                   call GroupRemoveUnit(Group[index], fog)
                   call SelectUnitAddForPlayer(fog, p)
               endloop
              
           else //Order one for one
               loop
                   set fog = FirstOfGroup(Group[index])
                   exitwhen fog == null
                   call GroupRemoveUnit(Group[index], fog)
                   call SelectUnitAddForPlayer(fog, p)
                   call IssuePointOrder(fog, "smart", MouseX[index], MouseY[index])
               endloop
           endif
       else //Not Right Click, Only Reselect
           loop
               set fog = FirstOfGroup(Group[index])
               exitwhen fog == null
               call GroupRemoveUnit(Group[index], fog)
               call SelectUnitAddForPlayer(fog, p)
           endloop
       endif      
   endif
   set p = null
endfunction

private function ActionTimerMouse takes nothing returns nothing
   call DisableTrigger(DeSelect)
   call DisableTrigger(Select)
  
   call ForForce(MouseForce, function ActionTimerMouseForce)
   call ForceClear(MouseForce)
  
   call EnableTrigger(DeSelect)
   call EnableTrigger(Select)
endfunction

private function ActionMouse takes nothing returns nothing
   local player p        = GetTriggerPlayer()
   local integer index   = GetPlayerId(p)
   local real x          = BlzGetTriggerPlayerMouseX()
   local real y          = BlzGetTriggerPlayerMouseY()
   if Enabled[index] and Allowed[index] and x != 0 and y != 0  then
           //Save Data of this MousePress
           set MouseX[index]        = x
           set MouseY[index]       = y
           set MouseButton[index]    = BlzGetTriggerPlayerMouseButton()
           call ForceAddPlayer(MouseForce, p)
           set MouseUnit[index]    = CurrentUnit[index]
           //Start a 0s timer, Allow the possible selection event of this mouse Press to run first.
           call TimerStart(MouseTimer,0, false, function ActionTimerMouse)
       //endif
   endif
   set p = null
endfunction

private function ActionDeselectForce takes nothing returns nothing
   local player p        = GetEnumPlayer()
   local integer index = GetPlayerId(p)
   if not IsUnitSelected(CurrentUnit[index], p) or not GetPlayerAlliance(GetOwningPlayer(CurrentUnit[index]), p, ALLIANCE_SHARED_CONTROL) then
       call GroupAddGroup(TempSave[index], Group[index])
       set Allowed[index] = true
   endif
   call GroupClear(TempSave[index])
   set p = null
endfunction

private function ActionDeselectTimer takes nothing returns nothing
   call ForForce(DeselectForce, function ActionDeselectForce)
   call ForceClear(DeselectForce)
endfunction

private function ActionDeSelect takes nothing returns nothing
   local player eventPlayer   = GetTriggerPlayer()
   local unit eventUnit       = GetTriggerUnit()
   local integer index        = GetPlayerId(eventPlayer)
   if Enabled[index] and GetPlayerAlliance(GetOwningPlayer(eventUnit), eventPlayer, ALLIANCE_SHARED_CONTROL) then
       call GroupAddUnit(TempSave[index], eventUnit)
       call ForceAddPlayer(DeselectForce, eventPlayer)
       call TimerStart(DeSelectTimer,0, false, function ActionDeselectTimer)
   endif  
   set eventPlayer   = null
   set eventUnit    = null
endfunction

private function InitPlayers takes nothing returns nothing
   local player p      = GetEnumPlayer()
   local integer index = GetPlayerId(p)
   if GetPlayerController(p) == MAP_CONTROL_USER then
       set Enabled[index] = true
       set Group[index]        = CreateGroup()
       set TempSave[index]     = CreateGroup()
       set orderAsGroup[index] = true
       call TriggerRegisterPlayerEvent(Mouse, p, EVENT_PLAYER_MOUSE_DOWN)
       call TriggerRegisterPlayerUnitEvent(Select, p, EVENT_PLAYER_UNIT_SELECTED, null)
       call TriggerRegisterPlayerUnitEvent(DeSelect, p, EVENT_PLAYER_UNIT_DESELECTED, null)
   endif  
   set p = null
endfunction

function ReselectEnable takes boolean flag returns nothing
   if flag then
       call EnableTrigger(Select)
       call EnableTrigger(Mouse)
       call EnableTrigger(DeSelect)
   else
       call DisableTrigger(Select)
       call DisableTrigger(Mouse)
       call DisableTrigger(DeSelect)
   endif
endfunction

private function Init takes nothing returns nothing
   call TriggerAddAction(Select, function ActionSelect)
   call TriggerAddAction(DeSelect, function ActionDeSelect)
   call TriggerAddAction(Mouse, function ActionMouse)
   call ForForce(bj_FORCE_ALL_PLAYERS, function InitPlayers)
endfunction
endlibrary

Changes:
V4:
added ReselectEnable a function to dis/enable Reselect Event Triggers.
added whitespace
ForGroup -> FoG loop​
V3:
Added OrderAsGroup for each player, (true) -> right click uses Formation.
Fixed a bug: reselecting to many units.​
V2:
(currently not gain) Formating adjusted
added some missing "set p = null"​
 
Last edited:
What is bad with (this) library initializer?

Oh, with the order of initialization and whatnot, there might be other triggers initialized first that might do stuff to the actions above, causing it to malfunction.

JPAG - JASS Proper Application Guide
Initialization Priorities

NOTE: Users of Cohadar's JassHelper can ignore this next
step for the most part as initialization is much more intuitive
in his version.


This is not clear enough to most, but here is how the order of
initialization fires in the realm of JASS:

  1. Module Initializers
  2. Struct Initializers
  3. Library Initializers
  4. Scope Initializers
  5. InitTrig_ Initializers
  6. "Event - Map Initialization" GUI Initializers
    • base.gif
      MyTrigger
      • joinminus.gif
        events.gif
        Events
        • line.gif
          joinbottom.gif
          folder.gif
          Map Initialization
      • join.gif
        cond.gif
        Conditions
      • joinbottom.gif
        actions.gif
        Actions

    .
  7. 0-seconds game time (happens after map has finished loading)
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Oh, with the order of initialization and whatnot, there might be other triggers initialized first that might do stuff to the actions above, causing it to malfunction.

JPAG - JASS Proper Application Guide
Yeah this one is actually not important though. You could initialize this thing even with a 0 second timer, because it involves selection events. The only thing this would misfire for would be things which are actually selected before map initialization (which is really just used for RPG-type things to pre-select the hero unit).
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
The following sequence selects more than just the group you had previously selected:
select owned unit A
select enemy or neutral
select owned unit B
select enemy or neutral
mouse click
Now I have both A and B selected, even though only B is supposed to be selected. Using that method you can even have units and buildings selected at the same time.

Maybe you can look into how this occurs. I also found another way to get the wrong units selected, though it is harder to reproduce/explain than the above one.

It seems like GroupPointOrder makes the units move in formation. I think you would rather want them to walk normally, considering most people do not use formation movement.
 
Thanks for your feedback.

Updaded to Reselect V3.

It seems like GroupPointOrder makes the units move in formation. I think you would rather want them to walk normally, considering most people do not use formation movement.
There is now an boolean array OrderAsGroup:
true; the reselected group will be ordered as group (uses formation)
False; each unit of the reselected group will perform an independent smart order.​

The following sequence selects more than just the group you had previously selected:
select owned unit A
select enemy or neutral
select owned unit B
select enemy or neutral
mouse click
Now I have both A and B selected, even though only B is supposed to be selected. Using that method you can even have units and buildings selected at the same time.

Maybe you can look into how this occurs. I also found another way to get the wrong units selected, though it is harder to reproduce/explain than the above one.
Should now be fixed.
 
Interesting system. Something like this might also be used to allow ordering multiple groups of units around.

Anyway, a few suggestions:

  • I would suggest storing the values of natives which are called many times for readability and performance. For example you could make your ActionDeSelect function look like:
    JASS:
    private function ActionDeSelect takes nothing returns nothing
        local player selectingPlayer = GetTriggerPlayer()
        local integer index = GetPlayerId(p)
        local unit selectedUnit = GetTriggerUnit()
    
        if Enabled[index] and GetPlayerAlliance(GetOwningPlayer(selectedUnit), selectingPlayer, ALLIANCE_SHARED_CONTROL) then
           call GroupAddUnit(TempSave[index], selectedUnit)
           call ForceAddPlayer(DeselectForce, selectingPlayer)
           call TimerStart(DeSelectTimer, 0, false, function ActionDeselectTimer)
        endif   
    
        set selectedUnit = null
    endfunction
  • In ActionTimerMouseForce you never set effect eff to null which causes a small memory leak.
  • You may want to look at using FirstOfGroup instead of ForGroup. vJass Optimization: Using a First of Group Loop for Enumeration
  • A way to enable/disable the system might be useful. function ReselectEnable takes boolean flag returns nothing
  • Maybe add a line between each function so they aren't so close together. It might be an encoding issue with the website or something though.
Once concern I have with this is how it will feel in multiplayer due to the selection events being somewhat slow. I think it should be fine but testing would be best.
 
Top