Sidebar

[Source] Инвентарь (Reborn)

MaSTeR

New member
15.04.2009
793
26
32
0
[Source] Инвентарь (Reborn)

Часть первая. Пролог.
Привет всем, кто меня знает и не знает. Сегодня я решил написать тутор по созданию инвентаря в сорсе. Очень очень давно, когда я работал над модом Alchemilla, у меня было желание сделать таки нормальный инвентарь. Несколько месяцев назад, я вернулся ненадолго к сорсу и решил все-же доделать то, что начал. Сегодня я уже могу сказать, что создал более-менее нормальную систему, которая имеет довольно неплохой функционал. Из всего что я реализовал на данный момент инвентарь включает в себя возможность динамического создания новых предметов, путем редактирования файла (Без необходимости компилировать каждый раз код) и возможность использования некоторых предметов только в определенных зонах (Вызвав при этом какой-либо инпут у любой энтити на карте). Так же, можно выполнить какую-либо команду при использовании предмета. В планах очень много всего ещё, поэтому, я планирую дополнять это сообщение по мере реализации новых возможностей. И так, приступим.
Часть вторая. Сервер.
Вся система инвентаря довольно несложная и выглядит примерно таким образом: Игрок заходит в специальную зону trigger_inventoryzone и использует какой-либо предмет. Если он использовал предмет, который должен использовать в этой зоне, то энтити logic_item_control активирует инпут у необходимой цели. Вся система может показаться немного громоздкой, но я сейчас как раз размышляю над оптимизацией этого всего, поэтому, пусть пока будет таким вот образом. :)
Начнем мы с файла shareddefs.h, в который где-то в области 260 строки после #define WEAPON_IS_ACTIVE нужно вписать:
Код:
#define DEFAULT_INVENTORY_FILE "scripts/inventoryitems.res" //Дефолтный путь до файла с информацией о предметах
#define INVENTORY_CONTROLLER_NAME "control_inventory" //Имя logic_item_control 
//(будет необходимо в trigger_inventoryzone 

// -----------------------------------------
//	MaSTeR: Флаги для предметов инвентаря
// -----------------------------------------
#define INVENTORY_FLAG_DELETE_AFTER_USE (1<<0) //Удалить после использования
#define INVENTORY_FLAG_CAN_USE	        (1<<1) //Может ли игрок использовать предмет в данный момент
#define INVENTORY_FLAG_CAN_EQUIP	        (1<<2) //Может ли игрок надеть (экипировать) этот предмет
#define INVENTORY_FLAG_EQUIPPED	        (1<<3) //Экипирован ли предмет сейчас
#define INVENTORY_FLAG_COMBINE		(1<<4) //И можно ли комбинировать его с другим предметом

#define MAX_INVENTORY_ITEMS			  256 //Максимальное количество предметов в инвентаре
Некоторые флаги пока что не используются (в скором времени это будет исправлено).
Теперь, давайте напишем парсер файла inventoryitems.res, который будет получать всю информацию о предметах. Для этого нам необходим отдельный класс (*.cpp и *.h файлы для удобства можно создать в папке shared). Начнем с заголовочного файла inventory_parse.h:
Код:
#include "cbase.h"
#include "KeyValues.h"
#include "utlvector.h"
#include "filesystem.h"
#include "shareddefs.h"
#if defined (CLIENT_DLL)
#include "vgui/ISurface.h"
#include <vgui/ILocalize.h>
#include "vgui_controls/Controls.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifndef INVPARSE_H
#define INVPARSE_H
#ifdef _WIN32
#pragma once
#endif

#define MAX_ITEM_STRING   256 

