User:Heoga: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
mNo edit summary
Line 4: Line 4:
This tutorial will detail how to add the necessary functionality to create a fully working ingame VGUI panel.  There are a few small flaws with the workings of the original VGUI panel presented in the source code that must be rectified before these panels will operate.
This tutorial will detail how to add the necessary functionality to create a fully working ingame VGUI panel.  There are a few small flaws with the workings of the original VGUI panel presented in the source code that must be rectified before these panels will operate.


This first problem is that CInput::ExtraMouseSample calls g_pClientMode->CreateMove() with a faked CUserCmd which does not have the correct button flags.  When DetermineVguiInputMode is called from within CreateMove with this bad input problems occur.  This can be fixed ifa boolean in an if function is added that will prevent the function running during the extra mouse samples.
This first problem is that <code>CInput::ExtraMouseSample</code> calls <code>g_pClientMode->CreateMove()</code> with a faked <code>CUserCmd</code> which does not have the correct button flags.  When <code>DetermineVguiInputMode</code> is called from within <code>CreateMove</code> with this bad input problems occur.  This can be fixed if a boolean is added that will prevent the function running during the extra mouse samples.


First in in_main.cpp Replace
First in <code>in_main.cpp</code> Replace
// Let the move manager override anything it wants to.
// Let the move manager override anything it wants to.
if ( g_pClientMode->CreateMove( frametime, cmd ) )
if ( g_pClientMode->CreateMove( frametime, cmd ) )

Revision as of 15:45, 19 November 2009

This is my page, some info on me will follow.


This tutorial will detail how to add the necessary functionality to create a fully working ingame VGUI panel. There are a few small flaws with the workings of the original VGUI panel presented in the source code that must be rectified before these panels will operate.

This first problem is that CInput::ExtraMouseSample calls g_pClientMode->CreateMove() with a faked CUserCmd which does not have the correct button flags. When DetermineVguiInputMode is called from within CreateMove with this bad input problems occur. This can be fixed if a boolean is added that will prevent the function running during the extra mouse samples.

First in in_main.cpp Replace // Let the move manager override anything it wants to. if ( g_pClientMode->CreateMove( frametime, cmd ) ) { // Get current view angles after the client mode tweaks with it engine->SetViewAngles( cmd->viewangles ); prediction->SetLocalViewAngles( cmd->viewangles ); } With // Let the move manager override anything it wants to. if ( g_pClientMode->CreateMove( frametime, cmd, true ) ) { // Get current view angles after the client mode tweaks with it engine->SetViewAngles( cmd->viewangles ); prediction->SetLocalViewAngles( cmd->viewangles ); } So now when CreateMove is called it passed a Boolean which will be used to show this is a valid call to the VGUI screen as well as frametime and cmd. In order for this to work the CreateMove function must be modified to accommodate this extra Boolean. To do this, add: virtual bool CreateMove( float flInputSampleTime, CUserCmd *cmd, bool bVguiUpdate ) = 0; above virtual bool CreateMove( float flInputSampleTime, CUserCmd *cmd ) = 0; in iclientmode.h, c_basehlplayer.h and clientmode_shared.h

Next functionality has to be provided for these headers, so in clientmode_shared.cpp change the section bool ClientModeShared::CreateMove( float flInputSampleTime, CUserCmd *cmd ) { // Let the player override the view. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); if(!pPlayer) return true;

// Let the player at it return pPlayer->CreateMove( flInputSampleTime, cmd ); } To bool ClientModeShared::CreateMove( float flInputSampleTime, CUserCmd *cmd, bool bVguiUpdate ) { // Let the player override the view. C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); if(!pPlayer) return true;

// Let the player at it return pPlayer->CreateMove( flInputSampleTime, cmd, bVguiUpdate ); } bool ClientModeShared::CreateMove( float flInputSampleTime, CUserCmd *cmd ) { Return CreateMove( flInputSampleTime, cmd, false ); } And in c_basehlplayer.cpp replace bool C_BaseHLPlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd ) { bool bResult = BaseClass::CreateMove( flInputSampleTime, pCmd ); with bool C_BaseHLPlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd , bool bVguiUpdate ) { bool bResult = BaseClass::CreateMove( flInputSampleTime, pCmd, bVguiUpdate );


Finally in c_baseplayer.cpp find: bool C_BasePlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd ) and replace it with: bool C_BasePlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd, bool bVguiUpdate )

Then inside the CreateMove function find: DetermineVguiInputMode( pCmd ); And replace it with: if (bVguiUpdate) { bool tempvguimode = IsInVGuiInputMode(); // Check to see if we're in vgui input mode... DetermineVguiInputMode( pCmd );

if (tempvguimode == !IsInVGuiInputMode()) { if (IsInVGuiInputMode()) { engine->ClientCmd( "vguimode_true" ); } else { engine->ClientCmd( "vguimode_false" ); } } }

This function witll now only call the DetermineVguiInputMode function when a valid call is being made to the CreateMove function. Furthermore, when the VguiMode changes as a player interacts with a screen it will send messages from the client to the server.

