Tutorial: Improved entering and exiting for Observer/Spectator mode Last edited 1 month ago2024-10-29 16:13:09 UTC

Improved entering and exiting out of Observer/Spectator mode

1. General Talk:

Not sure if you're aware, however in Half-Life you can use the "spectate" command to enter a Spectator/Observer mode.

While in Spectator/Observer mode you can look around the map (for secrets for example) but you can also observe the matches and see what the players are doing, however the "spectate" command has one limitation - if you're in Spectator / Observer mode induced by this command you cannot go back to the game as a player.

Valve decided that it would be too much, I presume so in order for you to go back as player, you need to disconnect from the server and reconnect again.

In my eyes this is a bit inconvenient. I do understand why Valve did it like that, most likely to prevent cheating (I presume).

For my modification Flatline Arena, I decided to lift this limitation. In this tutorial I will show you how to re-implement the exiting and entering in Observer mode.

This one is achieved with around 100 new or edited lines of code.

As part of this tutorial we will do:

2. Requirements

3. Additional notes and disclaimers:

OK, since we got those straightened up, let's jump into the fun part. I will work mostly in "client.cpp", "game.cpp", "game.h", "observer.cpp" and "player.h" files. All of those are located in the server side of the SDK.

What I will put under "observer.cpp" can also be put at the bottom of "player.cpp", however IMO it's place is with the remaining of the Observer stuff.

Let us start now.

4. Registering the server cvar for the delay.

Open "game.cpp" and look for "allow_spectators". You will get two entries, one where we do "cvar_t" and one for "CVAR_REGISTER" call.

Add the cvar_t for the new one under the one for "allow_spectators" It should look like this:
cvar_t    spectatate_cmd_delay = { "spectatate_cmd_delay", "30", FCVAR_SERVER };        // how long until we can type spectate or end_spectate again
Now go down to the* CVAR_REGISTER* and under the one for "allow_spectators " add the new one. It should look like this:
CVAR_REGISTER ( &spectatate_cmd_delay );
We're almost ready with the delay *cva*r, we need to go and add it to* game.h* so it could be used everywhere game.h is referenced.

Open game.h and add those two right under the line for "displaysoundlist" like this:
// Spectator settings
extern cvar_t     allow_spectators;
extern cvar_t     spectatate_cmd_delay;
If you'd like you can compile now, it should not give any errors, but the cvars will do nothing. We just added it. In the next sections we'll make it work properly.

5. Setting up a proper way to exit or end Observer with no need to quit and reconnect.

Now open player.h and look for "StartObserver", right under it we will add references to our magic that will allow us to exit and stop observer.

Add my lines like this:
// Observer stuff
void StopObserver();
void EndObserver();
float m_flNextSpectCmd;
It's clear what the float will do, we'll use it for the delay on the commands. Now... we need to take care of the two functions we added reference to. Let's do that.

Open "observer.cpp" and at the bottom of the file add my new functions, like this:
extern int gmsgTeamInfo;
extern int g_teamplay;
extern void respawn( entvars_t* pev, BOOL fCopyCorpse );

void CBasePlayer::StopObserver()
{
    // Turn off spectator
    pev->iuser1 = pev->iuser2 = 0;
    m_iHideHUD = 0;

    GetClassPtr( (CBasePlayer *)pev )->Spawn();
    pev->nextthink = -1;

    // Update Team Status
    MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo );
        WRITE_BYTE( ENTINDEX( edict() ) ); // index number of primary entity
    if( g_teamplay )
        WRITE_STRING( TeamID() );
    else
        WRITE_STRING( "Players" );
    MESSAGE_END();

    m_fWeapon = FALSE; // force weapon send
    m_iHideHUD = 0;
}

void CBasePlayer::EndObserver()
{
    m_iHideHUD &= ~(HIDEHUD_HEALTH | HIDEHUD_WEAPONS);
    m_afPhysicsFlags &= ~PFLAG_OBSERVER;
    pev->iuser1 = 0;
    pev->iuser2 = 0;

    RemoveAllItems(1);

    respawn(pev, false);
}
If you look at those two functions, basically they will do the same thing. Still as one of those will be called for when ending observer and the other one will be called after entering observer there are some differences. I presume you can figure out what they do on your own.

With this conclude this section. You can go and compile the libraries and you should not receive any errors. Yet again the functions are not yet called anywhere so... nothing much will happen.

It's time for the cool stuff.

