• Register
RSS My Blogs

Me and my friend have decided that we wanted to see who will complete Half-Life2 the first, but we ended up with a problem - it is a single player game, so we cannot observe each others progress and we can only trust eachothers word. We tried streaming, but it proved to be quite annoying and fruiteless, as it was hard to find a proper streaming software.So instead I came up with idea for mod (more of a plugin) that would do just what I need - allow us to observe eachother.So I came up with a half assed prototype in a day - but it works enough to be playable, completed episode one without any problems.

This was a half assed solution, so don't expect from this too much.
Required library: enet.bespin.org

point_cspcore.h

//=============================================================================//
// Author: Audrius 'Ratchet' Kulikajevas
//
// Version: Pre-Alpha
//
// Requirements: Enet library (enet.bespin.org)
//
//=============================================================================//

#ifndef POINT_CSPCORE_H
#define POINT_CSPCORE_H

#include "hl2_player.h"
#include "enet/enet.h"

#include <string>

#include "prop_nemesis.h"

class CCore;

// Multithreading
struct ThreadObject_T
{
	CCore *pObject;
};

unsigned ConnectThread( void *params );
unsigned ReconnectThread( void *params );
unsigned DisconnectThread( void *params );

class CCore : public CLogicalEntity
{
public:
	DECLARE_CLASS( CCore, CLogicalEntity );

	CCore( void );
	~CCore( void );
	void Spawn( void );
	void Think( void );

private:
	CBasePlayer *pPlayer;
	CPropNemesis *pNemesis;

// Networking stuff
private:
	ENetHost *client;
	ENetAddress address;
	ENetPeer *peer;
	ENetEvent event;
	int serviceResult;

	bool m_bIsConnected;

private:
	void InitEnet( void );
	void DeInitEnet( void );
	void ConnectToHost( const char *host, enet_uint16 port );
	void ReceivePackets( void );
	void SendPacket( void );
	void DecypherPacket( enet_uint8 *data, size_t size );
	std::string ReturnStringFromTo( std::string input, int indexBegin, int size, char separator );
	void UpdateNemesisPosition( Vector pos );
	void RemoveNemesis( void );
	void CreateNemesis( void );

	std::string playerName;

	std::string AddPaddingToString( const char *string, char padding );
	std::string RemovePaddingFromString( const char *string, char padding );

public:
	void Disconnect( void );
	void Connect( void );
	bool ReconnectToHost( void );

// Pointer to myself
private:
	ThreadObject_T *myPointer;
};

#endif

point_cspcore.cpp

//=============================================================================//
// Author: Audrius 'Ratchet' Kulikajevas
//
// Version: Pre-Alpha
//
// Purpose: This is an entity that supports 1v1 competitive single player 
// challange. It allows to see your nemesis progress while you play yourself.
// In theory it should support any sinle-player source game.
//
// Requirements: Enet library (enet.bespin.org)
//
// TODO: 
//		*Allow players to chat with eachother
//		*Allow global game pause
//		*Better packet support (more than 2 players)
//		*Optimize (especially string jugling)
//		*Set to identical difficulty for both players
//
//=============================================================================//

#include "cbase.h"
#include "point_cspcore.h"

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

LINK_ENTITY_TO_CLASS( csp_core, CCore );

static ConVar nemesis_enabled( "nemesis_client_enabled", "1", FCVAR_ARCHIVE );
static ConVar nemesis_ip( "nemesis_client_ip", "localhost", FCVAR_ARCHIVE );
static ConVar nemesis_port( "nemesis_client_port", "1234", FCVAR_ARCHIVE );
static ConVar nemesis_autoreconnect( "nemesis_client_autoreconnect", "1", FCVAR_CHEAT );
static ConVar nemesis_thread_disabled( "nemesis_client_thread_disabled", "0", FCVAR_CHEAT, "Only use it when game crashes, it seems to be working, but just in case I shall leave this here." );
static ConVar nemesis_tick_interval( "nemesis_client_tick_interval", "0.05", FCVAR_ARCHIVE, "The lower the number the faster the packets sent, but the better connection speed is required." );

