Welcome, Guest! Login | Register

Scoreboard [Print this Article]
Posted by: omega
Date posted: Jan 06 2005
User Rating: 4.8 out of 5.0
Number of views: 16559
Number of comments: 18
Description: Setting up a basic scoreboard (with teams!) in the HL2 FROM SCRATCH MP SDK.
There's a lot of copy and paste code here, but note: make sure you check over everything and read the comments BEFORE you start blindly pasting, or you may royally screw up your scoreboard :P

with that said..
This can be optomized a bit, using a struct for the team info, but when i did it, i was doing one piece at a time, so i haven't re-written mine yet.
there are a few things still not done with this scoreboard. 1) if you add playerclass (i've commented out everything regarding class, for this article), it will show all teams everyones playerclass (easy enough to fix) 2) you can see your own voice icon and muting isn't added yet.

The major changes to the scoreboard file are as follows:

shareddefs.h
------------

===============================================================================================
i changed my team defines to an enum, like so:
===============================================================================================
 CODE (c++) 

enum eteams_list
{
    TEAM_UNASSIGNED = 0,    // not assigned to a team
    TEAM_SPECTATOR,
    TEAM_REBELS,
    TEAM_COMMANDOS,
    TEAMS_COUNT
};


ClientScoreBoardDialog.h
------------------------

===============================================================================================
i added another 2 types, but note that only one of the new ones is used currently:
===============================================================================================
 CODE (c++) 

#define TYPE_NOTEAM         0   // NOTEAM must be zero :)
#define TYPE_TEAM           1   // a section for a single team  
#define TYPE_SPECTATORS     2   // a section for a spectator group
#define TYPE_BLANK          3
#define TYPE_HEADER         4
#define TYPE_UNASSIGNED     5


===============================================================================================
add the following variables to the class:
===============================================================================================
 CODE (c++) 

private:
    int iTeamSections[TEAMS_COUNT];     //store off the section id's of each team
    int numPlayersOnTeam[TEAMS_COUNT];  //store the player counts
    int teamLatency[TEAMS_COUNT];       //i suppose we could just make a single struct for all this info ;)

===============================================================================================
and change AddSection to:
===============================================================================================
 CODE (c++) 
    virtual int AddSection(int teamType, int teamNumber); // add a new section header for a team



ClientScoreBoardDialog.cpp
--------------------------

 CODE (c++) 
#include "sdk_gamerules.h" //include before memdbgon.h


uncomment memset(s_VoiceImage, 0x0, sizeof( s_VoiceImage )); in the scoreboard constructor

===============================================================================================
add AddHeader(); into the InitScoreboardSections() function:
===============================================================================================
 CODE (c++) 
void CClientScoreBoardDialog::InitScoreboardSections()
{
    AddHeader(); //omega; create the sections and header
}


===============================================================================================
modify ApplySchemeSettings so that the voice icon stuff is being setup
===============================================================================================
 CODE (c++) 
void CClientScoreBoardDialog::ApplySchemeSettings( IScheme *pScheme )
{
    BaseClass::ApplySchemeSettings( pScheme );
    ImageList *imageList = new ImageList(false);

    //omega; add the voice images
    s_VoiceImage[0] = 0;    // index 0 is always blank
    s_VoiceImage[CVoiceStatus::VOICE_NEVERSPOKEN] = imageList->AddImage(scheme()->GetImage("speaker1", true));
    s_VoiceImage[CVoiceStatus::VOICE_NOTTALKING] = imageList->AddImage(scheme()->GetImage("speaker2", true));
    s_VoiceImage[CVoiceStatus::VOICE_TALKING] = imageList->AddImage(scheme()->GetImage( "speaker3", true));
    s_VoiceImage[CVoiceStatus::VOICE_BANNED] = imageList->AddImage(scheme()->GetImage("voiceblocked", true));
   
    // resize the images to our resolution
<<---SNIP--->>


===============================================================================================
you will need to modify this, and you likely won't need any of the attacking/defending stuff unless you're copying flf ;p
===============================================================================================
 CODE (c++) 
//-----------------------------------------------------------------------------
// Purpose: Sort all the teams
//-----------------------------------------------------------------------------
void CClientScoreBoardDialog::UpdateTeamInfo()
{
    //omega; update the TEAM_REBELS m_iSectionId and the TEAM_COMMANDOS m_iSectionId
    int sectionId;
    int i;
    char cstring[256];
    char plural[2];
    wchar_t tn[256];
    IGameResources *gr = GameResources();
    if ( !gr )
        return;

    for (i=TEAM_UNASSIGNED;i<TEAMS_COUNT;i++)
    {
        sectionId = iTeamSections[i]; //get the section for the team

        if (numPlayersOnTeam[i] == 1)
            sprintf(plural,"");
        else
            sprintf(plural,"s");

        sprintf(cstring, "%s - (%i player%s)", gr->GetTeamName(i),  numPlayersOnTeam[i], plural);
        localize()->ConvertANSIToUnicode(cstring, tn, sizeof(tn));
        m_pPlayerList->ModifyColumn(sectionId, "name", tn);

        if ( numPlayersOnTeam[i] > 0 )
            teamLatency[i] /= numPlayersOnTeam[i];
        else
            teamLatency[i] = 0;

        wchar_t sz[6];
        swprintf(sz, L"%d", gr->GetTeamScore(i));
        m_pPlayerList->ModifyColumn(sectionId, "score", sz);
        if (teamLatency[i] < 1)
        {
            m_pPlayerList->ModifyColumn(sectionId, "ping", L"");
        }
        else
        {
            swprintf(sz, L"%i", teamLatency[i]);
            m_pPlayerList->ModifyColumn(sectionId, "ping", sz);
        }
        teamLatency[i] = 0; //omega; reset so next time when the scorebaord is drawn,
                            //it doesn't add the old values in and make the ping
                            //grow and grow and grow! ;)

        m_pPlayerList->SetSectionFgColor(sectionId, gr->GetTeamColor(i)); //mm colors
    }
}

===============================================================================================
modify UpdatePlayerInfo so it adds the player to the proper section:
===============================================================================================
 CODE (c++) 
<<-----SNIP--->>>
            playerData->SetString("name", newName);

            int itemID = FindItemIDForPlayerIndex( i );
            int playerTeam = gr->GetTeam(i); //omega; set a variable to team so we can reuse it
            int sectionID = iTeamSections[playerTeam]; //omega; make sure it goes into the proper section

            //omega!!!
            numPlayersOnTeam[playerTeam]++; //increment player count and add this player
            teamLatency[playerTeam]+=playerData->GetInt("ping");
           
            if ( gr->IsLocalPlayer( i ) )
            {
                selectedRow = itemID;
            }
            if (itemID == -1)
            {
                // add a new row
                itemID = m_pPlayerList->AddItem( sectionID, playerData );
            }
            else
            {
                // modify the current row
                m_pPlayerList->ModifyItem( itemID, sectionID, playerData );
            }

            // set the row color based on the players team
            m_pPlayerList->SetItemFgColor( itemID, gr->GetTeamColor( playerTeam ) );

            playerData->deleteThis();
<<----SNIP---->>>

===============================================================================================
modify AddHeader to create the proper sections / labels
===============================================================================================
 CODE (c++) 
void CClientScoreBoardDialog::AddHeader()
{
    m_iSectionId = 0; //make a blank one
    m_pPlayerList->AddSection(m_iSectionId, "");
    m_pPlayerList->SetSectionAlwaysVisible(m_iSectionId);
    m_pPlayerList->AddColumnToSection(m_iSectionId, "name", "", 0, scheme()->GetProportionalScaledValue(NAME_WIDTH) );

    m_iSectionId = 1;
    m_pPlayerList->AddSection(m_iSectionId, "");
    m_pPlayerList->SetSectionAlwaysVisible(m_iSectionId);
    m_pPlayerList->AddColumnToSection(m_iSectionId, "name", "", 0, scheme()->GetProportionalScaledValue(NAME_WIDTH) );
//  m_pPlayerList->AddColumnToSection(m_iSectionId, "class", "#FLF_PlayerClass", 0, scheme()->GetProportionalScaledValue(CLASS_WIDTH) );
    m_pPlayerList->AddColumnToSection(m_iSectionId, "score", "#PlayerScore", 0, scheme()->GetProportionalScaledValue(SCORE_WIDTH) );
    m_pPlayerList->AddColumnToSection(m_iSectionId, "ping", "#PlayerPing", 0, scheme()->GetProportionalScaledValue(PING_WIDTH) );
    m_pPlayerList->AddColumnToSection(m_iSectionId, "voice", "#PlayerVoice", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, scheme()->GetProportionalScaledValue(VOICE_WIDTH) );

    m_iSectionId = 2; //first team;
    iTeamSections[TEAM_REBELS]  = AddSection(TYPE_TEAM, TEAM_REBELS);
    iTeamSections[TEAM_COMMANDOS]   = AddSection(TYPE_TEAM, TEAM_COMMANDOS);
    iTeamSections[TEAM_SPECTATOR]   = AddSection(TYPE_SPECTATORS, TEAM_SPECTATOR);
    iTeamSections[TEAM_UNASSIGNED]  = AddSection(TYPE_UNASSIGNED, TEAM_UNASSIGNED);
}


===============================================================================================
here is my changed AddSection. if you compare, it's not THAT much different from the original:
===============================================================================================
 CODE (c++) 
//-----------------------------------------------------------------------------
// Purpose: Adds a new section to the scoreboard (i.e the team header)
//-----------------------------------------------------------------------------
int CClientScoreBoardDialog::AddSection(int teamType, int teamNumber)
{
    if ( teamType == TYPE_TEAM )
    {
        IGameResources *gr = GameResources();

        if ( !gr )
            return -1;

        // setup the team name
        wchar_t *teamName = localize()->Find( gr->GetTeamName(teamNumber) );
        wchar_t name[64];
       
        if (!teamName)
        {
            localize()->ConvertANSIToUnicode(gr->GetTeamName(teamNumber), name, sizeof(name));
            teamName = name;
        }

        m_pPlayerList->AddSection(m_iSectionId, "", StaticPlayerSortFunc);
//      m_pPlayerList->SetSectionAlwaysVisible(m_iSectionId);
        m_pPlayerList->SetFgColor(gr->GetTeamColor(teamNumber));

        m_pPlayerList->AddColumnToSection(m_iSectionId, "name", teamName, 0, scheme()->GetProportionalScaledValue(NAME_WIDTH) );
//      m_pPlayerList->AddColumnToSection(m_iSectionId, "class", "", 0, scheme()->GetProportionalScaledValue(CLASS_WIDTH) );
        m_pPlayerList->AddColumnToSection(m_iSectionId, "score", "0", 0, scheme()->GetProportionalScaledValue(SCORE_WIDTH) );
        m_pPlayerList->AddColumnToSection(m_iSectionId, "ping", "", 0, scheme()->GetProportionalScaledValue(PING_WIDTH) );
        m_pPlayerList->AddColumnToSection(m_iSectionId, "voice", "", SectionedListPanel::COLUMN_IMAGE | SectionedListPanel::COLUMN_CENTER, scheme()->GetProportionalScaledValue(VOICE_WIDTH) );
    }
    else if ( teamType == TYPE_SPECTATORS )
    {
        m_pPlayerList->AddSection(m_iSectionId, "");
        m_pPlayerList->AddColumnToSection(m_iSectionId, "name", "#Spectators", 0, scheme()->GetProportionalScaledValue(NAME_WIDTH));
        m_pPlayerList->AddColumnToSection(m_iSectionId, "score", "", 0, scheme()->GetProportionalScaledValue(SCORE_WIDTH) );
        m_pPlayerList->AddColumnToSection(m_iSectionId, "ping", "", 0, scheme()->GetProportionalScaledValue(PING_WIDTH) );
    }
    else if ( teamType == TYPE_UNASSIGNED )
    {
        m_pPlayerList->AddSection(m_iSectionId, "");
        m_pPlayerList->AddColumnToSection(m_iSectionId, "name", "#Unassigned", 0, scheme()->GetProportionalScaledValue(NAME_WIDTH));
        m_pPlayerList->AddColumnToSection(m_iSectionId, "score", "", 0, scheme()->GetProportionalScaledValue(SCORE_WIDTH) );
        m_pPlayerList->AddColumnToSection(m_iSectionId, "ping", "", 0, scheme()->GetProportionalScaledValue(PING_WIDTH) );
    }
    m_iSectionId++; //increment for next
    return m_iSectionId-1;
}

===============================================================================================
remove "deaths" from the StaticPlayerSortFunc function
===============================================================================================

===============================================================================================
modify GetPlayerScoreInfo to set the following:
===============================================================================================
 CODE (c++) 
<<---SNIP----->>>
    kv->SetInt("score", gr->GetFrags( playerIndex ) );
    kv->SetInt("ping", gr->GetPing( playerIndex ) ) ;
    kv->SetString("name", gr->GetPlayerName( playerIndex ) );
//  kv->SetString("class", FLFGameRules()->GetPlayerClassName( gr->GetPlayerClass( playerIndex ) ) );
    kv->SetInt("playerIndex", playerIndex);

    kv->SetInt("voice",  s_VoiceImage[GetClientVoiceMgr()->GetSpeakerStatus( playerIndex - 1) ]);
<<---SNIP----->>>


===============================================================================================
Change FillScoreBoard around to this:
===============================================================================================
 CODE (c++) 
void CClientScoreBoardDialog::FillScoreBoard()
{
    for (int i = TEAM_UNASSIGNED; i < TEAMS_COUNT; i++)
    {
        numPlayersOnTeam[i] = 0; //clear!
        //clear anything else for the team
    }
    // update player info
    UpdatePlayerInfo();

    UpdateTeamInfo(); //omega; update the team headers
}

===============================================================================================
Finally: replace all "frags" tags with "score" (for the columns)
===============================================================================================

c_playerresource.h
------------------

===============================================================================================
modify the GetFrags function to:
===============================================================================================
 CODE (c++) 
//omega; get the score
int C_PlayerResource::GetFrags(int index )
{
    if ( !IsConnected( index ) )
        return 0;

    return m_iScore[index];
}


voice_status.h
--------------

===============================================================================================
Under UpdateSpeakerStatus, add:
===============================================================================================
 CODE (c++) 
    //omega: scoreboard
    enum speakerIcons
    {
        VOICE_NEVERSPOKEN,
        VOICE_NOTTALKING,
        VOICE_TALKING,
        VOICE_BANNED
    };
    int GetSpeakerStatus(int entindex);
    //omega; end scoreboard


voice_status.cpp
----------------

===============================================================================================
Under UpdateSpeakerStatus, add:
===============================================================================================
 CODE (c++) 
int CVoiceStatus::GetSpeakerStatus(int entindex)
{
    player_info_t pi;
    if ( !engine->GetPlayerInfo( entindex, &pi ) )
        return 0;

    bool bTalking = !!m_VoicePlayers[entindex];
    bool bBanned  = m_BanMgr.GetPlayerBan(pi.guid);
    bool bNeverSpoken = !m_VoiceEnabledPlayers[entindex];

    if (bBanned)
        return VOICE_BANNED;
    else if (bTalking)
        return VOICE_TALKING;
    else if (bNeverSpoken)
        return VOICE_NEVERSPOKEN;
    else
        return VOICE_NOTTALKING;
}



That should be everything to make the scoreboard function like mine. if it isn't, i'm sure you can figure it out, it's not that complicated.

Additional
--------------


oh and you'll need to make the voice images, i took mine from HL1 (gfx\vgui\640_xx)
speaker1.tga, speaker2.tga, speaker3.tga, speaker4.tga, voiceblocked.tga
pop those tga's in materialsrc\VGUI and run vtex on them (shader unlitgeneric) to make your vmt/vtf's and edit the vmt's for these tags:
"$translucent" 1
"$vertexcolor" 1
"$vertexalpha" 1

Rate This Article
This article is currently rated: 4.8 out of 5.0 (4 Votes)

You have to register to rate this article.
User Comments Showing comments 1-18

Posted By: Zoc on Jan 06 2005 at 23:06:36
Thank you Omega :D
Niiiice Article :)

Some things that should help out:

1) Uncomment "#include "voice_status.h" in ClientScoreBoardDialog.cpp

