Ingame menu for server plugins (CS:S only)

From Valve Developer Community
Jump to navigation Jump to search
Broom icon.png
This article or section should be converted to third person to conform to wiki standards.

It should be noted, before you start, that this method is mod dependent and that this menu-system is not enabled in the standard SDK. This menu only seems to function in Counter-Strike: Source. Use of the simpler IServerPluginHelpers interface is preferred.


Creating an in-game menu for server plugins

The in-game menu is a usermessage so we will need to include src/tier1/bitbuf.cpp into the project. This needs to be added to the project and then bitbuf.h needs to be included in serverplugin_empty.cpp

We will also need a Recipient Filter, you can use the one from mosca.br at HL2Coding

MRecipientFilter.cpp

#include "MRecipientFilter.h"
#include "interface.h"
#include "filesystem.h"
#include "engine/iserverplugin.h"
#include "dlls/iplayerinfo.h"
#include "eiface.h"
#include "igameevents.h"
#include "convar.h"
#include "Color.h"

#include "shake.h"
#include "IEffects.h"
#include "engine/IEngineSound.h"

extern IVEngineServer		*engine;
extern IPlayerInfoManager	*playerinfomanager;
extern IServerPluginHelpers	*helpers;

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

MRecipientFilter::MRecipientFilter(void)
{
}

MRecipientFilter::~MRecipientFilter(void)
{
}

int MRecipientFilter::GetRecipientCount() const
{
	return m_Recipients.Size();
}

int MRecipientFilter::GetRecipientIndex(int slot) const
{
	if ( slot < 0 || slot >= GetRecipientCount() )
		return -1;

	return m_Recipients[ slot ];
}

bool MRecipientFilter::IsInitMessage() const
{
	return false;
}

bool MRecipientFilter::IsReliable() const
{
	return false;
}

void MRecipientFilter::AddAllPlayers(int maxClients)
{
	m_Recipients.RemoveAll();
	for ( int i = 1; i <= maxClients; i++ )
	{
		edict_t *pPlayer = engine->PEntityOfEntIndex(i);
		if ( !pPlayer || pPlayer->IsFree())
			continue;
		//AddRecipient( pPlayer );
		m_Recipients.AddToTail(i);
	}
} 
void MRecipientFilter::AddRecipient( int iPlayer )
{
	// Already in list
	if ( m_Recipients.Find( iPlayer ) != m_Recipients.InvalidIndex() )
		return;

	m_Recipients.AddToTail( iPlayer );
}

MRecipientFilter.h

#ifndef _MRECIPIENT_FILTER_H
#define _MRECIPIENT_FILTER_H
#include "irecipientfilter.h"
#include "bitvec.h"
#include "tier1/utlvector.h"

class MRecipientFilter : public IRecipientFilter
{
public:
	MRecipientFilter(void);
	~MRecipientFilter(void);

	virtual bool IsReliable( void ) const;
	virtual bool IsInitMessage( void ) const;

	virtual int GetRecipientCount( void ) const;
	virtual int GetRecipientIndex( int slot ) const;
	void AddAllPlayers( int maxClients );
	void AddRecipient (int iPlayer );

private:
	bool m_bReliable;
	bool m_bInitMessage;
	CUtlVector< int > m_Recipients;
};

#endif

Usage

These both need adding the project and have the line in the includes at the top or serverplugin_empty.cpp

#include "MRecipientFilter.h"

We also need a function to get the clients index from their userid.

Edit: This is not actually needed, valve have already got a function: engine->IndexOfEdict(edict_t *)

int getIndexFromUserID(int userid)
{
	edict_t *player;
	IPlayerInfo *info;
	for(int i = 1; i <= maxplayers; i++)  //int maxplayers; has to be added after the includes and maxplayers=clientMax; in the ServerActivate function
	{
		player = engine->PEntityOfEntIndex(i);
		if(!player || player->IsFree() )
			continue;
		info = playerinfomanager->GetPlayerInfo(player);

		if(info->GetUserID() == userid)
			return i;
	}
	return -1;
}

Making the menu

I have used made the menu as a client command activated on the command a_menu.

	if ( FStrEq( pcmd, "a_menu" ) )
	{
		/*IPlayerInfo *playerInfo = playerinfomanager->GetPlayerInfo(pEntity); 
		MRecipientFilter filter;
		filter.AddRecipient(getIndexFromUserID(playerInfo->GetUserID()));
                No need what so ever of The Custom function Valve have there own
                engine->IndexOfEdict(pEntity);
                */

		MRecipientFilter filter;
		filter.AddRecipient(engine->IndexOfEdict(pEntity));
		bf_write *pBuffer = engine->UserMessageBegin( &filter, 10 );
		pBuffer->WriteShort( (1<<0) | (1<<1) | (1<<2) ); //Sets how many options the menu has
		pBuffer->WriteChar( -1 ); //Sets how long the menu stays open -1 for stay until option selected
		pBuffer->WriteByte( false ); //true if there is more string yet to be received before displaying the menu, false otherwise
		pBuffer->WriteString( "1.Assault Rifle\n2.AWP\n3.Exit" ); //The text shown on the menu
		engine->MessageEnd();
		return PLUGIN_STOP;
	}

This menu will be an alternative buy menu that will buy ammo, armor and grenades, as well as the primary weapon.

The WriteShort contains a number of bits which tell the game which options are enabled for the menu.

  • (1<<0) enables 1
  • (1<<1) enables 2
  • etc..

To enable 4 and 7, one would use:

(1<<3) | (1<<6)

If you have done this correcly so far then you should have the menu show up in game when the client types a_menu.

Making commands for the menu

The menu run the command menuselect with the parameter as the option selected, so for this menu the commands would be menuselect 1, menuselect 2, and menuselect 3

	else if ( FStrEq( pcmd, "menuselect" ) )
	{
		const char *parameter = engine->Cmd_Argv(1);
		switch(atoi(parameter))
		{
		case 1:
			engine->ClientCommand (pEntity, "buy m4a1;buy ak47;buy primammo;buy vesthelm;buy deagle;buy secammo;buy flashbang;buy hegrenade;buy flashbang\n");
			break;
		case 2:
			engine->ClientCommand (pEntity, "buy awp;buy primammo;buy vesthelm;buy deagle;buy secammo;buy flashbang;buy hegrenade;buy flashbang\n");
			break;
		case 3:
			engine->ClientPrintf(pEntity, "Menu Exited");
			break;
		}
		return PLUGIN_STOP;
	}  

This will make the client run the commands to buy weapons ammo armor and grenades. The menu runs the commands.