//#define DEBUG_DRAW_ME

CCore::CCore( void )
{
	m_bIsConnected = false;

	if( !nemesis_enabled.GetBool() )
		return;

	pPlayer = UTIL_GetLocalPlayer();

	if( pNemesis )
		RemoveNemesis();

	pNemesis = NULL;

	if( !pPlayer )
	{
		Warning( "No local player!\n" );
		return;
	}

	playerName = AddPaddingToString(pPlayer->GetPlayerName(), '\n');

	myPointer = new ThreadObject_T;
	myPointer->pObject = this;

	InitEnet();
}

CCore::~CCore( void )
{
	m_bIsConnected = false;

	DeInitEnet();
	delete myPointer;
}

void CCore::InitEnet( void )
{
	Msg( "Starting ENET client.\n" );

	if ( enet_initialize () != 0 )
	{
		Warning( "Error initialising ENET!\n" );
		return;
	}
	else
		Msg( "ENET initialised sucessfully!\n" );

	client = enet_host_create( NULL, /* create a client host */
								1,    /* number of clients */
								2,    /* number of channels */
								57600 / 8,    /* incoming bandwith */
								14400 / 8 );   /* outgoing bandwith */

	if( client == NULL )
	{
		Warning( "Could not create client host\n" );
		return;
	}
}

void CCore::DeInitEnet( void )
{
	Warning( "Your gameplay will not be transmited!\n" );
	Disconnect();

	enet_host_destroy( client );
	enet_deinitialize();
}

void CCore::Disconnect( void )
{
	m_bIsConnected = false;

	if( peer )
		enet_peer_disconnect( peer, 0 );

	while( enet_host_service (client, &amp;event, 3000) > 0 )
	{
		switch( event.type )
		{
		case ENET_EVENT_TYPE_RECEIVE:
			enet_packet_destroy( event.packet );
		break;

		case ENET_EVENT_TYPE_DISCONNECT:
			Msg( "Disconnection succeeded.\n" );
		break;
		}
	}

	peer = NULL;
}

void CCore::ConnectToHost( const char *host, enet_uint16 port )
{
	if( m_bIsConnected )
		return;

	enet_address_set_host( &amp;address, host );
	address.port = port;

	peer = enet_host_connect( client,
							&amp;address,    /* address to connect to */
							2,           /* number of channels */
							0 );          /* user data supplied to the receiving host */

	if( peer == NULL )
	{
		Warning( "No available peers for initiating an ENET connection.\n" );
		return;
	}

	/* Try to connect to server within 5 seconds */
	if( enet_host_service (client, &amp;event, 5000) > 0 &amp;&amp; event.type == ENET_EVENT_TYPE_CONNECT )
	{
		Msg( "Connection to server succeeded.\n" );
		m_bIsConnected = true;
	}
	else
	{
		/* Either the 5 seconds are up or a disconnect event was */
		/* received. Reset the peer in the event the 5 seconds   */
		/* had run out without any significant event.            */
		enet_peer_reset( peer );

		Warning( "Connection to server failed.\n" );
		return;
	}
}

void CCore::Spawn( void )
{
	if( !client )
		return;

	SetNextThink( gpGlobals->curtime + nemesis_tick_interval.GetFloat() );

	ThreadObject_T *myPointer = new ThreadObject_T;
	myPointer->pObject = this;

	if( nemesis_thread_disabled.GetBool() )
		Connect();
	else
		CreateSimpleThread( ConnectThread, myPointer );
}

