Welcome, Guest! Login | Register

Enabling Spectator Mode [Print this Article]
Posted by: _Ms
Date posted: May 02 2005
User Rating: 4.7 out of 5.0
Number of views: 14700
Number of comments: 18
Description: How to get spectator mode working. Based on HL2MP SDK
Getting spectator mode working is rather easy but some may miss what's in front of their face (I know I did :D).

Resource Files
First thing we need to do is grab BottomSpectator.res and Spectator.res from counter-strike source shared.gcf.
In order to extract the needed files you will need a tool called GCFScape. Note that you may need to close steam so that GCFScape can access the files.

When you open counter-strike source shared.gcf the required resource files are located in root->cstrike->resource->ui as shown below.
user posted image
Once you have extracted those 2 files place them in SourceMods\yourmod\resource\ui.

Localized Files
Based on the data in Spectator.res there are 2 text entries that needs another file to get their values. The file that is needed is called yourmod_language.txt.
Those files are used to translate from one language to another (Note: the files do not translate for you, you must do that first). They are also useful for code upkeep in the common case when you need to change a string. By using the localization files you can link text from a tag which will allow you to change that text without having to look at the code.

From now on language is assumed English.

If you haven't already you need to grab HL2DM's English localization file. This is located in half-life 2 deathmatch.gcf.

When you open half-life 2 deathmatch.gcf the file you are looking for is called hl2mp_english.txt and is located in root->hl2mp->resource as shown below.
user posted image
Once you extract hl2mp_english.txt you will need to rename it as yourmod_english.txt. After you renamed it copy that file to SourceMods\yourmod\resource.

Now you will need to add 2 new entries into yourmod_english.txt: Cstrike_Spec_Ter_Score and Cstrike_Spec_CT_Score as shown below
user posted image
Cstrike_Spec_Ter_Score and Cstrike_Spec_CT_Score will show up on the spectator GUI as the names of your teams.

Missing Font
You may have noticed in CSS that they have a timer image next to the timer in the spectator GUI. That image is a font based icon which is located in cstrike.ttf. Most of the GUI graphics are done in this manner (weapon selection, item pickup, and death icon).

First you need to copy cstrike.ttf from CSS to your mod. The font is located in cstrike\resource and should be copied to SourceMods\yourmod\resource. If you cannot find the font in cstrike\resource it's also located in counter-strike source shared.gcf.

After that you'll need to make some changes in ClientScheme.res (located in SourceMods\yourmod\resource). If you don't already have one then grab it from HL2DM. There are 2 things you must add to get the timer icon. Those changes are shown below.

The first change is to define a font.
user posted image

The second change is to add cstrike.ttf to the list of fonts
user posted image

Now once we are done we will get a timer icon in the spectator GUI. If we didn't do this you would get an e where the icon should be. This is because the letter e corresponds to the timer icon in cstrike.ttf.

Server Side Code
Now we will code in the server side part of the spectator mode.

Step 1: Forcing Players to Spectator When First Joining Game
Open HL2MP_Player.cpp
Go To Function PickDefaultSpawnTeam
Change
 CODE (c++) 
void CHL2MP_Player::PickDefaultSpawnTeam( void )
{
    if ( GetTeamNumber() == 0 )
    {
        if ( HL2MPRules()->IsTeamplay() == false )
        {
            if ( GetModelPtr() == NULL )
            {
                ChangeTeam( TEAM_UNASSIGNED );
            }
        }
        else
        {
            CTeam *pCombine = g_Teams[TEAM_ELTADORIANS];
            CTeam *pRebels = g_Teams[TEAM_RATEALIANES];

            if ( pCombine == NULL || pRebels == NULL )
            {
                ChangeTeam( random->RandomInt( TEAM_ELTADORIANS, TEAM_RATEALIANES ) );
            }
            else
            {
                if ( pCombine->GetNumPlayers() > pRebels->GetNumPlayers() )
                {
                    ChangeTeam( TEAM_RATEALIANES );
                }
                else if ( pCombine->GetNumPlayers() < pRebels->GetNumPlayers() )
                {
                    ChangeTeam( TEAM_ELTADORIANS );
                }
                else
                {
                    ChangeTeam( random->RandomInt( TEAM_ELTADORIANS, TEAM_RATEALIANES ) );
                }
            }
        }
    }
}

To
 CODE (c++) 
void CHL2MP_Player::PickDefaultSpawnTeam( void )
{
    if ( GetTeamNumber() == 0 )
    {
        if ( HL2MPRules()->IsTeamplay() == false )
        {
            if ( GetModelPtr() == NULL )
            {
                ChangeTeam( TEAM_UNASSIGNED );
            }
        }
        else
        {
            ChangeTeam( TEAM_SPECTATOR );
        }
    }
}


Now when a player joins the game for the first time he will be forced to the spectator team.

Part 2: Starting Spectator Mode
Open HL2MP_Player.cpp
Go To Function Spawn
Add To End Of Function
 CODE (c++) 
if ( GetTeamNumber() != TEAM_SPECTATOR )
{
    StopObserverMode();
}
else
{
    // Ms - If we are spectating then go roaming
    StartObserverMode( OBS_MODE_ROAMING );
}
}


Now when the player spawns HL2 checks to see if the player is a member of the spectator team, if so then observer mode is started.

Part 3: Fixing CHL2MP_Player::StartObserverMode
Open HL2MP_Player.cpp
Go To Function StartObserverMode
Add To Function
 CODE (c++) 
// Ms - Call CBasePlayer::StartObserverMode(mode)
return BaseClass::StartObserverMode(mode);


Now when CHL2MP_Player's StartObserverMode is called it then calls the base class version of it (which by default is CBasePlayer).

Step 4: Bodies From the Sky
Open HL2MP_Player.cpp
Go To Function Event_Killed
Change
 CODE (c++) 
CreateRagdollEntity();

To
 CODE (c++) 
// Ms - Spectators don't have corpes
if (GetTeamNumber() != TEAM_SPECTATOR)
    CreateRagdollEntity();


Now it will check to see if the player is not on the spectator team before spawning a rag doll.

Step 5: Screaming Spectators
Open HL2MP_Player.cpp
Go To Function DeathSound
Add To Start Of Function
 CODE (c++) 
// Ms - Spectators can't scream
if (GetTeamNumber() == TEAM_SPECTATOR)
    return;


With that change spectators will no longer scream when you switch teams.

Step 6: Prevent Spectating Spectators
Open Player.cpp
Go To Function IsValidObserverTarget
Uncomment
 CODE (c++) 
// Don't spec observers or players who haven't picked a class yet
/*if ( player->IsObserver() )
    return false;*/


Now a spectator can no longer observe another spectator.

Step 7: Allowing Spectators to Move
Open Player.cpp
Go To Function StartObserverMode
Remove
 CODE (c++) 
m_lifeState = LIFE_DEAD; // Can't be dead, otherwise movement doesn't work right.
pl.deadflag = true;


Before the player was killed which forced the player into death cam mode. By removing those 2 lines the player stays alive and now has control.

Step 8: Preventing the Use of Impulse Commands
Open Player.cpp
Go To Function ImpulseCommands
Add To Start Of Function
 CODE (c++) 
// Ms - Spectators can't use impulse commands
if (GetTeamNumber() == TEAM_SPECTATOR)
    return;


Now spectators can no longer use impulse commands (paint decals, flashlight, etc).

Step 9: Disabling the Spectator's Use
Open Player.cpp
Go To Function PlayerRunCommand
Add To Start Of Function
 CODE (c++) 
// Ms - Spectators can't use anything
if (GetTeamNumber() == TEAM_SPECTATOR)
    ucmd->buttons &= ~IN_USE;


Now when the spectator presses the use key nothing will happen.

Step 10: No Spectator GUI When Dead
Open Player.cpp
Go To Function PlayerDeathThink
Remove
 CODE (c++) 
if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->curtime > (m_flDeathTime + DEATH_ANIMATION_TIME) ) && !IsObserver() )
{
    // go to dead camera.
    StartObserverMode( m_iObserverLastMode );
}


Now while you are dead you will not go into observer mode.

Work So Far
user posted image

Not bad but now we will finish this up on the client.

Client Side Code
Now for the final part, the client side code. This will fix several things that could not have been done on the server.

Step 1: Making the Team Label Invisible
Open HL2MP_HUD_Team.cpp
Go To Function OnThink
After
 CODE (c++) 
if ( pLocalPlayer == NULL )
     return;

Add
 CODE (c++) 
// Ms - Don't render the team label if you are a spectator
if ( pLocalPlayer->GetTeamNumber() == TEAM_SPECTATOR )
    return;


Now it checks to see if you are a spectator and if so don't render the team label.

Step 2: Team Scores
Open SpectatorGUI.h
Go To Class CSpectatorGUI
Add Define Under Protected List For void UpdateScores();

Open SpectatorGUI.cpp
Add C_Team.h To Include List
Add Function
 CODE (c++) 
// Ms - Update team scores
void CSpectatorGUI::UpdateScores()
{
    // Ms - Do team scores
    wchar_t eltTeamScore[6], ratTeamScore[6];
    C_Team *teamElt = GetGlobalTeam(TEAM_ELTADORIANS);
    C_Team *teamRat = GetGlobalTeam(TEAM_RATEALIANES);

    if (teamElt) {
        swprintf(eltTeamScore, L"%d", teamElt->Get_Score());
        SetLabelText("CTScoreValue", eltTeamScore);
    }
    if (teamRat) {
        swprintf(ratTeamScore, L"%d", teamRat->Get_Score());
        SetLabelText("TERScoreValue", ratTeamScore);
    }
}


Now we have the code to update the spectator GUI with the team scores but now we must decide when we want to update them.

Step 3: Giving That Timer Some Logic
Open SpectatorGUI.cpp
Go To UpdateTimer
Change
 CODE (c++) 
int timer = 0;

_snwprintf ( szText, sizeof( szText ), L"%d:%02d\n", (timer / 60), (timer % 60) );

To
 CODE (c++) 
int timer = gpGlobals->curtime;

_snwprintf ( szText, sizeof( szText ), L"%d:%02d", (timer / 60), (timer % 60) );


As I haven't been able to test it with another person I don't know if the client's time syncs with the server's time.
If it does sync then it will show how long the server has been running if not then it will be how long the player has been playing.
But regardless we still have to tell the spectator GUI when to update this.

Step 4: Updating the Timer and Scores
Open SpectatorGUI.cpp
Go To Function CSpectatorGUI::OnThink
Change
 CODE (c++) 
void CSpectatorGUI::OnThink()
{
    BaseClass::OnThink();

    if ( IsVisible() )
    {
        if ( m_bSpecScoreboard != spec_scoreboard.GetBool() )
        {
            if ( !spec_scoreboard.GetBool() || !gViewPortInterface->GetActivePanel() )
            {
                m_bSpecScoreboard = spec_scoreboard.GetBool();
                gViewPortInterface->ShowPanel( PANEL_SCOREBOARD, m_bSpecScoreboard );
            }
        }
    }
}

To
 CODE (c++) 
void CSpectatorGUI::OnThink()
{
    BaseClass::OnThink();

    if ( IsVisible() )
    {
        if ( m_bSpecScoreboard != spec_scoreboard.GetBool() )
        {
            if ( !spec_scoreboard.GetBool() || !gViewPortInterface->GetActivePanel() )
            {
                m_bSpecScoreboard = spec_scoreboard.GetBool();
                gViewPortInterface->ShowPanel( PANEL_SCOREBOARD, m_bSpecScoreboard );
            }
        }
        // Ms - Update the timer
        UpdateTimer();
        // Ms - Update team scores
        UpdateScores();
    }
}


Now the team scores and timer will be updated every think cycle.

Step 5: Prevent the Scoreboard From Showing
Open SpectatorGUI.cpp
Go To Function CSpectatorGUI::ShowPanel
Change
 CODE (c++) 
m_bSpecScoreboard = false;

To
 CODE (c++) 
m_bSpecScoreboard = true;


With that small change the scoreboard will no longer pop up when you go into spectator mode.

The Finished Product
user posted image

There we go. A completely functioning spectator system.

Unfinished Business
A few things left undone.

1. Spectator Settings Doesn't Work (unknown command spec_*)
The spectator settings (the first combo box on the left) issues commands but those commands are not in the game.
All you have to do is create the commands and deal with the logic for them.

2. Map Overview
Map overview is something completely different which is why it wasn't dealt with.
If you want map overview it may take a bit of work but I believe most of the code is already there for you.

Created by Steven Guy aka Ms
Coder for Exstruck

Rate This Article
This article is currently rated: 4.7 out of 5.0 (3 Votes)

You have to register to rate this article.
Related Files
Zip FileFilename: lang_txt.gif
File Size: 14.5 KB
Zip FileFilename: gcfscape2.gif
File Size: 7.1 KB
Zip FileFilename: font_res2.gif
File Size: 5.4 KB
Zip FileFilename: final2.jpg
File Size: 81.8 KB
Zip FileFilename: final1.jpg
File Size: 72.8 KB
Zip FileFilename: font_res1.gif
File Size: 8 KB
Zip FileFilename: gcfscape1.gif
File Size: 13.2 KB

User Comments Showing comments 1-18

Posted By: Persuter on May 14 2005 at 19:04:01
Awesome article, Ms_.

