diff --git a/engine/server/server.h b/engine/server/server.h index c9dbc588..90b90b8a 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -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 // diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c index cbf5d912..e336f0f3 100644 --- a/engine/server/sv_client.c +++ b/engine/server/sv_client.c @@ -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 \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 []\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 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 ...\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 \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 diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 61dd4fc8..ffa37f5d 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -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 ); } /* diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 1122905b..6a73724c 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -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" );