mirror of
https://github.com/Gigaslav/HL2Overcharged.git
synced 2026-01-01 09:48:38 +03:00
1322 lines
34 KiB
C++
1322 lines
34 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $Workfile: $
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
#include "cbase.h"
|
|
#include <stdarg.h>
|
|
#include "hud.h"
|
|
#include "itextmessage.h"
|
|
#include "materialsystem/imaterial.h"
|
|
#include "materialsystem/itexture.h"
|
|
#include "materialsystem/imaterialsystem.h"
|
|
#include "imovehelper.h"
|
|
#include "checksum_crc.h"
|
|
#include "decals.h"
|
|
#include "iefx.h"
|
|
#include "view_scene.h"
|
|
#include "filesystem.h"
|
|
#include "model_types.h"
|
|
#include "engine/IEngineTrace.h"
|
|
#include "engine/ivmodelinfo.h"
|
|
#include "c_te_effect_dispatch.h"
|
|
#include <vgui_controls/Controls.h>
|
|
#include <vgui/ISurface.h>
|
|
#include <vgui/ILocalize.h>
|
|
#include "view.h"
|
|
#include "ixboxsystem.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
ConVar localplayer_visionflags("localplayer_visionflags", "0", FCVAR_DEVELOPMENTONLY);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// ConVars
|
|
//-----------------------------------------------------------------------------
|
|
#ifdef _DEBUG
|
|
|
|
ConVar r_FadeProps("r_FadeProps", "1");
|
|
|
|
#endif
|
|
bool g_MakingDevShots = false;
|
|
extern ConVar cl_leveloverview;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Performs a var args printf into a static return buffer
|
|
// Input : *format -
|
|
// ... -
|
|
// Output : char
|
|
//-----------------------------------------------------------------------------
|
|
char *VarArgs(const char *format, ...)
|
|
{
|
|
va_list argptr;
|
|
static char string[1024];
|
|
|
|
va_start(argptr, format);
|
|
Q_vsnprintf(string, sizeof(string), format, argptr);
|
|
va_end(argptr);
|
|
|
|
return string;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if the entity index corresponds to a player slot
|
|
// Input : index -
|
|
// Output : bool
|
|
//-----------------------------------------------------------------------------
|
|
bool IsPlayerIndex(int index)
|
|
{
|
|
return (index >= 1 && index <= gpGlobals->maxClients) ? true : false;
|
|
}
|
|
|
|
int GetLocalPlayerIndex(void)
|
|
{
|
|
C_BasePlayer * player = C_BasePlayer::GetLocalPlayer();
|
|
|
|
if (player)
|
|
return player->entindex();
|
|
else
|
|
return 0; // game not started yet
|
|
}
|
|
|
|
int GetLocalPlayerVisionFilterFlags(bool bWeaponsCheck /*= false */)
|
|
{
|
|
C_BasePlayer * player = C_BasePlayer::GetLocalPlayer();
|
|
|
|
if (player)
|
|
return player->GetVisionFilterFlags(bWeaponsCheck);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
bool IsLocalPlayerUsingVisionFilterFlags(int nFlags, bool bWeaponsCheck /* = false */)
|
|
{
|
|
int nLocalPlayerFlags = GetLocalPlayerVisionFilterFlags(bWeaponsCheck);
|
|
|
|
if (!bWeaponsCheck)
|
|
{
|
|
// We can only modify the RJ flags with normal checks that won't take the forced kill cam flags that can happen in weapon checks
|
|
int nRJShaderFlags = nLocalPlayerFlags;
|
|
if (nRJShaderFlags != 0 && GameRules() && !GameRules()->AllowMapVisionFilterShaders())
|
|
{
|
|
nRJShaderFlags = 0;
|
|
}
|
|
|
|
if (nRJShaderFlags != localplayer_visionflags.GetInt())
|
|
{
|
|
localplayer_visionflags.SetValue(nRJShaderFlags);
|
|
}
|
|
}
|
|
|
|
return (nLocalPlayerFlags & nFlags) == nFlags;
|
|
}
|
|
|
|
bool IsLocalPlayerSpectator(void)
|
|
{
|
|
C_BasePlayer * player = C_BasePlayer::GetLocalPlayer();
|
|
|
|
if (player)
|
|
return player->IsObserver();
|
|
else
|
|
return false; // game not started yet
|
|
}
|
|
|
|
int GetSpectatorMode(void)
|
|
{
|
|
C_BasePlayer * player = C_BasePlayer::GetLocalPlayer();
|
|
|
|
if (player)
|
|
return player->GetObserverMode();
|
|
else
|
|
return OBS_MODE_NONE; // game not started yet
|
|
}
|
|
|
|
int GetSpectatorTarget(void)
|
|
{
|
|
C_BasePlayer * player = C_BasePlayer::GetLocalPlayer();
|
|
|
|
if (player)
|
|
{
|
|
CBaseEntity * target = player->GetObserverTarget();
|
|
|
|
if (target)
|
|
return target->entindex();
|
|
else
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return 0; // game not started yet
|
|
}
|
|
}
|
|
|
|
int GetLocalPlayerTeam(void)
|
|
{
|
|
C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
|
|
|
|
if (pPlayer)
|
|
return pPlayer->GetTeamNumber();
|
|
else
|
|
return TEAM_UNASSIGNED;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Convert angles to -180 t 180 range
|
|
// Input : angles -
|
|
//-----------------------------------------------------------------------------
|
|
void NormalizeAngles(QAngle& angles)
|
|
{
|
|
int i;
|
|
|
|
// Normalize angles to -180 to 180 range
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (angles[i] > 180.0)
|
|
{
|
|
angles[i] -= 360.0;
|
|
}
|
|
else if (angles[i] < -180.0)
|
|
{
|
|
angles[i] += 360.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Interpolate Euler angles using quaternions to avoid singularities
|
|
// Input : start -
|
|
// end -
|
|
// output -
|
|
// frac -
|
|
//-----------------------------------------------------------------------------
|
|
void InterpolateAngles(const QAngle& start, const QAngle& end, QAngle& output, float frac)
|
|
{
|
|
Quaternion src, dest;
|
|
|
|
// Convert to quaternions
|
|
AngleQuaternion(start, src);
|
|
AngleQuaternion(end, dest);
|
|
|
|
Quaternion result;
|
|
|
|
// Slerp
|
|
QuaternionSlerp(src, dest, frac, result);
|
|
|
|
// Convert to euler
|
|
QuaternionAngles(result, output);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Simple linear interpolation
|
|
// Input : frac -
|
|
// src -
|
|
// dest -
|
|
// output -
|
|
//-----------------------------------------------------------------------------
|
|
void InterpolateVector(float frac, const Vector& src, const Vector& dest, Vector& output)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
output[i] = src[i] + frac * (dest[i] - src[i]);
|
|
}
|
|
}
|
|
|
|
client_textmessage_t *TextMessageGet(const char *pName)
|
|
{
|
|
return engine->TextMessageGet(pName);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: ScreenHeight returns the height of the screen, in pixels
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int ScreenHeight(void)
|
|
{
|
|
int w, h;
|
|
GetHudSize(w, h);
|
|
return h;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: ScreenWidth returns the width of the screen, in pixels
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int ScreenWidth(void)
|
|
{
|
|
int w, h;
|
|
GetHudSize(w, h);
|
|
return w;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Return the difference between two angles
|
|
// Input : destAngle -
|
|
// srcAngle -
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float UTIL_AngleDiff(float destAngle, float srcAngle)
|
|
{
|
|
float delta;
|
|
|
|
delta = destAngle - srcAngle;
|
|
if (destAngle > srcAngle)
|
|
{
|
|
while (delta >= 180)
|
|
delta -= 360;
|
|
}
|
|
else
|
|
{
|
|
while (delta <= -180)
|
|
delta += 360;
|
|
}
|
|
return delta;
|
|
}
|
|
|
|
|
|
float UTIL_WaterLevel(const Vector &position, float minz, float maxz)
|
|
{
|
|
Vector midUp = position;
|
|
midUp.z = minz;
|
|
|
|
if (!(UTIL_PointContents(midUp) & MASK_WATER))
|
|
return minz;
|
|
|
|
midUp.z = maxz;
|
|
if (UTIL_PointContents(midUp) & MASK_WATER)
|
|
return maxz;
|
|
|
|
float diff = maxz - minz;
|
|
while (diff > 1.0)
|
|
{
|
|
midUp.z = minz + diff / 2.0;
|
|
if (UTIL_PointContents(midUp) & MASK_WATER)
|
|
{
|
|
minz = midUp.z;
|
|
}
|
|
else
|
|
{
|
|
maxz = midUp.z;
|
|
}
|
|
diff = maxz - minz;
|
|
}
|
|
|
|
return midUp.z;
|
|
}
|
|
|
|
void UTIL_Bubbles(const Vector& mins, const Vector& maxs, int count)
|
|
{
|
|
Vector mid = (mins + maxs) * 0.5;
|
|
|
|
float flHeight = UTIL_WaterLevel(mid, mid.z, mid.z + 1024);
|
|
flHeight = flHeight - mins.z;
|
|
|
|
CPASFilter filter(mid);
|
|
|
|
int bubbles = modelinfo->GetModelIndex("sprites/bubble.vmt");
|
|
|
|
te->Bubbles(filter, 0.0,
|
|
&mins, &maxs, flHeight, bubbles, count, 8.0);
|
|
}
|
|
|
|
void UTIL_ScreenShake(const Vector ¢er, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, bool bAirShake)
|
|
{
|
|
// Nothing for now
|
|
}
|
|
|
|
char TEXTURETYPE_Find(trace_t *ptr)
|
|
{
|
|
surfacedata_t *psurfaceData = physprops->GetSurfaceData(ptr->surface.surfaceProps);
|
|
|
|
return psurfaceData->game.material;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Make a tracer effect
|
|
//-----------------------------------------------------------------------------
|
|
void UTIL_Tracer(const Vector &vecStart, const Vector &vecEnd, int iEntIndex, int iAttachment, float flVelocity, bool bWhiz, char *pCustomTracerName)
|
|
{
|
|
CEffectData data;
|
|
data.m_vStart = vecStart;
|
|
data.m_vOrigin = vecEnd;
|
|
data.m_hEntity = ClientEntityList().EntIndexToHandle(iEntIndex);
|
|
data.m_flScale = flVelocity;
|
|
|
|
// Flags
|
|
if (bWhiz)
|
|
{
|
|
data.m_fFlags |= TRACER_FLAG_WHIZ;
|
|
}
|
|
if (iAttachment != TRACER_DONT_USE_ATTACHMENT)
|
|
{
|
|
data.m_fFlags |= TRACER_FLAG_USEATTACHMENT;
|
|
// Stomp the start, since it's not going to be used anyway
|
|
data.m_vStart[0] = iAttachment;
|
|
}
|
|
|
|
// Fire it off
|
|
if (pCustomTracerName)
|
|
{
|
|
DispatchEffect(pCustomTracerName, data);
|
|
}
|
|
else
|
|
{
|
|
DispatchEffect("Tracer", data);
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : Creates both an decal and any associated impact effects (such
|
|
// as flecks) for the given iDamageType and the trace's end position
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
void UTIL_ImpactTrace(trace_t *pTrace, int iDamageType, const char *pCustomImpactName)
|
|
{
|
|
C_BaseEntity *pEntity = pTrace->m_pEnt;
|
|
|
|
// Is the entity valid, is the surface sky?
|
|
if (!pEntity || (pTrace->surface.flags & SURF_SKY))
|
|
return;
|
|
|
|
if (pTrace->fraction == 1.0)
|
|
return;
|
|
|
|
// don't decal nodraw surfaces
|
|
if (pTrace->surface.flags & SURF_NODRAW)
|
|
return;
|
|
|
|
pEntity->ImpactTrace(pTrace, iDamageType, pCustomImpactName);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int UTIL_PrecacheDecal(const char *name, bool preload)
|
|
{
|
|
return effects->Draw_DecalIndexFromName((char*)name);
|
|
}
|
|
|
|
extern short g_sModelIndexSmoke;
|
|
|
|
void UTIL_Smoke(const Vector &origin, const float scale, const float framerate)
|
|
{
|
|
CPVSFilter filter(origin);
|
|
te->Smoke(filter, 0.0f, &origin, g_sModelIndexSmoke, scale, framerate);
|
|
}
|
|
|
|
void UTIL_SetOrigin(C_BaseEntity *entity, const Vector &vecOrigin)
|
|
{
|
|
entity->SetLocalOrigin(vecOrigin);
|
|
}
|
|
|
|
//#define PRECACHE_OTHER_ONCE
|
|
// UNDONE: Do we need this to avoid doing too much of this? Measure startup times and see
|
|
#if PRECACHE_OTHER_ONCE
|
|
|
|
#include "utlsymbol.h"
|
|
class CPrecacheOtherList : public CAutoServerSystem
|
|
{
|
|
public:
|
|
virtual void LevelInitPreEntity();
|
|
virtual void LevelShutdownPostEntity();
|
|
|
|
bool AddOrMarkPrecached(const char *pClassname);
|
|
|
|
private:
|
|
CUtlSymbolTable m_list;
|
|
};
|
|
|
|
void CPrecacheOtherList::LevelInitPreEntity()
|
|
{
|
|
m_list.RemoveAll();
|
|
}
|
|
|
|
void CPrecacheOtherList::LevelShutdownPostEntity()
|
|
{
|
|
m_list.RemoveAll();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: mark or add
|
|
// Input : *pEntity -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CPrecacheOtherList::AddOrMarkPrecached(const char *pClassname)
|
|
{
|
|
CUtlSymbol sym = m_list.Find(pClassname);
|
|
if (sym.IsValid())
|
|
return false;
|
|
|
|
m_list.AddString(pClassname);
|
|
return true;
|
|
}
|
|
|
|
CPrecacheOtherList g_PrecacheOtherList;
|
|
#endif
|
|
|
|
void UTIL_PrecacheOther(const char *szClassname)
|
|
{
|
|
#if PRECACHE_OTHER_ONCE
|
|
// already done this one?, if not, mark as done
|
|
if (!g_PrecacheOtherList.AddOrMarkPrecached(szClassname))
|
|
return;
|
|
#endif
|
|
|
|
// Client should only do this once entities are coming down from server!!!
|
|
// Assert( engine->IsConnected() );
|
|
|
|
C_BaseEntity *pEntity = CreateEntityByName(szClassname);
|
|
if (!pEntity)
|
|
{
|
|
Warning("NULL Ent in UTIL_PrecacheOther\n");
|
|
return;
|
|
}
|
|
|
|
if (pEntity)
|
|
{
|
|
pEntity->Precache();
|
|
}
|
|
|
|
// Bye bye
|
|
pEntity->Release();
|
|
}
|
|
|
|
static csurface_t g_NullSurface = { "**empty**", 0 };
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void UTIL_SetTrace(trace_t& trace, const Ray_t& ray, C_BaseEntity *ent, float fraction, int hitgroup, unsigned int contents, const Vector& normal, float intercept)
|
|
{
|
|
trace.startsolid = (fraction == 0.0f);
|
|
trace.fraction = fraction;
|
|
VectorCopy(ray.m_Start, trace.startpos);
|
|
VectorMA(ray.m_Start, fraction, ray.m_Delta, trace.endpos);
|
|
VectorCopy(normal, trace.plane.normal);
|
|
trace.plane.dist = intercept;
|
|
trace.m_pEnt = C_BaseEntity::Instance(ent);
|
|
trace.hitgroup = hitgroup;
|
|
trace.surface = g_NullSurface;
|
|
trace.contents = contents;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the x & y positions of a world position in screenspace
|
|
// Returns true if it's onscreen
|
|
//-----------------------------------------------------------------------------
|
|
bool GetVectorInScreenSpace(Vector pos, int& iX, int& iY, Vector *vecOffset)
|
|
{
|
|
Vector screen;
|
|
|
|
// Apply the offset, if one was specified
|
|
if (vecOffset != NULL)
|
|
pos += *vecOffset;
|
|
|
|
// Transform to screen space
|
|
int iFacing = ScreenTransform(pos, screen);
|
|
iX = 0.5f * (1.0f + screen[0]) * ScreenWidth();
|
|
iY = 0.5f * (1.0f - screen[1]) * ScreenHeight();
|
|
|
|
// Make sure the player's facing it
|
|
if (iFacing)
|
|
{
|
|
// We're actually facing away from the Target. Stomp the screen position.
|
|
iX = -640;
|
|
iY = -640;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the x & y positions of a world position in HUD space
|
|
// Returns true if it's onscreen
|
|
//-----------------------------------------------------------------------------
|
|
bool GetVectorInHudSpace(Vector pos, int& iX, int& iY, Vector *vecOffset)
|
|
{
|
|
Vector screen;
|
|
|
|
// Apply the offset, if one was specified
|
|
if (vecOffset != NULL)
|
|
pos += *vecOffset;
|
|
|
|
// Transform to HUD space
|
|
int iFacing = HudTransform(pos, screen);
|
|
iX = 0.5f * (1.0f + screen[0]) * ScreenWidth();
|
|
iY = 0.5f * (1.0f - screen[1]) * ScreenHeight();
|
|
|
|
// Make sure the player's facing it
|
|
if (iFacing)
|
|
{
|
|
// We're actually facing away from the Target. Stomp the screen position.
|
|
iX = -640;
|
|
iY = -640;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the x & y positions of an entity in screenspace
|
|
// Returns true if it's onscreen
|
|
//-----------------------------------------------------------------------------
|
|
bool GetTargetInScreenSpace(C_BaseEntity *pTargetEntity, int& iX, int& iY, Vector *vecOffset)
|
|
{
|
|
return GetVectorInScreenSpace(pTargetEntity->WorldSpaceCenter(), iX, iY, vecOffset);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the x & y positions of an entity in Vgui space
|
|
// Returns true if it's onscreen
|
|
//-----------------------------------------------------------------------------
|
|
bool GetTargetInHudSpace(C_BaseEntity *pTargetEntity, int& iX, int& iY, Vector *vecOffset)
|
|
{
|
|
return GetVectorInHudSpace(pTargetEntity->WorldSpaceCenter(), iX, iY, vecOffset);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *player -
|
|
// msg_dest -
|
|
// *msg_name -
|
|
// *param1 -
|
|
// *param2 -
|
|
// *param3 -
|
|
// *param4 -
|
|
//-----------------------------------------------------------------------------
|
|
void ClientPrint(C_BasePlayer *player, int msg_dest, const char *msg_name, const char *param1 /*= NULL*/, const char *param2 /*= NULL*/, const char *param3 /*= NULL*/, const char *param4 /*= NULL*/)
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// class CFlaggedEntitiesEnum
|
|
//-----------------------------------------------------------------------------
|
|
// enumerate entities that match a set of edict flags into a static array
|
|
class CFlaggedEntitiesEnum : public IPartitionEnumerator
|
|
{
|
|
public:
|
|
CFlaggedEntitiesEnum(C_BaseEntity **pList, int listMax, int flagMask);
|
|
// This gets called by the enumeration methods with each element
|
|
// that passes the test.
|
|
virtual IterationRetval_t EnumElement(IHandleEntity *pHandleEntity);
|
|
|
|
int GetCount() { return m_count; }
|
|
bool AddToList(C_BaseEntity *pEntity);
|
|
|
|
private:
|
|
C_BaseEntity **m_pList;
|
|
int m_listMax;
|
|
int m_flagMask;
|
|
int m_count;
|
|
};
|
|
|
|
CFlaggedEntitiesEnum::CFlaggedEntitiesEnum(C_BaseEntity **pList, int listMax, int flagMask)
|
|
{
|
|
m_pList = pList;
|
|
m_listMax = listMax;
|
|
m_flagMask = flagMask;
|
|
m_count = 0;
|
|
}
|
|
|
|
bool CFlaggedEntitiesEnum::AddToList(C_BaseEntity *pEntity)
|
|
{
|
|
if (m_count >= m_listMax)
|
|
return false;
|
|
m_pList[m_count] = pEntity;
|
|
m_count++;
|
|
return true;
|
|
}
|
|
|
|
IterationRetval_t CFlaggedEntitiesEnum::EnumElement(IHandleEntity *pHandleEntity)
|
|
{
|
|
IClientEntity *pClientEntity = cl_entitylist->GetClientEntityFromHandle(pHandleEntity->GetRefEHandle());
|
|
C_BaseEntity *pEntity = pClientEntity ? pClientEntity->GetBaseEntity() : NULL;
|
|
if (pEntity)
|
|
{
|
|
if (m_flagMask && !(pEntity->GetFlags() & m_flagMask)) // Does it meet the criteria?
|
|
return ITERATION_CONTINUE;
|
|
|
|
if (!AddToList(pEntity))
|
|
return ITERATION_STOP;
|
|
}
|
|
|
|
return ITERATION_CONTINUE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Pass in an array of pointers and an array size, it fills the array and returns the number inserted
|
|
// Input : **pList -
|
|
// listMax -
|
|
// &mins -
|
|
// &maxs -
|
|
// flagMask -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int UTIL_EntitiesInBox(C_BaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask, int partitionMask)
|
|
{
|
|
CFlaggedEntitiesEnum boxEnum(pList, listMax, flagMask);
|
|
partition->EnumerateElementsInBox(partitionMask, mins, maxs, false, &boxEnum);
|
|
|
|
return boxEnum.GetCount();
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Pass in an array of pointers and an array size, it fills the array and returns the number inserted
|
|
// Input : **pList -
|
|
// listMax -
|
|
// ¢er -
|
|
// radius -
|
|
// flagMask -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int UTIL_EntitiesInSphere(C_BaseEntity **pList, int listMax, const Vector ¢er, float radius, int flagMask, int partitionMask)
|
|
{
|
|
CFlaggedEntitiesEnum sphereEnum(pList, listMax, flagMask);
|
|
partition->EnumerateElementsInSphere(partitionMask, center, radius, false, &sphereEnum);
|
|
|
|
return sphereEnum.GetCount();
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Pass in an array of pointers and an array size, it fills the array and returns the number inserted
|
|
// Input : **pList -
|
|
// listMax -
|
|
// &ray -
|
|
// flagMask -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int UTIL_EntitiesAlongRay(C_BaseEntity **pList, int listMax, const Ray_t &ray, int flagMask, int partitionMask)
|
|
{
|
|
CFlaggedEntitiesEnum rayEnum(pList, listMax, flagMask);
|
|
partition->EnumerateElementsAlongRay(partitionMask, ray, false, &rayEnum);
|
|
|
|
return rayEnum.GetCount();
|
|
}
|
|
|
|
CEntitySphereQuery::CEntitySphereQuery(const Vector ¢er, float radius, int flagMask, int partitionMask)
|
|
{
|
|
m_listIndex = 0;
|
|
m_listCount = UTIL_EntitiesInSphere(m_pList, ARRAYSIZE(m_pList), center, radius, flagMask, partitionMask);
|
|
}
|
|
|
|
CBaseEntity *CEntitySphereQuery::GetCurrentEntity()
|
|
{
|
|
if (m_listIndex < m_listCount)
|
|
return m_pList[m_listIndex];
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Slightly modified strtok. Does not modify the input string. Does
|
|
// not skip over more than one separator at a time. This allows parsing
|
|
// strings where tokens between separators may or may not be present:
|
|
//
|
|
// Door01,,,0 would be parsed as "Door01" "" "" "0"
|
|
// Door01,Open,,0 would be parsed as "Door01" "Open" "" "0"
|
|
//
|
|
// Input : token - Returns with a token, or zero length if the token was missing.
|
|
// str - String to parse.
|
|
// sep - Character to use as separator. UNDONE: allow multiple separator chars
|
|
// Output : Returns a pointer to the next token to be parsed.
|
|
//-----------------------------------------------------------------------------
|
|
const char *nexttoken(char *token, const char *str, char sep)
|
|
{
|
|
if ((str == NULL) || (*str == '\0'))
|
|
{
|
|
*token = '\0';
|
|
return(NULL);
|
|
}
|
|
|
|
//
|
|
// Copy everything up to the first separator into the return buffer.
|
|
// Do not include separators in the return buffer.
|
|
//
|
|
while ((*str != sep) && (*str != '\0'))
|
|
{
|
|
*token++ = *str++;
|
|
}
|
|
*token = '\0';
|
|
|
|
//
|
|
// Advance the pointer unless we hit the end of the input string.
|
|
//
|
|
if (*str == '\0')
|
|
{
|
|
return(str);
|
|
}
|
|
|
|
return(++str);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : font -
|
|
// *str -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int UTIL_ComputeStringWidth(vgui::HFont& font, const char *str)
|
|
{
|
|
float pixels = 0;
|
|
const char *p = str;
|
|
const char *pAfter = p + 1;
|
|
const char *pBefore = "\0";
|
|
while (*p)
|
|
{
|
|
#if USE_GETKERNEDCHARWIDTH
|
|
float wide, abcA;
|
|
vgui::surface()->GetKernedCharWidth(font, *p, *pBefore, *pAfter, wide, abcA);
|
|
pixels += wide;
|
|
#else
|
|
pixels += vgui::surface()->GetCharacterWidth(font, *p);
|
|
#endif
|
|
pBefore = p;
|
|
p++;
|
|
if (*p)
|
|
pAfter = p + 1;
|
|
else
|
|
pAfter = "\0";
|
|
}
|
|
return (int)ceil(pixels);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : font -
|
|
// *str -
|
|
// Output : int
|
|
//-----------------------------------------------------------------------------
|
|
int UTIL_ComputeStringWidth(vgui::HFont& font, const wchar_t *str)
|
|
{
|
|
float pixels = 0;
|
|
const wchar_t *p = str;
|
|
const wchar_t *pAfter = p + 1;
|
|
const wchar_t *pBefore = L"\0";
|
|
while (*p)
|
|
{
|
|
#if USE_GETKERNEDCHARWIDTH
|
|
float wide, abcA;
|
|
vgui::surface()->GetKernedCharWidth(font, *p, *pBefore, *pAfter, wide, abcA);
|
|
pixels += wide;
|
|
#else
|
|
pixels += vgui::surface()->GetCharacterWidth(font, *p);
|
|
#endif
|
|
pBefore = p;
|
|
p++;
|
|
if (*p)
|
|
pAfter = p + 1;
|
|
else
|
|
pAfter = L"\0";
|
|
}
|
|
return (int)ceil(pixels);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Scans player names
|
|
//Passes the player name to be checked in a KeyValues pointer
|
|
//with the keyname "name"
|
|
// - replaces '&' with '&&' so they will draw in the scoreboard
|
|
// - replaces '#' at the start of the name with '*'
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void UTIL_MakeSafeName(const char *oldName, char *newName, int newNameBufSize)
|
|
{
|
|
Assert(newNameBufSize >= sizeof(newName[0]));
|
|
|
|
int newpos = 0;
|
|
|
|
for (const char *p = oldName; *p != 0 && newpos < newNameBufSize - 1; p++)
|
|
{
|
|
//check for a '#' char at the beginning
|
|
if (p == oldName && *p == '#')
|
|
{
|
|
newName[newpos] = '*';
|
|
newpos++;
|
|
}
|
|
else if (*p == '%')
|
|
{
|
|
// remove % chars
|
|
newName[newpos] = '*';
|
|
newpos++;
|
|
}
|
|
else if (*p == '&')
|
|
{
|
|
//insert another & after this one
|
|
if (newpos + 2 < newNameBufSize)
|
|
{
|
|
newName[newpos] = '&';
|
|
newName[newpos + 1] = '&';
|
|
newpos += 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newName[newpos] = *p;
|
|
newpos++;
|
|
}
|
|
}
|
|
newName[newpos] = 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Scans player names and replaces characters that vgui won't
|
|
// display properly
|
|
// Input : *oldName - player name to be fixed up
|
|
// Output : *char - static buffer with the safe name
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const char * UTIL_SafeName(const char *oldName)
|
|
{
|
|
static char safeName[MAX_PLAYER_NAME_LENGTH * 2 + 1];
|
|
UTIL_MakeSafeName(oldName, safeName, sizeof(safeName));
|
|
|
|
return safeName;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Looks up key bindings for commands and replaces them in string.
|
|
// %<commandname>% will get replaced with its bound control, e.g. %attack2%
|
|
// Input buffer sizes are in bytes rather than unicode character count
|
|
// for consistency with other APIs. If inbufsizebytes is 0 a NULL-terminated
|
|
// input buffer is assumed, or you can pass the size of the input buffer if
|
|
// not NULL-terminated.
|
|
//-----------------------------------------------------------------------------
|
|
void UTIL_ReplaceKeyBindings(const wchar_t *inbuf, int inbufsizebytes, OUT_Z_BYTECAP(outbufsizebytes) wchar_t *outbuf, int outbufsizebytes)
|
|
{
|
|
Assert(outbufsizebytes >= sizeof(outbuf[0]));
|
|
// copy to a new buf if there are vars
|
|
outbuf[0] = 0;
|
|
|
|
if (!inbuf || !inbuf[0])
|
|
return;
|
|
|
|
int pos = 0;
|
|
const wchar_t *inbufend = NULL;
|
|
if (inbufsizebytes > 0)
|
|
{
|
|
inbufend = inbuf + (inbufsizebytes / 2);
|
|
}
|
|
|
|
while (inbuf != inbufend && *inbuf != 0)
|
|
{
|
|
// check for variables
|
|
if (*inbuf == '%')
|
|
{
|
|
++inbuf;
|
|
|
|
const wchar_t *end = wcschr(inbuf, '%');
|
|
if (end && (end != inbuf)) // make sure we handle %% in the string, which should be treated in the output as %
|
|
{
|
|
wchar_t token[64];
|
|
wcsncpy(token, inbuf, end - inbuf);
|
|
token[end - inbuf] = 0;
|
|
|
|
inbuf += end - inbuf;
|
|
|
|
// lookup key names
|
|
char binding[64];
|
|
g_pVGuiLocalize->ConvertUnicodeToANSI(token, binding, sizeof(binding));
|
|
|
|
const char *key = engine->Key_LookupBinding(*binding == '+' ? binding + 1 : binding);
|
|
if (!key)
|
|
{
|
|
key = IsX360() ? "" : "< not bound >";
|
|
}
|
|
|
|
//!! change some key names into better names
|
|
char friendlyName[64];
|
|
bool bAddBrackets = false;
|
|
if (IsX360())
|
|
{
|
|
if (!key || !key[0])
|
|
{
|
|
Q_snprintf(friendlyName, sizeof(friendlyName), "#GameUI_None");
|
|
bAddBrackets = true;
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf(friendlyName, sizeof(friendlyName), "#GameUI_KeyNames_%s", key);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf(friendlyName, sizeof(friendlyName), "%s", key);
|
|
}
|
|
Q_strupr(friendlyName);
|
|
|
|
wchar_t *locName = g_pVGuiLocalize->Find(friendlyName);
|
|
if (!locName || wcslen(locName) <= 0)
|
|
{
|
|
g_pVGuiLocalize->ConvertANSIToUnicode(friendlyName, token, sizeof(token));
|
|
|
|
outbuf[pos] = '\0';
|
|
wcscat(outbuf, token);
|
|
pos += wcslen(token);
|
|
}
|
|
else
|
|
{
|
|
outbuf[pos] = '\0';
|
|
if (bAddBrackets)
|
|
{
|
|
wcscat(outbuf, L"[");
|
|
pos += 1;
|
|
}
|
|
wcscat(outbuf, locName);
|
|
pos += wcslen(locName);
|
|
if (bAddBrackets)
|
|
{
|
|
wcscat(outbuf, L"]");
|
|
pos += 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outbuf[pos] = *inbuf;
|
|
++pos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outbuf[pos] = *inbuf;
|
|
++pos;
|
|
}
|
|
|
|
++inbuf;
|
|
}
|
|
|
|
outbuf[pos] = '\0';
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *filename -
|
|
// *pLength -
|
|
// Output : byte
|
|
//-----------------------------------------------------------------------------
|
|
byte *UTIL_LoadFileForMe(const char *filename, int *pLength)
|
|
{
|
|
byte *buffer;
|
|
|
|
FileHandle_t file;
|
|
file = filesystem->Open(filename, "rb", "GAME");
|
|
if (FILESYSTEM_INVALID_HANDLE == file)
|
|
{
|
|
if (pLength) *pLength = 0;
|
|
return NULL;
|
|
}
|
|
|
|
int size = filesystem->Size(file);
|
|
buffer = new byte[size + 1];
|
|
if (!buffer)
|
|
{
|
|
Warning("UTIL_LoadFileForMe: Couldn't allocate buffer of size %i for file %s\n", size + 1, filename);
|
|
filesystem->Close(file);
|
|
return NULL;
|
|
}
|
|
filesystem->Read(buffer, size, file);
|
|
filesystem->Close(file);
|
|
|
|
// Ensure null terminator
|
|
buffer[size] = 0;
|
|
|
|
if (pLength)
|
|
{
|
|
*pLength = size;
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *buffer -
|
|
//-----------------------------------------------------------------------------
|
|
void UTIL_FreeFile(byte *buffer)
|
|
{
|
|
delete[] buffer;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Compute distance fade
|
|
//-----------------------------------------------------------------------------
|
|
static unsigned char ComputeDistanceFade(C_BaseEntity *pEntity, float flMinDist, float flMaxDist)
|
|
{
|
|
if ((flMinDist <= 0) && (flMaxDist <= 0))
|
|
return 255;
|
|
|
|
if (flMinDist > flMaxDist)
|
|
{
|
|
::V_swap(flMinDist, flMaxDist);
|
|
}
|
|
|
|
// If a negative value is provided for the min fade distance, then base it off the max.
|
|
if (flMinDist < 0)
|
|
{
|
|
flMinDist = flMaxDist - 400;
|
|
if (flMinDist < 0)
|
|
{
|
|
flMinDist = 0;
|
|
}
|
|
}
|
|
|
|
flMinDist *= flMinDist;
|
|
flMaxDist *= flMaxDist;
|
|
|
|
float flCurrentDistanceSq = CurrentViewOrigin().DistToSqr(pEntity->WorldSpaceCenter());
|
|
C_BasePlayer *pLocal = C_BasePlayer::GetLocalPlayer();
|
|
if (pLocal)
|
|
{
|
|
float flDistFactor = pLocal->GetFOVDistanceAdjustFactor();
|
|
flCurrentDistanceSq *= flDistFactor * flDistFactor;
|
|
}
|
|
|
|
// If I'm inside the minimum range than don't resort to alpha trickery
|
|
if (flCurrentDistanceSq <= flMinDist)
|
|
return 255;
|
|
|
|
if (flCurrentDistanceSq >= flMaxDist)
|
|
return 0;
|
|
|
|
// NOTE: Because of the if-checks above, flMinDist != flMinDist here
|
|
float flFalloffFactor = 255.0f / (flMaxDist - flMinDist);
|
|
int nAlpha = flFalloffFactor * (flMaxDist - flCurrentDistanceSq);
|
|
return clamp(nAlpha, 0, 255);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Compute fade amount
|
|
//-----------------------------------------------------------------------------
|
|
unsigned char UTIL_ComputeEntityFade(C_BaseEntity *pEntity, float flMinDist, float flMaxDist, float flFadeScale)
|
|
{
|
|
unsigned char nAlpha = 255;
|
|
|
|
// If we're taking devshots, don't fade props at all
|
|
if (g_MakingDevShots || cl_leveloverview.GetFloat() > 0)
|
|
return 255;
|
|
|
|
#ifdef _DEBUG
|
|
if (r_FadeProps.GetBool())
|
|
#endif
|
|
{
|
|
nAlpha = ComputeDistanceFade(pEntity, flMinDist, flMaxDist);
|
|
|
|
// NOTE: This computation for the center + radius is invalid!
|
|
// The center of the sphere is at the center of the OBB, which is not necessarily
|
|
// at the render origin. But it should be close enough.
|
|
Vector vecMins, vecMaxs;
|
|
pEntity->GetRenderBounds(vecMins, vecMaxs);
|
|
float flRadius = vecMins.DistTo(vecMaxs) * 0.5f;
|
|
|
|
Vector vecAbsCenter;
|
|
if (modelinfo->GetModelType(pEntity->GetModel()) == mod_brush)
|
|
{
|
|
Vector vecRenderMins, vecRenderMaxs;
|
|
pEntity->GetRenderBoundsWorldspace(vecRenderMins, vecRenderMaxs);
|
|
VectorAdd(vecRenderMins, vecRenderMaxs, vecAbsCenter);
|
|
vecAbsCenter *= 0.5f;
|
|
}
|
|
else
|
|
{
|
|
vecAbsCenter = pEntity->GetRenderOrigin();
|
|
}
|
|
|
|
unsigned char nGlobalAlpha = IsXbox() ? 255 : modelinfo->ComputeLevelScreenFade(vecAbsCenter, flRadius, flFadeScale);
|
|
unsigned char nDistAlpha;
|
|
|
|
if (!engine->IsLevelMainMenuBackground())
|
|
{
|
|
nDistAlpha = modelinfo->ComputeViewScreenFade(vecAbsCenter, flRadius, flFadeScale);
|
|
}
|
|
else
|
|
{
|
|
nDistAlpha = 255;
|
|
}
|
|
|
|
if (nDistAlpha < nGlobalAlpha)
|
|
{
|
|
nGlobalAlpha = nDistAlpha;
|
|
}
|
|
|
|
if (nGlobalAlpha < nAlpha)
|
|
{
|
|
nAlpha = nGlobalAlpha;
|
|
}
|
|
}
|
|
|
|
return nAlpha;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Given a vector, clamps the scalar axes to MAX_COORD_FLOAT ranges from worldsize.h
|
|
// Input : *pVecPos -
|
|
//-----------------------------------------------------------------------------
|
|
void UTIL_BoundToWorldSize(Vector *pVecPos)
|
|
{
|
|
Assert(pVecPos);
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
(*pVecPos)[i] = clamp((*pVecPos)[i], MIN_COORD_FLOAT, MAX_COORD_FLOAT);
|
|
}
|
|
}
|
|
|
|
#ifdef _X360
|
|
#define MAP_KEY_FILE_DIR "cfg"
|
|
#else
|
|
#define MAP_KEY_FILE_DIR "media"
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the filename to count map loads in
|
|
//-----------------------------------------------------------------------------
|
|
bool UTIL_GetMapLoadCountFileName(const char *pszFilePrependName, char *pszBuffer, int iBuflen)
|
|
{
|
|
if (IsX360())
|
|
{
|
|
#ifdef _X360
|
|
if (XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED)
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
if (IsX360())
|
|
{
|
|
Q_snprintf(pszBuffer, iBuflen, "%s:/%s", MAP_KEY_FILE_DIR, pszFilePrependName);
|
|
}
|
|
else
|
|
{
|
|
Q_snprintf(pszBuffer, iBuflen, "%s/%s", MAP_KEY_FILE_DIR, pszFilePrependName);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef TF_CLIENT_DLL
|
|
#define MAP_KEY_FILE "viewed.res"
|
|
#else
|
|
#define MAP_KEY_FILE "mapkeys.res"
|
|
#endif
|
|
|
|
void UTIL_IncrementMapKey(const char *pszCustomKey)
|
|
{
|
|
if (!pszCustomKey)
|
|
return;
|
|
|
|
char szFilename[_MAX_PATH];
|
|
if (!UTIL_GetMapLoadCountFileName(MAP_KEY_FILE, szFilename, _MAX_PATH))
|
|
return;
|
|
|
|
int iCount = 1;
|
|
|
|
KeyValues *kvMapLoadFile = new KeyValues(MAP_KEY_FILE);
|
|
if (kvMapLoadFile)
|
|
{
|
|
kvMapLoadFile->LoadFromFile(g_pFullFileSystem, szFilename, "MOD");
|
|
|
|
char mapname[MAX_MAP_NAME];
|
|
Q_FileBase(engine->GetLevelName(), mapname, sizeof(mapname));
|
|
Q_strlower(mapname);
|
|
|
|
// Increment existing, or add a new one
|
|
KeyValues *pMapKey = kvMapLoadFile->FindKey(mapname);
|
|
if (pMapKey)
|
|
{
|
|
iCount = pMapKey->GetInt(pszCustomKey, 0) + 1;
|
|
pMapKey->SetInt(pszCustomKey, iCount);
|
|
}
|
|
else
|
|
{
|
|
KeyValues *pNewKey = new KeyValues(mapname);
|
|
if (pNewKey)
|
|
{
|
|
pNewKey->SetString(pszCustomKey, "1");
|
|
kvMapLoadFile->AddSubKey(pNewKey);
|
|
}
|
|
}
|
|
|
|
// Write it out
|
|
|
|
// force create this directory incase it doesn't exist
|
|
filesystem->CreateDirHierarchy(MAP_KEY_FILE_DIR, "MOD");
|
|
|
|
CUtlBuffer buf(0, 0, CUtlBuffer::TEXT_BUFFER);
|
|
kvMapLoadFile->RecursiveSaveToFile(buf, 0);
|
|
g_pFullFileSystem->WriteFile(szFilename, "MOD", buf);
|
|
|
|
kvMapLoadFile->deleteThis();
|
|
}
|
|
|
|
if (IsX360())
|
|
{
|
|
#ifdef _X360
|
|
xboxsystem->FinishContainerWrites();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
int UTIL_GetMapKeyCount(const char *pszCustomKey)
|
|
{
|
|
if (!pszCustomKey)
|
|
return 0;
|
|
|
|
char szFilename[_MAX_PATH];
|
|
if (!UTIL_GetMapLoadCountFileName(MAP_KEY_FILE, szFilename, _MAX_PATH))
|
|
return 0;
|
|
|
|
int iCount = 0;
|
|
|
|
KeyValues *kvMapLoadFile = new KeyValues(MAP_KEY_FILE);
|
|
if (kvMapLoadFile)
|
|
{
|
|
// create an empty file if none exists
|
|
if (!g_pFullFileSystem->FileExists(szFilename, "MOD"))
|
|
{
|
|
// force create this directory incase it doesn't exist
|
|
filesystem->CreateDirHierarchy(MAP_KEY_FILE_DIR, "MOD");
|
|
|
|
CUtlBuffer buf(0, 0, CUtlBuffer::TEXT_BUFFER);
|
|
g_pFullFileSystem->WriteFile(szFilename, "MOD", buf);
|
|
}
|
|
|
|
kvMapLoadFile->LoadFromFile(g_pFullFileSystem, szFilename, "MOD");
|
|
|
|
char mapname[MAX_MAP_NAME];
|
|
Q_FileBase(engine->GetLevelName(), mapname, sizeof(mapname));
|
|
Q_strlower(mapname);
|
|
|
|
KeyValues *pMapKey = kvMapLoadFile->FindKey(mapname);
|
|
if (pMapKey)
|
|
{
|
|
iCount = pMapKey->GetInt(pszCustomKey);
|
|
}
|
|
|
|
kvMapLoadFile->deleteThis();
|
|
}
|
|
|
|
return iCount;
|
|
}
|
|
|
|
bool UTIL_HasLoadedAnyMap()
|
|
{
|
|
char szFilename[_MAX_PATH];
|
|
if (!UTIL_GetMapLoadCountFileName(MAP_KEY_FILE, szFilename, _MAX_PATH))
|
|
return false;
|
|
|
|
return g_pFullFileSystem->FileExists(szFilename, "MOD");
|
|
}
|