1
2 /**
3 *
4 * @file clientgame.cpp
5 *
6 * Part of the OpenJazz project
7 *
8 * @par History:
9 * - 23rd August 2005: Created level.c and menu.c
10 * - 3rd of February 2009: Renamed level.c to level.cpp and menu.c to menu.cpp
11 * - 9th March 2009: Created game.cpp from parts of menu.cpp and level.cpp
12 * - 18th July 2009: Created clientgame.cpp from parts of game.cpp
13 *
14 * @par Licence:
15 * Copyright (c) 2005-2017 Alister Thomson
16 *
17 * OpenJazz is distributed under the terms of
18 * the GNU General Public License, version 2.0
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 *
24 */
25
26
27 #include "game.h"
28 #include "gamemode.h"
29
30 #include "io/controls.h"
31 #include "io/file.h"
32 #include "io/gfx/font.h"
33 #include "io/gfx/video.h"
34 #include "io/network.h"
35 #include "player/player.h"
36 #include "loop.h"
37 #include "setup.h"
38 #include "util.h"
39
40 #include <string.h>
41
42
43 /**
44 * Create game client
45 *
46 * @param address Address of the server to which to connect
47 */
ClientGame(char * address)48 ClientGame::ClientGame (char* address) {
49
50 unsigned char buffer[BUFFER_LENGTH];
51 unsigned int timeout;
52 int count, ret;
53 GameModeType modeType;
54
55 sock = net->join(address);
56
57 if (sock < 0) throw sock; // Tee hee hee hee hee.
58
59
60 // Receive initialisation message
61
62 count = 0;
63 timeout = globalTicks + T_SCHECK + T_TIMEOUT;
64
65 // Wait for whole message to arrive
66 while (count < MTL_G_PROPS) {
67
68 if (loop(NORMAL_LOOP) == E_QUIT) {
69
70 net->close(sock);
71
72 throw E_QUIT;
73
74 }
75
76 if (controls.release(C_ESCAPE)) {
77
78 net->close(sock);
79
80 throw E_RETURN;
81
82 }
83
84 SDL_Delay(T_MENU_FRAME);
85
86 video.clearScreen(0);
87 fontmn2->showString("WAITING FOR REPLY", canvasW >> 2, (canvasH >> 1) - 16);
88
89 ret = net->recv(sock, buffer + count, MTL_G_PROPS - count);
90
91 if (ret > 0) count += ret;
92
93 if (globalTicks > timeout) {
94
95 net->close(sock);
96
97 throw E_TIMEOUT;
98
99 }
100
101 }
102
103 // Make sure message is valid
104 if (buffer[1] != MT_G_PROPS) {
105
106 net->close(sock);
107
108 throw E_DATA;
109
110 } else if (buffer[2] != 1) {
111
112 net->close(sock);
113
114 throw E_VERSION;
115
116 }
117
118 printf("Connected to server (version %d).\n", buffer[2]);
119
120 // Copy game parameters
121 modeType = GameModeType(buffer[3]);
122 difficulty = buffer[4];
123 maxPlayers = buffer[5];
124 nPlayers = buffer[6];
125 clientID = buffer[7];
126
127 printf("Game mode %d, difficulty %d, %d of %d players.\n", modeType, difficulty, nPlayers, maxPlayers);
128
129 if (nPlayers > maxPlayers) {
130
131 net->close(sock);
132
133 throw E_DATA;
134
135 }
136
137
138 mode = createMode(modeType);
139
140 if (!mode) {
141
142 net->close(sock);
143
144 throw E_DATA;
145
146 }
147
148
149 // Create players
150 nPlayers = 0;
151 players = new Player[maxPlayers];
152
153
154 // Download the level from the server
155
156 levelFile = createString(LEVEL_FILE);
157 file = NULL;
158
159 ret = setLevel(NULL);
160
161 if (ret < 0) {
162
163 net->close(sock);
164
165 if (file) delete file;
166
167 delete mode;
168
169 throw ret;
170
171 }
172
173 // Add a new player to the game
174
175 buffer[0] = MTL_G_PJOIN + strlen(setup.characterName);
176 buffer[1] = MT_G_PJOIN;
177 buffer[2] = clientID;
178 buffer[3] = 0; // Player's number, assigned by the server
179 buffer[4] = 0; // Player's team, assigned by the server
180 memcpy(buffer + 5, setup.characterCols, 4);
181 memcpy(buffer + 9, setup.characterName, strlen(setup.characterName) + 1);
182 send(buffer);
183
184
185 // Wait for acknowledgement
186
187 localPlayer = NULL;
188
189 while (!localPlayer) {
190
191 if (loop(NORMAL_LOOP) == E_QUIT) {
192
193 net->close(sock);
194
195 if (file) delete file;
196
197 delete mode;
198
199 throw E_QUIT;
200
201 }
202
203 if (controls.release(C_ESCAPE)) {
204
205 net->close(sock);
206
207 if (file) delete file;
208
209 delete mode;
210
211 throw E_RETURN;
212
213 }
214
215 video.clearScreen(0);
216 fontmn2->showString("JOINING GAME", canvasW >> 2, (canvasH >> 1) - 16);
217
218 ret = step(0);
219
220 if (ret < 0) {
221
222 net->close(sock);
223
224 if (file) delete file;
225
226 delete mode;
227
228 throw ret;
229
230 }
231
232 }
233
234 return;
235
236 }
237
238
239 /**
240 * Disconnect and destroy client
241 */
~ClientGame()242 ClientGame::~ClientGame () {
243
244 net->close(sock);
245
246 if (file) delete file;
247
248 delete mode;
249
250 return;
251
252 }
253
254
255 /**
256 * Set the next level and receive level data from server
257 *
258 * @param fileName The file name of the next level
259 *
260 * @return Error code
261 */
setLevel(char * fileName)262 int ClientGame::setLevel (char* fileName) {
263
264 (void)fileName;
265
266 int ret;
267
268 video.setPalette(menuPalette);
269
270 // Wait for level data to start arriving
271 while (!file && levelFile) {
272
273 if (loop(NORMAL_LOOP) == E_QUIT) return E_QUIT;
274
275 if (controls.release(C_ESCAPE)) return E_RETURN;
276
277 SDL_Delay(T_MENU_FRAME);
278
279 video.clearScreen(0);
280 fontmn2->showString("WAITING FOR SERVER", canvasW >> 2, (canvasH >> 1) - 16);
281
282 ret = step(0);
283
284 if (ret < 0) return ret;
285
286 }
287
288 // Wait for level data to finish arriving
289 while (file && levelFile) {
290
291 if (loop(NORMAL_LOOP) == E_QUIT) return E_QUIT;
292
293 if (controls.release(C_ESCAPE)) return E_RETURN;
294
295 SDL_Delay(T_MENU_FRAME);
296
297 video.clearScreen(0);
298 fontmn2->showString("downloaded", canvasW >> 2, (canvasH >> 1) - 16);
299 fontmn2->showNumber(file->tell(), (canvasW >> 2) + 56, canvasH >> 1);
300 fontmn2->showString("bytes", (canvasW >> 2) + 64, canvasH >> 1);
301
302 ret = step(0);
303
304 if (ret < 0) return ret;
305
306 }
307
308 return E_NONE;
309
310 }
311
312
313 /**
314 * Send data to server
315 *
316 * @param buffer Data to send. First byte indicates length.
317 */
send(unsigned char * buffer)318 void ClientGame::send (unsigned char* buffer) {
319
320 net->send(sock, buffer);
321
322 return;
323
324 }
325
326
327 /**
328 * Game iteration
329 *
330 * @param ticks Current time
331 *
332 * @return Error code
333 */
step(unsigned int ticks)334 int ClientGame::step (unsigned int ticks) {
335
336 unsigned char sendBuffer[BUFFER_LENGTH];
337 int length, count;
338 bool firstMessage;
339
340 // Receive data from server
341
342 if (received == 0) {
343
344 // Not currently receiving a message
345 // See if there is a new message to receive
346
347 length = net->recv(sock, recvBuffer, 1);
348
349 if (length > 0) received++;
350
351 }
352
353 if (received > 0) {
354
355 // Currently receiving a message
356 // See if there is any more data
357
358 length = net->recv(sock, recvBuffer + received,
359 recvBuffer[0] - received);
360
361 if (length > 0) received += length;
362
363
364 // See if the whole message has arrived
365
366 if (received >= recvBuffer[0]) {
367
368 switch (recvBuffer[1] & MCMASK) {
369
370 case MC_GAME:
371
372 if (recvBuffer[1] == MT_G_LEVEL) {
373
374 if (!file) {
375
376 // Not already storing level data, so open the file
377
378 try {
379
380 file = new File(levelFile, true);
381
382 } catch (int e) {
383
384 return e;
385
386 }
387
388 firstMessage = true;
389
390 } else firstMessage = false;
391
392 file->seek((recvBuffer[2] << 8) + recvBuffer[3], true);
393
394 for (count = 4; count < recvBuffer[0]; count++)
395 file->storeChar(recvBuffer[count]);
396
397 // If a zero-length block has been sent, it is the last
398 if (recvBuffer[0] == MTL_G_LEVEL) {
399
400 if (firstMessage) {
401
402 // If the last message was also the first,
403 // then the run of levels has ended
404
405 delete[] levelFile;
406 levelFile = NULL;
407
408 }
409
410 delete file;
411 file = NULL;
412
413 }
414
415 break;
416
417 }
418
419 if ((recvBuffer[1] == MT_G_PJOIN) &&
420 (recvBuffer[3] < maxPlayers)) {
421
422 printf("Player %d joined the game.\n", recvBuffer[3]);
423
424 // Add the new player, and any that have been missed
425
426 for (count = nPlayers; count <= recvBuffer[3]; count++) {
427
428 players[count].init(this, (char *)recvBuffer + 9,
429 recvBuffer + 5, recvBuffer[4]);
430 addLevelPlayer(players + count);
431
432 printf("Player %d joined team %d.\n", count, recvBuffer[4]);
433
434 }
435
436 nPlayers = count;
437
438 if (recvBuffer[2] == clientID)
439 localPlayer = players + recvBuffer[3];
440
441 }
442
443 if ((recvBuffer[1] == MT_G_PQUIT) &&
444 (recvBuffer[2] < nPlayers)) {
445
446 printf("Player %d left the game.\n", recvBuffer[2]);
447
448 // Remove the player
449
450 players[recvBuffer[2]].deinit();
451
452 // If necessary, move more recent players
453 for (count = recvBuffer[2]; count < nPlayers; count++)
454 memcpy(players + count, players + count + 1,
455 sizeof(Player));
456
457 // Clear duplicate pointers
458 memset(players + nPlayers, 0, sizeof(Player));
459
460 }
461
462 if (recvBuffer[1] == MT_G_CHECK) {
463
464 checkX = recvBuffer[2];
465 checkY = recvBuffer[3];
466
467 if (recvBuffer[0] > 4) {
468
469 checkX += recvBuffer[4] << 8;
470 checkY += recvBuffer[5] << 8;
471
472 }
473
474 }
475
476 if (recvBuffer[1] == MT_G_SCORE) {
477
478 for (count = 0; count < nPlayers; count++) {
479
480 if (players[count].getTeam() == recvBuffer[2])
481 players[count].teamScore++;
482
483 }
484
485 }
486
487 if (recvBuffer[1] == MT_G_LTYPE) {
488
489 levelType = (LevelType)recvBuffer[2];
490
491 }
492
493 break;
494
495 case MC_LEVEL:
496
497 if (baseLevel) baseLevel->receive(recvBuffer);
498
499 break;
500
501 case MC_PLAYER:
502
503 if (recvBuffer[2] < maxPlayers)
504 players[recvBuffer[2]].receive(recvBuffer);
505
506 break;
507
508 }
509
510 received = 0;
511
512 }
513
514 }
515
516 if (ticks >= checkTime) {
517
518 // Check for disconnection
519
520 if (!(net->isConnected(sock))) {
521
522 if (file) delete file;
523 file = NULL;
524
525 return E_N_DISCONNECT;
526
527 }
528
529 checkTime = ticks + T_CCHECK;
530
531 }
532
533 if (localPlayer && (ticks >= sendTime)) {
534
535 // Update server
536
537 sendBuffer[0] = MTL_P_TEMP;
538 sendBuffer[1] = MT_P_TEMP;
539 sendBuffer[2] = 0;
540 localPlayer->send(sendBuffer);
541 send(sendBuffer);
542
543 sendTime = ticks + T_CSEND;
544
545 }
546
547 return E_NONE;
548
549 }
550
551
552 /**
553 * Ask server to award team a point
554 *
555 * @param team Team to receive point
556 */
score(unsigned char team)557 void ClientGame::score (unsigned char team) {
558
559 unsigned char buffer[MTL_G_SCORE];
560
561 // Inform server
562 buffer[0] = MTL_G_SCORE;
563 buffer[1] = MT_G_SCORE;
564 buffer[2] = team;
565 send(buffer);
566
567 return;
568
569 }
570
571
572 /**
573 * Ask server to approve new checkpoint
574 *
575 * @param gridX X-coordinate (in tiles) of the checkpoint
576 * @param gridY Y-coordinate (in tiles) of the checkpoint
577 */
setCheckpoint(int gridX,int gridY)578 void ClientGame::setCheckpoint (int gridX, int gridY) {
579
580 unsigned char buffer[MTL_G_CHECK];
581
582 buffer[0] = MTL_G_CHECK;
583 buffer[1] = MT_G_CHECK;
584 buffer[2] = gridX & 0xFF;
585 buffer[3] = gridY & 0xFF;
586 buffer[4] = (gridX >> 8) & 0xFF;
587 buffer[5] = (gridY >> 8) & 0xFF;
588 send(buffer);
589
590 return;
591
592 }
593
594
595