multimedia @ VU
[] readme course 1 2 3 technical hammer characters models VGUI mods shaders media resources

talk show tell print

Modding for VU-Life 2
The IDE

The Visual Studio IDE has many different panels. Two of these, the solution explorer and the class view, can help us to navigate through the project. If they aren't visible as default, enable them by selecting the missing panel in the view menu.

In the solution explorer the actual files (plus its extension) of the project are listed in a tree form. The class view on the other hand shows us all the classes that exist in the project (note that these are automatically parsed out by Visual Studio, even when we are editing the source code). We will mainly use the "Class View" in this document, unless mentioned differently.


The class view

After loading the project file of the sourcemod, the class view shows us two sub-projects. The tree of the sub-project can be expanded by clicking on the plus ("+") sign, showing us all the classes that belong to this sub-project. Double-clicking on the class name opens the file which contains the class declaration (often an .h-file) while double-clicking on a member function or constructor takes you to the implementation file of the class (mostly a .cpp-file).

A note about adding members to a class: the IDE has a handy way to add member functions and variables by right-clicking in the class view and selecting "Add function" or "Add variable". This however, is not recommended when working on your SourceMod project. Doing this will mostly change options in your project file, resulting in very strange compiler errors.

Structure of the SourceMod

The project is divided into two sub-projects, client and hl. All classes that belong to first project are classes which implement functions for the client side (for example HUD elements). Classes of hl contain functions for the server side (like the player). There is also a special group of classes, also known as network entities. These classes are present in both sub-projects. Examples of such classes are the weapon classes.