By performing this fix, vguiscreens will be initialised with proper data, however due to a flaw in DetermineVguiInputMode the vgui screens are not detected and so the player never enters vguiinput mode. If the vgui screens are simply for display purposes this is fine, to get the screens interactive this must change however. The problem lies with the FindNearbyVguiScreen function in c_vguiscreen.cpp. In this function a sphere enumerater is used to detect the screens, but this fails to give a result. To change this a vector can be added to contain all vguiscreens present within the level. This is done by adding: CUtlVector<C_VGuiScreen*> g_pVGUIScreens; Above CUtlDict<KeyValues*, int> g_KeyValuesCache; In c_vguiscreen.cpp.

In order to fill this vector the function g_pVGUIScreens.AddToTail( this ); is added to the constructor of C_VGuiScreen and g_pVGUIScreens.FindAndRemove( this ); is added to the destructor. Next find the FindNearbyVguiScreen and replace it with:

C_BaseEntity *FindNearbyVguiScreen( const Vector &viewPosition, const QAngle &viewAngle, int nTeam ) { if ( IsX360() ) { // X360TBD: Turn this on if feature actually used return NULL; }

C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();

Assert( pLocalPlayer );

if ( !pLocalPlayer ) return NULL;

// Get the view direction... Vector lookDir; AngleVectors( viewAngle, &lookDir );

// Create a ray used for raytracing Vector lookEnd; VectorMA( viewPosition, 2.0f * VGUI_SCREEN_MODE_RADIUS, lookDir, lookEnd );

Ray_t lookRay; lookRay.Init( viewPosition, lookEnd );


Vector vecOut, vecViewDelta;


float flBestDist = 2.0f; C_VGuiScreen *pBestScreen = NULL;


for (int i = 0; i < g_pVGUIScreens.Count(); i++) { if (g_pVGUIScreens.IsValidIndex(i)) { C_VGuiScreen *pScreen = g_pVGUIScreens[i];

if ( pScreen->IsAttachedToViewModel() ) continue;

// Don't bother with screens I'm behind... // Hax - don't cancel backfacing with viewmodel attached screens. // we can get prediction bugs that make us backfacing for one frame and // it resets the mouse position if we lose focus. if ( pScreen->IsBackfacing(viewPosition) ) continue;

// Don't bother with screens that are turned off if (!pScreen->IsActive()) continue;

// FIXME: Should this maybe go into a derived class of some sort? // Don't bother with screens on the wrong team if (!pScreen->IsVisibleToTeam(nTeam)) continue;

if ( !pScreen->AcceptsInput() ) continue;

if ( pScreen->IsInputOnlyToOwner() && pScreen->GetPlayerOwner() != pLocalPlayer ) continue;

// Test perpendicular distance from the screen... pScreen->GetVectors( NULL, NULL, &vecOut ); VectorSubtract( viewPosition, pScreen->GetAbsOrigin(), vecViewDelta ); float flPerpDist = DotProduct(vecViewDelta, vecOut); if ( (flPerpDist < 0) || (flPerpDist > VGUI_SCREEN_MODE_RADIUS) ) continue;

// Perform a raycast to see where in barycentric coordinates the ray hits // the viewscreen; if it doesn't hit it, you're not in the mode float u, v, t; if (!pScreen->IntersectWithRay( lookRay, &u, &v, &t )) continue;

// Barycentric test if ((u < 0) || (v < 0) || (u > 1) || (v > 1)) continue;

if ( t < flBestDist ) { flBestDist = t; pBestScreen = pScreen; } } }

return pBestScreen; }

At this point the VGUI screens will not only work, but will be fully interactive. Buttons can be clicked by pressing the fire button whilst pointing at them on the VGUI screen. Try this with a rocket launcher though and a problem arises. The player’s gun still fires when the buttons are pressed, which in the example case will prove fatal and annoy the player. To prevent this, the simplest way is to make the player lower their gun and remove the fire button flags. This stops the firing and gives the player a cue to known when they can interact with the screen.

Start by looking at the CBasePlayer function in Player.h and adding the following lines: public: bool m_pVGUImode; bool GetVGUIMode(void) {return m_pVGUImode;} void SetVGUImode(bool newmode) { m_pVGUImode = newmode;} Then in Player.cpp at the end of the Spawn function add SetVGUImode(false);

And at the end of the ClientCommand function, after the existing else ifs add

else if ( stricmp( cmd, "vguimode_true" ) == 0 ) { SetVGUImode( true ); return true; } else if ( stricmp( cmd, "vguimode_false" ) == 0 ) { SetVGUImode( false ); return true; }

These functions will capture the commands we sent earlier when the player enters VGUI mode on the client and enable the server to know when the weapons should be lowered. Finally, in hl2_player.cpp beneath UpdateWeaponPosture(); Add

// If we're in VGUI mode we should avoid shooting if ( GetVGUIMode() ) { m_nButtons &= ~(IN_ATTACK|IN_ATTACK2); }

And inside the UpdateWeaponPosture function add

if ( GetVGUIMode() ) { //We're over a friendly, drop our weapon if ( Weapon_Lower() == false ) { //FIXME: We couldn't lower our weapon! }

return; } Beneath CBaseEntity *aimTarget = tr.m_pEnt;

These functions will stop the player firing, and lower the weapon respectively when the player is looking at a VGUI screen.