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.

[vJASS] PlayerUtils

Discussion in 'JASS Resources' started by TriggerHappy, Apr 27, 2016.

  1. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,671
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    System Code
    Code (vJASS):
    library PlayerUtils
    /**************************************************************
    *
    *   v1.2.9 by TriggerHappy
    *
    *   This library provides a struct which caches data about players
    *   as well as provides functionality for manipulating player colors.
    *
    *   Constants
    *   ------------------
    *
    *       force FORCE_PLAYING - Player group of everyone who is playing.
    *
    *   Struct API
    *   -------------------
    *     struct User
    *
    *       static method fromIndex takes integer i returns User
    *       static method fromLocal takes nothing returns User
    *       static method fromPlaying takes integer id returns User
    *
    *       static method operator []    takes integer id returns User
    *       static method operator count takes nothing returns integer
    *
    *       method operator name         takes nothing returns string
    *       method operator name=        takes string name returns nothing
    *       method operator color        takes nothing returns playercolor
    *       method operator color=       takes playercolor c returns nothing
    *       method operator defaultColor takes nothing returns playercolor
    *       method operator hex          takes nothing returns string
    *       method operator nameColored  takes nothing returns string
    *
    *       method toPlayer takes nothing returns player
    *       method colorUnits takes playercolor c returns nothing
    *
    *       readonly string originalName
    *       readonly boolean isPlaying
    *       readonly static player Local
    *       readonly static integer LocalId
    *       readonly static integer AmountPlaying
    *       readonly static playercolor array Color
    *       readonly static player array PlayingPlayer
    *
    **************************************************************/


        globals
            // automatically change unit colors when changing player color
            private constant boolean AUTO_COLOR_UNITS = true
       
            // use an array for name / color lookups (instead of function calls)
            private constant boolean ARRAY_LOOKUP     = false
       
            // this only applies if ARRAY_LOOKUP is true
            private constant boolean HOOK_SAFETY      = false // disable for speed, but only use the struct to change name/color safely
       
            constant force FORCE_PLAYING = CreateForce()
       
            private string array Name
            private string array Hex
            private string array OriginalHex
            private playercolor array CurrentColor
        endglobals

        private keyword PlayerUtilsInit

        struct User extends array
         
            static constant integer NULL = bj_MAX_PLAYER_SLOTS
           
            readonly player handle
            readonly integer id
            readonly thistype next
            readonly thistype prev

            readonly string originalName
            readonly boolean isPlaying
       
            readonly static thistype first
            readonly static thistype last
            readonly static player Local
            readonly static integer LocalId
            readonly static integer AmountPlaying = 0
            readonly static playercolor array Color

            static if not (LIBRARY_GroupUtils) then
                readonly static group ENUM_GROUP = CreateGroup()
            endif

            private static thistype array PlayingPlayer
            private static integer array PlayingPlayerIndex
       
            // similar to Player(#)
            static method fromIndex takes integer i returns thistype
                return thistype(i)
            endmethod
       
            // similar to GetLocalPlayer
            static method fromLocal takes nothing returns thistype
                return thistype(thistype.LocalId)
            endmethod
       
            // access active players array
            static method fromPlaying takes integer index returns thistype
                return PlayingPlayer[index]
            endmethod
       
            static method operator [] takes player p returns thistype
                return thistype(GetPlayerId(p))
            endmethod
       
            method toPlayer takes nothing returns player
                return this.handle
            endmethod
         
            method operator name takes nothing returns string
                static if (ARRAY_LOOKUP) then
                    return Name[this]
                else
                    return GetPlayerName(this.handle)
                endif
            endmethod
       
            method operator name= takes string newName returns nothing
                call SetPlayerName(this.handle, newName)
                static if (ARRAY_LOOKUP) then
                    static if not (HOOK_SAFETY) then
                        set Name[this] = newName
                    endif
                endif
            endmethod
       
            method operator color takes nothing returns playercolor
                static if (ARRAY_LOOKUP) then
                    return CurrentColor[this]
                else
                    return GetPlayerColor(this.handle)
                endif
            endmethod
       
            method operator hex takes nothing returns string
                return OriginalHex[GetHandleId(this.color)]
            endmethod
       
            method operator color= takes playercolor c returns nothing
                call SetPlayerColor(this.handle, c)
           
                static if (ARRAY_LOOKUP) then
                    set CurrentColor[this] = c
                    static if not (HOOK_SAFETY) then
                        static if (AUTO_COLOR_UNITS) then
                            call this.colorUnits(color)
                        endif
                    endif
                endif
            endmethod
       
            method operator defaultColor takes nothing returns playercolor
                return Color[this]
            endmethod
       
            method operator nameColored takes nothing returns string
                return hex + this.name + "|r"
            endmethod
       
            method colorUnits takes playercolor c returns nothing
                local unit u
           
                call GroupEnumUnitsOfPlayer(ENUM_GROUP, this.handle, null)
           
                loop
                    set u = FirstOfGroup(ENUM_GROUP)
                    exitwhen u == null
                    call SetUnitColor(u, c)
                    call GroupRemoveUnit(ENUM_GROUP, u)
                endloop
            endmethod
       
            static method onLeave takes nothing returns boolean
                local thistype p  = thistype[GetTriggerPlayer()]
                local integer i   = .PlayingPlayerIndex[p.id]
           
                // clean up
                call ForceRemovePlayer(FORCE_PLAYING, p.toPlayer())
           
                // recycle index
                set .AmountPlaying = .AmountPlaying - 1
                set .PlayingPlayerIndex[i] = .PlayingPlayerIndex[.AmountPlaying]
                set .PlayingPlayer[i] = .PlayingPlayer[.AmountPlaying]
               
                if (.AmountPlaying == 1) then
                    set p.prev.next = User.NULL
                    set p.next.prev = User.NULL
                else
                    set p.prev.next = p.next
                    set p.next.prev = p.prev
                endif

                set .last = .PlayingPlayer[.AmountPlaying]
               
                set p.isPlaying = false
           
                return false
            endmethod
       
            implement PlayerUtilsInit
       
        endstruct

        private module PlayerUtilsInit
            private static method onInit takes nothing returns nothing
                local trigger t = CreateTrigger()
                local integer i = 0
                local thistype p
           
                set thistype.Local   = GetLocalPlayer()
                set thistype.LocalId = GetPlayerId(thistype.Local)
           
                set OriginalHex[0]  = "|cffff0303"
                set OriginalHex[1]  = "|cff0042ff"
                set OriginalHex[2]  = "|cff1ce6b9"
                set OriginalHex[3]  = "|cff540081"
                set OriginalHex[4]  = "|cfffffc01"
                set OriginalHex[5]  = "|cfffe8a0e"
                set OriginalHex[6]  = "|cff20c000"
                set OriginalHex[7]  = "|cffe55bb0"
                set OriginalHex[8]  = "|cff959697"
                set OriginalHex[9]  = "|cff7ebff1"
                set OriginalHex[10] = "|cff106246"
                set OriginalHex[11] = "|cff4e2a04"
               
                if (bj_MAX_PLAYERS > 12) then
                    set OriginalHex[12] = "|cff9B0000"
                    set OriginalHex[13] = "|cff0000C3"
                    set OriginalHex[14] = "|cff00EAFF"
                    set OriginalHex[15] = "|cffBE00FE"
                    set OriginalHex[16] = "|cffEBCD87"
                    set OriginalHex[17] = "|cffF8A48B"
                    set OriginalHex[18] = "|cffBFFF80"
                    set OriginalHex[19] = "|cffDCB9EB"
                    set OriginalHex[20] = "|cff282828"
                    set OriginalHex[21] = "|cffEBF0FF"
                    set OriginalHex[22] = "|cff00781E"
                    set OriginalHex[23] = "|cffA46F33"
                endif
             
                set thistype.first = User.NULL

                loop
                    exitwhen i == bj_MAX_PLAYERS

                    set p         = User(i)
                    set p.handle  = Player(i)
                    set p.id      = i
               
                    set thistype.Color[i] = GetPlayerColor(p.handle)
                    set CurrentColor[i] = thistype.Color[i]
                 
                    if (GetPlayerController(p.handle) == MAP_CONTROL_USER and GetPlayerSlotState(p.handle) == PLAYER_SLOT_STATE_PLAYING) then

                        set .PlayingPlayer[AmountPlaying] = p
                        set .PlayingPlayerIndex[i] = .AmountPlaying
                       
                       set .last = i
                       
                        if (.first == User.NULL) then
                            set .first = i
                            set User(i).next = User.NULL
                            set User(i).prev = User.NULL
                        else
                            set User(i).prev = PlayingPlayer[AmountPlaying-1].id
                            set PlayingPlayer[AmountPlaying-1].next = User(i)
                            set User(i).next = User.NULL
                        endif

                        set p.isPlaying = true
                   
                        call TriggerRegisterPlayerEvent(t, p.handle, EVENT_PLAYER_LEAVE)
                        call ForceAddPlayer(FORCE_PLAYING, p.handle)
                   
                        set Hex[p] = OriginalHex[GetHandleId(thistype.Color[i])]
                   
                        set .AmountPlaying = .AmountPlaying + 1

                    endif
               
                    set Name[p] = GetPlayerName(p.handle)
                    set p.originalName=Name[p]
               
                    set i = i + 1
                endloop
           
                call TriggerAddCondition(t, Filter(function thistype.onLeave))
            endmethod
        endmodule

        //===========================================================================


        static if (ARRAY_LOOKUP) then
            static if (HOOK_SAFETY) then
                private function SetPlayerNameHook takes player whichPlayer, string name returns nothing
                    set Name[GetPlayerId(whichPlayer)] = name
                endfunction
           
                private function SetPlayerColorHook takes player whichPlayer, playercolor color returns nothing
                    local User p = User[whichPlayer]
               
                    set Hex[p] = OriginalHex[GetHandleId(color)]
                    set CurrentColor[p] = color
               
                    static if (AUTO_COLOR_UNITS) then
                        call p.colorUnits(color)
                    endif
                endfunction
           
                hook SetPlayerName SetPlayerNameHook
                hook SetPlayerColor SetPlayerColorHook
            endif
        endif

    endlibrary


    Example
    Code (vJASS):
    scope PlayerUtilsExample initializer Init

        private function OnGameStart takes nothing returns nothing
            local User p = User.first
     
            loop // only loop through players that are playing
                exitwhen p == User.NULL
     
                set p.name = "Pvt. " + p.originalName // change name
                set p.color = PLAYER_COLOR_GREEN // change color
     
                // show original name and new one
                call DisplayTextToPlayer(User.Local, 0, 0, p.originalName + " was promoted to " + p.nameColored)
     
                // compare player id's instead of handles (should be faster)
                if (User.fromLocal().id == p.id) then
                    call PanCameraToTimed(128, 128, 0)
                endif
     
                set p = p.next
            endloop
        endfunction

        //===========================================================================
        private function Init takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterTimerEvent(t, 0, false)
            call TriggerAddAction(t, function OnGameStart)
        endfunction

    endscope
     
    Last edited: Aug 1, 2018
  2. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,426
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    Looks pretty useful. I think the biggest benefit is being able to loop through only the players that concern you.

    • I would remove the hooks completely. You don't need to store the player color or the player name. Just have it as:
      Code (vJASS):
              method operator name takes nothing returns string
                  return GetPlayerName(this.handle, name)
              endmethod

              method operator name= takes string newName returns nothing
                  call SetPlayerName(this.handle, name)
              endmethod

              method operator color takes nothing returns playercolor
                  return GetPlayerColor(this.handle)
              endmethod

              method operator color= takes playercolor c returns nothing
                  call SetPlayerColor(this.handle, c)

                  static if (AUTO_COLOR_UNITS) then
                      call this.colorUnits(color)
                  endif
              endmethod

              method operator hex takes nothing returns string
                  return OriginalHex[GetHandleId(this.color)]
              endmethod
       

      I don't think people will use any of those functions too often, so you don't need to cache them.
    • I think it might be nice to have separate forces to ease looping through types of players. For example, it would be useful to be able to loop through users, computers, and observers separately. Or all of them together!
    • Most of the properties and functions are self explanatory, but fromIndex/fromPlaying/fromLocal could use a short description in the documentation.
     
  3. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,180
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    What do you think about screwing playergroups and let it to this ressource: goal. [vJASS] - players
    It feels handy to use it, and instead focus on other features like hex color default/current color, names, local player.
    Your's could be named PlayerTools and the linked one PlayerGroupTools.
     
  4. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,671
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    Updated.

    The system will function as @PurgeandFire suggested if you disable ARRAY_LOOKUP. I still prefer array lookups for name/color so I made it optional.

    @IcemanBo I never had any plans for this resource to deal with player groups. It just has the one for compatibility.
     
  5. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,180
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Resource Comment:

    The code looks good. The purpose is pretty clear when reading the documentation.
    This offers some more functionality in comparison to [Snippet] PlayerUtils.
    This one will be approved, and the latter one will be unapproved.

    Approved.
     
  6. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    397
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    static method onInit takes nothing returns nothing
    =>
    private static method onInit takes nothing returns nothing

    From what I've read, module initializers must be private otherwise they'll become struct initializers.
     
  7. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,180
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    True that! :)
     
  8. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,671
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    Update
    • Made module initializer private
    • Fixed a bug that would return players colors as red
     
  9. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,671
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    Updated, 1.2.5.
    • Now only runs for the first 12 players (potential users).
    • Implemented a linked list of playing players.
    • Default settings changed to safest but slowest.
     
  10. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,671
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    Updated, 1.2.6.
    • Fixed a bug where .name would not update properly (due to typo).
    • added method .toPlayer which returns
      player
     
  11. Fingolfin

    Fingolfin

    Joined:
    Jan 11, 2009
    Messages:
    3,195
    Resources:
    153
    Models:
    143
    Icons:
    1
    Packs:
    4
    Skins:
    2
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    153
    Personally, i don't see why anyone would use User.fromIndex(i) over just User(i). Other than that, i guess it has its uses. One thing though: you should also save each player's original name, since this is useful information in a lot of cases.
     
  12. Hotwer

    Hotwer

    Joined:
    Mar 10, 2013
    Messages:
    370
    Resources:
    0
    Resources:
    0
    If you're looking for a verbose more cleaner syntax over a short one, that's just another option. Personal preference, just that.
     
  13. CHA_Owner

    CHA_Owner

    Joined:
    Feb 11, 2008
    Messages:
    798
    Resources:
    2
    Spells:
    2
    Resources:
    2
    Yea this system comes in very useful and works exactly as it is supposed to helped to save me a bunch of time in my map.
     
  14. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,671
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    Updated, 1.2.7.
    • Removed operator keyword from toPlayer.
    • Removed an unneeded variable.
     
  15. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,671
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    Updated, 1.2.8.
    • Added member .last which returns the last playing player.
    • .prev and .next are now of type
      User
      allowing them to be compounded.
    • .prev and .next are now updated properly when a player leaves.
    • Fixed a bug where the linked list would become out of order.
     
  16. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    3,671
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    Updated, 1.2.9.
    • Support for 24 players & colors.
     
  17. Pinzu

    Pinzu

    Joined:
    Nov 30, 2007
    Messages:
    1,177
    Resources:
    3
    Spells:
    2
    Tutorials:
    1
    Resources:
    3
    Is there a loop for player slots that were playing at start and if not wouldn't that be something worth including?

    In a perfect world all user related data should be deallocated when a player leaves, but we don't live in such a world. And being able to optimize from 24 to lets say 3 players that were active from start is much better than not having that possibility available.
     
    Last edited: Jan 27, 2019
  18. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,180
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    If it's needed to remember the playing players at map start, it probably should be handled in the map itself, using the
    PlayingPlayer
    . Seems a bit a specific issue, to be needed to loop over already leaved players, at any time. Or would the amount of initial players maybe be enough?

    Not sure that it's good if all player related data would be deallocated - or actually, what is exactly meant by it? As we should keep it mind some things the user wants to stay on map, still being shared to the world, or other players in what ever form.
     
  19. Pinzu

    Pinzu

    Joined:
    Nov 30, 2007
    Messages:
    1,177
    Resources:
    3
    Spells:
    2
    Tutorials:
    1
    Resources:
    3
    I don't think it's that specifc to be honest, many systems are configured to remove stuff based on "All Player Slots" and it would be a way to coordinate what you use in setup IsPlaying with OncePlayed at a later point.

    For example the Cooldown Manager that I'm working on at the moment needs to iterate through all potential used player slots to remove any potentially allocated data. This might be considered sub-optimization but none the less:

    Code (vJASS):

            static method purgeFromOption takes SpellOption option returns nothing
                local integer pid = 0
                loop
                    exitwhen pid > bj_MAX_PLAYERS
                    set this = pid
                    if thistype(pid).table.timer.has(option) then
                        call cleanup(thistype(pid).table.timer[option])
                    endif
                    set pid = pid + 1
                endloop
            endmethod

    // -->

     
            static method purgeFromOption takes SpellOption option returns nothing
                local User p = User.firstSlot
                loop
                    exitwhen p = User.NULL // We skip such player slots that were empty from start
                    //
                    if thistype(p.id).table.timer.has(option) then
                        call cleanup(thistype(p.id).table.timer[option])
                    endif
                    //
                    set p = p.nextSlot
                endloop
            endmethod
     


    Its also a really simple thing to add with minimal impact.
     
    Last edited: Jan 27, 2019
  20. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,180
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Why do you deallocate data at a later point, instead of when the player leaves?