Posted by: _Ms
Date posted: May 02 2005 User Rating: 4.7 out of 5.0 | Number of views: 15681 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.
 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.
 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
 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.

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

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
| | 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
| | 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
| | if ( GetTeamNumber() != TEAM_SPECTATOR ) { StopObserverMode(); } else { 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
| | 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
To
| | 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
| | 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
Now a spectator can no longer observe another spectator.
Step 7: Allowing Spectators to Move Open Player.cpp Go To Function StartObserverMode Remove
| | m_lifeState = LIFE_DEAD; 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
| | 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
| | 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
| | if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->curtime > (m_flDeathTime + DEATH_ANIMATION_TIME) ) && !IsObserver() ) { StartObserverMode( m_iObserverLastMode ); } |
Now while you are dead you will not go into observer mode.
Work So Far

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
| | if ( pLocalPlayer == NULL ) return; |
Add
| | 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
| | void CSpectatorGUI::UpdateScores() { 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
| | int timer = 0;
_snwprintf ( szText, sizeof( szText ), L"%d:%02d\n", (timer / 60), (timer % 60) ); |
To
| | 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
| | 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
| | 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 ); } } UpdateTimer(); 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
| | m_bSpecScoreboard = false; |
To
| | m_bSpecScoreboard = true; |
With that small change the scoreboard will no longer pop up when you go into spectator mode.
The Finished Product

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 |
|
User Comments
Showing comments 1-18
I agree, I wanted to approve this before but I didnt want to test the code even though I knew it worked ;) |
|
Very useful and helpful. Great article. |
|
Great and useful, good for me and other people. |
|
Brilliant tutorial, very easy to follow, one of the best out there! |
|
Is it just me or , shouldn't you include "hl2mp_gamerules.h" inside SpectatorGUI.cpp too? |
|
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
|
|
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
|
|
how can i get this to work with mod from scratch??
i also cant find this in the sourcecode
PickDefaultSpawnTeam |
|
@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. |
|
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 |
|
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. |
|
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? |
|
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... |
|
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. |
|
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
|
|
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.
|
297 Approved Articless
8 Pending Articles
3940 Registered Members
0 People Online (4 guests)
|
|