struct InventoryItem //Структура, в которой будет храниться информация о предмете
{
		int   m_iId; // ID предмета
		char  szName[MAX_ITEM_STRING]; //Его имя (используется для получения szLocalizedName из файла локализации
		char  szDescr[MAX_ITEM_STRING]; //Описание (так же берется из файла локализации)
		char  szMaterial[MAX_ITEM_STRING]; //Материал. Этот параметр мы будем использовать для вывода иллюстрации к примеру.
		int   m_iMaxCount; //Максимальное количество. Этот и некоторые параметры ниже пока что не используются.
		int   m_iTakeCount; //Количество предмета, которое игрок получит за раз.
		char  szCommand[MAX_ITEM_STRING]; //Команда, которая выполнится при использовании предмета
		int   m_iItemflags; //Флаги
		int   m_iCombineWith; //С чем можно комбинировать
#if defined( CLIENT_DLL )
		int   m_iTextureID; //ID текстуры.
		wchar_t szLocalizedName[256]; //Имя, взятое из файла локализации
		wchar_t szLocalizedDescr[512];
#endif
};

//Ну и, собственно, сам класс
class CInventoryParser
{
public:	
	CInventoryParser(const char *filename); 
	void ParseFile(); //Парсинг информации из файла
	InventoryItem *GetItem(int ID); //Получить ссылку на предмет по его ID
	void SetItemFlags(int ItemID, int flags); //Добавить флаги
	void UnsetItemFlags(int ItemID, int flags); //Снять флаги
private:
	CUtlVector<InventoryItem*> m_ItemList; //Вектор со ссылками на предметы
	char  szFilename[MAX_ITEM_STRING]; 
	int   m_iItemsCount;
	bool  m_bFileParsed;
};
#endif
Теперь займемся файлом inventory_parse.cpp:
Код:
#include "cbase.h"
#include "Inventory_parse.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

void CInventoryParser::ParseFile()
{
	m_ItemList.PurgeAndDeleteElements();
	KeyValues *kv = new KeyValues("InventoryItems");
	if (kv->LoadFromFile(filesystem, szFilename))
	{
		for(KeyValues *sub = kv->GetFirstSubKey(); sub;)
		{
			InventoryItem *item = (InventoryItem*)malloc(sizeof(InventoryItem));
			item->m_iId = sub->GetInt("ID");
			Q_strncpy(item->szName, sub->GetString("Name"), MAX_ITEM_STRING);
			Q_strncpy(item->szDescr, sub->GetString("Description"), MAX_ITEM_STRING);
			Q_strncpy(item->szMaterial, sub->GetString("Material"), MAX_ITEM_STRING);
			item->m_iMaxCount = sub->GetInt("MaxCount");
			item->m_iTakeCount = sub->GetInt("PlayerTakeCount");
			Q_strncpy(item->szCommand, sub->GetString("Command"), MAX_ITEM_STRING);
			item->m_iItemflags = sub->GetInt("Itemflags");
			item->m_iCombineWith = sub->GetInt("CombineWith");
	
#if defined (CLIENT_DLL)
			item->m_iTextureID = vgui::surface()->CreateNewTextureID();
			vgui::surface()->DrawSetTextureFile(item->m_iTextureID, item->szMaterial, true, false);
			g_pVGuiLocalize->ConstructString( item->szLocalizedName, sizeof(item->szLocalizedName), g_pVGuiLocalize->Find(item->szName), 1);
			g_pVGuiLocalize->ConstructString( item->szLocalizedDescr, sizeof(item->szLocalizedDescr), g_pVGuiLocalize->Find(item->szDescr), 1);
#endif
			m_ItemList.AddToTail(item);
			item=NULL;
			sub = sub->GetNextKey();	
			m_iItemsCount++;	
		}
			m_bFileParsed = true;
	}
	else
		Error("Could not open file %s\n", szFilename);
}

InventoryItem * CInventoryParser::GetItem(int ID)
{
	for (int i = 0; i < m_iItemsCount; i++)
	{
		if(m_ItemList.Element(i)->m_iId == ID)
			return m_ItemList.Element(i);
	}
	return NULL;
}

CInventoryParser::CInventoryParser(const char *filename)
{
	Q_strncpy(szFilename, filename, MAX_ITEM_STRING);
	m_bFileParsed = false;
	m_iItemsCount = 0;
}

