Resetting Maps and Entities: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
 
m ({{merge}} with resetting the map ?)
 
(6 intermediate revisions by 3 users not shown)
Line 1: Line 1:
{{wip}}
{{merge|Resetting the map}}
{{pov}}
 
==Introduction==
==Introduction==


While you can pretty much copy and paste the provided here into your mod - it's best to read through it and gain an understanding of what's going on, to that end I'm going to explain each section of code as I deal with it. What I won't do is tell you where to find the files mentioned, it should be simple enough if you have the SDK open when reading this article.
The example code provided can be used by copying and pasting, but it is best to ensure you read through the rest of the text to gain a proper understanding of the process behind it. Each section of code is explained thoroughly in the body of text and via comments. It is recommended to have the source code open as you read through the article so you can easily find the relevant sections.


The goal of this article is to enable a mod to restart a round by resetting all map entities to their default locations, the code provided is for the Source Only SDK (not the HL2MP SDK), it may still work in the HL2MP SDK but it has not been tested!
The goal of this article is to enable a mod to restart a round by resetting all map entities to their default locations, the code provided was originally for the Source Only SDK, but has been tested in the HL2MP SDK to ensure it works.


Note: I have had success with this. It can be seen in use in The Battlegrounds II. The code is available with each release or at svn://bgmod.com/code/ - Draco Houston, a BG2 coder.
The first thing covered are the files that should be created, these contain the list of entities to keep and a custom entity filter.


There are a couple of things we need to do to restart a round, we need to get a copy of the map entity list, store a filter for entities we don't want reset and finally add some logic to gamerules that specifies what to do with all that stored information.
==Server Side==


==Creating the entity filter==
Add two new empty files to the server project: mapfilter.cpp and mapfilter.h. It is recommended that any new files are created in a separate folder from the core SDK codebase.
 
As there is no built in filter class that is suitable for us to use we need to create our own. So, add  two new empty files to your server project: mapfilter.cpp and mapfilter.h. - I recommend creating a seperate mod folder for any new files you add to the solution and also adding a prefix to the file names to distinguish between valve code and mod code.


===Mapfilter.h===
===Mapfilter.h===


// general definitions and includes
  #include "cbase.h"
  #include "cbase.h"
  #include "mapentities.h"
  #include "mapentities.h"
  #include "UtlSortVector.h"
  #include "utllinkedlist.h"<br>
  #ifndef CMAPENTITYFILTER_H
  #ifndef CMAPENTITYFILTER_H
  #define CMAPENTITYFILTER_H
  #define CMAPENTITYFILTER_H
  typedef const char* strptr;<br>
 
  static bool StrLessThan( const strptr &src1,  const strptr &src2, void *pCtx )
The following is a generic list of entities that should be preserved between resets, be sure that the last entry is set to NULL. Add any mod specific entities (such as custom game rules) that do not want to be recreated on reset.
 
  // These entities are preserved each round restart. The rest are removed and recreated.
static const char *s_PreserveEnts[] =
{
  "ai_network",
  "ai_hint",
  "ambient_generic",
  "sdk_gamerules",
  "sdk_team_manager",
  "player_manager",
  "env_soundscape",
  "env_soundscape_proxy",
  "env_soundscape_triggerable",
  "env_sun",
  "env_wind",
  "env_fog_controller",
  "func_brush",
  "func_wall",
  "func_illusionary",
  "func_rotating",
  "hl2mp_gamerules",
  "infodecal",
  "info_projecteddecal",
  "info_node",
  "info_target",
  "info_node_hint",
  "info_spectator",
  "info_map_parameters",
  "keyframe_rope",
  "move_rope",
  "info_ladder",
  "player",
  "point_viewcontrol",
  "scene_manager",
  "shadow_control",
  "sky_camera",
  "soundent",
  "trigger_soundscape",
  "viewmodel",
  "predicted_viewmodel",
  "worldspawn",
  "point_devshot_camera",
  "",
  NULL,// END Marker
};
 
CMapEntityRef is a purely helper class, it is accessed through the linked list that is declared extern beneath the class definition.
 
class CMapEntityRef
{
public:
  int m_iEdict;
  int m_iSerialNumber;
};<br>
  extern CUtlLinkedList<CMapEntityRef, unsigned short> g_MapEntityRefs;
 
