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

[General] GetLocalPlayer() and Strings

Status
Not open for further replies.
Level 12
Joined
Jan 10, 2023
Messages
191
Hello,

So the question is, do strings have any difficulty with GetLocalPlayer()?

I'm wondering because I heard that when a string is created, if it is a new string, it takes up some memory, but a used string will simply refer to that memory.

So if one player has a string that others don't, then that player refers to a string while other players make a new one?

Not sure if that would cause a desync, I'm leaning toward no but I wanted to be safe...

Thanks in advance
 
Level 12
Joined
Jan 10, 2023
Messages
191
Well you'd wish there is a simple answer but the answer it that it depends what will you do with that string.
The string will never be seen by anyone except the local player, the only concern is if a string exists already for me, and then is created for all globally, does that conflict?

I think I'm under a false impression, but I was worried if a global string comes along that matches a string that is known locally by one player by coincidence, would that one player would say "I know that string" and another player would say "look a new string", would that happen/ desync?
 
The string will never be seen by anyone except the local player, the only concern is if a string exists already for me, and then is created for all globally, does that conflict?

I think I'm under a false impression, but I was worried if a global string comes along that matches a string that is known locally by one player by coincidence, would that one player would say "I know that string" and another player would say "look a new string", would that happen/ desync?
I'm pretty sure that the string in and of itself is safe.
You can for example print it to local player or set an existing floating-text's text to it.

But what you say is correct for any "handle" types, but strings are kind of primitive.
Yes, there is a text-table that takes up some memory etc, but honestly, I don't really care, it's fine.
I use "dynamic tooltip" in my map (showing actual damage-values for spells with damage-scaling on stats) and sure, I spam strings, but with 64-bit PCs and basically everyone have over 8gb of ram, it's nothing.
 
Level 12
Joined
Jan 10, 2023
Messages
191
Thank you both! So I have been working on a chat system for my game. I know there are many out there, but I am new to JASS and thought I'd make a simple chat system for my own use and I thought it would be good practice.

The system I came up with works great until someone sends a message and then it desyncs all but host. The difficult thing to me is that the system is constantly sending self-sent messages to each player and it allows players to receive their proxy message, but if you send a message of your own, then it desyncs.

To make that a little more clear, My screen would continuously show:
"[Observers] Tristronic: "
and my friends would show:
"[Observers] FriendOfTristronic: "