void CInventoryParser::SetItemFlags(int ItemID, int flags)
{
	InventoryItem *pItem = GetItem(ItemID);
	if (pItem != NULL)
	{
		pItem->m_iItemflags = pItem->m_iItemflags | flags;
	}
}
void CInventoryParser::UnsetItemFlags(int ItemID, int flags)
{
	InventoryItem *pItem = GetItem(ItemID);
	if (pItem != NULL)
	{
		pItem->m_iItemflags = pItem->m_iItemflags & ~flags;
	}
}
И так, парсер у нас есть. Теперь, залезаем в player.h на сервере и добавляем наш заголовок: #include "Inventory_parse.h" а в разделе public вписываем следующее:
Код:
        CNetworkArray(int, m_iInventory, MAX_INVENTORY_ITEMS);
        CInventoryParser *pInvParser;
	virtual void InventoryAddItem( int EntityID, int Count = 1 ); // Добавляет предмет с заданным ID
	virtual void InventoryRemoveItem( int ID); // Убирает предмет с заданным ID
	int GetItemCount()
	{
	int ItemCount;
	for( int i = 0; i < MAX_INVENTORY_ITEMS; i++)
	{
		if(!m_iInventory.Get(i))
			break;
		else
			ItemCount ++;
	}	
	return ItemCount;
	}
А в player.cpp в самом конструкторе парсим наш файл с информацией о предметах, добавив перед m_flFlashTime следующие строки:
Код:
	pInvParser = new CInventoryParser(DEFAULT_INVENTORY_FILE);
	pInvParser->ParseFile();
Теперь, добавим наши функции.
Код:
void CBasePlayer::InventoryAddItem(int EntityID, int Count)
{
	for (int i = 0; i < MAX_INVENTORY_ITEMS; i++)
		if ( m_iInventory.Get(i) < 0)
			m_iInventory.Set(EntityID, 0);
	Msg("Adding entity with id: %i\n",EntityID);
	if (EntityID < MAX_INVENTORY_ITEMS) {
	int ItemCount = m_iInventory.Get(EntityID);
	ItemCount += Count;
	if (ItemCount < 0)
		ItemCount = 0;
	m_iInventory.Set(EntityID, ItemCount);
	} else
		Error("Unable to add entity with ID: %s !\n ", EntityID);
	engine->ClientCommand(edict(),"UpdateInventory");
}

void CBasePlayer::InventoryRemoveItem(int ID)
{
	int ItemCount = m_iInventory.Get(ID);
	if( ItemCount )
	{
			ItemCount--;
		if (ItemCount < 0)
			ItemCount = 0;
			m_iInventory.Set(ID, ItemCount); // -1 Ent
	}
	else
	{
		Error("Unable to delete entity with position: %s !\n ", Position);
	}
	engine->ClientCommand(edict(),"UpdateInventory");
}
Чтобы предметы в инвентаре сохранялись, нужно внутри BEGIN_DATADESC( CBasePlayer ) примерно после DEFINE_FIELD( m_hVehicle, FIELD_EHANDLE ), вписать строку:
Код:
DEFINE_ARRAY( m_iInventory, FIELD_INTEGER, MAX_INVENTORY_ITEMS),
И, нам так же будет необходимо пересылать клиенту наш инвентарь (для создания к нему VGUI панели) поэтому, внутри IMPLEMENT_SERVERCLASS_ST( CBasePlayer, DT_BasePlayer ) после строчки SendPropString (SENDINFO(m_szLastPlaceName) ), необходимо так же добавить:
Код:
SendPropArray3 (SENDINFO_ARRAY3(m_iInventory), SendPropInt( SENDINFO_ARRAY(m_iInventory) ) ),
И так, что у нас есть на данный момент: Мы можем парсить предметы, добавлять и убирать предметы с определенными ID из инвентаря, у нас есть массив, который сохраняется и пересылается клиенту. Теперь нужно добавить использование предметов. Для этого добавим обработку необходимой команды в функции bool CBasePlayer::ClientCommand( const CCommand &args ). Обработчик команды можно вписать после условия else if ( stricmp( cmd, "spec_goto" ) == 0 ) добавив:
Код:
else if ( FStrEq( cmd, "useitem" ) )
	{

		CBaseEntity *ent = NULL;
		CBaseEntity *pEntity = NULL;
		InventoryItem *pItem = pInvParser->GetItem(atoi(args[1]));
		if (m_iInventory.Get(atoi(args[1])) && pItem && pItem->m_iItemflags & INVENTORY_FLAG_CAN_USE)
		{
                 engine->ServerCommand(pItem->szCommand);
			while ( (ent = gEntList.NextEnt(ent)) != NULL )
			{
				if (  (ent->GetEntityName() != NULL_STRING	&& FStrEq(INVENTORY_CONTROLLER_NAME, STRING(ent->GetEntityName()))))
				{
					pEntity = ent;
					break;
				}
			}
			if (pEntity)
			{
				variant_t Value;
				Value.SetInt(atoi(args[1]));
				pEntity->AcceptInput("UseItem",this,NULL,Value,0);
			}
			if (pItem->m_iItemflags & INVENTORY_FLAG_DELETE_AFTER_USE)
			{
				InventoryRemoveItem(atoi(args[1]));
			}
		}
		return true;
	}
Часть третья. Новые энтити.

И так, сегодня суббота, почти семь утра. Самое время для продолжения работы над нашим инвентарём. А начнем мы создания энтити logic_item_control. Добавляем в проект *.h и *.cpp файлы с соответствующим названием.
Пишем в logic_item_control.h:
Код:
#ifndef ITEM_CTRL
#define ITEM_CTRL

#include "cbase.h"
#include "entityinput.h"
#include "entityoutput.h"
#include "eventqueue.h"

class CLogicItemController : public CLogicalEntity //Класс нашей энтити
{
public:
	DECLARE_CLASS( CLogicItemController, CLogicalEntity );

	CLogicItemController();

	void Activate();
	void Think();

	// Input handlers
	void InputEnable( inputdata_t &inputdata );
	void InputEnableRefire( inputdata_t &inputdata ); 
	void InputDisable( inputdata_t &inputdata );
	void InputToggle( inputdata_t &inputdata );
	void InputSetTarget( inputdata_t &inputdata ); //Задаёт цель, инпут которой мы активируем
	void InputSetItemID( inputdata_t &inputdata ); //Задаёт ID предмета
	void InputTrigger( inputdata_t &inputdata ); 
	void InputUseItem( inputdata_t &inputdata ); //Игрок использовал предмет
	void InputSetInput( inputdata_t &inputdata ); //Меняет инпут, который будет вызван у цели
	void InputCancelPending( inputdata_t &inputdata );

	DECLARE_DATADESC();

	// Outputs
	COutputEvent m_OnTrigger;
	COutputEvent m_OnSpawn;
	COutputEvent m_OCantUse;
	
private:

	bool m_bDisabled;
	char *Target; //Цель, которая будет активирована при использованиии "правильного" предмета
	int m_ItemID; //ID Предмета
	const char *InputName; //Имя инпута, который будет вызыван у цели
	bool m_bWaitForRefire;			 
};

#endif
А в logic_item_control.cpp:
Код:
#include "cbase.h"
#include "entityinput.h"
#include "entityoutput.h"
#include "eventqueue.h"
#include "soundent.h"
#include "logic_item_control.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

LINK_ENTITY_TO_CLASS(logic_item_control, CLogicItemController);


BEGIN_DATADESC( CLogicItemController )
        //Объявляем наши ключевые поля и инпуты с аутпутами
	DEFINE_FIELD(m_bWaitForRefire, FIELD_BOOLEAN),
	DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled"),
	DEFINE_KEYFIELD(m_ItemID,FIELD_INTEGER,"ItemID"),
	DEFINE_KEYFIELD(InputName,FIELD_STRING,"InputName"),

	// Inputs
	DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable),
	DEFINE_INPUTFUNC(FIELD_STRING, "SetTarget", InputSetTarget),
	DEFINE_INPUTFUNC(FIELD_INTEGER, "SetItemID", InputSetItemID),
	DEFINE_INPUTFUNC(FIELD_INTEGER, "UseItem",InputUseItem),
	DEFINE_INPUTFUNC(FIELD_VOID, "EnableRefire", InputEnableRefire),
	DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable),
	DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle),
	DEFINE_INPUTFUNC(FIELD_VOID, "Trigger", InputTrigger),
	DEFINE_INPUTFUNC(FIELD_STRING, "InputSetName",InputSetInput),
	DEFINE_INPUTFUNC(FIELD_VOID, "CancelPending", InputCancelPending),

	// Outputs
	DEFINE_OUTPUT(m_OnTrigger, "OnTrigger"),
	DEFINE_OUTPUT(m_OnSpawn, "OnSpawn"),
	DEFINE_OUTPUT(m_OCantUse, "OnCantUseItem"),
	

END_DATADESC()



//-----------------------------------------------------------------------------
// Purpose: Constructor.
//-----------------------------------------------------------------------------
CLogicItemController::CLogicItemController(void)
{
	m_ItemID = 0;
	Target = "null";
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CLogicItemController::Activate()
{
	BaseClass::Activate();
	
	if ( m_OnSpawn.NumberOfElements() > 0)
	{
		SetNextThink( gpGlobals->curtime + 0.01 );
	}
}


//-----------------------------------------------------------------------------
//Purpose: 
//-----------------------------------------------------------------------------
void CLogicItemController::Think()
{

	m_OnSpawn.FireOutput( this, this );

}


//------------------------------------------------------------------------------
// Purpose: 
//------------------------------------------------------------------------------
void CLogicItemController::InputEnable( inputdata_t &inputdata )
{
	m_bDisabled = false;
}

//------------------------------------------------------------------------------
// Purpose: 
//------------------------------------------------------------------------------
void CLogicItemController::InputEnableRefire( inputdata_t &inputdata )
{ 
	m_bWaitForRefire = false;
}


//------------------------------------------------------------------------------
// Purpose: 
//------------------------------------------------------------------------------
void CLogicItemController::InputCancelPending( inputdata_t &inputdata )
{ 
	g_EventQueue.CancelEvents( this );

	// Stop waiting; allow another Trigger.
	m_bWaitForRefire = false;
}


//------------------------------------------------------------------------------
// Purpose: 
//------------------------------------------------------------------------------
void CLogicItemController::InputDisable( inputdata_t &inputdata )
{ 
	m_bDisabled = true;
}


//------------------------------------------------------------------------------
// Purpose: Toggles the enabled/disabled state.
//------------------------------------------------------------------------------
void CLogicItemController::InputToggle( inputdata_t &inputdata )
{ 
	m_bDisabled = !m_bDisabled;
}

//------------------------------------------------------------------------------
// Purpose: 
//------------------------------------------------------------------------------
void CLogicItemController::InputUseItem( inputdata_t &inputdata ) //Игрок использовал какой-либо предмет
{ 
	bool Eq = inputdata.value.Int() == m_ItemID; //Получаем ID предмета, который использовал игрок и сравниваем с тем,
       //Который игрок должен использовать для соответствующей реакции триггера

	if(!m_bDisabled && Eq)
	{
		CBaseEntity *ent = NULL;
		CBaseEntity *pEntity = NULL;
		while ( (ent = gEntList.NextEnt(ent)) != NULL ) //Ищем в списке энтити нашу цель
		{
			if (  (ent->GetEntityName() != NULL_STRING	&& FStrEq(Target, STRING(ent->GetEntityName()))))
			{
				pEntity = ent;
				break;
			}
		}
		if (pEntity) //Как только мы нашли её
		{
			variant_t Value;
			pEntity->AcceptInput(InputName,this,NULL,Value,0); //Активируем у неё необходимый нам инпут
		}
		m_OnTrigger.FireOutput( inputdata.pActivator, this );
	}
	

        else (!Eq || m_bDisabled) //Если игрок не может использовать этот предмет здесь, то мы выдаём аутпут OnCantUse
	{
		m_OCantUse.FireOutput(inputdata.pActivator,this,0);
		return;
	}
}
//-----------------------------------------------------------------------------
//Sets Right item ID
//-----------------------------------------------------------------------------
void CLogicItemController::InputSetItemID( inputdata_t  &inputdata )
{
	if (!inputdata.value.Int())
		return;

	m_ItemID = inputdata.value.Int();
}
//-----------------------------------------------------------------------------
//Sets the target to activate
//-----------------------------------------------------------------------------
void CLogicItemController::InputSetTarget( inputdata_t  &inputdata )
{
	Target = UTIL_VarArgs("%s",inputdata.value.String());
}
//-----------------------------------------------------------------------------
//Sets the input that we activate
//-----------------------------------------------------------------------------
void CLogicItemController::InputSetInput( inputdata_t &inputdata )
{
	InputName = inputdata.value.String(); 
}
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CLogicItemController::InputTrigger( inputdata_t &inputdata )
{
	if ((!m_bDisabled) && (!m_bWaitForRefire))
	{
		m_OnTrigger.FireOutput( inputdata.pActivator, this );
		
	
	}
}
Теперь добавим в triggers.h наш новый триггер (сразу после CTriggerMultiple):
Код:
class CTriggerInventoryzone : public CTriggerMultiple //Да, класс на основе TriggerMultiple
{
	DECLARE_CLASS( CTriggerInventoryzone, CTriggerMultiple );
public:
	void Spawn( void );
	void StartTouch(CBaseEntity *pOther);
	void EndTouch(CBaseEntity *pOther);

	DECLARE_DATADESC();
	// Outputs
	COutputEvent m_OnTrigger;
private:
	int ControlItemID; //ID предмета
	string_t TargetName; //Имя цели
	string_t InputName; //Название инпута
};
а в triggers.cpp напишем следующее:
Код:
// ##################################################################################
//	>> TriggerInventoryZone
// ##################################################################################
LINK_ENTITY_TO_CLASS( trigger_inventoryzone, CTriggerInventoryzone );


BEGIN_DATADESC( CTriggerInventoryzone )

	// Function Pointers
	DEFINE_KEYFIELD(ControlItemID,FIELD_INTEGER,"ControlItemID"),
	DEFINE_KEYFIELD(TargetName,FIELD_STRING,"TargetToControl"),
	DEFINE_KEYFIELD(InputName,FIELD_STRING,"InputName"),
	DEFINE_FUNCTION(MultiTouch),
	DEFINE_FUNCTION(MultiWaitOver ),

	// Outputs
	DEFINE_OUTPUT(m_OnTrigger, "OnTrigger")

END_DATADESC()



//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CTriggerInventoryzone::Spawn( void )
{
	BaseClass::Spawn();

	InitTrigger();

	if (m_flWait == 0)
	{
		m_flWait = 0.2;
	}

	ASSERTSZ(m_iHealth == 0, "trigger_inventoryzone with health");
	SetTouch( &CTriggerInventoryzone::MultiTouch );
}
void CTriggerInventoryzone::StartTouch(CBaseEntity *pOther) //Игрок вошел в зону
{
	if (!m_bDisabled && pOther->IsPlayer())
	{
		CBaseEntity *ent = NULL;
		CBaseEntity *pEntity = NULL;
		
		while ( (ent = gEntList.NextEnt(ent)) != NULL )
			{
				if (  (ent->GetEntityName() != NULL_STRING	&& FStrEq(INVENTORY_CONTROLLER_NAME, STRING(ent->GetEntityName()))))
				{
					pEntity = ent;
					break;
				}
			}
			if (pEntity)
			{
				variant_t nill;
				variant_t Target;
				Target.SetString(TargetName);
				variant_t ItemID;
				ItemID.SetInt(ControlItemID);
				variant_t Input;
				Input.SetString(InputName); //Задаём logic_item_control необходимые параметры и запускаем его
				pEntity->AcceptInput("SetItemID",this,NULL,ItemID,0);
				pEntity->AcceptInput("InputSetName",this,NULL,Input,0);
				pEntity->AcceptInput("SetTarget",this,NULL,Target,0);
				pEntity->AcceptInput("Enable",this,NULL,nill,0);
				CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
				pPlayer->pInvParser->SetItemFlags(ControlItemID, INVENTORY_FLAG_CAN_USE); //Игрок теперь может использовать этот предмет
				
			}

	}
	BaseClass::StartTouch( pOther );

}
void CTriggerInventoryzone::EndTouch(CBaseEntity *pOther) //Игрок покинул зону
{
if (!m_bDisabled && pOther->IsPlayer())
	{
		CBaseEntity *ent = NULL;
		CBaseEntity *pEntity = NULL;

		while ( (ent = gEntList.NextEnt(ent)) != NULL )
			{
				if (  (ent->GetEntityName() != NULL_STRING	&& FStrEq(INVENTORY_CONTROLLER_NAME, STRING(ent->GetEntityName()))))
				{
					pEntity = ent;
					break;
				}
			}
			if (pEntity)
			{
				variant_t Value;
				pEntity->AcceptInput("Disable",this,NULL,Value,0); //Выключаем logic_item_control 
				CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
				//pPlayer->pInvParser->UnsetItemFlags(ControlItemID, INVENTORY_FLAG_CAN_USE); //Игрок больше не может использовать этот предмет (если необходимо - раскомментировать)
			}
	}

			BaseClass::EndTouch( pOther );

}
На этом мы завершаем нашу работу над кодом серверной части нашей системы.

Часть четвёртая. Клиент.

Для начала добавим в проект созданные нами inventory_parse.cpp и *.h, после этого открываем c_baseplayer.h и добавляем в класс C_BasePlayer наш массив с предметами и пару функций внутри public (Не забываем #include "inventory_parse.h" в самом начале):
Код:
	int GetInventoryArray( int ID) { return m_iInventory[ID]; } // Возвращает количество предмета с заданным ID
	int m_iInventory[MAX_INVENTORY_ITEMS]; //Массив с предметами
	CInventoryParser *pInvParser; //Парсер
Далее, в конструкторе C_BasePlayer в cpp файле парсим предметы (теперь уже это происходит на стороне клиента) вписав следующие строки:
Код:
	pInvParser = new CInventoryParser(DEFAULT_INVENTORY_FILE);
	pInvParser->ParseFile();
Так же, нам нужно получить от сервера наш массив, для этого нужно в IMPLEMENT_CLIENTCLASS_DT(C_BasePlayer, DT_BasePlayer, CBasePlayer) примерно после строки RecvPropEHandle (RECVINFO(m_hZoomOwner)), написать:
Код:
		RecvPropArray3( RECVINFO_ARRAY(m_iInventory), 
		RecvPropInt( RECVINFO( m_iInventory[0] ) ) ),
В итоге, нам остаётся только написать GUI меню для взаимодействия игрока с инвентарём и можно будет провести полноценный тест всей системы.
 

Вложения

Последнее редактирование:

Leshiy

Редактируемый
24.09.2011
216
26
4
18
Ох, ох! Вот за это огромное спасибо! Как починю свой Visual Studio - так и опробую. Но вот то что можно редактировать не компилируя по новой код - вообще супер!
 

MaSTeR

New member
15.04.2009
793
26
32
0
Leshiy сказал(а):
Но вот то что можно редактировать не компилируя по новой код - вообще супер!
Просто приведу пример того, как выглядят предметы в файле:
Код:
Item_1
	{
		ID "0"
		Name "#coltammo" //.45 ACP Colt Ammo
		Description "#coltammod" //Ammo for .45 ACP Colt 1911
		Material "inventory\items\pistol_ammo"
		MaxCount "30"
		PlayerTakeCount "1"
		Command "useitem ammo_pistol"
		Itemflags "3"
		CombineWith "14"
	}
 

Psycho-A

&#12288;
Команда форума
Модератор
29.08.2009
3 968
33
75
48
Неплохо, хотя над интерфейсом ещё много работы :)
Тогда уж сделай и проверку наличия нужного предмета в инвентаре по условию. Например, у нас дверь требует ключ класса "item_key_clinicdoor", и если предмет имеется в инвентаре, то производится открытие двери и освобождение занимаемого слота. А если ключа у нас нет, то, например, выводить на экран сообщение или отображать иконку. Можно также дописать проверку предметов для прочих управляемых энтить и NPC (NPC будет вести себя по-разному в зависимости от наличия у нас нужной вещи). Вот ещё пища для размышлений по энтитям:

trigger_inventory_check:
targetname <имя>
parentname <энтитя> // триггер можно припарентить
itemname <класс> // предмет в инвентаре, который проверяем
starthidden <0/1> // запускать триггер неактивным
Аутпуты: OnPlayerHasItem <энтитя,действие,...>
Интпуты: Hide/Unhide/Kill, SetNewItem <новый предмет>

Заместо спрайта лучше уж выводить готовую модель предмета - не придётся потом заморачиваться. И от ID я бы отказался в пользу классовой системы.
 
Последнее редактирование:

Leshiy

Редактируемый
24.09.2011
216
26
4
18
Да, отличная идея. Только что бы можно было отключать освобождение слота. Например если это электронный ключ-пропуск.
И еще, я что-то не нашел, ограничения инвентаря никакого нет? По предметам, или по весу.
 

MaSTeR

New member
15.04.2009
793
26
32
0
2 Psycho-A:
Очень интересная мысль! Думаю, это обязательно добавится в будущем) сейчас мне нужно дописать таки этот тутор, а там, уже, скорее всего, добавлю такую возможность.
P.S. Интерфейс еще будет сильно доработан=) Без этого никак.

