summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author/dev/humancontroller <devhc@example.com>2017-09-16 21:05:30 +0200
committer/dev/humancontroller <devhc@example.com>2017-09-16 21:24:55 +0200
commit0ec47fd7d8c7ffbf962b3e5105f26bc677a639a3 (patch)
tree7321558c918796a8c94fa7681e8a3f95b78f05a1
parentf48a7610eef092d8ce5c6b2ae761571745a9db3e (diff)
implement !control
-rw-r--r--src/game/g_active.c10
-rw-r--r--src/game/g_admin.c163
-rw-r--r--src/game/g_admin.h1
-rw-r--r--src/game/g_client.c281
-rw-r--r--src/game/g_cmds.c67
-rw-r--r--src/game/g_local.h10
-rw-r--r--src/game/g_main.c8
-rw-r--r--src/game/g_public.h5
-rw-r--r--src/game/g_svcmds.c46
-rw-r--r--src/game/g_syscalls.asm3
-rw-r--r--src/game/g_syscalls.c16
-rw-r--r--src/game/g_utils.c15
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 [<key> <value>] ...\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 );
}