1 /**
2  * @file
3  * @brief Multiplayer 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_shared.h"
27 #include "../cl_game.h"
28 #include "cl_game_multiplayer.h"
29 #include "mp_callbacks.h"
30 #include "mp_serverlist.h"
31 #include "../../ui/ui_data.h"
32 
33 static const cgame_import_t* cgi;
34 
35 CGAME_HARD_LINKED_FUNCTIONS
36 
GAME_MP_StartBattlescape(bool isTeamPlay)37 static void GAME_MP_StartBattlescape (bool isTeamPlay)
38 {
39 	cgi->UI_ExecuteConfunc("multiplayer_setTeamplay %i", isTeamPlay);
40 	cgi->UI_InitStack("multiplayer_wait", nullptr);
41 	rcon_client_password->modified = true;
42 }
43 
GAME_MP_NotifyEvent(event_t eventType)44 static void GAME_MP_NotifyEvent (event_t eventType)
45 {
46 	if (eventType != EV_RESET)
47 		return;
48 
49 	cgi->HUD_InitUI("multiplayerInGame");
50 }
51 
GAME_MP_EndRoundAnnounce(int playerNum,int team)52 static void GAME_MP_EndRoundAnnounce (int playerNum, int team)
53 {
54 	char buf[128];
55 
56 	/* it was our own turn */
57 	if (cgi->CL_GetPlayerNum() == playerNum) {
58 		Com_sprintf(buf, sizeof(buf), _("You've ended your turn.\n"));
59 	} else {
60 		const char* playerName = cgi->CL_PlayerGetName(playerNum);
61 		Com_sprintf(buf, sizeof(buf), _("%s ended his turn (team %i).\n"), playerName, team);
62 	}
63 	/* add translated message to chat buffer */
64 	cgi->HUD_DisplayMessage(buf);
65 }
66 
67 /**
68  * @brief Starts a server and checks if the server loads a team unless he is a dedicated
69  * server admin
70  */
GAME_MP_StartServer_f(void)71 static void GAME_MP_StartServer_f (void)
72 {
73 	const mapDef_t* md;
74 
75 	if (!cgi->Cvar_GetInteger("sv_dedicated") && cgi->GAME_IsTeamEmpty())
76 		cgi->GAME_AutoTeam("multiplayer_initial", cgi->GAME_GetCharacterArraySize());
77 
78 	if (cgi->Cvar_GetInteger("sv_teamplay")
79 	 && cl_maxsoldiersperplayer->integer > cl_maxsoldiersperteam->integer) {
80 		cgi->UI_Popup(_("Settings doesn't make sense"), _("Set soldiers per player lower than soldiers per team"));
81 		return;
82 	}
83 
84 	md = cgi->GAME_GetCurrentSelectedMap();
85 	if (!md || !md->multiplayer)
86 		return;
87 	assert(md->map);
88 
89 	/** @todo implement different ufo and dropship support for multiplayer, too (see skirmish) */
90 	cgi->Cvar_Set("rm_drop", "");
91 	cgi->Cvar_Set("rm_ufo", "");
92 	cgi->Cvar_Set("sv_hurtaliens", "0");
93 
94 	if (md->teams)
95 		cgi->Cvar_SetValue("sv_maxteams", md->teams);
96 	else
97 		cgi->Cvar_SetValue("sv_maxteams", 2);
98 
99 	cgi->Cmd_ExecuteString("map %s %s %s", cgi->Cvar_GetInteger("mn_serverday") ? "day" : "night", md->map, md->params ? (const char*)cgi->LIST_GetRandom(md->params) : "");
100 
101 	cgi->UI_InitStack("multiplayer_wait", "multiplayerInGame");
102 }
103 
104 /**
105  * @brief After a mission was finished this function is called
106  * @param msg The network message buffer
107  * @param winner The winning team
108  * @param numSpawned The amounts of all spawned actors per team
109  * @param numAlive The amount of survivors per team
110  * @param numKilled The amount of killed actors for all teams. The first dimension contains
111  * the attacker team, the second the victim team
112  * @param numStunned The amount of stunned actors for all teams. The first dimension contains
113  * the attacker team, the second the victim team
114  * @param nextmap Indicates if there is another map to follow within the same mission
115  */
GAME_MP_Results(dbuffer * msg,int winner,int * numSpawned,int * numAlive,int numKilled[][MAX_TEAMS],int numStunned[][MAX_TEAMS],bool nextmap)116 static void GAME_MP_Results (dbuffer* msg, int winner, int* numSpawned, int* numAlive, int numKilled[][MAX_TEAMS], int numStunned[][MAX_TEAMS], bool nextmap)
117 {
118 	linkedList_t* list = nullptr;
119 	int enemiesKilled, enemiesStunned;
120 	const int team = cgi->GAME_GetCurrentTeam();
121 
122 	enemiesKilled = enemiesStunned = 0;
123 	for (int i = 0; i < MAX_TEAMS; i++) {
124 		if (i == team)
125 			continue;
126 		enemiesKilled += numKilled[team][i];
127 		enemiesStunned += numStunned[team][i];
128 	}
129 
130 	cgi->LIST_AddString(&list, va(_("Enemies killed:\t%i"), enemiesKilled + enemiesStunned));
131 	cgi->LIST_AddString(&list, va(_("Team survivors:\t%i"), numAlive[team]));
132 	cgi->UI_RegisterLinkedListText(TEXT_LIST2, list);
133 	if (winner == team) {
134 		cgi->UI_PushWindow("won");
135 	} else {
136 		cgi->UI_PushWindow("lost");
137 	}
138 }
139 
GAME_MP_MapInfo(int step)140 static const mapDef_t* GAME_MP_MapInfo (int step)
141 {
142 	int i = 0;
143 	const char* gameType = cgi->Cvar_GetString("sv_gametype");
144 	for (;;) {
145 		i++;
146 		if (i > 100000)
147 			break;
148 
149 		const mapDef_t* md = cgi->GAME_GetCurrentSelectedMap();
150 		if (md == nullptr)
151 			break;
152 		if (!md->multiplayer || !cgi->LIST_ContainsString(md->gameTypes, gameType)) {
153 			cgi->GAME_SwitchCurrentSelectedMap(step ? step : 1);
154 			continue;
155 		}
156 		linkedList_t* gameNames = nullptr;
157 		for (int j = 0; j < cgi->csi->numGTs; j++) {
158 			const gametype_t* gt = &cgi->csi->gts[j];
159 			if (cgi->LIST_ContainsString(md->gameTypes, gt->id)) {
160 				cgi->LIST_AddString(&gameNames, _(gt->name));
161 			}
162 		}
163 		cgi->UI_RegisterLinkedListText(TEXT_LIST2, gameNames);
164 		cgi->Cvar_SetValue("ai_singleplayeraliens", md->maxAliens);
165 
166 		return md;
167 	}
168 
169 	cgi->Com_Printf("no multiplayer map found for the current selected gametype: '%s'", gameType);
170 	return nullptr;
171 }
172 
173 /**
174  * @brief Update the map according to the gametype
175  */
GAME_MP_UpdateGametype_f(void)176 static void GAME_MP_UpdateGametype_f (void)
177 {
178 	const int numGTs = cgi->csi->numGTs;
179 	/* no types defined or parsed */
180 	if (numGTs == 0)
181 		return;
182 
183 	cgi->Com_SetGameType();
184 
185 	const char* gameType = cgi->Cvar_GetString("sv_gametype");
186 	const mapDef_t* md = cgi->GAME_GetCurrentSelectedMap();
187 	if (md != nullptr && md->multiplayer && cgi->LIST_ContainsString(md->gameTypes, gameType)) {
188 		/* no change needed, gametype is supported */
189 		return;
190 	}
191 
192 	GAME_MP_MapInfo(1);
193 }
194 
195 static linkedList_t* mp_chatMessageStack = nullptr;
196 
197 /**
198  * @brief Displays a chat on the hud and add it to the chat buffer
199  */
GAME_MP_AddChatMessage(const char * text)200 static void GAME_MP_AddChatMessage (const char* text)
201 {
202 	char message[2048];
203 	Q_strncpyz(message, text, sizeof(message));
204 
205 	const char* msg = Com_Trim(message);
206 	cgi->LIST_AddString(&mp_chatMessageStack, msg);
207 	cgi->HUD_DisplayMessage(msg);
208 	cgi->UI_RegisterLinkedListText(TEXT_CHAT_WINDOW, mp_chatMessageStack);
209 	cgi->UI_TextScrollEnd("hud_chat.allchats.chatscreen.chat");
210 }
211 
GAME_MP_HandleServerCommand(const char * command,dbuffer * msg)212 static bool GAME_MP_HandleServerCommand (const char* command, dbuffer* msg)
213 {
214 	if (Q_streq(command, SV_CMD_TEAMINFO)) {
215 		GAME_MP_ParseTeamInfoMessage(msg);
216 		return true;
217 	}
218 
219 	return false;
220 }
221 
GAME_MP_InitStartup(void)222 static void GAME_MP_InitStartup (void)
223 {
224 	cgi->Cvar_ForceSet("sv_maxclients", "2");
225 	/** @todo make equipment configurable for multiplayer */
226 	cgi->Cvar_Set("cl_equip", "multiplayer_initial");
227 
228 	cgi->Cmd_AddCommand("mp_startserver", GAME_MP_StartServer_f, nullptr);
229 	cgi->Cmd_AddCommand("mp_updategametype", GAME_MP_UpdateGametype_f, "Update the menu values with current gametype values");
230 	GAME_MP_CallbacksInit(cgi);
231 	GAME_MP_ServerListInit(cgi);
232 }
233 
GAME_MP_Shutdown(void)234 static void GAME_MP_Shutdown (void)
235 {
236 	cgi->Cmd_RemoveCommand("mp_startserver");
237 	cgi->Cmd_RemoveCommand("mp_updategametype");
238 	GAME_MP_CallbacksShutdown();
239 	GAME_MP_ServerListShutdown();
240 
241 	cgi->SV_Shutdown("Game mode shutdown", false);
242 
243 	OBJZERO(teamData);
244 }
245 
GAME_MP_RunFrame(float secondsSinceLastFrame)246 static void GAME_MP_RunFrame (float secondsSinceLastFrame)
247 {
248 	if (!cgi->Com_ServerState() && cgi->CL_GetClientState() < ca_connected && Q_strnull(cgi->Cvar_GetString("rcon_address")))
249 		return;
250 
251 	if (rcon_client_password->modified) {
252 		rcon_client_password->modified = false;
253 		if (!cgi->Com_ServerState() && Q_strnull(rcon_client_password->string)) {
254 			cgi->UI_ExecuteConfunc("multiplayer_admin_panel 0");
255 		} else {
256 			cgi->UI_ExecuteConfunc("multiplayer_admin_panel 1");
257 		}
258 	}
259 
260 	cvar_t* cvars[] = {cl_maxsoldiersperteam, cl_maxsoldiersperplayer, cl_roundtimelimit};
261 	for (int i = 0; i < lengthof(cvars); i++) {
262 		if (!cvars[i]->modified) {
263 			continue;
264 		}
265 		cvars[i]->modified = false;
266 		if (!cgi->Com_ServerState()) {
267 			cgi->Cmd_ExecuteString(SV_CMD_RCON " set %s %s", cvars[i]->name, cvars[i]->string);
268 		}
269 	}
270 }
271 
272 #ifndef HARD_LINKED_CGAME
GetCGameAPI(const cgame_import_t * import)273 const cgame_export_t* GetCGameAPI (const cgame_import_t* import)
274 #else
275 const cgame_export_t* GetCGameMultiplayerAPI (const cgame_import_t* import)
276 #endif
277 {
278 	static cgame_export_t e;
279 
280 	OBJZERO(e);
281 
282 	e.name = "Multiplayer mode";
283 	e.menu = "multiplayer";
284 	e.isMultiplayer = 1;
285 	e.Init = GAME_MP_InitStartup;
286 	e.Shutdown = GAME_MP_Shutdown;
287 	e.MapInfo = GAME_MP_MapInfo;
288 	e.Results = GAME_MP_Results;
289 	e.EndRoundAnnounce = GAME_MP_EndRoundAnnounce;
290 	e.StartBattlescape = GAME_MP_StartBattlescape;
291 	e.NotifyEvent = GAME_MP_NotifyEvent;
292 	e.AddChatMessage = GAME_MP_AddChatMessage;
293 	e.HandleServerCommand = GAME_MP_HandleServerCommand;
294 	e.RunFrame = GAME_MP_RunFrame;
295 
296 	cgi = import;
297 
298 	return &e;
299 }
300