void CCore::ReceivePackets( void )
{
	if( !m_bIsConnected )
		return;

	serviceResult = 1;

		// Keep doing host_service until no events are left
		while( serviceResult > 0 )
		{
			serviceResult = enet_host_service( client, &amp;event, 0 );

			if( serviceResult > 0 )
			{
				switch( event.type )
				{
				case ENET_EVENT_TYPE_CONNECT:
					event.peer->data = (void*)"New User";
				break;

				case ENET_EVENT_TYPE_RECEIVE:
					DecypherPacket( event.packet->data, event.packet->dataLength );

					// Clean up the packet now that we're done using it.
					enet_packet_destroy( event.packet );

				break;

				case ENET_EVENT_TYPE_DISCONNECT:
					m_bIsConnected = false;
					if( nemesis_autoreconnect.GetBool() )

						if( nemesis_thread_disabled.GetBool() )
							ReconnectToHost();
						else
							CreateSimpleThread( ReconnectThread, myPointer );

					return;

				break;
				}
			}
			else if( serviceResult > 0 )
			{
				Warning( "Error with servicing the client" );
				return;
			}
		}
}

std::string CCore::ReturnStringFromTo( std::string input, int indexBegin, int size, char separator )
{
	//Empty string
	std::string returnString = "";

	for( int i = indexBegin; i < size; i++ )
		if( input[i]!= separator )
			returnString = returnString + input[i];
		else break;

	return returnString;
}

void CCore::DecypherPacket( enet_uint8 *data, size_t size )
{
	const char *strTemp = (const char*)data;
	std::string str(strTemp, size);

	int index = 0;

	std::string returnedString = ReturnStringFromTo( str, index, size, ' ' );

#ifndef DEBUG_DRAW_ME
	//It is us, we do not need to show us
	if( RemovePaddingFromString(returnedString.c_str(), '\n') == pPlayer->GetPlayerName() )
		return;
#endif

	index += returnedString.length()+1;

	returnedString = ReturnStringFromTo( str, index, size, ' ' );

	hudtextparms_s m_textParms;
	m_textParms.x = m_textParms.y = 0;
	m_textParms.r1 = m_textParms.r2 = 0;
	m_textParms.g1 = m_textParms.g2 = 128;
	m_textParms.b1 = m_textParms.b2 = 255;
	m_textParms.a1 = m_textParms.a2 = 255;
	m_textParms.channel = 0;
	m_textParms.effect = 0;
	m_textParms.fadeinTime = 0;
	m_textParms.fadeoutTime = 1;

	UTIL_HudMessage( pPlayer, m_textParms, UTIL_VarArgs("Your nemesis is at map %s", returnedString.c_str() ) );

	//If we are at diffrent maps do nothing
	if( returnedString != STRING(gpGlobals->mapname) )
	{
		if( pNemesis )
			RemoveNemesis();

		return;
	}

	index += returnedString.length()+1;


	//Position var
	Vector position;

	//Find X
	returnedString = ReturnStringFromTo( str, index, size, ' ' );
	index += returnedString.length()+1;
	position.x = atof(returnedString.c_str());

	//Find Y
	returnedString = ReturnStringFromTo( str, index, size, ' ' );
	index += returnedString.length()+1;
	position.y = atof(returnedString.c_str());

	//Find Z
	returnedString = ReturnStringFromTo( str, index, size, '\0' );
	index += returnedString.length()+1;
	position.z = atof(returnedString.c_str());

	UpdateNemesisPosition( position );
}

std::string CCore::AddPaddingToString( const char *string, char padding )
{
	std::string returnString(string);
	
	for( unsigned int i = 0; i < strlen(returnString.c_str()); i++ )
		if( returnString[i]== ' ' )
			returnString[i]= padding;

	return returnString;
};

std::string CCore::RemovePaddingFromString( const char *string, char padding )
{
	std::string returnString(string);
	
	for( unsigned int i = 0; i < strlen(returnString.c_str()); i++ )
		if( returnString[i]== padding )
			returnString[i]= ' ';

	return returnString;
};