2) Modify
#define TYPE_NOTEAM 0 // NOTEAM must be zero :)
to
#define TYPE_UNASSIGNED 0 // NOTEAM must be zero :)
in clientscoreboarddialog.h

3) Change
if ( !engine->GetPlayerInfo( entindex, p ) )
to
if ( !engine->GetPlayerInfo( entindex, &pi ) )
in voice_status.cpp

Hope have helped someone too :)

Posted By: Benualdo on Jan 10 2005 at 21:04:52
Thanks for you tutorial, I started looking for docs about creating a multiplayer mod recently and it helped me. But do you have some code example about how to assign players to teams, did you use modified VGUI from counterstrike source or is it possible to build it from scratch?

I also noticed the 2)define TYPE_NOTEAM 0... but not problem 1) and 3) but maybe that is because I updated to the latest SDK.

Posted By: omega on Jan 11 2005 at 00:44:50
never mention leaked code.
if you have it, and ask about it, you will be banned.

as for TYPE_NOTEAM, i renaemd it, forgot to mention it in the article.

Posted By: gagagu on Jan 13 2005 at 11:49:37
Hi, great stuff but my compiler have problem with the line
if (FLFGameRules()->GetAttackingTeam() == i)

FLFGameRules is not declared .....

Posted By: omega on Jan 13 2005 at 12:40:14
so take it out?
since you obviously don't have FLF's gamerules but rather SDKGameRules.

