Custom loading screen: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
(Created page with "File:Custom loading screen test.jpg|thumb|600px|Showing the final results, in this case, loading a custom screen upon detecting a defined map. Note: The RES file for the loa...")
 
m (url add)
 
(5 intermediate revisions by 2 users not shown)
Line 1: Line 1:
[[File:Custom loading screen test.jpg|thumb|600px|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". ]]
[[File:Custom loading screen test.jpg|thumb|600px|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 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.
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 as seen in [https://developer.valvesoftware.com/wiki/Override_GameUI here] and based on what it was hinted on [https://www.mail-archive.com/hlcoders@list.valvesoftware.com/msg26845.html this email from HLCoders].
This method [[Override GameUI|overrides GameUI]] and is based on what it was hinted at [https://www.mail-archive.com/hlcoders@list.valvesoftware.com/msg26845.html this email from HLCoders].


Credit me ( Maestra Fénix ) if you want to use this, that's the sole condition.
Credit me ([[User:MaestroFenix|Maestra Fénix]]) if you want to use this, that's the sole condition.
 
 
==Requirements==


== Requirements ==
* A mod running on [[Source SDK 2013]]
* A mod running on [[Source SDK 2013]]
* Knowledge of C++, RES, and VMT / VTF files.
* Knowledge of C++, RES, and VMT / VTF files.


== Code ==
== Code ==
 
Place the following two files somewhere in <code>client</code> (if possible, in a subfolder in order to keep it tidy):
Place the following two files somewhere in <code>client</code> ( if possible, in a subfolder in order to keep it tidy ):


=== mapload_background.h ===
=== mapload_background.h ===
<div style="max-height:20em; overflow:auto;">
<source lang=cpp>
<source lang=cpp>
//======= Maestra Fenix, 2017 ==================================================//
//======= Maestra Fenix, 2017 ==================================================//
Line 26: Line 21:
//
//
//==============================================================================//
//==============================================================================//
#ifndef MAPLOAD_BACKGROUND_H
#ifndef MAPLOAD_BACKGROUND_H
#define MAPLOAD_BACKGROUND_H
#define MAPLOAD_BACKGROUND_H
Line 39: Line 35:


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose:
// Purpose:  
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
class CMapLoadBG : public vgui::EditablePanel
class CMapLoadBG : public vgui::EditablePanel
Line 46: Line 42:


public:
public:
// Construction
// Construction
CMapLoadBG( char const *panelName );
CMapLoadBG( char const *panelName );
~CMapLoadBG();
~CMapLoadBG();


void SetNewBackgroundImage( char const *imageName );
void SetNewBackgroundImage( char const *imageName );


protected:
protected:
void ApplySchemeSettings( vgui::IScheme *pScheme );
void ApplySchemeSettings( vgui::IScheme *pScheme );


private:
private:
vgui::ImagePanel *m_pBackground;
vgui::ImagePanel *m_pBackground;
};
};


#endif // !MAPLOAD_BACKGROUND_H
#endif // !MAPLOAD_BACKGROUND_H
</source>
</source>
</div>


=== mapload_background.cpp ===
=== mapload_background.cpp ===
<div style="max-height:20em; overflow:auto;">
<source lang=cpp>
<source lang=cpp>
//======= Maestra Fenix, 2017 ==================================================//
//======= Maestra Fenix, 2017 ==================================================//
Line 83: Line 75:


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose:
// Purpose: Constructor
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CMapLoadBG::CMapLoadBG( char const *panelName ) : EditablePanel( NULL, panelName )
CMapLoadBG::CMapLoadBG( char const *panelName ) : EditablePanel( NULL, panelName )
Line 90: Line 82:
SetParent( toolParent );
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
// 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
// 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
// Congratulations to Valve for once again give options to only one side and not both
LoadControlSettings( "resource/loadingdialogbackground.res" );
LoadControlSettings( "resource/loadingdialogbackground.res" );


Line 99: Line 91:


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose:
// Purpose: Destructor
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CMapLoadBG::~CMapLoadBG()
CMapLoadBG::~CMapLoadBG()
Line 107: Line 99:


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose:
// Purpose:  
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CMapLoadBG::ApplySchemeSettings( IScheme *pScheme )
void CMapLoadBG::ApplySchemeSettings( IScheme *pScheme )
Line 126: Line 118:
}
}
</source>
</source>
</div>


 
=== clientmode_shared.cpp ===
=== clientmode_share.cpp ===
Go to '''clientmode_shared.cpp''' and add the following between lines 42 and 44 (after <code>#if defined( _X360 )</code> and before <code>#if defined( REPLAY_ENABLED )</code>):
Go to <code>clientmode_share.cpp</code> and add the following between lines 42 and 44 ( after <code>#if defined( _X360 )</code> and before <code>#if defined( REPLAY_ENABLED )</code> ):
 
<div style="max-height:20em; overflow:auto;">
<source lang=cpp>
<source lang=cpp>
//Fenix: Needed for the custom background loading screens
// Fenix: Needed for the custom background loading screens
#include "GameUI/IGameUI.h"
#include "GameUI/IGameUI.h"
#include "mapload_background.h"
#include "mapload_background.h"
</source>
</source>
</div>
Add this following snippet between lines 77 and 79 ( after <code>static vgui::HContext s_hVGuiContext = DEFAULT_VGUI_CONTEXT;</code> and before <code>ConVar cl_drawhud</code> ):


<div style="max-height:20em; overflow:auto;">
Add this following snippet between lines 77 and 79 (after <code>static vgui::HContext s_hVGuiContext = DEFAULT_VGUI_CONTEXT;</code> and before <code>ConVar cl_drawhud</code>):
<source lang=cpp>
<source lang=cpp>
//Fenix: Needed for the custom background loading screens
// 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  
// 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.
// over and over again.
static CDllDemandLoader g_GameUI( "GameUI" );
static CDllDemandLoader g_GameUI( "GameUI" );
</source>
</source>
</div>
Lines 87 and 89 ( after <code>extern bool IsInCommentaryMode( void );</code> and before <code>#ifdef VOICE_VOX_ENABLE</code> ):


<div style="max-height:20em; overflow:auto;">
Lines 87 and 89 (after <code>extern bool IsInCommentaryMode( void );</code> and before <code>#ifdef VOICE_VOX_ENABLE</code>):
<source lang=cpp>
<source lang=cpp>
//Fenix: Needed for the custom background loading screens
// Fenix: Needed for the custom background loading screens
CMapLoadBG *pPanelBg;
CMapLoadBG *pPanelBg;
IMaterial *pMatMapBg;
IMaterial *pMatMapBg;
</source>
</source>
</div>


In <code>ClientModeShared()</code>, add the following after line 278 ( <code>m_nRootSize[ 0 ] = m_nRootSize[ 1 ] = -1;</code> ):
In <code>ClientModeShared()</code>, add the following after line 278 (<code>m_nRootSize[ 0 ] = m_nRootSize[ 1 ] = -1;</code>):
 
<div style="max-height:20em; overflow:auto;">
<source lang=cpp>
<source lang=cpp>
//Fenix: Needed for the custom background loading screens
// Fenix: Needed for the custom background loading screens
pPanelBg = NULL;
pPanelBg = NULL;
pMatMapBg = NULL;
pMatMapBg = NULL;
</source>
</source>
</div>


At <code>Init()</code>, after line 368 ( <code>HOOK_MESSAGE( Rumble );</code> ):
At <code>Init()</code>, after line 368 (<code>HOOK_MESSAGE( Rumble );</code>):
 
<div style="max-height:20em; overflow:auto;">
<source lang=cpp>
<source lang=cpp>
//Fenix: Custom background loading screens - Injects the custom panel at the loading screen
// Fenix: Custom background loading screens - Injects the custom panel at the loading screen
CreateInterfaceFn gameUIFactory = g_GameUI.GetFactory();
CreateInterfaceFn gameUIFactory = g_GameUI.GetFactory();
if ( gameUIFactory )
if ( gameUIFactory )
{
{
IGameUI *pGameUI = ( IGameUI * )gameUIFactory( GAMEUI_INTERFACE_VERSION, NULL );
IGameUI *pGameUI = (IGameUI *)gameUIFactory( GAMEUI_INTERFACE_VERSION, NULL );
if ( pGameUI )
if ( pGameUI )
{
{
// insert custom loading panel for the loading dialog
// Insert custom loading panel for the loading dialog
pPanelBg = new CMapLoadBG( "Background" );
pPanelBg = new CMapLoadBG( "Background" );
pPanelBg->InvalidateLayout( false, true );
pPanelBg->InvalidateLayout( false, true );
pPanelBg->SetVisible(false);
pPanelBg->SetVisible( false );
pPanelBg->MakePopup( false );
pPanelBg->MakePopup( false );
pGameUI->SetLoadingBackgroundDialog( pPanelBg->GetVPanel() );
pGameUI->SetLoadingBackgroundDialog( pPanelBg->GetVPanel() );
Line 191: Line 167:
}
}
</source>
</source>
</div>




To end this, add the following in <code>LevelInit( const char *newmap )</code> at line 832 ( after <code>enginesound->SetPlayerDSP( filter, 0, true );</code> ):
To end this, add the following in <code>LevelInit( const char *newmap )</code> at line 832 (after <code>enginesound->SetPlayerDSP( filter, 0, true );</code>):
 
<div style="max-height:20em; overflow:auto;">
<source lang=cpp>
<source lang=cpp>
//Fenix: Custom background loading screens - decides if use a loading screen for a map or a default one
// Fenix: Custom background loading screens - decides if use a loading screen for a map or a default one
 
#ifdef _WIN32
#ifdef _WIN32
char szMapBgName[MAX_PATH];
char szMapBgName[MAX_PATH];
Line 220: Line 192:
}
}
</source>
</source>
</div>


== If you want to hide loading dialogue ==
The code for the [[GameUI]] is closed, but some hack can be made to hide the loading dialog.
In <code>mapload_background.h</code> add override for method <code>OnMessage</code> after <code>void SetNewBackgroundImage(char const* imageName);</code> line:
<source lang=cpp>
void OnMessage(const KeyValues* params, vgui::VPANEL fromPanel);
</source>
Next at the end of <code>mapload_background.cpp</code> add following code:
<source lang=cpp>
// URAKOLOUY5: Recursively find all elements with this name and unparent them
void SetVisibilityRecursive(vgui::VPANEL parentPanel, const char* targetPanelName, int depth = 0) {
    int numChildren = vgui::ipanel()->GetChildCount(parentPanel);
    for (int i = 0; i < numChildren; i++) {
        vgui::VPANEL childPanel = vgui::ipanel()->GetChild(parentPanel, i);
        const char* childPanelName = vgui::ipanel()->GetName(childPanel);
        if (V_strcmp(childPanelName, targetPanelName) == 0)
            vgui::ipanel()->SetParent(childPanel, NULL);
        SetVisibilityRecursive(childPanel, targetPanelName, depth + 1);
    }
}
// URAKOLOUY5: Override OnMessage to detect "Activate" message and call SetVisibilityRecursive
void CMapLoadBG::OnMessage(const KeyValues* params, vgui::VPANEL fromPanel) {
    if (V_strcmp(params->GetName(), "Activate") == 0) {
        vgui::VPANEL rootPanel = enginevgui->GetPanel(PANEL_GAMEUIDLL);
        SetVisibilityRecursive(rootPanel, "LoadingDialog");
    }
    // Call the base class OnMessage to handle messages we didn't process
    BaseClass::OnMessage(params, fromPanel);
}
</source>
This hack unparents LoadingDialogue on every loading (it finds <code>LoadingDialog</code> by name in <code>PANEL_GAMEUIDLL</code>.
Actually, you can use this code to "hide" other elements too.
<hr>


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


== RES and VMT / VTF ==
== RES and VMT / VTF ==
=== loadingdialogbackground.res ===
=== loadingdialogbackground.res ===
Create this RES file at the <code>resource</code> folder present on the main mod folder:
Create this RES file at the <code>resource</code> folder present on the main mod folder:


<div style="max-height:20em; overflow:auto;">
<source lang=cpp>
<source lang=cpp>
"Resource/loadingdialogbackground.res"
"Resource/loadingdialogbackground.res"
Line 254: Line 265:
}
}
</source>
</source>
</div>


=== VMT & VTF ===
=== VMT & VTF ===
Head to <code>materials/vgui/</code> and create a new folder called <code>loading</code>. In there, create a VMT called <code>default</code> with the following code:
Head to <code>materials/vgui/</code> and create a new folder called <code>loading</code>. In there, create a VMT called <code>default</code> with the following code:
<div style="max-height:20em; overflow:auto;">
<source lang=cpp>
<source lang=cpp>
"UnlitGeneric"
"UnlitGeneric"
{
{
"$basetexture" "vgui/loading/default"
"$basetexture"   "vgui/loading/default"
"$vertexcolor" 1
"$vertexcolor"1"
"$vertexalpha" 1
"$vertexalpha"1"
"$ignorez" 1
"$ignorez"      "1"
"$no_fullbright" "1"
"$no_fullbright" "1"
"$nolod" "1"
"$nolod"         "1"
}
}
</source>
</source>
</div>


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


This will be used as the default loading screen in case no defined loading screen for a map is found.
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 <code>maps</code>, in there you can place the VMTs and VTFs with the name of the maps desired ( example: <code>ep2_outland_03</code> ).
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.
After this step, the feature is ready to be tested.


== Notes ==
== Notes ==
* This code was made with having as a reference the Emails at HLCoders present above, and looking how NMRIH loading screens appears. Aside of 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 refused to share their secrets.
* 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.
* 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 <code>Developer 2</code> ) if it can't find one.
* 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 <code>developer 2</code>) if it can't find one.
 
* Reference for "hiding" loading dialogue was found in [[User:Psycommando/VGUI_OverrideTest]].


* Newer code repo based on this tutorial:  [https://github.com/Cvoxalury/sdk2013-custom-loading-screens Custom loading screens and loading bars for SDK2013 SP/MP]


[[Category:Free source code]]
[[Category:Free source code]]
[[Category:VGUI]]
[[Category:VGUI]]
[[Category:Programming]]
[[Category:Programming]]

Latest revision as of 19:20, 30 September 2024

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.

Requirements

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

Code

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

mapload_background.h

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

#ifndef MAPLOAD_BACKGROUND_H
#define MAPLOAD_BACKGROUND_H

#ifdef _WIN32
#pragma once
#endif

#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);

public:
	// Construction
	CMapLoadBG( char const *panelName );
	~CMapLoadBG();

	void SetNewBackgroundImage( char const *imageName );

protected:
	void ApplySchemeSettings( vgui::IScheme *pScheme );

private:
	vgui::ImagePanel *m_pBackground;
};

#endif	// !MAPLOAD_BACKGROUND_H

mapload_background.cpp

//======= 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
//-----------------------------------------------------------------------------
CMapLoadBG::~CMapLoadBG()
{
	// 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 );
}

clientmode_shared.cpp

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 );
	}
	else
	{
		pPanelBg->SetNewBackgroundImage( "loading/default" );
	}

If you want to hide loading dialogue

The code for the GameUI is closed, but some hack can be made to hide the loading dialog.

In mapload_background.h add override for method OnMessage after void SetNewBackgroundImage(char const* imageName); line:

void OnMessage(const KeyValues* params, vgui::VPANEL fromPanel);

Next at the end of mapload_background.cpp add following code:

// URAKOLOUY5: Recursively find all elements with this name and unparent them
void SetVisibilityRecursive(vgui::VPANEL parentPanel, const char* targetPanelName, int depth = 0) {
    int numChildren = vgui::ipanel()->GetChildCount(parentPanel);
    for (int i = 0; i < numChildren; i++) {
        vgui::VPANEL childPanel = vgui::ipanel()->GetChild(parentPanel, i);
        const char* childPanelName = vgui::ipanel()->GetName(childPanel);

        if (V_strcmp(childPanelName, targetPanelName) == 0)
            vgui::ipanel()->SetParent(childPanel, NULL);

        SetVisibilityRecursive(childPanel, targetPanelName, depth + 1);
    }
}

// URAKOLOUY5: Override OnMessage to detect "Activate" message and call SetVisibilityRecursive
void CMapLoadBG::OnMessage(const KeyValues* params, vgui::VPANEL fromPanel) {
    if (V_strcmp(params->GetName(), "Activate") == 0) {
        vgui::VPANEL rootPanel = enginevgui->GetPanel(PANEL_GAMEUIDLL);
        SetVisibilityRecursive(rootPanel, "LoadingDialog");
    }

    // Call the base class OnMessage to handle messages we didn't process
    BaseClass::OnMessage(params, fromPanel);
}

This hack unparents LoadingDialogue on every loading (it finds LoadingDialog by name in PANEL_GAMEUIDLL.

Actually, you can use this code to "hide" other elements too.


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

RES and VMT / VTF

loadingdialogbackground.res

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

"Resource/loadingdialogbackground.res"
{
	"LoadingImage"
	{
		"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"
	}
}

VMT & VTF

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

"UnlitGeneric"
{
	"$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.

Notes

  • 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.