/* * Copyright (C) 1997-2001 Id Software, Inc. * * 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; /* Knightmare 12/23/2001 */ /* Output a more useful error message to tell user what overflowed */ /* And don't bomb out, either- instead, return last possible index */ if (i == max) /* Com_Error (ERR_DROP, "*Index: overflow"); */ { if (start == CS_MODELS) Com_Printf("Warning: Index overflow for models\n"); else if (start == CS_SOUNDS) Com_Printf("Warning: Index overflow for sounds\n"); else if (start == CS_IMAGES) Com_Printf("Warning: Index overflow for images\n"); return (max - 1); /* return the last possible index */ } /* end Knightmare */ Q_strncpyz(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_CheckForSavegame ================= */ void SV_CheckForSavegame(void) { char name[MAX_OSPATH]; FILE *f; int i; if (sv_noreload->value) return; if (Cvar_VariableValue("deathmatch")) return; Com_sprintf(name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name); f = fopen(name, "rb"); if (!f) return; /* no savegame */ fclose(f); SV_ClearWorld(); /* get configstrings and areaportals */ SV_ReadLevelFile(); if (!sv.loadgame) { /* coming back to a level after being in a * different */ /* level, so run it for ten seconds */ /* * rlava2 was sending too many lightstyles, and overflowing * the */ /* * reliable data. temporarily changing the server state to * loading */ /* prevents these from being passed down. */ server_state_t previousState; /* PGM */ previousState = sv.state; /* PGM */ sv.state = ss_loading; /* PGM */ for (i = 0; i < 100; i++) ge->RunFrame(); sv.state = previousState; /* PGM */ } } /* * ================ SV_SpawnServer * * Change the server to a new map, taking all connected clients along with it. * * ================ */ void SV_SpawnServer(char *server, char *spawnpoint, server_state_t serverstate, qboolean attractloop, qboolean loadgame) { int i; unsigned checksum; if (attractloop) Cvar_Set("paused", "0"); Com_Printf("\n------- Server Initialization -------\n"); Com_DPrintf("SpawnServer: %s\n", server); if (sv.demofile) FS_FCloseFile(sv.demofile); svs.spawncount++; /* any partially connected client will be */ /* restarted */ sv.state = ss_dead; Com_SetServerState(sv.state); /* wipe the entire per-level structure */ memset(&sv, 0, sizeof(sv)); svs.realtime = 0; sv.loadgame = loadgame; sv.attractloop = attractloop; /* save name for levels that don't set message */ strcpy(sv.configstrings[CS_NAME], server); if (Cvar_VariableValue("deathmatch")) { 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; Q_strncpyz(sv.name, server, sizeof(sv.name)); Q_strncpyz(sv.configstrings[CS_NAME], server, sizeof(sv.configstrings[CS_NAME])); if (serverstate != ss_game) { sv.models[1] = CM_LoadMap("", false, &checksum); /* no real map */ } else { Com_sprintf(sv.configstrings[CS_MODELS + 1], sizeof(sv.configstrings[CS_MODELS + 1]), "maps/%s.bsp", server); sv.models[1] = CM_LoadMap(sv.configstrings[CS_MODELS + 1], false, &checksum); } Com_sprintf(sv.configstrings[CS_MAPCHECKSUM], sizeof(sv.configstrings[CS_MAPCHECKSUM]), "%i", checksum); /* clear physics interaction links */ SV_ClearWorld(); for (i = 1; i < CM_NumInlineModels(); i++) { Com_sprintf(sv.configstrings[CS_MODELS + 1 + i], sizeof(sv.configstrings[CS_MODELS + 1 + i]), "*%i", i); sv.models[i + 1] = CM_InlineModel(sv.configstrings[CS_MODELS + 1 + i]); } /* spawn the rest of the entities on the map */ /* precache and static commands can be issued during */ /* map initialization */ sv.state = ss_loading; Com_SetServerState(sv.state); /* load and spawn all other entities */ ge->SpawnEntities(sv.name, CM_EntityString(), spawnpoint); /* run two frames to allow everything to settle */ ge->RunFrame(); ge->RunFrame(); /* all precaches are complete */ sv.state = serverstate; Com_SetServerState(sv.state); /* create a baseline for more efficient communications */ SV_CreateBaseline(); /* check for a savegame */ SV_CheckForSavegame(); /* set serverinfo variable */ Cvar_FullSet("mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET); Com_Printf("-------------------------------------\n"); } /* * ============== SV_InitGame * * A brand new game has been started ============== */ void SV_InitGame(void) { int i; edict_t *ent; char idmaster[32]; if (svs.initialized) { /* cause any connected clients to reconnect */ SV_Shutdown("Server restarted\n", true); } else { /* make sure the client is down */ CL_Drop(); SCR_BeginLoadingPlaque(); } /* get any latched variable changes (maxclients, etc) */ Cvar_GetLatchedVars(); svs.initialized = true; if (Cvar_VariableValue("coop") && Cvar_VariableValue("deathmatch")) { Com_Printf("Deathmatch and Coop both set, disabling Coop\n"); Cvar_FullSet("coop", "0", CVAR_SERVERINFO | CVAR_LATCH); } /* dedicated servers are can't be single player and are usually DM */ /* so unless they explicity set coop, force it to deathmatch */ if (dedicated->value) { if (!Cvar_VariableValue("coop")) Cvar_FullSet("deathmatch", "1", CVAR_SERVERINFO | CVAR_LATCH); } /* init clients */ if (Cvar_VariableValue("deathmatch")) { 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 (Cvar_VariableValue("coop")) { 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]); /* init game */ 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_Map * * the full syntax is: * * map [*]$+ * * command from the console or progs. Map can also be a.cin, .pcx, or .dm2 file * Nextserver is used to allow a cinematic to play, then proceed to another * level: * * map tram.cin+jail_e3 ====================== */ void SV_Map(qboolean attractloop, char *levelstring, qboolean loadgame) { char level[MAX_QPATH]; char *ch; int l; char spawnpoint[MAX_QPATH]; sv.loadgame = loadgame; sv.attractloop = attractloop; if (sv.state == ss_dead && !sv.loadgame) SV_InitGame(); /* the game is just starting */ Q_strncpyz(level, levelstring, sizeof(level)); /* if there is a + in the map, set nextserver to the remainder */ ch = strstr(level, "+"); if (ch) { *ch = 0; Cvar_Set("nextserver", va("gamemap \"%s\"", ch + 1)); } else Cvar_Set("nextserver", ""); /* ZOID special hack for end game screen in coop mode */ if (Cvar_VariableValue("coop") && !Q_stricmp(level, "victory.pcx")) Cvar_Set("nextserver", "gamemap \"*base1\""); /* if there is a $, use the remainder as a spawnpoint */ ch = strstr(level, "$"); if (ch) { *ch = 0; Q_strncpyz(spawnpoint, ch + 1, sizeof(spawnpoint)); } else spawnpoint[0] = 0; /* skip the end-of-unit flag if necessary */ if (level[0] == '*') Q_strncpyz(level, level + 1, sizeof(level)); l = strlen(level); if (l > 4 && !strcmp(level + l - 4, ".cin")) { SCR_BeginLoadingPlaque(); /* for local system */ SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, ss_cinematic, attractloop, loadgame); } else if (l > 4 && !strcmp(level + l - 4, ".dm2")) { SCR_BeginLoadingPlaque(); /* for local system */ SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, ss_demo, attractloop, loadgame); } else if (l > 4 && !strcmp(level + l - 4, ".pcx")) { SCR_BeginLoadingPlaque(); /* for local system */ SV_BroadcastCommand("changing\n"); SV_SpawnServer(level, spawnpoint, ss_pic, attractloop, loadgame); } else { SCR_BeginLoadingPlaque(); /* for local system */ SV_BroadcastCommand("changing\n"); SV_SendClientMessages(); SV_SpawnServer(level, spawnpoint, ss_game, attractloop, loadgame); Cbuf_CopyToDefer(); } SV_BroadcastCommand("reconnect\n"); }