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