Adding an inventory

From Valve Developer Community
Jump to navigation Jump to search
Wikipedia - Letter.png
This article has multiple issues. Please help improve it or discuss these issues on the talk page. (Learn how and when to remove these template messages)
Dead End - Icon.png
This article has no Wikipedia icon links to other VDC articles. Please help improve this article by adding links Wikipedia icon that are relevant to the context within the existing text.
January 2024
Example 1
Example 2

How to implement a simple inventory

About

This inventory is pretty simple. It saves the entities, which the player finds on his way, but only for the current session. It's pretty simple and easy to understand. The player can carry 30 Items.

The Server

So where should we start? First of all we should think about the functions we'll need to get our inventory working. This is how it should look like.

SDK_PLAYER.h ( public: )

virtual void InitEntityList( void ); // The Entities gets added here.
virtual void InventoryAddItem( int EntityID ); // This will add an item to the players inventory.
virtual void InventoryRemoveItem( int Position ); // This will remove an item from the players inventory.
virtual void Think( void ); // This one is required to delay the inventory init.
int GetItemCount()
{
	int ItemCount;
	for( int i = 0; i < MAX_INVENTORY; i++)
	{
		if(!m_iInventory.Get(i))
			break;
		else
			ItemCount ++;
	}	
	return ItemCount;
} // Gets All Items from the Inventory.

Our player can carry 30 items. So we need to define this in sdk_shareddefs.h because the client and the server uses this file.

SDK_SHAREDDEFS.h

#define MAX_INVENTORY 31 // Add 1 to the maximum size of your inventory. It's required because count begins from 0 (0-30).

What kind of arrays do we need for it? This is a good question. We need to send a list to the client which contains the names of our entities. The best way to do this is CUtlVector. A CUtlVector is a dynamic array. This is the best way and it saves resources. Infos(http://developer.valvesoftware.com/wiki/CUtlVector). But we also need to send informations about our inventory to the client.

SDK_PLAYER.h ( private: )

CNetworkArray(int, m_iInventory, MAX_INVENTORY); // A Array for our Inventory with the size of MAX_INVENTORY.
CUtlVector<char*> m_EntitySpawnName; // The Vector for our EntitySpawnNames

Let's setup the Server. First of all we make sure, that our m_iInventory gets sent to the client.

SDK_PLAYER.CPP ( IMPLEMENT_CLIENTCLASS_DT( C_SDKPlayer, DT_SDKPlayer, CSDKPlayer )

SendPropArray3( SENDINFO_ARRAY3(m_iInventory), SendPropInt( SENDINFO_ARRAY(m_iInventory) ) ),

Now we need to add a bit to the InitialSpawn function. Get to the IntitalSpawn function in sdk_player.cpp and add the following code.

SetThink(&CSDKPlayer::Think); // Self-explanatory
SetNextThink(5.0); // Self-explanatory

Now we need to setup our think function. It's kinda small. Add the following code underneath the initialspawn function.

void CSDKPlayer::Think()
{
	InitEntityList(); // Calls our init function for the Entities.
}

To send names to our client, we need to create a usermessage.

SDK_USERMESSAGES.CPP

usermessages->Register( "EntityNames", -1 );

Because of posts are limited, I'll save space and explain everything within the comments.

SDK_PLAYER.CPP

const char *CSDKPlayer::GetEntityVectorElement(int position)
{
	return m_EntitySpawnName.Element(position); // Returns a element.
}

void CSDKPlayer::AddEntityVectorElement( char *Element) 
{ 
	char *_string = new char[Q_strlen(Element)+1]; // Allocates just that what is needed.
	Q_strcpy( _string, Element ); // Copies the element.
	m_EntitySpawnName.AddToTail(_string); // Adds the element into the vector.
}

void CSDKPlayer::ClearEntityVector()
{
	for (int i = 0; i < m_EntitySpawnName.Count(); i++ ) // Starts a for from 0 to "element count".
		delete [] m_EntitySpawnName[i]; // Deletes every single element.
	m_EntitySpawnName.Purge(); // Purges the vector.
}

void CSDKPlayer::InitEntityList()
{
	CUtlVector<char*> EntityName; // This is a CUtlVector for the entity name. More about them here:(http://developer.valvesoftware.com/wiki/CUtlVector).
	//This is the list of our entities.
	EntityName.AddToTail("none");  // It's needed because of some problems with the number 0 in the code.
	AddEntityVectorElement("none"); // Same as above...
	EntityName.AddToTail("Melon"); // Adds Melon into our EntityName CUtlVector.
	AddEntityVectorElement("func_melon"); // Adds the Spawn Name of our Melon into the CUtlVector.
	//EntityName.AddToTail("Coke"); Example
	//EntitySpawnname.AddToTail("func_coke"); Example

	CSingleUserRecipientFilter user( this ); // A Filter for our usermessage
	user.MakeReliable(); // Makes the filer reliable.
	for( int i = 0; i < EntityName.Count(); i++ ) // For each element a usermessage gets sent.
	{
		UserMessageBegin( user, "EntityNames" ); // Starts our usermessage.
			WRITE_SHORT( entindex() ); // Without the entindex, we couldn't find the right player at the client side. It would add them to everyone...
			WRITE_STRING( EntityName[i] ); // Sends our name to the Client.
		MessageEnd(); // Ends the usermessage.
	}
}

void CSDKPlayer::InventoryAddItem(int EntityID)
{
	for( int i = 0; i < MAX_INVENTORY; i++ ) // Loops through our inventory array.
	{
		if( !m_iInventory.Get(i) ) // If there's nothing, then ...
		{
			m_iInventory.Set(i, EntityID); // adds the entity in the current position.
			break; // calls the emergency break
		}
	}
	engine->ClientCommand(edict(),"UpdateInventory"); // Forces an update of our Inventory GUI.
}

void CSDKPlayer::InventoryRemoveItem(int Position)
{
	if( m_iInventory.Get(Position) )
	{
		Vector vecForward; // Sets up a vector.
		AngleVectors( EyeAngles(), &vecForward ); // Sets up a angle vector and gets the eyeangles of the player.
		CBaseEntity *pEnt = CreateEntityByName( GetEntityVectorElement(m_iInventory.Get(Position) ) ); // Gets the entity's spawn name and tries to spawn it.
		if ( pEnt )
		{
			Vector vecOrigin = GetAbsOrigin() + vecForward * 56 + Vector(0,0,64); // Makes sure the entity spawns a view unites away from the player.
			QAngle vecAngles(0, GetAbsAngles().y - 90, 0); // Changes the angle of the entity.
			pEnt->SetAbsOrigin(vecOrigin); // Apply origin changes.
			pEnt->SetAbsAngles(vecAngles); // Apply angle changes.
			pEnt->Spawn(); // Spawns the entity.
			m_iInventory.Set(Position, 0); // Removes the Entity from our m_iInventory array.
			int i, j, helper; // ...
			for( i = MAX_INVENTORY -1 ; i > 0; i--) // For each entry in our inventory, ...
			{
				for ( j = 0; j < i; j++) // Once again.
				{
					if(m_iInventory.Get(j) < m_iInventory.Get(j+1)) // If the next entry is smaller than the current entry, then ...
					{
						helper = m_iInventory.Get(j); // Saves the current entry in our helper.
						m_iInventory.Set(j,m_iInventory.Get(j+1)); // Sets the next entry to the current entry.
						m_iInventory.Set(j+1,helper); // Sets the entry saved in our helper into the next entry.
					}
				}
			}
		}
		else
			Warning("Unable to create entity?!\n"); // If the entity wasn't found.
		
		engine->ClientCommand(edict(),"UpdateInventory"); // Forces an update of our Inventory GUI.
	}
	else
		Warning("No Entity\n"); // If there's no entity in the current position.
}

The last thing we need to do in the server is, create a command that will call our drop function. For this go into the function CSDKPlayer::ClientCommand( const CCommand &args ) and add the following code after droptest.

else if(FStrEq(pcmd, "dropitem"))
{
	InventoryRemoveItem(atoi(args[1])); // Converts the args to a int so it can be used by our function.
	return true;
}

The Client

So now we'll setup the client. First we need to define our functions and the arrays. This is how I've done it.

C_SDK_PLAYER.H

const char *GetEntityVectorElement(int position); // Gets a name out of our m_EntityName vector.
virtual void AddEntityVectorElement( char *Element); // Adds a element to our vector.
virtual void ClearEntityVector(); // Clears the entity vector.
int GetInventoryArray( int Position ) { return m_iInventory[Position]; } // Returns the element on position X.

CUtlVector<char *> m_EntityName;
int m_iInventory[MAX_INVENTORY];

C_SDK_PLAYER.CPP

First we make sure, that our client recives our m_iInventory array. Add the following code to IMPLEMENT_CLIENTCLASS_DT( C_SDKPlayer, DT_SDKPlayer, CSDKPlayer )

RecvPropArray3( RECVINFO_ARRAY(m_iInventory), RecvPropInt( RECVINFO( m_iInventory[0] ) ) ), /// Receives our array.

Now we make sure, that our client will receives our usermessage.

void __MsgFunc_EntityNames( bf_read &msg ) // The function for our usermessage
{
	int iPlayer = msg.ReadShort(); // Reads the entindex and saves it into iPlayer.
		C_SDKPlayer *pPlayer = dynamic_cast< C_SDKPlayer* >( C_BaseEntity::Instance( iPlayer ) ); // A dynamic_cast that uses the entindex to create a pointer to the c_sdk_player
	char Str[254]; // A Buffer for our Entity name.
	msg.ReadString(Str, 254); // Gets the name and saves it into the buffer.
	pPlayer->AddEntityVectorElement(Str); // Adds the name into our Entity Vector
}

But that's not all. Now need to hook our usermessage to that function. This is how it's done. Go to the function C_SDKPlayer::C_SDKPlayer() : m_iv_angEyeAngles( "C_SDKPlayer::m_iv_angEyeAngles" ) and add the following code into it.

usermessages->HookMessage("EntityNames",__MsgFunc_EntityNames);

Now we setup the clients functions. This is how it's done. Explained with comments.

const char *C_SDKPlayer::GetEntityVectorElement(int position)
{
	return m_EntityName.Element(position); // Returns a element.
}

void C_SDKPlayer::AddEntityVectorElement( char *Element) 
{ 
	char *_string = new char[Q_strlen(Element)+1]; // Allocates just that what is needed.
	Q_strcpy( _string, Element ); // Copies the element.
	m_EntityName.AddToTail(_string); // Adds the element into the vector.
	Msg("_string:%s\n",_string);
}

void C_SDKPlayer::ClearEntityVector()
{
	for (int i = 0; i < m_EntityName.Count(); i++ ) // Starts a for from 0 to "element count".
		delete [] m_EntityName[i]; // Deletes every single element.
	m_EntityName.Purge(); // Purges the vector.
}

The GUI

First of all we'll add some code to the vgui_int.cpp. At the top we need to add the following code.

#include "sdk_inventory.h" // includes that file we'll create later.

Go to the VGui_CreateGlobalPanels( void ) function and add the following code.

VPANEL gameParent = enginevgui->GetPanel( PANEL_INGAMESCREENS ); // Our panel can be viewed as a ingamescreen.
InventoryPanel->Create(gameParent); // Creates the panel.

Now go to the VGui_Shutdown() function and add the following code.

InventoryPanel->Destroy(); // Destroys the panel.

Now create 2 new files in the client project. sdk_inventory.h and sdk_inventory.cpp

Now we'll put some code in the sdk_inventory.h.

SDK_INVENTORY.H

#ifndef SDK_INVENTORY_H
#define SDK_INVENTORY_H

class IInventoryPanel
{
public:
		virtual void Create( vgui::VPANEL parent ) = 0; // The function to create our gui.
		virtual void Destroy( void ) = 0; // The function to destory our gui.
};
 
extern IInventoryPanel* InventoryPanel;
#endif

The following code shows how to create the panel we need. To save space I explain everything with comments.

#include "cbase.h"
#include "sdk_inventory.h"
using namespace vgui;
#include <vgui/IVGui.h>
#include <vgui_controls/Frame.h>
#include <vgui_controls/SectionedListPanel.h>
#include <vgui_controls/Button.h>
#include "c_sdk_player.h"
 
//CInventoryPanel class: Tutorial example class
class CInventoryPanel : public vgui::Frame
{
	DECLARE_CLASS_SIMPLE(CInventoryPanel, vgui::Frame); 
	//CInventoryPanel : This Class / vgui::Frame : BaseClass
 
	CInventoryPanel(vgui::VPANEL parent); // Constructor
	~CInventoryPanel(){};// Destructor
 
	protected:
	//VGUI overrides:
	virtual void OnTick();
	virtual void OnCommand(const char* pcCommand);
 
	private:
	//Other used VGUI control Elements:
	C_SDKPlayer *pPlayer; // Self-explanatory. 
	SectionedListPanel *ItemSelection; // Self-explanatory. 
	Button *Drop; // Self-explanatory. 
	bool Updated; // Self-explanatory. 
};

// Constuctor: Initializes the Panel
CInventoryPanel::CInventoryPanel(vgui::VPANEL parent) : BaseClass(NULL, "InventoryPanel")
{
	SetParent( parent );
 
	SetKeyBoardInputEnabled( true );
	SetMouseInputEnabled( true );
 
	SetProportional( true );
	SetTitleBarVisible( true );
	SetMinimizeButtonVisible( false );
	SetMaximizeButtonVisible( false );
	SetCloseButtonVisible( false );
	SetSizeable( false );
	SetMoveable( false );
	SetVisible( true );
	ItemSelection = new SectionedListPanel(this,"ItemSelection"); // Self-explanatory. 
	Drop = new Button(this,"Drop","Drop"); // Self-explanatory. 
	Drop->SetCommand("dropitem");// Sets the command of our drop button to "dropitem".
	ItemSelection->AddSection(0, ""); // Adds a section to our SectionedListPanel.
	ItemSelection->AddColumnToSection(0, "Name", "Name", 0, 300); // The "Name" header.
 
	SetScheme(vgui::scheme()->LoadSchemeFromFile("resource/SourceScheme.res", "SourceScheme"));
 
	LoadControlSettings("resource/UI/InventoryPanel.res");
 
	vgui::ivgui()->AddTickSignal( GetVPanel(), 100 );
 
	DevMsg("InventoryPanel has been constructed\n");
}

//Class: CInventoryPanelInterface Class. Used for construction.
class CInventoryPanelInterface : public IInventoryPanel
{
	private:
		CInventoryPanel *InventoryPanel;
	public:
		CInventoryPanelInterface()
		{
			InventoryPanel = NULL;
		}
		void Create(vgui::VPANEL parent)
		{
			InventoryPanel = new CInventoryPanel(parent);
		}
		void Destroy()
		{
			if (InventoryPanel)
			{
				InventoryPanel->SetParent( (vgui::Panel *)NULL);
				delete InventoryPanel;
			}
		}
};

static CInventoryPanelInterface g_InventoryPanel;
IInventoryPanel* InventoryPanel = (IInventoryPanel*)&g_InventoryPanel;

ConVar cl_inventory("cl_showmypanel", "0", FCVAR_CLIENTDLL, "Sets the state of the Inventory <state>");
ConVar cl_UpdateInventory("cl_updateInventory", "0", FCVAR_CLIENTDLL, "Updates the Inventory");

CON_COMMAND(cl_inventoryToggle, "Toggles the Inventory Screen")
{
	cl_inventory.SetValue(!cl_inventory.GetBool());
};

void CInventoryPanel::OnTick()
{
	BaseClass::OnTick(); // Links this function with that one from the baseclass.
	pPlayer = C_SDKPlayer::GetLocalSDKPlayer(); // pPlayer becomes to a pointer to the client.
	if(pPlayer) // if the player exists, then...
	{
		SetVisible(cl_inventory.GetBool()); // Sets the inventory invisible/visible if the bool is false/true
		if(cl_UpdateInventory.GetBool()) // If the cl_UpdateInventory bool is true, then...
		{
			ItemSelection->RemoveAll(); // Deletes all elements.
			Updated = false; // Sets the Update bool to false.
			cl_UpdateInventory.SetValue(0); // Sets the cl_UpdateInventory bool to false.
		}
		if(cl_inventory.GetBool() && !Updated) // If the GUI is activated and Update is false, then...
		{	
			for(int i = 0; i < MAX_INVENTORY; i++) // Gets every item from the inventory array.
			{
				if(pPlayer->GetInventoryArray(i)) // If there's something in our array, then...
				{
					KeyValues *kv = new KeyValues("data"); // Creates a KeyValue.
					kv->SetString("Name",pPlayer->GetEntityVectorElement(pPlayer->GetInventoryArray(i))); // Gets the name, that matches to our inventory array entry.
					ItemSelection->AddItem(0,kv); // Adds the name to the list.
					kv->Clear(); // Clears the KeyValue.
					kv->deleteThis(); // Deletes the KeyValue.
				}
			}
			Updated = true;
		}
	} else // Else...
		SetVisible(0); // Our GUI will be invisible until the Player comes avaliable.
}

void CInventoryPanel::OnCommand(const char* cmd)
{
	if(!Q_stricmp(cmd, "turnoff")) // On turnoff,
		cl_inventory.SetValue(0); // closes the inventory panel.
	if(!Q_stricmp(cmd, "dropitem")) // If our button sends dropitem, then...
	{
		char com[12]; // Our buffer.
		Q_snprintf(com,sizeof(com), "dropitem %i", ItemSelection->GetSelectedItem()); // Prints our command into the buffer.
		engine->ServerCmd(com); // Sends the command.
		cl_UpdateInventory.SetValue(1); // Our GUI wants to do an update now.
	}
}

CON_COMMAND(UpdateInventory,"Updates the inventory")
{
	cl_UpdateInventory.SetValue(1);
}

The last thing we need to do is to create a file named as inventorypanel.res in the directory *modpath*/resource/UI/ Add the following lines to it. It's the simple design for the GUI.

"resource/UI/InventoryPanel.res"
{
	"InventoryPanel"
	{
	"ControlName""CInventoryPanel"
	"fieldName""InventoryPanel"
	"xpos""73"
	"ypos""45"
	"wide""495"
	"tall""393"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	"settitlebarvisible""1"
	"title""InventoryMenu"
	}
	"frame_topGrip"
	{
	"ControlName""Panel"
	"fieldName""frame_topGrip"
	"xpos""3"
	"ypos""0"
	"wide""487"
	"tall""2"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	}
	"frame_bottomGrip"
	{
	"ControlName""Panel"
	"fieldName""frame_bottomGrip"
	"xpos""3"
	"ypos""391"
	"wide""482"
	"tall""2"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	}
	"frame_leftGrip"
	{
	"ControlName""Panel"
	"fieldName""frame_leftGrip"
	"xpos""0"
	"ypos""3"
	"wide""2"
	"tall""386"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	}
	"frame_rightGrip"
	{
	"ControlName""Panel"
	"fieldName""frame_rightGrip"
	"xpos""492"
	"ypos""3"
	"wide""2"
	"tall""381"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	}
	"frame_tlGrip"
	{
	"ControlName""Panel"
	"fieldName""frame_tlGrip"
	"xpos""0"
	"ypos""0"
	"wide""3"
	"tall""3"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	}
	"frame_trGrip"
	{
	"ControlName""Panel"
	"fieldName""frame_trGrip"
	"xpos""491"
	"ypos""0"
	"wide""3"
	"tall""3"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	}
	"frame_blGrip"
	{
	"ControlName""Panel"
	"fieldName""frame_blGrip"
	"xpos""0"
	"ypos""390"
	"wide""3"
	"tall""3"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	}
	"frame_brGrip"
	{
	"ControlName""Panel"
	"fieldName""frame_brGrip"
	"xpos""486"
	"ypos""385"
	"wide""8"
	"tall""8"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	}
	"frame_caption"
	{
	"ControlName""Panel"
	"fieldName""frame_caption"
	"xpos""0"
	"ypos""0"
	"wide""490"
	"tall""10"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	}
	"frame_minimize"
	{
	"ControlName""Button"
	"fieldName""frame_minimize"
	"xpos""0"
	"ypos""0"
	"wide""2"
	"tall""2"
	"autoResize""0"
	"pinCorner""0"
	"visible""0"
	"enabled""1"
	"tabPosition""0"
	"labelText""0"
	"textAlignment""north-west"
	"dulltext""0"
	"brighttext""0"
	"wrap""0"
	"centerwrap""0"
	"textinsetx""2"
	"textinsety""1"
	"Default""0"
	}
	"frame_maximize"
	{
	"ControlName""Button"
	"fieldName""frame_maximize"
	"xpos""0"
	"ypos""0"
	"wide""2"
	"tall""2"
	"autoResize""0"
	"pinCorner""0"
	"visible""0"
	"enabled""1"
	"tabPosition""0"
	"labelText""1"
	"textAlignment""north-west"
	"dulltext""0"
	"brighttext""0"
	"wrap""0"
	"centerwrap""0"
	"textinsetx""2"
	"textinsety""1"
	"Default""0"
	}
	"frame_mintosystray"
	{
	"ControlName""Button"
	"fieldName""frame_mintosystray"
	"xpos""0"
	"ypos""0"
	"wide""2"
	"tall""2"
	"autoResize""0"
	"pinCorner""0"
	"visible""0"
	"enabled""1"
	"tabPosition""0"
	"labelText""o"
	"textAlignment""north-west"
	"dulltext""0"
	"brighttext""0"
	"wrap""0"
	"centerwrap""0"
	"textinsetx""2"
	"textinsety""1"
	"command""MinimizeToSysTray"
	"Default""0"
	}
	"frame_close"
	{
	"ControlName""Button"
	"fieldName""frame_close"
	"xpos""0"
	"ypos""0"
	"wide""2"
	"tall""2"
	"autoResize""0"
	"pinCorner""0"
	"visible""0"
	"enabled""1"
	"tabPosition""0"
	"labelText""r"
	"textAlignment""north-west"
	"dulltext""0"
	"brighttext""0"
	"wrap""0"
	"centerwrap""0"
	"textinsetx""2"
	"textinsety""1"
	"Default""0"
	}
	"frame_menu"
	{
	"ControlName""FrameSystemButton"
	"fieldName""frame_menu"
	"xpos""3"
	"ypos""3"
	"wide""8"
	"tall""8"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	"textAlignment""west"
	"dulltext""0"
	"brighttext""0"
	"wrap""0"
	"centerwrap""0"
	"textinsetx""0"
	"textinsety""0"
	"Default""0"
	}
	"ItemSelection"
	{
	"ControlName""SectionedListPanel"
	"fieldName""ItemSelection"
	"xpos""45"
	"ypos""180"
	"wide""398"
	"tall""198"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	}
	"Drop"
	{
	"ControlName""Button"
	"fieldName""Drop"
	"xpos""45"
	"ypos""155"
	"wide""25"
	"tall""15"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	"labelText""Drop"
	"textAlignment""west"
	"dulltext""0"
	"brighttext""0"
	"wrap""0"
	"centerwrap""0"
	"textinsetx""6"
	"textinsety""0"
	"Default""0"
	}
	"Exit"
	{
	"ControlName""Button"
	"fieldName""Exit"
	"xpos""80"
	"ypos""155"
	"wide""25"
	"tall""15"
	"autoResize""0"
	"pinCorner""0"
	"visible""1"
	"enabled""1"
	"tabPosition""0"
	"labelText""Exit"
	"textAlignment""west"
	"dulltext""0"
	"brighttext""0"
	"wrap""0"
	"centerwrap""0"
	"textinsetx""6"
	"textinsety""0"
	"command""turnoff"
	"Default""1"
	}
	"BuildModeDialog"
	{
		"ControlName""BuildModeDialog"
		"fieldName""BuildModeDialog"
		"xpos""18"
		"ypos""1"
		"wide""140"
		"tall""196"
		"autoResize""0"
		"pinCorner""0"
		"visible""1"
		"enabled""1"
		"tabPosition""0"
		"settitlebarvisible""1"
		"title""#Frame_Untitled"
	}
}

The Entity

This part shows how to create a basic entity.

First of all create a file in the server project. sdk_melon.cpp

The following code is the complete melon entity. It's explained with comments.

#include "cbase.h"
#include "sdk_player.h"
#define COMM_MODEL "models/props_junk/watermelon01.mdl" // The Model of our melon.
class CMelon : public CBaseAnimating { // The melons Class
public:
   DECLARE_CLASS(CMelon, CBaseAnimating); // Declares CMelon to CBaseAnimating.
   DECLARE_DATADESC(); // Declares all datadescs.
   CMelon(){

   }
   // Use System starts here//
	#ifdef GAME_DLL
	// Allow +USE pickup
	int	ObjectCaps( void ) 
	{ 
		return (BaseClass::ObjectCaps() | FCAP_IMPULSE_USE | FCAP_USE_IN_RADIUS);
	}
	#endif
	// Use System Ends here //
    	void Spawn( void ); // Spawn function
		void Precache( void ); // Precache function.
    	void OnUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); // Use function.
private:   
	bool CreateVPhysics(); // Physic function.
};

LINK_ENTITY_TO_CLASS(func_melon, CMelon);
BEGIN_DATADESC(CMelon)

   DEFINE_USEFUNC( OnUse ), // Use function gets linked here.

   
END_DATADESC()

void CMelon::Precache(){ 
   PrecacheModel( COMM_MODEL ); // The model gets precached.
   BaseClass::Precache(); // Links the precache function to the baseclass.
}

void CMelon::Spawn(){
   
   Precache(); // Runs the precache function.
   SetUse(&CMelon::OnUse); // Links our onuse function.
   SetModel( COMM_MODEL ); // Sets the model.
   SetSolid( SOLID_NONE ); // Sets our melon to SOLID_NONE because we use CreateVPhysics().
	CreateVPhysics();
}

void CMelon::OnUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ){
   	CSDKPlayer *pPlayer = ToSDKPlayer( pActivator ); // The activation becomes a CSDKPlayer.
	if ( pPlayer == NULL) // If he doesn't exists, then...
		return; // Abort the use function.
	pPlayer->InventoryAddItem(1); // Adds the entity with the id 1 (our Melon) into the players inventory.
	Remove(); // Deletes the entity from the world.
}

bool CMelon::CreateVPhysics()
{
	// Create the object in the physics system
	VPhysicsInitNormal( SOLID_BBOX, 0, false ); // Inits the VPhysics and makes our entity solid.
	return true;
}

CON_COMMAND(create_melon, "Melons... Yammeee.") // Spawns the melon.
{
	//Same like the InventoryRemoveItem function.
	Vector vecForward;
	CBasePlayer *pPlayer = UTIL_GetCommandClient();
	if(!pPlayer)
	{
		Warning("Could not deterime calling player!\n");
		return;
	}

	AngleVectors( pPlayer->EyeAngles(), &vecForward );
	CBaseEntity *pEnt = CreateEntityByName( "func_melon" );
	if ( pEnt )
	{
        trace_t tr;
        UTIL_TraceLine(pPlayer->EyePosition(), pPlayer->EyePosition() + (vecForward * 256), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr);
		pEnt->SetAbsOrigin(tr.endpos + Vector(0,0,32));
		pEnt->SetAbsAngles(vec3_angle);
		pEnt->Spawn();
	}
}

You're done! I hope I didn't forget anything. If you got problems just post in here.

Edit: With this tutorial you can do as example a MySQL Inventory or one that gets saved with valves filesystem.