void CCore::SendPacket( void )
{
	if( !m_bIsConnected )
		return;

	char message[1024];

	V_snprintf( message, sizeof(message), "%s %s %f %f %f", playerName.c_str(), gpGlobals->mapname, pPlayer->GetAbsOrigin().x, pPlayer->GetAbsOrigin().y, pPlayer->GetAbsOrigin().z );

	if( strlen(message) > 0 )
	{
		ENetPacket *packet = enet_packet_create( message, strlen(message) + 1, ENET_PACKET_FLAG_RELIABLE );
		enet_peer_send( peer, 0, packet );
	}
}

void CCore::Think( void )
{
	SetNextThink( gpGlobals->curtime + nemesis_tick_interval.GetFloat() );

	if( peer &amp;&amp; m_bIsConnected )
	{
		ReceivePackets();
		SendPacket();
	}
}

void CCore::RemoveNemesis( void )
{
	UTIL_Remove( pNemesis );
	pNemesis = NULL;
}

void CCore::UpdateNemesisPosition( Vector pos )
{
	if( !m_bIsConnected )
		return;

	if( !pNemesis )
		CreateNemesis();

	pNemesis->UpdatePosition( pos );
}

void CCore::CreateNemesis( void )
{
	if( !m_bIsConnected )
		return;

	pNemesis = (CPropNemesis*)CreateEntityByName( "prop_nemesis" );

	pNemesis->SetMoveType( MOVETYPE_NONE );
	pNemesis->SetSolid( SOLID_NONE );
	DispatchSpawn( pNemesis );
}

CON_COMMAND( nemesis_client_force_reconnect, "Forces reconnect." )
{
	CHL2_Player *pPlayer = (CHL2_Player*)UTIL_GetLocalPlayer();

	if( !pPlayer )
		return;

	CCore *pCore = (CCore*)pPlayer->GetCore();

	if( pCore )
		pCore->ReconnectToHost();
}

bool CCore::ReconnectToHost( void )
{
	peer = NULL;
	RemoveNemesis();
	DeInitEnet();
	InitEnet();
	Connect();

	return true;
}

void CCore::Connect( void )
{
	ConnectToHost( nemesis_ip.GetString(), nemesis_port.GetInt() );
}

unsigned ConnectThread( void *params )
{
	CCore *pCore = ((ThreadObject_T*)params)->pObject;

	if( pCore )
		pCore->Connect();

	return 0;
}

unsigned ReconnectThread( void *params )
{
	CCore *pCore = ((ThreadObject_T*)params)->pObject;
	if( pCore )
		if( !pCore->ReconnectToHost() )
			return 1;

	return 0;
}

unsigned DisconnectThread( void *params )
{
	CCore *pCore = ((ThreadObject_T*)params)->pObject;

	if( pCore )
		pCore->Disconnect();
	return 0;
}

prop_nemesis.h

//=============================================================================//
// Author: Audrius 'Ratchet' Kulikajevas
//
// Version: Pre-Alpha
//
// Requirements: Enet library (enet.bespin.org)
//
//=============================================================================//
#ifndef PROP_NEMESIS_H
#define PROP_NEMESIS_H

#include "EntityParticleTrail.h"
#include "props.h"

class CPropNemesis : public CBaseProp
{
public:
	DECLARE_CLASS( CPropNemesis, CLogicalEntity );

	CPropNemesis( void );
	~CPropNemesis( void );
	void Spawn( void );
	void Think( void );
	void Precache( void );

private:
	CEntityParticleTrail *pParticle;
	void Wobble( void );

	Vector m_Position;

public:
	void UpdatePosition( Vector pos );
};

#endif

prop_nemesis.cpp

//=============================================================================//
// Author: Audrius 'Ratchet' Kulikajevas
//
// Version: Pre-Alpha
//
// Purpose: Give visual representation to the player of where his nemesis is.
//
//
// TODO: 
//		*Optimize
//
//=============================================================================//

#include "cbase.h"
#include "prop_nemesis.h"

#include "engine/ivdebugoverlay.h"

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

#define NEMESIS_MODEL "models/combine_helicopter/helicopter_bomb01.mdl"
#define TRAIL_MATERIAL "sprites/animglow02"

