1 /*
2 * Copyright(c) 1997-2001 Id Software, Inc.
3 * Copyright(c) 2002 The Quakeforge Project.
4 * Copyright(c) 2006 Quetoo.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or(at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 *
15 * See the GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21
22 #include "server.h"
23
24 server_static_t svs; // persistant server info
25 server_t sv; // local server
26
27 /*
28 SV_FindIndex
29
30 */
SV_FindIndex(char * name,int start,int max,qboolean create)31 int SV_FindIndex(char *name, int start, int max, qboolean create){
32 int i;
33
34 if(!name || !name[0])
35 return 0;
36
37 for(i = 1; i < max && sv.configstrings[start + i][0]; i++)
38 if(!strcmp(sv.configstrings[start + i], name))
39 return i;
40
41 if(!create)
42 return 0;
43
44 if(i == max)
45 Com_Error(ERR_DROP, "*Index: overflow");
46
47 strncpy(sv.configstrings[start + i], name, sizeof(sv.configstrings[i]));
48
49 if(sv.state != ss_loading){ // send the update to everyone
50 SZ_Clear(&sv.multicast);
51 MSG_WriteChar(&sv.multicast, svc_configstring);
52 MSG_WriteShort(&sv.multicast, start + i);
53 MSG_WriteString(&sv.multicast, name);
54 SV_Multicast(vec3_origin, MULTICAST_ALL_R);
55 }
56
57 return i;
58 }
59
60
SV_ModelIndex(char * name)61 int SV_ModelIndex(char *name){
62 return SV_FindIndex(name, CS_MODELS, MAX_MODELS, true);
63 }
64
SV_SoundIndex(char * name)65 int SV_SoundIndex(char *name){
66 return SV_FindIndex(name, CS_SOUNDS, MAX_SOUNDS, true);
67 }
68
SV_ImageIndex(char * name)69 int SV_ImageIndex(char *name){
70 return SV_FindIndex(name, CS_IMAGES, MAX_IMAGES, true);
71 }
72
73
74 /*
75 SV_CreateBaseline
76
77 Entity baselines are used to compress the update messages
78 to the clients -- only the fields that differ from the
79 baseline will be transmitted
80 */
SV_CreateBaseline(void)81 void SV_CreateBaseline(void){
82 edict_t *svent;
83 int entnum;
84
85 for(entnum = 1; entnum < ge->num_edicts; entnum++){
86 svent = EDICT_NUM(entnum);
87 if(!svent->inuse)
88 continue;
89 if(!svent->s.modelindex && !svent->s.sound && !svent->s.effects)
90 continue;
91 svent->s.number = entnum;
92
93 // take current state as baseline
94 VectorCopy(svent->s.origin, svent->s.old_origin);
95 sv.baselines[entnum] = svent->s;
96 }
97 }
98
99
100 /*
101 SV_CheckMap
102
103 Ensures that map exists before attempting to spawn a server to it.
104 Returns -1 if the map does not exist, PROTOCOL_34 otherwise.
105 */
SV_CheckMap(char * name)106 int SV_CheckMap(char *name){
107 char map[MAX_OSPATH];
108 FILE *f;
109
110 Com_sprintf(map, sizeof(map), "maps/%s.bsp", name);
111 FS_FOpenFile(map, &f);
112
113 if(!f){
114 Com_Printf("Couldn't open %s\n", map);
115 return -1;
116 }
117
118 FS_FCloseFile(f);
119 return PROTOCOL_34;
120 }
121
122
123 /*
124 SV_CheckDemo
125
126 Attempts to open and peek into the specified demo file. Returns
127 the protocol version of the file if it exists. File is closed.
128 */
SV_CheckDemo(char * name)129 int SV_CheckDemo(char *name){
130 char demo[MAX_OSPATH];
131 byte buff[MAX_MSGLEN];
132 int msglen, protocol;
133 FILE *f;
134
135 Com_sprintf(demo, sizeof(demo), "demos/%s", name);
136 FS_FOpenFile(demo, &f);
137 if(!f){
138 Com_Printf("Couldn't open %s\n", demo);
139 return -1;
140 }
141
142 fread(&msglen, 4, 1, f);
143 msglen = LittleLong(msglen);
144
145 if(msglen == -1 || msglen > MAX_MSGLEN){
146 Com_Printf("%s is not a demo\n", demo);
147 return -1;
148 }
149
150 fread(buff, msglen, 1, f);
151 memcpy(&protocol, buff + 1, 4);
152
153 FS_FCloseFile(f);
154 return LittleLong(protocol);
155 }
156
157
158 /*
159 SV_InitGame
160
161 A brand new game has been started
162 */
SV_InitGame()163 void SV_InitGame(){
164 int i;
165 edict_t *ent;
166 char idmaster[32];
167
168 svs.initialized = true;
169
170 if(coop->value && deathmatch->value){
171 Com_Printf("Deathmatch and Coop both set, disabling Coop.\n");
172 Cvar_FullSet("coop", "0", CVAR_SERVERINFO | CVAR_LATCH);
173 }
174
175 // dedicated servers can't be single player and are usually dm
176 // so unless they explicity set coop, force it to deathmatch
177 if(dedicated->value && !coop->value){
178 Cvar_FullSet("deathmatch", "1", CVAR_SERVERINFO | CVAR_LATCH);
179 }
180
181 // init clients
182 if(deathmatch->value){
183 if(maxclients->value <= 1)
184 Cvar_FullSet("maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH);
185 else if(maxclients->value > MAX_CLIENTS)
186 Cvar_FullSet("maxclients", va("%i", MAX_CLIENTS), CVAR_SERVERINFO | CVAR_LATCH);
187 } else if(coop->value){
188 if(maxclients->value <= 1 || maxclients->value > 4)
189 Cvar_FullSet("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
190 } else { // non-deathmatch, non-coop is one player
191 Cvar_FullSet("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
192 }
193
194 svs.spawncount = rand();
195 svs.clients = Z_Malloc(sizeof(client_t) * maxclients->value);
196 svs.num_client_entities = maxclients->value * UPDATE_BACKUP * 64;
197 svs.client_entities = Z_Malloc(sizeof(entity_state_t) * svs.num_client_entities);
198
199 // init network stuff
200 NET_Config((maxclients->value > 1));
201
202 // heartbeats will always be sent to the id master
203 svs.last_heartbeat = -99999; // send immediately
204 Com_sprintf(idmaster, sizeof(idmaster), "192.246.40.37:%i", PORT_MASTER);
205 NET_StringToAdr(idmaster, &master_adr[0]);
206
207 SV_InitGameProgs();
208
209 for(i = 0; i < maxclients->value; i++){
210 ent = EDICT_NUM(i + 1);
211 ent->s.number = i + 1;
212 svs.clients[i].edict = ent;
213 memset(&svs.clients[i].lastcmd, 0, sizeof(svs.clients[i].lastcmd));
214 }
215 }
216
217
218 /*
219 SV_SpawnServer
220
221 Change the server to a new map, taking all connected clients along with it.
222 The serverstate parameter must either be ss_game or ss_demo. See SV_Map.
223 */
SV_SpawnServer(char * server,char * spawnpoint,server_state_t serverstate,int protocol)224 void SV_SpawnServer(char *server, char *spawnpoint,
225 server_state_t serverstate, int protocol){
226
227 int i;
228 unsigned checksum;
229 qboolean hardreconnect, initgame, changing;
230
231 Com_Printf("Server initialization..\n");
232
233 hardreconnect = protocol != sv.protocol;
234 initgame = changing = false;
235
236 if(!svs.initialized || Cvar_UpdateLatchedVars()){ // need reconnect
237 SV_Shutdown("Server restarting..\n", true, hardreconnect);
238 SV_InitGame(); // init clients, game progs, etc
239 initgame = true;
240 }
241 else { // server was initialized and no latched changes
242 if(hardreconnect) // protocol has changed
243 SV_BroadcastCommand("reconnect\n");
244 else {
245 SV_BroadcastCommand("changing\n");
246 changing = true;
247 }
248 SV_SendClientMessages();
249 }
250
251 if(sv.demofile)
252 fclose(sv.demofile);
253
254 svs.spawncount++; // any partially connected client will be restarted
255 svs.realtime = 0;
256
257 sv.state = ss_dead;
258 Com_SetServerState(sv.state);
259
260 // wipe the entire per-level structure
261 memset(&sv, 0, sizeof(sv));
262
263 if(serverstate == ss_demo || deathmatch->value)
264 Cvar_Set("paused", "0");
265
266 if(deathmatch->value){
267 sprintf(sv.configstrings[CS_AIRACCEL], "%g", sv_airaccelerate->value);
268 pm_airaccelerate = sv_airaccelerate->value;
269 } else {
270 strcpy(sv.configstrings[CS_AIRACCEL], "0");
271 pm_airaccelerate = 0;
272 }
273
274 SZ_Init(&sv.multicast, sv.multicast_buf, sizeof(sv.multicast_buf));
275
276 // leave slots at start for clients only
277 for(i = 0; i < maxclients->value; i++){
278 // needs to reconnect
279 if(svs.clients[i].state > cs_connected)
280 svs.clients[i].state = cs_connected;
281 svs.clients[i].lastframe = -1;
282 }
283
284 sv.time = 1000; // set time
285
286 sv.protocol = protocol; // force update cvar
287 Cvar_ForceSet("protocol", va("%d", sv.protocol));
288
289 // save name for levels that don't set message
290 strcpy(sv.name, server);
291 strcpy(sv.configstrings[CS_NAME], server);
292
293 if(serverstate == ss_demo){ // playing a demo, no map on the server
294 sv.models[1] = CM_LoadMap("", false, &checksum);
295 } else { // playing a game
296 Com_sprintf(sv.configstrings[CS_MODELS + 1], MAX_QPATH, "maps/%s.bsp", server);
297 sv.models[1] = CM_LoadMap(sv.configstrings[CS_MODELS + 1], false, &checksum);
298 }
299 Com_sprintf(sv.configstrings[CS_MAPCHECKSUM], MAX_QPATH, "%i", checksum);
300
301 // clear physics interaction links
302 SV_ClearWorld();
303
304 for(i = 1; i < CM_NumInlineModels(); i++){
305 Com_sprintf(sv.configstrings[CS_MODELS + 1 + i], MAX_QPATH, "*%i", i);
306 sv.models[i + 1] = CM_InlineModel(sv.configstrings[CS_MODELS + 1 + i]);
307 }
308
309 if(serverstate == ss_game){ // load and spawn all other entities
310
311 sv.state = ss_loading;
312 Com_SetServerState(sv.state);
313
314 ge->SpawnEntities(sv.name, CM_EntityString(), spawnpoint);
315
316 ge->RunFrame(); // run two frames to allow everything to settle
317 ge->RunFrame();
318
319 // create a baseline for more efficient communications
320 SV_CreateBaseline();
321 }
322
323 // all precaches are complete
324 sv.state = serverstate;
325 Com_SetServerState(sv.state);
326
327 // set serverinfo variable
328 Cvar_FullSet("mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET);
329
330 if(!initgame && hardreconnect){ // zombie clients so they may reconnect
331 for(i = 0; i < maxclients->value; i++){
332 if(svs.clients[i].state){
333 svs.clients[i].state = cs_zombie;
334 }
335 }
336 }
337 else if(changing){ // tell clients to reconnect
338 SV_BroadcastCommand("reconnect\n");
339 SV_SendClientMessages();
340 }
341
342 Com_Printf("Server initialized.\n");
343 }
344
345
346 /*
347 SV_Map
348
349 To be backwards compatible, Quetoo supports the full syntax:
350
351 map [*]<map>$<spawnpoint>+<nextserver>
352
353 However, if a +nextserver is present, it will be immediately
354 skipped to, as Quetoo does not support cinematics. String
355 may also specify a .dm2.
356
357 */
SV_Map(char * levelstring)358 void SV_Map(char *levelstring){
359 char level[MAX_QPATH];
360 char spawnpoint[MAX_QPATH];
361 server_state_t state;
362 int i, protocol;
363 char *ch;
364
365 strncpy(level, levelstring, sizeof(level)); // normally this is adequate, but..
366
367 // if there is a + in the map, skip the antecedent and jump to .bsp
368 if((ch = strstr(levelstring, "+")))
369 strncpy(level, ch + 1, sizeof(level));
370
371 // if there is a $, use the remainder as a spawnpoint
372 if((ch = strstr(level, "$"))){
373 strcpy(spawnpoint, ch + 1);
374 *ch = 0; // and truncate level
375 } else
376 spawnpoint[0] = 0;
377
378 // skip the end-of-unit flag if necessary
379 if(level[0] == '*')
380 strcpy(level, level + 1);
381
382 // hack for end game screen in coop mode
383 if(coop->value && !strcmp(levelstring, "victory.pcx"))
384 strcpy(level, "base1");
385
386 i = strlen(level);
387 state = i > 4 && !strcasecmp(level + i - 4, ".dm2") ? ss_demo : ss_game;
388
389 if(state == ss_demo){ // resolve next protocol to determine reconnect
390 protocol = SV_CheckDemo(level);
391 } else protocol = SV_CheckMap(level);
392
393 if(protocol == -1) // demo or map file didn't exist
394 return;
395
396 CL_Drop(); // make sure local client drops
397
398 Cbuf_CopyToDefer();
399 SV_SpawnServer(level, spawnpoint, state, protocol);
400 }
401