The following is our custom map entity filter, this gets used on initial level load and is used to fill the linked list with entity data.
 
class CMapLoadEntityFilter : public IMapEntityFilter
{
public:<br>
virtual bool ShouldCreateEntity( const char *pClassname )
  {
  {
  if( strcmp(src1, src2) >= 0)
  // During map load, create all the entities.
    return false;
  return true;
  else
    return true;
  }<br>
  }<br>
  class CMapEntityFilter : public IMapEntityFilter
  virtual CBaseEntity* CreateNextEntity( const char *pClassname )
  {
  {
  public:
      // create each entity in turn and an instance of CMapEntityRef
    // constructor
      CBaseEntity *pRet = CreateEntityByName( pClassname );<br>
    CMapEntityFilter();
      CMapEntityRef ref;
    // deconstructor
      ref.m_iEdict = -1;
    ~CMapEntityFilter();<br>
      ref.m_iSerialNumber = -1;<br>
    // used to check if we should reset an entity or not
      // if the new entity is valid store the entity information in ref
    virtual bool ShouldCreateEntity( const char *pClassname );
      if ( pRet )
    // creates the next entity in our stored list.
      {
    virtual CBaseEntity* CreateNextEntity( const char *pClassname );
        ref.m_iEdict = pRet->entindex();<br>
    // add an entity to our list
        if ( pRet->edict() )
    void AddKeep( const char*);<br>
            ref.m_iSerialNumber = pRet->edict()->m_NetworkSerialNumber;
  private:
      }<br>
    // our list of entities to keep
      // add the new ref to the linked list and return the entity
    CUtlSortVector< const char* > *keepList;
      g_MapEntityRefs.AddToTail( ref );
      return pRet;
  }
  };<br>
  };<br>
  #endif
  #endif


===MapFilter.cpp===
===MapFilter.cpp===
All that's done here is the declaration of our linked list


  #include "cbase.h"
  #include "cbase.h"
  #include "mapfilter.h"<br>
  #include "mapfilter.h"<br>
  // Constructor
  // memdbgon must be the last include file in a .cpp file!!!
  CMapEntityFilter::CMapEntityFilter()  
  #include "tier0/memdbgon.h"<br>
CUtlLinkedList<CMapEntityRef, unsigned short> g_MapEntityRefs;
 
===CServerGameDLL===
 
Find <code>CServerGameDLL::LevelInit_ParseAllEntities</code> in <code>sdk_gameinterface.cpp</code>, which is called when a level is first loaded and where the first addition is made. Add the following code to the function:
 
// Load the entities and build up a new list of the map entities and their starting state in here.
g_MapEntityRefs.Purge();
CMapLoadEntityFilter filter;
MapEntity_ParseAllEntities( pMapEntities, &filter );
 
===GameRules===
 
Now add a function called <code>CleanMap</code> to the gamerules, this is the function that will be called when the map has to be reset (due to a round end etc).  The function <code>FindInList</code> is used in at a couple of points, the following code show's the function definition.
 
// FindInList searches the provided array for the compare string
// we use it to check against our list of preserved entities (s_PreserveEnts)
bool FindInList(const char *s_List[], const char *compare)
  {
  {
   keepList = new CUtlSortVector< const char *> (StrLessThan);
   int index = 0;<br>
}<br>
  while(s_List[index])
// Deconstructor
  {
CMapEntityFilter::~CMapEntityFilter()
      if ( Q_strcmp(s_List[index], compare) == 0 )
{
        return true;<br>
  delete keepList;
        index++;
}<br>
   }<br>
// [bool] ShouldCreateEntity [char]
   return false;
// Purpose  : Used to check if the passed in entity is on our stored list
// Arguments : The classname of an entity
// Returns  : Boolean value - if we have it stored, we return false.<br>
bool CMapEntityFilter::ShouldCreateEntity( const char *pClassname )  
{
  //Check if the entity is in our keep list.
  if( keepList->Find( pClassname ) >= 0 )
      return false;
  else
      return true;
}<br>
// [CBaseEntity] CreateNextEntity [char]
// Purpose  : Creates an entity
// Arguments : The classname of an entity
// Returns  : A pointer to the new entity<br>
CBaseEntity* CMapEntityFilter::CreateNextEntity( const char *pClassname )
{
   return CreateEntityByName( pClassname);
}<br>
// [void] AddKeep [char]
// Purpose  : Adds the passed in value to our list of items to keep
// Arguments : The class name of an entity
// Returns  : Void<br>
void CMapEntityFilter::AddKeep( const char *sz)
{
   keepList->Insert(sz);
  }
  }