#define PI 3.14159265

LINK_ENTITY_TO_CLASS( prop_nemesis, CPropNemesis );

CPropNemesis::CPropNemesis( void )
{
	pParticle = NULL;
}

CPropNemesis::~CPropNemesis( void )
{
	if( pParticle )
		UTIL_Remove( pParticle );
}

void CPropNemesis::Spawn( void )
{
	AddEffects( EF_NODRAW );
	SetNextThink( gpGlobals->curtime + 0.05f );
	Precache();
	SetModel( NEMESIS_MODEL );

	EntityParticleTrailInfo_t trailData;

	trailData.m_strMaterialName = MAKE_STRING(TRAIL_MATERIAL);
	trailData.m_flEndSize = 0.4f;

	pParticle = CEntityParticleTrail::Create( this, trailData, this );
}

void CPropNemesis::Precache( void )
{
	PrecacheModel( NEMESIS_MODEL );
	PrecacheMaterial( TRAIL_MATERIAL );
}

void CPropNemesis::Think( void )
{
	SetNextThink( gpGlobals->curtime + 0.05f );
	Wobble();
}

void CPropNemesis::Wobble( void )
{
	Vector pos = m_Position;

	float sine = sin((PI/180) * gpGlobals->curtime * 10 );
	pos.z += 32 * sine;

	SetAbsOrigin( pos );
}

void CPropNemesis::UpdatePosition( Vector pos )
{
	m_Position = pos;
	m_Position.z += 32;
}

cs_server.h

//=============================================================================//
// Author: Audrius 'Ratchet' Kulikajevas
//
// Version: Pre-Alpha
//
// Requirements: Enet library (enet.bespin.org)
//
//=============================================================================//

#ifndef CS_SERVER_H
#define CS_SERVER_H

#include "enet/enet.h"

class CServer
{
	bool m_bServerInit;

	ENetAddress address;
	ENetHost *server;
	ENetEvent event;
	int serviceResult;

	void Init( void );

public:
	CServer( void );
	~CServer( void );
	bool IsServerInit( void ) { return m_bServerInit; }
	void RunServer( void );
};

#endif

cs_server.cpp

//=============================================================================//
// Author: Audrius 'Ratchet' Kulikajevas
//
// Version: Pre-Alpha
//
// Purpose: Act as a server for compsp
//
// Requirements: Enet library (enet.bespin.org)
//
// TODO: 
//		*Get rid of the bugs
//		*Optimize?
//
//=============================================================================//

#include "cbase.h"

#include "cs_server.h"

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

static bool bIsServerRunning = false;
static bool bServerIsShuttingDown = false;
static CServer *pServer = NULL;

//Threads
unsigned CreateServerThread( void *params );

//Command callbacks
static void Create_Server( void );
static void Stop_Server( void );

//Commands
static ConCommand server_create( "nemesis_server_create", Create_Server, "Creates compsp server on a new thread." );
static ConCommand server_shutdown( "nemesis_server_shutdown", Stop_Server, "Stops compsp server." );

//ConVars
static ConVar server_port( "nemesis_server_port", "1234", FCVAR_ARCHIVE, "Server port." );

static void Create_Server( void )
{
	if( bIsServerRunning || pServer )
		return;

	pServer = new CServer();

	if( !pServer->IsServerInit() )
		return;

	CreateSimpleThread( CreateServerThread, NULL );

}

static void Stop_Server( void )
{
	if( !pServer )
		return;

	if( !bIsServerRunning || !pServer->IsServerInit() )
		return;

	bServerIsShuttingDown = true;
	bIsServerRunning = false;
}

unsigned CreateServerThread( void *params )
{
	pServer->RunServer();
	return 0;
}

CServer::CServer( void )
{
	bIsServerRunning = false;
	m_bServerInit = false;
	bServerIsShuttingDown = false;
	Init();
}