Also note that while we are modding the Half-Life 2 Deathmatch, single player classes and files are also included in our project (like specific rules, single player NPC's and AI), but not used.

How to remove violence?

In our project we want to remove all violence; i.e. remove weapons, health bar, crosshair, ammo bar and weapons selection bar. One way of doing this, is to edit all the corresponding classes. For example to disable weapons selection, we can edit the VisibleInWeaponSelection function in CBaseCombatWeapon in such a way that it will always return false. But we will not do this. Instead, we want do all of the violence removal at one spot (the CHL2MP_Player class), so that we can to toggle it on and off easily later on.

Step by step removal of violence:

  1. Disable weapons;
    CHL2MP_Player has a function Spawn. This function is called when a player gets spawned (that is, when entering a map or after dying). Remove the line "GiveDefaultItems();", so that the player doesn't get any weapons when spawning.
    One note: Doing this will only prevent the player from getting a weapon. When the map contains weapons which can be picked up, the player is still going to have weapons.
    If the player already has items or weapons, we can remove them by calling the RemoveAllItems function.
  2. Remove health bar and crosshair;
    The health bar, crosshair and ammo bar are all drawn on separate HUDs, which are visible all the time at default. The HUDs are divided in "hide"-groups, each defined by a constant. Examples are: HIDEHUD_HEALTH, HIDEHUD_CROSSHAIR and so forth. See shareddefs.h for all constants.

    Part of shareddefs.h

    CHL2MP_Player has a member variable m_iHideHud of the struct m_Local which can be used to hide/unhide a group of HUD's. In order to hide a group of HUD's, use bitwise OR on this variable with the corresponding constant. Example:
    m_local.m_iHideHud |= HIDEHUD_HEALTH; //Hides health HUD.
    To make it visible again, use bitwise AND:
    m_local.m_iHideHud &= HIDEHUD_HEALTH; //Unhides health HUD.
    To put everything back to the default values, assign the variable to zero:
    m_local.m_iHideHud = 0; //set HUD's back to default
    Of course, just setting the variable is not enough. After setting the correct value of the variable, call UpdateClientData() to apply the settings.
  3. Immortality;
    Another thing about non-violence is to prevent the player from dying. In Half-Life 2, weaponry is not the only thing that can kill you. Physics and gravity can also be very painful (and we're not talking about the laws). To prevent any damage done to the player, the function OnTakeDamage has to be edited. We may want to toggle this on and off in the future, so add a member variable m_bViolence to the CHL2MP_Player class. Now in the OnTakeDamage function, add this to the top:
    if (!b_Violence) return 0;
    So whenever we are setting the variable b_Violence to false, the function will immediately return, and no damage will be done to this player.
  4. 4. All three steps together:
    Create a function SetWeaponsEnabled in CHL2MP_Player.
    void CHL2MP_Player::SetWeaponEnabled(bool enable) {
    	if (enable) { //Weapons on
    		GiveDefaultItems();
    		m_Local.m_iHideHUD = 0;
    		m_bViolence = true;	//Immortality	off!
     		UpdateClientData();	//Update HUD
    	} else { //No weapons please
    		RemoveAllItems(true);
    		m_Local.m_iHideHUD = 0;
    		m_Local.m_iHideHUD |= HIDEHUD_HEALTH;
    		m_Local.m_iHideHUD |= HIDEHUD_CROSSHAIR;
    		m_bViolence = false;    //player will be immortal
    		UpdateClientData();	//Update HUD.
    	}
    }   
    Add SetWeaponsEnabled(false) in the Spawn function of CHL2MP_Player, so every player starts without weapons.

Problem: Static player model when no items

There is a small problem when a player isn't holding any weapon. The player model does not have an animation for a weaponless player. There seems to be no solution for this, then to give the player something to hold. In our case, we have chosen to give every player a stun-stick when violence is disabled. The stun-stick is the most harmless looking object of all available holdable weapons. We just have to disable its original functionality.

The classes of the weapons are not directly accessible from within the CHLMP_Player, so this time we do need to change settings of the weapon directly in its class. Open up the solutions explorer this time, browse to the HL2MP folder in the client project, then to the weapons folder and double-click on weapons_stunstick.cpp. Almost every weapon is derived from the CBaseCombatWeapon (mostly not directly). Therefore, these weapons all have both functions PrimaryAttack and SecondaryAttack derived from their parents. We just need to overwrite the ones in the stun-stick class to always return immediately (and thus doing nothing when an attack is done with this weapon).

How to create a logical entity which you can use in Hammer?

A logical entity can be used to add functionality to your game, by communicating to other (non-)logical entities. These logical entities have to be triggered in order to do what they are supposing to do. The entity itself will not visible to the player. Logical entities can be dragged into your map in the Hammer level editor. Entities have inputs and outputs, which can be chained together to do certain things. For example: we have a trigger_proximiy entity and a game_text entity on our map. We then can chain an output of trigger_proximity, OnStartTouch, to an input of the game_text entity, like Display to show a message on the screen when we have triggered the proximity (by touching it).


OnStartTouch output is chained to the SetWeapons input of WeaponsDisabler

A logical entity can be created in our Mod as follows. First of all, all these logical entities which dynamically change the game rules are put together in one .cpp-file. This file is called maprules.cpp, so open up the solution explorer again and browse to the Source Files folder in the hl sub-project.

The best thing to do when experimenting with logical entities is trying to clone an existing entity to discover how it is implemented. A good one to clone or use as a skeleton to create your own entities is CGameText. This class corresponds to the game_text entity that we discussed earlier.

Important are the following lines:

//…
LINK_ENTITY_TO_CLASS(your_entity_name, CWeaponsEnabler);

BEGIN_DATADESC( CWeaponsEnabler )
	DEFINE_KEYFIELD( iEnable, FIELD_INTEGER, "enableWeapons" ),
	DEFINE_INPUTFUNC( FIELD_VOID, "SetWeapons", InputHandler ),
END_DATADESC()
//…

The first line sets the "connection" between the class and the entity. The DEFINE_KEYFIELD defines that the value, which is filled in the "enableWeapon" field in the Hammer editor, will be copied into the int variable iEnable.

DEFINE_INPUTFUNC defines an input of the entity. When this input is triggered by an output of another entity, the function "InputHandler" of this class will be called. The remainder should be self-explanatory. Check CGameText or CWeaponsEnabler (Appendix B.1) for more information.

When you have finished creating or editing your entity, you also have to create a .fgd file. This file has to be loaded in the Hammer editor (Tools -> options -> Game Configurations Tab -> Game Data Files) before you can use your entity. The .fgd contains an almost identical mapping of the fields and the input of the entity. Check Appendix A.3 for an example of a .fgd file, which we created for the vu_weapons_enabler entity (this entity will be discussed in the following paragraph).

The CWeaponEnabler logical entity that we have created

This logical entity will disable or enable weapons of the player who triggered it. This entity is almost identical to CGameText, but without all the unnecessary options. It does have a field "enableWeapons", which in fact is just a boolean. The entity will call the SetWeaponsEnabled function (defined earlier) of the CHL2MP_player class to enable or disable weapons. The player who has triggered the entity can be found by calling ToBasePlayer(pActivator).


The entity is called "WeaponsDisabler" and has a field called "Weapons enable/disable" (name of this field is defined in a .fgd file.

See Appendix A1 for the complete source code.

Entity to HUD

Earlier on we have created a customized HUD which can display pictures. Now we just need an entity to trigger it. Basically you can clone CGameText again and remove/add fields as you like, just like in the WeaponsEnabler. But to access our customized HUD from this entity, we need to adjust our HudMonitor a little.

  1. First we need to define a "message" in hl2_usermessages.cpp. Open up this file and add this to the constructor:
    usermessages->Register( "HudMonitor", -1 ); //Added for Custom HUD
  2. Then open up our CHudMonitor class and add the following macro's just below the constructor:
    DECLARE_HUDELEMENT(CHudMonitor);
    DECLARE_HUD_MESSAGE( CHudMonitor, HudMonitor );
  3. Goto the Init function of CHudMontor and add another macro:
    HOOK_HUD_MESSAGE(CHudMonitor, HudMonitor);
  4. Add a new function to the class, called MsgFunc_HudMonitor. This function will be called whenever UserMessageBegin is used in combination with MessageEnd.
  5. We have put the message part to the HUD in util.cpp as a UTIL_-function, so that it can be access everywhere in our project.

See Appendix A2 for the complete source code.

Appendix A1

In maprules.cpp:

class CWeaponsEnabler : public CRulePointEntity
{
public:
	DECLARE_CLASS( CWeaponsEnabler, CRulePointEntity );

	bool	KeyValue( const char *szKeyName, const char *szValue );

	DECLARE_DATADESC();

	void InputHandler( inputdata_t &inputdata );
	void SetWeapons( CBaseEntity *pActivator );

	void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
	{
		SetWeapons( pActivator );
	}

private:
  int iEnable;
};

LINK_ENTITY_TO_CLASS( vu_weapons_enabler, CWeaponsEnabler );

BEGIN_DATADESC( CWeaponsEnabler )
	DEFINE_KEYFIELD( iEnable, FIELD_INTEGER, "enableWeapons" ),
	DEFINE_INPUTFUNC( FIELD_VOID, "SetWeapons", InputHandler ),
END_DATADESC()

bool CWeaponsEnabler::KeyValue( const char *szKeyName, const char *szValue ) {
	return BaseClass::KeyValue( szKeyName, szValue );
}

void CWeaponsEnabler::InputHandler( inputdata_t &inputdata ) {
	SetWeapons( inputdata.pActivator );
}

void CWeaponsEnabler::SetWeapons( CBaseEntity *pActivator ) {
	if ( !CanFireForActivator( pActivator ) ) return;
	// If we're in singleplayer, show the message to the player.
	if ( gpGlobals->maxClients == 1 ) {
		//Do nothing
	} else if ( pActivator && pActivator->IsNetClient() ) {
		// Otherwise show the message to the player that triggered us.
		if (iEnable == 1) {   //True is selected
			UTIL_DoHudPicture(ToBasePlayer( pActivator ), 0, false);
			ToBasePlayer( pActivator )->SetWeaponsEnabled(true);
			ClientPrint(ToBasePlayer( pActivator ), HUD_PRINTCENTER, "FIGHT!!");
		} else {
			//Shoud the puzzlehud be enabled when weapond are disabled?
			UTIL_DoHudPicture(ToBasePlayer( pActivator ), 0, true);
			ToBasePlayer( pActivator )->SetWeaponsEnabled(false);
		}
	}
}

Appendix A2

hud_monitor.h:

#include "hudelement.h"
#include 


class CHudMonitor : public CHudElement, public vgui::Panel
{
	DECLARE_CLASS_SIMPLE(CHudMonitor, vgui::Panel);
	
public:
	CHudMonitor(const char *pElementName);
	void MsgFunc_HudMonitor( bf_read &msg);
	void DrawPart(int iIndex, int x, int y, int x2, int y2);
	void Init();
private:
	static const int OFFSET = 10;
	static const int PARTS_COUNT = 10;
	int m_nTextureID[PARTS_COUNT];
	int m_nBlinker[PARTS_COUNT];
	bool m_nPartToDraw[PARTS_COUNT];

protected:
	virtual void Paint();
};

hud_monitor.cpp:

#include "hud.h"
#include "cbase.h"
#include "hud_monitor.h"
#include "iclientmode.h"
#include "hud_macros.h"
#include "vgui_controls/controls.h"
#include "vgui/ISurface.h"

#include "tier0/memdbgon.h"

CHudMonitor::CHudMonitor(const char *pElementName) : CHudElement(pElementName), BaseClass(NULL, "HudMonitor")
{
	vgui::Panel *pParent = g_pClientMode->GetViewport();
	SetParent(pParent);

	for (int i=0; i<11; i++) {
		m_nTextureID[i] = vgui::surface()->CreateNewTextureID();
	}
	vgui::surface()->DrawSetTextureFile(m_nTextureID[1], "hud_monitor/p1", true, true);
	vgui::surface()->DrawSetTextureFile(m_nTextureID[2], "hud_monitor/p2", true, true);
	vgui::surface()->DrawSetTextureFile(m_nTextureID[3], "hud_monitor/p3", true, true);
	vgui::surface()->DrawSetTextureFile(m_nTextureID[4], "hud_monitor/p4", true, true);
	vgui::surface()->DrawSetTextureFile(m_nTextureID[5], "hud_monitor/p5", true, true);
	vgui::surface()->DrawSetTextureFile(m_nTextureID[6], "hud_monitor/p6", true, true);
	vgui::surface()->DrawSetTextureFile(m_nTextureID[7], "hud_monitor/p7", true, true);
	vgui::surface()->DrawSetTextureFile(m_nTextureID[8], "hud_monitor/p8", true, true);
	vgui::surface()->DrawSetTextureFile(m_nTextureID[9], "hud_monitor/p9", true, true);

	SetHiddenBits(0);//HIDEHUD_PLAYERDEAD | HIDEHUD_NEEDSUIT);
};

DECLARE_HUDELEMENT(CHudMonitor);
DECLARE_HUD_MESSAGE( CHudMonitor, HudMonitor );

void CHudMonitor::Init() {
	HOOK_HUD_MESSAGE(CHudMonitor, HudMonitor); 
	//At the start, there is always an empty hud
	//Initially, draw nothing
	for (int i=0; i 240
	}
}


void CHudMonitor::MsgFunc_HudMonitor( bf_read &msg) {
	int nMessage;
	bool bDraw;

	nMessage = msg.ReadByte();
	bDraw = msg.ReadOneBit();

	if (nMessage < 10 && nMessage > 0) { //1, 2, .., 9
		if (bDraw) {
			m_nPartToDraw[nMessage] = true;
			m_nBlinker[nMessage] = 0;
		} else {
			m_nPartToDraw[nMessage] = false;
		}
	} else if (nMessage == 0) {
		//Disable whole hud with SetHiddenBits(HIDEHUD_PLAYERDEAD | HIDEHUD_NEEDSUIT);
		if (bDraw) {
			//Show this HUD
			SetHiddenBits(0);
		} else {
			//Hide HUD
			//Put this HUD in the group of the HIDEHUD_VEHICLE_CROSSHAIR
			//As soon as you turn this off in hl2mp_player, this one turns off as well
						SetHiddenBits(HIDEHUD_VEHICLE_CROSSHAIR);
		}
	} else { //nMessage >= 10
		//Handle other messages
	}

}

void CHudMonitor::DrawPart(int iIndex, int x, int y, int x2, int y2) {
	vgui::surface()->DrawSetTexture(m_nTextureID[iIndex]);
	vgui::surface()->DrawTexturedRect(x, y, x2, y2);
	if (m_nBlinker[iIndex] < 40) {
		vgui::surface()->DrawSetColor(255, 255, 50, 50);
		vgui::surface()->DrawFilledRect(x, y, x2, y2);
		m_nBlinker[iIndex]++;
	} else if (m_nBlinker[iIndex] < 80 ) {
		m_nBlinker[iIndex]++;
	} else if (m_nBlinker[iIndex] >= 80 && m_nBlinker[iIndex] < 120) {
		vgui::surface()->DrawSetColor(255, 255, 50, 50);
		vgui::surface()->DrawFilledRect(x, y, x2, y2);
		m_nBlinker[iIndex]++;
	} else if (m_nBlinker[iIndex] >= 120) {
		//do nothing;
	}
}

void CHudMonitor::Paint()
{
	int nWide = this->GetWide();
	int nTall = this->GetTall();
	int x1, y1, x2, y2;

    /*  -----------
       | 1 | 2 | 3 |
	    -----------
       | 4 | 5 | 6 |
	    -----------
       | 7 | 8 | 9 |
	    -----------  */

	x1 = x2 = y1 = y2 = 0;

	for (int i=0; i 0) {
			if (m_nPartToDraw[i]) {
				DrawPart(i, x1, y1, x2, y2);
			} else {
				vgui::surface()->DrawSetColor(255, 255, 50, 50);
				vgui::surface()->DrawOutlinedRect(x1, y1, x2, y2);
			}
		}
		//Always draw background (wireframe of the whole picture)
		//On top:
		vgui::surface()->DrawSetColor(255, 255, 50, 50);
		vgui::surface()->DrawOutlinedRect(OFFSET, OFFSET, nWide-OFFSET, nTall-OFFSET);
	}
}

In util.cpp:

void UTIL_DoHudPicture( CBasePlayer *pToPlayer, int nMessage, bool bDraw )
{
	CRecipientFilter filter;
	
	if( pToPlayer )
	{
		filter.AddRecipient( pToPlayer );
	}
	else
	{
		filter.AddAllPlayers();
	}

	filter.MakeReliable();

	UserMessageBegin( filter, "HudMonitor" );
		WRITE_BYTE(nMessage);
		WRITE_BOOL(bDraw);
	MessageEnd();
}

Add to hl2_usermessages.cpp:

void RegisterUserMessages( void )
{
	usermessages->Register( "Geiger", 1 );
	usermessages->Register( "Train", 1 );
	usermessages->Register( "HudText", -1 );
	usermessages->Register( "SayText", -1 );
	usermessages->Register( "TextMsg", -1 );
	usermessages->Register( "HudMsg", -1 );
	usermessages->Register( "HudMonitor", -1 ); //Add this one <--
	...
	...

Appendix A3

@include "base.fgd"

@PointClass base(Targetname) = vu_weapons_enabler : "Logical entity which can enable/disable weapons."
[
	enableWeapons(choices) : "Weapon enable/disable" : 1 : "Select whether you want to enable/disable weapons." =
	[
		1 : "true"
		2 : "false"
	]
  input SetWeapons(void) : "Triggers input."
]

@PointClass base(Targetname) = vu_puzzle_setter : "Logical entity which can enable/disable parts of the puzzle-like HUD."
[
	puzzlePartIndex(choices) : "Index of the puzzle-part" : 1 : "1 maps to the first picture top-left, 9 to the bottom-right." =
	[
		1: "1 (top-left)"
		2: "2 (top-middle)"
		3: "3 (top-right)"
		4: "4 (center-left)"
		5: "5 (center-middle)"
		6: "6 (center-right)"
		7: "7 (bottom-left)"
		8: "8 (bottom-middle)"
		9: "9 (bottom-right)"
	]
	
	enablePart(choices) : "Visible/Invisible" : 1 : "Do you want it to be visible?" =
	[
		1 : "true"
		2 : "false"
	]
	
  input SetPuzzlePart(void) : "Triggers input."
]


[] readme course 1 2 3 technical hammer characters models VGUI mods shaders media resources