/* * Copyright(c) 1997-2001 Id Software, Inc. * Copyright(c) 2002 The Quakeforge Project. * Copyright(c) 2006 Quetoo. * * 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. */ #include "server.h" server_static_t svs; // persistant server info server_t sv; // local server /* SV_FindIndex */ int SV_FindIndex(char *name, int start, int max, qboolean create){ int i; if(!name || !name[0]) return 0; for(i = 1; i < max && sv.configstrings[start + i][0]; i++) if(!strcmp(sv.configstrings[start + i], name)) return i; if(!create) return 0; if(i == max) Com_Error(ERR_DROP, "*Index: overflow"); strncpy(sv.configstrings[start + i], name, sizeof(sv.configstrings[i])); if(sv.state != ss_loading){ // send the update to everyone SZ_Clear(&sv.multicast); MSG_WriteChar(&sv.multicast, svc_configstring); MSG_WriteShort(&sv.multicast, start + i); MSG_WriteString(&sv.multicast, name); SV_Multicast(vec3_origin, MULTICAST_ALL_R); } return i; } int SV_ModelIndex(char *name){ return SV_FindIndex(name, CS_MODELS, MAX_MODELS, true); } int SV_SoundIndex(char *name){ return SV_FindIndex(name, CS_SOUNDS, MAX_SOUNDS, true); } int SV_ImageIndex(char *name){ return SV_FindIndex(name, CS_IMAGES, MAX_IMAGES, true); } /* SV_CreateBaseline Entity baselines are used to compress the update messages to the clients -- only the fields that differ from the baseline will be transmitted */ void SV_CreateBaseline(void){ edict_t *svent; int entnum; for(entnum = 1; entnum < ge->num_edicts; entnum++){ svent = EDICT_NUM(entnum); if(!svent->inuse) continue; if(!svent->s.modelindex && !svent->s.sound && !svent->s.effects) continue; svent->s.number = entnum; // take current state as baseline VectorCopy(svent->s.origin, svent->s.old_origin); sv.baselines[entnum] = svent->s; } } /* SV_CheckMap Ensures that map exists before attempting to spawn a server to it. Returns -1 if the map does not exist, PROTOCOL_34 otherwise. */ int SV_CheckMap(char *name){ char map[MAX_OSPATH]; FILE *f; Com_sprintf(map, sizeof(map), "maps/%s.bsp", name); FS_FOpenFile(map, &f); if(!f){ Com_Printf("Couldn't open %s\n", map); return -1; } FS_FCloseFile(f); return PROTOCOL_34; } /* SV_CheckDemo Attempts to open and peek into the specified demo file. Returns the protocol version of the file if it exists. File is closed. */ int SV_CheckDemo(char *name){ char demo[MAX_OSPATH]; byte buff[MAX_MSGLEN]; int msglen, protocol; FILE *f; Com_sprintf(demo, sizeof(demo), "demos/%s", name); FS_FOpenFile(demo, &f); if(!f){ Com_Printf("Couldn't open %s\n", demo); return -1; } fread(&msglen, 4, 1, f); msglen = LittleLong(msglen); if(msglen == -1 || msglen > MAX_MSGLEN){ Com_Printf("%s is not a demo\n", demo); return -1; } fread(buff, msglen, 1, f); memcpy(&protocol, buff + 1, 4); FS_FCloseFile(f); return LittleLong(protocol); } /* SV_InitGame A brand new game has been started */ void SV_InitGame(){ int i; edict_t *ent; char idmaster[32]; svs.initialized = true; if(coop->value && deathmatch->value){ Com_Printf("Deathmatch and Coop both set, disabling Coop.\n"); Cvar_FullSet("coop", "0", CVAR_SERVERINFO | CVAR_LATCH); } // dedicated servers can't be single player and are usually dm // so unless they explicity set coop, force it to deathmatch if(dedicated->value && !coop->value){ Cvar_FullSet("deathmatch", "1", CVAR_SERVERINFO | CVAR_LATCH); } // init clients if(deathmatch->value){ if(maxclients->value <= 1) Cvar_FullSet("maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH); else if(maxclients->value > MAX_CLIENTS) Cvar_FullSet("maxclients", va("%i", MAX_CLIENTS), CVAR_SERVERINFO | CVAR_LATCH); } else if(coop->value){ if(maxclients->value <= 1 || maxclients->value > 4) Cvar_FullSet("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH); } else { // non-deathmatch, non-coop is one player Cvar_FullSet("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH); } svs.spawncount = rand(); svs.clients = Z_Malloc(sizeof(client_t) * maxclients->value); svs.num_client_entities = maxclients->value * UPDATE_BACKUP * 64; svs.client_entities = Z_Malloc(sizeof(entity_state_t) * svs.num_client_entities); // init network stuff NET_Config((maxclients->value > 1)); // heartbeats will always be sent to the id master svs.last_heartbeat = -99999; // send immediately Com_sprintf(idmaster, sizeof(idmaster), "192.246.40.37:%i", PORT_MASTER); NET_StringToAdr(idmaster, &master_adr[0]); SV_InitGameProgs(); for(i = 0; i < maxclients->value; i++){ ent = EDICT_NUM(i + 1); ent->s.number = i + 1; svs.clients[i].edict = ent; memset(&svs.clients[i].lastcmd, 0, sizeof(svs.clients[i].lastcmd)); } } /* SV_SpawnServer Change the server to a new map, taking all connected clients along with it. The serverstate parameter must either be ss_game or ss_demo. See SV_Map. */ void SV_SpawnServer(char *server, char *spawnpoint, server_state_t serverstate, int protocol){ int i; unsigned checksum; qboolean hardreconnect, initgame, changing; Com_Printf("Server initialization..\n"); hardreconnect = protocol != sv.protocol; initgame = changing = false; if(!svs.initialized || Cvar_UpdateLatchedVars()){ // need reconnect SV_Shutdown("Server restarting..\n", true, hardreconnect); SV_InitGame(); // init clients, game progs, etc initgame = true; } else { // server was initialized and no latched changes if(hardreconnect) // protocol has changed SV_BroadcastCommand("reconnect\n"); else { SV_BroadcastCommand("changing\n"); changing = true; } SV_SendClientMessages(); } if(sv.demofile) fclose(sv.demofile); svs.spawncount++; // any partially connected client will be restarted svs.realtime = 0; sv.state = ss_dead; Com_SetServerState(sv.state); // wipe the entire per-level structure memset(&sv, 0, sizeof(sv)); if(serverstate == ss_demo || deathmatch->value) Cvar_Set("paused", "0"); if(deathmatch->value){ sprintf(sv.configstrings[CS_AIRACCEL], "%g", sv_airaccelerate->value); pm_airaccelerate = sv_airaccelerate->value; } else { strcpy(sv.configstrings[CS_AIRACCEL], "0"); pm_airaccelerate = 0; } SZ_Init(&sv.multicast, sv.multicast_buf, sizeof(sv.multicast_buf)); // leave slots at start for clients only for(i = 0; i < maxclients->value; i++){ // needs to reconnect if(svs.clients[i].state > cs_connected) svs.clients[i].state = cs_connected; svs.clients[i].lastframe = -1; } sv.time = 1000; // set time sv.protocol = protocol; // force update cvar Cvar_ForceSet("protocol", va("%d", sv.protocol)); // save name for levels that don't set message strcpy(sv.name, server); strcpy(sv.configstrings[CS_NAME], server); if(serverstate == ss_demo){ // playing a demo, no map on the server sv.models[1] = CM_LoadMap("", false, &checksum); } else { // playing a game Com_sprintf(sv.configstrings[CS_MODELS + 1], MAX_QPATH, "maps/%s.bsp", server); sv.models[1] = CM_LoadMap(sv.configstrings[CS_MODELS + 1], false, &checksum); } Com_sprintf(sv.configstrings[CS_MAPCHECKSUM], MAX_QPATH, "%i", checksum); // clear physics interaction links SV_ClearWorld(); for(i = 1; i < CM_NumInlineModels(); i++){ Com_sprintf(sv.configstrings[CS_MODELS + 1 + i], MAX_QPATH, "*%i", i); sv.models[i + 1] = CM_InlineModel(sv.configstrings[CS_MODELS + 1 + i]); } if(serverstate == ss_game){ // load and spawn all other entities sv.state = ss_loading; Com_SetServerState(sv.state); ge->SpawnEntities(sv.name, CM_EntityString(), spawnpoint); ge->RunFrame(); // run two frames to allow everything to settle ge->RunFrame(); // create a baseline for more efficient communications SV_CreateBaseline(); } // all precaches are complete sv.state = serverstate; Com_SetServerState(sv.state); // set serverinfo variable Cvar_FullSet("mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET); if(!initgame && hardreconnect){ // zombie clients so they may reconnect for(i = 0; i < maxclients->value; i++){ if(svs.clients[i].state){ svs.clients[i].state = cs_zombie; } } } else if(changing){ // tell clients to reconnect SV_BroadcastCommand("reconnect\n"); SV_SendClientMessages(); } Com_Printf("Server initialized.\n"); } /* SV_Map To be backwards compatible, Quetoo supports the full syntax: map [*]$+ However, if a +nextserver is present, it will be immediately skipped to, as Quetoo does not support cinematics. String may also specify a .dm2. */ void SV_Map(char *levelstring){ char level[MAX_QPATH]; char spawnpoint[MAX_QPATH]; server_state_t state; int i, protocol; char *ch; strncpy(level, levelstring, sizeof(level)); // normally this is adequate, but.. // if there is a + in the map, skip the antecedent and jump to .bsp if((ch = strstr(levelstring, "+"))) strncpy(level, ch + 1, sizeof(level)); // if there is a $, use the remainder as a spawnpoint if((ch = strstr(level, "$"))){ strcpy(spawnpoint, ch + 1); *ch = 0; // and truncate level } else spawnpoint[0] = 0; // skip the end-of-unit flag if necessary if(level[0] == '*') strcpy(level, level + 1); // hack for end game screen in coop mode if(coop->value && !strcmp(levelstring, "victory.pcx")) strcpy(level, "base1"); i = strlen(level); state = i > 4 && !strcasecmp(level + i - 4, ".dm2") ? ss_demo : ss_game; if(state == ss_demo){ // resolve next protocol to determine reconnect protocol = SV_CheckDemo(level); } else protocol = SV_CheckMap(level); if(protocol == -1) // demo or map file didn't exist return; CL_Drop(); // make sure local client drops Cbuf_CopyToDefer(); SV_SpawnServer(level, spawnpoint, state, protocol); }