i forgot to rip that out apparently, but i'm glad. read the first line of the post :P

 QUOTE  
There's a lot of copy and paste code here, but note: make sure you check over everything and read the comments BEFORE you start blindly pasting, or you may royally screw up your scoreboard :P

Posted By: omega on Jan 14 2005 at 00:55:47
lol i just noticed rkzad must have made a parser for !pi to result to the pi symbol ;x
bulk needs to fix it.

Posted By: mgetz on Jan 15 2005 at 20:33:12
how do you make the scoreboard appear?

Posted By: omega on Jan 15 2005 at 21:48:06
bind a key to +showscores

Posted By: RJMacReady on Apr 01 2005 at 15:09:11
Your tutorial doesn't work. The team names dont get displayed at the top, and the spectator team section doesn't get created properly. Ive run through the tutorial about eight times to no avail.Edited by RJMacReady on Apr 01 2005, 15:09:47

Posted By: omega on Apr 01 2005 at 16:30:51
i'll edit the tutorial later becaause it's confusing now that valve has released hl2dm and it's called "hl mp"

this is for the "bare bones multiplayer from scratch configuration"

Posted By: RJMacReady on Apr 01 2005 at 17:11:22
Ahh right! Is that why it doesn't work then? Or is it a case of just editing bits? Would it be easier to just start a barebones multiplayer from scratch?

