• 🏆 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] players

Level 13
Joined
Nov 7, 2014
Messages
571
PlayerArray - what force should've been

JASS:
library PlayerArray

//! novjass

General notes:

    A PlayerArray is a 1 based array (indices start from 1, not 0), that has a capacity for 16 elements,
    because 16 is the value of the bj_MAX_PLAYER_SLOTS = 16 (from blizzard.j) variable.

    A PlayerArray stores the integers (aka player-numbers, abbreviated with pn) that are returned from the
    GetPlayerId native, namely: 0, 1, .., 14, 15.

    The integers are always stored in ascending order. This makes the adding and removing of new
    player-numbers an O(n) operations, which is fine becase those operations happen infrequently.

    A player-number can be added only once to a single PlayerArray; it can be added to two different PlayerArray(s) of course.

    Under the hood the array has a capacity for 17 elements, but the first is not used,
    this speeds up lookup times (pas[a + i] vs pas[a + i - 1], i.e no - 1 term).
    This puts a cap on the maximum number of PlayerArray(s) to 480.


How to correctly iterate:

    local PlayerArray a = PlayerArray.create()
    call a.add(0)
    call a.add(1)

    // Forward iteration of the whole array looks like:
    set i = 1
    loop
        exitwhen i > a.count // NOTE the '>', if we used '>=' instead then i >= a.count would be an off-by-one error, i.e 1 less iteration
        set pn = a[i]
        set i = i + 1
    endloop

    // Backwards iteration of the whole array looks like:
    set i = a.count
    loop
        exitwhen i < 1 // i <= 1 would be, again, an off-by-one error
        set pn = a[i]
        set i = i - 1
    endloop

API:

//
// Creating/Destroying a PlayerArray
//

// Creates a new empty array.
//
static method create takes nothing returns PlayerArray

// Destroys the array.
//
method destroy takes nothing returns nothing


//
// Adding/Removing elements
//

// Adds pn to the array, if pn was already in the array displays a warning in debug mode and returns false,
// otherwise if pn was not in the array returns true, i.e it was successfully added.
//
method add takes integer pn returns boolean

// Removes pn from the array, if pn was not in the array displays a warning in debug mode and returns false,
// otherwise if pn was in the array returns true, i.e it was  successfully removed.
//
method remove takes integer pn returns boolean

// Checks to see if the pn has been added, returns true when it has been, false otherwise.
//
method includes takes integer pn returns boolean


//
// Accesing elemnts
//

// The number of elements that the arrays contains.
//
readonly integer count

// Gets the i'th element of the array, i must be in the range 1 .. 16.
// Has bounds checking in debug mode.
//
method operator[] takes integer i returns integer

// Note: there is no operator[]=


//
// Determining whether a predefined array (PlayingPlayers, Obseervers, etc.) has been enabled.
//

// We can test if the Observers array has been enabled (USING_Observers = true) with:
if Observers != 0 then
    ...
endif

//! endnovjass


globals
    // the set of all human players
    private constant boolean USING_PlayingPlayers = true

     // the set of all computer players regardless of their AI difficulty
    private constant boolean USING_ComputerPlayers = false

    // the set of players either human or computer that got defeated (EVENT_PLAYER_DEFEAT)
    private constant boolean USING_DefeatedPlayers = false

    // the set of PlayingPlayers + ComputerPlayers - DefeatedPlayers
    private constant boolean USING_AllPlayingPlayers = false

    // the set of all human (computers can not be observers) players that are watching the game
    private constant boolean USING_Observers = false

    // the set of all human players that have left the game (computers can not leave the game)
    private constant boolean USING_LeftTheGamePlayers = false

    // the set of AllPlayingPlayers + Observers + DefeatedPlayers - LeftTheGamePlayers
    private constant boolean USING_AllPlayers = false

    // this is a lookup table for converting player-numbers to player-handles
    // PLAYER[0] == Player(0), PLAYER[15] == Player(15)
    //
    private constant boolean USING_PLAYER = true

    // this is a lookup table for determining if a player-number is the one of the local player
    // if LOCAL_PLAYER[0] then
    //     ...
    // endif
    //
    // is equivalent to:
    //
    // if GetLocalPlayer() == Player(0) then
    //    ...
    // endif
    //
    private constant boolean USING_LOCAL_PLAYER = true
endglobals


struct PlayerArray extends array
    private static thistype head = 1 // the free list's head node used by the create/destroy methods
    private thistype next

    private static integer array pas // storage for all PlayerArray(s)
    private static boolean array pin // a flag that determines if a pn is 'in' the array

    readonly integer count // the number of pns that were added to the array

static if DEBUG_MODE then
    private static method panic takes string msg returns nothing
        call BJDebugMsg("|cffFF0000[thistype] error: " + msg + "|r")
        set head = 1 / 0
    endmethod
endif

    static method create takes nothing returns thistype
        local thistype this
        local integer i

static if DEBUG_MODE then
        if head >= 481 then
            call panic("unable to allocate a thistype instance")
        endif
endif

        set this = head

        set head = head.next
        if head == 0 then
            set head = this + 1
        endif

        set this.next = -1 // set this before premultiplying

        set this = this * 17

        // clear the array
        set i = 1
        loop
            exitwhen i > 16

            set pas[this + i] = 0x7FFFFFFF
            set pin[this + /*pn: */ i - 1] = false

            set i = i + 1
        endloop

        set this.count = 0

        return this
    endmethod

    method destroy takes nothing returns nothing
        set this = this / 17

static if DEBUG_MODE then
        if this == 0 then
            call panic("unable to free a null thistype")
            return
        elseif this.next != -1 then
            call panic("double free for thistype(" + I2S(this) + ") instance")
            return
        endif
endif

        set this.next = head
        set head = this
    endmethod

    method operator[] takes integer i returns integer
static if DEBUG_MODE then
        if not (1 <= i and i <= this.count) then
            call panic("in operator[], index(" + I2S(i) + ") is out of range for thistype(" + I2S(this / 17) + ") instance with .count: " + I2S(this.count))
        endif
endif
        return pas[this + i]
    endmethod

    method includes takes integer pn returns boolean
static if DEBUG_MODE then
    if not (0 <= pn and pn <= 15) then
        call panic("in method includes, '" + I2S(pn) + "' is not a valid player-number")
        return false
    endif
endif
        return pin[this + pn]
    endmethod

    method add takes integer pn returns boolean
        local integer count
        local integer i
        local integer j
        local integer n

static if DEBUG_MODE then
        if not (0 <= pn and pn <= 15) then
            call panic("in method add, '" + I2S(pn) + "' is not a valid player-number")
        endif
endif

        if pin[this + pn] then
static if DEBUG_MODE then
            call panic("player-number(" + I2S(pn) + ") has already been added to the thistype(" + I2S(this / 17) + ") instance")
endif
            return false
        endif

        set pin[this + pn] = true
        set this.count = this.count + 1

        set count = this.count
        set i = 1
        loop
            exitwhen i > count
            if pas[this + i] > pn then
                // We found the right place for pn, shift the elements from i to count - 1 to the right by 1,
                // to make space for it.

                set j = count
                set n = j - i
                loop
                    exitwhen n <= 0
                    set pas[this + j] = pas[this + j - 1]
                    set j = j -1
                    set n = n - 1
                endloop

                set pas[this + i] = pn

                return true
            endif

            set i = i + 1
        endloop

        // unreachable
        return true
    endmethod

    method remove takes integer pn returns boolean
        local integer count
        local integer i

static if DEBUG_MODE then
        if not (0 <= pn and pn <= 15) then
            call panic("in method remove, '" + I2S(pn) + "' is not a valid player-number")
        endif
endif

        if not pin[this + pn] then
static if DEBUG_MODE then
            call panic("in method remove, player-number(" + I2S(pn) + ") is not in thistype(" + I2S(this / 17) + ") instance")
endif
            return false
        endif

        set pin[this + pn] = false

        set count = this.count
        set i = 1
        loop
            exitwhen i > count

            if pas[this + i] == pn then
                // we found pn, remove it by shifting the elements from i + 1 to count to the left by 1
                loop
                    exitwhen i >= count
                    set pas[this + i] = pas[this + i + 1]
                    set i = i + 1
                endloop

                set this.count = this.count - 1

                return true
            endif

            set i = i + 1
        endloop

        // unreachable
        return true
    endmethod

endstruct

// It seems that static if blocks cannot be nested inside a globals block.

// NOTE:
//    It seems that global blocks are parsed regardless if the static if's condition evaluates to true or not.
//    This means that if one has set a configuration variable (e.g: USING_ComputerPlayers = false)
//    and tries to use the ComputerPlayers variable, there will NOT be a compile time error but the script
//    will fail because the variable wouldn't have been initialized!
//
static if USING_PlayingPlayers then
globals
    PlayerArray PlayingPlayers = 0 // presumably human
