JASS: GetLocalPlayer()

Tutorial By PurgeandFire

GetLocalPlayer()

Introduction:

What is GetLocalPlayer()?
constant native GetLocalPlayer      takes nothing returns player


GetLocalPlayer is one of the most useful natives, but it is also one of the most dangerous. GetLocalPlayer is a native that retrieves the current player executing that code at that instance. So, if used correctly, you can perform an action for only one player, and it won't be done for the others.

GetLocalPlayer and Desyncs:

GetLocalPlayer is a function that we call a "Desync Function". This means that it is prone to causing desyncs. Desyncs are errors where, when playing multiplayer, will cause certain players to be disconnected from the map. When something is desynchronized, it is not synchronized with the other players. In this tutorial, I will explain to you how to properly use GetLocalPlayer(), without any problems.

Players in JASS:

In JASS, Player indexes range from 0-11 instead of 1-12.

(0 = 1, 1 = 2, 2 = 3 ... 10 = 11, 11 = 12)

So, you would do something like:
    if GetLocalPlayer() == Player(7) then
    // Player(7) refers to Player 8 (Pink)
endif


Other common ones would be to use:
GetTriggerPlayer()
GetOwningPlayer(GetTriggerUnit())


The first is the Triggering Player and the second is the owner of the triggering unit. You can use any player input that you want, but these are the most common ones, and useful for GUI users to know.

GetLocalPlayer Basic Usage:

GetLocalPlayer can be used to perform an action for a specific player, as stated previously. Now, lets look at a basic example of a GetLocalPlayer block:

function Test takes nothing returns nothing
    if GetLocalPlayer() == Player(0) then
        //actions
    endif
endfunction


Let's break down the function into parts, and show the definitions. Then we'll stitch the definitions together to get a basic concept of the function.

function Test... = The function "Test" takes nothing and returns nothing.
if GetLocalPlayer() = If the player executing this code (local player==player currently executing this code)
== Player(0) then = Is Equal to Player 1 (Red), then
//actions = do these actions
endif/endfunction = . (Period)

So, now lets bring together the function.

"The function "Test" takes nothing and returns nothing. If the player running this trigger is equal to Player 1 (Red), then do these actions.

So, this will perform the actions in that block if the player running the trigger is Player 1 (Red).

Disconnections of Handles:

First, let me show an example:

function Test takes nothing returns nothing
    if GetLocalPlayer() == Player(0) then
        call AddSpecialEffect("none.mdl",0.,0.)
        // Will this desync?
    endif
endfunction


This will desync.
native AddSpecialEffect             takes string modelName, real x, real y returns effect


This function returns effect.
type effect             extends     handle


Effect extends handle.

So you are creating a handle. Creating handles for players will cause desyncs, assuming it is normally-allocated. (for the most part, it will desync)

** No longer applies as of patch 1.24 ** There is a super special function called "Typecasting - Handle to Integer". Handle to Integer will retrieve a certain handle's value. So, let's look at the function:
function H2I takes handle h returns integer
    return h
    return 0
endfunction
//Outdated since this method of typecasting no longer works!

native GetHandleId takes handle h returns integer
//Update for 1.24. Use this instead of H2I.


The old H2I trick. The Jass parser only checks the last return value. So we allow it to return a handle when it is supposed to return an integer. This famous trick was exploited by SuperIKI, followed by Peppar. However, now we have to use [ljass]GetHandleId()[/ljass], which is just as easy to use.

Anyway, we can get the handle value using this function. If the value is greater than 0x100000 (or 1048576, in non-hexadecimal form), it will desync on creation in the block. If you test many handles, you might get something around 1048670, or generally something greater than 1048576 (depending on how many handles exist on the map)... To test to retrieve the value, you can use this:

function Wootage takes nothing returns nothing
    local location L = Location(0,0) //create a handle
    call BJDebugMsg(I2S(GetHandleId(L))) //get its id, display it
    call RemoveLocation(L) //remove it
    set L = null //null for handle id to be recycled
endfunction


And it will show the value in game. But what about "special handles"? Try this:

function Wootage takes nothing returns nothing
    local texttag t = CreateTextTag()
    call BJDebugMsg(I2S(GetHandleId(t)))
    call DestroyTextTag(t)
    set t = null
endfunction


That would probably return 99, unless there are more texttags. Some handles aren't allocated like regular handles. Why 0x100000? Handles are normally allocated with an internal ID of 0x100000. That is where it starts off. Then it progresses on, adding 1 each time. However, things like texttags start off with an id of 99, and progressively go down until they reach 0. Once they reach 0, it will essentially be a null handle, so try to avoid hitting the limit.

Anyway, so if you were to create a unit (which is a normally-allocated handle), it would cause a desync because its HandleId> 0x100000 (1048576). (0x symbolizes a hexadecimal number).

Manipulating the Editor's System:

Say you want to create a special effect for a player. How would you do that?
function Test takes nothing returns nothing
    if GetLocalPlayer() == Player(0) then
        call AddSpecialEffect("war3mapImported\\FX.mdl",0.,0.)
    endif
endfunction


WRONG! You cannot create a special effect because It's handle id > 0x100000 (1048576). So, how would you do that? In many cases, you would show/hide something for a player. Have no fear, because there is an alternative! Special effects don't have a hide/display function, so this is what we'll do!

function Test takes nothing returns nothing
    local string s = ""
//So the path will be nothing, so it won\'t show at all
    if GetLocalPlayer() == Player(0) then
        set s = "war3mapImported\\FX.mdl"
