/* Relay -- a tool to record and play Quake2 demos Copyright (C) 2000 Conor Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Conor Davis cedavis@planetquake.com */ #include #include #include #include "sv_local.h" server_t server; dm2_t dm2in; bsp_t map; // cvars cvar_t *cvar_basedir; cvar_t *cvar_demoname; cvar_t *cvar_game; cvar_t *cvar_hostname; cvar_t *cvar_mapname; cvar_t *cvar_maxclients; cvar_t *cvar_password; cvar_t *cvar_port; cvar_t *cvar_protocol; cvar_t *cvar_timescale; void StartServer() { unsigned short port; int i; client_t *client; port = cvar_port->value; server.socket = UDP_OpenSocket(&port); if (server.socket == -1) { Error("Unable to open socket\n"); return; } server.maxclients = cvar_maxclients->value; if (server.maxclients < 0) server.maxclients = 0; if (server.maxclients > MAX_CLIENTS) server.maxclients = MAX_CLIENTS; server.clients = Z_Malloc(sizeof(client_t) * server.maxclients); for (i = 0, client = server.clients; i < server.maxclients; i++, client++) InitClient(client); server.status = SV_RUNNING; } void RestartServer() { int i, j; block_t out; char out_buffer[MAX_MSGLEN]; client_t *client; BlockInit(&out, out_buffer, sizeof(out_buffer)); CL_cprintf(&out, PRINT_HIGH, "Server restart\n"); WriteByte(&out, SVC_RECONNECT); // tell clients to reconnect for (i = 0, client = server.clients; i < server.maxclients; i++, client++) { if (client->status == CL_UNCONNECTED) continue; for (j = 0; j < 3; j++) UDP_SendUnreliable(server.socket, out.buffer, out.writeoffset, &client->net); client->status = CL_UNCONNECTED; } } void SpawnServer(const char *filename) { char buf[MAX_OSPATH]; block_t bspfile; cvar_forceset("demoname", filename); // set up packfile locations RemoveAllPackDirs(); sprintf(buf, "%s/baseq2", cvar_basedir->string); AddPackDir(buf, PACK_ALL); if (cvar_game->string[0] && strcmp(cvar_game->string, "baseq2")) { sprintf(buf, "%s/%s", cvar_basedir->string, cvar_game->string); AddPackDir(buf, PACK_ALL); } if (server.infile) pfclose(server.infile); sprintf(buf, "demos/%s", filename); server.infile = pfopen(buf, "pvrb"); if (!server.infile) { Error("Unable to open file %s\n", buf); return; } // try to load the demo DM2_Init(&dm2in); if (DM2_ReadPreFrame(&dm2in.svd, NULL, dm2in.configstrings, &dm2in.baselines, server.infile) < 0) { Error("Unable to read pre-frame information from %s\n", buf); return; } DM2_FillConfigstrings(dm2in.configstrings); if (dm2in.svd.isdemo == RECORD_RELAY) dm2in.maxclients = atoi(dm2in.configstrings[CS_MAXCLIENTS]); else dm2in.maxclients = 1; // allow clients to adjust timescale //strcpy(dm2in.configstrings[CS_MAXCLIENTS], "1"); dm2in.players = Z_Malloc(dm2in.maxclients * sizeof(player_t)); memset(dm2in.players, 0, dm2in.maxclients * sizeof(player_t)); // load the map's BSP file if (Pack_LoadFile(dm2in.configstrings[CS_MODELS+1], &bspfile) < 0) { Error("Unable to load BSP file %s\n", dm2in.configstrings[CS_MODELS+1]); return; } if (ReadBSP(&map, &bspfile) < 0) { Error("Unable to read BSP file %s\n", dm2in.configstrings[CS_MODELS+1]); return; } Z_Free(bspfile.buffer); COM_FileBase(dm2in.configstrings[CS_MODELS+1], buf); cvar_forceset("mapname", buf); server.outsvd = dm2in.svd; server.outsvd.version = cvar_protocol->value; server.outsvd.isdemo = RECORD_NETWORK; server.outsvd.player = -1; // set up sockets and clients if (server.status == SV_RUNNING) RestartServer(); else StartServer(); } void KillServer(const char *message) { int i, j; client_t *client; block_t out; char out_buffer[MAX_MSGLEN]; BlockInit(&out, out_buffer, sizeof(out_buffer)); if (server.clients) { WriteByte(&out, SVC_PRINT); DM2_WritePrint(&out, PRINT_HIGH, message); WriteByte(&out, SVC_DISCONNECT); // tell all clients to disconnect for (i = 0, client = server.clients; i < server.maxclients; i++, client++) { if (client->status == CL_UNCONNECTED) continue; // send SVC_DISCONNECT message 3 times and // hope one gets through for (j = 0; j < 3; j++) UDP_SendUnreliable(server.socket, out.buffer, out.writeoffset, &client->net); } Z_Free(server.clients); server.clients = NULL; server.maxclients = 0; } if (server.socket != -1) { UDP_CloseSocket(server.socket); server.socket = -1; } if (server.infile) { pfclose(server.infile); server.infile = NULL; } FreeAllChallenges(); UnlatchCvars(); server.status = SV_IDLE; } void CloseProgram(int err) { KillServer("Server exit\n"); UDP_End(); FreeAllCvars(); FreeAllAliases(); Cmd_ResetArgs(); RemoveAllPackDirs(); Z_FreeAll(); exit(err); } void Error(const char *fmt, ...) { va_list argptr; char buffer[0x1000]; va_start(argptr, fmt); vsprintf(buffer, fmt, argptr); va_end(argptr); printf("%s", buffer); KillServer(buffer); } void DropClient(client_t *client, const char *fmt, ...) { va_list argptr; char buffer[0x1000]; block_t out; char out_buffer[MAX_MSGLEN]; int i; BlockInit(&out, out_buffer, sizeof(out_buffer)); if (fmt) { va_start(argptr, fmt); vsprintf(buffer, fmt, argptr); va_end(argptr); WriteByte(&out, SVC_PRINT); DM2_WritePrint(&out, PRINT_HIGH, buffer); } WriteByte(&out, SVC_DISCONNECT); for (i = 0; i < 3; i++) UDP_SendUnreliable(server.socket, out.buffer, out.writeoffset, &client->net); ClientDisconnect(client); } void CL_cprintf(block_t *block, int printlevel, const char *fmt, ...) { va_list argptr; char buffer[0x1000]; va_start(argptr, fmt); vsprintf(buffer, fmt, argptr); va_end(argptr); WriteByte(block, SVC_PRINT); DM2_WritePrint(block, printlevel, buffer); } void CL_bprintf(qboolean reliable, int printlevel, const char *fmt, ...) { va_list argptr; char buffer[0x1000]; int i; client_t *client; va_start(argptr, fmt); vsprintf(buffer, fmt, argptr); va_end(argptr); printf("%s", buffer); for (i = 0, client = server.clients; i < server.maxclients; i++, client++) { if (client->status == CL_UNCONNECTED) continue; if (reliable) { WriteByte(&client->nextreliable, SVC_PRINT); DM2_WritePrint(&client->nextreliable, printlevel, buffer); } else { WriteByte(&client->unreliable, SVC_PRINT); DM2_WritePrint(&client->unreliable, printlevel, buffer); } } } static int RunFrame() { int i; // freeze until players have connected if (!server.numclients) return 0; for (i = 0; i < server.maxclients; i++) { if (server.clients[i].status != CL_CONNECTED) continue; ClientBeginServerFrame(&server.clients[i]); } if (server.infile) { if (Frame_Read() < 0) return -1; } #if 0 else { state_t *current, *delta; delta = &dm2in.states[dm2in.current_frame & UPDATE_MASK]; dm2in.current_frame++; current = &dm2in.states[dm2in.current_frame & UPDATE_MASK]; *current = *delta; current->frame = dm2in.current_frame; } #endif for (i = 0; i < server.maxclients; i++) { if (server.clients[i].status != CL_CONNECTED) continue; ClientEndServerFrame(&server.clients[i]); } return 0; } int main(int argc, char **argv) { size_t nextupdate_time, nextchallenge_time, now; const char *command_buffer; // print copyright message printf("DM2 Server (dm2server) " __DATE__ "\n"); printf("Version " RELAY_VERSION " " RELAY_RELEASE "\n"); printf("Copyright 2000 Conor Davis\n"); printf("dm2server is free software, covered by the GNU General Public License,\nand you are welcome to change it and/or distribute copies of it\nunder certain conditions.\n"); printf("Look at the file \"COPYING\" to see the conditions.\n"); printf("There is absolutely no warranty for dm2server.\n\n"); printf("Type \"cmdlist\" to see a list of commands.\n"); printf("Type \"quit\" to exit dm2server.\n\n"); server.socket = -1; nextchallenge_time = nextupdate_time = 0; srand(time(NULL)); if (UDP_Begin() < 0) Sys_Error("Error: Unable to initialize sockets\n"); // initialize cvars cvar_basedir = cvar("basedir", ".", CVAR_LATCH); cvar_demoname = cvar("demoname", "", CVAR_SERVERINFO|CVAR_NOSET); cvar_game = cvar("game", "baseq2", CVAR_LATCH); cvar_hostname = cvar("hostname", "unnamed dm2 server", CVAR_SERVERINFO); cvar_mapname = cvar("mapname", "", CVAR_SERVERINFO|CVAR_NOSET); cvar_maxclients = cvar("maxclients", "16", CVAR_SERVERINFO|CVAR_LATCH); cvar_password = cvar("password", "", 0); cvar_port = cvar("port", "27910", CVAR_LATCH); cvar_protocol = cvar("protocol", "34", CVAR_NOSET); cvar_timescale = cvar("timescale", "1", 0); // parse command-line options Cmd_ParseCommandLine(argc, argv, RunConsoleCommand); for (;;) { beginloop: now = mstime(); // check if the user has typed a command and pressed enter // FIXME (Unix): make sure we are in line-mode first? if ((command_buffer = read_line()) != NULL) { if (strchr(command_buffer, '\n')) { Cmd_AddText(&server.console_commandstring, command_buffer); Cmd_RunCommands(&server.console_commandstring, RunConsoleCommand); } } if (server.nextcommand_time < now) Cmd_RunCommands(&server.console_commandstring, RunConsoleCommand); if (server.status == SV_RUNNING) { now = mstime(); // check for connection requests that have timed out if (nextchallenge_time <= now) { TimeoutChallenges(); nextchallenge_time = now + CHALLENGE_TIMEOUT; } // check for incoming packets ReadPackets(); if (server.status == SV_IDLE) goto beginloop; // advance the world by one frame if (nextupdate_time <= mstime() && cvar_timescale->value > 0) { if (RunFrame() < 0) goto beginloop; nextupdate_time = mstime() + 100/cvar_timescale->value; } // write outgoing packets WritePackets(); if (server.status == SV_IDLE) goto beginloop; now = mstime(); if (nextupdate_time > now) msleep(nextupdate_time - now); } else msleep(100); } return 0; } void Sys_Error(char *fmt, ...) { va_list argptr; char buffer[0x1000]; va_start(argptr, fmt); vsprintf(buffer, fmt, argptr); va_end(argptr); printf("%s", buffer); exit(1); } void Com_Printf(char *fmt, ...) { va_list argptr; char buffer[0x1000]; va_start(argptr, fmt); vsprintf(buffer, fmt, argptr); va_end(argptr); printf("%s", buffer); }