endglobals
endif

static if USING_ComputerPlayers then
globals
    PlayerArray ComputerPlayers = 0 // AI folks
endglobals
endif

static if USING_DefeatedPlayers then
globals
    PlayerArray DefeatedPlayers = 0 // last winners
endglobals
endif

static if USING_AllPlayingPlayers then
globals
    PlayerArray AllPlayingPlayers = 0 // PlayingPlayers + ComputerPlayers - DefeatedPlayers
endglobals
endif

static if USING_Observers then
globals
    PlayerArray Observers = 0 // presumably human players who just watch the game, unless skynet
endglobals
endif

static if USING_LeftTheGamePlayers then
globals
    PlayerArray LeftTheGamePlayers = 0 // ComputerPlayers never leave, you can always count on them
endglobals
endif

static if USING_AllPlayers then
globals
    PlayerArray AllPlayers = 0 // AllPlayingPlayers + Observers + DefeatedPlayers - LeftTheGamePlayers
endglobals
endif

static if USING_PLAYER then
globals
    player array PLAYER
endglobals
endif

static if USING_LOCAL_PLAYER then
globals
    boolean array LOCAL_PLAYER
endglobals
endif


private function on_player_leave takes nothing returns nothing
    // because the on_player_leave is registered for both PlayingPlayers and Observers
    // we have to guard against 2 attempts to add/remove, otherwise we will get an error
    //
    local integer pn = GetPlayerId(GetTriggerPlayer())

static if USING_LeftTheGamePlayers then
    if not LeftTheGamePlayers.includes(pn) then
        call LeftTheGamePlayers.add(pn)
    endif
endif

static if USING_PlayingPlayers then
    if PlayingPlayers.includes(pn) then
        call PlayingPlayers.remove(pn)
    endif
endif

static if USING_Observers then
    if Observers.includes(pn) then
        call Observers.remove(pn)
    endif
endif

    // ComputerPlayers can't leave

static if USING_DefeatedPlayers then
    if DefeatedPlayers.includes(pn) then
        call DefeatedPlayers.remove(pn)
    endif
endif

static if USING_AllPlayingPlayers then
    if AllPlayingPlayers.includes(pn) then
        call AllPlayingPlayers.remove(pn)
    endif
endif

static if USING_AllPlayers then
    if AllPlayers.includes(pn) then
        call AllPlayers.remove(pn)
    endif
endif

endfunction

private function on_player_defeated takes nothing returns nothing
    local integer pn = GetPlayerId(GetTriggerPlayer())
    local boolean observers_on_defeat

    call DefeatedPlayers.add(pn)

static if USING_PlayingPlayers then
    if PlayingPlayers.includes(pn) then
        call PlayingPlayers.remove(pn)
    endif
endif

static if USING_Observers then
    set observers_on_defeat = IsMapFlagSet(MAP_OBSERVERS_ON_DEATH)
    if observers_on_defeat and not ComputerPlayers.includes(pn) then
        call Observers.add(pn)
    endif
endif

static if USING_ComputerPlayers then
    if ComputerPlayers.includes(pn) then
        call ComputerPlayers.remove(pn)
    endif
endif

endfunction


private function init takes nothing returns nothing
    local boolean is_playing
    local boolean is_human
    local boolean is_computer
    local boolean is_observer
    local integer i
    local player p
    local trigger t


static if USING_PlayingPlayers then
    set PlayingPlayers = PlayerArray.create()
endif

static if USING_ComputerPlayers then
    set ComputerPlayers = PlayerArray.create()
endif

static if USING_DefeatedPlayers then
    set DefeatedPlayers = PlayerArray.create()
endif

static if USING_AllPlayingPlayers then
    set AllPlayingPlayers = PlayerArray.create()
endif

static if USING_Observers then
    set Observers = PlayerArray.create()
endif

static if USING_LeftTheGamePlayers then
    set LeftTheGamePlayers = PlayerArray.create()
endif

static if USING_AllPlayers then
    set AllPlayers = PlayerArray.create()
endif


    set i = 0
    loop
        exitwhen i >= bj_MAX_PLAYERS
        set p = Player(i)

static if USING_PLAYER then
        set PLAYER[i] = p
endif

static if USING_Observers then
        set is_observer = IsPlayerObserver(p)
        if is_observer then
            call Observers.add(i)
        endif
