Skip to content

Commit

Permalink
Implement object hints
Browse files Browse the repository at this point in the history
  • Loading branch information
FreeSlave committed Oct 29, 2024
1 parent 0636242 commit 1ecbce6
Show file tree
Hide file tree
Showing 28 changed files with 1,075 additions and 88 deletions.
1 change: 1 addition & 0 deletions cl_dll/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ set (CLDLL_SOURCES
hud_error_collection.cpp
hud_inventory.cpp
hud_msg.cpp
hud_objecthint.cpp
hud_redraw.cpp
hud_spectator.cpp
hud_renderer.cpp
Expand Down
2 changes: 2 additions & 0 deletions cl_dll/entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,8 @@ void DLLEXPORT HUD_CreateEntities( void )
Game_AddObjects();
CL_UpdateLaserSpot();

gHUD.objectHintManager.Update();

#if USE_VGUI
GetClientVoiceMgr()->CreateEntities();
#endif
Expand Down
1 change: 1 addition & 0 deletions cl_dll/hl/hl_baseentity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ BOOL CBaseMonster::FCanActiveIdle( void ) { return FALSE; }
bool CBaseToggle::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation, bool subtitle ) { return true; }
void CBaseToggle::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) { }
void CBaseToggle::SentenceStop( void ) { }
bool CBaseToggle::IsLockedByMaster() { return false; }
void CBaseMonster::MonsterInitDead( void ) { }
float CBaseMonster::HeadHitGroupDamageMultiplier() { return 3.0f; }
void CBaseMonster::TraceAttack( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { }
Expand Down
10 changes: 10 additions & 0 deletions cl_dll/hud.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,11 @@ int __MsgFunc_PlayMP3( const char *pszName, int iSize, void *pbuf )
return 1;
}

int __MsgFunc_ObjectHint( const char *pszName, int iSize, void *pbuf )
{
return gHUD.MsgFunc_ObjectHint( pszName, iSize, pbuf );
}

// TFFree Command Menu
void __CmdFunc_OpenCommandMenu( void )
{
Expand Down Expand Up @@ -617,6 +622,7 @@ void CHud::Init( void )
HOOK_MESSAGE( VGUIMenu );

HOOK_MESSAGE( PlayMP3 );
HOOK_MESSAGE( ObjectHint );

CVAR_CREATE( "hud_classautokill", "1", FCVAR_ARCHIVE | FCVAR_USERINFO ); // controls whether or not to suicide immediately on TF class switch
CVAR_CREATE( "hud_takesshots", "0", FCVAR_ARCHIVE ); // controls whether or not to automatically take screenshots at the end of a round
Expand Down Expand Up @@ -693,6 +699,8 @@ void CHud::Init( void )

CreateBooleanCvarConditionally(m_pCvarCrosshairColorable, "crosshair_colorable", clientFeatures.crosshair_colorable);

m_pCvarObjectHint = CVAR_CREATE("cl_objecthint", "1", FCVAR_ARCHIVE);

m_pCvarMOTDVGUI = CVAR_CREATE("cl_motd_vgui", "1", FCVAR_ARCHIVE);
m_pCvarScoreboardVGUI = CVAR_CREATE("cl_scoreboard_vgui", "1", FCVAR_ARCHIVE);

Expand Down Expand Up @@ -1239,6 +1247,8 @@ void CHud::VidInit( void )

m_iFontHeight = m_rgrcRects[m_HUD_number_0].bottom - m_rgrcRects[m_HUD_number_0].top;

objectHintManager.Clear();

m_Ammo.VidInit();
m_Health.VidInit();
m_Spectator.VidInit();
Expand Down
5 changes: 5 additions & 0 deletions cl_dll/hud.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

#include "hud_renderer.h"
#include "hud_inventory.h"
#include "hud_objecthint.h"

#include <vector>
#include <string>
Expand Down Expand Up @@ -906,6 +907,8 @@ class CHud
cvar_t *m_pCvarHudBlue;
cvar_t *m_pCvarArmorNearHealth;

cvar_t *m_pCvarObjectHint;

cvar_t *m_pCvarMOTDVGUI;
cvar_t *m_pCvarScoreboardVGUI;

Expand Down Expand Up @@ -1057,6 +1060,7 @@ class CHud
int _cdecl MsgFunc_Items(const char* pszName, int iSize, void* pbuf);
int _cdecl MsgFunc_SetFog( const char *pszName, int iSize, void *pbuf );
int _cdecl MsgFunc_KeyedDLight( const char *pszName, int iSize, void *pbuf );
int _cdecl MsgFunc_ObjectHint( const char *pszName, int iSize, void *pbuf );

// Screen information
SCREENINFO m_scrinfo;
Expand Down Expand Up @@ -1085,6 +1089,7 @@ class CHud
bool m_bFlashlight;

InventoryHudSpec inventorySpec;
ObjectHintManager objectHintManager;

HudSpriteRenderer hudRenderer;
bool hasHudScaleInEngine;
Expand Down
39 changes: 39 additions & 0 deletions cl_dll/hud_msg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include "parsemsg.h"
#include "r_efx.h"
#include "arraysize.h"
#include "string_utils.h"
#include "spritehint_flags.h"

#include "environment.h"

Expand Down Expand Up @@ -181,3 +183,40 @@ int CHud::MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf )
this->m_StatusIcons.DisableIcon( "dmg_concuss" );
return 1;
}

