From 0ec47fd7d8c7ffbf962b3e5105f26bc677a639a3 Mon Sep 17 00:00:00 2001 From: /dev/humancontroller Date: Sat, 16 Sep 2017 21:05:30 +0200 Subject: implement !control --- src/game/g_active.c | 10 +- src/game/g_admin.c | 163 ++++++++++++++++++++++++---- src/game/g_admin.h | 1 + src/game/g_client.c | 281 +++++++++++++++++++++++++++++++++++++++++++++--- src/game/g_cmds.c | 67 ++++++++++-- src/game/g_local.h | 10 +- src/game/g_main.c | 8 +- src/game/g_public.h | 5 +- src/game/g_svcmds.c | 46 ++++++++ src/game/g_syscalls.asm | 3 + src/game/g_syscalls.c | 16 +++ src/game/g_utils.c | 15 +++ 12 files changed, 579 insertions(+), 46 deletions(-) diff --git a/src/game/g_active.c b/src/game/g_active.c index 5bee472..72e070e 100644 --- a/src/game/g_active.c +++ b/src/game/g_active.c @@ -2050,6 +2050,9 @@ void ClientThink( int clientNum ) gentity_t *ent; ent = g_entities + clientNum; + if (ent->client->pers.control > 0) + ent = &g_entities[ent->client->pers.control - 1]; + trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); // mark the time we got info, so we can display the @@ -2063,7 +2066,12 @@ void ClientThink( int clientNum ) void G_RunClient( gentity_t *ent ) { - if( !g_synchronousClients.integer ) + if (ent->client->pers.control > 0 + || (ent->client->pers.isPlaceholder && ent->client->pers.control >= 0)) + { + ent->client->lastCmdTime = level.time; + } + else if (!g_synchronousClients.integer) return; ent->client->pers.cmd.serverTime = level.time; diff --git a/src/game/g_admin.c b/src/game/g_admin.c index 8a9d130..6208705 100644 --- a/src/game/g_admin.c +++ b/src/game/g_admin.c @@ -112,6 +112,11 @@ g_admin_cmd_t g_admin_cmds[ ] = "(-AHS) [^3message^7]" }, + {"control", G_admin_control, "control", + "take control of a placeholder client", + "[^3name|slot#^7] ..." + }, + {"demo", G_admin_demo, "demo", "turn admin chat off for the caller so it does not appear in demos. " "this is a toggle use !demo again to turn warnings back on", @@ -1808,6 +1813,9 @@ qboolean G_admin_cmd_check( gentity_t *ent, qboolean say ) { return qfalse; } + + if (!Q_stricmp(cmd, "control") && ent && ent->client->pers.isPlaceholder) + ent = &g_entities[-ent->client->pers.control - 1]; // Flood limit. If they're talking too fast, determine that and return. if( g_floodMinTime.integer ) @@ -2035,6 +2043,8 @@ void G_admin_namelog_update( gclient_t *client, qboolean disconnect ) Q_strncpyz( namelog->name[ 0 ], client->pers.netname, sizeof( namelog->name[ 0 ] ) ); namelog->slot = ( disconnect ) ? -1 : clientNum; + if (client->pers.isPlaceholder) + namelog->smj.ratingTime = 0x70000000; schachtmeisterProcess( namelog ); g_admin_namelog[ i ] = namelog; } @@ -3185,10 +3195,13 @@ qboolean G_admin_kick( gentity_t *ent, int skiparg ) vic->client->pers.karma -= 5000; - trap_SendServerCommand( pids[ 0 ], - va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\n%s\"", - ( ent ) ? va( "admin:\n%s", G_admin_adminPrintName( ent ) ) : "admin\nconsole", - ( *reason ) ? reason : "kicked by admin", notice ) ); + if (!vic->client->pers.isPlaceholder) + { + trap_SendServerCommand( pids[ 0 ], + va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\n%s\"", + ( ent ) ? va( "admin:\n%s", G_admin_adminPrintName( ent ) ) : "admin\nconsole", + ( *reason ) ? reason : "kicked by admin", notice ) ); + } G_LogPrintf( "kick: %i %i [%s] (%s) %s^7 %s^7\n", vic->client->ps.clientNum, @@ -3412,12 +3425,15 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg ) if( g_karma.integer ) vic->client->pers.karma -= 5000; - trap_SendServerCommand( g_admin_namelog[ logmatch ]->slot, - va( "disconnect \"You have been banned.\n" - "admin:\n%s^7\nduration:\n%s\nreason:\n%s\n%s\"", - ( ent ) ? G_admin_adminPrintName( ent ) : "console", - duration, - ( *reason ) ? reason : "banned by admin", notice ) ); + if (!vic->client->pers.isPlaceholder) + { + trap_SendServerCommand( g_admin_namelog[ logmatch ]->slot, + va( "disconnect \"You have been banned.\n" + "admin:\n%s^7\nduration:\n%s\nreason:\n%s\n%s\"", + ( ent ) ? G_admin_adminPrintName( ent ) : "console", + duration, + ( *reason ) ? reason : "banned by admin", notice ) ); + } trap_DropClient( g_admin_namelog[ logmatch ]->slot, va( "banned by %s^7, duration: %s, reason: %s", @@ -3483,8 +3499,9 @@ static void admin_autobahn(gentity_t *ent, int rating) if (rating > g_schachtmeisterAutobahnThreshold.integer) return; - trap_SendServerCommand(ent - g_entities, va("disconnect \"%s\"\n", - g_schachtmeisterAutobahnMessage.string)); + if (!ent->client->pers.isPlaceholder) + trap_SendServerCommand(ent - g_entities, va("disconnect \"%s\"\n", + g_schachtmeisterAutobahnMessage.string)); trap_DropClient(ent - g_entities, "dropped by the Autobahn"); } @@ -7989,13 +8006,18 @@ qboolean G_admin_drop( gentity_t *ent, int skiparg ) return qfalse; } - // victim's message - if( G_SayArgc() > 2 + skiparg ) - trap_SendServerCommand( pids[ 0 ], - va( "disconnect \"You have been dropped.\n%s^7\n\"", - G_SayConcatArgs( 2 + skiparg ) ) ); - else - trap_SendServerCommand( pids[ 0 ], va( "disconnect" ) ); + if (!g_clients[pids[0]].pers.isPlaceholder) + { + // victim's message + if( G_SayArgc() > 2 + skiparg ) + { + trap_SendServerCommand( pids[ 0 ], + va( "disconnect \"You have been dropped.\n%s^7\n\"", + G_SayConcatArgs( 2 + skiparg ) ) ); + } + else + trap_SendServerCommand( pids[ 0 ], va( "disconnect" ) ); + } // server message trap_DropClient( pids[ 0 ], va( "disconnected" ) ); @@ -8042,6 +8064,109 @@ qboolean G_admin_bubble( gentity_t *ent, int skiparg ) return qtrue; } +qboolean G_admin_control(gentity_t *ent, int skiparg) +{ + if (!ent) + { + ADMP("^3!control: ^7the console cannot be used to take control\n"); + return qfalse; + } + + if (G_SayArgc() <= 1 + skiparg) + { + ADMP("^3!control: ^7usage: !control [name|slot#] ...\n"); + return qfalse; + } + + int n = ent - g_entities; + gclient_t *cl = &g_clients[n]; + + byte sel[MAX_CLIENTS]; + int nsel = 0; + int ix = -1; + qboolean phc_matched = qfalse; + + for (int a = 1 + skiparg; a < G_SayArgc(); ++a) + { + char arg[MAX_NAME_LENGTH]; + G_SayArgv(a, arg, sizeof(arg)); + + int sel2[MAX_CLIENTS]; + int nsel2 = G_ClientNumbersFromString(arg, sel2); + + for (int j = 0; j < nsel2; ++j) + { + int k = sel2[j]; + clientPersistant_t *p = &g_clients[k].pers; + + if (k != n) + { + if (!p->isPlaceholder) + continue; + phc_matched = qtrue; + if (p->control < 0 && (-p->control - 1) != n) + continue; + } + + for (int i = 0; i < nsel; ++i) + { + if (sel[i] == k) + goto already_included; + } + + if ((!cl->pers.control && k == n) || k == cl->pers.control - 1) + ix = nsel; + sel[nsel++] = k; + + already_included:; + } + } + + if (nsel == 0) + { + if (phc_matched) + ADMP("^3!control: ^7all matching placeholder clients are currently controlled by others\n"); + else + ADMP("^3!control: ^7no placeholder clients (nor self) matched\n"); + return qfalse; + } + + int k = sel[(ix + 1) % nsel]; + + if ((!cl->pers.control && k == n) || k == cl->pers.control - 1) + return qfalse; + + if (cl->pers.control) + { + const gclient_t *c = &g_clients[cl->pers.control - 1]; + + g_clients[cl->pers.control - 1].pers.control = 0; + cl->pers.control = 0; + + cl->pers.cmd = c->pers.cmd; + for (int i = 0; i < 3; ++i) + cl->ps.delta_angles[i] = ANGLE2SHORT(cl->ps.viewangles[i]) - cl->pers.cmd.angles[i]; + } + + gclient_t *c = &g_clients[k]; + + trap_set_client_view_entity(n, k); + + ADMP(va("^3!control: ^7took control of %s^7\n", c->pers.netname)); + + if (k == n) + return qtrue; + + c->pers.cmd = cl->pers.cmd; + for (int i = 0; i < 3; ++i) + c->ps.delta_angles[i] = ANGLE2SHORT(c->ps.viewangles[i]) - c->pers.cmd.angles[i]; + + cl->pers.control = 1 + k; + c->pers.control = -1 - n; + + return qfalse; +} + qboolean G_admin_buildlog( gentity_t *ent, int skiparg ) { #define LOG_DISPLAY_LENGTH 10 diff --git a/src/game/g_admin.h b/src/game/g_admin.h index ff2f767..3f768f3 100644 --- a/src/game/g_admin.h +++ b/src/game/g_admin.h @@ -269,6 +269,7 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg ); qboolean G_admin_permission( gentity_t *ent, const char *flag ); qboolean G_admin_permission_guid( const char *guid, const char *flag ); qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len ); +qboolean G_admin_control(gentity_t *ent, int skiparg); void G_admin_namelog_update( gclient_t *ent, qboolean disconnect ); void G_admin_IPA_judgement( const char *ipa, int rating, const char *comment ); void G_admin_maplog_result( char *flag ); diff --git a/src/game/g_client.c b/src/game/g_client.c index 3325289..78e1c4f 100644 --- a/src/game/g_client.c +++ b/src/game/g_client.c @@ -1395,7 +1395,7 @@ to the server machine, but qfalse on map changes and tournement restarts. ============ */ -const char *ClientConnect( int clientNum, qboolean firstTime ) +const char *ClientConnect( int clientNum, qboolean firstTime, qboolean isPlaceholder ) { char *value; gclient_t *client; @@ -1408,8 +1408,22 @@ const char *ClientConnect( int clientNum, qboolean firstTime ) ent = &g_entities[ clientNum ]; + if (ent->client && ent->client->pers.connected != CON_DISCONNECTED) + ClientDisconnect(clientNum); + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + if (isPlaceholder) + { + const char *invalidity = review_placeholder_client_userinfo(userinfo); + if (invalidity) + return invalidity; + trap_SetUserinfo(clientNum, userinfo); + + inject_placeholder_client(userinfo, clientNum, firstTime); + return NULL; + } + value = Info_ValueForKey( userinfo, "cl_guid" ); Q_strncpyz( guid, value, sizeof( guid ) ); @@ -1476,6 +1490,31 @@ const char *ClientConnect( int clientNum, qboolean firstTime ) strcmp( g_password.string, value ) != 0 ) return "Invalid password"; + schachtmeisterJudgement_t *smj = NULL; + + if (!(G_admin_permission_guid(guid, ADMF_NOAUTOBAHN) + || G_admin_permission_guid(guid, ADMF_IMMUNITY))) + { + extern g_admin_namelog_t *g_admin_namelog[128]; + for (i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[i]; ++i) + { + if (!Q_stricmp(g_admin_namelog[i]->ip, ip) + || !Q_stricmp(g_admin_namelog[i]->guid, guid)) + { + schachtmeisterJudgement_t *j = &g_admin_namelog[i]->smj; + if (j->ratingTime) + { + if (j->rating >= g_schachtmeisterClearThreshold.integer) + break; + else if (j->rating <= g_schachtmeisterAutobahnThreshold.integer) + return g_schachtmeisterAutobahnMessage.string; + smj = j; + } + break; + } + } + } + // they can connect ent->client = level.clients + clientNum; client = ent->client; @@ -1551,35 +1590,243 @@ const char *ClientConnect( int clientNum, qboolean firstTime ) G_admin_namelog_update( client, qfalse ); } + if (smj) + G_AdminsPrintf( "%s^7 (#%d) has rating %d\n", client->pers.netname, clientNum, smj->rating ); + // if this is after !restart keepteams or !restart switchteams, apply said selection if ( client->sess.restartTeam != PTE_NONE ) { G_ChangeTeam( ent, client->sess.restartTeam ); client->sess.restartTeam = PTE_NONE; } - if( !( G_admin_permission( ent, ADMF_NOAUTOBAHN ) || - G_admin_permission( ent, ADMF_IMMUNITY ) ) ) + return NULL; +} + +const char *review_placeholder_client_userinfo(char *ui) +{ { - extern g_admin_namelog_t *g_admin_namelog[ 128 ]; - for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) + const char *ipa = Info_ValueForKey(ui, "ip"); + unsigned o[4]; + + if (*ipa) { - if( !Q_stricmp( ip, g_admin_namelog[ i ]->ip ) || !Q_stricmp( guid, g_admin_namelog[ i ]->guid ) ) + for (int i = 0; ipa[i]; ++i) + { + if (!isdigit(ipa[i]) && ipa[i] != '.') + return "malformed IPv4 address"; + } + + if (sscanf(ipa, "%u.%u.%u.%u", &o[0], &o[1], &o[2], &o[3]) != 4 + || o[0] > 255 || o[1] > 255 || o[2] > 255 || o[3] > 255) + { + return "malformed IPv4 address"; + } + + if (o[0] == 0 || o[0] == 127 || o[0] == 10 || o[0] >= 224) + return "reserved IP address"; + + if (o[1] == 0 || o[1] == 255 + || o[2] == 0 || o[2] == 255 + || o[3] == 0 || o[3] == 255) + { + return "weird IP address"; + } + } + else + { + o[0] = 1 + rand() % 221; + if (o[0] == 127) + o[0] = 222; + else if (o[0] == 10) + o[0] = 223; + o[1] = 1 + rand() % 254; + o[2] = 1 + rand() % 254; + o[3] = 1 + rand() % 254; + } + + char ipa2[16]; + Com_sprintf(ipa2, sizeof(ipa2), "%u.%u.%u.%u", o[0], o[1], o[2], o[3]); + Info_SetValueForKey(ui, "ip", ipa2); + } + + { + const char *name = Info_ValueForKey(ui, "name"); + if (strlen(name) >= MAX_NAME_LENGTH) + return "overly long name"; + else if (!*name) + Info_SetValueForKey(ui, "name", "UnnamedPlayer"); + } + + if (!Info_Validate(ui)) + return "malformed userinfo"; + + return NULL; +} + +void inject_placeholder_client(const char *const ui, const int sl, qboolean first_time) +{ + { + char reason[MAX_STRING_CHARS]; + if (G_admin_ban_check(ui, reason, sizeof(reason))) + Com_Printf("inject_placeholder_client: warning: would be denied by the admin subsystem: %s\n", reason); + } + + const char *ipa = Info_ValueForKey(ui, "ip"); + + if (G_FilterPacket(ipa)) + Com_Printf("inject_placeholder_client: warning: would be denied by the filter subsystem\n"); + + if (g_maxGhosts.integer > 1) + { + const char *ipa = Info_ValueForKey(ui, "ip"); + int count = 0; + + for (int i = 0; i < level.maxclients; ++i) + { + const gclient_t *other = &g_clients[i]; + if (other->pers.connected >= CON_CONNECTING && !strcmp(ipa, other->pers.ip)) + ++count; + } + + if (count + 1 > g_maxGhosts.integer) + Com_Printf("inject_placeholder_client: warning: would be denied by the max-ghosts subsystem\n"); + } + + if (*g_password.string && Q_stricmp(g_password.string, "none") + && strcmp(Info_ValueForKey(ui, "password"), g_password.string)) + { + Com_Printf("inject_placeholder_client: warning: would be denied by the password subsystem\n"); + } + + const char *guid = Info_ValueForKey(ui, "cl_guid"); + + if (!(G_admin_permission_guid(guid, ADMF_NOAUTOBAHN) + || G_admin_permission_guid(guid, ADMF_IMMUNITY))) + { + extern g_admin_namelog_t *g_admin_namelog[128]; + for (int i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[i]; ++i) + { + if (!Q_stricmp(g_admin_namelog[i]->ip, ipa) + || !Q_stricmp(g_admin_namelog[i]->guid, guid)) { schachtmeisterJudgement_t *j = &g_admin_namelog[i]->smj; - if( j->ratingTime ) + if (j->ratingTime) { - if( j->rating >= g_schachtmeisterClearThreshold.integer ) + if (j->rating >= g_schachtmeisterClearThreshold.integer) break; - else if( j->rating <= g_schachtmeisterAutobahnThreshold.integer ) - return g_schachtmeisterAutobahnMessage.string; - G_AdminsPrintf( "%s^7 (#%d) has rating %d\n", ent->client->pers.netname, ent - g_entities, j->rating ); + else if (j->rating <= g_schachtmeisterAutobahnThreshold.integer) + Com_Printf("inject_placeholder_client: warning: would be denied by der Schachtmeister\n"); } break; } } } - - return NULL; + + gentity_t *ent = &g_entities[sl]; + gclient_t *cl = &g_clients[sl]; + clientPersistant_t *per = &cl->pers; + + memset(cl, 0, sizeof(*cl)); + + per->connected = CON_CONNECTING; + per->isPlaceholder = qtrue; + + per->firstConnect = qfalse; + + strcpy(per->ip, ipa); + Q_strncpyz(per->guid, *guid ? guid : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", sizeof(per->guid)); + + per->adminLevel = G_admin_level(ent); + + G_InitSessionData(cl, ui); + G_ReadSessionData(cl); + + ClientUserinfoChanged(sl, qfalse); + + G_admin_set_adminname(ent); + + if (g_decolourLogfiles.integer) + { + char decoloured[MAX_STRING_CHARS] = ""; + if (g_decolourLogfiles.integer == 1) + { + Com_sprintf(decoloured, sizeof(decoloured), " (\"%s^7\")", per->netname); + G_DecolorString(decoloured, decoloured); + G_LogPrintfColoured("PlaceholderClientConnect: %i [%s] (%s) \"%s^7\"%s\n", + sl, per->ip, per->guid, per->netname, decoloured); + } + else + { + G_LogPrintf("PlaceholderClientConnect: %i [%s] (%s) \"%s^7\"%s\n", + sl, per->ip, per->guid, per->netname, decoloured); + } + } + else + { + G_LogPrintf("PlaceholderClientConnect: %i [%s] (%s) \"%s^7\"\n", + sl, per->ip, per->guid, per->netname); + } + + if (per->adminLevel) + { + G_LogPrintf("PlaceholderClientAuth: %i [%s] \"%s^7\" authenticated to admin level %i using GUID %s (^7%s)\n", + sl, per->ip, per->netname, per->adminLevel, per->guid, per->adminName); + } + + if (cl->sess.invisible != qtrue) + { + if (first_time) + trap_SendServerCommand(-1, va("print \"%s" S_COLOR_WHITE " connected\n\"", per->netname)); + + CalculateRanks(); + G_admin_namelog_update(cl, qfalse); + } + + if (cl->sess.restartTeam != PTE_NONE) + { + G_ChangeTeam(ent, cl->sess.restartTeam); + cl->sess.restartTeam = PTE_NONE; + } + + if (ent->r.linked) + trap_UnlinkEntity(ent); + + G_InitGentity(ent); + ent->touch = 0; + ent->pain = 0; + ent->client = cl; + + per->connected = CON_CONNECTED; + per->enterTime = level.time; + per->teamState.state = TEAM_BEGIN; + per->classSelection = PCL_NONE; + + int flags = cl->ps.eFlags; + memset(&cl->ps, 0, sizeof(cl->ps)); + memset(&cl->pmext, 0, sizeof(cl->pmext)); + cl->ps.eFlags = flags; + + ClientSpawn(ent, NULL, NULL, NULL); + + if (cl->sess.invisible != qtrue) + { + trap_SendServerCommand(-1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", per->netname)); + + G_admin_namelog_update(cl, qfalse); + + G_admin_chat_sync(ent); + + G_admin_report_check(sl); + } + + if (G_admin_permission(ent, ADMF_NO_CHAT)) + { + per->muted = qtrue; + } + + G_LogPrintf("PlaceholderClientBegin: %i\n", sl); + + CalculateRanks(); } /* @@ -1958,7 +2205,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; - trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); + trap_GetUsercmd(client - level.clients, client->pers.control < 0 ? &g_clients[-client->pers.control - 1].pers.cmd : &client->pers.cmd); G_SetClientViewAngle( ent, spawn_angles ); if( !( client->sess.sessionTeam == TEAM_SPECTATOR ) ) @@ -2057,6 +2304,12 @@ void ClientDisconnect( int clientNum ) if( !ent->client ) return; + if (ent->client->pers.control) + { + g_clients[abs(ent->client->pers.control) - 1].pers.control = 0; + ent->client->pers.control = 0; + } + // look through the bhist and readjust it if the referenced ent has left for( ptr = level.buildHistory; ptr; ptr = ptr->next ) { diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index bcd51de..cd68354 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -994,31 +994,31 @@ void Cmd_Team_f( gentity_t *ent ) G_Say ================== */ -static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, const char *prefix ) +static qboolean G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, const char *prefix ) { qboolean ignore = qfalse; qboolean specAllChat = qfalse; if( !other ) - return; + return qfalse; if( !other->inuse ) - return; + return qfalse; if( !other->client ) - return; + return qfalse; if( other->client->pers.connected != CON_CONNECTED ) - return; + return qfalse; if( ( mode == SAY_TEAM || mode == SAY_ACTION_T ) && !OnSameTeam( ent, other ) ) { if( other->client->pers.teamSelection != PTE_NONE ) - return; + return qfalse; specAllChat = G_admin_permission( other, ADMF_SPEC_ALLCHAT ); if( !specAllChat ) - return; + return qfalse; // specs with ADMF_SPEC_ALLCHAT flag can see team chat } @@ -1026,11 +1026,11 @@ static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, cons if( mode == SAY_ADMINS && (!G_admin_permission( other, ADMF_ADMINCHAT) || other->client->pers.ignoreAdminWarnings || ( g_scrimMode.integer != 0 && !G_admin_permission( ent, ADMF_NOSCRIMRESTRICTION ) ) ) ) - return; + return qfalse; if( mode == SAY_HADMINS && (!G_admin_permission( other, ADMF_HIGHADMINCHAT) || other->client->pers.ignoreAdminWarnings ) ) - return; + return qfalse; if( BG_ClientListTest( &other->client->sess.ignoreList, ent-g_entities ) ) ignore = qtrue; @@ -1040,6 +1040,8 @@ static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, cons ( ignore ) ? "[skipnotify]" : "", ( specAllChat ) ? prefix : "", name, Q_COLOR_ESCAPE, color, message ) ); + + return qtrue; } #define EC "\x19" @@ -1203,6 +1205,13 @@ void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) for( j = 0; j < level.maxclients; j++ ) { other = &g_entities[ j ]; + if (other->client->pers.connected != CON_DISCONNECTED && other->client->pers.control != 0) + { + if (other->client->pers.control < 0) + continue; + if (G_SayTo(ent, &g_entities[other->client->pers.control - 1], mode, color, name, text, prefix)) + continue; + } G_SayTo( ent, other, mode, color, name, text, prefix ); } } @@ -1222,6 +1231,13 @@ void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) for( j = 0; j < level.maxclients; j++ ) { other = &g_entities[ j ]; + if (other->client->pers.connected != CON_DISCONNECTED && other->client->pers.control != 0) + { + if (other->client->pers.control < 0) + continue; + if (G_SayTo(ent, &g_entities[other->client->pers.control - 1], mode, color, name, text, prefix)) + continue; + } G_SayTo( ent, other, mode, color, name, text, prefix ); } } @@ -1279,11 +1295,22 @@ static void Cmd_SayArea_f( gentity_t *ent ) num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) + { + if (g_clients[entityList[i]].pers.connected != CON_DISCONNECTED && g_clients[entityList[i]].pers.control != 0) + { + if (g_clients[entityList[i]].pers.control < 0) + continue; + if (G_SayTo(ent, &g_entities[g_clients[entityList[i]].pers.control - 1], SAY_TEAM, color, name, msg, prefix)) + continue; + } G_SayTo( ent, &g_entities[ entityList[ i ] ], SAY_TEAM, color, name, msg, prefix ); + } //Send to ADMF_SPEC_ALLCHAT candidates for( i = 0; i < level.maxclients; i++ ) { + if (g_clients[i].pers.connected != CON_DISCONNECTED && g_clients[i].pers.control > 0) + continue; if( (&g_entities[ i ])->client->pers.teamSelection == PTE_NONE && G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) { @@ -2865,6 +2892,12 @@ void Cmd_CallTeamVote_f( gentity_t *ent ) "called a team vote: %s^7 \n\"", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ) ); trap_SendServerCommand( i, "cp \"A team vote has been called\n^2F1: Yes^7, ^1F2: No^7\"" ); } + else if (level.clients[i].pers.control > 0 + && (G_admin_permission(&g_entities[level.clients[i].pers.control - 1], ADMF_ADMINCHAT) + && (!Q_stricmp(arg1, "kick") || !Q_stricmp(arg1, "denybuild") + || level.clients[level.clients[i].pers.control - 1].pers.teamSelection == PTE_NONE))) + { + } else if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) && ( ( !Q_stricmp( arg1, "kick" ) || !Q_stricmp( arg1, "denybuild" ) ) || level.clients[ i ].pers.teamSelection == PTE_NONE ) ) @@ -3322,6 +3355,17 @@ void DBCommand( gentity_t *builder, pTeam_t team, const char *text ) if( !ent->client || ent->client->pers.connected != CON_CONNECTED ) continue; + if (ent->client->pers.control > 0) + { + gentity_t *e = &g_entities[ent->client->pers.control - 1]; + + if ((e->client->pers.teamSelection == team && e->client->pers.designatedBuilder) + || (e->client->pers.teamSelection == PTE_NONE && G_admin_permission(e, ADMF_SPEC_ALLCHAT))) + { + continue; + } + } + if( ( ent->client->pers.teamSelection == team && ent->client->pers.designatedBuilder ) || ( ent->client->pers.teamSelection == PTE_NONE && @@ -5454,6 +5498,11 @@ void ClientCommand( int clientNum ) int i; ent = g_entities + clientNum; + if (ent->client->pers.control > 0) + { + clientNum = ent->client->pers.control - 1; + ent = g_entities + clientNum; + } if( !ent->client ) return; // not fully in game yet diff --git a/src/game/g_local.h b/src/game/g_local.h index 83431c0..6547741 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -378,6 +378,8 @@ typedef struct typedef struct { clientConnected_t connected; + qboolean isPlaceholder; + int control; usercmd_t cmd; // we would lose angles if not persistant qboolean localClient; // true if "ip" info key is "localhost" qboolean initialSpawn; // the first spawn should be at a cool location @@ -1160,12 +1162,15 @@ qboolean G_Flood_Limited( gentity_t *ent ); // // g_client.c // -const char *ClientConnect( int clientNum, qboolean firstTime ); +const char *ClientConnect( int clientNum, qboolean firstTime, qboolean isPlaceholder ); void ClientUserinfoChanged( int clientNum, qboolean forceName ); void ClientDisconnect( int clientNum ); void ClientBegin( int clientNum ); void ClientCommand( int clientNum ); +const char *review_placeholder_client_userinfo(char *userinfo); +void inject_placeholder_client(const char *userinfo, int slot, qboolean first_time); + // // g_active.c // @@ -1600,3 +1605,6 @@ qboolean trap_GetEntityToken( char *buffer, int bufferSize ); void trap_SnapVector( float *v ); void trap_SendGameStat( const char *data ); + +int trap_install_placeholder_client(const char *userinfo); +void trap_set_client_view_entity(int cortex, int eye); diff --git a/src/game/g_main.c b/src/game/g_main.c index 55508be..cb372a3 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -595,7 +595,7 @@ Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, i return 0; case GAME_CLIENT_CONNECT: - return (intptr_t)ClientConnect( arg0, arg1 ); + return (intptr_t)ClientConnect( arg0, arg1, arg2 ); case GAME_CLIENT_THINK: ClientThink( arg0 ); @@ -2173,6 +2173,12 @@ void QDECL G_AdminsPrintf( const char *fmt, ... ) for( j = 0; j < level.maxclients; j++ ) { tempent = &g_entities[ j ]; + if (tempent->client->pers.control > 0) + { + gentity_t *e = &g_entities[tempent->client->pers.control - 1]; + if (G_admin_permission(e, ADMF_ADMINCHAT) && !e->client->pers.ignoreAdminWarnings) + continue; + } if( G_admin_permission( tempent, ADMF_ADMINCHAT) && !tempent->client->pers.ignoreAdminWarnings ) { diff --git a/src/game/g_public.h b/src/game/g_public.h index e1e01d7..7ecbaea 100644 --- a/src/game/g_public.h +++ b/src/game/g_public.h @@ -227,7 +227,10 @@ typedef enum { G_SEND_GAMESTAT, G_ADDCOMMAND, - G_REMOVECOMMAND + G_REMOVECOMMAND, + + G_INSTALL_PLACEHOLDER_CLIENT, + G_SET_CLIENT_VIEW_ENTITY, } gameImport_t; diff --git a/src/game/g_svcmds.c b/src/game/g_svcmds.c index c074abc..faf6c63 100644 --- a/src/game/g_svcmds.c +++ b/src/game/g_svcmds.c @@ -450,6 +450,46 @@ gclient_t *ClientForString( const char *s ) return NULL; } +static void G_InjPhC_f(void) +{ + int argc = trap_Argc(); + + if (argc % 2 != 1) + { + Com_Printf("usage: injphc [ ] ...\n" + " eg: injphc name ^0NEGRO^7 ip 1.3.3.7 cl_guid AAA... version \"Tremulous 1.3\"\n"); + return; + } + + char ui[MAX_INFO_STRING]; + *ui = '\0'; + + for (int i = 1; i != argc; i += 2) + { + char key[MAX_STRING_CHARS]; + char value[MAX_STRING_CHARS]; + trap_Argv(i, key, sizeof(key)); + trap_Argv(i + 1, value, sizeof(value)); + Info_SetValueForKey(ui, key, value); + } + + const char *invalidity = review_placeholder_client_userinfo(ui); + if (invalidity) + { + Com_Printf("injphc: %s\n", invalidity); + return; + } + + int sl = trap_install_placeholder_client(ui); + if (sl < 0) + { + Com_Printf("injphc: failed to acquire a client slot\n"); + return; + } + + inject_placeholder_client(ui, sl, qtrue); +} + /* =================== Svcmd_ForceTeam_f @@ -594,6 +634,12 @@ qboolean ConsoleCommand( void ) return qtrue; } + if (!Q_stricmp(cmd, "injphc")) + { + G_InjPhC_f(); + return qtrue; + } + if( Q_stricmp( cmd, "forceteam" ) == 0 ) { Svcmd_ForceTeam_f( ); diff --git a/src/game/g_syscalls.asm b/src/game/g_syscalls.asm index 242c2ad..ce613fd 100644 --- a/src/game/g_syscalls.asm +++ b/src/game/g_syscalls.asm @@ -55,6 +55,9 @@ equ trap_SendGameStat -49 equ trap_AddCommand -50 equ trap_RemoveCommand -51 +equ trap_install_placeholder_client -52 +equ trap_set_client_view_entity -53 + equ memset -101 equ memcpy -102 equ strncpy -103 diff --git a/src/game/g_syscalls.c b/src/game/g_syscalls.c index 6e3dc26..99eff66 100644 --- a/src/game/g_syscalls.c +++ b/src/game/g_syscalls.c @@ -134,6 +134,13 @@ void trap_DropClient( int clientNum, const char *reason ) void trap_SendServerCommand( int clientNum, const char *text ) { + if (clientNum >= 0) + { + const clientPersistant_t *per = &g_clients[clientNum].pers; + if (per->connected != CON_DISCONNECTED && per->control < 0) + clientNum = -per->control - 1; + } + syscall( G_SEND_SERVER_COMMAND, clientNum, text ); } @@ -282,3 +289,12 @@ int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ) return syscall( G_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line ); } +int trap_install_placeholder_client(const char *const ui) +{ + return syscall(G_INSTALL_PLACEHOLDER_CLIENT, ui); +} + +void trap_set_client_view_entity(int c, int e) +{ + syscall(G_SET_CLIENT_VIEW_ENTITY, c, e); +} diff --git a/src/game/g_utils.c b/src/game/g_utils.c index a74df3f..272c968 100644 --- a/src/game/g_utils.c +++ b/src/game/g_utils.c @@ -162,6 +162,17 @@ void G_TeamCommand( pTeam_t team, char *cmd ) { if( level.clients[ i ].pers.connected == CON_CONNECTED ) { + if (level.clients[i].pers.control > 0) + { + int j = level.clients[i].pers.control - 1; + + if (level.clients[j].pers.teamSelection == team + || (level.clients[j].pers.teamSelection == PTE_NONE + && G_admin_permission(&g_entities[j], ADMF_SPEC_ALLCHAT))) + { + continue; + } + } if( level.clients[ i ].pers.teamSelection == team || ( level.clients[ i ].pers.teamSelection == PTE_NONE && G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) ) @@ -828,6 +839,10 @@ void G_TriggerMenu( int clientNum, dynMenu_t menu ) { char buffer[ 32 ]; + clientPersistant_t *p = &g_clients[clientNum].pers; + if (p->control > 0 || (p->isPlaceholder && p->control >= 0)) + return; + Com_sprintf( buffer, 32, "servermenu %d", menu ); trap_SendServerCommand( clientNum, buffer ); } -- cgit