1 /**
2 * @file
3 * @brief Skirmish game type implementation
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_shared.h"
27 #include "../cl_game.h"
28 #include "cl_game_skirmish.h"
29 #include "../../ui/ui_data.h"
30
31 #define DROPSHIP_MAX INTERCEPTOR_STILETTO
32
33 static cvar_t* cl_equip;
34 static const cgame_import_t* cgi;
35
36 CGAME_HARD_LINKED_FUNCTIONS
37
GAME_SK_InitMissionBriefing(const char ** title,linkedList_t ** victoryConditionsMsgIDs,linkedList_t ** missionBriefingMsgIDs)38 static void GAME_SK_InitMissionBriefing (const char** title, linkedList_t** victoryConditionsMsgIDs, linkedList_t** missionBriefingMsgIDs)
39 {
40 const mapDef_t* md = cgi->GAME_GetCurrentSelectedMap();
41 if (Q_strvalid(md->victoryCondition)) {
42 cgi->LIST_AddString(victoryConditionsMsgIDs, md->victoryCondition);
43 }
44 if (Q_strvalid(md->missionBriefing)) {
45 cgi->LIST_AddString(missionBriefingMsgIDs, md->missionBriefing);
46 }
47 if (Q_strvalid(md->description)) {
48 *title = _(md->description);
49 }
50 }
51
GAME_SK_GetRandomMapAssemblyNameForCraft(const char * name)52 static inline const char* GAME_SK_GetRandomMapAssemblyNameForCraft (const char* name)
53 {
54 return cgi->Com_GetRandomMapAssemblyNameForCraft(name);
55 }
56
57 /**
58 * @brief Register some data in the shared client/server structs to ensure that e.g. every known
59 * alien race is used in a skirmish game
60 */
GAME_SK_SetMissionParameters(const mapDef_t * md)61 static void GAME_SK_SetMissionParameters (const mapDef_t* md)
62 {
63 int i;
64
65 cgi->Cvar_SetValue("ai_numcivilians", 8);
66 if (md->civTeam != nullptr)
67 cgi->Cvar_Set("ai_civilianteam", "%s", md->civTeam);
68 else
69 cgi->Cvar_Set("ai_civilianteam", "europe");
70
71 cgi->Cvar_Set("sv_hurtaliens", "0");
72
73 /* now store the alien teams in the shared csi struct to let the game dll
74 * have access to this data, too */
75 cgi->csi->numAlienTeams = 0;
76 for (i = 0; i < cgi->csi->numTeamDefs; i++) {
77 const teamDef_t* td = &cgi->csi->teamDef[i];
78 if (CHRSH_IsTeamDefAlien(td))
79 cgi->csi->alienTeams[cgi->csi->numAlienTeams++] = td;
80 if (cgi->csi->numAlienTeams >= MAX_TEAMS_PER_MISSION)
81 break;
82 }
83 }
84
85 /**
86 * @brief Starts a new skirmish game
87 */
GAME_SK_Start_f(void)88 static void GAME_SK_Start_f (void)
89 {
90 const mapDef_t* md;
91
92 if (cgi->GAME_IsTeamEmpty()) {
93 cgi->GAME_LoadDefaultTeam(false);
94 }
95
96 if (cgi->GAME_IsTeamEmpty()) {
97 unsigned int i;
98 /** @todo make the teamdef configurable */
99 const char* ugvTeamDefID = "phalanx_ugv_phoenix";
100 const char* name = cgi->Cvar_GetString("cl_equip");
101 const equipDef_t* ed = cgi->INV_GetEquipmentDefinitionByID(name);
102 const size_t size = cgi->GAME_GetCharacterArraySize();
103 uint32_t maxSoldiers = cgi->Cvar_GetInteger("sv_maxsoldiersperplayer");
104 uint32_t ugvs = cgi->Cvar_GetInteger("cl_ugvs");
105
106 if (maxSoldiers <= 0)
107 maxSoldiers = size;
108
109 ugvs = std::min(ugvs, (uint32_t)(size - maxSoldiers));
110 cgi->Com_Printf("Starting skirmish with %i soldiers and %i ugvs\n", maxSoldiers, ugvs);
111 cgi->GAME_AutoTeam(name, maxSoldiers);
112 for (i = 0; i < ugvs; i++)
113 cgi->GAME_AppendTeamMember(i + maxSoldiers, ugvTeamDefID, ed);
114 } else {
115 cgi->Com_Printf("Using already loaded team\n");
116 }
117
118 md = cgi->GAME_GetCurrentSelectedMap();
119 if (!md)
120 return;
121
122 GAME_SK_SetMissionParameters(md);
123
124 assert(md->map);
125
126 cgi->Cbuf_AddText("map %s %s %s;", cgi->Cvar_GetInteger("mn_serverday") ? "day" : "night", md->map, md->params ? (const char*)cgi->LIST_GetRandom(md->params) : "");
127 }
128
GAME_SK_Restart_f(void)129 static void GAME_SK_Restart_f (void)
130 {
131 cgi->GAME_ReloadMode();
132 GAME_SK_Start_f();
133 }
134
135 /**
136 * @brief Changed the given cvar to the next/prev equipment definition
137 */
GAME_SK_ChangeEquip_f(void)138 static void GAME_SK_ChangeEquip_f (void)
139 {
140 const char* cvarName;
141 const char* command;
142 changeEquipType_t type;
143 const equipDef_t* ed;
144 char cvarBuf[MAX_VAR];
145
146 if (cgi->Cmd_Argc() < 2) {
147 cgi->Com_Printf("Usage: %s <cvarname>\n", cgi->Cmd_Argv(0));
148 return;
149 }
150
151 command = cgi->Cmd_Argv(0);
152 cvarName = cgi->Cmd_Argv(1);
153
154 if (Q_streq(command, "sk_prevequip")) {
155 type = BACKWARD;
156 } else if (Q_streq(command, "sk_nextequip")) {
157 type = FORWARD;
158 } else {
159 type = INIT;
160 }
161
162 ed = cgi->GAME_ChangeEquip(cgi->cgameType->equipmentList, type, cgi->Cvar_GetString(cvarName));
163
164 Com_sprintf(cvarBuf, sizeof(cvarBuf), "%sname", cvarName);
165
166 cgi->Cvar_Set(cvarName, "%s", ed->id);
167 cgi->Cvar_Set(cvarBuf, "_%s", ed->name);
168 }
169
170 /**
171 * @brief After a mission was finished this function is called
172 * @param msg The network message buffer
173 * @param winner The winning team
174 * @param numSpawned The amounts of all spawned actors per team
175 * @param numAlive The amount of survivors per team
176 * @param numKilled The amount of killed actors for all teams. The first dimension contains
177 * the attacker team, the second the victim team
178 * @param numStunned The amount of stunned actors for all teams. The first dimension contains
179 * the attacker team, the second the victim team
180 * @param nextmap Indicates if there is another map to follow within the same msission
181 */
GAME_SK_Results(dbuffer * msg,int winner,int * numSpawned,int * numAlive,int numKilled[][MAX_TEAMS],int numStunned[][MAX_TEAMS],bool nextmap)182 static void GAME_SK_Results (dbuffer* msg, int winner, int* numSpawned, int* numAlive, int numKilled[][MAX_TEAMS], int numStunned[][MAX_TEAMS], bool nextmap)
183 {
184 char resultText[1024];
185 int enemiesKilled, enemiesStunned;
186 const int team = cgi->GAME_GetCurrentTeam();
187 const int ownFriendlyFire = numKilled[team][team] + numKilled[TEAM_CIVILIAN][team];
188 const int ownLost = numSpawned[team] - numAlive[team] - ownFriendlyFire;
189 const int civFriendlyFire = numKilled[team][TEAM_CIVILIAN] + numKilled[TEAM_CIVILIAN][TEAM_CIVILIAN];
190 const int civLost = numSpawned[TEAM_CIVILIAN] - numAlive[TEAM_CIVILIAN] - civFriendlyFire;
191
192 if (nextmap)
193 return;
194
195 cgi->CL_Drop();
196
197 if (winner == -1) {
198 cgi->UI_Popup(_("Game Drawn!"), "%s\n", _("The game was a draw!\n\nEnemies escaped."));
199 return;
200 }
201
202 if (winner == 0) {
203 cgi->UI_Popup(_("Game Drawn!"), "%s\n", _("The game was a draw!\n\nNo survivors left on any side."));
204 return;
205 }
206
207 enemiesStunned = 0;
208 for (int i = 0; i <= MAX_TEAMS; ++i) {
209 enemiesStunned += numStunned[i][TEAM_ALIEN];
210 }
211 enemiesKilled = numSpawned[TEAM_ALIEN] - numAlive[TEAM_ALIEN] - enemiesStunned;
212
213 Com_sprintf(resultText, sizeof(resultText),
214 _("\n%i of %i enemies killed, %i stunned, %i survived.\n"
215 "%i of %i team members survived, %i lost in action, %i friendly fire loses.\n"
216 "%i of %i civilians saved, %i civilian loses, %i friendly fire loses."),
217 enemiesKilled, numSpawned[TEAM_ALIEN], enemiesStunned, numAlive[TEAM_ALIEN],
218 numAlive[team], numSpawned[team], ownLost, ownFriendlyFire,
219 numAlive[TEAM_CIVILIAN], numSpawned[TEAM_CIVILIAN], civLost, civFriendlyFire);
220 if (winner == team) {
221 cgi->UI_Popup(_("Congratulations"), "%s\n%s\n", _("You won the game!"), resultText);
222 } else {
223 cgi->UI_Popup(_("Better luck next time"), "%s\n%s\n", _("You've lost the game!"), resultText);
224 }
225 }
226
227 /**
228 * @brief Hide the dropship selection or show it with the dropship given in the parameter
229 * @param dropships if @c nullptr, the dropship selection panel will be hidden, otherwise it
230 * will be shown with the given list entries as content.
231 */
GAME_SK_HideDropships(const linkedList_t * dropships)232 static inline void GAME_SK_HideDropships (const linkedList_t* dropships)
233 {
234 const bool hide = (dropships == nullptr);
235 if (hide) {
236 cgi->UI_ExecuteConfunc("skirmish_hide_dropships true");
237 cgi->Cvar_Set("rm_drop", "");
238 } else {
239 const char* rma = GAME_SK_GetRandomMapAssemblyNameForCraft((const char*)dropships->data);
240 cgi->Cvar_Set("rm_drop", "%s", rma);
241 cgi->UI_UpdateInvisOptions(cgi->UI_GetOption(OPTION_DROPSHIPS), dropships);
242 cgi->UI_RegisterOption(OPTION_DROPSHIPS, cgi->UI_GetOption(OPTION_DROPSHIPS));
243
244 cgi->UI_ExecuteConfunc("skirmish_hide_dropships false");
245 }
246 }
247
248 /**
249 * @brief Hide the ufo selection or show it with the ufos given in the parameter
250 * @param ufos if @c nullptr, the ufo selection panel will be hidden, otherwise it
251 * will be shown with the given list entries as content.
252 */
GAME_SK_HideUFOs(const linkedList_t * ufos)253 static inline void GAME_SK_HideUFOs (const linkedList_t* ufos)
254 {
255 const bool hide = (ufos == nullptr);
256 if (hide) {
257 cgi->UI_ExecuteConfunc("skirmish_hide_ufos true");
258 cgi->Cvar_Set("rm_ufo", "");
259 } else {
260 const char* rma = GAME_SK_GetRandomMapAssemblyNameForCraft((const char*)ufos->data);
261 cgi->Cvar_Set("rm_ufo", "%s", rma);
262 cgi->UI_UpdateInvisOptions(cgi->UI_GetOption(OPTION_UFOS), ufos);
263 cgi->UI_RegisterOption(OPTION_UFOS, cgi->UI_GetOption(OPTION_UFOS));
264
265 cgi->UI_ExecuteConfunc("skirmish_hide_ufos false");
266 }
267 cgi->Cvar_Set("rm_crashed", "");
268 }
269
GAME_SK_MapInfo(int step)270 static const mapDef_t* GAME_SK_MapInfo (int step)
271 {
272 const mapDef_t* md;
273 int i = 0;
274
275 while (!cgi->GAME_GetCurrentSelectedMap()->singleplayer) {
276 i++;
277 cgi->GAME_SwitchCurrentSelectedMap(step ? step : 1);
278 if (i > 100000)
279 cgi->Com_Error(ERR_DROP, "no singleplayer map found");
280 }
281
282 md = cgi->GAME_GetCurrentSelectedMap();
283
284 cgi->Cvar_SetValue("ai_singleplayeraliens", md->maxAliens);
285
286 if (md->map[0] == '.')
287 return nullptr;
288
289 if (md->map[0] == '+') {
290 GAME_SK_HideUFOs(md->ufos);
291 GAME_SK_HideDropships(md->aircraft);
292 } else {
293 GAME_SK_HideUFOs(nullptr);
294 GAME_SK_HideDropships(nullptr);
295 }
296
297 return md;
298 }
299
GAME_InitMenuOptions(void)300 static void GAME_InitMenuOptions (void)
301 {
302 int i;
303 uiNode_t* ufoOptions = nullptr;
304 uiNode_t* aircraftOptions = nullptr;
305
306 for (i = 0; i < UFO_MAX; i++) {
307 const char* shortName = cgi->Com_UFOTypeToShortName((ufoType_t)i);
308 cgi->UI_AddOption(&ufoOptions, shortName, shortName, GAME_SK_GetRandomMapAssemblyNameForCraft(shortName));
309 }
310 for (i = 0; i < UFO_MAX; i++) {
311 const char* shortName = cgi->Com_UFOCrashedTypeToShortName((ufoType_t)i);
312 cgi->UI_AddOption(&ufoOptions, shortName, shortName, GAME_SK_GetRandomMapAssemblyNameForCraft(shortName));
313 }
314 cgi->UI_RegisterOption(OPTION_UFOS, ufoOptions);
315
316 for (i = 0; i < DROPSHIP_MAX; i++) {
317 const char* shortName = cgi->Com_DropShipTypeToShortName((humanAircraftType_t)i);
318 cgi->UI_AddOption(&aircraftOptions, shortName, shortName, GAME_SK_GetRandomMapAssemblyNameForCraft(shortName));
319 }
320 cgi->UI_RegisterOption(OPTION_DROPSHIPS, aircraftOptions);
321 }
322
GAME_SK_InitStartup(void)323 static void GAME_SK_InitStartup (void)
324 {
325 cgi->Cvar_ForceSet("sv_maxclients", "1");
326 cl_equip = cgi->Cvar_Get("cl_equip", "multiplayer_initial", 0, "Equipment that is used for skirmish mode games");
327
328 cgi->Cmd_AddCommand("sk_start", GAME_SK_Start_f, "Start the new skirmish game");
329 cgi->Cmd_AddCommand("sk_prevequip", GAME_SK_ChangeEquip_f, "Previous equipment definition");
330 cgi->Cmd_AddCommand("sk_nextequip", GAME_SK_ChangeEquip_f, "Next equipment definition");
331 cgi->Cmd_AddCommand("sk_initequip", GAME_SK_ChangeEquip_f, "Init equipment definition");
332 cgi->Cmd_AddCommand("game_go", GAME_SK_Restart_f, "Restart the skirmish mission");
333
334 GAME_InitMenuOptions();
335 }
336
GAME_SK_Shutdown(void)337 static void GAME_SK_Shutdown (void)
338 {
339 cgi->Cmd_RemoveCommand("sk_start");
340 cgi->Cmd_RemoveCommand("sk_nextequip");
341 cgi->Cmd_RemoveCommand("sk_prevequip");
342 cgi->Cmd_RemoveCommand("sk_initequip");
343 cgi->Cmd_RemoveCommand("game_go");
344
345 cgi->UI_ResetData(OPTION_DROPSHIPS);
346 cgi->UI_ResetData(OPTION_UFOS);
347
348 cgi->SV_Shutdown("Quitting server.", false);
349 }
350
351 #ifndef HARD_LINKED_CGAME
GetCGameAPI(const cgame_import_t * import)352 const cgame_export_t* GetCGameAPI (const cgame_import_t* import)
353 #else
354 const cgame_export_t* GetCGameSkirmishAPI (const cgame_import_t* import)
355 #endif
356 {
357 static cgame_export_t e;
358
359 OBJZERO(e);
360
361 e.name ="Skirmish mode";
362 e.menu = "skirmish";
363 e.Init = GAME_SK_InitStartup;
364 e.Shutdown = GAME_SK_Shutdown;
365 e.MapInfo = GAME_SK_MapInfo;
366 e.Results = GAME_SK_Results;
367 e.InitMissionBriefing = GAME_SK_InitMissionBriefing;
368
369 cgi = import;
370
371 return &e;
372 }
373