You know the hell? Well i came from there. I'm the master of the underworld.

Image RSS Feed Latest Screens
New Inventory sm_constructionyard sm_graveyard
Blog RSS Feed Report abuse Latest Blog: Implenting nemesis to any singleplayer mod

0 comments by audryzas on Nov 4th, 2012

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
 

cpp code:
//=============================================================================//
// 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
 

cpp code:
//=============================================================================//
// 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

cpp code:
//=============================================================================//
// 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

cpp code:
//=============================================================================//
// 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

cpp code:
//=============================================================================//
// 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
 

cpp code:
//=============================================================================//
// 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)
 

cpp code:
private:
  CBaseEntity *pCore;

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

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

cpp code:
  CreateCore();

in void CHL2_Player::OnRestore() bottom

cpp code:
  CreateCore();

in the bottom of hl2_player.cpp

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

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

Media RSS Feed Latest Video
Groups
Source Developers

Source Developers

Fans & Clans group with 962 members, open to all members

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

Post comment Comments
audryzas
audryzas Mar 22 2011, 12:34pm says:

666 profile visits **** yeah

+2 votes     reply to comment
Ravebot
Ravebot Mar 7 2011, 6:24am says:

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

+1 vote     reply to comment
[IoV]Cueball
[IoV]Cueball Feb 28 2011, 11:01pm says:

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

+1 vote     reply to comment
Low543
Low543 May 3 2010, 7:25am says:

Hello :D

+4 votes     reply to comment
audryzas
audryzas May 4 2010, 11:55am replied:

Bie pie.

+1 vote     reply to comment
Post a Comment
click to sign in

You are not logged in, your comment will be anonymous unless you join the community today (totally free - or sign in with your social account on the right) which we encourage all contributors to do.

2000 characters limit; HTML formatting and smileys are not supported - text only

Level
Avatar
Avatar
Offline Since
Jun 4, 2014
Country
Lithuania Lithuania
Gender
Male
Member Watch
Track this member
Statistics
Activity Points
821
Rank
2,594 of 474,816
Watchers
4 members
Time Online
17 hours
Comments
166
Site Visits
1,705
Profile Visitors
5,375 (2 today)
Contact
Private Message
Send Now
Email
Members Only
Skype
rachet4167
Steam
_ks_ratchet
Pinkis007
Pinkis007 friends since May 19, 2010
zeddd
zeddd friends since May 29, 2010
karodbz
karodbz friends since May 6, 2010
[IoV]Cueball
[IoV]Cueball friends since Mar 1, 2011
Low543
Low543 friends since May 3, 2010
zeretex
zeretex friends since Jul 8, 2010