# Keeping the scoreboard up-to-date

In order to update scoreboard we need to do two things:

1. Write the logic for updating the UI
2. Adding hooks to trigger this logic where necessary

### Writing the client-side logic

We want to be able to add/update a player in the scoreboard when they join and remove a player when they leave.

In `data/CoinCollection/client/gui/scoreBoard.tscript` add the following section to the bottom:

```csharp
//-----------------------------------------------------------------------------
// ScoreBoardGUI data handler methods
//-----------------------------------------------------------------------------

function ScoreBoardGUI::upsertPlayer(%this, %clientID, %name, %score, %kills, %deaths) {
    %text = StripMLControlChars(%name);
    if (%score !$= "null")
        %text = setField(%text, 1, %score);
    %text = setField(%text, 2, %kills);
    %text = setField(%text, 3, %deaths);

    // Update or add the player to the control
    if (ScoreBoardGUIList.getRowNumById(%clientId) == -1)
        ScoreBoardGUIList.addRow(%clientId, %text);
    else ScoreBoardGUIList.setRowById(%clientId, %text);

    // Sorts by score
    ScoreBoardGUIList.sortNumerical(1, false);
    ScoreBoardGUIList.clearSelection();
}

function ScoreBoardGUI::removePlayer(%this, %clientId) {
    PlayerListGuiList.removeRowById(%clientId);
}
```

So lets see what this does.  The `upsertPlayer` function first creates a new variable `%text` which stores name, score, kills and deaths with columns seperated by tabs. **Then** we check if there is already stored a row with that id. If not, then we add a new row else we update the row with that id. **Finally** we sort the list by column 1 (the second column because it starts at 0).&#x20;

The `removePlayer` function simply removes the row with the player's ID.

In order to trigger these two functions we will use the network message system. Let's add the client-side listeners right away in the top of the `scoreBoard.tscript` file:

```csharp
//-----------------------------------------------------------------------------
// Callbacks
//-----------------------------------------------------------------------------
addMessageCallback('MsgClientWelcome', SBGUIWelcome);
addMessageCallback('MsgClientJoin', SBGUIPlayerJoined);
addMessageCallback('MsgClientDrop', SBGUIPlayerLeft);
addMessageCallback('MsgClientScoreChanged', SBGUIScoreChanged);

function SBGUIWelcome(%msgType, %msgString, %clientName, %clientId,
        %isAI, %isAdmin, %isSuperAdmin) {
    ScoreBoardGUI.clear();
    ScoreBoardGUI.upsertPlayer(%clientId, detag(%clientName), 0, 0, 0);
}

function SBGUIPlayerJoined(%msgType, %msgString, %clientName,
        %clientId, %score, %kills,
        %deaths, %isAI, %isAdmin, %isSuperAdmin) {
    ScoreBoardGUI.upsertPlayer(%clientId, detag(%clientName), %score, %kills, %deaths);
}

function SBGUIPlayerLeft(%msgType, %msgString, %clientName, %clientId) {
    ScoreBoardGUI.removePlayer(%clientId);
}

function SBGUIScoreChanged(%msgType, %msgString, %clientName,
        %clientId, %score, %kills, %deaths) {
    ScoreBoardGUI.upsertPlayer(%clientId, detag(%clientName), %score, %kills, %deaths);
}
```

{% hint style="info" %}
Here we use `detag` on the clientName, that is because the clientName is a tagged string, so we need to look up the clientName ID to get the string.
{% endhint %}

### Adding the hooks on the server

We have four different callbacks we need to implement. Let's start with the callback `MsgClientScoreChanged`, we will trigger that one whenever we pick up a coin. In `data/CoinCollection/server/coin.tscript` add:

```csharp
messageAll('MsgClientScoreChanged', -1, %col.client.playername,
    %col.client, %col.client.coinsFound,
    %col.client.kills, %col.client.deaths);
```

Right after:

```csharp
%col.client.coinsFound++; // Automatically starts at 0
```

The three other hooks should happen whenever a client enters or leaves the game. So let's open up `data/CoinCollection/gamemode.tscript` and change `onClientEnterGame` and add `onClientLeaveGame` :

```csharp
function CoinCollectionGameMode::onClientEnterGame(%this, %client) {
    //Set the player name based on the client's connection data
    %client.setPlayerName(%client.connectData);

    %this.spawnControlObject(%client);

    // Welcome the client
    messageClient(
            %client, 'MsgClientWelcome',
                    "\c2Welcome to the Torque demo app %1.",
                    %client.playername,
                    %client,
                    %client.isAiControlled()
            );

    // Inform the client about everyone else
    foreach(%other in ClientGroup) {
        if (%other == %client) {
            continue;
        }

        messageClient(%client, 'MsgClientJoin', -1,
                    %other.playername,
                    %other, %other.coinsFound,
                    %other.kills, %other.deaths);
    }

    // Inform everyone else about the client
    messageAllExcept(
            %client,
                    "-1",
                    'MsgClientJoin',
                    "\c1 % 1 joined the game.",
                    %client.playername,
                    %client,
                    %client.coinsFound,
                    %client.kills, %client.deaths,
                    %client.isAiControlled()
            );
}

function CoinCollectionGameMode::onClientLeaveGame(%this, %client) {
    // Inform everyone that the client left
    messageAllExcept(
            %client,
                    "-1",
                    'MsgClientDrop',
                    "\c1 % 1 left the game.",
                    %client.playername,
                    %client,
                    %client.isAiControlled()
            );
}

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.torque3d.org/getting-started/getting-familiar/your-first-game/keeping-the-scoreboard-up-to-date.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
