Custom loading screen

From Valve Developer Community
Jump to: navigation, search
Showing the final results, in this case, loading a custom screen upon detecting a defined map. Note: The RES file for the loading pop-up was edited in order to remove borders so the background isn't "cut-off".

The following code allows us to have custom loading screens when loading maps, either unique ones depending on the map name, or a default one. The final aspect will make it look like in No More Room in Hell, or Obsidian Conflict.

This method overrides GameUI and is based on what it was hinted at this email from HLCoders.

Credit me (Maestra Fénix) if you want to use this, that's the sole condition.


  • A mod running on Source SDK 2013
  • Knowledge of C++, RES, and VMT / VTF files.


Place the following two files somewhere in client (if possible, in a subfolder in order to keep it tidy):


//======= Maestra Fenix, 2017 ==================================================//
// Purpose: Map load background panel


#ifdef _WIN32
#pragma once

#include "vgui\ISurface.h"
#include <vgui_controls/EditablePanel.h>
#include <vgui_controls\ImagePanel.h>
#include "ienginevgui.h"

// Purpose: 
class CMapLoadBG : public vgui::EditablePanel
	DECLARE_CLASS_SIMPLE( CMapLoadBG, vgui::EditablePanel);

	// Construction
	CMapLoadBG( char const *panelName );

	void SetNewBackgroundImage( char const *imageName );

	void ApplySchemeSettings( vgui::IScheme *pScheme );

	vgui::ImagePanel *m_pBackground;



//======= Maestra Fenix, 2017 ==================================================//
// Purpose: Map load background panel

#include "cbase.h"
#include "mapload_background.h"

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

using namespace vgui;

// Purpose: Constructor
CMapLoadBG::CMapLoadBG( char const *panelName ) : EditablePanel( NULL, panelName )
	VPANEL toolParent = enginevgui->GetPanel( PANEL_GAMEUIDLL );
	SetParent( toolParent );

	// Fenix: We load a RES file rather than create the element here for taking advantage of the "F" parameter for wide and tall
	// Is the sole thing that makes fill the background to the entire screen regardless of the texture size
	// Congratulations to Valve for once again give options to only one side and not both
	LoadControlSettings( "resource/loadingdialogbackground.res" );

	m_pBackground = FindControl<ImagePanel>( "LoadingImage", true );

// Purpose: Destructor
	// None

// Purpose: 
void CMapLoadBG::ApplySchemeSettings( IScheme *pScheme )
	BaseClass::ApplySchemeSettings( pScheme );

	int iWide, iTall;
	surface()->GetScreenSize( iWide, iTall );
	SetSize( iWide, iTall );

// Purpose: Sets a new background on demand
void CMapLoadBG::SetNewBackgroundImage( char const *imageName )
	m_pBackground->SetImage( imageName );


Go to clientmode_shared.cpp and add the following between lines 42 and 44 (after #if defined( _X360 ) and before #if defined( REPLAY_ENABLED )):

// Fenix: Needed for the custom background loading screens
#include "GameUI/IGameUI.h"
#include "mapload_background.h"

Add this following snippet between lines 77 and 79 (after static vgui::HContext s_hVGuiContext = DEFAULT_VGUI_CONTEXT; and before ConVar cl_drawhud):

// Fenix: Needed for the custom background loading screens
// See interface.h/.cpp for specifics: basically this ensures that we actually Sys_UnloadModule the dll and that we don't call Sys_LoadModule 
// over and over again.
static CDllDemandLoader g_GameUI( "GameUI" );

Lines 87 and 89 (after extern bool IsInCommentaryMode( void ); and before #ifdef VOICE_VOX_ENABLE):

// Fenix: Needed for the custom background loading screens
CMapLoadBG *pPanelBg;
IMaterial *pMatMapBg;

In ClientModeShared(), add the following after line 278 (m_nRootSize[ 0 ] = m_nRootSize[ 1 ] = -1;):

// Fenix: Needed for the custom background loading screens
pPanelBg = NULL;
pMatMapBg = NULL;

At Init(), after line 368 (HOOK_MESSAGE( Rumble );):

	// Fenix: Custom background loading screens - Injects the custom panel at the loading screen
	CreateInterfaceFn gameUIFactory = g_GameUI.GetFactory();
	if ( gameUIFactory )
		IGameUI *pGameUI = (IGameUI *)gameUIFactory( GAMEUI_INTERFACE_VERSION, NULL );
		if ( pGameUI )
			// Insert custom loading panel for the loading dialog
			pPanelBg = new CMapLoadBG( "Background" );
			pPanelBg->InvalidateLayout( false, true );
			pPanelBg->SetVisible( false );
			pPanelBg->MakePopup( false );
			pGameUI->SetLoadingBackgroundDialog( pPanelBg->GetVPanel() );

To end this, add the following in LevelInit( const char *newmap ) at line 832 (after enginesound->SetPlayerDSP( filter, 0, true );):

// Fenix: Custom background loading screens - decides if use a loading screen for a map or a default one
#ifdef _WIN32
	char szMapBgName[MAX_PATH];
#else	// !_WIN32
	char szMapBgName[PATH_MAX];
#endif	// _WIN32
	Q_snprintf( szMapBgName, sizeof( szMapBgName ), "vgui/loading/maps/%s", newmap );

	pMatMapBg = materials->FindMaterial( szMapBgName, TEXTURE_GROUP_OTHER );

	if ( !pMatMapBg->IsErrorMaterial() )
		Q_snprintf( szMapBgName, sizeof( szMapBgName ), "loading/maps/%s", newmap );
		pPanelBg->SetNewBackgroundImage( szMapBgName );
		pPanelBg->SetNewBackgroundImage( "loading/default" );

Here ends all the work at the code. Time to create the RES file and the VMT and VTF files.



Create this RES file at the resource folder present on the main mod folder:

		"ControlName"		"ImagePanel"
		"fieldName"			"LoadingImage"
		"xpos"				"0"
		"ypos"				"0"
		"wide"				"f-1"
		"tall"				"f-1"
		"autoResize"		"0"
		"pinCorner"			"0"
		"visible"			"1"
		"enabled"			"1"
		"tabPosition"		"0"
		"border"			"1"
		"image"				"loading/default" //Leaving it empty will make appear a grey background the first time on map load
		"scaleImage"		"1"


Head to materials/vgui/ and create a new folder called loading. In there, create a VMT called default with the following code:

	"$basetexture"   "vgui/loading/default"
	"$vertexcolor"   "1"
	"$vertexalpha"   "1"
	"$ignorez"       "1"
	"$no_fullbright" "1"
	"$nolod"         "1"

You are free to create a VTF texture with the same name of any size, although I recommend being 2048x1024 big, with No Mipmap, No Level of Detail and No Minimum Mipmap flags checked.

This will be used as the default loading screen in case no defined loading screen for a map is found.

Create now a subfolder called maps/, in there you can place the VMTs and VTFs with the name of the maps desired (example: ep2_outland_03).

After this step, the feature is ready to be tested.


  • This code was made with having as a reference the emails at HLCoders present above and looking at how NMRIH loading screens appear. Aside from that, all was guesswork and trial & error. Likely there can be better methods out there, but couldn't find other ways. Those teams who did refuse to share their secrets.
  • As seen in NMRIH and OC, the checks about if a map has a present custom loading screen aren't made until late in the load. Other mods apparently were able to do it instantly, but IDK how they did that without engine access.
  • This is an obvious one, but this code was very old and can be improved, like having different default loading screens, or avoiding throwing an error in console (visible with developer 2) if it can't find one.