CServer::~CServer( void )
{
	m_bServerInit = false;

	enet_host_destroy( server );
	enet_deinitialize();

	bIsServerRunning = false;
	bServerIsShuttingDown = false;
	pServer = NULL;
}

void CServer::Init( void )
{
	if( m_bServerInit )
		return;

	Msg( "Starting server on port %i...\n", server_port.GetInt() );

	if( enet_initialize() != 0 )
	{
		Warning("Error initialising enet\n");
		return;
	}

	/* Bind the server to the default localhost.     */
	/* A specific host address can be specified by   */
	/* enet_address_set_host (&amp; address, "x.x.x.x"); */
	address.host = ENET_HOST_ANY;
	/* Bind the server to port 1234. */
	address.port = server_port.GetInt();

	server = enet_host_create( &amp;address,
								3,   /* number of clients, although it is set to 3, the modification is only supporting 2 */
								2,    /* number of channels */
								0,    /* Any incoming bandwith */
								0 );   /* Any outgoing bandwith */

	if( server == NULL )
	{
		Warning("Could not create server host.\n");
		return;
	}
	
	m_bServerInit = true;
}

void CServer::RunServer( void )
{
	if( !IsServerInit() )
		return;

	bIsServerRunning = true;

	while( bIsServerRunning &amp;&amp; !bServerIsShuttingDown )
	{
		/* Keep doing host_service until no events are left */
		do
		{
			/* Wait up to 1000 milliseconds for an event. */
			serviceResult = enet_host_service( server, &amp;event, 1000 );

			if( serviceResult > 0 )
			{
				switch( event.type )
				{
				case ENET_EVENT_TYPE_CONNECT:
					Msg ( "A new client connected from %x:%u.\n",
					event.peer->address.host,
					event.peer->address.port );

					/* Store any relevant client information here. */
					event.peer->data = (void*)"Client information";
				break;

				case ENET_EVENT_TYPE_RECEIVE:

					/* Tell all clients about this message */
					enet_host_broadcast ( server, 0, event.packet );
				break;

				case ENET_EVENT_TYPE_DISCONNECT:
					Msg( "%s disconected.\n", event.peer->data );

					/* Reset the peer's client information. */

					event.peer->data = NULL;
				break;
				}
			}
			else if( serviceResult > 0 )
			{
				Warning( "Error with servicing the server\n" );
				bIsServerRunning = false;
				delete this;
				return;
			}
		}
		while( serviceResult > 0 &amp;&amp; bIsServerRunning &amp;&amp; !bServerIsShuttingDown );
	}

	Msg( "Server was shut down.\n" );
	delete this;
}

hl2_player.h (Bottom off CHL2_Player class)

private:
	CBaseEntity *pCore;

public:
	void				CreateCore( void );
	CBaseEntity *GetCore( void ) { return pCore; }

hl2_player.cpp
in void CHL2_Player::Spawn(void) bottom

	CreateCore();

in void CHL2_Player::OnRestore() bottom

	CreateCore();

in the bottom of hl2_player.cpp

void CHL2_Player::CreateCore( void )
{
	pCore = NULL;

	pCore = CreateEntityByName( "csp_core" );
	if ( pCore )
		DispatchSpawn( pCore );
}

Start a group Groups
Source Developers

Source Developers

1,111 members Fans & Clans

For people and teams developing mods and games with Valve's Source engine.

Post comment Comments
audryzas Creator
audryzas

666 profile visits **** yeah

Reply Good karma+2 votes
Ravebot
Ravebot

Me too, just got back on and noticed Blade Symphony was in Beta already :O

Reply Good karma Bad karma+1 vote
[IoV]Cueball
[IoV]Cueball

Hey i'd be interested in a beta key for blade symphony :D

Reply Good karma Bad karma+1 vote
Low543
Low543

Hello :D

Reply Good karma Bad karma+4 votes
audryzas Creator
audryzas

Bie pie.

Reply Good karma+1 vote
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account:

X