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).
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.
See Appendix A2 for the complete source code.
hud_monitor.h:
#include "hudelement.h" #includeclass 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; i240 } } 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 <-- ... ...