Adding an HTML Main Menu: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
mNo edit summary
Line 380: Line 380:
You can improve this implementation by yourself, and this is an older implementation which my mod (Brutal: Source) used.
You can improve this implementation by yourself, and this is an older implementation which my mod (Brutal: Source) used.


=== Remember!!! ==
== Remember!!! ==
Remember to credit the following people:
Remember to credit the following people:
- YourLocalMoon - base implementation
- YourLocalMoon - base implementation
- DecalOverdose - fixes for Linux
- DecalOverdose - fixes for Linux

Revision as of 05:53, 8 November 2025

This is a tutorial on adding an HTML main menu screen, but please note that performance is pretty bad while having it.

This guide expects you to have knowledge of overriding the GameUI.

Tutorial

Firstly you want to add these 2 files:

basemenu.cpp:

//========= Copyright OpenMod (slightly modified by Brutal: Source Devs), All rights reserved. ============//
//
// Purpose: 
//
//=========================================================================================================//
#include "cbase.h"
#include "basemenu.h"

#include "vgui/ILocalize.h"
#include "vgui/IPanel.h"
#include "vgui/ISurface.h"
#include "vgui/ISystem.h"
#include "vgui/IVGui.h"
#include "ienginevgui.h"
#include <engine/IEngineSound.h>
#include "filesystem.h"

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

using namespace vgui;

static CDllDemandLoader g_GameUIDLL( "GameUI" );

RootPanel *guiroot = NULL;

void OverrideRootUI()
{
	if( !OverrideUI->GetPanel() )
	{
		OverrideUI->Create(NULL);
	}
	if( guiroot->GetGameUI() )
	{
		guiroot->GetGameUI()->SetMainMenuOverride( guiroot->GetVPanel() );
		return;
	}
}

RootPanel::RootPanel(VPANEL parent) : Panel( NULL, "OverrideUIRootPanel" )
{
	SetParent(parent);
	guiroot = this;

	m_bCopyFrameBuffer = false;
	gameui = NULL;

	LoadGameUI();

	m_ExitingFrameCount = 0;

    m_pHTMLPanel = new vgui::HTML(this, "HTMLPanel");

	vgui::ivgui()->AddTickSignal(GetVPanel(), 100);
}

void RootPanel::OnThink()
{
	m_pHTMLPanel->Refresh();

    if (engine->IsInGame())
        m_pHTMLPanel->RunJavascript("togglevisible(true);");
    else
        m_pHTMLPanel->RunJavascript("togglevisible(false);");

    m_pHTMLPanel->RequestFocus();
}

IGameUI *RootPanel::GetGameUI()
{
	if( !gameui )
	{
		if ( !LoadGameUI() )
			return NULL;
	}

	return gameui;
}

bool RootPanel::LoadGameUI()
{
	if( !gameui )
	{
		CreateInterfaceFn gameUIFactory = g_GameUIDLL.GetFactory();
		if ( gameUIFactory )
		{
			gameui = (IGameUI *) gameUIFactory(GAMEUI_INTERFACE_VERSION, NULL);
			if( !gameui )
			{
				return false;
			}
		}
		else
		{
			return false;
		}
	}
	return true;
}

RootPanel::~RootPanel()
{
	gameui = NULL;
	g_GameUIDLL.Unload();
}

void RootPanel::ApplySchemeSettings(IScheme *pScheme)
{
	BaseClass::ApplySchemeSettings(pScheme);

	// Resize the panel to the screen size
	// Otherwise, it'll just be in a little corner
	int wide, tall;
	vgui::surface()->GetScreenSize(wide, tall);
	SetSize(wide,tall);

    const char *gameDir = engine->GetGameDirectory();
    char filePath[256];
    // Windows can handle raw paths, while Linux (and other POSIX platforms) only handle file:// paths.
#if defined(_WIN32)
    sprintf( filePath, "%s/html/menu.html", gameDir ); // You can replace menu.html with whatever you want to name your HTML file
#else
    sprintf( filePath, "file://%s/html/menu.html", gameDir ); // You can replace menu.html with whatever you want to name your HTML file
#endif
    m_pHTMLPanel->SetBounds(0, 0, wide, tall);
    m_pHTMLPanel->OpenURL(filePath, nullptr, true);
    m_pHTMLPanel->RunJavascript("togglevisible(false);");
    m_pHTMLPanel->RequestFocus();
}

class COverrideInterface : public IOverrideInterface
{
private:
	RootPanel *MainMenu;
 
public:
	int UI_Interface( void )
	{
		MainMenu = NULL;
	}
 
	void Create( vgui::VPANEL parent )
	{
		MainMenu = new RootPanel(parent);
	}

	vgui::VPANEL GetPanel( void )
	{
		if ( !MainMenu )
			return NULL;
		return MainMenu->GetVPanel();
	}
 
	void Destroy( void )
	{
		if ( MainMenu )
		{
			MainMenu->SetParent( (vgui::Panel *)NULL );
			delete MainMenu;
		}
	}
 
    virtual RootPanel* GetMenuBase() override
    {
        return MainMenu;
    }
};
 
static COverrideInterface g_SMenu;
IOverrideInterface *OverrideUI = ( IOverrideInterface * )&g_SMenu;

basemenu.h:

#ifndef BASEMENU_H
#define BASEMENU_H
#ifdef _WIN32
#pragma once
#endif

