- Joined
- Nov 7, 2014
- Messages
- 571
PlayerArray - what
examples:
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.
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: