engine: server: backported enttools from old engine

This commit is contained in:
SNMetamorph 2022-11-07 19:08:38 +04:00 committed by Alibek Omarov
parent afa1d429fe
commit 5d73c6cb84
4 changed files with 871 additions and 55 deletions

View File

@ -435,6 +435,8 @@ extern convar_t sv_consistency;
extern convar_t sv_password;
extern convar_t sv_uploadmax;
extern convar_t sv_trace_messages;
extern convar_t sv_enttools_enable;
extern convar_t sv_enttools_maxfire;
extern convar_t deathmatch;
extern convar_t hostname;
extern convar_t skill;
@ -642,8 +644,11 @@ void SV_RestartAmbientSounds( void );
void SV_RestartDecals( void );
void SV_RestartStaticEnts( void );
int pfnGetCurrentPlayer( void );
int pfnDropToFloor( edict_t* e );
edict_t *SV_EdictNum( int n );
char *SV_Localinfo( void );
void SV_SetModel( edict_t *ent, const char *name );
//
// sv_log.c
//

View File

@ -2119,6 +2119,780 @@ static qboolean SV_SendBuildInfo_f( sv_client_t *cl )
return true;
}
/*
==================
SV_GetCrossEnt
==================
*/
static edict_t *SV_GetCrossEnt( edict_t *player )
{
edict_t *ent = EDICT_NUM(1);
edict_t *closest = NULL;
float flMaxDot = 0.94;
vec3_t forward;
vec3_t viewPos;
int i;
float maxLen = 1000;
AngleVectors( player->v.v_angle, forward, NULL, NULL );
VectorAdd( player->v.origin, player->v.view_ofs, viewPos );
// find bmodels by trace
{
trace_t trace;
vec3_t target;
VectorMA( viewPos, 1000, forward, target );
trace = SV_Move( viewPos, vec3_origin, vec3_origin, target, 0, player, false );
closest = trace.ent;
VectorSubtract( viewPos, trace.endpos, target );
maxLen = VectorLength(target) + 30;
}
// check untraceable entities
for ( i = 1; i < svgame.numEntities; i++, ent++ )
{
vec3_t vecLOS;
vec3_t vecOrigin;
float flDot, traceLen;
vec3_t boxSize;
trace_t trace;
vec3_t vecTrace;
if( ent->free )
continue;
if( ent->v.solid == SOLID_BSP || ent->v.movetype == MOVETYPE_PUSHSTEP )
continue; // bsp models will be found by trace later
// do not touch following weapons
if( ent->v.movetype == MOVETYPE_FOLLOW )
continue;
if( ent == player )
continue;
VectorAdd( ent->v.absmin, ent->v.absmax, vecOrigin );
VectorScale( vecOrigin, 0.5, vecOrigin );
VectorSubtract( vecOrigin, viewPos, vecLOS );
traceLen = VectorLength(vecLOS);
if( traceLen > maxLen )
continue;
VectorCopy( ent->v.size, boxSize);
VectorScale( boxSize, 0.5, boxSize );
if ( vecLOS[0] > boxSize[0] )
vecLOS[0] -= boxSize[0];
else if ( vecLOS[0] < -boxSize[0] )
vecLOS[0] += boxSize[0];
else
vecLOS[0] = 0;
if ( vecLOS[1] > boxSize[1] )
vecLOS[1] -= boxSize[1];
else if ( vecLOS[1] < -boxSize[1] )
vecLOS[1] += boxSize[1];
else
vecLOS[1] = 0;
if ( vecLOS[2] > boxSize[2] )
vecLOS[2] -= boxSize[2];
else if ( vecLOS[2] < -boxSize[2] )
vecLOS[2] += boxSize[2];
else
vecLOS[2] = 0;
VectorNormalize( vecLOS );
flDot = DotProduct (vecLOS , forward);
if ( flDot <= flMaxDot )
continue;
trace = SV_Move( viewPos, vec3_origin, vec3_origin, vecOrigin, 0, player, false );
VectorSubtract( trace.endpos, viewPos, vecTrace );
if( VectorLength( vecTrace ) + 30 < traceLen )
continue;
closest = ent, flMaxDot = flDot;
}
return closest;
}
/*
==================
SV_EntFindSingle
==================
*/
static edict_t *SV_EntFindSingle( sv_client_t *cl, const char *pattern )
{
edict_t *ent = NULL;
int i = 0;
if( Q_isdigit( pattern ) )
{
i = Q_atoi( pattern );
if( i >= svgame.numEntities )
return NULL;
}
else if( !Q_stricmp( pattern, "!cross" ) )
{
ent = SV_GetCrossEnt( cl->edict );
if( !SV_IsValidEdict( ent ) )
return NULL;
i = NUM_FOR_EDICT( ent );
}
else if( pattern[0] == '!' ) // check for correct instance with !(num)_(serial)
{
const char *p = pattern + 1;
i = Q_atoi( p );
while( Q_isdigit( p )) p++;
if( *p++ != '_' )
return NULL;
if( i >= svgame.numEntities )
return NULL;
ent = EDICT_NUM( i );
if( ent->serialnumber != Q_atoi( p ) )
return NULL;
}
else
{
for( i = svgame.globals->maxClients + 1; i < svgame.numEntities; i++ )
{
ent = EDICT_NUM( i );
if( !SV_IsValidEdict( ent ) )
continue;
if( Q_stricmpext( pattern, STRING( ent->v.targetname ) ) )
break;
}
}
ent = EDICT_NUM( i );
if( !SV_IsValidEdict( ent ) )
return NULL;
return ent;
}
/*
===============
SV_EntList_f
Print list of entities to client
===============
*/
static qboolean SV_EntList_f( sv_client_t *cl )
{
vec3_t borigin;
edict_t *ent = NULL;
int i;
for( i = 0; i < svgame.numEntities; i++ )
{
ent = EDICT_NUM( i );
if( !SV_IsValidEdict( ent ))
continue;
// filter by string
if( Cmd_Argc() > 1 )
{
if( !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.classname ) ) && !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.targetname ) ) )
continue;
}
VectorAdd( ent->v.absmin, ent->v.absmax, borigin );
VectorScale( borigin, 0.5, borigin );
SV_ClientPrintf( cl, "%5i origin: %.f %.f %.f", i, ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] );
SV_ClientPrintf( cl, "%5i borigin: %.f %.f %.f", i, borigin[0], borigin[1], borigin[2] );
if( ent->v.classname )
SV_ClientPrintf( cl, ", class: %s", STRING( ent->v.classname ));
if( ent->v.globalname )
SV_ClientPrintf( cl, ", global: %s", STRING( ent->v.globalname ));
if( ent->v.targetname )
SV_ClientPrintf( cl, ", name: %s", STRING( ent->v.targetname ));
if( ent->v.target )
SV_ClientPrintf( cl, ", target: %s", STRING( ent->v.target ));
if( ent->v.model )
SV_ClientPrintf( cl, ", model: %s", STRING( ent->v.model ));
SV_ClientPrintf( cl, "\n" );
}
return true;
}
/*
===============
SV_EntInfo_f
Print specified entity information to client
===============
*/
static qboolean SV_EntInfo_f( sv_client_t *cl )
{
edict_t *ent = NULL;
vec3_t borigin;
if( Cmd_Argc() != 2 )
{
SV_ClientPrintf( cl, "Use ent_info <index|name|inst>\n" );
return false;
}
ent = SV_EntFindSingle( cl, Cmd_Argv( 1 ) );
if( !SV_IsValidEdict( ent ))
return false;
VectorAdd( ent->v.absmin, ent->v.absmax, borigin );
VectorScale( borigin, 0.5, borigin );
SV_ClientPrintf( cl, "origin: %.f %.f %.f\n", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] );
SV_ClientPrintf( cl, "angles: %.f %.f %.f\n", ent->v.angles[0], ent->v.angles[1], ent->v.angles[2] );
SV_ClientPrintf( cl, "borigin: %.f %.f %.f\n", borigin[0], borigin[1], borigin[2] );
if( ent->v.classname )
SV_ClientPrintf( cl, "class: %s\n", STRING( ent->v.classname ));
if( ent->v.globalname )
SV_ClientPrintf( cl, "global: %s\n", STRING( ent->v.globalname ));
if( ent->v.targetname )
SV_ClientPrintf( cl, "name: %s\n", STRING( ent->v.targetname ));
if( ent->v.target )
SV_ClientPrintf( cl, "target: %s\n", STRING( ent->v.target ));
if( ent->v.model )
SV_ClientPrintf( cl, "model: %s\n", STRING( ent->v.model ));
SV_ClientPrintf( cl, "health: %.f\n", ent->v.health );
if( ent->v.gravity != 1.0f )
SV_ClientPrintf( cl, "gravity: %.2f\n", ent->v.gravity );
SV_ClientPrintf( cl, "movetype: %d\n", ent->v.movetype );
SV_ClientPrintf( cl, "rendermode: %d\n", ent->v.rendermode );
SV_ClientPrintf( cl, "renderfx: %d\n", ent->v.renderfx );
SV_ClientPrintf( cl, "renderamt: %f\n", ent->v.renderamt );
SV_ClientPrintf( cl, "rendercolor: %f %f %f\n", ent->v.rendercolor[0], ent->v.rendercolor[1], ent->v.rendercolor[2] );
SV_ClientPrintf( cl, "maxspeed: %f\n", ent->v.maxspeed );
if( ent->v.solid )
SV_ClientPrintf( cl, "solid: %d\n", ent->v.solid );
SV_ClientPrintf( cl, "flags: 0x%x\n", ent->v.flags );
SV_ClientPrintf( cl, "spawnflags: 0x%x\n", ent->v.spawnflags );
return true;
}
/*
===============
SV_EntFire_f
Perform some actions
===============
*/
static qboolean SV_EntFire_f( sv_client_t *cl )
{
edict_t *ent = NULL;
int i = 1, count = 0;
qboolean single; // true if user specified something that match single entity
if( Cmd_Argc() < 3 )
{
SV_ClientPrintf( cl, "Use ent_fire <index||pattern> <command> [<values>]\n"
"Use ent_fire 0 help to get command list\n" );
return false;
}
if( ( single = Q_isdigit( Cmd_Argv( 1 ) ) ) )
{
i = Q_atoi( Cmd_Argv( 1 ) );
if( i < 0 || i >= svgame.numEntities )
return false;
ent = EDICT_NUM( i );
}
else if( ( single = !Q_stricmp( Cmd_Argv( 1 ), "!cross" ) ) )
{
ent = SV_GetCrossEnt( cl->edict );
if (!SV_IsValidEdict(ent))
return false;
i = NUM_FOR_EDICT( ent );
}
else if( ( single = ( Cmd_Argv( 1 )[0] == '!') ) ) // check for correct instance with !(num)_(serial)
{
const char *cmd = Cmd_Argv( 1 ) + 1;
i = Q_atoi( cmd );
while( Q_isdigit( cmd )) cmd++;
if( *cmd++ != '_' )
return false;
if( i < 0 || i >= svgame.numEntities )
return false;
ent = EDICT_NUM( i );
if( ent->serialnumber != Q_atoi( cmd ) )
return false;
}
else
{
i = svgame.globals->maxClients + 1;
}
for( ; ( i < svgame.numEntities ) && ( count < sv_enttools_maxfire.value ); i++ )
{
ent = EDICT_NUM( i );
if( !SV_IsValidEdict( ent ))
{
// SV_ClientPrintf( cl, PRINT_LOW, "Got invalid entity\n" );
if( single )
break;
continue;
}
// if user specified not a number, try find such entity
if( !single )
{
if( !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.targetname ) ) && !Q_stricmpext( Cmd_Argv( 1 ), STRING( ent->v.classname ) ))
continue;
}
SV_ClientPrintf( cl, "entity %i\n", i );
count++;
if( !Q_stricmp( Cmd_Argv( 2 ), "health" ) )
ent->v.health = Q_atoi( Cmd_Argv ( 3 ) );
else if( !Q_stricmp( Cmd_Argv( 2 ), "gravity" ) )
ent->v.gravity = Q_atof( Cmd_Argv ( 3 ) );
else if( !Q_stricmp( Cmd_Argv( 2 ), "movetype" ) )
ent->v.movetype = Q_atoi( Cmd_Argv ( 3 ) );
else if( !Q_stricmp( Cmd_Argv( 2 ), "solid" ) )
ent->v.solid = Q_atoi( Cmd_Argv ( 3 ) );
else if( !Q_stricmp( Cmd_Argv( 2 ), "rename" ) )
ent->v.targetname = ALLOC_STRING( Cmd_Argv ( 3 ) );
else if( !Q_stricmp( Cmd_Argv( 2 ), "settarget" ) )
ent->v.target = ALLOC_STRING( Cmd_Argv ( 3 ) );
else if( !Q_stricmp( Cmd_Argv( 2 ), "setmodel" ) )
SV_SetModel( ent, Cmd_Argv( 3 ) );
else if( !Q_stricmp( Cmd_Argv( 2 ), "set" ) )
{
string keyname;
string value;
KeyValueData pkvd;
if( Cmd_Argc() != 5 )
return false;
pkvd.szClassName = (char*)STRING( ent->v.classname );
Q_strncpy( keyname, Cmd_Argv( 3 ), sizeof( keyname ));
Q_strncpy( value, Cmd_Argv( 4 ), sizeof( value ));
pkvd.szKeyName = keyname;
pkvd.szValue = value;
pkvd.fHandled = false;
svgame.dllFuncs.pfnKeyValue( ent, &pkvd );
if( pkvd.fHandled )
SV_ClientPrintf( cl, "value set successfully!\n" );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "touch" ) )
{
if( Cmd_Argc() == 4 )
{
edict_t *other = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );
if( other && other->pvPrivateData )
svgame.dllFuncs.pfnTouch( ent, other );
}
else
svgame.dllFuncs.pfnTouch( ent, cl->edict );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "use" ) )
{
if( Cmd_Argc() == 4 )
{
edict_t *other = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );
if( other && other->pvPrivateData )
svgame.dllFuncs.pfnUse( ent, other );
}
else
svgame.dllFuncs.pfnUse( ent, cl->edict );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "movehere" ) )
{
ent->v.origin[2] = cl->edict->v.origin[2] + 25;
ent->v.origin[1] = cl->edict->v.origin[1] + 100 * sin( DEG2RAD( cl->edict->v.angles[1] ) );
ent->v.origin[0] = cl->edict->v.origin[0] + 100 * cos( DEG2RAD( cl->edict->v.angles[1] ) );
SV_LinkEdict( ent, true );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "drop2floor" ) )
{
pfnDropToFloor( ent );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "moveup" ) )
{
float dist = 25;
if( Cmd_Argc() >= 4 )
dist = Q_atof( Cmd_Argv( 3 ) );
ent->v.origin[2] += dist;
if( Cmd_Argc() >= 5 )
{
dist = Q_atof( Cmd_Argv( 4 ) );
ent->v.origin[0] += dist * cos( DEG2RAD( cl->edict->v.angles[1] ) );
ent->v.origin[1] += dist * sin( DEG2RAD( cl->edict->v.angles[1] ) );
}
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeowner" ) )
{
if( Cmd_Argc() == 4 )
ent->v.owner = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );
else
ent->v.owner = cl->edict;
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeenemy" ) )
{
if( Cmd_Argc() == 4 )
ent->v.enemy = SV_EntFindSingle( cl, Cmd_Argv( 3 ) );
else
ent->v.enemy = cl->edict;
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "becomeaiment" ) )
{
if( Cmd_Argc() == 4 )
ent->v.aiment= SV_EntFindSingle( cl, Cmd_Argv( 3 ) );
else
ent->v.aiment = cl->edict;
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "hullmin" ) )
{
if( Cmd_Argc() != 6 )
return false;
ent->v.mins[0] = Q_atof( Cmd_Argv( 3 ) );
ent->v.mins[1] = Q_atof( Cmd_Argv( 4 ) );
ent->v.mins[2] = Q_atof( Cmd_Argv( 5 ) );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "hullmax" ) )
{
if( Cmd_Argc() != 6 )
return false;
ent->v.maxs[0] = Q_atof( Cmd_Argv( 3 ) );
ent->v.maxs[1] = Q_atof( Cmd_Argv( 4 ) );
ent->v.maxs[2] = Q_atof( Cmd_Argv( 5 ) );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "rendercolor" ) )
{
if( Cmd_Argc() != 6 )
return false;
ent->v.rendercolor[0] = Q_atof( Cmd_Argv( 3 ) );
ent->v.rendercolor[1] = Q_atof( Cmd_Argv( 4 ) );
ent->v.rendercolor[2] = Q_atof( Cmd_Argv( 5 ) );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "renderamt" ) )
{
ent->v.renderamt = Q_atof( Cmd_Argv( 3 ) );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "renderfx" ) )
{
ent->v.renderfx = Q_atoi( Cmd_Argv( 3 ) );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "rendermode" ) )
{
ent->v.rendermode = Q_atoi( Cmd_Argv( 3 ) );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "angles" ) )
{
ent->v.angles[0] = Q_atof( Cmd_Argv( 3 ) );
ent->v.angles[1] = Q_atof( Cmd_Argv( 4 ) );
ent->v.angles[2] = Q_atof( Cmd_Argv( 5 ) );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "setflag" ) )
{
ent->v.flags |= 1U << Q_atoi( Cmd_Argv ( 3 ) );
SV_ClientPrintf( cl, "flags set to 0x%x\n", ent->v.flags );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "clearflag" ) )
{
ent->v.flags &= ~( 1U << Q_atoi( Cmd_Argv ( 3 ) ) );
SV_ClientPrintf( cl, "flags set to 0x%x\n", ent->v.flags );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "setspawnflag" ) )
{
ent->v.spawnflags |= 1U << Q_atoi( Cmd_Argv ( 3 ) );
SV_ClientPrintf( cl, "spawnflags set to 0x%x\n", ent->v.spawnflags );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "clearspawnflag" ) )
{
ent->v.spawnflags &= ~( 1U << Q_atoi( Cmd_Argv ( 3 ) ) );
SV_ClientPrintf( cl, "spawnflags set to 0x%x\n", ent->v.flags );
}
else if( !Q_stricmp( Cmd_Argv( 2 ), "help" ) )
{
SV_ClientPrintf( cl, "Available commands:\n"
"Set fields:\n"
" (Only set entity field, does not call any functions)\n"
" health\n"
" gravity\n"
" movetype\n"
" solid\n"
" rendermode\n"
" rendercolor (vector)\n"
" renderfx\n"
" renderamt\n"
" hullmin (vector)\n"
" hullmax (vector)\n"
"Actions\n"
" rename: set entity targetname\n"
" settarget: set entity target (only targetnames)\n"
" setmodel: set entity model\n"
" set: set <key> <value> by server library\n"
" See game FGD to get list.\n"
" command takes two arguments\n"
" touch: touch entity by current player.\n"
" use: use entity by current player.\n"
" movehere: place entity in player fov.\n"
" drop2floor: place entity to nearest floor surface\n"
" moveup: move entity to 25 units up\n"
"Flags:\n"
" (Set/clear specified flag bit, arg is bit number)\n"
" setflag\n"
" clearflag\n"
" setspawnflag\n"
" clearspawnflag\n"
);
return true;
}
else
{
SV_ClientPrintf( cl, "Unknown command %s!\nUse \"ent_fire 0 help\" to list commands.\n", Cmd_Argv( 2 ) );
return false;
}
if( single )
break;
}
return true;
}
/*
===============
SV_EntSendVars
===============
*/
static void SV_EntSendVars( sv_client_t *cl, edict_t *ent )
{
if( !ent )
return;
MSG_WriteByte( &cl->netchan.message, svc_stufftext );
MSG_WriteString( &cl->netchan.message, va( "set ent_last_name \"%s\"\n", STRING( ent->v.targetname ) ));
MSG_WriteByte( &cl->netchan.message, svc_stufftext );
MSG_WriteString( &cl->netchan.message, va( "set ent_last_num %i\n", NUM_FOR_EDICT( ent ) ));
MSG_WriteByte( &cl->netchan.message, svc_stufftext );
MSG_WriteString( &cl->netchan.message, va( "set ent_last_inst !%i_%i\n", NUM_FOR_EDICT( ent ), ent->serialnumber ));
MSG_WriteByte( &cl->netchan.message, svc_stufftext );
MSG_WriteString( &cl->netchan.message, va( "set ent_last_origin \"%f %f %f\"\n", ent->v.origin[0], ent->v.origin[1], ent->v.origin[2]));
MSG_WriteByte( &cl->netchan.message, svc_stufftext );
MSG_WriteString( &cl->netchan.message, va( "set ent_last_class \"%s\"\n", STRING( ent->v.classname )));
MSG_WriteByte( &cl->netchan.message, svc_stufftext );
MSG_WriteString( &cl->netchan.message, "ent_getvars_cb\n" ); // why do we need this?
}
/*
===============
SV_EntCreate_f
Create new entity with specified name.
===============
*/
static qboolean SV_EntCreate_f( sv_client_t *cl )
{
edict_t *ent = NULL;
int i = 0;
string_t classname;
if( Cmd_Argc() < 2 )
{
SV_ClientPrintf( cl, "Use ent_create <classname> <key1> <value1> <key2> <value2> ...\n" );
return false;
}
classname = ALLOC_STRING( Cmd_Argv( 1 ) );
ent = SV_AllocPrivateData( 0, classname );
// Xash3D extension
if( !ent && svgame.physFuncs.SV_CreateEntity )
{
ent = SV_AllocEdict();
ent->v.classname = classname;
if( svgame.physFuncs.SV_CreateEntity( ent, (char*)STRING( classname ) ) == -1 )
{
if( ent && !ent->free )
SV_FreeEdict( ent );
ent = NULL;
}
}
// XashXT does not implement SV_CreateEntity, use saverestore export
if( !ent && svgame.physFuncs.pfnCreateEntitiesInRestoreList )
{
SAVERESTOREDATA data = { 0 };
ENTITYTABLE table = { 0 };
data.tableCount = 1;
data.pTable = &table;
table.classname = classname;
table.id = -1;
table.size = 1;
svgame.physFuncs.pfnCreateEntitiesInRestoreList( &data, 0, false );
ent = table.pent;
}
if( !ent )
{
SV_ClientPrintf( cl, "Invalid entity!\n" );
return false;
}
// choose default origin
ent->v.origin[2] = cl->edict->v.origin[2] + 25;
ent->v.origin[1] = cl->edict->v.origin[1] + 100 * sin( DEG2RAD( cl->edict->v.angles[1] ) );
ent->v.origin[0] = cl->edict->v.origin[0] + 100 * cos( DEG2RAD( cl->edict->v.angles[1] ) );
SV_LinkEdict( ent, false );
// apply keyvalues if supported
if( svgame.dllFuncs.pfnKeyValue )
{
for( i = 2; i < Cmd_Argc() - 1; i++ )
{
string keyname;
string value;
KeyValueData pkvd;
// allow split keyvalues to prespawn and postspawn
if( !Q_strcmp( Cmd_Argv( i ), "|" ) )
break;
Q_strncpy( keyname, Cmd_Argv( i++ ), sizeof( keyname ));
Q_strncpy( value, Cmd_Argv( i ), sizeof( value ));
pkvd.fHandled = false;
pkvd.szClassName = (char*)STRING( ent->v.classname );
pkvd.szKeyName = keyname;
pkvd.szValue = value;
svgame.dllFuncs.pfnKeyValue( ent, &pkvd );
if( pkvd.fHandled )
SV_ClientPrintf( cl, "value \"%s\" set to \"%s\"!\n", pkvd.szKeyName, pkvd.szValue );
}
}
// set default targetname
if( !ent->v.targetname )
{
string newname, clientname;
int j;
for( j = 0; j < sizeof( cl->name ); j++ )
{
char c = Q_tolower( cl->name[j] );
if( c < 'a' || c > 'z' )
c = '_';
if( !cl->name[j] )
{
clientname[j] = 0;
break;
}
clientname[j] = c;
}
// generate name based on nick name and index
Q_snprintf( newname, sizeof( newname ), "%s_%i_e%i", clientname, cl->userid, NUM_FOR_EDICT( ent ));
// i know, it may break strict aliasing rules
// but we will not lose anything in this case.
Q_strnlwr( newname, newname, sizeof( newname ));
ent->v.targetname = ALLOC_STRING( newname );
SV_EntSendVars( cl, ent );
}
SV_ClientPrintf( cl, "Created %i: %s, targetname %s\n", NUM_FOR_EDICT( ent ), Cmd_Argv( 1 ), STRING( ent->v.targetname ) );
if( svgame.dllFuncs.pfnSpawn )
svgame.dllFuncs.pfnSpawn( ent );
// now drop entity to floor.
pfnDropToFloor( ent );
// force think. Otherwise given weapon may crash server if player touch it before.
svgame.dllFuncs.pfnThink( ent );
pfnDropToFloor( ent );
// apply postspawn keyvales if supported
if( svgame.dllFuncs.pfnKeyValue )
{
for( i = i + 1; i < Cmd_Argc() - 1; i++ )
{
string keyname;
string value;
KeyValueData pkvd;
Q_strncpy( keyname, Cmd_Argv( i++ ), sizeof( keyname ));
Q_strncpy( value, Cmd_Argv( i ), sizeof( value ));
pkvd.fHandled = false;
pkvd.szClassName = (char*)STRING( ent->v.classname );
pkvd.szKeyName = keyname;
pkvd.szValue = value;
svgame.dllFuncs.pfnKeyValue( ent, &pkvd );
if( pkvd.fHandled )
SV_ClientPrintf( cl, "value \"%s\" set to \"%s\"!\n", pkvd.szKeyName, pkvd.szValue );
}
}
return true;
}
static qboolean SV_EntGetVars_f( sv_client_t *cl )
{
edict_t *ent = NULL;
if( Cmd_Argc() != 2 )
{
SV_ClientPrintf( cl, "Use ent_getvars <index|name|inst>\n" );
return false;
}
ent = SV_EntFindSingle( cl, Cmd_Argv( 1 ) );
if( Cmd_Argc() )
{
if( !SV_IsValidEdict( ent ))
return false;
}
SV_EntSendVars( cl, ent );
return true;
}
ucmd_t ucmds[] =
{
@ -2140,6 +2914,16 @@ ucmd_t ucmds[] =
{ NULL, NULL }
};
ucmd_t enttoolscmds[] =
{
{ "ent_list", SV_EntList_f },
{ "ent_info", SV_EntInfo_f },
{ "ent_fire", SV_EntFire_f },
{ "ent_create", SV_EntCreate_f },
{ "ent_getvars", SV_EntGetVars_f },
{ NULL, NULL }
};
/*
==================
SV_ExecuteUserCommand
@ -2162,6 +2946,24 @@ void SV_ExecuteClientCommand( sv_client_t *cl, const char *s )
}
}
if( !u->name && sv_enttools_enable.value > 0.0f && !sv.background )
{
for( u = enttoolscmds; u->name; u++ )
{
if( !Q_strcmp( Cmd_Argv( 0 ), u->name ))
{
Con_Reportf( "enttools->%s(): %s\n", u->name, s );
Log_Printf( "\"%s<%i><%s><>\" performed: %s\n", Info_ValueForKey( cl->userinfo, "name" ),
cl->userid, SV_GetClientIDString( cl ), NET_AdrToString( cl->netchan.remote_address ), s );
if( u->func )
u->func( cl );
break;
}
}
}
if( !u->name && sv.state == ss_active )
{
// custom client commands

View File

@ -257,6 +257,63 @@ void SV_CopyTraceToGlobal( trace_t *trace )
else svgame.globals->trace_ent = svgame.edicts;
}
/*
==============
SV_SetModel
==============
*/
void SV_SetModel( edict_t *ent, const char *modelname )
{
char name[MAX_QPATH];
qboolean found = false;
model_t *mod;
int i = 1;
if( !SV_IsValidEdict( ent ))
{
Con_Printf( S_WARN "SV_SetModel: invalid entity %s\n", SV_ClassName( ent ));
return;
}
if( !modelname || modelname[0] <= ' ' )
{
Con_Printf( S_WARN "SV_SetModel: null name\n" );
return;
}
if( *modelname == '\\' || *modelname == '/' )
modelname++;
Q_strncpy( name, modelname, sizeof( name ));
COM_FixSlashes( name );
i = SV_ModelIndex( name );
if( i == 0 )
{
if( sv.state == ss_active )
Con_Printf( S_ERROR "SV_SetModel: failed to set model %s: world model cannot be changed\n", name );
return;
}
if( COM_CheckString( name ))
{
ent->v.model = MAKE_STRING( sv.model_precache[i] );
ent->v.modelindex = i;
mod = sv.models[i];
}
else
{
// model will be cleared
ent->v.model = ent->v.modelindex = 0;
mod = NULL;
}
// set the model size
if( mod && mod->type != mod_studio )
SV_SetMinMaxSize( ent, mod->mins, mod->maxs, true );
else SV_SetMinMaxSize( ent, vec3_origin, vec3_origin, true );
}
/*
=============
SV_ConvertTrace
@ -1319,61 +1376,7 @@ pfnSetModel
*/
void GAME_EXPORT pfnSetModel( edict_t *e, const char *m )
{
char name[MAX_QPATH];
qboolean found = false;
model_t *mod;
int i = 1;
if( !SV_IsValidEdict( e ))
return;
if( *m == '\\' || *m == '/' ) m++;
Q_strncpy( name, m, sizeof( name ));
COM_FixSlashes( name );
if( COM_CheckString( name ))
{
// check to see if model was properly precached
for( ; i < MAX_MODELS && sv.model_precache[i][0]; i++ )
{
if( !Q_stricmp( sv.model_precache[i], name ))
{
found = true;
break;
}
}
if( !found )
{
Con_Printf( S_ERROR "Failed to set model %s: was not precached\n", name );
return;
}
}
if( e == svgame.edicts )
{
if( sv.state == ss_active )
Con_Printf( S_ERROR "Failed to set model %s: world model cannot be changed\n", name );
return;
}
if( COM_CheckString( name ))
{
e->v.model = MAKE_STRING( sv.model_precache[i] );
e->v.modelindex = i;
mod = sv.models[i];
}
else
{
// model will be cleared
e->v.model = e->v.modelindex = 0;
mod = NULL;
}
// set the model size
if( mod && mod->type != mod_studio )
SV_SetMinMaxSize( e, mod->mins, mod->maxs, true );
else SV_SetMinMaxSize( e, vec3_origin, vec3_origin, true );
SV_SetModel( e, m );
}
/*

View File

@ -116,6 +116,10 @@ CVAR_DEFINE_AUTO( violence_agibs, "1", 0, "show alien gib entities" );
CVAR_DEFINE_AUTO( sv_voiceenable, "1", FCVAR_ARCHIVE|FCVAR_SERVER, "enable voice support" );
CVAR_DEFINE_AUTO( sv_voicequality, "3", FCVAR_ARCHIVE|FCVAR_SERVER, "voice chat quality level, from 0 to 5, higher is better" );
// enttools
CVAR_DEFINE_AUTO( sv_enttools_enable, "0", FCVAR_ARCHIVE|FCVAR_PROTECTED, "enable powerful and dangerous entity tools" );
CVAR_DEFINE_AUTO( sv_enttools_maxfire, "5", FCVAR_ARCHIVE|FCVAR_PROTECTED, "limit ent_fire actions count to prevent flooding" );
convar_t *sv_novis; // disable server culling entities by vis
convar_t *sv_pausable;
convar_t *timeout; // seconds without any message
@ -981,6 +985,8 @@ void SV_Init( void )
Cvar_RegisterVariable( &sv_voiceenable );
Cvar_RegisterVariable( &sv_voicequality );
Cvar_RegisterVariable( &sv_trace_messages );
Cvar_RegisterVariable( &sv_enttools_enable );
Cvar_RegisterVariable( &sv_enttools_maxfire );
sv_allow_joystick = Cvar_Get( "sv_allow_joystick", "1", FCVAR_ARCHIVE, "allow connect with joystick enabled" );
sv_allow_mouse = Cvar_Get( "sv_allow_mouse", "1", FCVAR_ARCHIVE, "allow connect with mouse" );