endif

        set is_playing = GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING

        if is_playing then
            set is_human = GetPlayerController(p) == MAP_CONTROL_USER
            set is_computer = GetPlayerController(p) == MAP_CONTROL_COMPUTER

            if is_human then

static if USING_PlayingPlayers then
                call PlayingPlayers.add(i)
endif

static if USING_AllPlayingPlayers then
                call AllPlayingPlayers.add(i)
endif

            elseif is_computer then

static if USING_ComputerPlayers then
                call ComputerPlayers.add(i)
endif

static if USING_AllPlayingPlayers then
                call AllPlayingPlayers.add(i)
endif

            endif

static if USING_AllPlayers then
            call AllPlayers.add(i)
endif

        endif

static if USING_LOCAL_PLAYER then
        set LOCAL_PLAYER[i] = GetLocalPlayer() == p
endif

        set i = i + 1
    endloop

static if USING_PLAYER then
    set PLAYER[12] = Player(12)
    set PLAYER[13] = Player(13)
    set PLAYER[14] = Player(14)
    set PLAYER[15] = Player(15)
endif


    set t = CreateTrigger()

static if USING_PlayingPlayers then

    set i = 1
    loop
        exitwhen i > PlayingPlayers.count
        set p = Player(PlayingPlayers[i])
        call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_LEAVE)
        set i = i + 1
    endloop
    call TriggerAddAction(t, function on_player_leave)

endif


static if USING_Observers then

    set i = 1
    loop
        exitwhen i > Observers.count
        set p = Player(Observers[i])
        call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_LEAVE)
        set i = i + 1
    endloop
    call TriggerAddAction(t, function on_player_leave)

endif


static if USING_DefeatedPlayers then
    set t = CreateTrigger()
    set i = 1
    loop
        exitwhen i > AllPlayingPlayers.count // I guess you can't use DefeatedPlayers without AllPlayingPlayers =)
        set p = Player(AllPlayingPlayers[i])
        call TriggerRegisterPlayerEvent(t, p, EVENT_PLAYER_DEFEAT)
        set i = i + 1
    endloop
    call TriggerAddAction(t, function on_player_defeated)
endif

endfunction

private module UsingModuleInitialization
    private static method onInit takes nothing returns nothing
        call init()
    endmethod
endmodule

private struct UseModuleInitialization extends array
    implement UsingModuleInitialization
endstruct

endlibrary


examples:

JASS:
local integer i
local integer pn
local trigger t = CreateTrigger()

set i = 1
loop
    exitwhen i > PlayingPlayers.count
    set pn = PlayingPlayers[i]

    // we don't want empty slots, observers nor computers to chat
    call TriggerRegisterPlayerChatEvent(t, Player(pn), "", false)

    set i = i + 1
endloop


JASS:
// [url]http://xgm.guru/p/wc3/intentional-desync-player[/url]
function DesyncPlayer takes integer pn returns nothing
    if LOCAL_PLAYER[pn] then
        call Location(0, 0)
    endif
endfunction

// Russian roulette
call DesyncPlayer(PlayingPlayers[GetRandomInt(1, PlayingPlayers.count)])

JASS:
set i = 1
loop
    exitwhen i > Observers.count
    // we don't want this guy watching our game
    if GetPlayerName(PLAYER[Observers[i]])) == "NSA.creep" then
        call RemovePlayer(PLAYER[Observers[i]], PLAYER_GAME_RESULT_DEFEAT)
    endif
    set i = i + 1
endloop

Note:
It seems that global blocks are parsed regardless if the static if's condition evaluates to true or not.
This means that if one has set a configuration variable (e.g: USING_ComputerPlayers = false)
and tries to use the ComputerPlayers variable, there will NOT be a compile time error but the script
will fail because the variable wouldn't have been initialized! Users beware.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
So first, why this over force

Second, fix the vJASS convention stuff : ).

new -> create
Player_Array -> PlayerArray
node_next -> nodeNext
node_head -> nodeHead
a -> descriptive name
i -> descriptive name (or include a comment for what it's used for)


Poor encapsulation here and more breakage of vJASS conventions : )

JASS:
globals
    Player_Array playing_players // presumably human
    Player_Array computer_players // AI folks
    Player_Array defeated_players // last winners
    Player_Array all_playing_players // playing_players + computer_players - defeated_players
    Player_Array observers // presumably human players who just watch the game, unless skynet
    Player_Array left_the_game_players // computer_players never leave, you can always count on them
    Player_Array all_players // all_playing_players + observers + defeated_players - left_the_game_players
