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