These messages are resent every 0.01 seconds (I know this amount of time is overkill, it isn't the point of my question and all I will say is that I like to test at extremes)

When I say "Hello World" mine should update to:
"[Observers] Tristronic: Hello World"
and my friends should still show:
"[Observers] FriendOfTristronic: "

And it does, and about 2 seconds later "FriendOfTristonic" is desynced.

Last important thing to know is that I have imported my own "war3mapSkin.txt" fill which destroys all chats except Observer Chat:
war3mapSkin.txt:
[FrameDef]
CHAT_RECIPIENT_ALL= (380 spaces)
CHAT_RECIPIENT_ALLIES= (380 spaces)
CHAT_RECIPIENT_OBSERVERS=[
CHAT_RECIPIENT_REFEREES= (380 spaces)
CHAT_RECIPIENT_PRIVATE= (380 spaces)
Without further ado, here's the full JASS, I have the whole thing in the map header except a call to initiate the chat.
JASS:
library CommandLineChat

    globals
        // These will be used with GetLocalPlayer
        private    boolean    array    cDisabled
        private    boolean    array    cSilenced
        private    boolean    array    cAutoTime
        private    integer    array    cDefaultTicks
        private    integer    array    cTicks
        private    player    array    cSender
        private    string    array    cPrefixTag
        private    string    array    cChatEntry
        private    string    array    cSuffixTag
        private    string    array    cChatLabel
        private    string    array    cChatFinal
        private    string    array    cChatClose
    endglobals

///////////////////////////////////////////////////////////////////////////////////////////////
    private function CLC_SendBlank takes player p returns nothing
        local integer i = GetPlayerId( p )
        if GetLocalPlayer() == p then
            call BlzDisplayChatMessage( p, 0, cChatLabel[i] + cChatFinal[i] + cChatClose[i] )
        endif
    endfunction

    private function CLC_SendString takes player sender, player p, string s returns nothing
        local integer i = GetPlayerId( p )
        if GetLocalPlayer() == p then
            call BlzDisplayChatMessage( sender, 2, cChatLabel[i] + cChatFinal[i] + cChatClose[i] )
        endif
    endfunction
///////////////////////////////////////////////////////////////////////////////////////////////
    function CLC_RefreshPlayerChat takes integer p returns nothing
        local string s
        if cDisabled[p] == true then
            call CLC_SendBlank( Player( p ) )
        else
            set s = cChatLabel[p] + cChatFinal[p] + cChatClose[p]
            call CLC_SendString( cSender[p], Player( p ), s )
        endif
    endfunction

    function CLC_ClearPlayerChat takes integer p returns nothing
        set cChatFinal[p] = ""
        call CLC_RefreshPlayerChat( p )
    endfunction

    function CLC_RefreshAllChat takes nothing returns nothing
        local integer i = 0
        loop
            call CLC_RefreshPlayerChat( i )
            set i = i + 1
            exitwhen i == bj_MAX_PLAYERS
        endloop
    endfunction
///////////////////////////////////////////////////////////////////////////////////////////////
    private function CLC_Tick takes nothing returns nothing
        local integer i = 0
        loop
            set cTicks[i] = cTicks[i] - 1
            if cTicks[i] <= 0 then
                set cTicks[i] = 0
                set cChatFinal[i] = ""
            endif
            call CLC_RefreshPlayerChat( i )
            set i = i + 1
            exitwhen i == bj_MAX_PLAYERS
        endloop
    endfunction
///////////////////////////////////////////////////////////////////////////////////////////////
    function CLC_PostString takes integer sender, integer target, string s returns nothing
        if cAutoTime[target] == true then
            set cTicks[target] = StringLength( s ) * 3 + 141
        else
            set cTicks[target] = cDefaultTicks[target]
        endif
        set cSender[target] = Player( sender )
        set cChatEntry[target] = s
        set cChatFinal[target] = cPrefixTag[target] + s + cSuffixTag[target]
        call CLC_RefreshPlayerChat( target )
    endfunction
///////////////////////////////////////////////////////////////////////////////////////////////
    private function CLC_TagChat takes nothing returns nothing
        local integer p = GetPlayerId( GetTriggerPlayer() )
        local string s = GetEventPlayerChatString()
        if cSilenced[p] ==  false then
            call CLC_PostString( p, p, s )
        else
            set cChatEntry[p] = GetEventPlayerChatString()
        endif
    endfunction

    function CLC_ResetPlayerChat takes integer p returns nothing
        call CLC_PostString( p, p, "" )
    endfunction
///////////////////////////////////////////////////////////////////////////////////////////////
    function CLC_SetPlayerLabel takes integer p, string s returns nothing
        set cChatLabel[p] = s
    endfunction

    function CLC_AddPlayerLabelLb takes integer p returns nothing
        set cChatLabel[p] = cChatLabel[p] + "\n"
    endfunction

    function CLC_SetPlayerPrefixTag takes integer p, string s returns nothing
        set cPrefixTag[p] = s
        set cChatFinal[p] = cPrefixTag[p] + s + cSuffixTag[p]
    endfunction

    function CLC_SetPlayerSuffixTag takes integer p, string s returns nothing
        set cSuffixTag[p] = s
        set cChatFinal[p] = cPrefixTag[p] + s + cSuffixTag[p]
    endfunction

    function CLC_SetPlayerClose takes integer p, string s returns nothing
        set cChatClose[p] = s
    endfunction
///////////////////////////////////////////////////////////////////////////////////////////////
    function CLC_DisablePlayer takes integer p, boolean b returns nothing
        set cDisabled[p] = b
        call CLC_RefreshAllChat()
    endfunction

    function CLC_SilencePlayer takes integer p, boolean b returns nothing
        set cSilenced[p] = b
    endfunction
///////////////////////////////////////////////////////////////////////////////////////////////
    function CLC_EnablePlayerAutoTime takes integer p, boolean b returns nothing
        set cAutoTime[p] = b
    endfunction

    function CLC_SetPlayerDefaultTicks takes integer p, integer t returns nothing
        set cDefaultTicks[p] = t
    endfunction
///////////////////////////////////////////////////////////////////////////////////////////////
    //     The top-left corner of screen is coordinate ( 0.00, 0.00 )
    // The bottom-right corner of screen is coordinate ( 0.80, 0.60 )
    // Only monitors with resolution wider than 8:6 will see the chat
    // if xcoor < 0.00 or xcoor > 0.80
    ///////////////////////////////////////////////////////////////////////////////////////////
    // player width is self explanatory.
    //
    // x and y represent the position of the window with the top-left corner
    // of the single chat field being x and y and the top-left corner of the
    // the minimum x and y.
    // This uses the frame coordinate system where 0.0 x is left of standard
    // resolution, for compatibility, stay within an x value of (0.00-0.80).
    ///////////////////////////////////////////////////////////////////////////////////////////
    // To be fully visible:
    // ( 0 <= |w| + |x| <= 0.800000000 ) \
    // ( 0 <=    |x|    <= 0.800000000 ) --->  These values should not be more accurate
    // ( 0 <=    |y|    <= 0.587625000 ) /     than 9 decimal places or a crash may occur.
    //
    // If y = 0.6 it is off-screen, the exact buffer I have not tested
    // the single chat line works better with a buffer below it anyway
    // because a string that's more than one line will need some space.
    ///////////////////////////////////////////////////////////////////////////////////////////
    // A width of < 0.005 will display "........" with each dot having its own line
    // (this will include the text "> Player 1:" in the message).
    //
    // A width of 0 will be infinite width (one line that shows the whole entered text.
    ///////////////////////////////////////////////////////////////////////////////////////////
    // x of 1.0 should safely be off screen and not visible
    // ( 0.0 <= x <= 2.0 has been tested and is safe )
    // x of 2.0 has proven safe to use in my testing
    //
    // y of 0.6 will be below the screen and not visible
    // ( 0.0 <= y <= 0.6 has been tested and is safe )
    //
    // As long as none of these values are negative the game will not crash, so as a protective measure:
    ///////////////////////////////////////////////////////////////////////////////////////////
    function CLC_SetPlayerChatWindow takes player p, real w, real x, real y returns nothing
        if w < 0 then
            set w = -w
        endif
        if x < 0 then
            set x = -x
        endif
        if y < 0 then
            set y = -y
        endif
        if GetLocalPlayer() == p then
            call BlzFrameClearAllPoints( BlzGetOriginFrame( ORIGIN_FRAME_CHAT_MSG, 0 ) )
            call BlzFrameSetAbsPoint( BlzGetOriginFrame( ORIGIN_FRAME_CHAT_MSG, 0 ), FRAMEPOINT_BOTTOMLEFT, 0.0 + x,     0.587000039 - y )
            call BlzFrameSetAbsPoint( BlzGetOriginFrame( ORIGIN_FRAME_CHAT_MSG, 0 ), FRAMEPOINT_TOPRIGHT,   0.0 + x + w, 0.600000000 - y )
        endif
    endfunction
///////////////////////////////////////////////////////////////////////////////////////////////
    // Can you tell I hate retyping events?
    // Also makes sure that this will work
    // whether the max players is 12 or 24.
    //////////////////////////////////////////////////////////////////////
    function TriggerRegisterGenericPlayerEvent takes trigger t, playerevent e returns nothing
        local integer i = 0
        loop
            call TriggerRegisterPlayerEvent( t, Player( i ), e )
            set i = i + 1
            exitwhen i == bj_MAX_PLAYERS
        endloop
    endfunction

    function TriggerRegisterGenericChatEvent takes trigger t, string s, boolean b returns nothing
        local integer i = 0
        loop
            call TriggerRegisterPlayerChatEvent( t, Player( i ), s, b )
            set i = i + 1
            exitwhen i == bj_MAX_PLAYERS
        endloop
    endfunction
///////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////
    // Fire away and forget, w is width; ( x, y ) are coordinates. All
    // of them use Frame coordinate ranges ( 0.0, 0.0 ) to ( 0.8, 0.6 )
    //
    // chatLabel, chatPrefix, chatSuffix, and chatClose are not counted
    // for the string length with auto timed messages.
    //
    // chatLabel and chatClose will persist when the message is blank
    // chatPrefix and chat Suffix will disappear with the message.
    //////////////////////////////////////////////////////////////////////
    function CLC_Init takes real w, real x, real y, boolean autoTime, string chatLabel, string chatPrefix, string chatSuffix, string chatClose returns nothing
        local integer i = 0
        local timer T = CreateTimer(  )
        local trigger t = CreateTrigger(  )
        call TriggerRegisterGenericPlayerEvent( t, ConvertPlayerEvent( 16 ) )
        call TriggerAddAction( t, function CLC_RefreshAllChat )
        set t = CreateTrigger(  )
        call TriggerRegisterGenericChatEvent( t, "", true )
        call TriggerAddAction( t, function CLC_TagChat )
        call TimerStart( T, .02, true, function CLC_Tick )

        loop
            set cDisabled[i] = false
            set cSilenced[i] = false
            set cAutoTime[i] = autoTime
            set cDefaultTicks[i] = 20
            set cTicks[i] = 0
            set cAutoTime[i] = autoTime
            set cChatLabel[i] = chatLabel
            set cPrefixTag[i] = chatPrefix
            set cSuffixTag[i] = chatSuffix
            set cChatClose[i] = chatClose
            set cSender[i] = Player( i )
            call CLC_SetPlayerChatWindow( Player ( i ), w, x, y )
            set i = i + 1
            exitwhen i == bj_MAX_PLAYERS
        endloop
        call CLC_Tick()
    endfunction

endlibrary

Before I had any trouble, I had written the library with a lot more use of the local player and I reworked it to be the above in fear that the local storage was causing the problems. After testing, it seems to be something to do specifically with calling "BlzDisplayChatMessage" in a local context, but in most cases there is no desync when calling it.

As you can see in the code, BlzDisplayChatMessage is being called a lot locally for each player, but only when a player uses CLC_TagChat there is a desync. The only difference I can see happening here is the length of the messages contributing to the messages having different timers.

My plan is to 'format' the inbound chat so that all strings are equal string length by adding "|c00000000" and then a "." until the string is the maximum chat string length. Unfortunately I ran out of time with my 'tester' and so I am hoping to come up with a new beta-version to test the next chance I get.
 
Last edited:
I guess that you're using latest version?
Regarding timers, you only have 1 timer that ticks-away and does some stuff depending on values, right? I don't think that primitives having different values in and of themselves is the issue, but I could be wrong... You need to be careful in local-blocks and I haven't really studied the details...

1.31 does not work with lines longer than 255 chars, and you have a few ~500 chars-lines with spaces. Don't think this is a problem, but why so many spaces?
I've heard that there would be issues with strings longer than 1024 or so chars, but I haven't tested it myself. Your strings is shorter than that, right?

Anyways, I wouldn't be surprised if it depended on to whom you sent the message. If the "other party" would receive it but doesn't, then it could desync, maybe? I.E. if the code ends up in the CLC_SendBlank-function, you send to all players, but you only do it for the local player?
From what I read, values of 3+ are "private", and should result in only yourself?

I'm unsure about latest version, but in 1.31 you can start multiple instances of Warcraft 3 to try out basic stuff out, connecting via LAN. It's hard to test mouse natives on 2 instances on the same computer though...

I have never used BlzDisplayChatMessage, so I had to look it up to read it. I'd probably go for named constants instead of magic-constants ("random" numbers such as 0, or 2), instead use constant integer ALL_CHAT_INTEGER = 0 and constant integer OBSERVER_INTEGER = 2 or something like that.

Edit: One idea is to make it 100% safe is to call the natives for all players, but give them different string-values (I.E. empty string vs actual string)
 
Last edited:
Level 12
Joined
Jan 10, 2023
Messages
191
I guess that you're using latest version?
Yes, my bad, I should get used to stating that.

I know you said you have used BlzDisplayChatMessage so there's one 'funny' thing about it you should know that will make my functions more clear. Thank you a ton for the help! I figure I need you to know this to help me better and for anyone else that may come along:
Anyways, I wouldn't be surprised if it depended on to whom you sent the message. If the "other party" would receive it but doesn't, then it could desync, maybe? I.E. if the code ends up in the CLC_SendBlank-function, you send to all players, but you only do it for the local player?
From what I read, values of 3+ are "private", and should result in only yourself?
BlzDisplayChatMessage sends the message to all players no matter what chat constant you place as an argument. The only difference the argument seems to make is the caption that comes before the player's name such as "[All], [Allies], [Observers], and [Private]".
Apparently "[Referee]" has no constant that can be input to send synthetic chat on this channel... I guess that's like pretending to be a cop lol.

Whenever you see a function send on channel 0 or to "[All]" players, it's sending a blanked message using war3mapSkin.txt to all players
  • This .txt file lets you set those captions "[All], [Allies], [Observers], [Private], and [Referees]" to whatever you want.
  • If you set the caption to 380+ spaces, it will show a blank message, but it will bump all chat strings like it would if, but even the player name will have been 'pushed' out of the message.
The Observer channel and the Referee channel are the only ones I didn't blank out.
A normal message will still bump other messages on the selected chat channel whenever a player enters one and that is why I am continuously resending either the blank or the most recent message, to bump all other messages out of the way and make the wanted message 'stick'.
The timer then comes in by erasing the string when it is 'expired'.

Regarding timers, you only have 1 timer that ticks-away and does some stuff depending on values, right? I don't think that primitives having different values in and of themselves is the issue, but I could be wrong... You need to be careful in local-blocks and I haven't really studied the details...
Yes, that's the only timer. I'm feeling pretty safe about this one too, I had a former version that was running this trigger fine, but instead of having any global arrays, I had non-arrayed global variables each with individual local values per player, and under those circumstances the timer and CLC_Tick were working without desync, but when a player entered something, the desync seemed to be pending on that, 2-3 seconds after entering the chat each time.

What is interesting to me about the timer and CLC_Tick is that these are always firing the chain of functions that sends the chat message for each player. It seems to me that this function/ the timer proves that the issue isn't with BlzDisplayChatMessage, and that made me think that the only other difference is the auto-time that the game is making for the messages when the players get unique messages for the first time.

Scenario A:
As in maybe it's perfectly fine with sending me the message
"[Observers] Tristronic: "
And sending you the message
"[Observers] ThompZon: "

Scenario B:
But it desyncs if the message is
"[Observers] Tristronic: THANOS NO!!!!"
And sending you the message
"[Observers] ThompZon: Mr. Stark I don't feel so good."

Scenario A is happy because our messages are 'identical'.
  • But they really aren't identical, we got away with using two different names.
  • Seems to mean the name is separate from the string, which makes sense
  • This would suggest that different senders is safe
Scenario B is the desync scenario as made apparent by the pop-culture reference.
  • Seems to be caused when players have different message content.
  • Seems to me this should be safe, players have differing UI and strings are like nothing
  • The only remaining difference I see is the auto time that the game is assigning to the chat string
  • Seems to me this should be safe too because the players shouldn't need to share this information, but I am at a loss..

1.31 does not work with lines longer than 255 chars, and you have a few ~500 chars-lines with spaces. Don't think this is a problem, but why so many spaces?
I've heard that there would be issues with strings longer than 1024 or so chars, but I haven't tested it myself. Your strings is shorter than that, right?
The spaces I actually forgot about and were not present at the time of testing. Along with my timer theory, I rushed a hackneyed solution of adding a ton of space at the end. I was hoping in vain that the game would truncate them to a maximum lengthvhopefully causing all of the chat strings to have an equal length and duration, because I was losing the patience of my help.

I'll clean that up now, thanks for pointing that out.

I'm unsure about latest version, but in 1.31 you can start multiple instances of Warcraft 3 to try out basic stuff out, connecting via LAN. It's hard to test mouse natives on 2 instances on the same computer though...
Well who needs friends anyway! lol, JOKING, but this will save me and my buddy some grief. Glad to learn that, thank you for sharing!!

I have never used BlzDisplayChatMessage, so I had to look it up to read it. I'd probably go for named constants instead of magic-constants ("random" numbers such as 0, or 2), instead use constant integer ALL_CHAT_INTEGER = 0 and constant integer OBSERVER_INTEGER = 2 or something like that.
Is this a visual thing for readability/ asking for help? I never thought of it until now but I guess it made you have to look up the function, so that's a good point, I'll try to remember that in similar situations when I have a constant like that to use.

Edit: One idea is to make it 100% safe is to call the natives for all players, but give them different string-values (I.E. empty string vs actual string)
I think this is the idea, I was thinking about doing this instead:
(a crude example that I think will make sense given the context)
This:
JASS:
function ieSendToAll takes nothing returns nothing
    local integer i = GetPlayerId( GetLocalPlayer() )
    call BlzDisplayChatMessage( cSender[i], 2, cChatLabel[i] + cChatFinal[i] + cChatClose[i] )
endfunction
Instead of this:
JASS:
function ieSendToOne takes player returns nothing
    local integer i = GetPlayerId( p )
    if GetLocalPlayer() == p then
        call BlzDisplayChatMessage( cSender[i], 2, cChatLabel[GetPlayer] + cChatFinal[i] + cChatClose[i] )
    endif
endfunction
If that doesn't work, I had one or two more ideas and then I'm going to drop the idea. I liked the idea because this chat window is 'unclearable' with the typical clear message function, so it could be convenient for someone who doesn't want to mess much with frames... but if it loses too much simplicity it will lose all appeal...

Thanks again! I really appreciate it.
 
Last edited:
Ohh my, I'll read it all tomorrow, but I want to quickly answer:
Is this a visual thing for readability/ asking for help? I never thought of it until now but I guess it made you have to look up the function, so that's a good point, I'll try to remember that in similar situations when I have a constant like that to use.
This is only for code-readability and a "style" thing. As with any style thing, there are different opinions etc. Anyways, if I see a 0, I have to know what it means in this context (requiring some amount of brain-power), if I see ALL_PLAYERS, have more guidance on what it means and if you come back to the code a month later or two, you'll be happier with named-constants than with "random numbers".

Thanks again! I really appreciate it.
No problem, it's fun when people are enthusiastic and seem to want to learn :)
 
Level 12
Joined
Jan 10, 2023
Messages
191
As far as I am aware strings need to be created on all clients (not locally). What you then do with the strings might be client local compatible, such as setting a special effect model path.
Thank you, that is consistent with what I have found in the past few days.

It seems the game is perfectly fine with sending chat messages locally, but I must have been messing something else up I'm basically starting fresh and testing as I go now. I was assigning a lot of private global variables locally and I'm not sure if that was a mistake, but I started a new concept for the system with a synthetic (unseen) 'chat log' for each player that is updated when a chat is accepted by the system, or if manually added by the system.

The 'chat log' consists of 4 arrays:
- A string array for storing each message
- A player array for storing each message's sender
- An integer array for storing each message's expiration counter
- A boolean array for storing each message's visibility
- Each players 'chat log' is represented by its own interval within each array.
(player 1 is index 0-340, player 2 is 341-682, et c; for 24 players, doubled if 12 players)

Now that I have all of the info globally shared, I am more optimistic.

An explanation for the above changes:
One of the tricks is that the chat needs to be refreshed whenever a player enters a message because this system relies on breaking the [All], [Allies], and [Private] chats with a war3mapSkin.txt file, but even when these chats are made invisible, a sent message at least causes a linebreak.

To circumvent this:
  1. Every time a chat is entered the chat log is resent per player and the garbage chat is bumped out of the window - using ConvertPlayerEvent(16) because ConvertPlayerEvent(96) is slow and causes a very poor visual performance.
  2. The string is later captured using ConvertPlayerEvent(96) and added to the log of each player who is enabled to receive it, then the chat logs are resent per player.
This solution causes another problem; the resent messages have new expiration timers.
To circumvent this:
  • Each log entry has a counter that is periodically reduced by a timer.
  • When the counter reaches zero for an item on the chat log it is made invisible.
  • 'Invisible' messages are sent on the [All] channel, which will cause a linebreak.
  • All messages are given the same amount of time, like standard chat
    • This prevents older messages from outlasting newer messages
  • Messages do not fade away, they just disappear.
I'll share my final solution here for the heck of it and will be accepting any tips if anyone has any based on the code they've seen.

Still learning a lot, I like to turn a project into a learning exercise so I'm all ears.
 
Status
Not open for further replies.
Top