endglobals

on_player_leave -> OnPlayerLeave
on_player_defeated -> OnPlayerDefeated
players_init -> PlayersInit
static method onInit takes nothing returns nothing -> private static method onInit takes nothing returns nothing (it must be private in order for it to be caught as a module initializer, otherwise it will be a struct initializer)


Also, TriggerAddCondition is smarter to use than TriggerAddAction




Nice first attempt ^_^


It would also be good to include an API listing in your library header so that people won't have to navigate to this page or read through your source whenever they want to use your lib. Good documentation that a user can glance at to get what they want out of it is a good first step : ).

I'd like to link you this to help clean up your documentation. You don't need to follow it exactly. Everyone usually takes it and modifies it to suit their own style. I've kind of diverged from it too.

https://github.com/nestharus/JASS/blob/master/jass/Tutorials/comments.txt

It's a good guide in my opinion and will help you write documentation that is easy for the rest of us to read.


Also, thank you for sharing this with THW community Aniki =D. I hope that we can help you grow in kind : ).
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
^Second, fix the vJASS convention stuff : ).

There is nothing to "fix", they're optional.

From the JASS submission guide

Resources must follow convention. Learn more.

http://www.hiveworkshop.com/forums/...80/jpag-jass-proper-application-guide-204383/

It states nothing about using create... but I mean... I don't think Bribe even thought about it because it was so obvious to use create/destroy, lol. We can see about adding that to the JPAG.

Probably also need to add allocate/deallocate to JPAG as well as the "Ex" convention, like createEx or CreateUnitEx. The "Ex" convention has been observed for many, many years, but there is no note of it anywhere.


I do get where you're coming from with new/free, but this isn't C++. Also, shouldn't it be new/delete? : P. free goes with malloc.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
It's more about keeping public API coherent. Had everyone used their own writing method, hive resources would have consistence of spaghetti.

Let's keep it simple. Hive resources included in custom maps by users usually follow JPAG in 100%, especially the most popular ones. Now, consider the fact that after reading so many lines of API written in the same fashion, our random John got used to it.

Let's not make John frustrated about having different API style. And whatever you counter me here with, know this: it is random Johns whom we write public resources for.
 
Level 13
Joined
Nov 7, 2014
Messages
571
So first, why this over force

They use callbacks (code, boolexpr) for iteration, no random access (eg.: you can't iterate them backwards), you
need to iterate (using callbacks) to find out how many players they have (CountPlayersInForceBJ).

static method onInit takes nothing returns nothing -> private static method onInit takes nothing returns nothing (it must be private in order for it to be caught as a module initializer, otherwise it will be a struct initializer)

I see, fair enough.

Second, fix the vJASS convention stuff : ).

Errr, no.

Poor encapsulation here and more breakage of vJASS conventions : )
La La La

Also, TriggerAddCondition is smarter to use than TriggerAddAction

