1 /**
2  * @file
3  * @brief Shared game type code
4  */
5 
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "cl_game.h"
27 #include "../client.h"
28 #include "../cl_language.h"
29 #include "cl_game_team.h"
30 #include "../battlescape/cl_localentity.h"
31 #include "../battlescape/cl_hud.h"
32 #include "../battlescape/cl_parse.h"
33 #include "../ui/ui_main.h"
34 #include "../ui/ui_draw.h"
35 #include "../ui/ui_nodes.h"
36 #include "../ui/ui_popup.h"
37 #include "../ui/ui_render.h"
38 #include "../ui/ui_windows.h"
39 #include "../ui/ui_sprite.h"
40 #include "../ui/ui_font.h"
41 #include "../ui/ui_tooltip.h"
42 #include "../ui/node/ui_node_container.h"
43 #include "../ui/node/ui_node_messagelist.h"
44 #include "../ui/node/ui_node_model.h"
45 #include "../cl_team.h"
46 #include "../web/web_cgame.h"
47 #include "../battlescape/events/e_main.h"
48 #include "../cl_inventory.h"
49 #include "../../shared/parse.h"
50 #include "../../common/filesys.h"
51 #include "../renderer/r_draw.h"
52 #include "../renderer/r_framebuffer.h"
53 #include "../renderer/r_geoscape.h"
54 
55 #include <set>
56 #include <string>
57 
58 #define MAX_CGAMETYPES 16
59 static cgameType_t cgameTypes[MAX_CGAMETYPES];
60 static int numCGameTypes;
61 
GAME_GetCurrentType(void)62 static inline const cgame_export_t* GAME_GetCurrentType (void)
63 {
64 	return cls.gametype;
65 }
66 
67 class GAMECvarListener: public CvarListener
68 {
69 private:
70 	typedef std::set<const struct cvar_s* > GameCvars;
71 	GameCvars _cvars;
72 public:
~GAMECvarListener()73 	~GAMECvarListener ()
74 	{
75 		_cvars.clear();
76 	}
77 
onCreate(const struct cvar_s * cvar)78 	void onCreate (const struct cvar_s* cvar)
79 	{
80 		const cgame_export_t* list = GAME_GetCurrentType();
81 		if (list)
82 			_cvars.insert(cvar);
83 	}
84 
onDelete(const struct cvar_s * cvar)85 	void onDelete (const struct cvar_s* cvar)
86 	{
87 		_cvars.erase(cvar);
88 	}
89 
onGameModeChange()90 	void onGameModeChange ()
91 	{
92 		GameCvars copy = _cvars;
93 		for (GameCvars::const_iterator i = copy.begin(); i != copy.end(); ++i) {
94 			const struct cvar_s* cvar = *i;
95 			if (cvar->flags == 0) {
96 				const cgame_export_t* list = GAME_GetCurrentType();
97 				Com_DPrintf(DEBUG_CLIENT, "Delete cvar %s because it was created in the context of the cgame %s\n",
98 						cvar->name, list ? list->name : "none");
99 				Cvar_Delete(cvar->name);
100 			}
101 		}
102 	}
103 };
104 
105 static SharedPtr<GAMECvarListener> cvarListener(new GAMECvarListener());
106 
107 /* @todo: remove me - this should be per geoscape node data */
108 geoscapeData_t geoscapeData;
109 
110 #ifdef HARD_LINKED_CGAME
111 #include "campaign/cl_game_campaign.h"
112 #include "multiplayer/cl_game_multiplayer.h"
113 #include "skirmish/cl_game_skirmish.h"
114 
115 static const cgame_api_t gameTypeList[] = {
116 	GetCGameMultiplayerAPI,
117 	GetCGameCampaignAPI,
118 	GetCGameSkirmishAPI
119 };
120 
121 static const char* cgameMenu;
122 
GetCGameAPI(const cgame_import_t * import)123 const cgame_export_t* GetCGameAPI (const cgame_import_t* import)
124 {
125 	const size_t len = lengthof(gameTypeList);
126 	int i;
127 
128 	if (cgameMenu == nullptr)
129 		return nullptr;
130 
131 	for (i = 0; i < len; i++) {
132 		const cgame_api_t list = gameTypeList[i];
133 		const cgame_export_t* cgame = list(import);
134 		if (Q_streq(cgame->menu, cgameMenu)) {
135 			return cgame;
136 		}
137 	}
138 
139 	return nullptr;
140 }
141 #endif
142 
143 static equipDef_t equipDefStandard;
144 
145 /**
146  * @brief static character array that can be used by a game mode to store the needed character values.
147  */
148 static character_t characters[MAX_ACTIVETEAM];
149 
150 /**
151  * @brief Returns a character that can be used to store the game type specific character values
152  * @note The returned pointer is a reference to static memory
153  * @param index The index of the character array. This value must be greater than 0 and not bigger than the
154  * value @c GAME_GetCharacterArraySize returned
155  * @sa GAME_GetCharacterArraySize
156  * @sa GAME_ResetCharacters
157  * @return A character slot
158  */
GAME_GetCharacter(int index)159 character_t* GAME_GetCharacter (int index)
160 {
161 	if (index < 0 || index >= lengthof(characters))
162 		Com_Error(ERR_DROP, "Out of bounds character access");
163 
164 	return &characters[index];
165 }
166 
167 /**
168  * @brief Returns a character that can be used to store the game type specific character values
169  * @note The returned pointer is a reference to static memory
170  * @param ucn The unique character number
171  * @return @c null if no character with the specified ucn was found.
172  */
GAME_GetCharacterByUCN(int ucn)173 character_t* GAME_GetCharacterByUCN (int ucn)
174 {
175 	const int size = lengthof(characters);
176 	for (int i = 0; i < size; i++) {
177 		character_t* chr = &characters[i];
178 		if (chr->ucn == ucn)
179 			return chr;
180 	}
181 	return nullptr;
182 }
183 
184 /**
185  * @return The size of the static character array
186  * @sa GAME_GetCharacter
187  * @sa GAME_ResetCharacters
188  */
GAME_GetCharacterArraySize(void)189 size_t GAME_GetCharacterArraySize (void)
190 {
191 	return lengthof(characters);
192 }
193 
GAME_GetCurrentName(void)194 const char* GAME_GetCurrentName (void)
195 {
196 	const cgame_export_t* cgame = GAME_GetCurrentType();
197 	if (cgame == nullptr)
198 		return nullptr;
199 	return cgame->menu;
200 }
201 /**
202  * @brief Reset all characters in the static character array
203  * @sa GAME_GetCharacterArraySize
204  * @sa GAME_GetCharacter
205  */
GAME_ResetCharacters(void)206 void GAME_ResetCharacters (void)
207 {
208 	for (int i = 0; i < MAX_ACTIVETEAM; i++)
209 		characters[i].init();
210 	LIST_Delete(&chrDisplayList);
211 	UI_ExecuteConfunc("team_membersclear");
212 }
213 
GAME_AppendTeamMember(int memberIndex,const char * teamDefID,const equipDef_t * ed)214 void GAME_AppendTeamMember (int memberIndex, const char* teamDefID, const equipDef_t* ed)
215 {
216 	character_t* chr;
217 
218 	if (ed == nullptr)
219 		Com_Error(ERR_DROP, "No equipment definition given");
220 
221 	chr = GAME_GetCharacter(memberIndex);
222 
223 	CL_GenerateCharacter(chr, teamDefID);
224 	/* pack equipment */
225 	cls.i.EquipActor(chr, ed, GAME_GetChrMaxLoad(chr));
226 
227 	LIST_AddPointer(&chrDisplayList, (void*)chr);
228 
229 	UI_ExecuteConfunc("team_memberadd %i \"%s\" \"%s\" %i", memberIndex, chr->name, chr->head, chr->headSkin);
230 }
231 
GAME_GenerateTeam(const char * teamDefID,const equipDef_t * ed,int teamMembers)232 void GAME_GenerateTeam (const char* teamDefID, const equipDef_t* ed, int teamMembers)
233 {
234 	int i;
235 
236 	if (teamMembers > GAME_GetCharacterArraySize())
237 		Com_Error(ERR_DROP, "More than the allowed amount of team members");
238 
239 	if (ed == nullptr)
240 		Com_Error(ERR_DROP, "No equipment definition given");
241 
242 	GAME_ResetCharacters();
243 
244 	for (i = 0; i < teamMembers; i++)
245 		GAME_AppendTeamMember(i, teamDefID, ed);
246 }
247 
GAME_ReloadMode(void)248 void GAME_ReloadMode (void)
249 {
250 	const cgame_export_t* list = GAME_GetCurrentType();
251 	if (list != nullptr) {
252 		GAME_SetMode(nullptr);
253 		GAME_SetMode(list);
254 	}
255 }
256 
GAME_IsMultiplayer(void)257 bool GAME_IsMultiplayer (void)
258 {
259 	const cgame_export_t* list = GAME_GetCurrentType();
260 	if (list != nullptr) {
261 		const bool isMultiplayer = list->isMultiplayer == 1;
262 		return isMultiplayer;
263 	}
264 
265 	return false;
266 }
267 
268 /**
269  * @brief This is called when a client quits the battlescape
270  * @sa GAME_StartBattlescape
271  */
GAME_EndBattlescape(void)272 void GAME_EndBattlescape (void)
273 {
274 	Cvar_Set("cl_onbattlescape", "0");
275 	Com_Printf("Used inventory slots after battle: %i\n", cls.i.GetUsedSlots());
276 }
277 
278 /**
279  * @brief Send end round announcements
280  * @param playerNum The server player number of the player that has ended the round
281  * @param team The team the player is in
282  */
GAME_EndRoundAnnounce(int playerNum,int team)283 void GAME_EndRoundAnnounce (int playerNum, int team)
284 {
285 	/** @todo do we need the team number here? isn't the playernum enough to get the team? */
286 	const cgame_export_t* list = GAME_GetCurrentType();
287 	if (list != nullptr && list->EndRoundAnnounce)
288 		list->EndRoundAnnounce(playerNum, team);
289 }
290 
291 /**
292  * @brief Shows game type specific item information (if it's not resolvable via @c objDef_t).
293  * @param[in] node The menu node to show the information in.
294  * @param[in] string The id of the 'thing' to show information for.
295  */
GAME_DisplayItemInfo(uiNode_t * node,const char * string)296 void GAME_DisplayItemInfo (uiNode_t* node, const char* string)
297 {
298 	const cgame_export_t* list = GAME_GetCurrentType();
299 	if (list != nullptr && list->GetModelForItem) {
300 		const char* model = list->GetModelForItem(string);
301 		UI_DrawModelNode(node, model);
302 	}
303 }
304 
GAME_SetServerInfo(const char * server,const char * serverport)305 void GAME_SetServerInfo (const char* server, const char* serverport)
306 {
307 	Q_strncpyz(cls.servername, server, sizeof(cls.servername));
308 	Q_strncpyz(cls.serverport, serverport, sizeof(cls.serverport));
309 }
310 
311 /**
312  * @sa CL_PingServers_f
313  */
CL_QueryMasterServer(const char * action,http_callback_t callback)314 static void CL_QueryMasterServer (const char* action, http_callback_t callback)
315 {
316 	HTTP_GetURL(va("%s/ufo/masterserver.php?%s", masterserver_url->string, action), callback);
317 }
318 
GAME_HandleServerCommand(const char * command,dbuffer * msg)319 bool GAME_HandleServerCommand (const char* command, dbuffer* msg)
320 {
321 	const cgame_export_t* list = GAME_GetCurrentType();
322 	if (!list || list->HandleServerCommand == nullptr)
323 		return false;
324 
325 	return list->HandleServerCommand(command, msg);
326 }
327 
GAME_AddChatMessage(const char * format,...)328 void GAME_AddChatMessage (const char* format, ...)
329 {
330 	va_list argptr;
331 	char string[4096];
332 
333 	const cgame_export_t* list = GAME_GetCurrentType();
334 	if (!list || list->AddChatMessage == nullptr)
335 		return;
336 
337 	S_StartLocalSample("misc/talk", SND_VOLUME_DEFAULT);
338 
339 	va_start(argptr, format);
340 	Q_vsnprintf(string, sizeof(string), format, argptr);
341 	va_end(argptr);
342 
343 	list->AddChatMessage(string);
344 }
345 
GAME_IsTeamEmpty(void)346 bool GAME_IsTeamEmpty (void)
347 {
348 	return LIST_IsEmpty(chrDisplayList);
349 }
350 
GAME_NET_OOB_Printf(struct net_stream * s,const char * format,...)351 static void GAME_NET_OOB_Printf (struct net_stream *s, const char* format, ...)
352 {
353 	va_list argptr;
354 	char string[4096];
355 
356 	va_start(argptr, format);
357 	Q_vsnprintf(string, sizeof(string), format, argptr);
358 	va_end(argptr);
359 
360 	NET_OOB_Printf(s, "%s", string);
361 }
362 
GAME_NET_OOB_Printf2(const char * format,...)363 static void GAME_NET_OOB_Printf2 (const char* format, ...)
364 {
365 	va_list argptr;
366 	char string[4096];
367 
368 	va_start(argptr, format);
369 	Q_vsnprintf(string, sizeof(string), format, argptr);
370 	va_end(argptr);
371 
372 	NET_OOB_Printf(cls.netStream, "%s", string);
373 }
374 
GAME_UI_Popup(const char * title,const char * format,...)375 static void GAME_UI_Popup (const char* title, const char* format, ...)
376 {
377 	va_list argptr;
378 
379 	va_start(argptr, format);
380 	Q_vsnprintf(popupText, sizeof(popupText), format, argptr);
381 	va_end(argptr);
382 
383 	UI_Popup(title, popupText);
384 }
385 
GAME_StrDup(const char * string)386 static char* GAME_StrDup (const char* string)
387 {
388 	return Mem_PoolStrDup(string, cl_genericPool, 0);
389 }
390 
GAME_Free(void * ptr)391 static void GAME_Free (void* ptr)
392 {
393 	Mem_Free(ptr);
394 }
395 
GAME_DestroyInventory(Inventory * const inv)396 static void GAME_DestroyInventory (Inventory*  const inv)
397 {
398 	cls.i.destroyInventory(inv);
399 }
400 
GAME_EquipActor(character_t * const chr,const equipDef_t * ed,int maxWeight)401 static void GAME_EquipActor (character_t* const chr, const equipDef_t* ed, int maxWeight)
402 {
403 	cls.i.EquipActor(chr, ed, maxWeight);
404 }
405 
GAME_EquipActorMelee(Inventory * const inv,const teamDef_t * td)406 static void GAME_EquipActorMelee (Inventory* const inv, const teamDef_t* td)
407 {
408 	cls.i.EquipActorMelee(inv, td);
409 }
410 
GAME_EquipActorRobot(Inventory * const inv,const objDef_t * weapon)411 static void GAME_EquipActorRobot (Inventory* const inv, const objDef_t* weapon)
412 {
413 	cls.i.EquipActorRobot(inv, weapon);
414 }
415 
GAME_RemoveFromInventory(Inventory * const i,const invDef_t * container,Item * fItem)416 static bool GAME_RemoveFromInventory (Inventory* const i, const invDef_t* container, Item* fItem)
417 {
418 	return cls.i.removeFromInventory(i, container, fItem);
419 }
420 
GAME_WebUpload(int category,const char * filename)421 static void GAME_WebUpload (int category, const char* filename)
422 {
423 	WEB_CGameUpload(GAME_GetCurrentName(), category, filename);
424 }
425 
GAME_WebDelete(int category,const char * filename)426 static void GAME_WebDelete (int category, const char* filename)
427 {
428 	WEB_CGameDelete(GAME_GetCurrentName(), category, filename);
429 }
430 
GAME_WebDownloadFromUser(int category,const char * filename,int userId)431 static void GAME_WebDownloadFromUser (int category, const char* filename, int userId)
432 {
433 	WEB_CGameDownloadFromUser(GAME_GetCurrentName(), category, filename, userId);
434 }
435 
GAME_WebListForUser(int category,int userId)436 static void GAME_WebListForUser (int category, int userId)
437 {
438 	WEB_CGameListForUser(GAME_GetCurrentName(), category, userId);
439 }
440 
GAME_SetNextUniqueCharacterNumber(int ucn)441 static void GAME_SetNextUniqueCharacterNumber (int ucn)
442 {
443 	cls.nextUniqueCharacterNumber = ucn;
444 }
445 
GAME_GetNextUniqueCharacterNumber(void)446 static int GAME_GetNextUniqueCharacterNumber (void)
447 {
448 	return cls.nextUniqueCharacterNumber;
449 }
450 
GAME_CollectItems(void * data,int won,void (* collectItem)(void *,const objDef_t *,int),void (* collectAmmo)(void *,const Item *),void (* ownitems)(const Inventory *))451 static void GAME_CollectItems (void* data, int won, void (*collectItem)(void*, const objDef_t*, int), void (*collectAmmo) (void* , const Item*), void (*ownitems) (const Inventory*))
452 {
453 	le_t* le = nullptr;
454 	while ((le = LE_GetNextInUse(le))) {
455 		/* Winner collects everything on the floor, and everything carried
456 		 * by surviving actors. Loser only gets what their living team
457 		 * members carry. */
458 		if (LE_IsItem(le)) {
459 			if (won) {
460 				Item* i = le->getFloorContainer();
461 				for ( ; i; i = i->getNext()) {
462 					collectItem(data, i->def(), 1);
463 					if (i->isReloadable() && i->getAmmoLeft() > 0)
464 						collectAmmo(data, i);
465 				}
466 			}
467 		} else if (LE_IsActor(le)) {
468 			/* The items are already dropped to floor and are available
469 			 * as ET_ITEM if the actor is dead; or the actor is not ours. */
470 			/* First of all collect armour of dead or stunned actors (if won). */
471 			if (won && LE_IsDead(le)) {
472 				Item* item = le->inv.getArmour();
473 				if (item)
474 					collectItem(data, item->def(), 1);
475 			} else if (le->team == cls.team && !LE_IsDead(le)) {
476 				/* Finally, the living actor from our team. */
477 				ownitems(&le->inv);
478 			}
479 		}
480 	}
481 }
482 
483 /**
484  * @brief Collecting stunned and dead alien bodies after the mission.
485  */
GAME_CollectAliens(void * data,void (* collect)(void *,const teamDef_t *,int,bool))486 static void GAME_CollectAliens (void* data, void (*collect)(void*, const teamDef_t*, int, bool))
487 {
488 	le_t* le = nullptr;
489 
490 	while ((le = LE_GetNextInUse(le))) {
491 		if (LE_IsActor(le) && LE_IsAlien(le)) {
492 			assert(le->teamDef);
493 
494 			if (LE_IsStunned(le))
495 				collect(data, le->teamDef, 1, false);
496 			else if (LE_IsDead(le))
497 				collect(data, le->teamDef, 1, true);
498 		}
499 	}
500 }
501 
UI_DrawString_(const char * fontID,align_t align,int x,int y,const char * c)502 static int UI_DrawString_ (const char* fontID, align_t align, int x, int y, const char* c)
503 {
504 	return UI_DrawString(fontID, align, x, y, 0, 0, 0, c);
505 }
506 
UI_PushWindow_(const char * name)507 static void UI_PushWindow_ (const char* name)
508 {
509 	UI_PushWindow(name);
510 }
511 
UI_DrawNormImageByName_(bool flip,float x,float y,float w,float h,float sh,float th,float sl,float tl,const char * name)512 static void UI_DrawNormImageByName_ (bool flip, float x, float y, float w, float h, float sh, float th, float sl, float tl, const char* name)
513 {
514 	UI_DrawNormImageByName(flip, x, y, w, h, sh, th, sl, tl, name);
515 }
516 
R_UploadAlpha_(const char * name,const byte * alphaData)517 static void R_UploadAlpha_ (const char* name, const byte* alphaData)
518 {
519 	image_t* image = R_GetImage(name);
520 	if (!image) {
521 		Com_Printf("Could not find image '%s'\n", name);
522 		return;
523 	}
524 	R_UploadAlpha(image, alphaData);
525 }
526 
R_DrawImageCentered(int x,int y,const char * name)527 static void R_DrawImageCentered (int x, int y, const char* name)
528 {
529 	image_t* image = R_FindImage(name, it_pic);
530 	if (image)
531 		R_DrawImage(x - image->width / 2, y - image->height / 2, image);
532 }
533 
Com_EParse_(const char ** text,const char * errhead,const char * errinfo)534 static const char* Com_EParse_ (const char** text, const char* errhead, const char* errinfo)
535 {
536 	return Com_EParse(text, errhead, errinfo);
537 }
538 
GAME_GetImportData(const cgameType_t * t)539 static const cgame_import_t* GAME_GetImportData (const cgameType_t* t)
540 {
541 	static cgame_import_t gameImport;
542 	static cgame_import_t* cgi = nullptr;
543 
544 	if (cgi == nullptr) {
545 		cgi = &gameImport;
546 
547 		cgi->ui_inventory = &ui_inventory;
548 		cgi->csi = &csi;
549 
550 		cgi->r_xviAlpha = geoscapeData.r_xviAlpha;
551 		cgi->r_radarPic = geoscapeData.r_radarPic;
552 		cgi->r_radarSourcePic = geoscapeData.r_radarSourcePic;
553 
554 		/** @todo add a wrapper here that stores the cgame command and removes them on shutdown automatically */
555 		cgi->Cmd_AddCommand = Cmd_AddCommand;
556 		cgi->Cmd_Argc = Cmd_Argc;
557 		cgi->Cmd_Args = Cmd_Args;
558 		cgi->Cmd_Argv = Cmd_Argv;
559 		cgi->Cmd_ExecuteString = Cmd_ExecuteString;
560 		cgi->Cmd_RemoveCommand = Cmd_RemoveCommand;
561 		cgi->Cmd_AddParamCompleteFunction = Cmd_AddParamCompleteFunction;
562 		cgi->Cmd_GenericCompleteFunction = Cmd_GenericCompleteFunction;
563 		cgi->Com_GetMapDefinitionByID = Com_GetMapDefinitionByID;
564 
565 		cgi->Cbuf_AddText = Cbuf_AddText;
566 		cgi->Cbuf_Execute = Cbuf_Execute;
567 
568 		cgi->LIST_PrependString = LIST_PrependString;
569 		cgi->LIST_AddString = LIST_AddString;
570 		cgi->LIST_AddStringSorted = LIST_AddStringSorted;
571 		cgi->LIST_AddPointer = LIST_AddPointer;
572 		cgi->LIST_Add = LIST_Add;
573 		cgi->LIST_ContainsString = LIST_ContainsString;
574 		cgi->LIST_GetPointer = LIST_GetPointer;
575 		cgi->LIST_Delete = LIST_Delete;
576 		cgi->LIST_RemoveEntry = LIST_RemoveEntry;
577 		cgi->LIST_IsEmpty = LIST_IsEmpty;
578 		cgi->LIST_Count = LIST_Count;
579 		cgi->LIST_CopyStructure = LIST_CopyStructure;
580 		cgi->LIST_GetByIdx = LIST_GetByIdx;
581 		cgi->LIST_Remove = LIST_Remove;
582 		cgi->LIST_Sort = LIST_Sort;
583 		cgi->LIST_GetRandom = LIST_GetRandom;
584 
585 		cgi->Cvar_Delete = Cvar_Delete;
586 		/** @todo add a wrapper here that stores the cgame cvars and removes them on shutdown automatically */
587 		cgi->Cvar_Get = Cvar_Get;
588 		cgi->Cvar_GetInteger = Cvar_GetInteger;
589 		cgi->Cvar_GetValue = Cvar_GetValue;
590 		cgi->Cvar_VariableStringOld = Cvar_VariableStringOld;
591 		cgi->Cvar_Set = Cvar_Set;
592 		cgi->Cvar_SetValue = Cvar_SetValue;
593 		cgi->Cvar_GetString = Cvar_GetString;
594 		cgi->Cvar_ForceSet = Cvar_ForceSet;
595 
596 		cgi->FS_FreeFile = FS_FreeFile;
597 		cgi->FS_LoadFile = FS_LoadFile;
598 		cgi->FS_CheckFile = FS_CheckFile;
599 		cgi->FS_BuildFileList = FS_BuildFileList;
600 		cgi->FS_NextFileFromFileList = FS_NextFileFromFileList;
601 		cgi->FS_NextScriptHeader = FS_NextScriptHeader;
602 
603 		cgi->UI_AddOption = UI_AddOption;
604 		cgi->UI_ExecuteConfunc = UI_ExecuteConfunc;
605 		cgi->UI_InitStack = UI_InitStack;
606 		cgi->UI_Popup = GAME_UI_Popup;
607 		cgi->UI_PopupList = UI_PopupList;
608 		cgi->UI_PopWindow = UI_PopWindow;
609 		cgi->UI_PushWindow = UI_PushWindow_;
610 		cgi->UI_RegisterLinkedListText = UI_RegisterLinkedListText;
611 		cgi->UI_MessageGetStack = UI_MessageGetStack;
612 		cgi->UI_MessageAddStack = UI_MessageAddStack;
613 		cgi->UI_MessageResetStack = UI_MessageResetStack;
614 		cgi->UI_TextScrollEnd = UI_TextScrollEnd;
615 		cgi->UI_RegisterOption = UI_RegisterOption;
616 		cgi->UI_RegisterText = UI_RegisterText;
617 		cgi->UI_ResetData = UI_ResetData;
618 		cgi->UI_UpdateInvisOptions = UI_UpdateInvisOptions;
619 		cgi->UI_GetOption = UI_GetOption;
620 		cgi->UI_SortOptions = UI_SortOptions;
621 		cgi->UI_DrawString = UI_DrawString_;
622 		cgi->UI_GetFontFromNode = UI_GetFontFromNode;
623 		cgi->UI_DrawNormImageByName = UI_DrawNormImageByName_;
624 		cgi->UI_DrawRect = UI_DrawRect;
625 		cgi->UI_DrawFill = UI_DrawFill;
626 		cgi->UI_DrawTooltip = UI_DrawTooltip;
627 		cgi->UI_GetNodeAbsPos = UI_GetNodeAbsPos;
628 		cgi->UI_PopupButton = UI_PopupButton;
629 		cgi->UI_GetSpriteByName = UI_GetSpriteByName;
630 		cgi->UI_ContainerNodeUpdateEquipment = UI_ContainerNodeUpdateEquipment;
631 		cgi->UI_RegisterLineStrip = UI_RegisterLineStrip;
632 		cgi->UI_GetNodeByPath = UI_GetNodeByPath;
633 		cgi->UI_DisplayNotice = UI_DisplayNotice;
634 		cgi->UI_GetActiveWindowName = UI_GetActiveWindowName;
635 		cgi->UI_TextNodeSelectLine = UI_TextNodeSelectLine;
636 
637 		cgi->CL_Translate = CL_Translate;
638 
639 		cgi->NET_StreamSetCallback = NET_StreamSetCallback;
640 		cgi->NET_Connect = NET_Connect;
641 		cgi->NET_ReadString = NET_ReadString;
642 		cgi->NET_ReadStringLine = NET_ReadStringLine;
643 		cgi->NET_ReadByte = NET_ReadByte;
644 		cgi->NET_ReadMsg = NET_ReadMsg;
645 		cgi->NET_StreamGetData = NET_StreamGetData;
646 		cgi->NET_StreamSetData = NET_StreamSetData;
647 		cgi->NET_StreamFree = NET_StreamFree;
648 		cgi->NET_StreamPeerToName = NET_StreamPeerToName;
649 		cgi->NET_SockaddrToStrings = NET_SockaddrToStrings;
650 		cgi->NET_DatagramSocketNew = NET_DatagramSocketNew;
651 		cgi->NET_DatagramBroadcast = NET_DatagramBroadcast;
652 		cgi->NET_DatagramSocketClose = NET_DatagramSocketClose;
653 		cgi->NET_OOB_Printf = GAME_NET_OOB_Printf;
654 		cgi->NET_OOB_Printf2 = GAME_NET_OOB_Printf2;
655 
656 		cgi->Com_ServerState = Com_ServerState;
657 		cgi->Com_Printf = Com_Printf;
658 		cgi->Com_DPrintf = Com_DPrintf;
659 		cgi->Com_Error = Com_Error;
660 		cgi->Com_DropShipTypeToShortName = Com_DropShipTypeToShortName;
661 		cgi->Com_UFOCrashedTypeToShortName = Com_UFOCrashedTypeToShortName;
662 		cgi->Com_UFOTypeToShortName = Com_UFOTypeToShortName;
663 		cgi->Com_GetRandomMapAssemblyNameForCraft = Com_GetRandomMapAssemblyNameForCraft;
664 		cgi->Com_RegisterConstList = Com_RegisterConstList;
665 		cgi->Com_UnregisterConstList = Com_UnregisterConstList;
666 		cgi->Com_GetConstVariable = Com_GetConstVariable;
667 		cgi->Com_GetConstIntFromNamespace = Com_GetConstIntFromNamespace;
668 		cgi->Com_GetConstInt = Com_GetConstInt;
669 		cgi->Com_EParse = Com_EParse_;
670 		cgi->Com_EParseValue = Com_EParseValue;
671 		cgi->Com_ValueToStr = Com_ValueToStr;
672 		cgi->Com_GetTeamDefinitionByID = Com_GetTeamDefinitionByID;
673 		cgi->Com_UFOShortNameToID = Com_UFOShortNameToID;
674 		cgi->Com_GetRandomMapAssemblyNameForCrashedCraft = Com_GetRandomMapAssemblyNameForCrashedCraft;
675 		cgi->Com_SetGameType = Com_SetGameType;
676 		cgi->Com_RegisterConstInt = Com_RegisterConstInt;
677 		cgi->Com_UnregisterConstVariable = Com_UnregisterConstVariable;
678 		cgi->Com_Drop = Com_Drop;
679 		cgi->Com_GetUGVByID = Com_GetUGVByID;
680 		cgi->Com_GetUGVByIDSilent = Com_GetUGVByIDSilent;
681 		cgi->Com_DropShipShortNameToID = Com_DropShipShortNameToID;
682 
683 		cgi->SV_ShutdownWhenEmpty = SV_ShutdownWhenEmpty;
684 		cgi->SV_Shutdown = SV_Shutdown;
685 
686 		cgi->CL_Drop = CL_Drop;
687 		cgi->CL_Milliseconds = CL_Milliseconds;
688 		cgi->CL_PlayerGetName = CL_PlayerGetName;
689 		cgi->CL_GetPlayerNum = CL_GetPlayerNum;
690 		cgi->CL_SetClientState = CL_SetClientState;
691 		cgi->CL_GetClientState = CL_GetClientState;
692 		cgi->CL_Disconnect = CL_Disconnect;
693 		cgi->CL_QueryMasterServer = CL_QueryMasterServer;
694 
695 		cgi->GAME_SwitchCurrentSelectedMap = GAME_SwitchCurrentSelectedMap;
696 		cgi->GAME_GetCurrentSelectedMap = GAME_GetCurrentSelectedMap;
697 		cgi->GAME_GetCurrentTeam = GAME_GetCurrentTeam;
698 		cgi->GAME_StrDup = GAME_StrDup;
699 		cgi->GAME_AutoTeam = GAME_AutoTeam;
700 		cgi->GAME_ChangeEquip = GAME_ChangeEquip;
701 		cgi->GAME_GetCharacterArraySize = GAME_GetCharacterArraySize;
702 		cgi->GAME_IsTeamEmpty = GAME_IsTeamEmpty;
703 		cgi->GAME_LoadDefaultTeam = GAME_LoadDefaultTeam;
704 		cgi->GAME_AppendTeamMember = GAME_AppendTeamMember;
705 		cgi->GAME_ReloadMode = GAME_ReloadMode;
706 		cgi->GAME_SetServerInfo = GAME_SetServerInfo;
707 		cgi->GAME_GetChrMaxLoad = GAME_GetChrMaxLoad;
708 
709 		cgi->Free = GAME_Free;
710 
711 		cgi->R_LoadImage = R_LoadImage;
712 		cgi->R_SoftenTexture = R_SoftenTexture;
713 		cgi->R_ImageExists = R_ImageExists;
714 		cgi->R_Color = R_Color;
715 		cgi->R_DrawLineStrip = R_DrawLineStrip;
716 		cgi->R_DrawLine = R_DrawLine;
717 		cgi->R_DrawFill = R_DrawFill;
718 		cgi->R_DrawRect = R_DrawRect;
719 		cgi->R_DrawBloom = R_DrawBloom;
720 		cgi->R_Draw2DMapMarkers = R_Draw2DMapMarkers;
721 		cgi->R_Draw3DMapMarkers = R_Draw3DMapMarkers;
722 		cgi->R_UploadAlpha = R_UploadAlpha_;
723 		cgi->R_DrawImageCentered = R_DrawImageCentered;
724 
725 		cgi->S_SetSampleRepeatRate = S_SetSampleRepeatRate;
726 		cgi->S_StartLocalSample = S_StartLocalSample;
727 
728 		cgi->CL_GenerateCharacter = CL_GenerateCharacter;
729 		cgi->CL_OnBattlescape = CL_OnBattlescape;
730 
731 		cgi->SetNextUniqueCharacterNumber = GAME_SetNextUniqueCharacterNumber;
732 		cgi->GetNextUniqueCharacterNumber = GAME_GetNextUniqueCharacterNumber;
733 
734 		cgi->CollectItems = GAME_CollectItems;
735 		cgi->CollectAliens = GAME_CollectAliens;
736 
737 		cgi->INV_GetEquipmentDefinitionByID = INV_GetEquipmentDefinitionByID;
738 		cgi->INV_DestroyInventory = GAME_DestroyInventory;
739 		cgi->INV_EquipActor = GAME_EquipActor;
740 		cgi->INV_EquipActorMelee = GAME_EquipActorMelee;
741 		cgi->INV_EquipActorRobot = GAME_EquipActorRobot;
742 		cgi->INV_RemoveFromInventory = GAME_RemoveFromInventory;
743 
744 		cgi->INV_ItemDescription = INV_ItemDescription;
745 
746 		cgi->WEB_Upload = GAME_WebUpload;
747 		cgi->WEB_Delete = GAME_WebDelete;
748 		cgi->WEB_DownloadFromUser = GAME_WebDownloadFromUser;
749 		cgi->WEB_ListForUser = GAME_WebListForUser;
750 
751 		cgi->GetRelativeSavePath = GAME_GetRelativeSavePath;
752 		cgi->GetAbsoluteSavePath = GAME_GetAbsoluteSavePath;
753 
754 		cgi->Sys_Error = Sys_Error;
755 
756 		cgi->HUD_InitUI = HUD_InitUI;
757 		cgi->HUD_DisplayMessage = HUD_DisplayMessage;
758 
759 		cgi->XML_AddBool = XML_AddBool;
760 		cgi->XML_AddBoolValue = XML_AddBoolValue;
761 		cgi->XML_AddByte = XML_AddByte;
762 		cgi->XML_AddByteValue = XML_AddByteValue;
763 		cgi->XML_AddDate = XML_AddDate;
764 		cgi->XML_AddDouble = XML_AddDouble;
765 		cgi->XML_AddDoubleValue = XML_AddDoubleValue;
766 		cgi->XML_AddFloat = XML_AddFloat;
767 		cgi->XML_AddFloatValue = XML_AddFloatValue;
768 		cgi->XML_AddInt = XML_AddInt;
769 		cgi->XML_AddIntValue = XML_AddIntValue;
770 		cgi->XML_AddLong = XML_AddLong;
771 		cgi->XML_AddLongValue = XML_AddLongValue;
772 		cgi->XML_AddNode = XML_AddNode;
773 		cgi->XML_AddPos2 = XML_AddPos2;
774 		cgi->XML_AddPos3 = XML_AddPos3;
775 		cgi->XML_AddShort = XML_AddShort;
776 		cgi->XML_AddShortValue = XML_AddShortValue;
777 		cgi->XML_AddString = XML_AddString;
778 		cgi->XML_AddStringValue = XML_AddStringValue;
779 
780 		cgi->XML_GetBool = XML_GetBool;
781 		cgi->XML_GetInt = XML_GetInt;
782 		cgi->XML_GetShort = XML_GetShort;
783 		cgi->XML_GetLong = XML_GetLong;
784 		cgi->XML_GetString = XML_GetString;
785 		cgi->XML_GetFloat = XML_GetFloat;
786 		cgi->XML_GetDouble = XML_GetDouble;
787 		cgi->XML_Parse = XML_Parse;
788 		cgi->XML_GetPos2 = XML_GetPos2;
789 		cgi->XML_GetNextPos2 = XML_GetNextPos2;
790 		cgi->XML_GetPos3 = XML_GetPos3;
791 		cgi->XML_GetNextPos3 = XML_GetNextPos3;
792 		cgi->XML_GetDate = XML_GetDate;
793 		cgi->XML_GetNode = XML_GetNode;
794 		cgi->XML_GetNextNode = XML_GetNextNode;
795 	}
796 
797 	cgi->cgameType = t;
798 
799 	return cgi;
800 }
801 
802 static const int TAG_INVENTORY = 17462;
803 
GAME_FreeInventory(void * data)804 static void GAME_FreeInventory (void* data)
805 {
806 	Mem_Free(data);
807 }
808 
GAME_AllocInventoryMemory(size_t size)809 static void* GAME_AllocInventoryMemory (size_t size)
810 {
811 	return Mem_PoolAlloc(size, cl_genericPool, TAG_INVENTORY);
812 }
813 
814 
GAME_FreeAllInventory(void)815 static void GAME_FreeAllInventory (void)
816 {
817 	Mem_FreeTag(cl_genericPool, TAG_INVENTORY);
818 }
819 
820 static const inventoryImport_t inventoryImport = { GAME_FreeInventory, GAME_FreeAllInventory, GAME_AllocInventoryMemory };
821 
GAME_UnloadGame(void)822 void GAME_UnloadGame (void)
823 {
824 #ifndef HARD_LINKED_CGAME
825 	if (cls.cgameLibrary) {
826 		GAME_SetMode(nullptr);
827 		Com_Printf("Unload the cgame library\n");
828 		SDL_UnloadObject(cls.cgameLibrary);
829 		cls.cgameLibrary = nullptr;
830 	}
831 #endif
832 }
833 
GAME_SwitchCurrentSelectedMap(int step)834 void GAME_SwitchCurrentSelectedMap (int step)
835 {
836 	cls.currentSelectedMap += step;
837 
838 	if (cls.currentSelectedMap < 0)
839 		cls.currentSelectedMap = csi.numMDs - 1;
840 	cls.currentSelectedMap %= csi.numMDs;
841 }
842 
GAME_GetCurrentSelectedMap(void)843 const mapDef_t* GAME_GetCurrentSelectedMap (void)
844 {
845 	return Com_GetMapDefByIDX(cls.currentSelectedMap);
846 }
847 
GAME_GetCurrentTeam(void)848 int GAME_GetCurrentTeam (void)
849 {
850 	return cls.team;
851 }
852 
GAME_DrawMap(geoscapeData_t * data)853 void GAME_DrawMap (geoscapeData_t* data)
854 {
855 	const cgame_export_t* list = GAME_GetCurrentType();
856 	if (list && list->MapDraw)
857 		list->MapDraw(data);
858 }
859 
GAME_DrawMapMarkers(uiNode_t * node)860 void GAME_DrawMapMarkers (uiNode_t* node)
861 {
862 	const cgame_export_t* list = GAME_GetCurrentType();
863 	if (list && list->MapDrawMarkers)
864 		list->MapDrawMarkers(node);
865 }
866 
GAME_MapClick(uiNode_t * node,int x,int y,const vec2_t pos)867 void GAME_MapClick (uiNode_t* node, int x, int y, const vec2_t pos)
868 {
869 	const cgame_export_t* list = GAME_GetCurrentType();
870 	if (list && list->MapClick)
871 		list->MapClick(node, x, y, pos);
872 }
873 
GAME_SetMode(const cgame_export_t * gametype)874 void GAME_SetMode (const cgame_export_t* gametype)
875 {
876 	const cgame_export_t* list;
877 
878 	if (cls.gametype == gametype)
879 		return;
880 
881 	GAME_ResetCharacters();
882 	LIST_Delete(&cl.chrList);
883 
884 	list = GAME_GetCurrentType();
885 	if (list) {
886 		Com_Printf("Shutdown gametype '%s'\n", list->name);
887 		list->Shutdown();
888 		cvarListener->onGameModeChange();
889 
890 		/* we dont need to go back to "main" stack if we are already on this stack */
891 		if (!UI_IsWindowOnStack("main"))
892 			UI_InitStack("main", "");
893 	}
894 
895 	cls.gametype = gametype;
896 
897 	CL_Disconnect();
898 
899 	list = GAME_GetCurrentType();
900 	if (list) {
901 		Com_Printf("Change gametype to '%s'\n", list->name);
902 		/* inventory structure switched/initialized */
903 		cls.i.destroyInventoryInterface();
904 		cls.i.initInventory(list->name, &csi, &inventoryImport);
905 		list->Init();
906 	}
907 }
908 
UI_MapInfoGetNext(int step)909 static void UI_MapInfoGetNext (int step)
910 {
911 	int ref = cls.currentSelectedMap;
912 
913 	for (;;) {
914 		cls.currentSelectedMap += step;
915 		if (cls.currentSelectedMap < 0)
916 			cls.currentSelectedMap = csi.numMDs - 1;
917 		cls.currentSelectedMap %= csi.numMDs;
918 
919 		const mapDef_t* md = Com_GetMapDefByIDX(cls.currentSelectedMap);
920 
921 		/* avoid infinite loop */
922 		if (ref == cls.currentSelectedMap)
923 			break;
924 		/* special purpose maps are not startable without the specific context */
925 		if (md->map[0] == '.')
926 			continue;
927 
928 		if (md->map[0] != '+' && FS_CheckFile("maps/%s.bsp", md->map) != -1)
929 			break;
930 		if (md->map[0] == '+' && FS_CheckFile("maps/%s.ump", md->map + 1) != -1)
931 			break;
932 	}
933 }
934 
935 /**
936  * @brief Prints the map info for the server creation dialogue
937  */
UI_MapInfo(int step)938 static void UI_MapInfo (int step)
939 {
940 	const char* mapname;
941 	const mapDef_t* md;
942 	const cgame_export_t* list = GAME_GetCurrentType();
943 
944 	if (!list)
945 		return;
946 
947 	if (!csi.numMDs)
948 		return;
949 
950 	UI_MapInfoGetNext(step);
951 
952 	md = list->MapInfo(step);
953 	if (!md)
954 		return;
955 
956 	mapname = md->map;
957 	/* skip random map char. */
958 	Cvar_Set("mn_svmapid", "%s", md->id);
959 	if (mapname[0] == '+') {
960 		Cvar_Set("mn_svmapname", "%s %s", md->map, md->params ? (const char*)LIST_GetRandom(md->params) : "");
961 		mapname++;
962 	} else {
963 		Cvar_Set("mn_svmapname", "%s", md->map);
964 	}
965 
966 	if (R_ImageExists("pics/maps/shots/%s", mapname))
967 		Cvar_Set("mn_mappic", "maps/shots/%s", mapname);
968 	else
969 		Cvar_Set("mn_mappic", "maps/shots/default");
970 
971 	if (R_ImageExists("pics/maps/shots/%s_2", mapname))
972 		Cvar_Set("mn_mappic2", "maps/shots/%s_2", mapname);
973 	else
974 		Cvar_Set("mn_mappic2", "maps/shots/default");
975 
976 	if (R_ImageExists("pics/maps/shots/%s_3", mapname))
977 		Cvar_Set("mn_mappic3", "maps/shots/%s_3", mapname);
978 	else
979 		Cvar_Set("mn_mappic3", "maps/shots/default");
980 }
981 
UI_RequestMapList_f(void)982 static void UI_RequestMapList_f (void)
983 {
984 	const char* callbackCmd;
985 	const mapDef_t* md;
986 	const bool multiplayer = GAME_IsMultiplayer();
987 
988 	if (Cmd_Argc() != 2) {
989 		Com_Printf("Usage: %s <callback>\n", Cmd_Argv(0));
990 		return;
991 	}
992 
993 	if (!csi.numMDs)
994 		return;
995 
996 	callbackCmd = Cmd_Argv(1);
997 
998 	Cbuf_AddText("%s begin\n", callbackCmd);
999 
1000 	MapDef_ForeachCondition(md, multiplayer ? md->multiplayer : md->singleplayer) {
1001 		const char* preview;
1002 
1003 		/* special purpose maps are not startable without the specific context */
1004 		if (md->map[0] == '.')
1005 			continue;
1006 
1007 		/* do we have the map file? */
1008 		if (md->map[0] != '+' && FS_CheckFile("maps/%s.bsp", md->map) == -1)
1009 			continue;
1010 		if (md->map[0] == '+' && FS_CheckFile("maps/%s.ump", md->map + 1) == -1)
1011 			continue;
1012 
1013 		preview = md->map;
1014 		if (preview[0] == '+')
1015 			preview++;
1016 		if (!R_ImageExists("pics/maps/shots/%s", preview))
1017 			preview = "default";
1018 
1019 		Cbuf_AddText("%s add \"%s\" \"%s\"\n", callbackCmd, md->id, preview);
1020 	}
1021 	Cbuf_AddText("%s end\n", callbackCmd);
1022 }
1023 
UI_GetMaps_f(void)1024 static void UI_GetMaps_f (void)
1025 {
1026 	UI_MapInfo(0);
1027 }
1028 
1029 /**
1030  * @brief Select the next available map.
1031  */
UI_NextMap_f(void)1032 static void UI_NextMap_f (void)
1033 {
1034 	UI_MapInfo(1);
1035 }
1036 
1037 /**
1038  * @brief Select the previous available map.
1039  */
UI_PreviousMap_f(void)1040 static void UI_PreviousMap_f (void)
1041 {
1042 	UI_MapInfo(-1);
1043 }
1044 
UI_SelectMap_f(void)1045 static void UI_SelectMap_f (void)
1046 {
1047 	const char* mapname;
1048 	const mapDef_t* md;
1049 	int i;
1050 
1051 	if (Cmd_Argc() != 2) {
1052 		Com_Printf("Usage: %s <mapname>\n", Cmd_Argv(0));
1053 		return;
1054 	}
1055 
1056 	if (!csi.numMDs)
1057 		return;
1058 
1059 	mapname = Cmd_Argv(1);
1060 	i = 0;
1061 
1062 	MapDef_Foreach(md) {
1063 		i++;
1064 		if (!Q_streq(md->map, mapname))
1065 			continue;
1066 		cls.currentSelectedMap = i - 1;
1067 		UI_MapInfo(0);
1068 		return;
1069 	}
1070 
1071 	i = 0;
1072 	MapDef_Foreach(md) {
1073 		i++;
1074 		if (!Q_streq(md->id, mapname))
1075 			continue;
1076 		cls.currentSelectedMap = i - 1;
1077 		UI_MapInfo(0);
1078 		return;
1079 	}
1080 
1081 	Com_Printf("Could not find map %s\n", mapname);
1082 }
1083 
1084 /** @brief Valid equipment definition values from script files. */
1085 static const value_t cgame_vals[] = {
1086 	{"window", V_STRING, offsetof(cgameType_t, window), 0},
1087 	{"name", V_STRING, offsetof(cgameType_t, name), 0},
1088 	{"equipments", V_LIST, offsetof(cgameType_t, equipmentList), 0},
1089 
1090 	{nullptr, V_NULL, 0, 0}
1091 };
1092 
GAME_ParseModes(const char * name,const char ** text)1093 void GAME_ParseModes (const char* name, const char** text)
1094 {
1095 	cgameType_t* cgame;
1096 	int i;
1097 
1098 	/* search for equipments with same name */
1099 	for (i = 0; i < numCGameTypes; i++)
1100 		if (Q_streq(name, cgameTypes[i].id))
1101 			break;
1102 
1103 	if (i < numCGameTypes) {
1104 		Com_Printf("GAME_ParseModes: cgame def \"%s\" with same name found, second ignored\n", name);
1105 		return;
1106 	}
1107 
1108 	if (i >= MAX_CGAMETYPES)
1109 		Sys_Error("GAME_ParseModes: MAX_CGAMETYPES exceeded");
1110 
1111 	/* initialize the equipment definition */
1112 	cgame = &cgameTypes[numCGameTypes++];
1113 	OBJZERO(*cgame);
1114 
1115 	Q_strncpyz(cgame->id, name, sizeof(cgame->id));
1116 
1117 	Com_ParseBlock(name, text, cgame, cgame_vals, nullptr);
1118 }
1119 
1120 #ifndef HARD_LINKED_CGAME
GAME_LoadGame(const char * path,const char * name)1121 static bool GAME_LoadGame (const char* path, const char* name)
1122 {
1123 	char fullPath[MAX_OSPATH];
1124 
1125 	Com_sprintf(fullPath, sizeof(fullPath), "%s/cgame-%s_" CPUSTRING ".%s", path, name, SO_EXT);
1126 	cls.cgameLibrary = SDL_LoadObject(fullPath);
1127 	if (!cls.cgameLibrary) {
1128 		Com_sprintf(fullPath, sizeof(fullPath), "%s/cgame-%s.%s", path, name, SO_EXT);
1129 		cls.cgameLibrary = SDL_LoadObject(fullPath);
1130 	}
1131 
1132 	if (cls.cgameLibrary) {
1133 		Com_Printf("found at '%s'\n", path);
1134 		return true;
1135 	} else {
1136 		Com_Printf("not found at '%s'\n", path);
1137 		Com_Printf("%s\n", SDL_GetError());
1138 		return false;
1139 	}
1140 }
1141 #endif
1142 
GAME_GetCGameAPI(const cgameType_t * t)1143 static const cgame_export_t* GAME_GetCGameAPI (const cgameType_t* t)
1144 {
1145 	const char* name = t->id;
1146 #ifndef HARD_LINKED_CGAME
1147 	cgame_api_t GetCGameAPI;
1148 	const char* path;
1149 
1150 	if (cls.cgameLibrary)
1151 		Com_Error(ERR_FATAL, "GAME_GetCGameAPI without GAME_UnloadGame");
1152 
1153 	Com_Printf("------- Loading cgame-%s.%s -------\n", name, SO_EXT);
1154 
1155 #ifdef PKGLIBDIR
1156 	GAME_LoadGame(PKGLIBDIR, name);
1157 #endif
1158 
1159 	/* now run through the search paths */
1160 	path = nullptr;
1161 	while (!cls.cgameLibrary) {
1162 		path = FS_NextPath(path);
1163 		if (!path)
1164 			/* couldn't find one anywhere */
1165 			return nullptr;
1166 		else if (GAME_LoadGame(path, name))
1167 			break;
1168 	}
1169 
1170 	GetCGameAPI = (cgame_api_t)(uintptr_t)SDL_LoadFunction(cls.cgameLibrary, "GetCGameAPI");
1171 	if (!GetCGameAPI) {
1172 		GAME_UnloadGame();
1173 		return nullptr;
1174 	}
1175 #endif
1176 
1177 	/* sanity checks */
1178 	if (!LIST_IsEmpty(t->equipmentList)) {
1179 		LIST_Foreach(t->equipmentList, char const, equipID) {
1180 			if (INV_GetEquipmentDefinitionByID(equipID) == nullptr)
1181 				Sys_Error("Could not find the equipDef '%s' in the cgame mode: '%s'", equipID, name);
1182 		}
1183 	}
1184 
1185 	return GetCGameAPI(GAME_GetImportData(t));
1186 }
1187 
GAME_GetCGameAPI_(const cgameType_t * t)1188 static const cgame_export_t* GAME_GetCGameAPI_ (const cgameType_t* t)
1189 {
1190 #ifdef HARD_LINKED_CGAME
1191 	cgameMenu = t->window;
1192 #endif
1193 	return GAME_GetCGameAPI(t);
1194 }
1195 
1196 /**
1197  * @brief Decides with game mode should be set - takes the menu as reference
1198  */
GAME_SetMode_f(void)1199 static void GAME_SetMode_f (void)
1200 {
1201 	const char* modeName;
1202 	int i;
1203 
1204 	if (Cmd_Argc() == 2)
1205 		modeName = Cmd_Argv(1);
1206 	else
1207 		modeName = UI_GetActiveWindowName();
1208 
1209 	if (modeName[0] == '\0')
1210 		return;
1211 
1212 	for (i = 0; i < numCGameTypes; i++) {
1213 		cgameType_t* t = &cgameTypes[i];
1214 		if (Q_streq(t->window, modeName)) {
1215 			const cgame_export_t* gametype;
1216 			GAME_UnloadGame();
1217 
1218 			gametype = GAME_GetCGameAPI_(t);
1219 			GAME_SetMode(gametype);
1220 
1221 			return;
1222 		}
1223 	}
1224 	Com_Printf("GAME_SetMode_f: Mode '%s' not found\n", modeName);
1225 }
1226 
GAME_ItemIsUseable(const objDef_t * od)1227 bool GAME_ItemIsUseable (const objDef_t* od)
1228 {
1229 	const cgame_export_t* list = GAME_GetCurrentType();
1230 
1231 	if (od->isArmour()) {
1232 		const char* teamDefID = GAME_GetTeamDef();
1233 		const teamDef_t* teamDef = Com_GetTeamDefinitionByID(teamDefID);
1234 
1235 		/* Don't allow armour for other teams */
1236 		if (teamDef != nullptr && !CHRSH_IsArmourUseableForTeam(od, teamDef))
1237 			return false;
1238 	}
1239 
1240 	if (list && list->IsItemUseable)
1241 		return list->IsItemUseable(od);
1242 
1243 	return true;
1244 }
1245 
1246 /**
1247  * @brief After a mission was finished this function is called
1248  * @param msg The network message buffer
1249  * @param winner The winning team or -1 if it was a draw
1250  * @param numSpawned The amounts of all spawned actors per team
1251  * @param numAlive The amount of survivors per team
1252  * @param numKilled The amount of killed actors for all teams. The first dimension contains
1253  * the attacker team, the second the victim team
1254  * @param numStunned The amount of stunned actors for all teams. The first dimension contains
1255  * the attacker team, the second the victim team
1256  * @param nextmap Indicates if there is another map to follow within the same msission
1257  */
GAME_HandleResults(dbuffer * msg,int winner,int * numSpawned,int * numAlive,int numKilled[][MAX_TEAMS],int numStunned[][MAX_TEAMS],bool nextmap)1258 void GAME_HandleResults (dbuffer* msg, int winner, int* numSpawned, int* numAlive, int numKilled[][MAX_TEAMS], int numStunned[][MAX_TEAMS], bool nextmap)
1259 {
1260 	const cgame_export_t* list = GAME_GetCurrentType();
1261 	if (list)
1262 		list->Results(msg, winner, numSpawned, numAlive, numKilled, numStunned, nextmap);
1263 	else
1264 		CL_Drop();
1265 }
1266 
1267 /**
1268  * @sa G_WriteItem
1269  * @sa G_ReadItem
1270  * @note The amount of the Item should not be needed here - because
1271  * the amount is only valid for CID_FLOOR and CID_EQUIP
1272  */
GAME_NetSendItem(dbuffer * buf,const Item * item,containerIndex_t container,int x,int y)1273 static void GAME_NetSendItem (dbuffer* buf, const Item* item, containerIndex_t container, int x, int y)
1274 {
1275 	const int ammoIdx = item->ammoDef() ? item->ammoDef()->idx : NONE;
1276 	const eventRegister_t* eventData = CL_GetEvent(EV_INV_TRANSFER);
1277 	assert(item->def());
1278 	Com_DPrintf(DEBUG_CLIENT, "GAME_NetSendItem: Add item %s to container %i (t=%i:a=%i:m=%i) (x=%i:y=%i)\n",
1279 		item->def()->id, container, item->def()->idx, item->getAmmoLeft(), ammoIdx, x, y);
1280 	NET_WriteFormat(buf, eventData->formatString, item->def()->idx, item->getAmmoLeft(), ammoIdx, container, x, y, item->rotated, item->getAmount());
1281 }
1282 
1283 /**
1284  * @sa G_SendInventory
1285  */
GAME_NetSendInventory(dbuffer * buf,const Inventory * inv)1286 static void GAME_NetSendInventory (dbuffer* buf, const Inventory* inv)
1287 {
1288 	const int nr = inv->countItems();
1289 
1290 	NET_WriteShort(buf, nr);
1291 
1292 	containerIndex_t container;
1293 	for (container = 0; container < CID_MAX; container++) {
1294 		if (INVDEF(container)->temp)
1295 			continue;
1296 		const Item* ic;
1297 		for (ic = inv->getContainer2(container); ic; ic = ic->getNext()) {
1298 			GAME_NetSendItem(buf, ic, container, ic->getX(), ic->getY());
1299 		}
1300 	}
1301 }
1302 
1303 /**
1304  * @brief Send the character information to the server that is needed to spawn the soldiers of the player.
1305  * @param[out] buf The net channel buffer to write the character data into.
1306  * @param[in] chr The character to get the data from.
1307  */
GAME_NetSendCharacter(dbuffer * buf,const character_t * chr)1308 static void GAME_NetSendCharacter (dbuffer* buf, const character_t* chr)
1309 {
1310 	int j;
1311 
1312 	if (!chr)
1313 		Com_Error(ERR_DROP, "No character given");
1314 	if (chr->fieldSize != ACTOR_SIZE_2x2 && chr->fieldSize != ACTOR_SIZE_NORMAL)
1315 		Com_Error(ERR_DROP, "Invalid character size given for character '%s': %i",
1316 				chr->name, chr->fieldSize);
1317 	if (chr->teamDef == nullptr)
1318 		Com_Error(ERR_DROP, "Character with no teamdef set (%s)", chr->name);
1319 
1320 	NET_WriteByte(buf, chr->fieldSize);
1321 	NET_WriteShort(buf, chr->ucn);
1322 	NET_WriteString(buf, chr->name);
1323 
1324 	/* model */
1325 	NET_WriteString(buf, chr->path);
1326 	NET_WriteString(buf, chr->body);
1327 	NET_WriteString(buf, chr->head);
1328 	NET_WriteByte(buf, chr->bodySkin);
1329 	NET_WriteByte(buf, chr->headSkin);
1330 
1331 	NET_WriteShort(buf, chr->HP);
1332 	NET_WriteShort(buf, chr->maxHP);
1333 	NET_WriteByte(buf, chr->teamDef->idx);
1334 	NET_WriteByte(buf, chr->gender);
1335 	NET_WriteByte(buf, chr->STUN);
1336 	NET_WriteByte(buf, chr->morale);
1337 
1338 	for (j = 0; j < chr->teamDef->bodyTemplate->numBodyParts(); ++j)
1339 		NET_WriteByte(buf, chr->wounds.treatmentLevel[j]);
1340 
1341 	for (j = 0; j < SKILL_NUM_TYPES + 1; j++)
1342 		NET_WriteLong(buf, chr->score.experience[j]);
1343 	for (j = 0; j < SKILL_NUM_TYPES; j++)
1344 		NET_WriteByte(buf, chr->score.skills[j]);
1345 	for (j = 0; j < KILLED_NUM_TYPES; j++)
1346 		NET_WriteShort(buf, chr->score.kills[j]);
1347 	for (j = 0; j < KILLED_NUM_TYPES; j++)
1348 		NET_WriteShort(buf, chr->score.stuns[j]);
1349 	NET_WriteShort(buf, chr->score.assignedMissions);
1350 }
1351 
1352 /**
1353  * @brief Stores a team-list (chr-list) info to buffer (which might be a network buffer, too).
1354  * @sa G_ClientTeamInfo
1355  * @sa MP_SaveTeamMultiplayerInfo
1356  * @note Called in CL_Precache_f to send the team info to server
1357  */
GAME_SendCurrentTeamSpawningInfo(dbuffer * buf,linkedList_t * team)1358 static void GAME_SendCurrentTeamSpawningInfo (dbuffer* buf, linkedList_t* team)
1359 {
1360 	const int teamSize = LIST_Count(team);
1361 
1362 	/* header */
1363 	NET_WriteByte(buf, clc_teaminfo);
1364 	NET_WriteByte(buf, teamSize);
1365 
1366 	LIST_Foreach(team, character_t, chr) {
1367 		Inventory* inv = &chr->inv;
1368 
1369 		/* unlink all temp containers */
1370 		inv->resetTempContainers();
1371 
1372 		GAME_NetSendCharacter(buf, chr);
1373 		GAME_NetSendInventory(buf, inv);
1374 	}
1375 }
1376 
GAME_GetTeamDef(void)1377 const char* GAME_GetTeamDef (void)
1378 {
1379 	const char* teamDefID;
1380 	const cgame_export_t* list = GAME_GetCurrentType();
1381 
1382 	if (list && list->GetTeamDef)
1383 		return list->GetTeamDef();
1384 
1385 	teamDefID = Cvar_GetString("cl_teamdef");
1386 	if (teamDefID[0] == '\0')
1387 		teamDefID = "phalanx";
1388 	return teamDefID;
1389 }
1390 
GAME_Spawn(linkedList_t ** chrList)1391 static bool GAME_Spawn (linkedList_t** chrList)
1392 {
1393 	const size_t size = GAME_GetCharacterArraySize();
1394 
1395 	/* If there is no active gametype we create a team with default values.
1396 	 * This is e.g. the case when someone starts a map with the map command */
1397 	if (GAME_GetCurrentType() == nullptr || LIST_IsEmpty(chrDisplayList)) {
1398 		const char* teamDefID = GAME_GetTeamDef();
1399 		const equipDef_t* ed = INV_GetEquipmentDefinitionByID("multiplayer_initial");
1400 
1401 		/* inventory structure switched/initialized */
1402 		cls.i.destroyInventoryInterface();
1403 		cls.i.initInventory("client", &csi, &inventoryImport);
1404 		GAME_GenerateTeam(teamDefID, ed, size);
1405 	}
1406 
1407 	int count = 0;
1408 	LIST_Foreach(chrDisplayList, character_t, chr) {
1409 		if (count > size)
1410 			break;
1411 		LIST_AddPointer(chrList, (void*)chr);
1412 	}
1413 
1414 	return true;
1415 }
1416 
1417 /**
1418  * @brief Called when the server sends the @c EV_START event.
1419  * @param isTeamPlay @c true if the game is a teamplay round. This can be interesting for
1420  * multiplayer based game types
1421  * @sa GAME_EndBattlescape
1422  */
GAME_StartBattlescape(bool isTeamPlay)1423 void GAME_StartBattlescape (bool isTeamPlay)
1424 {
1425 	const cgame_export_t* list = GAME_GetCurrentType();
1426 
1427 	Cvar_Set("cl_onbattlescape", "1");
1428 
1429 	Cvar_Set("cl_maxworldlevel", "%i", cl.mapMaxLevel - 1);
1430 	if (list != nullptr && list->StartBattlescape) {
1431 		list->StartBattlescape(isTeamPlay);
1432 	} else {
1433 		HUD_InitUI("singleplayermission");
1434 		GAME_InitMissionBriefing(_(CL_GetConfigString(CS_MAPTITLE)));
1435 	}
1436 }
1437 
GAME_InitMissionBriefing(const char * title)1438 void GAME_InitMissionBriefing (const char* title)
1439 {
1440 	const cgame_export_t* list = GAME_GetCurrentType();
1441 
1442 	linkedList_t* victoryConditionsMsgIDs = nullptr;
1443 	linkedList_t* missionBriefingMsgIDs = nullptr;
1444 
1445 	/* allow the server to add e.g. the misc_mission victory condition */
1446 	const char* serverVictoryMsgID = CL_GetConfigString(CS_VICTORY_CONDITIONS);
1447 	if (Q_strvalid(serverVictoryMsgID))
1448 		LIST_AddString(&victoryConditionsMsgIDs, serverVictoryMsgID);
1449 
1450 	UI_PushWindow("mission_briefing");
1451 
1452 	if (list != nullptr && list->InitMissionBriefing)
1453 		list->InitMissionBriefing(&title, &victoryConditionsMsgIDs, &missionBriefingMsgIDs);
1454 
1455 	/* if the cgame has nothing to contribute here, we will add a default victory condition */
1456 	if (LIST_IsEmpty(victoryConditionsMsgIDs))
1457 		LIST_AddString(&victoryConditionsMsgIDs, "*msgid:victory_condition_default");
1458 
1459 	/* if the cgame has nothing to contribute here, we will add a default mission briefing */
1460 	if (LIST_IsEmpty(missionBriefingMsgIDs))
1461 		LIST_AddString(&missionBriefingMsgIDs, "*msgid:mission_briefing_default");
1462 
1463 	UI_RegisterLinkedListText(TEXT_MISSIONBRIEFING, missionBriefingMsgIDs);
1464 	UI_RegisterLinkedListText(TEXT_MISSIONBRIEFING_VICTORY_CONDITIONS, victoryConditionsMsgIDs);
1465 	UI_RegisterText(TEXT_MISSIONBRIEFING_TITLE, title);
1466 }
1467 
1468 /**
1469  * @brief This is called if actors are spawned (or at least the spawning commands were send to
1470  * the server). This callback can e.g. be used to set initial actor states. E.g. request crouch and so on.
1471  * These events are executed without consuming time
1472  */
GAME_InitializeBattlescape(linkedList_t * team)1473 static void GAME_InitializeBattlescape (linkedList_t* team)
1474 {
1475 	int i;
1476 	const size_t size = lengthof(cl.teamList);
1477 	const cgame_export_t* list = GAME_GetCurrentType();
1478 
1479 	for (i = 0; i < size; i++) {
1480 		UI_ExecuteConfunc("huddisable %i", i);
1481 	}
1482 
1483 	if (list && list->InitializeBattlescape) {
1484 		dbuffer* msg = list->InitializeBattlescape(team);
1485 		if (msg != nullptr) {
1486 			NET_WriteMsg(cls.netStream, *msg);
1487 			delete msg;
1488 		}
1489 	}
1490 }
1491 
1492 /**
1493  * @brief Called during startup of mission to send team info
1494  */
GAME_SpawnSoldiers(void)1495 void GAME_SpawnSoldiers (void)
1496 {
1497 	const cgame_export_t* list = GAME_GetCurrentType();
1498 	bool spawnStatus;
1499 
1500 	/* this callback is responsible to set up the teamlist */
1501 	if (list && list->Spawn)
1502 		spawnStatus = list->Spawn(&cl.chrList);
1503 	else
1504 		spawnStatus = GAME_Spawn(&cl.chrList);
1505 
1506 	Com_Printf("Used inventory slots: %i\n", cls.i.GetUsedSlots());
1507 
1508 	if (spawnStatus && !LIST_IsEmpty(cl.chrList)) {
1509 		/* send team info */
1510 		dbuffer msg;
1511 		GAME_SendCurrentTeamSpawningInfo(&msg, cl.chrList);
1512 		NET_WriteMsg(cls.netStream, msg);
1513 	}
1514 }
1515 
GAME_StartMatch(void)1516 void GAME_StartMatch (void)
1517 {
1518 	if (!LIST_IsEmpty(cl.chrList)) {
1519 		dbuffer msg(12);
1520 		NET_WriteByte(&msg, clc_stringcmd);
1521 		NET_WriteString(&msg, NET_STATE_STARTMATCH "\n");
1522 		NET_WriteMsg(cls.netStream, msg);
1523 
1524 		GAME_InitializeBattlescape(cl.chrList);
1525 	}
1526 }
1527 
GAME_GetRelativeSavePath(char * buf,size_t bufSize)1528 const char* GAME_GetRelativeSavePath (char* buf, size_t bufSize)
1529 {
1530 	Com_sprintf(buf, bufSize, "save/%s/", GAME_GetCurrentName());
1531 	return buf;
1532 }
1533 
GAME_GetAbsoluteSavePath(char * buf,size_t bufSize)1534 const char* GAME_GetAbsoluteSavePath (char* buf, size_t bufSize)
1535 {
1536 	Com_sprintf(buf, bufSize, "%s/save/%s/", FS_Gamedir(), GAME_GetCurrentName());
1537 	return buf;
1538 }
1539 
GAME_GetEquipmentDefinition(void)1540 equipDef_t* GAME_GetEquipmentDefinition (void)
1541 {
1542 	const cgame_export_t* list = GAME_GetCurrentType();
1543 
1544 	if (list && list->GetEquipmentDefinition != nullptr)
1545 		return list->GetEquipmentDefinition();
1546 	return &equipDefStandard;
1547 }
1548 
GAME_NotifyEvent(event_t eventType)1549 void GAME_NotifyEvent (event_t eventType)
1550 {
1551 	const cgame_export_t* list = GAME_GetCurrentType();
1552 	if (list && list->NotifyEvent)
1553 		list->NotifyEvent(eventType);
1554 }
1555 
GAME_TeamIsKnown(const teamDef_t * teamDef)1556 bool GAME_TeamIsKnown (const teamDef_t* teamDef)
1557 {
1558 	const cgame_export_t* list = GAME_GetCurrentType();
1559 
1560 	if (!teamDef)
1561 		return false;
1562 
1563 	if (list && list->IsTeamKnown != nullptr)
1564 		return list->IsTeamKnown(teamDef);
1565 	return true;
1566 }
1567 
GAME_CharacterCvars(const character_t * chr)1568 void GAME_CharacterCvars (const character_t* chr)
1569 {
1570 	const cgame_export_t* list = GAME_GetCurrentType();
1571 	if (list && list->UpdateCharacterValues != nullptr)
1572 		list->UpdateCharacterValues(chr);
1573 }
1574 
1575 /**
1576  * @brief Let the aliens win the match
1577  */
GAME_Abort_f(void)1578 static void GAME_Abort_f (void)
1579 {
1580 	/* aborting means letting the aliens win */
1581 	Cbuf_AddText("sv win %i\n", TEAM_ALIEN);
1582 }
1583 
GAME_Drop(void)1584 void GAME_Drop (void)
1585 {
1586 	const cgame_export_t* list = GAME_GetCurrentType();
1587 
1588 	if (list && list->Drop) {
1589 		list->Drop();
1590 	} else {
1591 		SV_Shutdown("Drop", false);
1592 		GAME_SetMode(nullptr);
1593 
1594 		GAME_UnloadGame();
1595 
1596 		UI_InitStack("main", nullptr);
1597 	}
1598 }
1599 
1600 /**
1601  * @brief Quits the current running game by calling the @c shutdown callback
1602  */
GAME_Exit_f(void)1603 static void GAME_Exit_f (void)
1604 {
1605 	GAME_SetMode(nullptr);
1606 
1607 	GAME_UnloadGame();
1608 }
1609 
1610 /**
1611  * @brief Called every frame and allows us to hook into the current running game mode
1612  */
GAME_Frame(void)1613 void GAME_Frame (void)
1614 {
1615 	const cgame_export_t* list;
1616 
1617 	/* don't run the cgame in console mode */
1618 	if (cls.keyDest == key_console)
1619 		return;
1620 
1621 	list = GAME_GetCurrentType();
1622 	if (list && list->RunFrame != nullptr)
1623 		list->RunFrame(cls.frametime);
1624 }
1625 
GAME_DrawBase(int baseIdx,int x,int y,int w,int h,int col,int row,bool hover,int overlap)1626 void GAME_DrawBase (int baseIdx, int x, int y, int w, int h, int col, int row, bool hover, int overlap)
1627 {
1628 	const cgame_export_t* list = GAME_GetCurrentType();
1629 	if (list && list->DrawBase != nullptr)
1630 		list->DrawBase(baseIdx, x, y, w, h, col, row, hover, overlap);
1631 }
1632 
GAME_DrawBaseTooltip(int baseIdx,int x,int y,int col,int row)1633 void GAME_DrawBaseTooltip (int baseIdx, int x, int y, int col, int row)
1634 {
1635 	const cgame_export_t* list = GAME_GetCurrentType();
1636 	if (list && list->DrawBaseTooltip != nullptr)
1637 		list->DrawBaseTooltip(baseIdx, x, y, col, row);
1638 }
1639 
GAME_DrawBaseLayout(int baseIdx,int x,int y,int totalMarge,int w,int h,int padding,const vec4_t bgcolor,const vec4_t color)1640 void GAME_DrawBaseLayout (int baseIdx, int x, int y, int totalMarge, int w, int h, int padding, const vec4_t bgcolor, const vec4_t color)
1641 {
1642 	const cgame_export_t* list = GAME_GetCurrentType();
1643 	if (list && list->DrawBaseLayout != nullptr)
1644 		list->DrawBaseLayout(baseIdx, x, y, totalMarge, w, h, padding, bgcolor, color);
1645 }
1646 
GAME_HandleBaseClick(int baseIdx,int key,int col,int row)1647 void GAME_HandleBaseClick (int baseIdx, int key, int col, int row)
1648 {
1649 	const cgame_export_t* list = GAME_GetCurrentType();
1650 	if (list && list->HandleBaseClick != nullptr)
1651 		list->HandleBaseClick(baseIdx, key, col, row);
1652 }
1653 
1654 /**
1655  * @brief Get a model for an item.
1656  * @param[in] od The object definition to get the model from.
1657  * @param[out] uiModel The menu model pointer.
1658  * @return The model path for the item. Never @c nullptr.
1659  */
GAME_GetModelForItem(const objDef_t * od,uiModel_t ** uiModel)1660 const char* GAME_GetModelForItem (const objDef_t* od, uiModel_t** uiModel)
1661 {
1662 	const cgame_export_t* list = GAME_GetCurrentType();
1663 	if (list && list->GetModelForItem != nullptr) {
1664 		const char* model = list->GetModelForItem(od->id);
1665 		if (model != nullptr) {
1666 			if (uiModel != nullptr)
1667 				*uiModel = UI_GetUIModel(model);
1668 			return model;
1669 		}
1670 	}
1671 
1672 	if (uiModel != nullptr)
1673 		*uiModel = nullptr;
1674 	return od->model;
1675 }
1676 
1677 /**
1678  * @brief Returns the currently selected character.
1679  * @return The selected character or @c nullptr.
1680  */
GAME_GetSelectedChr(void)1681 character_t* GAME_GetSelectedChr (void)
1682 {
1683 	const cgame_export_t* list = GAME_GetCurrentType();
1684 	if (list && list->GetSelectedChr != nullptr)
1685 		return list->GetSelectedChr();
1686 
1687 	const int ucn = Cvar_GetInteger("mn_ucn");
1688 	character_t* chr = nullptr;
1689 	LIST_Foreach(chrDisplayList, character_t, chrTmp) {
1690 		if (ucn == chrTmp->ucn) {
1691 			chr = chrTmp;
1692 			break;
1693 		}
1694 	}
1695 	return chr;
1696 }
1697 
1698 /**
1699  * @brief Returns the max weight the given character can carry.
1700  * @param[in] chr The character to find the max load for.
1701  * @return The max load the character can carry or @c NONE.
1702  */
GAME_GetChrMaxLoad(const character_t * chr)1703 int GAME_GetChrMaxLoad (const character_t* chr)
1704 {
1705 	if (chr == nullptr)
1706 		return NONE;
1707 
1708 	const cgame_export_t* list = GAME_GetCurrentType();
1709 	const int strength = chr->score.skills[ABILITY_POWER];
1710 	if (list && list->GetChrMaxLoad != nullptr)
1711 		return std::min(strength, list->GetChrMaxLoad(chr));
1712 	return strength;
1713 }
1714 
1715 /**
1716  * @brief Changed the given cvar to the next/prev equipment definition
1717  */
GAME_ChangeEquip(const linkedList_t * equipmentList,changeEquipType_t changeType,const char * equipID)1718 const equipDef_t* GAME_ChangeEquip (const linkedList_t* equipmentList, changeEquipType_t changeType, const char* equipID)
1719 {
1720 	const equipDef_t* ed;
1721 
1722 	if (LIST_IsEmpty(equipmentList)) {
1723 		int index;
1724 		ed = INV_GetEquipmentDefinitionByID(equipID);
1725 		if (ed == nullptr)
1726 			Com_Error(ERR_DROP, "Could not find the equipment definition for '%s'", equipID);
1727 		index = ed - csi.eds;
1728 
1729 		switch (changeType) {
1730 		case BACKWARD:
1731 			index--;
1732 			if (index < 0)
1733 				index = csi.numEDs - 1;
1734 			break;
1735 		case FORWARD:
1736 			index++;
1737 			if (index >= csi.numEDs)
1738 				index = 0;
1739 			break;
1740 		default:
1741 			break;
1742 		}
1743 		ed = &csi.eds[index];
1744 	} else {
1745 		const linkedList_t* entry = LIST_ContainsString(equipmentList, equipID);
1746 		if (entry == nullptr) {
1747 			equipID = (const char*)equipmentList->data;
1748 		} else if (changeType == FORWARD) {
1749 			equipID = (const char*)(entry->next != nullptr ? entry->next->data : equipmentList->data);
1750 		} else if (changeType == BACKWARD) {
1751 			const char* newEntry = nullptr;
1752 			const char* prevEntry = nullptr;
1753 			LIST_Foreach(equipmentList, char const, tmp) {
1754 				if (Q_streq(tmp, equipID)) {
1755 					if (prevEntry != nullptr) {
1756 						newEntry = prevEntry;
1757 						break;
1758 					}
1759 				}
1760 				prevEntry = tmp;
1761 				newEntry = tmp;
1762 			}
1763 			equipID = newEntry;
1764 		}
1765 		ed = INV_GetEquipmentDefinitionByID(equipID);
1766 		if (ed == nullptr)
1767 			Com_Error(ERR_DROP, "Could not find the equipment definition for '%s'", equipID);
1768 	}
1769 
1770 	return ed;
1771 }
1772 
1773 /**
1774  * @brief Fills the game mode list entries with the parsed values from the script
1775  */
GAME_InitUIData(void)1776 void GAME_InitUIData (void)
1777 {
1778 	int i;
1779 
1780 	Com_Printf("----------- game modes -------------\n");
1781 	for (i = 0; i < numCGameTypes; i++) {
1782 		const cgameType_t* t = &cgameTypes[i];
1783 		const cgame_export_t* e = GAME_GetCGameAPI_(t);
1784 		if (e == nullptr)
1785 			continue;
1786 
1787 		if (e->isMultiplayer)
1788 			UI_ExecuteConfunc("game_addmode_multiplayer \"%s\" \"%s\"", t->window, t->name);
1789 		else
1790 			UI_ExecuteConfunc("game_addmode_singleplayer \"%s\" \"%s\"", t->window, t->name);
1791 		Com_Printf("added %s\n", t->name);
1792 		GAME_UnloadGame();
1793 	}
1794 
1795 	Com_Printf("added %i game modes\n", numCGameTypes);
1796 }
1797 
GAME_InitStartup(void)1798 void GAME_InitStartup (void)
1799 {
1800 	Cmd_AddCommand("game_setmode", GAME_SetMode_f, "Decides with game mode should be set - takes the menu as reference");
1801 	Cmd_AddCommand("game_exit", GAME_Exit_f, "Abort the game and let the aliens/opponents win");
1802 	Cmd_AddCommand("game_abort", GAME_Abort_f, "Abort the game and let the aliens/opponents win");
1803 	Cmd_AddCommand("game_autoteam", GAME_AutoTeam_f, "Assign initial equipment to soldiers");
1804 	Cmd_AddCommand("game_toggleactor", GAME_ToggleActorForTeam_f);
1805 	Cmd_AddCommand("game_saveteamstate", GAME_SaveTeamState_f);
1806 	Cmd_AddCommand("game_saveteam", GAME_SaveTeam_f, "Save a team slot");
1807 	Cmd_AddCommand("game_loadteam", GAME_LoadTeam_f, "Load a team slot");
1808 	Cmd_AddCommand("game_teamcomments", GAME_TeamSlotComments_f, "Fills the team selection menu with the team names");
1809 	Cmd_AddCommand("game_teamupdate", GAME_UpdateTeamMenuParameters_f, "");
1810 	Cmd_AddCommand("game_teamdelete", GAME_TeamDelete_f, "Removes a team");
1811 	Cmd_AddCommand("game_actorselect", GAME_ActorSelect_f, "Select an actor in the equipment menu");
1812 	Cmd_AddCommand("mn_getmaps", UI_GetMaps_f, "The initial map to show");
1813 	Cmd_AddCommand("mn_nextmap", UI_NextMap_f, "Switch to the next valid map for the selected gametype");
1814 	Cmd_AddCommand("mn_prevmap", UI_PreviousMap_f, "Switch to the previous valid map for the selected gametype");
1815 	Cmd_AddCommand("mn_selectmap", UI_SelectMap_f, "Switch to the map given by the parameter - may be invalid for the current gametype");
1816 	Cmd_AddCommand("mn_requestmaplist", UI_RequestMapList_f, "Request to send the list of available maps for the current gametype to a command.");
1817 
1818 	Cvar_RegisterCvarListener(cvarListener);
1819 }
1820 
GAME_Shutdown(void)1821 void GAME_Shutdown (void)
1822 {
1823 	OBJZERO(cgameTypes);
1824 	numCGameTypes = 0;
1825 	OBJZERO(equipDefStandard);
1826 	OBJZERO(characters);
1827 
1828 	Cvar_UnRegisterCvarListener(cvarListener);
1829 }
1830