[ADDED=MaSTeR]1448035709[/ADDED]
2 Leshiy:
INVENTORY_FLAG_DELETE_AFTER_USE если стоит 0, то предмет не пропадёт. Можно использовать это, если что:)
 
Последнее редактирование:

Psycho-A

&#12288;
Команда форума
Модератор
29.08.2009
3 968
33
75
48
2 Leshiy:
Удаление или неудаление из слота можно было бы задавать в параметрах самого скрипта, или же ещё одной энтитией. ТС предлагал logic_item_control - вот в ней через инпуты от других энтить (двери и т.д.) можно было бы решать, удалять объект из слота, или нет.
 

MaSTeR

New member
15.04.2009
793
26
32
0
2 Psycho-A:
Да, с помощью этой энтити, думаю, можно будет как раз задавать флаги к примеру :) У нее так же есть аутпут OnCantUse - вызывается когда игрок не может использовать предмет (любой) в данный момент.
 

MaSTeR

New member
15.04.2009
793
26
32
0
2 Дядя Миша:
Не думаю, что с этим могут возникнуть проблемы. В будущем могу попробовать написать вариант для ГС, если будет кому надо=)
 
Команда форума
VIP
28.03.2010
15 330
257
83
Кубань
  • Золотая медаль 215
  • Серебряная медаль 214
  • Золотая медаль 221
  • Cat
2 MaSTeR: у нас тут один товарищ уже долгие годы мечтает "рюкзак как в сталкере".
 

MaSTeR

New member
15.04.2009
793
26
32
0
2 Дядя Миша:
А что ему мешает?

UPD: Обновил шапку темы, добавил архив с fgd и, на всякий случай, res файлом с предметами.

P.S. Идея с выводом моделей вместо спрайтов очень даже понравилась. Скорее всего, в будущем, я напишу апдейт к этому тутору, успешно имплементирую это в своём коде.
 
Последнее редактирование:

RAZUM38RUS

New member
28.03.2021
3
0
1
Здравствуйте! Закодил инвентарь по вашему уроку для Source engine 2013 + добавил панель из данного урока (https://developer.valvesoftware.com/wiki/Adding_an_inventory) и у меня в ничего не срастается - подскажите пожалуйста как правильно написать GUI и как расставить предметы для инвентаря на карте... я просто уже всё перепробовал - второй день голову ломаю - скора лопнет)))
 

Донат - Хостинг

Итого
200.00 $
Цель
600.00 $

Доноры Красавчики

Пользователи онлайн

Нет пользователей онлайн.