Posted By: omega on Apr 01 2005 at 21:54:37
if you're dealing with the hl2dm source you need to edit a different file.
look in the hl2mp folder for the scoreboard file.

the file i edit here, is the base one, hl2dm inherits off of it.

Posted By: RJMacReady on Apr 02 2005 at 14:58:53
I already tried usin your code with the hl2dmscoreboard file, but it still didn't seem to work. Should I just use the multiplayer code from scratch and not modify HL2DM?

Posted By: _Ms on Apr 03 2005 at 20:15:00
It works I just did it

Some code needs to go into hl2dmclientscoreboard and some in the file that it inherits from, clientscoreboarddialog

Posted By: Arsyesis on Apr 19 2005 at 19:54:34
i have probleme i dont have any result.

look this screenshot: http://eserv.tonsite.biz/tg/images/pb.jpg

and how can i force the choose team after the motdEdited by Arsyesis on Apr 19 2005, 19:55:44

Posted By: omega on Apr 20 2005 at 13:54:48
you're not creating the headers.

as for choosing team, you need to make it call the team panel at the same time. the motd should show up above it.

Posted By: Arsyesis on Apr 20 2005 at 14:26:30
i added the addheader :s

and for the choosing team where ? in teammenu.cpp ?Edited by Arsyesis on Apr 20 2005, 14:28:20