6. Implementing the "spectate" toggle command and the "end_spectate" command.

This one cannot be easier, open "client.cpp" and look for "spectate". Then replace the full body of the function with the one I'll provide here.

Note: If you already did some changes to your "spectate" command, just compare what I'm doing and edit accordingly.
else if ( FStrEq( pcmd, "spectate" ) )    // clients wants to become a spectator
{
    CBasePlayer *pPlayer = GetClassPtr( (CBasePlayer *)pev );
    // Block too offten spectator command usage
    if (pPlayer->m_flNextSpectCmd < gpGlobals->time)
    {
        pPlayer->m_flNextSpectCmd = gpGlobals->time + (spectatate_cmd_delay.value < 1.0 ? 1.0 : spectatate_cmd_delay.value);

        if (!pPlayer->IsObserver())
        {
            // always allow proxies to become a spectator
            if ((pev->flags & FL_PROXY) || allow_spectators.value)
            {
                CBasePlayer* pPlayer = GetClassPtr((CBasePlayer*)pev);

                edict_t* pentSpawnSpot = g_pGameRules->GetPlayerSpawnSpot(pPlayer);
                pPlayer->StartObserver(pev->origin, VARS(pentSpawnSpot)->angles);

                // notify other clients of player switching to spectator mode
                UTIL_ClientPrintAll(HUD_PRINTNOTIFY, UTIL_VarArgs("%s switched to spectator mode\n",
                    (pev->netname && STRING(pev->netname)[0] != 0) ? STRING(pev->netname) : "unconnected"));
            }
            else
            {
                ClientPrint(pev, HUD_PRINTCONSOLE, "Spectator mode is disabled.\n");
            }
        }
        else
        {
            // get out of Spectator mode
            pPlayer->StopObserver();
            // notify other clients of player left spectators

            UTIL_ClientPrintAll(HUD_PRINTNOTIFY, UTIL_VarArgs("%s has left spectator mode\n",
                (pev->netname && (STRING(pev->netname))[0] != 0) ? STRING(pev->netname) : "unconnected"));
        }
    }
}
We're done with the "spectate" toggle now. Let's do the "end_spectate" command. Just paste this code block under the modified "spectate" code block, like this:
else if ( FStrEq(pcmd, "end_spectate" ) )
{
    CBasePlayer *pPlayer = GetClassPtr( (CBasePlayer *)pev );

    if (pPlayer->m_flNextSpectCmd < gpGlobals->time)
    {
        pPlayer->m_flNextSpectCmd = gpGlobals->time + (spectatate_cmd_delay.value < 1.0 ? 1.0 : spectatate_cmd_delay.value);

        pPlayer->EndObserver();

        // notify other clients of player left spectators
        UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs( "%s has left spectator mode\n",
                ( pev->netname && ( STRING( pev->netname ) )[0] != 0 ) ? STRING( pev->netname ) : "unconnected" ) );
    }
}
Now, go compile the client and server library, paste them in your mod directory, and let's try the functionality.

Note: You will have to enable the spectate first, as by default it's disabled. This is controlled by the default "allow_spectators" cvar. To enable it you need to set it to 1.

After that you can type "spectate" and you will go into spectator mode. Now you need to wait for 30 seconds and you can type either "spectate" as a toggle, or "end_spectate" to exit the Observer mode.

If you look closely to the code I have given, there is one major difference between "spectate" toggle and "end_spectate". The "spectate" toggle is actually dependent on the "allow_spectators" value, while "end_spectate" is not.

The server administrator might disable the option for spectating on the fly, so in that case "end_spectate" will allow quick getaway from Observer mode with no reconnect/quit needed.

Well this will wrap up this tutorial. You should now have fully functional Spectator mode, with a way to enter and a proper way to exit from it.

7. I'll add few more closing thoughts here:

GitHub Repo: https://github.com/N7P0L3ON/halflife25th/commit/62274d5ed42f99680cc1a76c028becf9f0cf7efd Flatline Arena Homepage: https://flatline-arena.droppages.com/

Flatline Arena on ModDB: https://www.moddb.com/mods/flatlinea/

Cold Ice Remastered on ModDB: https://www.moddb.com/mods/cold-ice-remastered

You can also join our Discord server here to stay up to date with the latest on both mods.

Discord: https://discord.com/invite/Hu2Q6pcJn3 Napoleon was here at some undisclosed time, 2024.

Comments

You must log in to post a comment. You can login or register a new account.