/*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ /* ===== util.cpp ======================================================== Utility code. Really not optional after all. */ #include "extdll.h" #include "util.h" #include "cbase.h" #include "saverestore.h" #include #include "shake.h" #include "decals.h" #include "player.h" #include "weapons.h" #include "gamerules.h" #include "movewith.h" #include "locus.h" #include #include "render_api.h" void Msg( const char *szText, ... ) { va_list szCommand; static char value[1024]; va_start( szCommand, szText ); Q_vsnprintf( value, sizeof( value ), szText, szCommand ); va_end( szCommand ); g_engfuncs.pfnAlertMessage( at_console, value ); } float UTIL_WeaponTimeBase( void ) { return gpGlobals->time; } static unsigned int glSeed = 0; unsigned int seed_table[ 256 ] = { 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 }; unsigned int U_Random( void ) { glSeed *= 69069; glSeed += seed_table[ glSeed & 0xff ]; return ( ++glSeed & 0x0fffffff ); } void U_Srand( unsigned int seed ) { glSeed = seed_table[ seed & 0xff ]; } /* ===================== UTIL_SharedRandomLong ===================== */ int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) { unsigned int range; U_Srand( (int)seed + low + high ); range = high - low + 1; if ( !(range - 1) ) { return low; } else { int offset; int rnum; rnum = U_Random(); offset = rnum % range; return (low + offset); } } /* ===================== UTIL_SharedRandomFloat ===================== */ float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) { // unsigned int range; U_Srand( (int)seed + *(int *)&low + *(int *)&high ); U_Random(); U_Random(); range = high - low; if ( !range ) { return low; } else { int tensixrand; float offset; tensixrand = U_Random() & 65535; offset = (float)tensixrand / 65536.0; return (low + offset * range ); } } void UTIL_ParametricRocket( entvars_t *pev, Vector vecOrigin, Vector vecAngles, edict_t *owner ) { pev->startpos = vecOrigin; // Trace out line to end pos TraceResult tr; UTIL_MakeVectors( vecAngles ); UTIL_TraceLine( pev->startpos, pev->startpos + gpGlobals->v_forward * 8192, ignore_monsters, owner, &tr); pev->endpos = tr.vecEndPos; // Now compute how long it will take based on current velocity Vector vecTravel = pev->endpos - pev->startpos; float travelTime = 0.0; if ( pev->velocity.Length() > 0 ) { travelTime = vecTravel.Length() / pev->velocity.Length(); } pev->starttime = gpGlobals->time; pev->impacttime = gpGlobals->time + travelTime; } int g_groupmask = 0; int g_groupop = 0; // Normal overrides void UTIL_SetGroupTrace( int groupmask, int op ) { g_groupmask = groupmask; g_groupop = op; ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); } void UTIL_UnsetGroupTrace( void ) { g_groupmask = 0; g_groupop = 0; ENGINE_SETGROUPMASK( 0, 0 ); } // Smart version, it'll clean itself up when it pops off stack UTIL_GroupTrace::UTIL_GroupTrace( int groupmask, int op ) { m_oldgroupmask = g_groupmask; m_oldgroupop = g_groupop; g_groupmask = groupmask; g_groupop = op; ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); } UTIL_GroupTrace::~UTIL_GroupTrace( void ) { g_groupmask = m_oldgroupmask; g_groupop = m_oldgroupop; ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); } TYPEDESCRIPTION gEntvarsDescription[] = { DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), DEFINE_ENTITY_GLOBAL_FIELD( globalname, FIELD_STRING ), DEFINE_ENTITY_FIELD( origin, FIELD_POSITION_VECTOR ), DEFINE_ENTITY_FIELD( oldorigin, FIELD_POSITION_VECTOR ), DEFINE_ENTITY_FIELD( velocity, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( basevelocity, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( movedir, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( angles, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( avelocity, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( punchangle, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( v_angle, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( fixangle, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( idealpitch, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( pitch_speed, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( ideal_yaw, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( yaw_speed, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( modelindex, FIELD_INTEGER ), DEFINE_ENTITY_GLOBAL_FIELD( model, FIELD_MODELNAME ), DEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ), DEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ), DEFINE_ENTITY_FIELD( weaponanim, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( absmin, FIELD_POSITION_VECTOR ), DEFINE_ENTITY_FIELD( absmax, FIELD_POSITION_VECTOR ), DEFINE_ENTITY_GLOBAL_FIELD( mins, FIELD_VECTOR ), DEFINE_ENTITY_GLOBAL_FIELD( maxs, FIELD_VECTOR ), DEFINE_ENTITY_GLOBAL_FIELD( size, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( ltime, FIELD_TIME ), DEFINE_ENTITY_FIELD( nextthink, FIELD_TIME ), DEFINE_ENTITY_FIELD( solid, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( movetype, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( skin, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( body, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( effects, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( gravity, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( friction, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( light_level, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( frame, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( scale, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( sequence, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( animtime, FIELD_TIME ), DEFINE_ENTITY_FIELD( framerate, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( controller, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( blending, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( rendermode, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( renderamt, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( rendercolor, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( renderfx, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( health, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( frags, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( weapons, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( takedamage, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( deadflag, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( view_ofs, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( button, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( impulse, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( chain, FIELD_EDICT ), DEFINE_ENTITY_FIELD( dmg_inflictor, FIELD_EDICT ), DEFINE_ENTITY_FIELD( enemy, FIELD_EDICT ), DEFINE_ENTITY_FIELD( aiment, FIELD_EDICT ), DEFINE_ENTITY_FIELD( owner, FIELD_EDICT ), DEFINE_ENTITY_FIELD( groundentity, FIELD_EDICT ), DEFINE_ENTITY_FIELD( spawnflags, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( flags, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( colormap, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( team, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( max_health, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( teleport_time, FIELD_TIME ), DEFINE_ENTITY_FIELD( armortype, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( armorvalue, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( waterlevel, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( watertype, FIELD_INTEGER ), // Having these fields be local to the individual levels makes it easier to test those levels individually. DEFINE_ENTITY_GLOBAL_FIELD( target, FIELD_STRING ), DEFINE_ENTITY_GLOBAL_FIELD( targetname, FIELD_STRING ), DEFINE_ENTITY_FIELD( netname, FIELD_STRING ), DEFINE_ENTITY_FIELD( message, FIELD_STRING ), DEFINE_ENTITY_FIELD( dmg_take, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( dmg_save, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( dmg, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( dmgtime, FIELD_TIME ), DEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ), DEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ), DEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ), DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), DEFINE_ENTITY_FIELD( speed, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( air_finished, FIELD_TIME ), DEFINE_ENTITY_FIELD( pain_finished, FIELD_TIME ), DEFINE_ENTITY_FIELD( radsuit_finished, FIELD_TIME ), DEFINE_ENTITY_FIELD( fuser1, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( fuser2, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( fuser3, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( fuser4, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( iuser1, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( iuser2, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( iuser3, FIELD_STRING ), // because we store player->handmodel DEFINE_ENTITY_FIELD( iuser4, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( vuser1, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( vuser2, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( vuser3, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( vuser4, FIELD_VECTOR ), DEFINE_ENTITY_FIELD( maxspeed, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( startpos, FIELD_VECTOR ), }; #define ENTVARS_COUNT (sizeof(gEntvarsDescription)/sizeof(gEntvarsDescription[0])) #ifdef DEBUG edict_t *DBG_EntOfVars( const entvars_t *pev ) { if (pev->pContainingEntity != NULL) return pev->pContainingEntity; ALERT(at_debug, "entvars_t pContainingEntity is NULL, calling into engine"); edict_t* pent = (*g_engfuncs.pfnFindEntityByVars)((entvars_t*)pev); if (pent == NULL) ALERT(at_debug, "DAMN! Even the engine couldn't FindEntityByVars!"); ((entvars_t *)pev)->pContainingEntity = pent; return pent; } #endif //DEBUG #ifdef DEBUG void DBG_AssertFunction( BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage) { if (fExpr) return; char szOut[512]; if (szMessage != NULL) sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage); else sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)", szExpr, szFile, szLine); ALERT(at_debug, szOut); } #endif // DEBUG BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) { return g_pGameRules->GetNextBestWeapon( pPlayer, pCurrentWeapon ); } // ripped this out of the engine float UTIL_AngleMod(float a) { if (a < 0) { a = a + 360 * ((int)(a / 360) + 1); } else if (a >= 360) { a = a - 360 * ((int)(a / 360)); } // a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); return a; } float UTIL_AngleDiff( float destAngle, float srcAngle ) { float delta; delta = destAngle - srcAngle; if ( destAngle > srcAngle ) { if ( delta >= 180 ) delta -= 360; } else { if ( delta <= -180 ) delta += 360; } return delta; } Vector UTIL_VecToAngles( const Vector &vec ) { float rgflVecOut[3]; VEC_TO_ANGLES(vec, rgflVecOut); return Vector(rgflVecOut); } Vector UTIL_YawToVector( float yaw ) { Vector ret; ret.z = 0; float angle = DEG2RAD( yaw ); SinCos( angle, &ret.y, &ret.x ); return ret; } //LRC - pass in a normalised axis vector and a number of degrees, and this returns the corresponding // angles value for an entity. Vector UTIL_AxisRotationToAngles( const Vector &vecAxis, float flDegs ) { Vector vecTemp = UTIL_AxisRotationToVec( vecAxis, flDegs ); float rgflVecOut[3]; //ugh, mathsy. rgflVecOut[0] = asin(vecTemp.z) * (-180.0 / M_PI); rgflVecOut[1] = acos(vecTemp.x) * (180.0 / M_PI); if (vecTemp.y < 0) rgflVecOut[1] = -rgflVecOut[1]; rgflVecOut[2] = 0; //for now return Vector(rgflVecOut); } //LRC - as above, but returns the position of point 1 0 0 under the given rotation Vector UTIL_AxisRotationToVec( const Vector &vecAxis, float flDegs ) { float rgflVecOut[3]; float flRads = flDegs * (M_PI / 180.0); float c = cos(flRads); float s = sin(flRads); float v = vecAxis.x * (1-c); //ugh, more maths. Thank goodness for internet geometry sites... rgflVecOut[0] = vecAxis.x*v + c; rgflVecOut[1] = vecAxis.y*v + vecAxis.z*s; rgflVecOut[2] = vecAxis.z*v - vecAxis.y*s; return Vector(rgflVecOut); } // float UTIL_MoveToOrigin( edict_t *pent, const Vector vecGoal, float flDist, int iMoveType ) void UTIL_MoveToOrigin( edict_t *pent, const Vector &vecGoal, float flDist, int iMoveType ) { float rgfl[3]; vecGoal.CopyToArray(rgfl); // return MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); } int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) { edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); CBaseEntity *pEntity; int count; count = 0; if ( !pEdict ) return count; for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) { if ( pEdict->free ) // Not in use continue; if ( flagMask && !(pEdict->v.flags & flagMask) ) // Does it meet the criteria? continue; if ( mins.x > pEdict->v.absmax.x || mins.y > pEdict->v.absmax.y || mins.z > pEdict->v.absmax.z || maxs.x < pEdict->v.absmin.x || maxs.y < pEdict->v.absmin.y || maxs.z < pEdict->v.absmin.z ) continue; pEntity = CBaseEntity::Instance(pEdict); if ( !pEntity ) continue; pList[ count ] = pEntity; count++; if ( count >= listMax ) return count; } return count; } int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ) { edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); CBaseEntity *pEntity; int count; float distance, delta; count = 0; float radiusSquared = radius * radius; if ( !pEdict ) return count; for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) { if ( pEdict->free ) // Not in use continue; if ( !(pEdict->v.flags & (FL_CLIENT|FL_MONSTER)) ) // Not a client/monster ? continue; // Use origin for X & Y since they are centered for all monsters // Now X delta = center.x - pEdict->v.origin.x;//(pEdict->v.absmin.x + pEdict->v.absmax.x)*0.5; delta *= delta; if ( delta > radiusSquared ) continue; distance = delta; // Now Y delta = center.y - pEdict->v.origin.y;//(pEdict->v.absmin.y + pEdict->v.absmax.y)*0.5; delta *= delta; distance += delta; if ( distance > radiusSquared ) continue; // Now Z delta = center.z - (pEdict->v.absmin.z + pEdict->v.absmax.z)*0.5; delta *= delta; distance += delta; if ( distance > radiusSquared ) continue; pEntity = CBaseEntity::Instance(pEdict); if ( !pEntity ) continue; pList[ count ] = pEntity; count++; if ( count >= listMax ) return count; } return count; } CBaseEntity *UTIL_FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ) { edict_t *pentEntity; if (pStartEntity) pentEntity = pStartEntity->edict(); else pentEntity = NULL; pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); if (!FNullEnt(pentEntity)) return CBaseEntity::Instance(pentEntity); return NULL; } CBaseEntity *UTIL_FindEntityByString( CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ) { edict_t *pentEntity; CBaseEntity *pEntity; if (pStartEntity) pentEntity = pStartEntity->edict(); else pentEntity = NULL; for (;;) { // Don't change this to use UTIL_FindEntityByString! pentEntity = FIND_ENTITY_BY_STRING( pentEntity, szKeyword, szValue ); // if pentEntity (the edict) is null, we're at the end of the entities. Give up. if (FNullEnt(pentEntity)) { return NULL; } else { // ...but if only pEntity (the classptr) is null, we've just got one dud, so we try again. pEntity = CBaseEntity::Instance(pentEntity); if (pEntity) return pEntity; } } } CBaseEntity *UTIL_FindEntityByClassname( CBaseEntity *pStartEntity, const char *szName ) { return UTIL_FindEntityByString( pStartEntity, "classname", szName ); } #define MAX_ALIASNAME_LEN 80 //LRC - things get messed up if aliases change in the middle of an entity traversal. // so instead, they record what changes should be made, and wait until this function gets // called. void UTIL_FlushAliases( void ) { // ALERT(at_console, "Flushing alias list\n"); if (!g_pWorld) { ALERT(at_debug, "FlushAliases has no AliasList!\n"); return; } while (g_pWorld->m_pFirstAlias) { if (g_pWorld->m_pFirstAlias->m_iLFlags & LF_ALIASLIST) { // ALERT(at_console, "call FlushChanges for %s \"%s\"\n", STRING(g_pWorld->m_pFirstAlias->pev->classname), STRING(g_pWorld->m_pFirstAlias->pev->targetname)); g_pWorld->m_pFirstAlias->FlushChanges(); g_pWorld->m_pFirstAlias->m_iLFlags &= ~LF_ALIASLIST; } g_pWorld->m_pFirstAlias = g_pWorld->m_pFirstAlias->m_pNextAlias; } } void UTIL_AddToAliasList( CBaseAlias *pAlias ) { if (!g_pWorld) { ALERT(at_debug, "AddToAliasList has no AliasList!\n"); return; } pAlias->m_iLFlags |= LF_ALIASLIST; // ALERT(at_console, "Adding %s \"%s\" to alias list\n", STRING(pAlias->pev->classname), STRING(pAlias->pev->targetname)); if (g_pWorld->m_pFirstAlias == NULL) { g_pWorld->m_pFirstAlias = pAlias; pAlias->m_pNextAlias = NULL; } else if (g_pWorld->m_pFirstAlias == pAlias) { // already in the list return; } else { CBaseAlias *pCurrent = g_pWorld->m_pFirstAlias; while (pCurrent->m_pNextAlias != NULL) { if (pCurrent->m_pNextAlias == pAlias) { // already in the list return; } pCurrent = pCurrent->m_pNextAlias; } pCurrent->m_pNextAlias = pAlias; pAlias->m_pNextAlias = NULL; } } // for every alias which has the given name, find the earliest entity which any of them refers to // and which is later than pStartEntity. CBaseEntity *UTIL_FollowAliasReference(CBaseEntity *pStartEntity, const char* szValue) { CBaseEntity* pEntity; CBaseEntity* pBestEntity = NULL; // the entity we're currently planning to return. int iBestOffset = -1; // the offset of that entity. CBaseEntity* pTempEntity; int iTempOffset; pEntity = UTIL_FindEntityByTargetname(NULL,szValue); while ( pEntity ) { if (pEntity->IsAlias()) { pTempEntity = ((CBaseAlias*)pEntity)->FollowAlias( pStartEntity ); if ( pTempEntity ) { // We've found an entity; only use it if its offset is lower than the offset we've currently got. iTempOffset = OFFSET(pTempEntity->pev); if (iBestOffset == -1 || iTempOffset < iBestOffset) { iBestOffset = iTempOffset; pBestEntity = pTempEntity; } } } pEntity = UTIL_FindEntityByTargetname(pEntity,szValue); } return pBestEntity; } // for every info_group which has the given groupname, find the earliest entity which is referred to by its member // with the given membername and which is later than pStartEntity. CBaseEntity *UTIL_FollowGroupReference(CBaseEntity *pStartEntity, char* szGroupName, char* szMemberName) { CBaseEntity* pEntity; CBaseEntity* pBestEntity = NULL; // the entity we're currently planning to return. int iBestOffset = -1; // the offset of that entity. CBaseEntity* pTempEntity; int iTempOffset; char szBuf[MAX_ALIASNAME_LEN]; char* szThisMember = szMemberName; char* szTail = NULL; int iszMemberValue; int i; // find the first '.' in the membername and if there is one, split the string at that point. for (i = 0; szMemberName[i]; i++) { if (szMemberName[i] == '.') { // recursive member-reference // FIXME: we should probably check that i < MAX_ALIASNAME_LEN. strncpy(szBuf,szMemberName,i); szBuf[i] = 0; szTail = &(szMemberName[i+1]); szThisMember = szBuf; break; } } pEntity = UTIL_FindEntityByTargetname(NULL,szGroupName); while ( pEntity ) { if (FStrEq(STRING(pEntity->pev->classname), "info_group")) { iszMemberValue = ((CInfoGroup*)pEntity)->GetMember(szThisMember); // ALERT(at_console,"survived getMember\n"); // return NULL; if (!FStringNull(iszMemberValue)) { if (szTail) // do we have more references to follow? pTempEntity = UTIL_FollowGroupReference(pStartEntity, (char*)STRING(iszMemberValue), szTail); else pTempEntity = UTIL_FindEntityByTargetname(pStartEntity,STRING(iszMemberValue)); if ( pTempEntity ) { iTempOffset = OFFSET(pTempEntity->pev); if (iBestOffset == -1 || iTempOffset < iBestOffset) { iBestOffset = iTempOffset; pBestEntity = pTempEntity; } } } } pEntity = UTIL_FindEntityByTargetname(pEntity,szGroupName); } if (pBestEntity) { // ALERT(at_console,"\"%s\".\"%s\" returns %s\n",szGroupName,szMemberName,STRING(pBestEntity->pev->targetname)); return pBestEntity; } return NULL; } // Returns the first entity which szName refers to and which is after pStartEntity. CBaseEntity *UTIL_FollowReference( CBaseEntity *pStartEntity, const char* szName ) { char szRoot[MAX_ALIASNAME_LEN+1]; // allow room for null-terminator char* szMember; int i; CBaseEntity *pResult; if (!szName || szName[0] == 0) return NULL; // reference through an info_group? for (i = 0; szName[i]; i++) { if (szName[i] == '.') { // yes, it looks like a reference through an info_group... // FIXME: we should probably check that i < MAX_ALIASNAME_LEN. strncpy(szRoot,szName,i); szRoot[i] = 0; szMember = (char*)&szName[i+1]; //ALERT(at_console,"Following reference- group %s with member %s\n",szRoot,szMember); pResult = UTIL_FollowGroupReference(pStartEntity, szRoot, szMember); // if (pResult) // ALERT(at_console,"\"%s\".\"%s\" = %s\n",szRoot,szMember,STRING(pResult->pev->targetname)); return pResult; } } // reference through an info_alias? if ( szName[0] == '*' ) { if (FStrEq(szName, "*player")) { CBaseEntity* pPlayer = UTIL_FindEntityByClassname(NULL, "player"); if (pPlayer && (pStartEntity == NULL || pPlayer->eoffset() > pStartEntity->eoffset())) return pPlayer; else return NULL; } //ALERT(at_console,"Following alias %s\n",szName+1); pResult = UTIL_FollowAliasReference( pStartEntity, szName+1 ); // if (pResult) // ALERT(at_console,"alias \"%s\" = %s\n",szName+1,STRING(pResult->pev->targetname)); return pResult; } // not a reference // ALERT(at_console,"%s is not a reference\n",szName); return NULL; } CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName ) { CBaseEntity *pFound = UTIL_FollowReference( pStartEntity, szName ); if (pFound) return pFound; else return UTIL_FindEntityByString( pStartEntity, "targetname", szName ); } CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pActivator ) { if (FStrEq(szName, "*locus")) { if (pActivator && (pStartEntity == NULL || pActivator->eoffset() > pStartEntity->eoffset())) return pActivator; else return NULL; } else return UTIL_FindEntityByTargetname( pStartEntity, szName ); } CBaseEntity *UTIL_FindEntityByTarget( CBaseEntity *pStartEntity, const char *szName ) { return UTIL_FindEntityByString( pStartEntity, "target", szName ); } CBaseEntity *UTIL_FindEntityGeneric( const char *szWhatever, Vector &vecSrc, float flRadius ) { CBaseEntity *pEntity = NULL; pEntity = UTIL_FindEntityByTargetname( NULL, szWhatever ); if (pEntity) return pEntity; CBaseEntity *pSearch = NULL; float flMaxDist2 = flRadius * flRadius; while ((pSearch = UTIL_FindEntityByClassname( pSearch, szWhatever )) != NULL) { float flDist2 = (pSearch->pev->origin - vecSrc).Length(); flDist2 = flDist2 * flDist2; if (flMaxDist2 > flDist2) { pEntity = pSearch; flMaxDist2 = flDist2; } } return pEntity; } // returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected // otherwise returns NULL // Index is 1 based CBaseEntity *UTIL_PlayerByIndex( int playerIndex ) { CBaseEntity *pPlayer = NULL; if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients ) { edict_t *pPlayerEdict = INDEXENT( playerIndex ); if ( pPlayerEdict && !pPlayerEdict->free ) { pPlayer = CBaseEntity::Instance( pPlayerEdict ); } } return pPlayer; } void UTIL_MakeVectors( const Vector &vecAngles ) { MAKE_VECTORS( vecAngles ); } void UTIL_MakeAimVectors( const Vector &vecAngles ) { float rgflVec[3]; vecAngles.CopyToArray(rgflVec); rgflVec[0] = -rgflVec[0]; MAKE_VECTORS(rgflVec); } #define SWAP(a,b,temp) ((temp)=(a),(a)=(b),(b)=(temp)) void UTIL_MakeInvVectors( const Vector &vec, globalvars_t *pgv ) { MAKE_VECTORS(vec); float tmp; pgv->v_right = pgv->v_right * -1; SWAP(pgv->v_forward.y, pgv->v_right.x, tmp); SWAP(pgv->v_forward.z, pgv->v_up.x, tmp); SWAP(pgv->v_right.z, pgv->v_up.y, tmp); } void UTIL_EmitAmbientSound( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ) { float rgfl[3]; vecOrigin.CopyToArray(rgfl); if (samp && *samp == '!') { char name[32]; if (SENTENCEG_Lookup(samp, name) >= 0) EMIT_AMBIENT_SOUND(entity, rgfl, name, vol, attenuation, fFlags, pitch); // buz: send sencences as text messages to lookup subtitles in titles.txt UTIL_ShowMessagePVS( samp, vecOrigin ); } else EMIT_AMBIENT_SOUND(entity, rgfl, samp, vol, attenuation, fFlags, pitch); } static unsigned short FixedUnsigned16( float value, float scale ) { int output; output = value * scale; if ( output < 0 ) output = 0; if ( output > 0xFFFF ) output = 0xFFFF; return (unsigned short)output; } static short FixedSigned16( float value, float scale ) { int output; output = value * scale; if ( output > 32767 ) output = 32767; if ( output < -32768 ) output = -32768; return (short)output; } // Shake the screen of all clients within radius // radius == 0, shake all clients // UNDONE: Allow caller to shake clients not ONGROUND? // UNDONE: Fix falloff model (disabled)? // UNDONE: Affect user controls? //LRC UNDONE: Work during trigger_camera? void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius ) { int i; float localAmplitude; ScreenShake shake; shake.duration = FixedUnsigned16( duration, 1<<12 ); // 4.12 fixed shake.frequency = FixedUnsigned16( frequency, 1<<8 ); // 8.8 fixed for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); if ( !pPlayer || !(pPlayer->pev->flags & FL_ONGROUND) ) // Don't shake if not onground continue; localAmplitude = 0; if ( radius <= 0 ) localAmplitude = amplitude; else { Vector delta = center - pPlayer->pev->origin; float distance = delta.Length(); // Had to get rid of this falloff - it didn't work well if ( distance < radius ) localAmplitude = amplitude;//radius - distance; } if ( localAmplitude ) { shake.amplitude = FixedUnsigned16( localAmplitude, 1<<12 ); // 4.12 fixed MESSAGE_BEGIN( MSG_ONE, gmsgShake, NULL, pPlayer->edict() ); // use the magic #1 for "one client" WRITE_SHORT( shake.amplitude ); // shake amount WRITE_SHORT( shake.duration ); // shake lasts this long WRITE_SHORT( shake.frequency ); // shake noise frequency MESSAGE_END(); } } } void UTIL_ScreenShakeAll( const Vector ¢er, float amplitude, float frequency, float duration ) { UTIL_ScreenShake( center, amplitude, frequency, duration, 0 ); } void UTIL_ScreenFadeBuild( ScreenFade &fade, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) { fade.duration = FixedUnsigned16( fadeTime, 1<<12 ); // 4.12 fixed fade.holdTime = FixedUnsigned16( fadeHold, 1<<12 ); // 4.12 fixed fade.r = (int)color.x; fade.g = (int)color.y; fade.b = (int)color.z; fade.a = alpha; fade.fadeFlags = flags; } void UTIL_ScreenFadeWrite( const ScreenFade &fade, CBaseEntity *pEntity ) { if ( !pEntity || !pEntity->IsNetClient() ) return; MESSAGE_BEGIN( MSG_ONE, gmsgFade, NULL, pEntity->edict() ); // use the magic #1 for "one client" WRITE_SHORT( fade.duration ); // fade lasts this long WRITE_SHORT( fade.holdTime ); // fade lasts this long WRITE_SHORT( fade.fadeFlags ); // fade type (in / out) WRITE_BYTE( fade.r ); // fade red WRITE_BYTE( fade.g ); // fade green WRITE_BYTE( fade.b ); // fade blue WRITE_BYTE( fade.a ); // fade blue MESSAGE_END(); } void UTIL_ScreenFadeAll( const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) { int i; ScreenFade fade; UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); UTIL_ScreenFadeWrite( fade, pPlayer ); } } void UTIL_ScreenFade( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) { ScreenFade fade; UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); UTIL_ScreenFadeWrite( fade, pEntity ); } void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ) { if ( !pEntity || !pEntity->IsNetClient() ) return; MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, NULL, pEntity->edict() ); WRITE_BYTE( TE_TEXTMESSAGE ); WRITE_BYTE( textparms.channel & 0xFF ); WRITE_SHORT( FixedSigned16( textparms.x, 1<<13 ) ); WRITE_SHORT( FixedSigned16( textparms.y, 1<<13 ) ); WRITE_BYTE( textparms.effect ); WRITE_BYTE( textparms.r1 ); WRITE_BYTE( textparms.g1 ); WRITE_BYTE( textparms.b1 ); WRITE_BYTE( textparms.a1 ); WRITE_BYTE( textparms.r2 ); WRITE_BYTE( textparms.g2 ); WRITE_BYTE( textparms.b2 ); WRITE_BYTE( textparms.a2 ); WRITE_SHORT( FixedUnsigned16( textparms.fadeinTime, 1<<8 ) ); WRITE_SHORT( FixedUnsigned16( textparms.fadeoutTime, 1<<8 ) ); WRITE_SHORT( FixedUnsigned16( textparms.holdTime, 1<<8 ) ); if ( textparms.effect == 2 ) WRITE_SHORT( FixedUnsigned16( textparms.fxTime, 1<<8 ) ); if ( strlen( pMessage ) < 512 ) { WRITE_STRING( pMessage ); } else { char tmp[512]; strncpy( tmp, pMessage, 511 ); tmp[511] = 0; WRITE_STRING( tmp ); } MESSAGE_END(); } void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) { int i; for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer ) UTIL_HudMessage( pPlayer, textparms, pMessage ); } } extern int gmsgTextMsg, gmsgSayText; void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) { MESSAGE_BEGIN( MSG_ALL, gmsgTextMsg ); WRITE_BYTE( msg_dest ); WRITE_STRING( msg_name ); if ( param1 ) WRITE_STRING( param1 ); if ( param2 ) WRITE_STRING( param2 ); if ( param3 ) WRITE_STRING( param3 ); if ( param4 ) WRITE_STRING( param4 ); MESSAGE_END(); } void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) { MESSAGE_BEGIN( MSG_ONE, gmsgTextMsg, NULL, client ); WRITE_BYTE( msg_dest ); WRITE_STRING( msg_name ); if ( param1 ) WRITE_STRING( param1 ); if ( param2 ) WRITE_STRING( param2 ); if ( param3 ) WRITE_STRING( param3 ); if ( param4 ) WRITE_STRING( param4 ); MESSAGE_END(); } void UTIL_SayText( const char *pText, CBaseEntity *pEntity ) { if ( !pEntity->IsNetClient() ) return; MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, pEntity->edict() ); WRITE_BYTE( pEntity->entindex() ); WRITE_STRING( pText ); MESSAGE_END(); } void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ) { MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); WRITE_BYTE( pEntity->entindex() ); WRITE_STRING( pText ); MESSAGE_END(); } char *UTIL_dtos1( int d ) { static char buf[8]; sprintf( buf, "%d", d ); return buf; } char *UTIL_dtos2( int d ) { static char buf[8]; sprintf( buf, "%d", d ); return buf; } char *UTIL_dtos3( int d ) { static char buf[8]; sprintf( buf, "%d", d ); return buf; } char *UTIL_dtos4( int d ) { static char buf[8]; sprintf( buf, "%d", d ); return buf; } void UTIL_ShowMessage( const char *pString, CBaseEntity *pEntity ) { if ( !pEntity || !pEntity->IsNetClient() ) return; MESSAGE_BEGIN( MSG_ONE, gmsgHudText, NULL, pEntity->edict() ); WRITE_STRING( pString ); MESSAGE_END(); } void UTIL_ShowMessageAll( const char *pString ) { int i; // loop through all players for ( i = 1; i <= gpGlobals->maxClients; i++ ) { CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer ) UTIL_ShowMessage( pString, pPlayer ); } } void UTIL_ShowMessagePVS( const char *pString, const Vector &org ) // buz { // loop through all players for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer && pPlayer->IsNetClient() ) { // MESSAGE_BEGIN( MSG_ONE, gmsgHudText, NULL, pEntity->edict() ); MESSAGE_BEGIN( MSG_PAS, gmsgHudText, org ); WRITE_STRING( pString ); MESSAGE_END(); } } } // Overloaded to add IGNORE_GLASS void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr ) { TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE) | (ignoreGlass?0x100:0), pentIgnore, ptr ); } void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) { TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), pentIgnore, ptr ); } void UTIL_TraceHull( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr ) { TRACE_HULL( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), hullNumber, pentIgnore, ptr ); } void UTIL_TraceModel( const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr ) { g_engfuncs.pfnTraceModel( vecStart, vecEnd, hullNumber, pentModel, ptr ); } TraceResult UTIL_GetGlobalTrace( ) { TraceResult tr; tr.fAllSolid = gpGlobals->trace_allsolid; tr.fStartSolid = gpGlobals->trace_startsolid; tr.fInOpen = gpGlobals->trace_inopen; tr.fInWater = gpGlobals->trace_inwater; tr.flFraction = gpGlobals->trace_fraction; tr.flPlaneDist = gpGlobals->trace_plane_dist; tr.pHit = gpGlobals->trace_ent; tr.vecEndPos = gpGlobals->trace_endpos; tr.vecPlaneNormal = gpGlobals->trace_plane_normal; tr.iHitgroup = gpGlobals->trace_hitgroup; return tr; } void UTIL_SetSize( entvars_t *pev, const Vector &vecMin, const Vector &vecMax ) { SET_SIZE( ENT(pev), vecMin, vecMax ); } float UTIL_VecToYaw( const Vector &vec ) { return VEC_TO_YAW(vec); } void UTIL_SetEdictOrigin( edict_t *pEdict, const Vector &vecOrigin ) { SET_ORIGIN(pEdict, vecOrigin ); } // 'links' the entity into the world void UTIL_SetOrigin( CBaseEntity *pEntity, const Vector &vecOrigin ) { SET_ORIGIN(ENT(pEntity->pev), vecOrigin ); } void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ) { PARTICLE_EFFECT( vecOrigin, vecDirection, (float)ulColor, (float)ulCount ); } float UTIL_Approach( float target, float value, float speed ) { float delta = target - value; if ( delta > speed ) value += speed; else if ( delta < -speed ) value -= speed; else value = target; return value; } float UTIL_ApproachAngle( float target, float value, float speed ) { target = UTIL_AngleMod( target ); value = UTIL_AngleMod( target ); float delta = target - value; // Speed is assumed to be positive if ( speed < 0 ) speed = -speed; if ( delta < -180 ) delta += 360; else if ( delta > 180 ) delta -= 360; if ( delta > speed ) value += speed; else if ( delta < -speed ) value -= speed; else value = target; return value; } float UTIL_AngleDistance( float next, float cur ) { float delta = next - cur; // LRC- correct for deltas > 360 while ( delta < -180 ) delta += 360; while ( delta > 180 ) delta -= 360; return delta; } float UTIL_SplineFraction( float value, float scale ) { value = scale * value; float valueSquared = value * value; // Nice little ease-in, ease-out spline-like curve return 3 * valueSquared - 2 * valueSquared * value; } char* UTIL_VarArgs( char *format, ... ) { va_list argptr; static char string[1024]; va_start (argptr, format); vsprintf (string, format,argptr); va_end (argptr); return string; } Vector UTIL_GetAimVector( edict_t *pent, float flSpeed ) { Vector tmp; GET_AIM_VECTOR(pent, flSpeed, tmp); return tmp; } BOOL UTIL_IsMasterTriggered(string_t iszMaster, CBaseEntity *pActivator) { int i, j, found = false; const char *szMaster; char szBuf[80]; CBaseEntity *pMaster; int reverse = false; if (iszMaster) { // ALERT(at_console, "IsMasterTriggered(%s, %s \"%s\")\n", STRING(iszMaster), STRING(pActivator->pev->classname), STRING(pActivator->pev->targetname)); szMaster = STRING(iszMaster); if (szMaster[0] == '~') //inverse master { reverse = true; szMaster++; } pMaster = UTIL_FindEntityByTargetname( NULL, szMaster ); if ( !pMaster ) { for (i = 0; szMaster[i]; i++) { if (szMaster[i] == '(') { for (j = i+1; szMaster[j]; j++) { if (szMaster[j] == ')') { strncpy(szBuf, szMaster+i+1, (j-i)-1); szBuf[(j-i)-1] = 0; pActivator = UTIL_FindEntityByTargetname( NULL, szBuf ); found = true; break; } } if (!found) // no ) found { ALERT(at_error, "Missing ')' in master \"%s\"\n", szMaster); return FALSE; } break; } } if (!found) // no ( found { ALERT(at_debug, "Master \"%s\" not found!\n",szMaster); return TRUE; } strncpy(szBuf, szMaster, i); szBuf[i] = 0; pMaster = UTIL_FindEntityByTargetname( NULL, szBuf ); } if (pMaster) { if (reverse) return (pMaster->GetState( pActivator ) != STATE_ON); else return (pMaster->GetState( pActivator ) == STATE_ON); } } // if the entity has no master (or the master is missing), just say yes. return TRUE; } BOOL UTIL_ShouldShowBlood( int color ) { if ( color != DONT_BLEED ) { if ( color == BLOOD_COLOR_RED ) { if ( CVAR_GET_FLOAT("violence_hblood") != 0 ) return TRUE; } else { if ( CVAR_GET_FLOAT("violence_ablood") != 0 ) return TRUE; } } return FALSE; } int UTIL_PointContents( const Vector &vec ) { return POINT_CONTENTS(vec); } void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ) { if ( !UTIL_ShouldShowBlood( color ) ) return; if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) color = 0; MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); WRITE_BYTE( TE_BLOODSTREAM ); WRITE_COORD( origin.x ); WRITE_COORD( origin.y ); WRITE_COORD( origin.z ); WRITE_COORD( direction.x ); WRITE_COORD( direction.y ); WRITE_COORD( direction.z ); WRITE_BYTE( color ); WRITE_BYTE( min( amount, 255 ) ); MESSAGE_END(); } void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) { if ( !UTIL_ShouldShowBlood( color ) ) return; if ( color == DONT_BLEED || amount == 0 ) return; if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) color = 0; if ( g_pGameRules->IsMultiplayer() ) { // scale up blood effect in multiplayer for better visibility amount *= 2; } if ( amount > 255 ) amount = 255; MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); WRITE_BYTE( TE_BLOODSPRITE ); WRITE_COORD( origin.x); // pos WRITE_COORD( origin.y); WRITE_COORD( origin.z); WRITE_SHORT( g_sModelIndexBloodSpray ); // initial sprite model WRITE_SHORT( g_sModelIndexBloodDrop ); // droplet sprite models WRITE_BYTE( color ); // color index into host_basepal WRITE_BYTE( min( max( 3, amount / 10 ), 16 ) ); // size MESSAGE_END(); } Vector UTIL_RandomBloodVector( void ) { Vector direction; direction.x = RANDOM_FLOAT ( -1, 1 ); direction.y = RANDOM_FLOAT ( -1, 1 ); direction.z = RANDOM_FLOAT ( 0, 1 ); return direction; } void UTIL_DecalTrace( TraceResult *pTrace, const char *decalName ) { short entityIndex; int index; int message; index = DECAL_INDEX( decalName ); if( index < 0 ) return; if (pTrace->flFraction == 1.0) return; // Only decal BSP models if ( pTrace->pHit ) { CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); if ( pEntity && !pEntity->IsBSPModel() ) return; entityIndex = ENTINDEX( pTrace->pHit ); } else entityIndex = 0; message = TE_DECAL; if ( entityIndex != 0 ) { if ( index > 255 ) { message = TE_DECALHIGH; index -= 256; } } else { message = TE_WORLDDECAL; if ( index > 255 ) { message = TE_WORLDDECALHIGH; index -= 256; } } MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( message ); WRITE_COORD( pTrace->vecEndPos.x ); WRITE_COORD( pTrace->vecEndPos.y ); WRITE_COORD( pTrace->vecEndPos.z ); WRITE_BYTE( index ); if ( entityIndex ) WRITE_SHORT( entityIndex ); MESSAGE_END(); } // buz BOOL UTIL_TraceCustomDecal( TraceResult *pTrace, const char *name, float angle, int persistent ) // Wargon: Значение по умолчанию прописано в util.h. { short entityIndex; short modelIndex = 0; byte flags = 0; if( !name || !*name ) return FALSE; if (pTrace->flFraction == 1.0) return FALSE; // Only decal BSP models if ( pTrace->pHit ) { CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); if ( pEntity && !pEntity->IsBSPModel() ) return FALSE; entityIndex = ENTINDEX( pTrace->pHit ); } else entityIndex = 0; edict_t *pEdict = INDEXENT( entityIndex ); if( pEdict ) modelIndex = pEdict->v.modelindex; if( persistent ) flags |= FDECAL_PERMANENT; angle = anglemod( angle ); MESSAGE_BEGIN( persistent ? MSG_INIT : MSG_BROADCAST, gmsgCustomDecal ); WRITE_COORD( pTrace->vecEndPos.x ); WRITE_COORD( pTrace->vecEndPos.y ); WRITE_COORD( pTrace->vecEndPos.z ); WRITE_COORD( pTrace->vecPlaneNormal.x * 8192 ); WRITE_COORD( pTrace->vecPlaneNormal.y * 8192 ); WRITE_COORD( pTrace->vecPlaneNormal.z * 8192 ); WRITE_SHORT( entityIndex ); WRITE_SHORT( modelIndex ); WRITE_STRING( name ); WRITE_BYTE( flags ); WRITE_ANGLE( angle ); MESSAGE_END(); return TRUE; } void UTIL_RestoreCustomDecal( const Vector &vecPos, const Vector &vecNormal, int entityIndex, int modelIndex, const char *name, int flags, float angle ) { MESSAGE_BEGIN( MSG_INIT, gmsgCustomDecal ); WRITE_COORD( vecPos.x ); WRITE_COORD( vecPos.y ); WRITE_COORD( vecPos.z ); WRITE_COORD( vecNormal.x * 8192 ); WRITE_COORD( vecNormal.y * 8192 ); WRITE_COORD( vecNormal.z * 8192 ); WRITE_SHORT( entityIndex ); WRITE_SHORT( modelIndex ); WRITE_STRING( name ); WRITE_BYTE( flags ); WRITE_ANGLE( angle ); MESSAGE_END(); } BOOL UTIL_StudioDecalTrace( TraceResult *pTrace, const char *name, int flags ) { short entityIndex; if( !name || !*name ) return FALSE; if( pTrace->flFraction == 1.0f ) return FALSE; CBaseEntity *pEntity; // Only decal Studio models if( pTrace->pHit ) { pEntity = CBaseEntity::Instance( pTrace->pHit ); if( !pEntity || !GET_MODEL_PTR( pEntity->edict() )) return FALSE; // not a studiomodel? entityIndex = ENTINDEX( pTrace->pHit ); } else { return FALSE; } Vector vecNormal = pTrace->vecPlaneNormal; Vector vecEnd = pTrace->vecEndPos; Vector scale = Vector( 1.0f, 1.0f, 1.0f ); if( FBitSet( pEntity->pev->iuser1, CF_STATIC_ENTITY )) { if( pEntity->pev->startpos != g_vecZero ) scale = Vector( 1.0f / pEntity->pev->startpos.x, 1.0f / pEntity->pev->startpos.y, 1.0f / pEntity->pev->startpos.z ); } // studio decals is always converted into local space to avoid troubles with precision and modelscale matrix4x4 mat = matrix4x4( pEntity->pev->origin, pEntity->pev->angles, scale ); vecNormal = mat.VectorIRotate( vecNormal ); vecEnd = mat.VectorITransform( vecEnd ); SetBits( flags, FDECAL_LOCAL_SPACE ); // now it's in local space MESSAGE_BEGIN( MSG_BROADCAST, gmsgStudioDecal ); WRITE_COORD( vecEnd.x ); // write pos WRITE_COORD( vecEnd.y ); WRITE_COORD( vecEnd.z ); WRITE_COORD( vecNormal.x * 1000.0f ); // write normal WRITE_COORD( vecNormal.y * 1000.0f ); WRITE_COORD( vecNormal.z * 1000.0f ); WRITE_SHORT( entityIndex ); WRITE_SHORT( pEntity->pev->modelindex ); WRITE_STRING( name ); // decal texture WRITE_BYTE( flags ); // write model state for correct restore WRITE_SHORT( pEntity->pev->sequence ); WRITE_SHORT( (int)( pEntity->pev->frame * 8.0f )); WRITE_BYTE( pEntity->pev->blending[0] ); WRITE_BYTE( pEntity->pev->blending[1] ); WRITE_BYTE( pEntity->pev->controller[0] ); WRITE_BYTE( pEntity->pev->controller[1] ); WRITE_BYTE( pEntity->pev->controller[2] ); WRITE_BYTE( pEntity->pev->controller[3] ); WRITE_BYTE( pEntity->pev->body ); WRITE_BYTE( pEntity->pev->skin ); if( FBitSet( pEntity->pev->iuser1, CF_STATIC_ENTITY )) WRITE_SHORT( pEntity->pev->colormap ); else WRITE_SHORT( 0 ); MESSAGE_END(); return TRUE; } void UTIL_RestoreStudioDecal( const Vector &vecEnd, const Vector &vecNormal, int entityIndex, int modelIndex, const char *name, int flags, modelstate_t *state, int lightcache, const Vector &scale ) { MESSAGE_BEGIN( MSG_INIT, gmsgStudioDecal ); WRITE_COORD( vecEnd.x ); // write pos WRITE_COORD( vecEnd.y ); WRITE_COORD( vecEnd.z ); WRITE_COORD( vecNormal.x * 1000.0f ); // write normal WRITE_COORD( vecNormal.y * 1000.0f ); WRITE_COORD( vecNormal.z * 1000.0f ); WRITE_SHORT( entityIndex ); WRITE_SHORT( modelIndex ); WRITE_STRING( name ); // decal texture WRITE_BYTE( flags ); // write model state for correct restore WRITE_SHORT( state->sequence ); WRITE_SHORT( state->frame ); // already premultiplied by 8 WRITE_BYTE( state->blending[0] ); WRITE_BYTE( state->blending[1] ); WRITE_BYTE( state->controller[0] ); WRITE_BYTE( state->controller[1] ); WRITE_BYTE( state->controller[2] ); WRITE_BYTE( state->controller[3] ); WRITE_BYTE( state->body ); WRITE_BYTE( state->skin ); WRITE_SHORT( lightcache ); // model scale WRITE_COORD( scale.x * 1000.0f ); WRITE_COORD( scale.y * 1000.0f ); WRITE_COORD( scale.z * 1000.0f ); MESSAGE_END(); } void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ) { if ( UTIL_ShouldShowBlood( bloodColor ) ) { if ( bloodColor == BLOOD_COLOR_RED ) UTIL_TraceCustomDecal( pTrace, "redblood", RANDOM_FLOAT( 0.0f, 360.0f )); else UTIL_TraceCustomDecal( pTrace, "yellowblood", RANDOM_FLOAT( 0.0f, 360.0f )); } } void UTIL_BloodStudioDecalTrace( TraceResult *pTrace, int bloodColor ) { if ( UTIL_ShouldShowBlood( bloodColor ) ) { if ( bloodColor == BLOOD_COLOR_RED ) UTIL_StudioDecalTrace( pTrace, "redblood" ); else UTIL_StudioDecalTrace( pTrace, "yellowblood" ); } } void UTIL_GunshotDecalTrace( TraceResult *pTrace, const char *name ) { if (pTrace->flFraction == 1.0) return; UTIL_TraceCustomDecal( pTrace, name ); } void UTIL_Sparks( const Vector &position ) { #if 0 MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); WRITE_BYTE( TE_SPARKS ); WRITE_COORD( position.x ); WRITE_COORD( position.y ); WRITE_COORD( position.z ); MESSAGE_END(); #else MESSAGE_BEGIN( MSG_PVS, gmsgPartEffect, position ); WRITE_COORD( position.x ); WRITE_COORD( position.y ); WRITE_COORD( position.z ); WRITE_COORD( 0.0f ); WRITE_COORD( 0.0f ); WRITE_COORD( 0.0f ); WRITE_STRING( "env_spark.part1" ); MESSAGE_END(); MESSAGE_BEGIN( MSG_PVS, gmsgPartEffect, position ); WRITE_COORD( position.x ); WRITE_COORD( position.y ); WRITE_COORD( position.z ); WRITE_COORD( 0.0f ); WRITE_COORD( 0.0f ); WRITE_COORD( 0.0f ); WRITE_STRING( "env_spark.part2" ); MESSAGE_END(); #endif } void UTIL_Ricochet( const Vector &position, float scale ) { MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); WRITE_BYTE( TE_ARMOR_RICOCHET ); WRITE_COORD( position.x ); WRITE_COORD( position.y ); WRITE_COORD( position.z ); WRITE_BYTE( (int)(scale*10) ); MESSAGE_END(); } BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ) { // Everyone matches unless it's teamplay if ( !g_pGameRules->IsTeamplay() ) return TRUE; // Both on a team? if ( *pTeamName1 != 0 && *pTeamName2 != 0 ) { if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team? return TRUE; } return FALSE; } //LRC - moved here from barney.cpp BOOL UTIL_IsFacing( entvars_t *pevTest, const Vector &reference ) { Vector vecDir = (reference - pevTest->origin); vecDir.z = 0; vecDir = vecDir.Normalize(); Vector forward, angle; angle = pevTest->v_angle; angle.x = 0; UTIL_MakeVectorsPrivate( angle, forward, NULL, NULL ); // He's facing me, he meant it if ( DotProduct( forward, vecDir ) > 0.96 ) // +/- 15 degrees or so { return TRUE; } return FALSE; } void UTIL_StringToVector( float *pVector, const char *pString ) { char *pstr, *pfront, tempString[128]; int j; strcpy( tempString, pString ); pstr = pfront = tempString; for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c { pVector[j] = atof( pfront ); while ( *pstr && *pstr != ' ' ) pstr++; if (!*pstr) break; pstr++; pfront = pstr; } if (j < 2) { /* ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); */ for (j = j+1;j < 3; j++) pVector[j] = 0; } } //LRC - randomized vectors of the form "0 0 0 .. 1 0 0" void UTIL_StringToRandomVector( float *pVector, const char *pString ) { char *pstr, *pfront, tempString[128]; int j; float pAltVec[3]; strcpy( tempString, pString ); pstr = pfront = tempString; for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c { pVector[j] = atof( pfront ); while ( *pstr && *pstr != ' ' ) pstr++; if (!*pstr) break; pstr++; pfront = pstr; } if (j < 2) { /* ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); */ for (j = j+1;j < 3; j++) pVector[j] = 0; } else if (*pstr == '.') { pstr++; if (*pstr != '.') return; pstr++; if (*pstr != ' ') return; UTIL_StringToVector(pAltVec, pstr); pVector[0] = RANDOM_FLOAT( pVector[0], pAltVec[0] ); pVector[1] = RANDOM_FLOAT( pVector[1], pAltVec[1] ); pVector[2] = RANDOM_FLOAT( pVector[2], pAltVec[2] ); } } void UTIL_StringToIntArray( int *pVector, int count, const char *pString ) { char *pstr, *pfront, tempString[128]; int j; strcpy( tempString, pString ); pstr = pfront = tempString; for ( j = 0; j < count; j++ ) // lifted from pr_edict.c { pVector[j] = atoi( pfront ); while ( *pstr && *pstr != ' ' ) pstr++; if (!*pstr) break; pstr++; pfront = pstr; } for ( j++; j < count; j++ ) { pVector[j] = 0; } } Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ) { Vector sourceVector = input; if ( sourceVector.x > clampSize.x ) sourceVector.x -= clampSize.x; else if ( sourceVector.x < -clampSize.x ) sourceVector.x += clampSize.x; else sourceVector.x = 0; if ( sourceVector.y > clampSize.y ) sourceVector.y -= clampSize.y; else if ( sourceVector.y < -clampSize.y ) sourceVector.y += clampSize.y; else sourceVector.y = 0; if ( sourceVector.z > clampSize.z ) sourceVector.z -= clampSize.z; else if ( sourceVector.z < -clampSize.z ) sourceVector.z += clampSize.z; else sourceVector.z = 0; return sourceVector.Normalize(); } float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) { Vector midUp = position; midUp.z = minz; if (UTIL_PointContents(midUp) != CONTENTS_WATER) return minz; midUp.z = maxz; if (UTIL_PointContents(midUp) == CONTENTS_WATER) return maxz; float diff = maxz - minz; while (diff > 1.0) { midUp.z = minz + diff/2.0; if (UTIL_PointContents(midUp) == CONTENTS_WATER) { minz = midUp.z; } else { maxz = midUp.z; } diff = maxz - minz; } return midUp.z; } extern DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model void UTIL_Bubbles( Vector mins, Vector maxs, int count ) { Vector mid = (mins + maxs) * 0.5; float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 ); flHeight = flHeight - mins.z; MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, mid ); WRITE_BYTE( TE_BUBBLES ); WRITE_COORD( mins.x ); // mins WRITE_COORD( mins.y ); WRITE_COORD( mins.z ); WRITE_COORD( maxs.x ); // maxz WRITE_COORD( maxs.y ); WRITE_COORD( maxs.z ); WRITE_COORD( flHeight ); // height WRITE_SHORT( g_sModelIndexBubbles ); WRITE_BYTE( count ); // count WRITE_COORD( 8 ); // speed MESSAGE_END(); } void UTIL_BubbleTrail( Vector from, Vector to, int count ) { float flHeight = UTIL_WaterLevel( from, from.z, from.z + 256 ); flHeight = flHeight - from.z; if (flHeight < 8) { flHeight = UTIL_WaterLevel( to, to.z, to.z + 256 ); flHeight = flHeight - to.z; if (flHeight < 8) return; // UNDONE: do a ploink sound flHeight = flHeight + to.z - from.z; } if (count > 255) count = 255; MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_BUBBLETRAIL ); WRITE_COORD( from.x ); // mins WRITE_COORD( from.y ); WRITE_COORD( from.z ); WRITE_COORD( to.x ); // maxz WRITE_COORD( to.y ); WRITE_COORD( to.z ); WRITE_COORD( flHeight ); // height WRITE_SHORT( g_sModelIndexBubbles ); WRITE_BYTE( count ); // count WRITE_COORD( 8 ); // speed MESSAGE_END(); } void UTIL_Remove( CBaseEntity *pEntity ) { if ( !pEntity ) return; pEntity->UpdateOnRemove(); pEntity->pev->flags |= FL_KILLME; pEntity->pev->targetname = 0; } BOOL UTIL_IsValidEntity( edict_t *pent ) { if ( !pent || pent->free || (pent->v.flags & FL_KILLME) ) return FALSE; return TRUE; } void UTIL_PrecacheOther( const char *szClassname ) { edict_t *pent; pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); if ( FNullEnt( pent ) ) { ALERT ( at_debug, "NULL Ent in UTIL_PrecacheOther\n" ); return; } CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); if (pEntity) pEntity->Precache( ); REMOVE_ENTITY(pent); } //========================================================= // UTIL_LogPrintf - Prints a logged message to console. // Preceded by LOG: ( timestamp ) < message > //========================================================= void UTIL_LogPrintf( char *fmt, ... ) { va_list argptr; static char string[1024]; va_start ( argptr, fmt ); vsprintf ( string, fmt, argptr ); va_end ( argptr ); // Print to server console ALERT( at_logged, "%s", string ); } //========================================================= // UTIL_DotPoints - returns the dot product of a line from // src to check and vecdir. //========================================================= float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ) { Vector2D vec2LOS; vec2LOS = ( vecCheck - vecSrc ).Make2D(); vec2LOS = vec2LOS.Normalize(); return DotProduct (vec2LOS , ( vecDir.Make2D() ) ); } //========================================================= // UTIL_StripToken - for redundant keynames //========================================================= void UTIL_StripToken( const char *pKey, char *pDest ) { int i = 0; while ( pKey[i] && pKey[i] != '#' ) { pDest[i] = pKey[i]; i++; } pDest[i] = 0; } char* GetStringForUseType( USE_TYPE useType ) { switch(useType) { case USE_ON: return "USE_ON"; case USE_OFF: return "USE_OFF"; case USE_TOGGLE: return "USE_TOGGLE"; case USE_KILL: return "USE_KILL"; case USE_NOT: return "USE_NOT"; default: return "USE_UNKNOWN!?"; } } char* GetStringForState( STATE state ) { switch(state) { case STATE_ON: return "ON"; case STATE_OFF: return "OFF"; case STATE_TURN_ON: return "TURN ON"; case STATE_TURN_OFF: return "TURN OFF"; case STATE_IN_USE: return "IN USE"; default: return "STATE_UNKNOWN!?"; } } // -------------------------------------------------------------- // // CSave // // -------------------------------------------------------------- static int gSizes[FIELD_TYPECOUNT] = { sizeof(float), // FIELD_FLOAT sizeof(int), // FIELD_STRING sizeof(int), // FIELD_ENTITY sizeof(int), // FIELD_CLASSPTR sizeof(int), // FIELD_EHANDLE sizeof(int), // FIELD_entvars_t sizeof(int), // FIELD_EDICT sizeof(float)*3, // FIELD_VECTOR sizeof(float)*3, // FIELD_POSITION_VECTOR sizeof(int *), // FIELD_POINTER sizeof(int), // FIELD_INTEGER sizeof(int *), // FIELD_FUNCTION sizeof(int), // FIELD_BOOLEAN sizeof(short), // FIELD_SHORT sizeof(char), // FIELD_CHARACTER sizeof(float), // FIELD_TIME sizeof(int), // FIELD_MODELNAME sizeof(int), // FIELD_SOUNDNAME sizeof(float)*2, // FIELD_RANGE }; // Base class includes common SAVERESTOREDATA pointer, and manages the entity table CSaveRestoreBuffer :: CSaveRestoreBuffer( void ) { m_pdata = NULL; } CSaveRestoreBuffer :: CSaveRestoreBuffer( SAVERESTOREDATA *pdata ) { m_pdata = pdata; } CSaveRestoreBuffer :: ~CSaveRestoreBuffer( void ) { } int CSaveRestoreBuffer :: EntityIndex( CBaseEntity *pEntity ) { if ( pEntity == NULL ) return -1; return EntityIndex( pEntity->pev ); } int CSaveRestoreBuffer :: EntityIndex( entvars_t *pevLookup ) { if ( pevLookup == NULL ) return -1; return EntityIndex( ENT( pevLookup ) ); } int CSaveRestoreBuffer :: EntityIndex( EOFFSET eoLookup ) { return EntityIndex( ENT( eoLookup ) ); } int CSaveRestoreBuffer :: EntityIndex( edict_t *pentLookup ) { if ( !m_pdata || pentLookup == NULL ) return -1; int i; ENTITYTABLE *pTable; for ( i = 0; i < m_pdata->tableCount; i++ ) { pTable = m_pdata->pTable + i; if ( pTable->pent == pentLookup ) return i; } return -1; } edict_t *CSaveRestoreBuffer :: EntityFromIndex( int entityIndex ) { if ( !m_pdata || entityIndex < 0 ) return NULL; int i; ENTITYTABLE *pTable; for ( i = 0; i < m_pdata->tableCount; i++ ) { pTable = m_pdata->pTable + i; if ( pTable->id == entityIndex ) return pTable->pent; } return NULL; } int CSaveRestoreBuffer :: EntityFlagsSet( int entityIndex, int flags ) { if ( !m_pdata || entityIndex < 0 ) return 0; if ( entityIndex > m_pdata->tableCount ) return 0; m_pdata->pTable[ entityIndex ].flags |= flags; return m_pdata->pTable[ entityIndex ].flags; } void CSaveRestoreBuffer :: BufferRewind( int size ) { if ( !m_pdata ) return; if ( m_pdata->size < size ) size = m_pdata->size; m_pdata->pCurrentData -= size; m_pdata->size -= size; } #ifndef _WIN32 extern "C" { unsigned _rotr ( unsigned val, int shift) { register unsigned lobit; /* non-zero means lo bit set */ register unsigned num = val; /* number to rotate */ shift &= 0x1f; /* modulo 32 -- this will also make negative shifts work */ while (shift--) { lobit = num & 1; /* get high bit */ num >>= 1; /* shift right one bit */ if (lobit) num |= 0x80000000; /* set hi bit if lo bit was set */ } return num; } } #endif unsigned int CSaveRestoreBuffer :: HashString( const char *pszToken ) { unsigned int hash = 0; while ( *pszToken ) hash = _rotr( hash, 4 ) ^ *pszToken++; return hash; } unsigned short CSaveRestoreBuffer :: TokenHash( const char *pszToken ) { unsigned short hash = (unsigned short)(HashString( pszToken ) % (unsigned)m_pdata->tokenCount ); #if _DEBUG static int tokensparsed = 0; tokensparsed++; if ( !m_pdata->tokenCount || !m_pdata->pTokens ) ALERT( at_error, "No token table array in TokenHash()!" ); #endif for ( int i=0; itokenCount; i++ ) { #if _DEBUG static qboolean beentheredonethat = FALSE; if ( i > 50 && !beentheredonethat ) { beentheredonethat = TRUE; ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is getting too full!" ); } #endif int index = hash + i; if ( index >= m_pdata->tokenCount ) index -= m_pdata->tokenCount; if ( !m_pdata->pTokens[index] || strcmp( pszToken, m_pdata->pTokens[index] ) == 0 ) { m_pdata->pTokens[index] = (char *)pszToken; return index; } } // Token hash table full!!! // [Consider doing overflow table(s) after the main table & limiting linear hash table search] ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is COMPLETELY FULL!" ); return 0; } void CSave :: WriteData( const char *pname, int size, const char *pdata ) { BufferField( pname, size, pdata ); } void CSave :: WriteShort( const char *pname, const short *data, int count ) { BufferField( pname, sizeof(short) * count, (const char *)data ); } void CSave :: WriteInt( const char *pname, const int *data, int count ) { BufferField( pname, sizeof(int) * count, (const char *)data ); } void CSave :: WriteFloat( const char *pname, const float *data, int count ) { BufferField( pname, sizeof(float) * count, (const char *)data ); } void CSave :: WriteTime( const char *pname, const float *data, int count ) { int i; Vector tmp, input; BufferHeader( pname, sizeof(float) * count ); for ( i = 0; i < count; i++ ) { float tmp = data[0]; // Always encode time as a delta from the current time so it can be re-based if loaded in a new level // Times of 0 are never written to the file, so they will be restored as 0, not a relative time if ( m_pdata ) tmp -= m_pdata->time; BufferData( (const char *)&tmp, sizeof(float) ); data ++; } } void CSave :: WriteString( const char *pname, const char *pdata ) { #ifdef TOKENIZE short token = (short)TokenHash( pdata ); WriteShort( pname, &token, 1 ); #else BufferField( pname, strlen(pdata) + 1, pdata ); #endif } void CSave :: WriteString( const char *pname, const int *stringId, int count ) { int i, size; #ifdef TOKENIZE short token = (short)TokenHash( STRING( *stringId ) ); WriteShort( pname, &token, 1 ); #else #if 0 if ( count != 1 ) ALERT( at_error, "No string arrays!\n" ); WriteString( pname, (char *)STRING(*stringId) ); #endif size = 0; for ( i = 0; i < count; i++ ) size += strlen( STRING( stringId[i] ) ) + 1; BufferHeader( pname, size ); for ( i = 0; i < count; i++ ) { const char *pString = STRING(stringId[i]); BufferData( pString, strlen(pString)+1 ); } #endif } void CSave :: WriteVector( const char *pname, const Vector &value ) { WriteVector( pname, &value.x, 1 ); } void CSave :: WriteVector( const char *pname, const float *value, int count ) { BufferHeader( pname, sizeof(float) * 3 * count ); BufferData( (const char *)value, sizeof(float) * 3 * count ); } void CSave :: WriteRange( const char *pname, const RandomRange &value ) { WriteRange( pname, &value.m_flMin, 1 ); } void CSave :: WriteRange( const char *pname, const float *value, int count ) { BufferHeader( pname, sizeof(float) * 2 * count ); BufferData( (const char *)value, sizeof(float) * 2 * count ); } void CSave :: WritePositionVector( const char *pname, const Vector &value ) { if ( m_pdata && m_pdata->fUseLandmark ) { Vector tmp = value - m_pdata->vecLandmarkOffset; WriteVector( pname, tmp ); } WriteVector( pname, value ); } void CSave :: WritePositionVector( const char *pname, const float *value, int count ) { int i; Vector tmp, input; BufferHeader( pname, sizeof(float) * 3 * count ); for ( i = 0; i < count; i++ ) { Vector tmp( value[0], value[1], value[2] ); if ( m_pdata && m_pdata->fUseLandmark ) tmp = tmp - m_pdata->vecLandmarkOffset; BufferData( (const char *)&tmp.x, sizeof(float) * 3 ); value += 3; } } void CSave :: WriteFunction( const char* cname, const char *pname, void **data, int count ) { const char *functionName; functionName = NAME_FOR_FUNCTION( *data ); if ( functionName ) BufferField( pname, strlen(functionName) + 1, functionName ); else ALERT( at_error, "Member \"%s\" of \"%s\" contains an invalid function pointer %p!", pname, cname, *data ); } void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ) { int i; TYPEDESCRIPTION *pField; for ( i = 0; i < ENTVARS_COUNT; i++ ) { pField = &gEntvarsDescription[i]; if ( !stricmp( pField->fieldName, pkvd->szKeyName ) ) { switch( pField->fieldType ) { case FIELD_MODELNAME: case FIELD_SOUNDNAME: case FIELD_STRING: (*(int *)((char *)pev + pField->fieldOffset)) = ALLOC_STRING( pkvd->szValue ); break; case FIELD_TIME: case FIELD_FLOAT: (*(float *)((char *)pev + pField->fieldOffset)) = atof( pkvd->szValue ); break; case FIELD_INTEGER: (*(int *)((char *)pev + pField->fieldOffset)) = atoi( pkvd->szValue ); break; case FIELD_POSITION_VECTOR: case FIELD_VECTOR: UTIL_StringToVector( (float *)((char *)pev + pField->fieldOffset), pkvd->szValue ); break; default: case FIELD_EVARS: case FIELD_CLASSPTR: case FIELD_EDICT: case FIELD_ENTITY: case FIELD_POINTER: ALERT( at_error, "Bad field in entity!!\n" ); break; } pkvd->fHandled = TRUE; return; } } } int CSave :: WriteEntVars( const char *pname, entvars_t *pev ) { if (pev->targetname) return WriteFields( STRING(pev->targetname), pname, pev, gEntvarsDescription, ENTVARS_COUNT ); else return WriteFields( STRING(pev->classname), pname, pev, gEntvarsDescription, ENTVARS_COUNT ); } int CSave :: WriteFields( const char *cname, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) { int i, j, actualCount, emptyCount; TYPEDESCRIPTION *pTest; int entityArray[MAX_ENTITYARRAY]; // Precalculate the number of empty fields emptyCount = 0; for ( i = 0; i < fieldCount; i++ ) { void *pOutputData; pOutputData = ((char *)pBaseData + pFields[i].fieldOffset ); if ( DataEmpty( (const char *)pOutputData, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ) ) emptyCount++; } // Empty fields will not be written, write out the actual number of fields to be written actualCount = fieldCount - emptyCount; WriteInt( pname, &actualCount, 1 ); for ( i = 0; i < fieldCount; i++ ) { void *pOutputData; pTest = &pFields[ i ]; pOutputData = ((char *)pBaseData + pTest->fieldOffset ); // UNDONE: Must we do this twice? if ( DataEmpty( (const char *)pOutputData, pTest->fieldSize * gSizes[pTest->fieldType] ) ) continue; switch( pTest->fieldType ) { case FIELD_FLOAT: WriteFloat( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); break; case FIELD_TIME: WriteTime( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); break; case FIELD_MODELNAME: case FIELD_SOUNDNAME: case FIELD_STRING: WriteString( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); break; case FIELD_CLASSPTR: case FIELD_EVARS: case FIELD_EDICT: case FIELD_ENTITY: case FIELD_EHANDLE: if ( pTest->fieldSize > MAX_ENTITYARRAY ) ALERT( at_error, "Can't save more than %d entities in an array!!!\n", MAX_ENTITYARRAY ); for ( j = 0; j < pTest->fieldSize; j++ ) { switch( pTest->fieldType ) { case FIELD_EVARS: entityArray[j] = EntityIndex( ((entvars_t **)pOutputData)[j] ); break; case FIELD_CLASSPTR: entityArray[j] = EntityIndex( ((CBaseEntity **)pOutputData)[j] ); break; case FIELD_EDICT: entityArray[j] = EntityIndex( ((edict_t **)pOutputData)[j] ); break; case FIELD_ENTITY: entityArray[j] = EntityIndex( ((EOFFSET *)pOutputData)[j] ); break; case FIELD_EHANDLE: entityArray[j] = EntityIndex( (CBaseEntity *)(((EHANDLE *)pOutputData)[j]) ); break; } } WriteInt( pTest->fieldName, entityArray, pTest->fieldSize ); break; case FIELD_POSITION_VECTOR: WritePositionVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); break; case FIELD_VECTOR: WriteVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); break; case FIELD_RANGE: WriteRange( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); break; case FIELD_BOOLEAN: case FIELD_INTEGER: WriteInt( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); break; case FIELD_SHORT: WriteData( pTest->fieldName, 2 * pTest->fieldSize, ((char *)pOutputData) ); break; case FIELD_CHARACTER: WriteData( pTest->fieldName, pTest->fieldSize, ((char *)pOutputData) ); break; // For now, just write the address out, we're not going to change memory while doing this yet! case FIELD_POINTER: WriteInt( pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); break; case FIELD_FUNCTION: WriteFunction( cname, pTest->fieldName, (void **)pOutputData, pTest->fieldSize ); break; default: ALERT( at_error, "Bad field type\n" ); } } return 1; } void CSave :: BufferString( char *pdata, int len ) { char c = 0; BufferData( pdata, len ); // Write the string BufferData( &c, 1 ); // Write a null terminator } int CSave :: DataEmpty( const char *pdata, int size ) { for ( int i = 0; i < size; i++ ) { if ( pdata[i] ) return 0; } return 1; } void CSave :: BufferField( const char *pname, int size, const char *pdata ) { BufferHeader( pname, size ); BufferData( pdata, size ); } void CSave :: BufferHeader( const char *pname, int size ) { short hashvalue = TokenHash( pname ); if ( size > 1<<(sizeof(short)*8) ) ALERT( at_error, "CSave :: BufferHeader() size parameter exceeds 'short'!" ); BufferData( (const char *)&size, sizeof(short) ); BufferData( (const char *)&hashvalue, sizeof(short) ); } void CSave :: BufferData( const char *pdata, int size ) { if ( !m_pdata ) return; if ( m_pdata->size + size > m_pdata->bufferSize ) { ALERT( at_error, "Save/Restore overflow!" ); m_pdata->size = m_pdata->bufferSize; return; } memcpy( m_pdata->pCurrentData, pdata, size ); m_pdata->pCurrentData += size; m_pdata->size += size; } // -------------------------------------------------------------- // // CRestore // // -------------------------------------------------------------- int CRestore::ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ) { int i, j, stringCount, fieldNumber, entityIndex; TYPEDESCRIPTION *pTest; float time, timeData; Vector position; edict_t *pent; char *pString; time = 0; position = Vector(0,0,0); if ( m_pdata ) { time = m_pdata->time; if ( m_pdata->fUseLandmark ) position = m_pdata->vecLandmarkOffset; } for ( i = 0; i < fieldCount; i++ ) { fieldNumber = (i+startField)%fieldCount; pTest = &pFields[ fieldNumber ]; if ( !stricmp( pTest->fieldName, pName ) ) { if ( !m_global || !(pTest->flags & FTYPEDESC_GLOBAL) ) { for ( j = 0; j < pTest->fieldSize; j++ ) { void *pOutputData = ((char *)pBaseData + pTest->fieldOffset + (j*gSizes[pTest->fieldType]) ); void *pInputData = (char *)pData + j * gSizes[pTest->fieldType]; switch( pTest->fieldType ) { case FIELD_TIME: timeData = *(float *)pInputData; // Re-base time variables timeData += time; *((float *)pOutputData) = timeData; break; case FIELD_FLOAT: *((float *)pOutputData) = *(float *)pInputData; break; case FIELD_MODELNAME: case FIELD_SOUNDNAME: case FIELD_STRING: // Skip over j strings pString = (char *)pData; for ( stringCount = 0; stringCount < j; stringCount++ ) { while (*pString) pString++; pString++; } pInputData = pString; if ( strlen( (char *)pInputData ) == 0 ) *((int *)pOutputData) = 0; else { int string; string = ALLOC_STRING( (char *)pInputData ); *((int *)pOutputData) = string; if ( !FStringNull( string ) && m_precache ) { if ( pTest->fieldType == FIELD_MODELNAME ) PRECACHE_MODEL( (char *)STRING( string ) ); else if ( pTest->fieldType == FIELD_SOUNDNAME ) PRECACHE_SOUND( (char *)STRING( string ) ); } } break; case FIELD_EVARS: entityIndex = *( int *)pInputData; pent = EntityFromIndex( entityIndex ); if ( pent ) *((entvars_t **)pOutputData) = VARS(pent); else *((entvars_t **)pOutputData) = NULL; break; case FIELD_CLASSPTR: entityIndex = *( int *)pInputData; pent = EntityFromIndex( entityIndex ); if ( pent ) *((CBaseEntity **)pOutputData) = CBaseEntity::Instance(pent); else { *((CBaseEntity **)pOutputData) = NULL; if (entityIndex != -1) ALERT(at_console, "## Restore: invalid entitynum %d\n", entityIndex); } break; case FIELD_EDICT: entityIndex = *( int *)pInputData; pent = EntityFromIndex( entityIndex ); *((edict_t **)pOutputData) = pent; break; case FIELD_EHANDLE: // Input and Output sizes are different! pOutputData = (char *)pOutputData + j*(sizeof(EHANDLE) - gSizes[pTest->fieldType]); entityIndex = *( int *)pInputData; pent = EntityFromIndex( entityIndex ); if ( pent ) *((EHANDLE *)pOutputData) = CBaseEntity::Instance(pent); else *((EHANDLE *)pOutputData) = NULL; break; case FIELD_ENTITY: entityIndex = *( int *)pInputData; pent = EntityFromIndex( entityIndex ); if ( pent ) *((EOFFSET *)pOutputData) = OFFSET(pent); else *((EOFFSET *)pOutputData) = 0; break; case FIELD_RANGE: ((float *)pOutputData)[0] = ((float *)pInputData)[0]; ((float *)pOutputData)[1] = ((float *)pInputData)[1]; break; case FIELD_VECTOR: ((float *)pOutputData)[0] = ((float *)pInputData)[0]; ((float *)pOutputData)[1] = ((float *)pInputData)[1]; ((float *)pOutputData)[2] = ((float *)pInputData)[2]; break; case FIELD_POSITION_VECTOR: ((float *)pOutputData)[0] = ((float *)pInputData)[0] + position.x; ((float *)pOutputData)[1] = ((float *)pInputData)[1] + position.y; ((float *)pOutputData)[2] = ((float *)pInputData)[2] + position.z; break; case FIELD_BOOLEAN: case FIELD_INTEGER: *((int *)pOutputData) = *( int *)pInputData; break; case FIELD_SHORT: *((short *)pOutputData) = *( short *)pInputData; break; case FIELD_CHARACTER: *((char *)pOutputData) = *( char *)pInputData; break; case FIELD_POINTER: *((int *)pOutputData) = *( int *)pInputData; break; case FIELD_FUNCTION: if( ( (char *)pInputData )[0] == '\0' ) *( (void**)pOutputData ) = 0; else *( (void**)pOutputData ) = (void*)FUNCTION_FROM_NAME( (char *)pInputData ); break; default: ALERT( at_error, "Bad field type\n" ); } } } #if 0 else { ALERT( at_debug, "Skipping global field %s\n", pName ); } #endif return fieldNumber; } } return -1; } int CRestore::ReadEntVars( const char *pname, entvars_t *pev ) { return ReadFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); } int CRestore::ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) { unsigned short i, token; int lastField, fileCount; HEADER header; i = ReadShort(); ASSERT( i == sizeof(int) ); // First entry should be an int token = ReadShort(); // Check the struct name if ( token != TokenHash(pname) ) // Field Set marker { // ALERT( at_error, "Expected %s found %s!\n", pname, BufferPointer() ); BufferRewind( 2*sizeof(short) ); return 0; } // Skip over the struct name fileCount = ReadInt(); // Read field count lastField = 0; // Make searches faster, most data is read/written in the same order // Clear out base data for ( i = 0; i < fieldCount; i++ ) { // Don't clear global fields if ( !m_global || !(pFields[i].flags & FTYPEDESC_GLOBAL) ) memset( ((char *)pBaseData + pFields[i].fieldOffset), 0, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ); } for ( i = 0; i < fileCount; i++ ) { BufferReadHeader( &header ); lastField = ReadField( pBaseData, pFields, fieldCount, lastField, header.size, m_pdata->pTokens[header.token], header.pData ); lastField++; } return 1; } void CRestore::BufferReadHeader( HEADER *pheader ) { ASSERT( pheader!=NULL ); pheader->size = ReadShort(); // Read field size pheader->token = ReadShort(); // Read field name token pheader->pData = BufferPointer(); // Field Data is next BufferSkipBytes( pheader->size ); // Advance to next field } short CRestore::ReadShort( void ) { short tmp = 0; BufferReadBytes( (char *)&tmp, sizeof(short) ); return tmp; } int CRestore::ReadInt( void ) { int tmp = 0; BufferReadBytes( (char *)&tmp, sizeof(int) ); return tmp; } int CRestore::ReadNamedInt( const char *pName ) { HEADER header; BufferReadHeader( &header ); return ((int *)header.pData)[0]; } char *CRestore::ReadNamedString( const char *pName ) { HEADER header; BufferReadHeader( &header ); #ifdef TOKENIZE return (char *)(m_pdata->pTokens[*(short *)header.pData]); #else return (char *)header.pData; #endif } char *CRestore::BufferPointer( void ) { if ( !m_pdata ) return NULL; return m_pdata->pCurrentData; } void CRestore::BufferReadBytes( char *pOutput, int size ) { ASSERT( m_pdata !=NULL ); if ( !m_pdata || Empty() ) return; if ( (m_pdata->size + size) > m_pdata->bufferSize ) { ALERT( at_error, "Restore overflow!" ); m_pdata->size = m_pdata->bufferSize; return; } if ( pOutput ) memcpy( pOutput, m_pdata->pCurrentData, size ); m_pdata->pCurrentData += size; m_pdata->size += size; } void CRestore::BufferSkipBytes( int bytes ) { BufferReadBytes( NULL, bytes ); } int CRestore::BufferSkipZString( void ) { char *pszSearch; int len; if ( !m_pdata ) return 0; int maxLen = m_pdata->bufferSize - m_pdata->size; len = 0; pszSearch = m_pdata->pCurrentData; while ( *pszSearch++ && len < maxLen ) len++; len++; BufferSkipBytes( len ); return len; } int CRestore::BufferCheckZString( const char *string ) { if ( !m_pdata ) return 0; int maxLen = m_pdata->bufferSize - m_pdata->size; int len = strlen( string ); if ( len <= maxLen ) { if ( !strncmp( string, m_pdata->pCurrentData, len ) ) return 1; } return 0; } void UTIL_FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ) { float distance; TraceResult tmpTrace; float *minmaxs[2] = {mins, maxs}; Vector vecHullEnd = tr.vecEndPos; Vector vecEnd; distance = 1e6f; vecHullEnd = vecSrc + ((vecHullEnd - vecSrc) * 2); UTIL_TraceLine( vecSrc, vecHullEnd, dont_ignore_monsters, pEntity, &tmpTrace ); if ( tmpTrace.flFraction < 1.0 ) { tr = tmpTrace; return; } for ( int i = 0; i < 2; i++ ) { for ( int j = 0; j < 2; j++ ) { for ( int k = 0; k < 2; k++ ) { vecEnd.x = vecHullEnd.x + minmaxs[i][0]; vecEnd.y = vecHullEnd.y + minmaxs[j][1]; vecEnd.z = vecHullEnd.z + minmaxs[k][2]; UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, pEntity, &tmpTrace ); if ( tmpTrace.flFraction < 1.0 ) { float thisDistance = (tmpTrace.vecEndPos - vecSrc).Length(); if ( thisDistance < distance ) { tr = tmpTrace; distance = thisDistance; } } } } } } unsigned short UTIL_PrecacheMovie( string_t iString, int allow_sound ) { return UTIL_PrecacheMovie( STRING( iString ), allow_sound ); } unsigned short UTIL_PrecacheMovie( const char *s, int allow_sound ) { int iCompare; char path[64], temp[64]; Q_snprintf( path, sizeof( path ), "media/%s", s ); Q_snprintf( temp, sizeof( temp ), "%s%s", allow_sound ? "*" : "", s ); // verify file exists // g-cont. idea! use COMPARE_FILE_TIME instead of LOAD_FILE_FOR_ME if( COMPARE_FILE_TIME( path, path, &iCompare )) { return g_engfuncs.pfnPrecacheGeneric( temp ); } ALERT( at_console, "Warning: video (%s) not found!\n", path ); return 0; }