Posted By: pplante on May 14 2005 at 19:50:32
I agree, I wanted to approve this before but I didnt want to test the code even though I knew it worked ;)

Posted By: trepid_jon on May 15 2005 at 00:10:07
Very useful and helpful. Great article.

Posted By: deathz0rz on May 18 2005 at 18:32:29
very nice

Posted By: Roh_Z on May 25 2005 at 07:28:20
Great and useful, good for me and other people.

Posted By: Hellbound on Jun 05 2005 at 23:26:24
Brilliant tutorial, very easy to follow, one of the best out there!

Posted By: redlink on Jun 19 2005 at 01:58:15
Is it just me or , shouldn't you include "hl2mp_gamerules.h" inside SpectatorGUI.cpp too?

Posted By: ts2do on Jun 22 2005 at 09:14:41
to get SpectatorGUI to work, add it to the creation list in the teamfortress viewport and use overview_mode 1 to show it

also...you should disable the giving of weapons to a spectator on spawn, coz if a spec uses the lastinv command or use , then they can see the weapon--a place to start editing is HL2MP_Player::Spawn and CMultiplayRules::PlayerSpawn....similarly, HL2MP_Player::EquipSuit should be overridden because it uses the one from the singleplayer class

AND you can even fire the weapon with +attack

also...npcs see spectators...so that should be disabledEdited by ts2do on Jun 22 2005, 22:03:18

Posted By: ts2do on Jun 22 2005 at 22:21:23
Here's a fix for death notices...
int CBaseViewport::GetDeathMessageStartHeight( void )
{
bool isObs = C_BasePlayer::GetLocalPlayer()->IsObserver();
int retVal = YRES(2);
if(isObs)
retVal+=((CSpectatorGUI *)FindPanelByName(PANEL_SPECGUI))->GetTopBarHeight();
return retVal;
}


also...look into MOVETYPE_OBSERVEREdited by ts2do on Jun 28 2005, 21:40:14

Posted By: cct on Jul 17 2005 at 19:56:34
how can i get this to work with mod from scratch??

i also cant find this in the sourcecode

PickDefaultSpawnTeam

Posted By: _Ms on Aug 01 2005 at 22:26:10
@cct: This tut is based on the HL2MP SDK and it should work on a clean SDK as long as it's the MP SDK.

@ts2do: I know about the +attack thing. I forgot about it when I was doing the tut. When I get back home I'm going to make some small changes to disable commands issued by the client when in observer (all but the ones used in the spec GUI). The problem is that the spectator can still use any of the commands such as use which would allow them to drive vehicles and activate buttons. I did a short fix for the use and impulse commands but you can still use other action commands like jump, attack and reload.

Posted By: Inf. on Sep 28 2005 at 16:59:58
any chance on updating to remove the weapon models from showing? currently when not specing anyone you can fly around with weapons unholstered but ofc they do not fire

Posted By: _Ms on Sep 29 2005 at 06:08:28
That's an issue with your team system not the spectator system. The problem is you're giving players on the spectator team weapons. In the section of code where you give the player weapons check what team they are on and if they are on the spectator team don't give them weapons.

Posted By: Nic2 on Oct 26 2005 at 01:15:48
I made the player join the spectator team when joining the server.
Problem is that they must press one of the movement-keys(no other keys work, like jump or attack) to spawn after choosing a team.
How do I make the players spawn without having to press a movement-key?

Posted By: Trend on Nov 16 2005 at 16:57:33
How hard would it be to make the spectator be able to see the HUD of the player he is spectating ? To see health, armor, ammo, etc...

Posted By: brent23 on May 31 2006 at 13:36:35
Awsome tutorial worked great for me. But a question I have also got this team menu system working from another tutorial (http://www.sourcewiki.org/wiki/index.php/Creating_a_Team-Menu) my question is how would you get this menu to display once when you join the spectator mode for the first time, kinda like how counter strike source works with the joing.

Posted By: rookie on Aug 30 2006 at 02:13:08
may someone tell me?
i get this to work with mod from scratch.
everything work except the "PickDefaultSpawnTeam".
thanks a lot.
i know this articles is for hl2mp but i want to try with scratch.Edited by rookie on Aug 30 2006, 02:18:39

Posted By: Unknown on Dec 11 2006 at 21:51:14
I know that this is a really dumb and weird enquiry, but I was wondering about the ability to spectate NPC's in single player.


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: 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
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Persuter | Aug 02 2009
 

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

Wavelength version: 3.0.0.9
Valid XHTML 1.0! Valid CSS!