HalfLife
March 5

Half-Life SDK: Рамка-обводка на выделяемой сущности

Суть: Рисование рамки вокруг сущности (entity), на которую смотрит игрок.

Итоговый результат:

(!) Кого интересует сразу решение рисования — может перейти на шаг 7-ой.

1) В "dlls/player.cpp" получаем ищем нужную сущность. Например в CBasePlayer::UpdateStatusBar(). Сразу после проверки (pEntity->Classify() == CLASS_PLAYER) пишем:

else if (pEntity->Classify() != CLASS_PLAYER) // если сущность НЕ игрок
{
	if (pEntity->IsAlive()) // если сущность жива
	{
		MESSAGE_BEGIN(MSG_ONE, gmsgNpcHealth, NULL, this->pev); // gmsgNpcHealth у меня содержит инфу о наблюдаемом NPC вообще
		WRITE_STRING(STRING(pEntity->pev->classname)); // имя класса сущности (напр. monster_human_grunt)
		WRITE_SHORT(static_cast<int>(pEntity->pev->health)); // здоровье сущности
		WRITE_SHORT(static_cast<int>(pEntity->pev->max_health)); // максимальное здоровье сущности
		WRITE_SHORT(ENTINDEX(pEntity->edict())); // индекс сущности
		MESSAGE_END();
	}
	else // а это на всякий случай
	{
		MESSAGE_BEGIN(MSG_ONE, gmsgNpcHealth, NULL, this->pev);
		WRITE_STRING("");
		WRITE_SHORT(0);
		WRITE_SHORT(0);
		MESSAGE_END();
	}
}

Нам для дела понадобится только "ENTINDEX(pEntity->edict())".

2) В "dlls/UserMessages.h" в самом конце, после остальных сообщений, пишем:

inline int gmsgNpcHealth = 0;

3) В "dlls/UserMessages.cpp" в функции "LinkUserMessages()" пишем:

gmsgNpcHealth = REG_USER_MSG("NpcHealth", -1);

4) В "cl_dll/hud.h" в классе CHudStatusBar (class CHudStatusBar : public CHudBase) пишем (в условный protected):

// Для удобства: в каком порядке отправили из "dlls/player.cpp", в таком и записываем...
char NPC_Name[256]; // 1 - classname
int NPC_Health; // 2 - health
int NPC_MaxHealth; // 3 - maxhealth
int NPC_Index; // 4 - индекс сущности

5) В "cl_dll/statusbar.cpp" в начале файла, например после "DECLARE_MESSAGE(m_StatusBar, StatusValue);" декларируем/объявляем наше сообщение:

DECLARE_MESSAGE(m_StatusBar, NpcHealth);

6) В "cl_dll/statusbar.cpp" где-нибудь в конце пишем код ниже. Здесь мы получаем и читаем содержимое, которое мы отправили в "dlls/player.cpp".

bool CHudStatusBar::MsgFunc_NpcHealth(const char* pszName, int iSize, void* pbuf)
{
	BEGIN_READ(pbuf, iSize);

	strncpy(NPC_Name, READ_STRING(), sizeof(NPC_Name) - 1); // получаем classname
	NPC_Name[sizeof(NPC_Name) - 1] = '\0';

	NPC_Health = READ_SHORT(); // получаем здоровье
	NPC_MaxHealth = READ_SHORT(); // получаем максимальное здоровье
	NPC_Index = READ_SHORT(); // получаем индекс

	m_iFlags |= HUD_ACTIVE;

	return true;
}

7) В "cl_dll/statusbar.cpp" в "Draw()" ("bool CHudStatusBar::Draw(float fTime)") после имеющегося кода пишем:

cl_entity_t* pEntity = gEngfuncs.GetEntityByIndex(NPC_Index); // Ищем сущность с нашим индексом

if (pEntity && pEntity->model)
{
	DrawBoundingBox(pEntity);
}

где "DrawBoundingBox" это следующая функция:

void CHudStatusBar::DrawBoundingBox(cl_entity_t* pEntity)
{
	Vector vecMins = pEntity->curstate.mins;
	Vector vecMaxs = pEntity->curstate.maxs;
	Vector vecOrigin = pEntity->curstate.origin;

	float flScreenX[8], flScreenY[8];
	bool bVisible = false;
	float minX = 9999, maxX = -9999, minY = 9999, maxY = -9999;

	for (int i = 0; i < 8; i++)
	{
		Vector corner = vecOrigin;
		corner.x += (i & 1) ? vecMaxs.x : vecMins.x;
		corner.y += (i & 2) ? vecMaxs.y : vecMins.y;
		corner.z += (i & 4) ? vecMaxs.z : vecMins.z;

		float screen[2];

		if (gEngfuncs.pTriAPI->WorldToScreen(corner, screen) == 0)
		{
			flScreenX[i] = (screen[0] + 1) * gHUD.m_scrinfo.iWidth / 2;
			flScreenY[i] = (-screen[1] + 1) * gHUD.m_scrinfo.iHeight / 2;

			minX = min(minX, flScreenX[i]);
			maxX = max(maxX, flScreenX[i]);
			minY = min(minY, flScreenY[i]);
			maxY = max(maxY, flScreenY[i]);
			bVisible = true;
		}
	}

	if (!bVisible)
		return;
	
	// Рисование рамок

	int x = static_cast<int>(minX);
	int y = static_cast<int>(minY);
	int w = static_cast<int>(maxX - minX);
	int h = static_cast<int>(maxY - minY);

	gEngfuncs.pfnFillRGBA(x, y, w, 1, 255, 0, 0, 255);
	gEngfuncs.pfnFillRGBA(x, y + h, w, 1, 255, 0, 0, 255);
	gEngfuncs.pfnFillRGBA(x, y, 1, h, 255, 0, 0, 255);
	gEngfuncs.pfnFillRGBA(x + w, y, 1, h, 255, 0, 0, 255);
}

Не забываем добавить эту функцию в "public" "CHudStatusBar"а:

class CHudStatusBar : public CHudBase
{
public:
	
	...
	void DrawBoundingBox(cl_entity_t* pEntity);
	...
}

Конечный результат можно украсить, нарисовав элементы рамки теми же спрайтами.


(!) Возможно при сборке компилятор поругается на отсутствие функций "min/max" в "DrawBoundingBox". Тогда в начале "cl_dll/statusbar.cpp" пишем:

#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))

Это решение есть в некоторых файлах Half-Life SDK.

(!) И возможно ещё потребно будет подключить "TriangleAPI":

#include "triangleapi.h"

На этом всё. Пишите, делитесь и т.д. и т.п. — будет интересно поглядеть.