Fixing VGUI Screens in Source 2013 Singleplayer

From Valve Developer Community
Jump to: navigation, search

About

This is a comprehensive guide to fixing the Source SDK 2013 mod in-game VGUI screens. Unlike my previous tutorial, for SDK 2013, this one does not give step-by-step directions on implementing the other two required fixes. This will be changed in the future, after I can put together the steps from between them into one well-written, cohesive, easy-to-understand set of instructions.

Step 1: Crash Fix

Follow the directions here: VGUI_Screen_Creation

Step 2: Interactivity

Follow the directions here: Interactive_Ingame_VGUI_Panels

Step 3: Client-Server Interface

Upon clicking a button on a VGUI panel, a console command will be sent to the server, but cannot be used to fire any outputs. So here, we are going to make VGUI screens fire outputs on the server using client commands.

In server/vguiscreen.h, paste the following at the bottom of the definition for CVGuiScreen:

public:
	COutputEvent Output1;
	COutputEvent Output2;
	COutputEvent Output3;
	COutputEvent Output4;
	COutputEvent Output5;
	COutputEvent Output6;
	COutputEvent Output7;
	COutputEvent Output8;
	COutputEvent Output9;
	COutputEvent Output10;
	COutputEvent Output11;
	COutputEvent Output12;
	COutputEvent Output13;
	COutputEvent Output14;
	COutputEvent Output15;
	COutputEvent Output16;

And in the respective vguiscreen.cpp, paste this after BEGIN_DATADESC( CVGuiScreen ):

	DEFINE_OUTPUT( Output1, "Output1" ),
	DEFINE_OUTPUT( Output2, "Output2" ),
	DEFINE_OUTPUT( Output3, "Output3" ),
	DEFINE_OUTPUT( Output4, "Output4" ),
	DEFINE_OUTPUT( Output5, "Output5" ),
	DEFINE_OUTPUT( Output6, "Output6" ),
	DEFINE_OUTPUT( Output7, "Output7" ),
	DEFINE_OUTPUT( Output8, "Output8" ),
	DEFINE_OUTPUT( Output9, "Output9" ),
	DEFINE_OUTPUT( Output10, "Output10" ),
	DEFINE_OUTPUT( Output11, "Output11" ),
	DEFINE_OUTPUT( Output12, "Output12" ),
	DEFINE_OUTPUT( Output13, "Output13" ),
	DEFINE_OUTPUT( Output14, "Output14" ),
	DEFINE_OUTPUT( Output15, "Output15" ),
	DEFINE_OUTPUT( Output16, "Output16" ),

This way, we register the ability for up to 16 outputs defined in Hammer to be able to be fired in-game. They must be public, because we'll be firing them from another file.

Now we have to modify c_vguiscreen.cpp (in the client) so that it detects special commands (ex. out1, out2, etc.) and tells the server to find the VGUI screen entity through its entity index and fire its respective output. To go about doing this, open up c_vguiscreen.cpp, find the function CVGuiScreenPanel::OnCommand, and add at the top of the definition:

	if ( stricmp( command, "out1" ) == 0
		|| stricmp( command, "out2" ) == 0
		|| stricmp( command, "out3" ) == 0
		|| stricmp( command, "out4" ) == 0
		|| stricmp( command, "out5" ) == 0
		|| stricmp( command, "out6" ) == 0
		|| stricmp( command, "out7" ) == 0
		|| stricmp( command, "out8" ) == 0
		|| stricmp( command, "out9" ) == 0
		|| stricmp( command, "out10" ) == 0
		|| stricmp( command, "out11" ) == 0
		|| stricmp( command, "out12" ) == 0
		|| stricmp( command, "out13" ) == 0
		|| stricmp( command, "out14" ) == 0
		|| stricmp( command, "out15" ) == 0
		|| stricmp( command, "out16" ) == 0 )
	{
		char entindex[8];
		itoa( this->GetEntity()->entindex(), entindex, 10 ); //Radix is base 10 (decimal)
		char newcommand[16] = { ' ' };
		strcat( newcommand, command );
		strcat( newcommand, " " );
		strcat( newcommand, entindex );
		engine->ClientCmd_Unrestricted( const_cast<char *>( newcommand ) );
		BaseClass::OnCommand( newcommand );
		return;
	}

What this does is fetch the entity index of the VGUI entity, of which is shared between the client and server, and append it to the command, only if the command is out(1-16). The return at the end is to prevent from the command being sent twice.

Now, all that's left is to allow CBasePlayer::ClientCommand in player.cpp (server) to recognize these commands. First, at the top, we must add an include entry for vguiscreen.h, like so:

#include "vguiscreen.h"

It must be before tier0/memdbgon.h. Without it, the following code will have errors since we try to access an incomplete class.