int CHud::MsgFunc_ObjectHint(const char *pszName, int iSize, void *pbuf)
{
BEGIN_READ(pbuf, iSize);

ObjectHint objectHint;

int flags = READ_BYTE();
if (flags & OBJECTHINT_FLAG_CLOSEST)
objectHint.interactable = true;
else
objectHint.interactable = false;
objectHint.entindex = READ_SHORT();

if (objectHint.entindex > 0)
{
objectHint.color = READ_COLOR();
objectHint.scaleFactor = READ_COORD();
objectHint.center = READ_VECTOR();
objectHint.size = READ_VECTOR();
const char* spriteName = READ_STRING();
strncpyEnsureTermination(objectHint.sprite, spriteName);

if (m_pCvarObjectHint->value)
{
const bool shouldSet = m_pCvarObjectHint->value == 2 ? objectHint.interactable : true;
if (shouldSet)
objectHintManager.SetHint(objectHint);
}
}
else if (objectHint.interactable)
{
objectHintManager.RemoveInteractable();
}

return 1;
}
223 changes: 223 additions & 0 deletions cl_dll/hud_objecthint.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
#include "hud_objecthint.h"
#include "hud.h"
#include "event_api.h"
#include "color_utils.h"

#include <cassert>

static void SetParentEntityIndex(TEMPENTITY* te, int entindex)
{
te->entity.curstate.iuser1 = entindex;
}

static int GetParentEntityIndex(const TEMPENTITY* te)
{
return te->entity.curstate.iuser1;
}

static cl_entity_t* GetParentEntity(const TEMPENTITY* te)
{
int entindex = GetParentEntityIndex(te);
if (entindex)
return gEngfuncs.GetEntityByIndex(entindex);
return nullptr;
}

static void SetExpirationTime(TEMPENTITY* te, float t)
{
te->entity.curstate.fuser1 = t;
te->die = t + 1.0f;
}

static float GetExpirationTime(TEMPENTITY* te)
{
return te->entity.curstate.fuser1;
}

static void SetScaleFactor(TEMPENTITY* te, float scale)
{
te->entity.curstate.fuser2 = scale;
}

static void SetSizeVector(TEMPENTITY* te, const Vector& size)
{
te->entity.curstate.vuser1 = size;
}

static void SetSpriteScaleFromSize(TEMPENTITY* te, const Vector& size)
{
te->entity.curstate.scale = size.Length() * 0.015625f * te->entity.curstate.fuser2;
}

static void SetOffsetVector(TEMPENTITY* te, const Vector& offset)
{
te->entity.curstate.vuser2 = offset;
}

static color24 UnpackRGB(int rgb)
{
color24 color;
color.r = (rgb & 0xFF0000) >> 16;
color.g = (rgb & 0xFF00) >> 8;
color.b = rgb & 0xFF;
return color;
}

static void SetColor(TEMPENTITY* te, color24 color)
{
te->entity.curstate.iuser2 = PackRGB(color.r, color.g, color.b);
if (color.r == 0 && color.g == 0 && color.b == 0)
te->entity.curstate.rendercolor = UnpackRGB(gHUD.HUDColor());
else
te->entity.curstate.rendercolor = color;
}

