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