This code is based on code provided by stefaniii of the VERC forums, the original code and discussion can be found in the following post: [http://www.chatbear.com/board.plm?a=viewthread&t=549,1104207081,14733&b=4991&id=&v=flatold&s=20 Tutorial: Creating a Roundtimer (VERC forums)].
The following code makes up the entire <code>CleanUpMap</code> function, it is divided into smaller sections to allow for clarification of some of the finer points.


==Putting it all together==
void CSDKGameRules::CleanMap()
{
  // Recreate all the map entities from the map data (preserving their indices),
  // then remove everything else except the players.<br>
  // Get rid of all entities except players.
  CBaseEntity *pCur = gEntList.FirstEnt();
  while ( pCur )
  {
      if ( !FindInList( s_PreserveEnts, pCur->GetClassname() ) )
      {
        UTIL_Remove( pCur );
      }<br>
      pCur = gEntList.NextEnt( pCur );
  }<br>
  // Really remove the entities so we can have access to their slots below.
  gEntList.CleanupDeleteList();


So, we have the building blocks in place it's time to make the magic happen! We need to make some changes to our gamerules, namely we want to add a '''RestartRound()''' function. So open up '''sdk_gamerules.h'''
The following is an inline class declaration, it is meant to be here - it acts in a similar fashion to the previous CMapLoadEntityFilter, but rather than storing entity values it checks against the stored values to decide how to recreate entities.


// only include the mapfilter if we're in the server dll.
  // Now reload the map entities.
#ifdef GAME_DLL
  class CMapEntityFilter : public IMapEntityFilter
  #include "mapfilter.h"
  {
#endif
      public:
        virtual bool ShouldCreateEntity( const char *pClassname )
        {
            // Don't recreate the preserved entities.
            if ( !FindInList( s_PreserveEnts, pClassname ) )
            {
              return true;
            }
            else
            {
              // Increment our iterator since it's not going to call CreateNextEntity for this ent.
              if ( m_iIterator != g_MapEntityRefs.InvalidIndex() )
                  m_iIterator = g_MapEntityRefs.Next( m_iIterator );<br>
              return false;
            }
        }<br>
        virtual CBaseEntity* CreateNextEntity( const char *pClassname )
        {
            if ( m_iIterator == g_MapEntityRefs.InvalidIndex() )
            {
              // We should never reach this point - g_MapEntityRefs should have been filled
              // when we loaded the map due to the use of CHDNMapLoadFilter, but we cover ourselves
              // by checking here.
              Assert( m_iIterator != g_MapEntityRefs.InvalidIndex() );
              return NULL;
            }
            else
            {
              CMapEntityRef &ref = g_MapEntityRefs[m_iIterator];
              m_iIterator = g_MapEntityRefs.Next( m_iIterator ); // Seek to the next entity.<br>
              if ( ref.m_iEdict == -1 || engine->PEntityOfEntIndex( ref.m_iEdict ) )
              {
                  // the entities previous edict has been used for whatever reason,
                  // so just create it and use any spare edict slot
                  return CreateEntityByName( pClassname );
              }
              else
              {
                  // The entity's edict slot was free, so we put it back where it came from.
                  return CreateEntityByName( pClassname, ref.m_iEdict );
              }
            }
        }<br>
      public:
        int m_iIterator; // Iterator into g_MapEntityRefs.
  };


Add the include and surrounding #if block to the top of the file, then using a similar #if block declare the restart round function and an instance of the filter class - like so:
The final task of this function is to call <code>MapEntity_ParseAllEntities</code> using the new filter class, this guarantees that only the entities that need to be recreated will be.


#ifdef GAME_DLL
  // restart round function declaration
  void RestartRound(void);<br>
  // map entity filter instance.
   CMapEntityFilter filter;
   CMapEntityFilter filter;
  #endif
  filter.m_iIterator = g_MapEntityRefs.Head();<br>
  // final task, trigger the recreation of any entities that need it.
  MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true );
  }
 
An important stage is to fire an event that signifies when the map has been restarted, this will allow the clients to pick up and perform their own clean up - the code below is a demonstration of this, be sure to add the event to <code>ModEvents.res</code>
 
IGameEvent * event = gameeventmanager->CreateEvent( "game_round_restart" );
if ( event )
{
  DevMsg(1, "Fired game_round_restart event\n");
  // fire the event
  gameeventmanager->FireEvent( event );
}


With that done save '''sdk_gamerules.h''' and open '''sdk_gamerules.cpp'''.
==Client Side==


Somewhere sensible (I'd recommend the constructor) put the following:
As part of the reset process client side entities and decals also have to be cleared up, this is done by listening for the <code>game_round_restart</code> event that was fired by the server during the reset process. The best place to do this is in '''clientmode_shared.cpp'''.


filter.AddKeep("worldspawn");
Add a listener to <code>ClientModeShared::Init()</code>
filter.AddKeep("soundent");
filter.AddKeep("sdk_gamerules");
filter.AddKeep("scene_manager");
filter.AddKeep("predicted_viewmodel");
filter.AddKeep("sdk_team_manager");
filter.AddKeep("event_queue_saveload_proxy");
filter.AddKeep("player_manager");
filter.AddKeep("player");


This creates our list of entities to keep, with further work your mod may require more of these to be added, simply use '''filter.AddKeep("entity_name");'''. Though I'd hope that is fairly obvious by now!
gameeventmanager->AddListener( this, "game_round_restart", false );


Now for the core of the matter, the '''RestartRound()''' function!
Finally put the following at the end of <code>ClientModeShared::FireGameEvent</code>


  // [void] RestartRound [void]
  else if ( Q_strcmp( "game_round_restart", eventname ) == 0 )
// Purpose  : Trigger the resetting of all map entities and the respawning of all players.
// Arguments : Void
// Returns  : Void
void CSDKGameRules::RestartRound()
  {
  {
  CBaseEntity *pEnt;
   // recreate all client side physics props
  CBaseEntity *tmpEnt;<br>
   C_PhysPropClientside::RecreateAll();<br>
   // find the first entity in the entity list
   // Just tell engine to clear decals
   pEnt = gEntList.FirstEnt();<br>
   engine->ClientCmd( "r_cleardecals\n" );<br>
   // as long as we've got a valid pointer, keep looping through the list
   tempents->Clear();<br>
   while (pEnt != NULL) {
   //stop any looping sounds
      if (filter.ShouldCreateEntity (pEnt->GetClassname() ) )
   enginesound->StopAllSounds( true );<br>
      {
   Soundscape_OnStopAllSounds(); // Tell the soundscape system.
        // if we don't need to keep the entity, we remove it from the list
        tmpEnt = gEntList.NextEnt (pEnt);
        UTIL_Remove (pEnt);
        pEnt = tmpEnt;
      }
      else
      {
        // if we need to keep it, we move on to the next entity
        pEnt = gEntList.NextEnt (pEnt);
      }
  }<br>
   // force the entities we've set to be removed to actually be removed
  gEntList.CleanupDeleteList();<br>
   // with any unrequired entities removed, we use MapEntity_ParseAllEntities
  // to reparse the map entities this in effect causes them to spawn back to their normal position.
   MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true);<br>
   // print a message to all clients telling them that the round is restarting
  UTIL_ClientPrintAll( HUD_PRINTCENTER, "Round restarting..." );<br>
  // now we've got all our entities back in place and looking pretty,
  //we need to respawn all the players
  for (int i = 1; i <= gpGlobals->maxClients; i++ )
  {
      CBaseEntity *plr = UTIL_PlayerByIndex( i );<br>
      if ( plr )
      {
        plr->Spawn();
      } else {
        break;
      }
  }<br>
  roundRestart = false;
  }
  }
The comments should explain what this function does and how it does it - there is one thing to note, the function does not deal with client side temporary effects (debris, decals etc). I will not cover that in this tutorial right now but an update may be forthcoming in the long run.


[[Category:Tutorials]] [[Category:Programming]]
[[Category:Tutorials]] [[Category:Programming]]

Latest revision as of 06:29, 20 February 2011

Merge-arrows.png
It has been suggested that this article or section be merged with Resetting the map. (Discuss)

Introduction

The example code provided can be used by copying and pasting, but it is best to ensure you read through the rest of the text to gain a proper understanding of the process behind it. Each section of code is explained thoroughly in the body of text and via comments. It is recommended to have the source code open as you read through the article so you can easily find the relevant sections.

The goal of this article is to enable a mod to restart a round by resetting all map entities to their default locations, the code provided was originally for the Source Only SDK, but has been tested in the HL2MP SDK to ensure it works.

The first thing covered are the files that should be created, these contain the list of entities to keep and a custom entity filter.

Server Side

Add two new empty files to the server project: mapfilter.cpp and mapfilter.h. It is recommended that any new files are created in a separate folder from the core SDK codebase.

Mapfilter.h

// general definitions and includes
#include "cbase.h"
#include "mapentities.h"
#include "utllinkedlist.h"
#ifndef CMAPENTITYFILTER_H #define CMAPENTITYFILTER_H

The following is a generic list of entities that should be preserved between resets, be sure that the last entry is set to NULL. Add any mod specific entities (such as custom game rules) that do not want to be recreated on reset.

// These entities are preserved each round restart. The rest are removed and recreated.
static const char *s_PreserveEnts[] =
{
  "ai_network",
  "ai_hint",
  "ambient_generic",
  "sdk_gamerules",
  "sdk_team_manager",
  "player_manager",
  "env_soundscape",
  "env_soundscape_proxy",
  "env_soundscape_triggerable",
  "env_sun",
  "env_wind",
  "env_fog_controller",
  "func_brush",
  "func_wall",
  "func_illusionary",
  "func_rotating",
  "hl2mp_gamerules",
  "infodecal",
  "info_projecteddecal",
  "info_node",
  "info_target",
  "info_node_hint",
  "info_spectator",
  "info_map_parameters",
  "keyframe_rope",
  "move_rope",
  "info_ladder",
  "player",
  "point_viewcontrol",
  "scene_manager",
  "shadow_control",
  "sky_camera",
  "soundent",
  "trigger_soundscape",
  "viewmodel",
  "predicted_viewmodel",
  "worldspawn",
  "point_devshot_camera",
  "",
  NULL,// END Marker
};

CMapEntityRef is a purely helper class, it is accessed through the linked list that is declared extern beneath the class definition.

class CMapEntityRef
{
public:
  int m_iEdict;
  int m_iSerialNumber;
};
extern CUtlLinkedList<CMapEntityRef, unsigned short> g_MapEntityRefs;

The following is our custom map entity filter, this gets used on initial level load and is used to fill the linked list with entity data.

class CMapLoadEntityFilter : public IMapEntityFilter
{
public:
virtual bool ShouldCreateEntity( const char *pClassname ) { // During map load, create all the entities. return true; }
virtual CBaseEntity* CreateNextEntity( const char *pClassname ) { // create each entity in turn and an instance of CMapEntityRef CBaseEntity *pRet = CreateEntityByName( pClassname );
CMapEntityRef ref; ref.m_iEdict = -1; ref.m_iSerialNumber = -1;
// if the new entity is valid store the entity information in ref if ( pRet ) { ref.m_iEdict = pRet->entindex();
if ( pRet->edict() ) ref.m_iSerialNumber = pRet->edict()->m_NetworkSerialNumber; }
// add the new ref to the linked list and return the entity g_MapEntityRefs.AddToTail( ref ); return pRet; } };
#endif

MapFilter.cpp

All that's done here is the declaration of our linked list

#include "cbase.h"
#include "mapfilter.h"
// memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h"
CUtlLinkedList<CMapEntityRef, unsigned short> g_MapEntityRefs;

CServerGameDLL

Find CServerGameDLL::LevelInit_ParseAllEntities in sdk_gameinterface.cpp, which is called when a level is first loaded and where the first addition is made. Add the following code to the function:

// Load the entities and build up a new list of the map entities and their starting state in here.
g_MapEntityRefs.Purge();
CMapLoadEntityFilter filter;
MapEntity_ParseAllEntities( pMapEntities, &filter );

GameRules

Now add a function called CleanMap to the gamerules, this is the function that will be called when the map has to be reset (due to a round end etc). The function FindInList is used in at a couple of points, the following code show's the function definition.

// FindInList searches the provided array for the compare string
// we use it to check against our list of preserved entities (s_PreserveEnts)
bool FindInList(const char *s_List[], const char *compare)
{
  int index = 0;
while(s_List[index]) { if ( Q_strcmp(s_List[index], compare) == 0 ) return true;
index++; }
return false; }

The following code makes up the entire CleanUpMap function, it is divided into smaller sections to allow for clarification of some of the finer points.

void CSDKGameRules::CleanMap()
{
  // Recreate all the map entities from the map data (preserving their indices),
  // then remove everything else except the players.
// Get rid of all entities except players. CBaseEntity *pCur = gEntList.FirstEnt(); while ( pCur ) { if ( !FindInList( s_PreserveEnts, pCur->GetClassname() ) ) { UTIL_Remove( pCur ); }
pCur = gEntList.NextEnt( pCur ); }
// Really remove the entities so we can have access to their slots below. gEntList.CleanupDeleteList();

The following is an inline class declaration, it is meant to be here - it acts in a similar fashion to the previous CMapLoadEntityFilter, but rather than storing entity values it checks against the stored values to decide how to recreate entities.

  // Now reload the map entities.
  class CMapEntityFilter : public IMapEntityFilter
  {
     public:
        virtual bool ShouldCreateEntity( const char *pClassname )
        {
           // Don't recreate the preserved entities.
           if ( !FindInList( s_PreserveEnts, pClassname ) )
           {
              return true;
           }
           else
           {
              // Increment our iterator since it's not going to call CreateNextEntity for this ent.
              if ( m_iIterator != g_MapEntityRefs.InvalidIndex() )
                 m_iIterator = g_MapEntityRefs.Next( m_iIterator );
return false; } }
virtual CBaseEntity* CreateNextEntity( const char *pClassname ) { if ( m_iIterator == g_MapEntityRefs.InvalidIndex() ) { // We should never reach this point - g_MapEntityRefs should have been filled // when we loaded the map due to the use of CHDNMapLoadFilter, but we cover ourselves // by checking here. Assert( m_iIterator != g_MapEntityRefs.InvalidIndex() ); return NULL; } else { CMapEntityRef &ref = g_MapEntityRefs[m_iIterator]; m_iIterator = g_MapEntityRefs.Next( m_iIterator ); // Seek to the next entity.
if ( ref.m_iEdict == -1 || engine->PEntityOfEntIndex( ref.m_iEdict ) ) { // the entities previous edict has been used for whatever reason, // so just create it and use any spare edict slot return CreateEntityByName( pClassname ); } else { // The entity's edict slot was free, so we put it back where it came from. return CreateEntityByName( pClassname, ref.m_iEdict ); } } }
public: int m_iIterator; // Iterator into g_MapEntityRefs. };

The final task of this function is to call MapEntity_ParseAllEntities using the new filter class, this guarantees that only the entities that need to be recreated will be.

  CMapEntityFilter filter;
  filter.m_iIterator = g_MapEntityRefs.Head();
// final task, trigger the recreation of any entities that need it. MapEntity_ParseAllEntities( engine->GetMapEntitiesString(), &filter, true ); }

An important stage is to fire an event that signifies when the map has been restarted, this will allow the clients to pick up and perform their own clean up - the code below is a demonstration of this, be sure to add the event to ModEvents.res

IGameEvent * event = gameeventmanager->CreateEvent( "game_round_restart" );
if ( event )
{		
  DevMsg(1, "Fired game_round_restart event\n");
  // fire the event
  gameeventmanager->FireEvent( event );
}

Client Side

As part of the reset process client side entities and decals also have to be cleared up, this is done by listening for the game_round_restart event that was fired by the server during the reset process. The best place to do this is in clientmode_shared.cpp.

Add a listener to ClientModeShared::Init()

gameeventmanager->AddListener( this, "game_round_restart", false );

Finally put the following at the end of ClientModeShared::FireGameEvent

else if ( Q_strcmp( "game_round_restart", eventname ) == 0 )
{
  // recreate all client side physics props
  C_PhysPropClientside::RecreateAll();
// Just tell engine to clear decals engine->ClientCmd( "r_cleardecals\n" );
tempents->Clear();
//stop any looping sounds enginesound->StopAllSounds( true );
Soundscape_OnStopAllSounds(); // Tell the soundscape system. }