static void UpdateObjectHintParams(TEMPENTITY* te, const ObjectHint &objectHint)
{
SetColor(te, objectHint.color);

SetScaleFactor(te, objectHint.scaleFactor);
SetSpriteScaleFromSize(te, objectHint.size);
SetSizeVector(te, objectHint.size);

cl_entity_t* ent = GetParentEntity(te);
if (ent)
SetOffsetVector(te, ent->origin - objectHint.center);
else
SetOffsetVector(te, Vector(0,0,0));
}

void ObjectHintManager::SetHint(const ObjectHint &objectHint)
{
TEMPENTITY* existingHint = ExistingCurrentHint(objectHint.entindex);
if (existingHint)
{
if (strcmp(existingHint->entity.model->name, objectHint.sprite) == 0)
{
existingHint->entity.origin = objectHint.center;
UpdateObjectHintParams(existingHint, objectHint);

SetExpirationTime(existingHint, gEngfuncs.GetClientTime() + 0.2f);
return;
}
else
{
existingHint->die = gEngfuncs.GetClientTime();
_independentHints.erase(objectHint.entindex);
}
}

if (!*objectHint.sprite)
return;

model_t* model = EnsureSpriteLoaded(objectHint.sprite);
if (!model)
return;

Vector pos = objectHint.center;
TEMPENTITY* te = gEngfuncs.pEfxAPI->CL_TempEntAlloc(pos, model);
if (!te)
return;

SetParentEntityIndex(te, objectHint.entindex);

te->entity.curstate.rendermode = kRenderGlow;
te->entity.curstate.renderfx = kRenderFxNoDissipation;
te->entity.curstate.renderamt = 255;

UpdateObjectHintParams(te, objectHint);

SetExpirationTime(te, gEngfuncs.GetClientTime() + 0.2f);

if (objectHint.interactable)
{
RemoveInteractable();
_interactableHint = te;
}
else
{
_independentHints[objectHint.entindex] = te;
}
}

void ObjectHintManager::RemoveInteractable()
{
if (_interactableHint)
{
_interactableHint->die = gEngfuncs.GetClientTime();
_interactableHint = nullptr;
}
}

void ObjectHintManager::Update()
{
const float clientTime = gEngfuncs.GetClientTime();
if (_interactableHint)
{
if (GetExpirationTime(_interactableHint) <= clientTime)
RemoveInteractable();
else
UpdateHint(_interactableHint);
}
for (auto it = _independentHints.begin(); it != _independentHints.end();)
{
if (GetExpirationTime(it->second) <= clientTime)
{
it->second->die = clientTime;
_independentHints.erase(it++);
}
else
{
UpdateHint(it->second);
++it;
}
}
}

void ObjectHintManager::UpdateHint(TEMPENTITY* te)
{
int entindex = GetParentEntityIndex(te);
if (entindex)
{
cl_entity_t* ent = gEngfuncs.GetEntityByIndex(entindex);
if (ent)
te->entity.origin = ent->origin - te->entity.curstate.vuser2;
}
if (te->entity.curstate.iuser2 == 0)
te->entity.curstate.rendercolor = UnpackRGB(gHUD.HUDColor());
}

model_t* ObjectHintManager::EnsureSpriteLoaded(const char* name)
{
assert(name && *name);
std::string sname = name;
auto it = _loadedSprites.find(sname);
if (it != _loadedSprites.end())
{
return it->second;
}
model_t* model = const_cast<model_t*>(gEngfuncs.GetSpritePointer(gEngfuncs.pfnSPR_Load(name)));
_loadedSprites[sname] = model;
return model;
}

void ObjectHintManager::Clear()
{
_loadedSprites.clear();
_interactableHint = nullptr;
_independentHints.clear();
}

TEMPENTITY* ObjectHintManager::ExistingCurrentHint(int entindex)
{
if (_interactableHint && GetParentEntityIndex(_interactableHint) == entindex)
{
return _interactableHint;
}
auto it = _independentHints.find(entindex);
if (it != _independentHints.end())
{
return it->second;
}
return nullptr;
}
Loading

0 comments on commit 1ecbce6

Please sign in to comment.