//An actual path, so it <span style=\'font-style:italic;\'>will</span> have a path for that player but not for
//the other players
    endif
    call DestroyEffect(AddSpecialEffect(s,0.,0.))
//Don\'t use this function though, look below real quick to see the proper way to do this without disconnecting
endfunction


Congratulations, you made it correctly! HOWEVER, if this string is used for the first time (as in, have you never used that string before [which means it isn't in the string table]) can cause the string table to become unsynchronized (it enters the string table for only one person), which can also lead to some desyncs:
function Test takes nothing returns nothing
    //this is the proper method
    local string s = "war3mapImported\\FX.mdl"

    if GetLocalPlayer() != Player(0) then
        set s = ""
    endif
    call DestroyEffect(AddSpecialEffect(s,0.,0.))
endfunction


That is the proper method for creating effects locally.

Basic Usage:

Say you have a multiboard, but you only want it for a player? Remember, you can't create it for a player, and they have no path values or whatever, so you can display/hide it whenever!

function Test takes multiboard mb returns nothing
    call MultiboardDisplay(mb,false)
    if GetLocalPlayer() == Player(0) then
        call MultiboardDisplay(mb,true)
    endif


And it will display it only for player 1! Sometimes though, you might run into problems with locally modifying items and what not (it is generally just too tedious), so sometimes it is better to just loop through creating a multiboard, and then display the respective one for the proper player. (hide it for everyone else)

Performing Functions For Forces

Performing functions for forces, also has a technique. Instead of using GetLocalPlayer() over and over again, you can use a function called "IsPlayerInForce()". An example:
function DisplayTextToForce takes force toForce, string message returns nothing
    if (IsPlayerInForce(GetLocalPlayer(), toForce))
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, message)
    endif
endfunction


See this function? The "toForce" area is the force you will perform it for, and the message is the message to be displayed.

IsPlayerInForce checks if a player (argument 1) is in a specified force (argument 2). Since GetLocalPlayer() returns a player, (the player performing the trigger at that moment), you can check if that player is in that force, then it will perform that function to that player. This trigger will run for all players, and those in the force will receive a message, and those outside the force, will receive nothing.

This is a useful technique and can save a lot of time.

Conclusion:

Now you know how to show stuff for only the local player! In GUI, you can also just use custom scripts as blocks to perform things locally. Be careful though, as many GUI functions have hidden things you might not know about!

Credits:


• SuperIKI and Peppar for H2I
• Earth-Fury for the "technical" side of GetLocalPlayer()
• weaaddar for a small H2I definition


Enjoy! :)

Some quotes that might help for some overall knowledge, thanks to phyrex and strilanc:


phyrex1an;722831 wrote:


Second, creating handles is not the only thing that can/will cause a desync. Lets see if I remember them all :):
1. Creating or destroying a handle
2. Use a string for the first time
3. Change the game in any way that affects gameplay (eg, change the move speed of a unit).
4. Assign a new random seed.
5. Pretty much anything that have an effect that you can read later.
6. TriggerSleepAction



1. Creating or destroying a handle/agent with a handle id > 0x100000 will cause desyncs. Basically, within the blocks don't use any Create/Destroy functions, unless it is a texttag since that is one of the exceptions.
2. By "using a string for the first time", it means literally that it is used for the first time. Say you used some string like "Hello" and you never used the string "Hello" before. When a new string is used, it gets stored into a string table. Now, if you enter it for the first time for one player and not the others, it will cause the string table to be unsynched, which could be potentially dangerous and lead to disconnections.
3. Basically, changing stuff that you will actually interact with can just mess up a map. Such as if you were to add health locally, or change move speed, or hide a unit locally. Mostly, tinkering with units or other widgets and whatnot that you will eventually interact with can desync players pretty easily.
4. Assigning a new random seed is essentially this native:
native SetRandomSeed            takes integer seed returns nothing

It is used by CinematicModeExBJ (haven't found it used anywhere else yet) and can cause desyncs in local player blocks.
5. Essentially the same as 3, except a bit more broad since there are probably things besides just tinkering with widgets that can cause a desync in a local player block.
6. TriggerSleepAction() is equivalent to the "Wait" function in GUI. Using this in a local player block will desync.

Strilanc;809404 wrote:

Unlike almost all the other functions, GetLocalPlayer returns a different value for each player. On player 1's computer the function returns player 1, etc. That means you can do things like:
if GetLocalPlayer() == Player(0) then //0 is the first player, player 1
  call CreateUnit(Player(0), 'hfoo', 0, 0, 0) //create a footman at the center
endif

which will create a footman for player 1, but only if you're player 1!

Suppose the game allowed that. Player 1 orders the new footman to attack. All the other players' computers go "huh? order what footman to attack? there is no footman!". So they ignore the command to the non-existent footman and whatever the footman was attacking only dies for player 1. This effect (called desyncing) gets progressively worse as the effects of the footman spread. Eventually player 1 is looking at a game totally different from player 2, and nothing either of them does makes sense to the other.

To avoid that whole song and dance, desynced players are just instantly removed from the game.


** Update: March 5th and March 25th ** : I reformatted a bit of it for the 1.24 update since H2I no longer applies. I've also added "0x" to denote the hexadecimals (0x100000) since I forgot it. It is decently important since I don't want people to be misled into thinking that handles desync when over 100,000 instead of 0x100000. =P Well, usually handles will be either higher than 0x100000 or completely lower.

Click here to comment on this tutorial.
 
 
Blizzard Entertainment, Inc.
Silkroad Online Forums
Team Griffonrawl Trains Muay Thai and MMA fighters in Ohio.
Apex Steel Pipe - Buys and sells Steel Pipe.