increment svs.clients[clientNum].lastUsercmd.serverTime (adding 1ms should be enough to get the command parsed)
set svs.clients[clientNum].lastUsercmd.buttons |= 1 (BUTTON_ATTACK)
run VM_Call( gvm, GAME_CLIENT_THINK, clientNum );
The goal is to get client->readyToExit = 1 via ClientThink_real->ClientIntermissionThink.
It’s also likely possible to just set readyToExit directly from the engine. You have to figure out the address relative to the playerstate. It’s a bit more mod-dependent but might be more robust otherwise.
I wrote up an /rcon attack function to test it out:
static void SV_Attack_f(void) {
int i;
client_t *cl;
if (!com_sv_running->integer) {
Com_Printf("Server is not running\n");
return;
}
if (Cmd_Argc() != 2) {
Com_Printf("Usage: attack <client>\n");
return;
}
cl = SV_GetPlayerByHandle();
if (!cl) {
return;
}
i = cl - svs.clients;
cl->lastUsercmd.serverTime += 1;
cl->lastUsercmd.buttons = 1;
VM_Call(gvm, GAME_CLIENT_THINK, i);
}
The only problem is that it only works every other time. If I type it 4 times in a row: /rcon attack 1 — it’ll work the first and third time. But, not 2nd or 4th. Is there a reason why it’s not being processed every single time?
At the end of the map, I set a timestamp to now + 4 seconds and if that much time has elapsed, it will call the intermissionReady() function which forces all clients to click attack and become ready. With OSP, I don’t think there’s really any other way to do it.
Inside of sv_game.c:
Add the function:
void intermissionReady(void){
client_t *cl;
int i;
for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) {
if ( !cl->state ) { continue; }
cl->lastUsercmd.serverTime += 100;
cl->lastUsercmd.buttons = 1;
VM_Call(gvm, GAME_CLIENT_THINK, i);
}
}
Then inside of void SV_GameSendServerCommand( int clientNum, const char *text ) {
I parse the line and if the first word is ‘print’ and second word is ‘Next’, I do the following: