Custom loading screen

From Valve Developer Community
Revision as of 22:44, 9 December 2019 by MaestroFenix (talk | contribs) (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...")

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
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 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 here and based on what it was hinted on 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:
//-----------------------------------------------------------------------------
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:
//-----------------------------------------------------------------------------
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_share.cpp

Go to clientmode_share.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" );
	}


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 2048 x 1024 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 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.
  • 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.