#include "vgui_controls/Panel.h"
#include "GameUI/IGameUI.h"
#include <vgui/VGUI.h>
#include <vgui_controls/HTML.h>

namespace vgui
{
	class Panel;
}

void OverrideRootUI();
 
class RootPanel : public vgui::Panel
{
	DECLARE_CLASS_SIMPLE( RootPanel, vgui::Panel );
public:
	RootPanel(vgui::VPANEL parent);
	virtual ~RootPanel();

	IGameUI*		GetGameUI();

    virtual void	OnThink() override;

    // elements
    vgui::HTML *m_pHTMLPanel;

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

private:
	bool			LoadGameUI();

	int				m_ExitingFrameCount;
	bool			m_bCopyFrameBuffer;

	IGameUI*		gameui;
};

class IOverrideInterface
{
public:
	virtual void		Create( vgui::VPANEL parent ) = 0;
	virtual vgui::VPANEL	GetPanel( void ) = 0;
	virtual void		Destroy( void ) = 0;

    virtual RootPanel *GetMenuBase() = 0;
};

extern RootPanel *guiroot;
extern IOverrideInterface *OverrideUI;

#endif

Make sure to add these files to the VPC file of your game!!!

Now in cdll_client_int.cpp you want to include the file named "basemenu.h". Now right under:

IHaptics* haptics = NULL;// NVNT haptics system interface singleton

add:

RootPanel          	*IBaseMenu; // Base HTML Menu interface

Then inside the int CHLClient::Init( ... ) function, right under:

	ClientSteamContext().Activate();

add:

	if ( CommandLine()->FindParm( "-gameui" ) == 0 )
	{
		if ( !IBaseMenu )
		{
			OverrideUI->Create( NULL );
			IBaseMenu = OverrideUI->GetMenuBase();
			OverrideRootUI();
		}
	}

now in the function void CHLClient::Shutdown( void ) add: Right under:

	IGameSystem::ShutdownAllSystems();

add:

	if ( IBaseMenu )
  	{
    	IBaseMenu->~RootPanel();
  	}

Now on the top of the void CHLClient::LevelInitPostEntity( ) function, add:

	IBaseMenu->m_pHTMLPanel->RunJavascript("togglevisible(true);");
	IBaseMenu->m_pHTMLPanel->RequestFocus();

Then inside of the void CHLClient::LevelShutdown( void ) function and right under: tempents->LevelShutdown();

Add:

	IBaseMenu->m_pHTMLPanel->RunJavascript("togglevisible(false);");
	IBaseMenu->m_pHTMLPanel->RequestFocus();

Now you're gonna need to modify the VGUI2 code, so in vgui2/vgui_controls/HTML.cpp in the function void HTML::CHTMLFindBar::OnCommand right under BaseClass::OnCommand( pchCmd );

Add:

	RequestFocus();

Now right inside the void HTML::BrowserJSAlert(HTML_JSAlert_t* pCmd) function, replace everything with:

Add:

void HTML::BrowserJSAlert(HTML_JSAlert_t* pCmd)
{
    // check if alert
    if (strstr(pCmd->pchMessage, "cmd:") == pCmd->pchMessage)
    {
        const char* command = pCmd->pchMessage + 4;
		if (command && strlen(command) > 0)
		{
			engine->ClientCmd_Unrestricted(command);
			RequestFocus();
		}
		RequestFocus();
    }
    else
    {
        MessageBox* pDlg = new MessageBox(m_sCurrentURL, (const char*)pCmd->pchMessage, this);
        pDlg->AddActionSignalTarget(this);
        pDlg->SetCommand(new KeyValues("DismissJSDialog", "result", false));
        pDlg->DoModal();
    }
	RequestFocus();
}

And now you should have an error where the game ui only shows -105, so inside of html/menu.html add:

<!DOCTYPE html>
<html>
	<head>
		<style>
			body {
				background-color: gray;
			}
		</style>
		<script>
			function togglevisible(show) {
				var div = document.getElementById("InGameOnly");
				if (div) {
					div.style.display = show ? "block" : "none";
				}
			}
		</script>
	</head>
	<body>
		<main>
			<h1>My Awesome MP Mod!</h1>
			<div id="InGameOnly">
				<button onclick="alert('cmd:gamemenucommand ResumeGame');">RESUME GAME</button> <br /> <br />
				<button onclick="alert('cmd:gamemenucommand Disconnect');">DISCONNECT</button> <br /> <br />
				<button onclick="alert('cmd:gamemenucommand OpenPlayerListDialog');">PLAYER LIST</button> <br /> <br />
			</div>
			<button onclick="alert('cmd:gamemenucommand OpenServerBrowser');">SERVER BROWSER</button> <br /> <br />
			<button onclick="alert('cmd:gamemenucommand OpenCreateMultiplayerGameDialog');">CREATE SERVER</button> <br /> <br />
			<button onclick="alert('cmd:gamemenucommand OpenOptionsDialog');">OPTIONS</button> <br /> <br />
			<button onclick="alert('cmd:gamemenucommand Quit');">QUIT</button> <br /> <br /> <br />
		</main>
	</body>
</html>

You can add more buttons by using alert('cmd:whatever your command is'); Now in gameinfo.txt remove what ever is inside of title1, title2, or title3. And you should now have an HTML main menu UI.

You can improve this implementation by yourself, and this is an older implementation which my mod (Brutal: Source) used.

Remember!!!

Remember to credit the following people: - YourLocalMoon - base implementation - DecalOverdose - fixes for Linux