1 /**
2 * @file
3 * @brief Interface to the game library.
4 */
5
6 /*
7 All original material Copyright (C) 2002-2013 UFO: Alien Invasion.
8
9 Original file from Quake 2 v3.21: quake2-2.31/server/sv_game.c
10 Copyright (C) 1997-2001 Id Software, Inc.
11
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 as published by the Free Software Foundation; either version 2
15 of the License, or (at your option) any later version.
16
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20
21 See the GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26
27 */
28
29 #include "server.h"
30 #include "sv_log.h"
31 #include "../common/grid.h"
32 #include "../common/routing.h"
33 #include "../ports/system.h"
34 #include "../shared/scopedmutex.h"
35 #include <SDL.h>
36
37 /**
38 * @brief Debug print to server console
39 */
SV_dprintf(const char * fmt,...)40 static void SV_dprintf (const char* fmt, ...)
41 {
42 va_list ap;
43
44 va_start(ap, fmt);
45 SV_LogAdd(fmt, ap);
46 va_end(ap);
47 }
48
49 /**
50 * @brief Print to a single client
51 * @sa SV_BroadcastPrintf
52 */
SV_PlayerPrintf(const SrvPlayer * player,int level,const char * fmt,va_list ap)53 static void SV_PlayerPrintf (const SrvPlayer *player, int level, const char* fmt, va_list ap)
54 {
55 if (level == PRINT_NONE)
56 return;
57
58 if (player) {
59 char msg[1024];
60 Q_vsnprintf(msg, sizeof(msg), fmt, ap);
61 client_t* cl = SV_GetClient(player->getNum());
62 SV_ClientPrintf(cl, level, "%s", msg);
63 } else
64 SV_LogAdd(fmt, ap);
65 }
66
67 /**
68 * @brief Glue function to get the visibility from a given position
69 */
SV_GetVisibility(const pos3_t position)70 static float SV_GetVisibility (const pos3_t position)
71 {
72 return CM_GetVisibility(&sv->mapTiles, position);
73 }
74
75 static void SV_error (const char* fmt, ...) __attribute__((noreturn));
76 /**
77 * @brief Abort the server with a game error
78 * @note The error message should not have a newline - it's added inside of this function
79 */
SV_error(const char * fmt,...)80 static void SV_error (const char* fmt, ...)
81 {
82 char msg[1024];
83 va_list argptr;
84
85 va_start(argptr, fmt);
86 Q_vsnprintf(msg, sizeof(msg), fmt, argptr);
87 va_end(argptr);
88
89 Com_Error(ERR_DROP, "Game Error: %s", msg);
90 }
91
92 /**
93 * @brief Search the index in the config strings relative to a given start
94 * @param name The value of the config string to search the index for
95 * @param start The relative start point for the search
96 * @param max The max. searched entries in the config string before giving up
97 * @param create if @c true the value will get written into the config strings (appended)
98 * @return @c 0 if not found
99 */
SV_FindIndex(const char * name,int start,int max,bool create)100 static unsigned int SV_FindIndex (const char* name, int start, int max, bool create)
101 {
102 int i;
103
104 if (!name || !name[0])
105 return 0;
106
107 for (i = 1; i < max && SV_GetConfigString(start + i)[0] != '\0'; i++) {
108 const char* configString = SV_GetConfigString(start + i);
109 if (Q_streq(configString, name))
110 return i;
111 }
112
113 if (!create)
114 return 0;
115
116 if (i == max)
117 SV_error("*Index: overflow '%s' start: %i, max: %i", name, start, max);
118
119 SV_SetConfigString(start + i, name);
120
121 if (Com_ServerState() != ss_loading) { /* send the update to everyone */
122 dbuffer msg(4 + strlen(name));
123 NET_WriteByte(&msg, svc_configstring);
124 NET_WriteShort(&msg, start + i);
125 NET_WriteString(&msg, name);
126 SV_Multicast(~0, msg);
127 }
128
129 return i;
130 }
131
SV_ModelIndex(const char * name)132 static unsigned int SV_ModelIndex (const char* name)
133 {
134 return SV_FindIndex(name, CS_MODELS, MAX_MODELS, true);
135 }
136
137 /**
138 * @note Also sets mins and maxs for inline bmodels
139 * @sa CM_InlineModel
140 */
SV_SetModel(edict_t * ent,const char * name)141 static void SV_SetModel (edict_t* ent, const char* name)
142 {
143 if (!name)
144 SV_error("SV_SetModel: nullptr");
145
146 ent->modelindex = SV_ModelIndex(name);
147
148 /* if it is an inline model, get the size information for it */
149 if (name[0] == '*') {
150 const cBspModel_t* mod = CM_InlineModel(&sv->mapTiles, name);
151 /* Copy model mins and maxs to entity */
152 VectorCopy(mod->mins, ent->mins);
153 VectorCopy(mod->maxs, ent->maxs);
154 /* This is to help the entity collision code out */
155 /* Copy entity origin and angles to model*/
156 CM_SetInlineModelOrientation(&sv->mapTiles, name, ent->origin, ent->angles);
157 }
158 }
159
160 /**
161 * @sa CL_ParseConfigString
162 */
SV_Configstring(int index,const char * fmt,...)163 static void SV_Configstring (int index, const char* fmt, ...)
164 {
165 char val[MAX_TOKEN_CHARS * MAX_TILESTRINGS];
166 va_list argptr;
167
168 if (!Com_CheckConfigStringIndex(index))
169 SV_error("configstring: bad index %i", index);
170
171 va_start(argptr, fmt);
172 Q_vsnprintf(val, sizeof(val), fmt, argptr);
173 va_end(argptr);
174
175 SV_SetConfigString(index, val);
176
177 if (Com_ServerState() != ss_loading) { /* send the update to everyone */
178 dbuffer msg(4 + strlen(val));
179 NET_WriteByte(&msg, svc_configstring);
180 NET_WriteShort(&msg, index);
181 NET_WriteString(&msg, val);
182
183 /* send to all clients */
184 SV_Multicast(~0, msg);
185 }
186 }
187
SV_WriteChar(char c)188 static void SV_WriteChar (char c)
189 {
190 NET_WriteChar(sv->pendingEvent.buf, c);
191 }
192
SV_WriteByte(byte c)193 static void SV_WriteByte (byte c)
194 {
195 NET_WriteByte(sv->pendingEvent.buf, c);
196 }
197
SV_WriteShort(int c)198 static void SV_WriteShort (int c)
199 {
200 NET_WriteShort(sv->pendingEvent.buf, c);
201 }
202
SV_WriteLong(int c)203 static void SV_WriteLong (int c)
204 {
205 NET_WriteLong(sv->pendingEvent.buf, c);
206 }
207
SV_WriteString(const char * s)208 static void SV_WriteString (const char* s)
209 {
210 NET_WriteString(sv->pendingEvent.buf, s);
211 }
212
SV_WritePos(const vec3_t pos)213 static void SV_WritePos (const vec3_t pos)
214 {
215 NET_WritePos(sv->pendingEvent.buf, pos);
216 }
217
SV_WriteGPos(const pos3_t pos)218 static void SV_WriteGPos (const pos3_t pos)
219 {
220 NET_WriteGPos(sv->pendingEvent.buf, pos);
221 }
222
SV_WriteDir(const vec3_t dir)223 static void SV_WriteDir (const vec3_t dir)
224 {
225 NET_WriteDir(sv->pendingEvent.buf, dir);
226 }
227
SV_WriteAngle(float f)228 static void SV_WriteAngle (float f)
229 {
230 NET_WriteAngle(sv->pendingEvent.buf, f);
231 }
232
SV_WriteFormat(const char * format,...)233 static void SV_WriteFormat (const char* format, ...)
234 {
235 va_list ap;
236 va_start(ap, format);
237 NET_vWriteFormat(sv->pendingEvent.buf, format, ap);
238 va_end(ap);
239 }
240
SV_ReadChar(void)241 static int SV_ReadChar (void)
242 {
243 return NET_ReadChar(sv->messageBuffer);
244 }
245
SV_ReadByte(void)246 static int SV_ReadByte (void)
247 {
248 return NET_ReadByte(sv->messageBuffer);
249 }
250
SV_ReadShort(void)251 static int SV_ReadShort (void)
252 {
253 return NET_ReadShort(sv->messageBuffer);
254 }
255
SV_ReadLong(void)256 static int SV_ReadLong (void)
257 {
258 return NET_ReadLong(sv->messageBuffer);
259 }
260
SV_ReadString(char * str,size_t length)261 static int SV_ReadString (char* str, size_t length)
262 {
263 return NET_ReadString(sv->messageBuffer, str, length);
264 }
265
SV_ReadPos(vec3_t pos)266 static void SV_ReadPos (vec3_t pos)
267 {
268 NET_ReadPos(sv->messageBuffer, pos);
269 }
270
SV_ReadGPos(pos3_t pos)271 static void SV_ReadGPos (pos3_t pos)
272 {
273 NET_ReadGPos(sv->messageBuffer, pos);
274 }
275
SV_ReadDir(vec3_t vector)276 static void SV_ReadDir (vec3_t vector)
277 {
278 NET_ReadDir(sv->messageBuffer, vector);
279 }
280
SV_ReadAngle(void)281 static float SV_ReadAngle (void)
282 {
283 return NET_ReadAngle(sv->messageBuffer);
284 }
285
SV_ReadData(void * buffer,int size)286 static void SV_ReadData (void* buffer, int size)
287 {
288 NET_ReadData(sv->messageBuffer, buffer, size);
289 }
290
291 /**
292 * @sa NET_vReadFormat
293 */
SV_ReadFormat(const char * format,...)294 static void SV_ReadFormat (const char* format, ...)
295 {
296 va_list ap;
297
298 assert(format);
299 if (!*format) /* PA_NULL */
300 return;
301
302 va_start(ap, format);
303 NET_vReadFormat(sv->messageBuffer, format, ap);
304 va_end(ap);
305 }
306
307 /**
308 * @sa gi.AbortEvents
309 */
SV_AbortEvents(void)310 static void SV_AbortEvents (void)
311 {
312 pending_event_t* p = &sv->pendingEvent;
313
314 if (!p->pending)
315 return;
316
317 p->pending = false;
318 delete p->buf;
319 p->buf = nullptr;
320 }
321
SV_SendQueuedEvents(void)322 static void SV_SendQueuedEvents (void)
323 {
324 for (int i = 0; i < sv->eventQueuePos; i++) {
325 pending_event_t &entry = sv->eventQueue[i];
326 NET_WriteByte(entry.buf, EV_NULL);
327 SV_Multicast(entry.playerMask, *entry.buf);
328 delete entry.buf;
329 }
330 sv->eventQueuePos = 0;
331 }
332
333 /**
334 * @sa gi.EndEvents
335 */
SV_EndEvents(void)336 static void SV_EndEvents (void)
337 {
338 pending_event_t* p = &sv->pendingEvent;
339
340 if (!p->pending) {
341 SV_SendQueuedEvents();
342 return;
343 }
344
345 NET_WriteByte(p->buf, EV_NULL);
346 SV_Multicast(p->playerMask, *p->buf);
347 p->pending = false;
348 delete p->buf;
349 p->buf = nullptr;
350
351 SV_SendQueuedEvents();
352 }
353
354 typedef struct {
355 const char* name;
356 } eventNames_t;
357
358 #define M(x) { #x }
359 static const eventNames_t eventNames[] = {
360 M(EV_NULL),
361 M(EV_RESET),
362 M(EV_START),
363 M(EV_ENDROUND),
364 M(EV_ENDROUNDANNOUNCE),
365
366 M(EV_RESULTS),
367 M(EV_CENTERVIEW),
368 M(EV_MOVECAMERA),
369
370 M(EV_ENT_APPEAR),
371 M(EV_ENT_PERISH),
372 M(EV_ENT_DESTROY),
373 M(EV_ADD_BRUSH_MODEL),
374 M(EV_ADD_EDICT),
375
376 M(EV_ACTOR_APPEAR),
377 M(EV_ACTOR_ADD),
378 M(EV_ACTOR_TURN),
379 M(EV_ACTOR_MOVE),
380 M(EV_ACTOR_REACTIONFIRECHANGE),
381 M(EV_ACTOR_REACTIONFIREADDTARGET),
382 M(EV_ACTOR_REACTIONFIREREMOVETARGET),
383 M(EV_ACTOR_REACTIONFIRETARGETUPDATE),
384
385 M(EV_ACTOR_START_SHOOT),
386 M(EV_ACTOR_SHOOT),
387 M(EV_ACTOR_SHOOT_HIDDEN),
388 M(EV_ACTOR_THROW),
389 M(EV_ACTOR_END_SHOOT),
390
391 M(EV_ACTOR_DIE),
392 M(EV_ACTOR_REVITALISED),
393 M(EV_ACTOR_STATS),
394 M(EV_ACTOR_STATECHANGE),
395 M(EV_ACTOR_RESERVATIONCHANGE),
396 M(EV_ACTOR_WOUND),
397
398 M(EV_INV_ADD),
399 M(EV_INV_DEL),
400 M(EV_INV_AMMO),
401 M(EV_INV_RELOAD),
402 M(EV_INV_TRANSFER),
403
404 M(EV_MODEL_EXPLODE),
405 M(EV_MODEL_EXPLODE_TRIGGERED),
406
407 M(EV_PARTICLE_APPEAR),
408 M(EV_PARTICLE_SPAWN),
409
410 M(EV_SOUND),
411
412 M(EV_DOOR_OPEN),
413 M(EV_DOOR_CLOSE),
414 M(EV_CLIENT_ACTION),
415 M(EV_RESET_CLIENT_ACTION),
416
417 M(EV_CAMERA_APPEAR)
418 };
419 #undef M
420 CASSERT(lengthof(eventNames) == EV_NUM_EVENTS);
421
422 /**
423 * @sa gi.AddEvent
424 * @param[in] mask The player bitmask to send the events to. Use @c PM_ALL to send to every connected player.
425 */
SV_AddEvent(unsigned int mask,int eType,int entnum)426 static void SV_AddEvent (unsigned int mask, int eType, int entnum)
427 {
428 pending_event_t* p = &sv->pendingEvent;
429 const int rawType = eType &~ EVENT_INSTANTLY;
430
431 if (rawType >= EV_NUM_EVENTS || rawType < 0)
432 Com_Error(ERR_DROP, "SV_AddEvent: invalid event %i", rawType);
433
434 const char* eventName = eventNames[rawType].name;
435 Com_DPrintf(DEBUG_EVENTSYS, "Event type: %s (%i - %i) (mask %s) (entnum: %i)\n", eventName,
436 rawType, eType, Com_UnsignedIntToBinary(mask), entnum);
437
438 /* finish the last event */
439 if (p->pending)
440 SV_EndEvents();
441 else
442 SV_SendQueuedEvents();
443
444 /* start the new event */
445 p->pending = true;
446 p->playerMask = mask;
447 p->type = eType;
448 p->entnum = entnum;
449 p->buf = new dbuffer();
450
451 /* write header */
452 NET_WriteByte(p->buf, svc_event);
453 NET_WriteByte(p->buf, eType);
454 if (entnum != -1)
455 NET_WriteShort(p->buf, entnum);
456 }
457 /**
458 * @sa gi.QueueEvent
459 * @param[in] mask The player bitmask to send the events to. Use @c PM_ALL to send to every connected player.
460 */
SV_QueueEvent(unsigned int mask,int eType,int entnum)461 static void SV_QueueEvent (unsigned int mask, int eType, int entnum)
462 {
463 if (sv->eventQueuePos > lengthof(sv->eventQueue))
464 Com_Error(ERR_DROP, "overflow in SV_QueueEvent");
465
466 pending_event_t &p = sv->eventQueue[sv->eventQueuePos++];
467
468 /* start the new event */
469 p.pending = false;
470 p.playerMask = mask;
471 p.type = eType;
472 p.entnum = entnum;
473 p.buf = new dbuffer();
474 /* write header */
475 NET_WriteByte(p.buf, svc_event);
476 NET_WriteByte(p.buf, eType);
477 if (p.entnum != -1)
478 NET_WriteShort(p.buf, p.entnum);
479 }
480
SV_QueueWriteByte(byte c)481 static void SV_QueueWriteByte (byte c)
482 {
483 NET_WriteByte(sv->eventQueue[sv->eventQueuePos - 1].buf, c);
484 }
485
SV_QueueWriteString(const char * s)486 static void SV_QueueWriteString (const char* s)
487 {
488 NET_WriteString(sv->eventQueue[sv->eventQueuePos - 1].buf, s);
489 }
490
SV_QueueWritePos(const vec3_t pos)491 static void SV_QueueWritePos (const vec3_t pos)
492 {
493 NET_WritePos(sv->eventQueue[sv->eventQueuePos - 1].buf, pos);
494 }
495
SV_QueueWriteShort(int c)496 static void SV_QueueWriteShort (int c)
497 {
498 NET_WriteShort(sv->eventQueue[sv->eventQueuePos - 1].buf, c);
499 }
500
501 /**
502 * @return The current active event or -1 if no event is active at the moment
503 */
SV_GetEvent(void)504 static int SV_GetEvent (void)
505 {
506 const pending_event_t* p = &sv->pendingEvent;
507 if (!p->pending)
508 return -1;
509
510 return p->type;
511 }
512
SV_GetEventEdict(void)513 static edict_t* SV_GetEventEdict (void)
514 {
515 const pending_event_t* p = &sv->pendingEvent;
516 if (!p->pending)
517 return nullptr;
518
519 if (p->entnum == -1)
520 return nullptr;
521
522 const sv_edict_t &e = sv->edicts[p->entnum];
523 return e.ent;
524 }
525
526 /**
527 * @brief Makes sure the game DLL does not use client, or signed tags
528 */
SV_TagAlloc(int size,int tagNum,const char * file,int line)529 static void* SV_TagAlloc (int size, int tagNum, const char* file, int line)
530 {
531 if (tagNum < 0)
532 tagNum *= -1;
533
534 return _Mem_Alloc(size, true, sv->gameSysPool, tagNum, file, line);
535 }
536
SV_MemFree(void * ptr,const char * file,int line)537 static void SV_MemFree (void* ptr, const char* file, int line)
538 {
539 _Mem_Free(ptr, file, line);
540 }
541
542 /**
543 * @brief Makes sure the game DLL does not use client, or signed tags
544 */
SV_FreeTags(int tagNum,const char * file,int line)545 static void SV_FreeTags (int tagNum, const char* file, int line)
546 {
547 if (tagNum < 0)
548 tagNum *= -1;
549
550 _Mem_FreeTag(sv->gameSysPool, tagNum, file, line);
551 }
552
SV_TestLine(const vec3_t start,const vec3_t stop,const int levelmask)553 static bool SV_TestLine (const vec3_t start, const vec3_t stop, const int levelmask)
554 {
555 return TR_TestLine(&sv->mapTiles, start, stop, levelmask);
556 }
557
SV_TestLineWithEnt(const vec3_t start,const vec3_t stop,const int levelmask,const char ** entlist)558 static bool SV_TestLineWithEnt (const vec3_t start, const vec3_t stop, const int levelmask, const char** entlist)
559 {
560 /* do the line test */
561 const bool hit = CM_EntTestLine(&sv->mapTiles, start, stop, levelmask, entlist);
562 return hit;
563 }
564
SV_GridFall(const int actorSize,const pos3_t pos)565 static pos_t SV_GridFall (const int actorSize, const pos3_t pos)
566 {
567 return Grid_Fall(sv->mapData.routing, actorSize, pos);
568 }
569
SV_RecalcRouting(const char * name,const GridBox & box,const char ** list)570 static void SV_RecalcRouting (const char* name, const GridBox& box, const char** list)
571 {
572 Grid_RecalcRouting(&sv->mapTiles, sv->mapData.routing, name, box, list);
573 }
574
SV_GridPosToVec(const int actorSize,const pos3_t pos,vec3_t vec)575 static void SV_GridPosToVec (const int actorSize, const pos3_t pos, vec3_t vec)
576 {
577 Grid_PosToVec(sv->mapData.routing, actorSize, pos, vec);
578 }
579
SV_GridCalcPathing(actorSizeEnum_t actorSize,pathing_t * path,const pos3_t from,int distance,pos_t ** forbiddenList,int forbiddenListLength)580 static void SV_GridCalcPathing (actorSizeEnum_t actorSize, pathing_t* path, const pos3_t from, int distance, pos_t** forbiddenList, int forbiddenListLength)
581 {
582 Grid_CalcPathing(sv->mapData.routing, actorSize, path, from, distance, forbiddenList, forbiddenListLength);
583 }
584
SV_CanActorStandHere(const int actorSize,const pos3_t pos)585 static bool SV_CanActorStandHere (const int actorSize, const pos3_t pos)
586 {
587 return RT_CanActorStandHere(sv->mapData.routing, actorSize, pos);
588 }
589
SV_SetInlineModelOrientation(const char * name,const vec3_t origin,const vec3_t angles)590 static void SV_SetInlineModelOrientation (const char* name, const vec3_t origin, const vec3_t angles)
591 {
592 CM_SetInlineModelOrientation(&sv->mapTiles, name, origin, angles);
593 }
594
SV_GetInlineModelAABB(const char * name,AABB & aabb)595 static void SV_GetInlineModelAABB (const char* name, AABB& aabb)
596 {
597 CM_GetInlineModelAABB(&sv->mapTiles, name, aabb);
598 }
599
SV_UnloadGame(void)600 static void SV_UnloadGame (void)
601 {
602 #ifndef HARD_LINKED_GAME
603 if (svs.gameLibrary) {
604 Com_Printf("Unload the game library\n");
605 SDL_UnloadObject(svs.gameLibrary);
606 }
607 #endif
608 }
609
610 #ifndef HARD_LINKED_GAME
SV_LoadGame(const char * path)611 static bool SV_LoadGame (const char* path)
612 {
613 char name[MAX_OSPATH];
614
615 Com_sprintf(name, sizeof(name), "%s/game_" CPUSTRING ".%s", path, SO_EXT);
616 svs.gameLibrary = SDL_LoadObject(name);
617 if (!svs.gameLibrary) {
618 Com_sprintf(name, sizeof(name), "%s/game.%s", path, SO_EXT);
619 svs.gameLibrary = SDL_LoadObject(name);
620 }
621
622 if (svs.gameLibrary) {
623 Com_Printf("found at '%s'\n", path);
624 return true;
625 } else {
626 Com_Printf("not found at '%s'\n", path);
627 Com_DPrintf(DEBUG_SYSTEM, "%s\n", SDL_GetError());
628 return false;
629 }
630 }
631 #endif
632
633 /**
634 * @brief Loads the game shared library and calls the api init function
635 */
SV_GetGameAPI(game_import_t * parms)636 static game_export_t* SV_GetGameAPI (game_import_t* parms)
637 {
638 #ifndef HARD_LINKED_GAME
639 typedef game_export_t* (*game_api_t) (game_import_t*);
640 game_api_t GetGameAPI;
641 const char* path;
642
643 if (svs.gameLibrary)
644 Com_Error(ERR_FATAL, "SV_GetGameAPI without SV_UnloadGame");
645
646 Com_Printf("------- Loading game.%s -------\n", SO_EXT);
647
648 #ifdef PKGLIBDIR
649 SV_LoadGame(PKGLIBDIR);
650 #endif
651
652 /* now run through the search paths */
653 path = nullptr;
654 while (!svs.gameLibrary) {
655 path = FS_NextPath(path);
656 if (!path)
657 /* couldn't find one anywhere */
658 return nullptr;
659 else if (SV_LoadGame(path))
660 break;
661 }
662
663 GetGameAPI = (game_api_t)(uintptr_t)SDL_LoadFunction(svs.gameLibrary, "GetGameAPI");
664 if (!GetGameAPI) {
665 SV_UnloadGame();
666 return nullptr;
667 }
668 #endif
669
670 return GetGameAPI(parms);
671 }
672
673 static char const* const gameSysPoolName = "Server: Game system";
674
675 /**
676 * @brief Called when either the entire server is being killed, or it is changing to a different game directory.
677 * @sa G_Shutdown
678 * @sa SV_InitGameProgs
679 */
SV_ShutdownGameProgs(void)680 void SV_ShutdownGameProgs (void)
681 {
682 uint32_t size;
683
684 if (!svs.ge)
685 return;
686
687 Com_SetServerState(ss_game_shutdown);
688
689 if (svs.gameThread) {
690 SDL_CondSignal(svs.gameFrameCond);
691 SDL_WaitThread(svs.gameThread, nullptr);
692 SDL_DestroyCond(svs.gameFrameCond);
693 svs.gameFrameCond = nullptr;
694 svs.gameThread = nullptr;
695 }
696
697 svs.ge->Shutdown();
698
699 size = Mem_PoolSize(sv->gameSysPool);
700 if (size > 0) {
701 Com_Printf("WARNING: Game memory leak (%u bytes)\n", size);
702 Cmd_ExecuteString("mem_stats %s", gameSysPoolName);
703 }
704
705 Mem_DeletePool(sv->gameSysPool);
706 sv->gameSysPool = nullptr;
707
708 SV_UnloadGame();
709
710 svs.ge = nullptr;
711 }
712
713 /**
714 * @brief Thread for the game frame function
715 * @sa SV_RunGameFrame
716 * @sa SV_Frame
717 */
SV_RunGameFrameThread(void * data)718 int SV_RunGameFrameThread (void* data)
719 {
720 do {
721 const ScopedMutex scopedMutex(svs.serverMutex);
722 SDL_CondWait(svs.gameFrameCond, svs.serverMutex);
723 SV_RunGameFrame();
724 } while (!sv->endgame);
725
726 return 0;
727 }
728
729 /**
730 * @brief Calls the G_RunFrame function from game api
731 * let everything in the world think and move
732 * @sa G_RunFrame
733 * @sa SV_Frame
734 */
SV_RunGameFrame(void)735 void SV_RunGameFrame (void)
736 {
737 sv->endgame = svs.ge->RunFrame();
738 if (sv->state == ss_game_shutdown)
739 sv->endgame = true;
740 }
741
742 /**
743 * @brief Init the game subsystem for a new map
744 * @sa SV_ShutdownGameProgs
745 */
SV_InitGameProgs(void)746 void SV_InitGameProgs (void)
747 {
748 game_import_t import;
749
750 /* unload anything we have now */
751 /*SV_ShutdownGameProgs();*/
752
753 /* load a new game dll */
754 import.BroadcastPrintf = SV_BroadcastPrintf;
755 import.DPrintf = SV_dprintf;
756 import.PlayerPrintf = SV_PlayerPrintf;
757 import.Error = SV_error;
758
759 import.Trace = SV_Trace;
760 import.LinkEdict = SV_LinkEdict;
761 import.UnlinkEdict = SV_UnlinkEdict;
762 import.BoxEdicts = SV_AreaEdicts;
763
764 import.TestLine = SV_TestLine;
765 import.TestLineWithEnt = SV_TestLineWithEnt;
766 import.GrenadeTarget = Com_GrenadeTarget;
767
768 import.GridCalcPathing = SV_GridCalcPathing;
769 import.MoveStore = Grid_MoveStore;
770 import.MoveLength = Grid_MoveLength;
771 import.MoveNext = Grid_MoveNext;
772 import.GetTUsForDirection = Grid_GetTUsForDirection;
773 import.GridFall = SV_GridFall;
774 import.GridPosToVec = SV_GridPosToVec;
775 import.GridRecalcRouting = SV_RecalcRouting;
776 import.CanActorStandHere = SV_CanActorStandHere;
777 import.GridShouldUseAutostand = Grid_ShouldUseAutostand;
778
779 import.GetVisibility = SV_GetVisibility;
780
781 import.ModelIndex = SV_ModelIndex;
782
783 import.SetInlineModelOrientation = SV_SetInlineModelOrientation;
784 import.GetInlineModelAABB = SV_GetInlineModelAABB;
785
786 import.SetModel = SV_SetModel;
787
788 import.ConfigString = SV_Configstring;
789
790 import.PointContents = SV_PointContents;
791 import.GetFootstepSound = SV_GetFootstepSound;
792 import.GetBounceFraction = SV_GetBounceFraction;
793 import.LoadModelAABB = SV_LoadModelAABB;
794
795 import.FS_Gamedir = FS_Gamedir;
796 import.FS_LoadFile = FS_LoadFile;
797 import.FS_FreeFile = FS_FreeFile;
798
799 import.WriteChar = SV_WriteChar;
800 import.WriteByte = SV_WriteByte;
801 import.WriteShort = SV_WriteShort;
802 import.WriteLong = SV_WriteLong;
803 import.WriteString = SV_WriteString;
804 import.WritePos = SV_WritePos;
805 import.WriteGPos = SV_WriteGPos;
806 import.WriteDir = SV_WriteDir;
807 import.WriteAngle = SV_WriteAngle;
808 import.WriteFormat = SV_WriteFormat;
809
810 import.AbortEvents = SV_AbortEvents;
811 import.EndEvents = SV_EndEvents;
812 import.AddEvent = SV_AddEvent;
813 import.GetEvent = SV_GetEvent;
814 import.GetEventEdict = SV_GetEventEdict;
815
816 import.QueueEvent = SV_QueueEvent;
817 import.QueueWriteByte = SV_QueueWriteByte;
818 import.QueueWritePos = SV_QueueWritePos;
819 import.QueueWriteString = SV_QueueWriteString;
820 import.QueueWriteShort = SV_QueueWriteShort;
821
822 import.ReadChar = SV_ReadChar;
823 import.ReadByte = SV_ReadByte;
824 import.ReadShort = SV_ReadShort;
825 import.ReadLong = SV_ReadLong;
826 import.ReadString = SV_ReadString;
827 import.ReadPos = SV_ReadPos;
828 import.ReadGPos = SV_ReadGPos;
829 import.ReadDir = SV_ReadDir;
830 import.ReadAngle = SV_ReadAngle;
831 import.ReadData = SV_ReadData;
832 import.ReadFormat = SV_ReadFormat;
833
834 import.GetConstInt = Com_GetConstInt;
835 import.GetConstIntFromNamespace = Com_GetConstIntFromNamespace;
836 import.GetConstVariable = Com_GetConstVariable;
837 import.RegisterConstInt = Com_RegisterConstInt;
838 import.UnregisterConstVariable = Com_UnregisterConstVariable;
839
840 import.GetCharacterValues = Com_GetCharacterValues;
841
842 import.TagMalloc = SV_TagAlloc;
843 import.TagFree = SV_MemFree;
844 import.FreeTags = SV_FreeTags;
845
846 import.Cvar_Get = Cvar_Get;
847 import.Cvar_Set = Cvar_Set;
848 import.Cvar_String = Cvar_GetString;
849
850 import.Cmd_Argc = Cmd_Argc;
851 import.Cmd_Argv = Cmd_Argv;
852 import.Cmd_Args = Cmd_Args;
853 import.AddCommandString = Cbuf_AddText;
854
855 import.seed = Sys_Milliseconds();
856 import.csi = &csi;
857
858 Com_Printf("setting game random seed to %i\n", import.seed);
859
860 svs.ge = SV_GetGameAPI(&import);
861
862 if (!svs.ge)
863 Com_Error(ERR_DROP, "failed to load game library");
864 if (svs.ge->apiversion != GAME_API_VERSION)
865 Com_Error(ERR_DROP, "game is version %i, not %i", svs.ge->apiversion, GAME_API_VERSION);
866
867 sv->gameSysPool = Mem_CreatePool(gameSysPoolName);
868
869 svs.ge->Init();
870
871 if (sv_threads->integer) {
872 svs.gameFrameCond = SDL_CreateCond();
873 #if SDL_VERSION_ATLEAST(2,0,0)
874 svs.gameThread = SDL_CreateThread(SV_RunGameFrameThread, "GameThread", nullptr);
875 #else
876 svs.gameThread = SDL_CreateThread(SV_RunGameFrameThread, nullptr);
877 #endif
878 }
879 }
880