Posted By: wh0racle on May 06 2005 at 06:16:27
lol I wish I would have know this didn't work for hl2dm mods BEFORE I did all of this..now I gotta read the tutorial backwards to fix it :/. Good tutorial though.

Ms..would you be willing to share what you did?


You must register to post a comment. If you have already registered, you must login.

Latest Articles
3rd person View in Multiplayer
Half-Life 2 | Coding | Client Side Tutorials
How to enable it in HL2DM

By: cct | Nov 13 2006

Making a Camera
Half-Life 2 | Level Design
This camera is good for when you join a map, it gives you a view of the map before you join a team

By: slackiller | Mar 05 2006

Making a camera , Part 2
Half-Life 2 | Level Design
these cameras are working monitors that turn on when a button is pushed.

By: slackiller | Mar 04 2006

Storing weapons on ladder
Half-Life 2 | Coding | Snippets
like Raven Sheild or BF2

By: British_Bomber | Dec 24 2005

Implementation of a string lookup table
Half-Life 2 | Coding | Snippets
A string lookup table is a set of functions that is used to convert strings to pre-defined values

By: deathz0rz | Nov 13 2005


Latest Comments
knock knock
General | News
By: MIFUNE | Dec 31 2017
 
knock knock
General | News
By: omega | Dec 22 2016
 
knock knock
General | News
By: MIFUNE | Oct 10 2015
 
New HL HUD Message System
Half-Life | Coding | Shared Tutorials
By: chbrules | Dec 31 2011
 
knock knock
General | News
By: Whistler | Nov 05 2011
 
Particle Engine tutorial part 4
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 18 2010
 
Particle Engine tutorial part 2
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Particle Engine tutorial part 3
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Game Movement Series #2: Analog Jumping and Floating
Half-Life 2 | Coding | Shared Tutorials
By: mars3554 | Oct 26 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Deadpool | Aug 02 2009
 

Site Info
297 Approved Articless
8 Pending Articles
3940 Registered Members
0 People Online (5 guests)
About - Credits - Contact Us

Wavelength version: 3.0.0.9
Valid XHTML 1.0! Valid CSS!