In tight loops sure, but here it doesn't matter.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
They use callbacks (code, boolexpr) for iteration, no random access (eg.: you can't iterate them backwards), you
need to iterate (using callbacks) to find out how many players they have (CountPlayersInForceBJ).



I see, fair enough.



Errr, no.


La La La



In tight loops sure, but here it doesn't matter.


Well, uhm... there's the difference between a resource submitted for use by others and a resource that's made just for yourself. This resource falls into the latter.

I do appreciate you sharing what you made for yourself so that if others want to, they can use it to. I just don't know that something like that can be officially approved : \.


I mean, we do have The Lab. Maybe this should go into The Lab then? It doesn't seem like you really intend to have it be approved =). The Lab would allow everyone to view it and would make it so that you wouldn't have to go through the approval process ^_-. I don't know that anyone will use this, but you did share something rather than hide it away, and I think everyone here would agree that THW wants to encourage that : ). That's what helps keep the community alive and helps us all progress and grow.

I mean if you're going to submit something formally to a community and then not follow that community's convention, going so far as to explicitly say so, then what's to stop that community from outright rejecting your resource? That would be the immediate reaction =). It's like trying to pick a fight. So uhm... maybe that kind of behavior isn't very beneficial to anyone : ).


I mean, you can just say "I'm not going to follow any of your rules and I'm going to submit stuff into the formal sections of the forum" and we can just immediately graveyard whatever you submit for not following the rules, but I don't think that's going to help anyone : (.


The other thing is that there's nothing stopping anyone from using a general collection like a linked list to store players. Plenty of people do this for units instead of using groups already. This just seems like a List implemented using an array that's specialized for players. I could argue beyond usage of conventions and state that the resource doesn't actually fill any need.
 
Level 13
Joined
Nov 7, 2014
Messages
571
The other thing is that there's nothing stopping anyone from using a general collection like a linked list to store players. Plenty of people do this for units instead of using groups already. This just seems like a List implemented using an array that's specialized for players. I could argue beyond usage of conventions and state that the resource doesn't actually fill any need.

I think all "random Johns" / map makers should carefully (that includes both the idea(s) behind the module/library and the implementation) evaluate each module/library that they put in their maps regardless of where they found it (the graveyard, SfYP or from the "officially approved") and decide if it brings any value to their map.
If they decide that something is not useful for their map, then yes they should absolutely not use it.
 
I like this. Can I convince you to change namings a bit? If you are totaly for your variable_like_this convention, it's okay,
but what I mean is the structure order:

Players_User
Players_Computer
Players_Defeated
Players_Observer
Players_Playing
Players_Left
Players_All

... what I suggest is to move the general "Players" keyword in front, and then make a specification after it. That's at least how I personaly prefer it. :)

I am a bit uncertain about the explaination why integers are stored over actual players. It would be more intuitive with players in my eyes. Could we discuss this? It seems like also more work for the user in loop because he always has to think of using the Player() native.

Why is the free function static?

I agree that is fine the counter starts with 1 and not with 0.

What about naming it to PlayerGroupTools or something similar? :)
 
For the books, Aniki and me had private talks, so some points above might be changed. I just make a new post.

library players
->
library PlayerArray

.. start with capital letter, and also a minor specification was good, as there also exist player snippets/tools with a bit other purposes. So to distinguish them, "PlayerArray" library would sound good for me, as an example.

// initializer players_init
No note about this initializer. But anyways, just make it straight forward, and run it I would say. The groups are useful and really could make sense to be set onDefault.
If you plan to really make them only as optional, then classify all related blocks, like the global variables with static if to not generate extra code that is never used.

Again. I really would personaly prefer to see player agents stored. Comparison: call DisplayTextToPlayer(Player(playing_players[1]), 0, 0, "You are first player")
which is less intuitive against:
call DisplayTextToPlayer(playing_players[1], 0, 0, "You are first player")

If you insist, we can go with integers, because the system is still useful, but with loss of useability imo.
I don't know if it would be an option for you to make a constant variable config for to use integers or players. Just maybe a possible alternative.

I would not care you stay with your own conventions privatly, so you can let them for your system-only code, but please make following changes for public usage:

struct Player_Array
->
struct PlayerArray

and optionaly, if globals block stays:

set playing_players = ...
->
set PlayingPlayers = ...

... etc. (for other globals)

Could you move the listed API from above into actual system code?
It does not make relly much difference from here, I know, but when users copy jass snippets, it is good if they directly have everything important, and must not look in hive thread for the API list.

All members and methods that are not meant to be used by the listed API should be private.

free can be a non-static method.

====

I like the resource concept, and think it's useful addition.
 
My calculator would even allow 481 instances, verus 480 ones.^^
But looks very good, and even each default group can be seperatly be set in config. :eek: : )


Resource Comment:

A handy library for easy and efficient use of certain pre-set player arrays, like Comps/Observers/Players..., but also open to allow custom player arrays with a simple management.

Documentation and configuration is fine.

Just remember that the system works with integers, not with players, and you will be fine.

I don't know why I personaly like this so much, because the idea itself is pretty easy actually.. but I think it has a really good usage and just becomes very handy at general init usage. :thumbs_up:

Approved.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Added the ability to detect whether a predefined array has been enabled (previously the arrays weren't initialized to 0):
JASS:
// We can test if the Observers array has been enabled (USING_Observers = true) with:
if Observers != 0 then
    ...
endif

Fixed a destroy method bug (set this.next = -1, after premultiplying) ;P!
 
Level 13
Joined
Nov 7, 2014
Messages
571
If anyone by any chance is using this...

A simplified implementation (not backwards compatible) that supports the newer patches with up to 28 players:

JASS:
library libplayerarray uses libassert

//! novjass

Description:

    A Player_Array is a 0 based array (indices start from 0).
    Player_Array(s) are simply integers that have their bits set if a player (an
    integer between 0 and Max_Player_Id) has been added to them.
    I.e if the players 0, 5, 16, 27 have been added to a Player_Array, then in
    base 2, it would look like: 00001000000000010000000000100001, which is
    134283297 in base 10.

API:

    // creating an empty Player_Array
    local Player_Array pa = 0

    // destroying a Player_Array
    // N/A

    // add player p (p is an integer)
    // Note: a reassignment is required because add cannot update pa
    // (we can't pass the address of pa to add)
    set pa = pa.add(p)

    // remove player p
    // Note: rem also requires reassignment
    set pa = pa.rem(p)

    // test if player p has been added
    local boolean red_in_pa = pa.has(0)

    local integer number_of_players_in_the_array = pa.len

    local integer first_player_in_the_array = pa[0]

Notes:

    Because the operations pa.len (retrieving the number of players in the array) and
    pa[n] (getting the nth player) are not super fast,  I would strongly recommend that
    one should compute them once and store them into local variables:

    local integer p
    local integer a
    local integer aa

    set a = 0
    set aa = pa.len // len computed outside of the loop
    loop
        exitwhen a == aa
        set p = pa[a] // pa[a] called only once inside of the loop

        // use p

        set a = a + 1
    endloop

    There are a few global variables that are created and tracked by the library:

    globals
        Player_Array g_playing_players = 0
        Player_Array g_computer_players = 0
        Player_Array g_observers = 0
        Player_Array g_defeated_players = 0
        Player_Array g_left_the_game_players = 0

        Player_Array g_all_playing_players = 0 // g_playing_players + g_computer_players - g_defeated_players
        Player_Array g_all_players = 0 // g_all_playing_players + g_observers + g_defeated_players - g_left_the_game_players
    endglobals

    these 2 lookup tables are just small optimizations

    globals
        // g_player == Player(p)
        // array lookup vs native function call
        player array g_player

        // g_is_local_player == GetLocalPlayer() == Player(p)
        // array lookup vs native function call(s) and a comparison
        boolean array g_is_local_player
    endglobals

//! endnovjass

globals
    // wc3 supported up to 16 players before patch 1.29, and up to 28 after it
    private constant boolean Patch_129 = true

    private /*constant*/ integer Max_Player_Id // initialized in init()
    private integer array pow2
endglobals

struct Player_Array extends array // we are a value type

    method add takes integer p returns Player_Array
static if DEBUG_MODE then
        call assert("[Player_Array.add] 0 <= p && p <= Max_Player_Id, p: " + I2S(p) + ", Max_Player_Id: " + I2S(Max_Player_Id), /*
        */ 0 <= p && p <= Max_Player_Id)
endif
        //if (this << (31 - p)) >= 0 then
        if (this * pow2[31 - p]) >= 0 then
            //set this = this + (1 << p)
            set this = this + pow2
        endif
        return this
    endmethod

    method rem takes integer p returns Player_Array
static if DEBUG_MODE then
        call assert("[Player_Array.rem] 0 <= p && p <= Max_Player_Id, p: " + I2S(p) + ", Max_Player_Id: " + I2S(Max_Player_Id), /*
        */ 0 <= p && p <= Max_Player_Id)
endif

        //if (this << (31 - p)) < 0 then
        if (this * pow2[31 - p]) < 0 then
            //set this = this - (1 << p)
            set this = this - pow2
        endif
        return this
    endmethod

    method has takes integer p returns boolean
static if DEBUG_MODE then
        call assert("[Player_Array.has] 0 <= p && p <= Max_Player_Id, p: " + I2S(p) + ", Max_Player_Id: " + I2S(Max_Player_Id), /*
        */ 0 <= p && p <= Max_Player_Id)
endif

        //return (this << (31 - p)) < 0
        return this * pow2[31 - p] < 0
    endmethod

    method operator len takes nothing returns integer
        local integer r = 0
        local integer p = 0
        set this = this * pow2[31 - Max_Player_Id]
        loop
            exitwhen p > Max_Player_Id
            if this < 0 then
                set r = r + 1
            endif
            set this = this * 2;
            set p = p + 1
        endloop
        return r
    endmethod

    method operator[] takes integer a returns integer
        local integer b = 0
        local integer c = 0

static if DEBUG_MODE then
        call assert("[Player_Array.operator[]] 0 <= a && a < this.len, a: " + I2S(a) + ", this.len: " + I2S(this.len), /*
        */ 0 <= a && a < this.len)
endif
        loop
            //if (this << (31 - c)) < 0 then
            if this * pow2[31 - c] < 0 then
                if a == b then
                    return c
                else
                    set b = b + 1
                endif
            endif
            set c = c + 1
        endloop
        // unreachable
        return 0x7FFFFFFF
    endmethod

endstruct

globals
    Player_Array g_playing_players = 0 // presumably human
    Player_Array g_computer_players = 0 // AI folks
    Player_Array g_observers = 0
    Player_Array g_defeated_players = 0 // last winners
    Player_Array g_left_the_game_players = 0 // g_computer_players never leave, one can always count on them

    Player_Array g_all_playing_players = 0 // g_playing_players + g_computer_players - g_defeated_players
    Player_Array g_all_players = 0 // g_all_playing_players + g_observers + g_defeated_players - g_left_the_game_players

    player array g_player
    boolean array g_is_local_player
endglobals

private function on_event takes nothing returns nothing
    local eventid ee = GetTriggerEventId()
    local integer p = GetPlayerId(GetTriggerPlayer())

    if ee == EVENT_PLAYER_LEAVE then
        set g_left_the_game_players = g_left_the_game_players.add(p)
        set g_playing_players = g_playing_players.rem(p)
        set g_observers = g_observers.rem(p)
        set g_defeated_players = g_defeated_players.rem(p)
        set g_all_playing_players = g_all_playing_players.rem(p)
        set g_all_players = g_all_players.rem(p)

    elseif ee == EVENT_PLAYER_DEFEAT then
        set g_defeated_players = g_defeated_players.add(p)
        set g_playing_players = g_playing_players.rem(p)

        if IsMapFlagSet(MAP_OBSERVERS_ON_DEATH) /*
        */ and not g_computer_players.has(p) then
            set g_observers = g_observers.add(p)
        endif

        set g_computer_players = g_computer_players.rem(p)
    endif
endfunction

private function init takes nothing returns nothing
    local integer pow
    local integer a
    local integer aa
    local trigger t
    local integer p
    local player pp

static if Patch_129 then
    set Max_Player_Id = GetBJMaxPlayerSlots() - 1
else
    set Max_Player_Id = 16 - 1
endif

    set pow = 1
    set a = 0
    loop
        exitwhen a == 32
        set pow2[a] = pow
        set pow = pow * 2
        set a = a + 1
    endloop

    set p = 0
    loop
        exitwhen p > Max_Player_Id
        set pp = Player(p)

        set g_player = pp
        set g_is_local_player = GetLocalPlayer() == pp

        if p <= Max_Player_Id - 4 then
            if GetPlayerSlotState(pp) == PLAYER_SLOT_STATE_PLAYING then
                if GetPlayerController(pp) == MAP_CONTROL_USER then
                    set g_playing_players = g_playing_players.add(p)
                    set g_all_playing_players = g_all_playing_players.add(p)

                elseif GetPlayerController(pp) == MAP_CONTROL_COMPUTER then
                    set g_computer_players = g_computer_players.add(p)
                    set g_all_playing_players = g_all_playing_players.add(p)
                endif

                set g_all_players = g_all_players.add(p)
            endif

            if IsPlayerObserver(pp) then
                set g_observers = g_observers.add(p)
            endif
        endif

        set p = p + 1
    endloop

    set t = CreateTrigger()
    call TriggerAddAction(t, function on_event)

    // eating our own dog food
    set a = 0
    set aa = g_all_players.len
    loop
        exitwhen a == aa
        set p = g_all_players[a]
        set pp = g_player
        call TriggerRegisterPlayerEvent(t, pp, EVENT_PLAYER_LEAVE)
        call TriggerRegisterPlayerEvent(t, pp, EVENT_PLAYER_DEFEAT)
        set a = a + 1
    endloop
endfunction

private module M
    private static method onInit takes nothing returns nothing
        call init()
    endmethod
endmodule
private struct S
    implement M
endstruct

endlibrary // libplayerarray


library libassert

function assert takes string s, boolean b returns nothing
    if not b then
        call BJDebugMsg("|cffFF0000assert failed: |r" + s)
        call I2S(1/0)
    endif
endfunction

function writeln takes string s returns nothing
    call BJDebugMsg(s)
endfunction

endlibrary
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
The usefulness of this is small. If I need player extension methods, I'd go for PlayerUtils. If I need an array, I'd go for VectorT.
The naming convention you chose is horrible. Does not follow submission rules, thus should be graveyarded.
Your comments are unreadable, your local vars are unreadable.
Given its very limited functionality, generates a lot of globals.
 
Top