Not all 16 options in the function will be posted (as it's far too long), but here's one of the 16 entries to place at the bottom of CBasePlayer::ClientCommand:

	else if ( stricmp( cmd, "out1" ) == 0 )
	{
		int entindex = atoi( args[1] );
		if ( entindex )
		{
			IHandleEntity* hEnt = gEntList.LookupEntityByNetworkIndex( entindex );
			if ( hEnt )
			{
				CVGuiScreen* screen = (CVGuiScreen *)gEntList.LookupEntity( hEnt->GetRefEHandle() );
				if ( screen )
				{
					if ( screen->entindex() == entindex )
					{
						screen->Output1.FireOutput( this, NULL );
						return true;
					}
				}
			}
		}
		else
		{
			Warning( "No ent index specified for VGUI output\n" );
		}
	}

This is for the first output. For the other 15 options, copy-paste the code, then change...

else if ( stricmp( cmd, "out1" ) == 0 )
...
screen->Output1.FireOutput( this, NULL );

...depending on which output you intend to implement. So, if you wanted to add for the next option (Output 2), it would become:

else if ( stricmp( cmd, "out2" ) == 0 )
...
screen->Output2.FireOutput( this, NULL );

What this code does is check if the incoming client command is out1. If it is, then we get the first argument and convert it to an integer, to be used as the entity index. After that, we check to see if there's an entity handle to a screen with this index - if there is, and if the index hasn't changed between frames, then we fire Output1 on that entity. Warning: This code assumes that we're dealing with a vgui_screen entity, but the index can point to anything. Do not call it using an index of a different type of entity or the engine could crash!

Now that we have the code configured to handle client commands, all that's left is to make the outputs recognizable to Hammer.

Step 4: FGDs & RES's

Lastly, open up your mod's FGD (I just took Valve's base.fgd and copied it into my mod's bin folder) and use this to replace the existing VGUI screen entry:

@BaseClass base(Targetname, Parentname, Angles) = vgui_screen_base
[
	panelname(string) : "Panel Name"
	overlaymaterial(string) : "Overlay Material" : "" : "Name of a material to overlay over the top of the VGUI screen. NOTE: This material must write Z for the VGUI screen to work."
	width(integer) : "Panel Width in World" : 32 : "Width of the panel in units."
	height(integer) : "Panel Height in World" : 32 : "Height of the panel in units."

	// Inputs
	input SetActive(void) : "Make the vgui screen visible."
	input SetInactive(void) : "Make the vgui screen invisible."
	output Output1(void) : "Fire output 1."
	output Output2(void) : "Fire output 2."
	output Output3(void) : "Fire output 3."
	output Output4(void) : "Fire output 4."
	output Output5(void) : "Fire output 5."
	output Output6(void) : "Fire output 6."
	output Output7(void) : "Fire output 7."
	output Output8(void) : "Fire output 8."
	output Output9(void) : "Fire output 9."
	output Output10(void) : "Fire output 10."
	output Output11(void) : "Fire output 11."
	output Output12(void) : "Fire output 12."
	output Output13(void) : "Fire output 13."
	output Output14(void) : "Fire output 14."
	output Output15(void) : "Fire output 15."
	output Output16(void) : "Fire output 16."
]

Next, you'll need to create the actual screen configurations, stored in scripts/screens/*.res. Here's an example, followed by a brief explanation on how to make the outputs work:

"screen_basic.res"
{
	"Background"
	{
		"ControlName"	"MaterialImage"
		"fieldName"		"Background"
		"xpos"			"0"
		"ypos"			"0"
		"zpos"			"-2"
		"wide"			"480"
		"tall"			"240"

		"material"		"vgui/screens/vgui_bg"
	}
	"DismantleButton"
	{
		"ControlName"	"MaterialButton"
		"fieldName"		"Dismantle"
		"xpos"			"78"
		"ypos"			"160"
		"wide"			"324"
		"tall"			"48"
		"autoResize"	"0"
		"pinCorner"		"0"
		"visible"		"1"
		"enabled"		"1"
		"tabPosition"	"2"
		"labelText"		"Dismantle"
		"textAlignment"	"center"
		"dulltext"		"0"
		"brighttext"	"0"
		"Default"		"0"
		"command"		"1"
		"paintborder"	"0"

		"enabledImage"
		{
			"material"	"vgui/screens/vgui_button_enabled"
			"color" "255 255 255 255"
		}

		"mouseOverImage"
		{
			"material"	"vgui/screens/vgui_button_hover"
			"color" "255 255 255 255"
		}

		"pressedImage"
		{
			"material"	"vgui/screens/vgui_button_pushed"
			"color" "255 255 255 255"
		}

		"disabledImage"
		{
			"material"	"vgui/screens/vgui_button_disabled"
			"color" "255 255 255 255"
		}
	}
}

In the "DismantleButton" section, you'll notice the field "command" "1". The VGUI screens, as implemented support a total of 16 outputs, and the command value is the ASCII representation of the number of the output you wish to fire. For example, "1" fires Output1. So long as you keep this pattern (integer values between 1 and 16, inclusive), things should work just fine.

Summary

If everything was done right, the in-game VGUI screens should now respond to input, fire output, be easily addressable in Hammer, and everything should be dandy. If there are any problems, bring them up in the discussion page and I'll do my best to address them as promptly as time allows.

Credits

VGUI_Screen_Creation

Interactive_Ingame_VGUI_Panels