1 /////////////////////////////////////////
2 //
3 // OpenLieroX
4 //
5 // code under LGPL, based on JasonBs work,
6 // enhanced by Dark Charlie and Albert Zeyer
7 //
8 //
9 /////////////////////////////////////////
10
11
12 // Client class - Parsing
13 // Created 1/7/02
14 // Jason Boettcher
15
16
17
18 #include <cassert>
19 #include <time.h>
20
21 #include "LieroX.h"
22 #include "Cache.h"
23 #include "CClient.h"
24 #include "CServer.h"
25 #include "console.h"
26 #include "GfxPrimitives.h"
27 #include "FindFile.h"
28 #include "StringUtils.h"
29 #include "Protocol.h"
30 #include "CWorm.h"
31 #include "Error.h"
32 #include "Entity.h"
33 #include "MathLib.h"
34 #include "EndianSwap.h"
35 #include "Physics.h"
36 #include "AuxLib.h"
37 #include "NotifyUser.h"
38 #include "Timer.h"
39 #include "XMLutils.h"
40 #include "CClientNetEngine.h"
41 #include "CChannel.h"
42 #include "DeprecatedGUI/Menu.h"
43 #include "DeprecatedGUI/CBrowser.h"
44 #include "ProfileSystem.h"
45 #include "IRC.h"
46 #include "CWormHuman.h"
47 #include "CWormBot.h"
48 #include "Debug.h"
49 #include "CGameMode.h"
50 #include "ConversationLogger.h"
51 #include "FlagInfo.h"
52 #include "CMap.h"
53 #include "Utils.h"
54
55
56 #ifdef _MSC_VER
57 #undef min
58 #undef max
59 #endif
60
61 // declare them only locally here as nobody really should use them explicitly
62 std::string Utf8String(const std::string &OldLxString);
63
64 static Logger DebugNetLogger(0,2,1000, "DbgNet: ");
65 static bool Debug_Net_ClPrintFullStream = false;
66 static bool Debug_Net_ClConnLess = false;
67 static bool Debug_Net_ClConn = false;
68
69 static bool bRegisteredNetDebugVars = CScriptableVars::RegisterVars("Debug.Net")
70 ( DebugNetLogger.minCoutVerb, "LoggerCoutVerb" )
71 ( DebugNetLogger.minIngameConVerb, "LoggerIngameVerb" )
72 ( Debug_Net_ClPrintFullStream, "ClPrintFullStream" )
73 ( Debug_Net_ClConnLess, "ClConnLess" )
74 ( Debug_Net_ClConn, "ClConn" );
75
76
77 ///////////////////
78 // Parse a connectionless packet
ParseConnectionlessPacket(CBytestream * bs)79 void CClientNetEngine::ParseConnectionlessPacket(CBytestream *bs)
80 {
81 size_t s1 = bs->GetPos();
82 std::string cmd = bs->readString(128);
83 size_t s2 = bs->GetPos();
84
85 if(Debug_Net_ClConnLess)
86 DebugNetLogger << "ConnectionLessPacket: { '" << cmd << "', restlen: " << bs->GetRestLen() << endl;
87
88 if(cmd == "lx::challenge")
89 ParseChallenge(bs);
90
91 else if(cmd == "lx::goodconnection")
92 ParseConnected(bs);
93
94 else if(cmd == "lx::pong")
95 ParsePong();
96
97 else if(cmd == "lx::timeis")
98 ParseTimeIs(bs);
99
100 // A Bad Connection
101 else if(cmd == "lx::badconnect") {
102 // If we are already connected, ignore this
103 if (client->iNetStatus == NET_CONNECTED || client->iNetStatus == NET_PLAYING) {
104 notes << "CClientNetEngine::ParseConnectionlessPacket: already connected, ignoring" << endl;
105 } else {
106 client->iNetStatus = NET_DISCONNECTED;
107 client->bBadConnection = true;
108 client->strBadConnectMsg = "Server message: " + Utf8String(bs->readString());
109 }
110 }
111
112 // Host has OpenLX Beta 3+
113 else if(cmd.find("lx::openbeta") == 0) {
114 if (cmd.size() > 12) {
115 int betaver = MAX(0, atoi(cmd.substr(12)));
116 Version version = OLXBetaVersion(betaver);
117 if(client->cServerVersion < version) {
118 client->cServerVersion = version;
119 notes << "host is at least using OLX Beta3" << endl;
120 }
121 }
122 }
123
124 // this is only important for Beta4 as it's the main method there to inform about the version
125 // we send the version string now together with the challenge packet
126 else if(cmd == "lx::version") {
127 std::string versionStr = bs->readString();
128 Version version(versionStr);
129 if(version > client->cServerVersion) { // only update if this version is really newer than what we know already
130 client->cServerVersion = version;
131 notes << "Host is using " << versionStr << endl;
132 }
133 }
134
135 else if (cmd == "lx:strafingAllowed")
136 client->bHostAllowsStrafing = true;
137
138 else if(cmd == "lx::traverse")
139 ParseTraverse(bs);
140
141 else if(cmd == "lx::connect_here")
142 ParseConnectHere(bs);
143
144 // ignore this (we get it very often if we have hosted once - it's a server package)
145 else if(cmd == "lx::ping") {}
146 // ignore this also, it's sent by old servers
147 else if(cmd == "lx:mouseAllowed") {}
148
149 // Unknown
150 else {
151 warnings << "CClientNetEngine::ParseConnectionlessPacket: unknown command \"" << cmd << "\"" << endl;
152 bs->SkipAll(); // Safety: ignore any data behind this unknown packet
153 }
154
155 if(Debug_Net_ClConnLess) {
156 if(Debug_Net_ClPrintFullStream)
157 bs->Dump(PrintOnLogger(DebugNetLogger), Set(s1, s2, bs->GetPos()));
158 else
159 bs->Dump(PrintOnLogger(DebugNetLogger), Set(s2 - s1), s1, bs->GetPos() - s1);
160 DebugNetLogger << "}" << endl;
161 }
162 }
163
164
165 ///////////////////
166 // Parse a challenge packet
ParseChallenge(CBytestream * bs)167 void CClientNetEngine::ParseChallenge(CBytestream *bs)
168 {
169 // If we are already connected, ignore this
170 if (client->iNetStatus == NET_CONNECTED || client->iNetStatus == NET_PLAYING) {
171 notes << "CClientNetEngine::ParseChallenge: already connected, ignoring" << endl;
172 return;
173 }
174
175 #ifdef DEBUG
176 if (client->bConnectingBehindNat)
177 notes << "Got a challenge from the server." << endl;
178 #endif
179
180 CBytestream bytestr;
181 bytestr.Clear();
182 client->iChallenge = bs->readInt(4);
183 if( ! bs->isPosAtEnd() ) {
184 client->setServerVersion( bs->readString(128) );
185 notes << "CClient: got challenge response from " << client->getServerVersion().asString() << " server" << endl;
186 } else
187 notes << "CClient: got challenge response from old (<= OLX beta3) server" << endl;
188
189 // TODO: move this out here
190 // Tell the server we are connecting, and give the server our details
191 bytestr.writeInt(-1,4);
192 bytestr.writeString("lx::connect");
193 bytestr.writeInt(PROTOCOL_VERSION,1);
194 bytestr.writeInt(client->iChallenge,4);
195 bytestr.writeInt(client->iNetSpeed,1);
196 bytestr.writeInt(client->iNumWorms, 1);
197
198 // Send my worms info
199 //
200 // __MUST__ match the layout in CWorm::writeInfo() !!!
201 //
202
203 for(uint i=0;i<client->iNumWorms;i++) {
204 // TODO: move this out here
205 bytestr.writeString(RemoveSpecialChars(client->tProfiles[i]->sName));
206 bytestr.writeInt(client->tProfiles[i]->iType,1);
207 bytestr.writeInt(client->tProfiles[i]->iTeam,1);
208 bytestr.writeString(client->tProfiles[i]->cSkin.getFileName());
209 bytestr.writeInt(client->tProfiles[i]->R,1);
210 bytestr.writeInt(client->tProfiles[i]->G,1);
211 bytestr.writeInt(client->tProfiles[i]->B,1);
212 }
213
214 client->tSocket->reapplyRemoteAddress();
215 bytestr.Send(client->tSocket.get());
216
217 client->setNetEngineFromServerVersion(); // *this may be deleted here! so it's the last command
218 }
219
220
221 ///////////////////
222 // Parse a connected packet
ParseConnected(CBytestream * bs)223 void CClientNetEngine::ParseConnected(CBytestream *bs)
224 {
225 if(client->reconnectingAmount >= 2) {
226 // This is needed because only the last connect-package will have the correct worm-amount
227 client->reconnectingAmount--;
228 notes << "ParseConnected: We are waiting for " << client->reconnectingAmount << " other reconnect, ignoring this one now" << endl;
229 bs->SkipAll(); // the next connect should be in a seperate UDP package
230 return;
231 }
232
233 bool isReconnect = client->reconnectingAmount > 0;
234 if(isReconnect) client->reconnectingAmount--;
235
236 NetworkAddr addr;
237
238 if(!isReconnect) {
239 if(tLX->iGameType == GME_JOIN) {
240 // If already connected, ignore this
241 if (client->iNetStatus == NET_CONNECTED) {
242 notes << "CClientNetEngine::ParseConnected: already connected but server received our connect-package twice and we could have other worm-ids" << endl;
243 }
244 else if(client->iNetStatus == NET_PLAYING) {
245 warnings << "CClientNetEngine::ParseConnected: currently playing; ";
246 warnings << "it's too risky to proceed a reconnection, so we ignore this" << endl;
247 return;
248 }
249 }
250 else { // hosting and no official reconnect
251 if (client->iNetStatus != NET_CONNECTING) {
252 warnings << "ParseConnected: local client is not supposed to reconnect right now";
253 warnings << " (state: " << NetStateString((ClientNetState)client->iNetStatus) << ")" << endl;
254 bs->Dump();
255 return;
256 }
257 }
258 } else
259 notes << "ParseConnected: reconnected" << endl;
260
261 // Setup the client
262 client->iNetStatus = NET_CONNECTED;
263
264 // small cleanup (needed for example for reconnecting)
265 /*for(int i = 0; i < MAX_WORMS; i++) {
266 client->cRemoteWorms[i].setUsed(false);
267 client->cRemoteWorms[i].setAlive(false);
268 client->cRemoteWorms[i].setKills(0);
269 client->cRemoteWorms[i].setDeaths(0);
270 client->cRemoteWorms[i].setTeamkills(0);
271 client->cRemoteWorms[i].setLives(WRM_OUT);
272 client->cRemoteWorms[i].setProfile(NULL);
273 client->cRemoteWorms[i].setType(PRF_HUMAN);
274 client->cRemoteWorms[i].setLocal(false);
275 client->cRemoteWorms[i].setTagIT(false);
276 client->cRemoteWorms[i].setTagTime(TimeDiff(0));
277 client->cRemoteWorms[i].setClientVersion(Version());
278 }*/
279
280 if( client->iNumWorms < 0 ) {
281 errors << "client->iNumWorms = " << client->iNumWorms << " is less than zero" << endl;
282 client->Disconnect();
283 return;
284 }
285
286
287
288 // Get the id's
289 int id=0;
290 for(ushort i=0;i<client->iNumWorms;i++) {
291 id = bs->readInt(1);
292 if (id < 0 || id >= MAX_WORMS) {
293 warnings << "ParseConnected: parsed invalid id " << id << endl;
294 notes << "Something is screwed up -> reconnecting" << endl;
295 client->Reconnect();
296 return;
297 }
298 for(int j = 0; j < i; j++) {
299 if(client->cLocalWorms[j]->getID() == id) {
300 warnings << "ParseConnected: local worm nr " << j << " already has the id " << id;
301 warnings << ", cannot assign it twice to local worm nr " << i << endl;
302 notes << "Something is screwed up -> reconnecting" << endl;
303 client->Reconnect();
304 return;
305 }
306 }
307 if(client->cRemoteWorms[id].isUsed() && !client->cRemoteWorms[id].getLocal()) {
308 warnings << "ParseConnected: worm " << id << " is a remote worm";
309 warnings << ", cannot be used as our local worm " << i << endl;
310 notes << "Something is screwed up -> reconnecting" << endl;
311 client->Reconnect();
312 return;
313 }
314 client->cLocalWorms[i] = &client->cRemoteWorms[id];
315 if(!client->cLocalWorms[i]->isUsed()) {
316 client->cLocalWorms[i]->setUsed(true);
317 client->cLocalWorms[i]->setClient(NULL); // Local worms won't get CServerConnection owner
318 client->cLocalWorms[i]->setGameScript(client->cGameScript.get()); // TODO: why was this commented out?
319 //client->cLocalWorms[i]->setLoadingTime(client->fLoadingTime); // TODO: why is this commented out?
320 client->cLocalWorms[i]->setProfile(client->tProfiles[i]);
321 if(client->tProfiles[i]) {
322 client->cLocalWorms[i]->setTeam(client->tProfiles[i]->iTeam);
323 client->cLocalWorms[i]->setType(WormType::fromInt(client->tProfiles[i]->iType));
324 } else
325 warnings << "ParseConnected: profile " << i << " for worm " << id << " is not set" << endl;
326 client->cLocalWorms[i]->setLocal(true);
327 client->cLocalWorms[i]->setClientVersion(client->getClientVersion());
328 }
329 if(!client->cLocalWorms[i]->getLocal()) {
330 warnings << "ParseConnected: already used local worm " << id << " was not set to local" << endl;
331 client->cLocalWorms[i]->setLocal(true);
332 }
333 }
334
335 // TODO: why do we setup the viewports only if we have at least one worm?
336 if(!isReconnect && !bDedicated && client->iNumWorms > 0) {
337 // Setup the viewports
338 client->SetupViewports();
339
340 client->SetupGameInputs();
341 }
342
343 // Create my channel
344 addr = client->tSocket->remoteAddress();
345
346 if( isReconnect && !client->cNetChan )
347 warnings << "we are reconnecting but we don't have a working communication channel yet" << endl;
348 if( !isReconnect && client->cNetChan ) {
349 warnings << "ParseConnected: we still have an old channel" << endl;
350 delete client->cNetChan;
351 client->cNetChan = NULL;
352 }
353
354 if( client->cNetChan == NULL ) {
355 if( ! client->createChannel( std::min( client->getServerVersion(), GetGameVersion() ) ) )
356 {
357 client->bServerError = true;
358 client->strServerErrorMsg = "Your client is incompatible to this server";
359 return;
360 }
361 client->cNetChan->Create(addr, client->tSocket);
362 }
363
364 if( client->getServerVersion().isBanned() )
365 {
366 // There is no Beta9 release, everything that reports itself as Beta9
367 // is pre-release and have incompatible net protocol
368
369 notes << "Banned server version " << client->getServerVersion().asString() << " detected - it is not supported anymore" << endl;
370
371 client->bServerError = true;
372 client->strServerErrorMsg = "This server uses " + client->getServerVersion().asString() + " which is not supported anymore";
373
374 return;
375 }
376
377 DeprecatedGUI::bJoin_Update = true;
378 DeprecatedGUI::bHost_Update = true;
379
380 if(!isReconnect) {
381 // We always allow it on servers < 0.57 beta5 because 0.57b5 is the first version
382 // which has a setting for allowing/disallowing it.
383 client->bHostAllowsStrafing = client->getServerVersion() < OLXBetaVersion(0,57,5);
384 }
385
386 // Log the connecting
387 if (!isReconnect && tLXOptions->bLogConvos && convoLogger)
388 convoLogger->enterServer(client->getServerName());
389
390 if( !isReconnect && GetGlobalIRC() )
391 GetGlobalIRC()->setAwayMessage("Server: " + client->getServerName());
392 }
393
394 //////////////////
395 // Parse the server's ping reply
ParsePong()396 void CClientNetEngine::ParsePong()
397 {
398 if (client->fMyPingSent.seconds() > 0) {
399 int png = (int) ((tLX->currentTime-client->fMyPingSent).milliseconds());
400
401 // Make the ping slighter
402 if (png - client->iMyPing > 5 && client->iMyPing && png)
403 png = (png + client->iMyPing + client->iMyPing)/3;
404 if (client->iMyPing - png > 5 && client->iMyPing && png)
405 png = (png + png + client->iMyPing)/3;
406
407 client->iMyPing = png;
408 }
409 }
410
411 //////////////////
412 // Parse the server's servertime request reply
ParseTimeIs(CBytestream * bs)413 void CClientNetEngine::ParseTimeIs(CBytestream* bs)
414 {
415 float serverTime = bs->readFloat();
416 if( serverTime > 0.0 )
417 {
418 TimeDiff time = TimeDiff(serverTime);
419 if (time > client->fServertime)
420 client->fServertime = time;
421 }
422
423 // This is the response of the lx::time packet, which is sent instead of the lx::ping.
424 // Therefore we should also handle this as a normal response to a ping.
425 ParsePong();
426 }
427
428
429 //////////////////
430 // Parse a NAT traverse packet
ParseTraverse(CBytestream * bs)431 void CClientNetEngine::ParseTraverse(CBytestream *bs)
432 {
433 client->iNatTraverseState = NAT_SEND_CHALLENGE;
434 client->iNatTryPort = 0;
435 std::string addr = bs->readString();
436 if( addr.rfind(':') == std::string::npos )
437 return;
438 StringToNetAddr(addr, client->cServerAddr); // HINT: this changes the address so the lx::challenge in CClientNetEngine::ConnectingBehindNat is sent to the real server
439 int port = atoi( addr.substr( addr.rfind(':') + 1 ) );
440 SetNetAddrPort(client->cServerAddr, port);
441 NetAddrToString( client->cServerAddr, addr );
442
443 // HINT: the connecting process now continues by sending a challenge in CClientNetEngine::ConnectingBehindNAT()
444
445 notes << "Client got traverse from " << addr << " port " << port << endl;
446 }
447
448 /////////////////////
449 // Parse a connect-here packet (when a public-ip client connects to a symmetric-nat server)
ParseConnectHere(CBytestream * bs)450 void CClientNetEngine::ParseConnectHere(CBytestream *bs)
451 {
452 client->iNatTraverseState = NAT_SEND_CHALLENGE;
453 client->iNatTryPort = 0;
454
455 NetworkAddr addr = client->tSocket->remoteAddress();
456 std::string a1, a2;
457 NetAddrToString( client->cServerAddr, a1 );
458 NetAddrToString( addr, a2 );
459 notes << "Client got connect_here from " << a1 << " to " << a2 << (a1 != a2 ? "- server behind symmetric NAT" : "") << endl;
460
461 client->cServerAddr = client->tSocket->remoteAddress();
462 CBytestream bs1;
463 bs1.writeInt(-1,4);
464 bs1.writeString("lx::ping"); // So NAT/firewall will understand we really want to connect there
465 bs1.Send(client->tSocket.get());
466 bs1.Send(client->tSocket.get());
467 bs1.Send(client->tSocket.get());
468 }
469
470
471 /*
472 =======================================
473 Connected Packets
474 =======================================
475 */
476
477
478 ///////////////////
479 // Parse a packet
ParsePacket(CBytestream * bs)480 bool CClientNetEngine::ParsePacket(CBytestream *bs)
481 {
482 if(bs->isPosAtEnd())
483 return false;
484
485 size_t s = bs->GetPos();
486 uchar cmd = bs->readInt(1);
487
488 if(Debug_Net_ClConn)
489 DebugNetLogger << "Packet: { '" << (int)cmd << "', restlen: " << bs->GetRestLen() << endl;
490
491 bool ret = true;
492
493 switch(cmd) {
494
495 // Prepare the game
496 case S2C_PREPAREGAME:
497 ret &= ParsePrepareGame(bs);
498 // HINT: Don't disconnect, we often get here after a corrupted packet because S2C_PREPAREGAME=0 which is a very common value
499 break;
500
501 // Start the game
502 case S2C_STARTGAME:
503 ParseStartGame(bs);
504 break;
505
506 // Spawn a worm
507 case S2C_SPAWNWORM:
508 ParseSpawnWorm(bs);
509 break;
510
511 // Worm info
512 case S2C_WORMINFO:
513 ParseWormInfo(bs);
514 break;
515
516 // Worm weapon info
517 case S2C_WORMWEAPONINFO:
518 ParseWormWeaponInfo(bs);
519 break;
520
521 // Text
522 case S2C_TEXT:
523 ParseText(bs);
524 break;
525
526 // Chat command completion solution
527 case S2C_CHATCMDCOMPLSOL:
528 ParseChatCommandCompletionSolution(bs);
529 break;
530
531 // chat command completion list
532 case S2C_CHATCMDCOMPLLST:
533 ParseChatCommandCompletionList(bs);
534 break;
535
536 // AFK message
537 case S2C_AFK:
538 ParseAFK(bs);
539 break;
540
541 // Worm score
542 case S2C_SCOREUPDATE:
543 ParseScoreUpdate(bs);
544 break;
545
546 // Game over
547 case S2C_GAMEOVER:
548 ParseGameOver(bs);
549 break;
550
551 // Spawn bonus
552 case S2C_SPAWNBONUS:
553 ParseSpawnBonus(bs);
554 break;
555
556 // Tag update
557 case S2C_TAGUPDATE:
558 ParseTagUpdate(bs);
559 break;
560
561 // Some worms are ready to play (not lobby)
562 case S2C_CLREADY:
563 ParseCLReady(bs);
564 break;
565
566 // Update the lobby state of some worms
567 case S2C_UPDATELOBBY:
568 ParseUpdateLobby(bs);
569 break;
570
571 // Client has left
572 case S2C_WORMSOUT:
573 ParseWormsOut(bs);
574 break;
575
576 // Worm state update
577 case S2C_UPDATEWORMS:
578 ParseUpdateWorms(bs);
579 break;
580
581 // Game lobby update
582 case S2C_UPDATELOBBYGAME:
583 ParseUpdateLobbyGame(bs);
584 break;
585
586 // Worm down packet
587 case S2C_WORMDOWN:
588 ParseWormDown(bs);
589 break;
590
591 // Server has left
592 case S2C_LEAVING:
593 ParseServerLeaving(bs);
594 break;
595
596 // Single shot
597 case S2C_SINGLESHOOT:
598 ParseSingleShot(bs);
599 break;
600
601 // Multiple shots
602 case S2C_MULTISHOOT:
603 ParseMultiShot(bs);
604 break;
605
606 // Stats
607 case S2C_UPDATESTATS:
608 ParseUpdateStats(bs);
609 break;
610
611 // Destroy bonus
612 case S2C_DESTROYBONUS:
613 ParseDestroyBonus(bs);
614 break;
615
616 // Goto lobby
617 case S2C_GOTOLOBBY:
618 ParseGotoLobby(bs);
619 break;
620
621 // I have been dropped
622 case S2C_DROPPED:
623 ParseDropped(bs);
624 break;
625
626 case S2C_SENDFILE:
627 ParseSendFile(bs);
628 break;
629
630 case S2C_REPORTDAMAGE:
631 ParseReportDamage(bs);
632 break;
633
634 case S2C_HIDEWORM:
635 ParseHideWorm(bs);
636 break;
637
638 case S2C_TEAMSCOREUPDATE:
639 ParseTeamScoreUpdate(bs);
640 break;
641
642 case S2C_FLAGINFO:
643 ParseFlagInfo(bs);
644 break;
645
646 case S2C_SETWORMPROPS:
647 ParseWormProps(bs);
648 break;
649
650 case S2C_SELECTWEAPONS:
651 ParseSelectWeapons(bs);
652 break;
653
654 default:
655 #if !defined(FUZZY_ERROR_TESTING_S2C)
656 warnings << "cl: Unknown packet " << (unsigned)cmd << endl;
657 #ifdef DEBUG
658 // only if debugging sys is disabled because we would print it anyway then
659 if(!Debug_Net_ClConn) {
660 notes << "Bytestream dump:" << endl;
661 // Note: independent from net debugging system because I want to see this in users log even if he didn't enabled the debugging system
662 bs->Dump();
663 notes << "Done dumping bytestream" << endl;
664 }
665 #endif //DEBUG
666
667 #endif //FUZZY_ERROR_TESTING
668 ret = false;
669 }
670
671 if(Debug_Net_ClConn) {
672 if(Debug_Net_ClPrintFullStream)
673 bs->Dump(PrintOnLogger(DebugNetLogger), Set(s, bs->GetPos()));
674 else
675 bs->Dump(PrintOnLogger(DebugNetLogger), std::set<size_t>(), s, bs->GetPos() - s);
676 DebugNetLogger << "}" << endl;
677 }
678
679 return ret;
680 }
681
682
683 ///////////////////
684 // Parse a prepare game packet
ParsePrepareGame(CBytestream * bs)685 bool CClientNetEngine::ParsePrepareGame(CBytestream *bs)
686 {
687 notes << "Client: Got ParsePrepareGame" << endl;
688
689 bool isReconnect = false;
690
691 if(Warning_QuitEngineFlagSet("CClientNetEngine::ParsePrepareGame: ")) {
692 warnings << "some previous action tried to quit the GameLoop; we are ignoring this now" << endl;
693 ResetQuitEngineFlag();
694 }
695
696 // We've already got this packet
697 if (client->bGameReady && !client->bGameOver) {
698 ((tLX->iGameType == GME_JOIN) ? warnings : notes)
699 << "CClientNetEngine::ParsePrepareGame: we already got this" << endl;
700
701 // HINT: we ignore it here for the safety because S2C_PREPAREGAME is 0 and it is
702 // a very common value in corrupted streams
703 // TODO: skip to the right position, the packet could be valid
704 if( tLX->iGameType == GME_JOIN )
705 return false;
706 isReconnect = true;
707 }
708
709 // If we're playing, the game has to be ready
710 if (client->iNetStatus == NET_PLAYING && !client->bGameOver) {
711 ((tLX->iGameType == GME_JOIN) ? warnings : notes)
712 << "CClientNetEngine::ParsePrepareGame: playing, already had to get this" << endl;
713 client->bGameReady = true;
714
715 // The same comment here as above.
716 // TODO: skip to the right position, the packet could be valid
717 if( tLX->iGameType == GME_JOIN )
718 return false;
719 isReconnect = true;
720 }
721
722 if(!isReconnect)
723 NotifyUserOnEvent();
724
725 if(!isReconnect) {
726 client->bGameRunning = false; // wait for ParseStartGame
727 client->bGameOver = false;
728 }
729
730 // remove from notifier; we don't want events anymore, we have a fixed FPS rate ingame
731 if(!isReconnect)
732 client->tSocket->setWithEvents(false);
733
734 client->bGameReady = true;
735 client->flagInfo()->reset();
736 for(int i = 0; i < 4; ++i) {
737 client->iTeamScores[i] = 0;
738 }
739
740 int random = bs->readInt(1);
741 std::string sMapFilename;
742 if(!random)
743 sMapFilename = bs->readString();
744
745 // Other game details
746 client->tGameInfo.iGeneralGameType = bs->readInt(1);
747 if( client->tGameInfo.iGeneralGameType > GMT_MAX || client->tGameInfo.iGeneralGameType < 0 )
748 client->tGameInfo.iGeneralGameType = GMT_NORMAL;
749 client->tGameInfo.gameMode = NULL;
750 client->tGameInfo.sGameMode = guessGeneralGameTypeName(client->tGameInfo.iGeneralGameType);
751
752 client->tGameInfo.iLives = bs->readInt16();
753 client->tGameInfo.iKillLimit = bs->readInt16();
754 client->tGameInfo.fTimeLimit = (float)bs->readInt16();
755 client->tGameInfo.iLoadingTime = bs->readInt16();
756 client->tGameInfo.bBonusesOn = bs->readBool();
757 client->tGameInfo.bShowBonusName = bs->readBool();
758 client->fServertime = 0;
759
760 if(client->getGeneralGameType() == GMT_TIME)
761 client->tGameInfo.iTagLimit = bs->readInt16();
762
763 // Load the gamescript
764 client->tGameInfo.sModName = bs->readString();
765
766 // Bad packet
767 if (client->tGameInfo.sModName == "") {
768 hints << "CClientNetEngine::ParsePrepareGame: invalid mod name (none)" << endl;
769 client->bGameReady = false;
770 return false;
771 }
772
773 // Clear any previous instances of the map
774 if(tLX->iGameType == GME_JOIN) {
775 if(client->cMap) {
776 client->cMap->Shutdown();
777 delete client->cMap;
778 client->cMap = NULL;
779 cServer->resetMap();
780 }
781 }
782
783 client->m_flagInfo->reset();
784
785 // HINT: gamescript is shut down by the cache
786
787 //bs->Dump();
788
789
790 if(tLX->iGameType == GME_JOIN) {
791 client->cMap = new CMap;
792 if(client->cMap == NULL) {
793
794 // Disconnect
795 client->Disconnect();
796
797 DeprecatedGUI::Menu_MessageBox("Out of memory", "Out of memory when allocating the map.", DeprecatedGUI::LMB_OK);
798
799 client->bGameReady = false;
800
801 errors << "CClientNetEngine::ParsePrepareGame: out of memory when allocating map" << endl;
802
803 return false;
804 }
805 }
806
807 if(random)
808 {
809 // TODO: why don't we support that anymore? and since when?
810 // Since I've moved all dynamically allocated datas to smartpointers, don't remember why I've removed that
811 hints << "CClientNetEngine::ParsePrepareGame: random map requested, and we do not support these anymore" << endl;
812 client->bGameReady = false;
813 return false;
814 } else
815 {
816 // Load the map from a file
817
818 // Invalid packet
819 if (sMapFilename == "") {
820 hints << "CClientNetEngine::ParsePrepareGame: bad map name (none)" << endl;
821 client->bGameReady = false;
822 return false;
823 }
824
825 if(tLX->iGameType == GME_JOIN) {
826
827 // check if we have level
828 if(CMap::GetLevelName(GetBaseFilename(sMapFilename)) == "") {
829 client->DownloadMap(GetBaseFilename(sMapFilename)); // Download the map
830 // we have bDownloadingMap = true when this was successfull
831 }
832
833 // If we are downloading a map, wait until it finishes
834 if (!client->bDownloadingMap) {
835 client->bWaitingForMap = false;
836
837 client->cMap->SetMinimapDimensions(client->tInterfaceSettings.MiniMapW, client->tInterfaceSettings.MiniMapH);
838 if(!client->cMap->Load(sMapFilename)) {
839 notes << "CClientNetEngine::ParsePrepareGame: could not load map " << sMapFilename << endl;
840
841 // Show a cannot load level error message
842 // If this is a host/local game, something is pretty wrong but if we display the message, things could
843 // go even worse
844 FillSurface(DeprecatedGUI::tMenu->bmpBuffer.get(), tLX->clBlack);
845
846 DeprecatedGUI::Menu_MessageBox(
847 "Loading Error",
848 std::string("Could not load the level '") + sMapFilename + "'.\n" + LxGetLastError(),
849 DeprecatedGUI::LMB_OK);
850 client->bClientError = true;
851
852 // Go back to the menu
853 QuittoMenu();
854
855 client->bGameReady = false;
856
857 return false;
858 }
859 } else {
860 client->bWaitingForMap = true;
861 notes << "Client: we got PrepareGame but we have to wait first until the download of the map finishes" << endl;
862 }
863 } else { // GME_HOST
864 assert(cServer);
865
866 // Grab the server's copy of the map
867 client->cMap = cServer->getMap();
868 if (!client->cMap) { // Bad packet
869 errors << "our server has map unset" << endl;
870 client->bGameReady = false;
871 return false;
872 }
873
874 client->cMap->SetMinimapDimensions(client->tInterfaceSettings.MiniMapW, client->tInterfaceSettings.MiniMapH);
875 client->bMapGrabbed = true;
876
877 if(client->cMap->getFilename() != sMapFilename) {
878 errors << "Client (in host mode): we got PrepareGame for map " << sMapFilename << " but server has loaded map " << client->cMap->getFilename() << endl;
879 client->bGameReady = false;
880 return false;
881 }
882 }
883
884 }
885
886 if(!isReconnect)
887 PhysicsEngine::Get()->initGame();
888
889 if(!isReconnect) {
890 if(tLX->iGameType == GME_JOIN) {
891 client->cGameScript = cCache.GetMod( client->tGameInfo.sModName );
892 if( client->cGameScript.get() == NULL )
893 {
894 client->cGameScript = new CGameScript();
895
896 if (client->bDownloadingMod)
897 client->bWaitingForMod = true;
898 else {
899 client->bWaitingForMod = false;
900
901 int result = client->cGameScript.get()->Load(client->tGameInfo.sModName);
902 cCache.SaveMod( client->tGameInfo.sModName, client->cGameScript );
903 if(result != GSE_OK) {
904
905 // Show any error messages
906 if (tLX->iGameType == GME_JOIN) {
907 FillSurface(DeprecatedGUI::tMenu->bmpBuffer.get(), tLX->clBlack);
908 std::string err("Error load game mod: ");
909 err += client->tGameInfo.sModName + "\r\nError code: " + itoa(result);
910 DeprecatedGUI::Menu_MessageBox("Loading Error", err, DeprecatedGUI::LMB_OK);
911 client->bClientError = true;
912
913 // Go back to the menu
914 GotoNetMenu();
915 } else {
916 errors << "ParsePrepareGame: load mod error for a local game!" << endl;
917 }
918 client->bGameReady = false;
919
920 errors << "CClientNetEngine::ParsePrepareGame: error loading mod " << client->tGameInfo.sModName << endl;
921 return false;
922 }
923 }
924 }
925 }
926 else { // hosting
927 client->cGameScript = cServer->getGameScript();
928 if(client->cGameScript.get() == NULL) {
929 errors << "ParsePrepareGame: server has mod unset" << endl;
930 client->bGameReady = false;
931
932 errors << "CClientNetEngine::ParsePrepareGame: error loading mod " << client->tGameInfo.sModName << endl;
933 return false;
934 }
935 }
936 }
937
938 // Read the weapon restrictions
939 client->cWeaponRestrictions.updateList(client->cGameScript.get());
940 client->cWeaponRestrictions.readList(bs);
941
942 client->projPosMap.clear();
943 client->projPosMap.resize(CClient::MapPosIndex( VectorD2<int>(client->cMap->GetWidth(), client->cMap->GetHeight())).index(client->cMap) );
944 client->cProjectiles.clear();
945
946 client->tGameInfo.features[FT_GameSpeed] = 1.0f;
947 client->bServerChoosesWeapons = false;
948
949 // TODO: Load any other stuff
950 client->bGameReady = true;
951
952 if(!isReconnect) {
953 // Reset the scoreboard here so it doesn't show kills & lives when waiting for players
954 client->InitializeIngameScore(true);
955
956 // Copy the chat text from lobby to ingame chatbox
957 if( tLX->iGameType == GME_HOST )
958 client->sChat_Text = DeprecatedGUI::Menu_Net_HostLobbyGetText();
959 else if( tLX->iGameType == GME_JOIN )
960 client->sChat_Text = DeprecatedGUI::Menu_Net_JoinLobbyGetText();
961
962 if (!client->sChat_Text.empty()) {
963 client->bChat_Typing = true;
964 client->bChat_CursorVisible = true;
965 client->iChat_Pos = client->sChat_Text.size();
966 SendAFK( client->cLocalWorms[0]->getID(), AFK_TYPING_CHAT );
967 }
968
969 if(!bDedicated) {
970 // TODO: move that out, that does not belong here
971 // Load the chat
972 DeprecatedGUI::CBrowser *lv = client->cChatList;
973 if (lv) {
974 lv->setBorderSize(0);
975 lv->InitializeChatBox();
976 lines_iterator it = client->cChatbox.At((int)client->cChatbox.getNumLines()-256); // If there's more than 256 messages, we start not from beginning but from end()-256
977 //int id = (lv->getLastItem() && lv->getItems()) ? lv->getLastItem()->iIndex + 1 : 0;
978
979 for (; it != client->cChatbox.End(); it++) {
980
981 // Add only chat text (PM and Team PM messages too)
982 if (it->iTextType == TXT_CHAT || it->iTextType == TXT_PRIVATE || it->iTextType == TXT_TEAMPM ) {
983 lv->AddChatBoxLine(it->strLine, it->iColour, it->iTextType);
984 }
985 }
986 }
987 }
988 }
989
990 CWorm *w = client->cRemoteWorms;
991 int num_worms = 0;
992 ushort i;
993 for(i=0;i<MAX_WORMS;i++,w++) {
994 if(w->isUsed()) {
995 num_worms++;
996
997 // (If this is a local game?), we need to reload the worm graphics
998 // We do this again because we've only just found out what type of game it is
999 // Team games require changing worm colours to match the team colour
1000 // Inefficient, but i'm not going to redesign stuff for a simple gametype
1001 w->ChangeGraphics(client->getGeneralGameType());
1002
1003 if(isReconnect && w->isPrepared())
1004 continue;
1005
1006 notes << "Client: preparing worm " << i << ":" << w->getName() << " for battle" << endl;
1007
1008 // Also set some game details
1009 w->setLives(client->tGameInfo.iLives);
1010 w->setAlive(false);
1011 w->setKills(0);
1012 w->setDeaths(0);
1013 w->setTeamkills(0);
1014 w->setDamage(0);
1015 w->setHealth(100);
1016 w->setGameScript(client->cGameScript.get());
1017 w->setWpnRest(&client->cWeaponRestrictions);
1018 w->setLoadingTime(client->tGameInfo.iLoadingTime/100.0f);
1019 w->setWeaponsReady(false);
1020
1021 // Prepare for battle!
1022 w->Prepare(false);
1023 }
1024 }
1025
1026 // The worms are first prepared here in this function and thus the input handlers where not set before.
1027 // We have to set the control keys now.
1028 client->SetupGameInputs();
1029
1030
1031 // Initialize the worms weapon selection menu & other stuff
1032 if (!client->bWaitingForMod)
1033 for(i=0;i<client->iNumWorms;i++) {
1034 // we already prepared all the worms (cRemoteWorms) above
1035 if(!client->cLocalWorms[i]->getWeaponsReady())
1036 client->cLocalWorms[i]->initWeaponSelection();
1037 }
1038
1039 // Start the game logging
1040 if(!isReconnect)
1041 client->StartLogging(num_worms);
1042
1043 if(!isReconnect)
1044 {
1045 client->SetupViewports();
1046 // Init viewports once if we're playing with bot
1047 if(client->cLocalWorms[0] && client->cLocalWorms[0]->getType() == PRF_COMPUTER)
1048 client->SetupViewports(client->cLocalWorms[0], NULL, VW_FOLLOW, VW_FOLLOW);
1049 }
1050
1051 client->UpdateScoreboard();
1052 client->bShouldRepaintInfo = true;
1053
1054 DeprecatedGUI::bJoin_Update = true;
1055
1056 if(!isReconnect) {
1057 if( GetGlobalIRC() )
1058 GetGlobalIRC()->setAwayMessage("Playing: " + client->getServerName());
1059 }
1060
1061 foreach( Feature*, f, Array(featureArray,featureArrayLen()) ) {
1062 client->tGameInfo.features[f->get()] = f->get()->unsetValue;
1063 }
1064
1065 return true;
1066 }
1067
ParsePrepareGame(CBytestream * bs)1068 bool CClientNetEngineBeta7::ParsePrepareGame(CBytestream *bs)
1069 {
1070 if( ! CClientNetEngine::ParsePrepareGame(bs) )
1071 return false;
1072
1073 // >=Beta7 is sending this
1074 client->tGameInfo.features[FT_GameSpeed] = bs->readFloat();
1075 client->bServerChoosesWeapons = bs->readBool();
1076
1077 return true;
1078 }
1079
ParseFeatureSettings(CBytestream * bs)1080 void CClientNetEngineBeta9::ParseFeatureSettings(CBytestream* bs) {
1081 // FeatureSettings() constructor initializes with default values, and we want here an unset values
1082 foreach( Feature*, f, Array(featureArray,featureArrayLen()) ) {
1083 client->tGameInfo.features[f->get()] = f->get()->unsetValue; // Clean it up
1084 }
1085 client->otherGameInfo.clear();
1086 int ftC = bs->readInt(2);
1087 for(int i = 0; i < ftC; ++i) {
1088 std::string name = bs->readString();
1089 if(name == "") {
1090 warnings << "Server gives bad features" << endl;
1091 bs->SkipAll();
1092 break;
1093 }
1094 std::string humanName = bs->readString();
1095 ScriptVar_t value; bs->readVar(value);
1096 bool olderClientsSupported = bs->readBool(); // to be understand as: if feature is unknown to us, it's save to ignore
1097 Feature* f = featureByName(name);
1098 // f != NULL -> we know about the feature -> we support it
1099 if(f && !f->serverSideOnly) {
1100 // we support the feature
1101 if(value.type == f->valueType) {
1102 client->tGameInfo.features[f] = value;
1103 } else {
1104 client->tGameInfo.features[f] = f->unsetValue; // fallback, the game is anyway somehow screwed
1105 if( !olderClientsSupported ) {
1106 errors << "server setting for feature " << name << " has wrong type " << value.type << endl;
1107 } else {
1108 warnings << "server setting for feature " << name << " has wrong type " << value.type << " but it's safe to ignore" << endl;
1109 }
1110 }
1111 } else if(f && f->serverSideOnly) {
1112 // we support it && serversideonly -> just store it for convenience
1113 client->otherGameInfo.set(name, humanName, value, FeatureCompatibleSettingList::Feature::FCSL_SUPPORTED);
1114 } else if(olderClientsSupported) {
1115 // unknown for us but we support it
1116 client->otherGameInfo.set(name, humanName, value, FeatureCompatibleSettingList::Feature::FCSL_JUSTUNKNOWN);
1117 } else {
1118 // server setting is incompatible with our client
1119 client->otherGameInfo.set(name, humanName, value, FeatureCompatibleSettingList::Feature::FCSL_INCOMPATIBLE);
1120 }
1121 }
1122 }
1123
ParsePrepareGame(CBytestream * bs)1124 bool CClientNetEngineBeta9::ParsePrepareGame(CBytestream *bs)
1125 {
1126 bool isReconnect = client->bGameReady || client->iNetStatus == NET_PLAYING;
1127
1128 if( ! CClientNetEngineBeta7::ParsePrepareGame(bs) )
1129 return false;
1130
1131 client->tGameInfo.fTimeLimit = bs->readFloat();
1132 if(client->tGameInfo.fTimeLimit < 0) client->tGameInfo.fTimeLimit = -1;
1133
1134 ParseFeatureSettings(bs);
1135
1136 client->tGameInfo.sGameMode = bs->readString();
1137 client->tGameInfo.gameMode = GameMode( client->tGameInfo.sGameMode );
1138
1139 // TODO: shouldn't this be somewhere in the clear function?
1140 if(!isReconnect)
1141 cDamageReport.clear(); // In case something left from prev game
1142
1143 return true;
1144 }
1145
1146
1147 ///////////////////
1148 // Parse a start game packet (means BeginMatch actually)
ParseStartGame(CBytestream * bs)1149 void CClientNetEngine::ParseStartGame(CBytestream *bs)
1150 {
1151 // Check that the game is ready
1152 if (!client->bGameReady) {
1153 warnings << "CClientNetEngine::ParseStartGame: cannot start the game because the game is not ready" << endl;
1154 return;
1155 }
1156
1157 if (client->iNetStatus == NET_PLAYING && !client->bGameRunning) {
1158 warnings << "Client: got start signal in runnign game with unset gamerunning flag" << endl;
1159 client->iNetStatus = NET_CONNECTED;
1160 }
1161
1162 notes << "Client: get BeginMatch signal";
1163
1164 if(client->bGameRunning) {
1165 notes << ", back to game" << endl;
1166 client->iNetStatus = NET_PLAYING;
1167 for(uint i=0;i<client->iNumWorms;i++) {
1168 if(client->cLocalWorms[i]->getWeaponsReady())
1169 client->cLocalWorms[i]->StartGame();
1170 }
1171 return;
1172 }
1173 notes << endl;
1174 client->bGameRunning = true;
1175
1176 // Already got this
1177 if (client->iNetStatus == NET_PLAYING) {
1178 notes << "CClientNetEngine::ParseStartGame: already playing - ignoring" << endl;
1179 return;
1180 }
1181
1182 client->fLastSimulationTime = tLX->currentTime;
1183 client->iNetStatus = NET_PLAYING;
1184 client->fServertime = 0;
1185
1186 // Set the local players to dead so we wait until the server spawns us
1187 for(uint i=0;i<client->iNumWorms;i++) {
1188 if(!client->cLocalWorms[i]->haveSpawnedOnce())
1189 client->cLocalWorms[i]->setAlive(false);
1190 }
1191
1192 // Re-initialize the ingame scoreboard
1193 client->InitializeIngameScore(false);
1194 client->bUpdateScore = true;
1195
1196
1197 // let our worms know that the game starts know
1198 for(uint i=0;i<client->iNumWorms;i++) {
1199 client->cLocalWorms[i]->StartGame();
1200 }
1201
1202 NotifyUserOnEvent();
1203
1204 client->bShouldRepaintInfo = true;
1205 }
1206
ParseStartGame(CBytestream * bs)1207 void CClientNetEngineBeta9::ParseStartGame(CBytestream *bs)
1208 {
1209 CClientNetEngine::ParseStartGame(bs);
1210 }
1211
1212 ///////////////////
1213 // Parse a spawn worm packet
ParseSpawnWorm(CBytestream * bs)1214 void CClientNetEngine::ParseSpawnWorm(CBytestream *bs)
1215 {
1216 int id = bs->readByte();
1217 int x = bs->readInt(2);
1218 int y = bs->readInt(2);
1219
1220 // Check
1221 if (!client->bGameReady) {
1222 warnings << "CClientNetEngine::ParseSpawnWorm: Cannot spawn worm when not playing" << endl;
1223 return;
1224 }
1225
1226 if (!client->cMap) {
1227 warnings << "CClientNetEngine::ParseSpawnWorm: cMap not set (packet ignored)" << endl;
1228 return;
1229 }
1230
1231 // Is the spawnpoint in the map?
1232 if (x > (int)client->cMap->GetWidth() || x < 0) {
1233 warnings << "CClientNetEngine::ParseSpawnWorm: X-coordinate not in map (" << x << ")" << endl;
1234 return;
1235 }
1236 if (y > (int)client->cMap->GetHeight() || y < 0) {
1237 warnings << "CClientNetEngine::ParseSpawnWorm: Y-coordinate not in map (" << y << ")" << endl;
1238 return;
1239 }
1240
1241 if(x == 0 || y == 0) {
1242 hints << "spawned in strange pos (" << x << "," << y << "), could be a bug" << endl;
1243 }
1244
1245 CVec p = CVec( (float)x, (float)y );
1246
1247 if (id < 0 || id >= MAX_PLAYERS) {
1248 warnings << "CClientNetEngine::ParseSpawnWorm: invalid ID (" << id << ")" << endl;
1249 return;
1250 }
1251
1252 client->cRemoteWorms[id].setAlive(true);
1253 client->cRemoteWorms[id].Spawn(p);
1254
1255 client->cMap->CarveHole(SPAWN_HOLESIZE,p,cClient->getGameLobby()->features[FT_InfiniteMap]);
1256
1257 // Show a spawn entity
1258 SpawnEntity(ENT_SPAWN,0,p,CVec(0,0),Color(),NULL);
1259
1260 client->UpdateScoreboard();
1261 //if (client->cRemoteWorms[id].getLocal()) // We may spectate and watch other worm, so redraw always
1262 client->bShouldRepaintInfo = true;
1263
1264
1265 bool both = client->cViewports[1].getUsed();
1266
1267 // Lock viewport back on local worm, if it was screwed when spectating after death
1268 if( client->iNumWorms > 0 && !both ) {
1269 if( client->cLocalWorms[0] == &client->cRemoteWorms[id] && client->cLocalWorms[0]->getType() == PRF_HUMAN )
1270 {
1271 client->SetupViewports(client->cLocalWorms[0], NULL, VW_FOLLOW, VW_FOLLOW);
1272 client->sSpectatorViewportMsg = "";
1273 }
1274 }
1275 if( both ) {
1276 if (client->cLocalWorms[1] && client->cLocalWorms[1]->getType() == PRF_HUMAN) {
1277 client->SetupViewports(client->cLocalWorms[0], client->cLocalWorms[1], VW_FOLLOW, VW_FOLLOW);
1278 client->sSpectatorViewportMsg = "";
1279 }
1280 else if (client->cLocalWorms[0]->getType() == PRF_HUMAN) {
1281 client->SetupViewports(client->cLocalWorms[0], client->cViewports[1].getTarget(), VW_FOLLOW,
1282 client->cViewports[1].getType());
1283 client->sSpectatorViewportMsg = "";
1284 }
1285 }
1286 }
1287
1288 ///////////////////
1289 // Parse a worm info packet
ParseWormInfo(CBytestream * bs)1290 int CClientNetEngine::ParseWormInfo(CBytestream *bs)
1291 {
1292 if(bs->GetRestLen() == 0) {
1293 warnings << "CClientNetEngine::ParseWormInfo: data screwed up" << endl;
1294 return -1;
1295 }
1296
1297 int id = bs->readInt(1);
1298
1299 // Validate the id
1300 if (id < 0 || id >= MAX_WORMS) {
1301 warnings << "CClientNetEngine::ParseWormInfo: invalid ID (" << id << ")" << endl;
1302 bs->SkipAll(); // data is screwed up
1303 return -1;
1304 }
1305
1306 // A new worm?
1307 if (!client->cRemoteWorms[id].isUsed()) {
1308 client->cRemoteWorms[id].Clear();
1309 client->cRemoteWorms[id].setLives((client->tGameInfo.iLives < 0) ? WRM_UNLIM : client->tGameInfo.iLives);
1310 client->cRemoteWorms[id].setUsed(true);
1311 client->cRemoteWorms[id].setClient(NULL); // Client-sided worms won't have CServerConnection
1312 client->cRemoteWorms[id].setLocal(false);
1313 client->cRemoteWorms[id].setGameScript(client->cGameScript.get());
1314 if (client->iNetStatus == NET_PLAYING || client->bGameReady) {
1315 client->cRemoteWorms[id].Prepare(false);
1316 }
1317 client->cRemoteWorms[id].setID(id);
1318 if( client->getServerVersion() < OLXBetaVersion(0,58,1) &&
1319 ! client->cRemoteWorms[id].getLocal() ) // Pre-Beta9 servers won't send us info on other clients version
1320 client->cRemoteWorms[id].setClientVersion(Version()); // LX56 version
1321 }
1322
1323 WormJoinInfo wormInfo;
1324 wormInfo.readInfo(bs);
1325 wormInfo.applyTo(&client->cRemoteWorms[id]);
1326
1327 // Load the worm graphics
1328 if(!client->cRemoteWorms[id].ChangeGraphics(client->getGeneralGameType())) {
1329 warnings << "CClientNetEngine::ParseWormInfo(): ChangeGraphics() failed" << endl;
1330 }
1331
1332 client->UpdateScoreboard();
1333 if (client->cRemoteWorms[id].getLocal())
1334 client->bShouldRepaintInfo = true;
1335
1336 DeprecatedGUI::bJoin_Update = true;
1337 DeprecatedGUI::bHost_Update = true;
1338 return id;
1339 }
1340
ParseWormInfo(CBytestream * bs)1341 int CClientNetEngineBeta9::ParseWormInfo(CBytestream *bs)
1342 {
1343 int id = CClientNetEngine::ParseWormInfo(bs);
1344 if( id >= 0 && id < MAX_WORMS )
1345 {
1346 Version ver(bs->readString());
1347 client->cRemoteWorms[id].setClientVersion(ver);
1348 }
1349 return id;
1350 }
1351
getWorm(CClient * cl,CBytestream * bs,const std::string & fct,bool (* skipFct)(CBytestream * bs)=NULL)1352 static CWorm* getWorm(CClient* cl, CBytestream* bs, const std::string& fct, bool (*skipFct)(CBytestream*bs) = NULL) {
1353 if(bs->GetRestLen() == 0) {
1354 warnings << fct << ": data screwed up at worm ID" << endl;
1355 return NULL;
1356 }
1357
1358 int id = bs->readByte();
1359 if(id < 0 || id >= MAX_WORMS) {
1360 warnings << fct << ": worm ID " << id << " is invalid" << endl;
1361 bs->SkipAll();
1362 return NULL;
1363 }
1364
1365 if(cl->getRemoteWorms() == NULL) {
1366 warnings << fct << ": worms are not initialised" << endl;
1367 if(skipFct) (*skipFct)(bs);
1368 return NULL;
1369 }
1370
1371 CWorm* w = &cl->getRemoteWorms()[id];
1372 if(!w->isUsed()) {
1373 warnings << fct << ": worm ID " << id << " is unused" << endl;
1374 if(skipFct) (*skipFct)(bs);
1375 return NULL;
1376 }
1377
1378 return w;
1379 }
1380
1381 ///////////////////
1382 // Parse a worm info packet
ParseWormWeaponInfo(CBytestream * bs)1383 void CClientNetEngine::ParseWormWeaponInfo(CBytestream *bs)
1384 {
1385 CWorm* w = getWorm(client, bs, "CClientNetEngine::ParseWormWeaponInfo", CWorm::skipWeapons);
1386 if(!w) return;
1387
1388 //notes << "Client:ParseWormWeaponInfo: ";
1389 w->readWeapons(bs);
1390
1391 client->UpdateScoreboard();
1392 if (w->getLocal()) {
1393 client->bShouldRepaintInfo = true;
1394 }
1395 }
1396
1397
1398
1399
1400 ///////////////////
1401 // Parse a text packet
ParseText(CBytestream * bs)1402 void CClientNetEngine::ParseText(CBytestream *bs)
1403 {
1404 int type = bs->readInt(1);
1405 if( type < TXT_CHAT )
1406 type = TXT_CHAT;
1407 if( type > TXT_TEAMPM )
1408 type = TXT_TEAMPM;
1409
1410 Color col = tLX->clWhite;
1411 int t = client->getNumWorms() == 0 ? 0 : client->cLocalWorms[0]->getTeam();
1412 switch(type) {
1413 // Chat
1414 case TXT_CHAT: col = tLX->clChatText; break;
1415 // Normal
1416 case TXT_NORMAL: col = tLX->clNormalText; break;
1417 // Notice
1418 case TXT_NOTICE: col = tLX->clNotice; break;
1419 // Network
1420 case TXT_NETWORK: col = tLX->clNetworkText; break;
1421 // Private
1422 case TXT_PRIVATE: col = tLX->clPrivateText; break;
1423 // Team Private Chat
1424 case TXT_TEAMPM: col = tLX->clTeamColors[t]; break;
1425 }
1426
1427 std::string buf = bs->readString();
1428
1429 // If we are playing a local game, discard network messages
1430 if(tLX->iGameType == GME_LOCAL) {
1431 if(type == TXT_NETWORK)
1432 return;
1433 if(type != TXT_CHAT)
1434 col = tLX->clNormalText;
1435 }
1436
1437 buf = Utf8String(buf); // Convert any possible pseudo-UTF8 (old LX compatible) to normal UTF8 string
1438
1439 // Escape all HTML/XML markup, it is mostly used to annoy other players
1440 xmlEntityText(buf);
1441
1442 client->cChatbox.AddText(buf, col, (TXT_TYPE)type, tLX->currentTime);
1443
1444 // Log the conversation
1445 if (tLXOptions->bLogConvos && convoLogger)
1446 convoLogger->logMessage(buf, (TXT_TYPE)type);
1447 }
1448
1449
getChatText(CClient * client)1450 static std::string getChatText(CClient* client) {
1451 if(client->getStatus() == NET_PLAYING || client->getGameReady())
1452 return client->chatterText();
1453 else if(tLX->iGameType == GME_HOST)
1454 return DeprecatedGUI::Menu_Net_HostLobbyGetText();
1455 else if(tLX->iGameType == GME_JOIN)
1456 return DeprecatedGUI::Menu_Net_JoinLobbyGetText();
1457
1458 warnings << "WARNING: getChatText(): cannot find chat source" << endl;
1459 return "";
1460 }
1461
setChatText(CClient * client,const std::string & txt)1462 static void setChatText(CClient* client, const std::string& txt) {
1463 if(client->getStatus() == NET_PLAYING || client->getGameReady()) {
1464 client->chatterText() = txt;
1465 client->setChatPos( Utf8StringSize(txt) );
1466 } else if(tLX->iGameType == GME_HOST) {
1467 DeprecatedGUI::Menu_Net_HostLobbySetText( txt );
1468 } else if(tLX->iGameType == GME_JOIN) {
1469 DeprecatedGUI::Menu_Net_JoinLobbySetText( txt );
1470 } else
1471 warnings << "WARNING: setChatText(): cannot find chat source" << endl;
1472 }
1473
1474
ParseChatCommandCompletionSolution(CBytestream * bs)1475 void CClientNetEngineBeta7::ParseChatCommandCompletionSolution(CBytestream* bs) {
1476 std::string startStr = bs->readString();
1477 std::string solution = bs->readString();
1478
1479 std::string chatCmd = getChatText(client);
1480
1481 if(strSeemsLikeChatCommand(chatCmd))
1482 chatCmd = chatCmd.substr(1);
1483 else
1484 return;
1485
1486 if(stringcaseequal(startStr, chatCmd))
1487 setChatText(client, "/" + solution);
1488 }
1489
ParseChatCommandCompletionList(CBytestream * bs)1490 void CClientNetEngineBeta7::ParseChatCommandCompletionList(CBytestream* bs) {
1491 std::string startStr = bs->readString();
1492
1493 std::list<std::string> possibilities;
1494 uint n = bs->readInt(4);
1495 if (n > 32)
1496 warnings << "ParseChatCompletionList got a too big number of suggestions (" << n << ")" << endl;
1497
1498 for(uint i = 0; i < n && !bs->isPosAtEnd(); i++)
1499 possibilities.push_back(bs->readString());
1500
1501 std::string chatCmd = getChatText(client);
1502
1503 if(strSeemsLikeChatCommand(chatCmd))
1504 chatCmd = chatCmd.substr(1);
1505 else
1506 return;
1507
1508 if(!stringcaseequal(startStr, chatCmd))
1509 return;
1510
1511 std::string posStr;
1512 for(std::list<std::string>::iterator it = possibilities.begin(); it != possibilities.end(); ++it) {
1513 if(it != possibilities.begin()) posStr += " ";
1514 posStr += *it;
1515 }
1516
1517 client->cChatbox.AddText(posStr, tLX->clNotice, TXT_NOTICE, tLX->currentTime);
1518 }
1519
1520 ///////////////////
1521 // Parse AFK packet
ParseAFK(CBytestream * bs)1522 void CClientNetEngineBeta7::ParseAFK(CBytestream *bs)
1523 {
1524 CWorm* w = getWorm(client, bs, "CClientNetEngine::ParseAFK", SkipMult<Skip<2>, SkipString>);
1525 if(!w) return;
1526
1527 AFK_TYPE afkType = (AFK_TYPE)bs->readByte();
1528 std::string message = bs->readString(128);
1529
1530 w->setAFK(afkType, message);
1531 }
1532
1533
1534 ///////////////////
1535 // Parse a score update packet
ParseScoreUpdate(CBytestream * bs)1536 void CClientNetEngine::ParseScoreUpdate(CBytestream *bs)
1537 {
1538 CWorm* w = getWorm(client, bs, "ParseScoreUpdate", Skip<3>);
1539 if(!w) return;
1540
1541 log_worm_t *l = client->GetLogWorm(w->getID());
1542
1543 w->setLives( MAX<int>((int)bs->readInt16(), WRM_UNLIM) );
1544 w->setKills( MAX(bs->readInt(1), 0) );
1545
1546
1547 if (w->getLocal())
1548 client->bShouldRepaintInfo = true;
1549
1550 // Logging
1551 if (l) {
1552 // Check if the stats changed
1553 bool stats_changed = false;
1554 if (l->iLives != w->getLives()) {
1555 l->iLives = w->getLives();
1556 client->iLastVictim = w->getID();
1557 stats_changed = true;
1558 }
1559
1560 if (l->iKills != w->getScore()) {
1561 l->iKills = w->getScore();
1562 client->iLastKiller = w->getID();
1563 stats_changed = true;
1564 }
1565
1566 // If the update was sent but no changes made -> this is a killer that made a teamkill
1567 // See CServer::ParseDeathPacket for more info
1568 if (!stats_changed)
1569 client->iLastKiller = w->getID();
1570 }
1571
1572 client->UpdateScoreboard();
1573
1574 DeprecatedGUI::bJoin_Update = true;
1575 DeprecatedGUI::bHost_Update = true;
1576 }
1577
ParseTeamScoreUpdate(CBytestream * bs)1578 void CClientNetEngine::ParseTeamScoreUpdate(CBytestream *bs) {
1579 warnings << "ParseTeamScoreUpdate: got update from too old " << client->getServerVersion().asString() << " server" << endl;
1580 bs->SkipAll(); // screwed up
1581 }
1582
ParseTeamScoreUpdate(CBytestream * bs)1583 void CClientNetEngineBeta9::ParseTeamScoreUpdate(CBytestream *bs) {
1584 if(client->tGameInfo.iGeneralGameType != GMT_TEAMS)
1585 warnings << "ParseTeamScoreUpdate: it's not a teamgame" << endl;
1586
1587 bool someTeamScored = false;
1588 int teamCount = bs->readByte();
1589 for(int i = 0; i < teamCount; ++i) {
1590 if(bs->isPosAtEnd()) {
1591 warnings << "ParseTeamScoreUpdate: network is screwed up" << endl;
1592 break;
1593 }
1594
1595 if(i == 4) warnings << "ParseTeamScoreUpdate: cannot handle teamscores for other than the first 4 teams" << endl;
1596 int score = bs->readInt16();
1597 if(i < 4) {
1598 if(score > client->iTeamScores[i]) someTeamScored = true;
1599 client->iTeamScores[i] = score;
1600 }
1601 }
1602
1603 if(someTeamScored && client->tGameInfo.gameMode == GameMode(GM_CTF))
1604 PlaySoundSample(sfxGame.smpTeamScore.get());
1605
1606 // reorder the list
1607 client->UpdateScoreboard();
1608 }
1609
1610
1611 ///////////////////
1612 // Parse a game over packet
ParseGameOver(CBytestream * bs)1613 void CClientNetEngine::ParseGameOver(CBytestream *bs)
1614 {
1615 if(client->getServerVersion() < OLXBetaVersion(0,58,1)) {
1616 client->iMatchWinner = CLAMP(bs->readInt(1), 0, MAX_PLAYERS - 1);
1617
1618 client->iMatchWinnerTeam = -1;
1619
1620 // Get the winner team if TDM (old servers send wrong info here, better when we find it out)
1621 if (client->tGameInfo.iGeneralGameType == GMT_TEAMS) {
1622
1623 if (client->tGameInfo.iKillLimit != -1) {
1624 client->iMatchWinnerTeam = client->cRemoteWorms[client->iMatchWinner].getTeam();
1625 } else if (client->tGameInfo.iLives != -2) {
1626 for (int i=0; i < MAX_WORMS; i++) {
1627 if (client->cRemoteWorms[i].getLives() >= 0) {
1628 client->iMatchWinnerTeam = client->cRemoteWorms[i].getTeam();
1629 break;
1630 }
1631 }
1632 }
1633 }
1634
1635 // Older servers send wrong info about tag winner, better if we count it ourself
1636 if (client->tGameInfo.iGeneralGameType == GMT_TIME) {
1637 TimeDiff max = TimeDiff(0);
1638
1639 for (int i=0; i < MAX_WORMS; i++) {
1640 if (client->cRemoteWorms[i].isUsed() && client->cRemoteWorms[i].getTagTime() > max) {
1641 max = client->cRemoteWorms[i].getTagTime();
1642 client->iMatchWinner = i;
1643 }
1644 }
1645 }
1646 }
1647 else { // server >=beta9
1648 client->iMatchWinner = bs->readByte();
1649
1650 if(client->tGameInfo.iGeneralGameType == GMT_TEAMS) {
1651 client->iMatchWinnerTeam = bs->readByte();
1652 int teamCount = bs->readByte();
1653 for(int i = 0; i < teamCount; ++i) {
1654 if(bs->isPosAtEnd()) {
1655 warnings << "ParseGameOver: network is screwed up" << endl;
1656 break;
1657 }
1658
1659 if(i == 4) warnings << "ParseGameOver: cannot handle teamscores for other than the first 4 teams" << endl;
1660 int score = bs->readInt16();
1661 if(i < 4) client->iTeamScores[i] = score;
1662 }
1663 } else
1664 client->iMatchWinnerTeam = -1;
1665 }
1666
1667 // Check
1668 if (client->bGameOver) {
1669 notes << "CClientNetEngine::ParseGameOver: the game is already over, ignoring" << endl;
1670 return;
1671 }
1672
1673
1674 // Game over
1675 hints << "Client: the game is over";
1676 if(client->iMatchWinner >= 0 && client->iMatchWinner < MAX_WORMS) {
1677 hints << ", the winner is worm " << client->iMatchWinner << ":" << client->cRemoteWorms[client->iMatchWinner].getName();
1678 }
1679 if(client->iMatchWinnerTeam >= 0) {
1680 hints << ", the winning team is team " << client->iMatchWinnerTeam;
1681 }
1682 hints << endl;
1683 client->bGameOver = true;
1684 client->fGameOverTime = tLX->currentTime;
1685
1686 if (client->tGameLog)
1687 client->tGameLog->iWinner = client->iMatchWinner;
1688
1689 // Clear the projectiles
1690 client->cProjectiles.clear();
1691
1692 client->UpdateScoreboard();
1693 client->bShouldRepaintInfo = true;
1694
1695 // if we are away (perhaps waiting because we were out), notify us
1696 NotifyUserOnEvent();
1697 }
1698
1699
1700 ///////////////////
1701 // Parse a spawn bonus packet
ParseSpawnBonus(CBytestream * bs)1702 void CClientNetEngine::ParseSpawnBonus(CBytestream *bs)
1703 {
1704 int wpn = 0;
1705 int type = MAX(0,MIN((int)bs->readByte(),2));
1706
1707 if(type == BNS_WEAPON)
1708 wpn = bs->readInt(1);
1709
1710 int id = bs->readByte();
1711 int x = bs->readInt(2);
1712 int y = bs->readInt(2);
1713
1714 // Check
1715 if (!client->bGameReady) {
1716 warnings << "CClientNetEngine::ParseSpawnBonus: Cannot spawn bonus when not playing (packet ignored)" << endl;
1717 return;
1718 }
1719
1720 if (id < 0 || id >= MAX_BONUSES) {
1721 warnings << "CClientNetEngine::ParseSpawnBonus: invalid bonus ID (" << id << ")" << endl;
1722 return;
1723 }
1724
1725 if (!client->cMap) { // Weird
1726 warnings << "CClientNetEngine::ParseSpawnBonus: cMap not set" << endl;
1727 return;
1728 }
1729
1730 if (x > (int)client->cMap->GetWidth() || x < 0) {
1731 warnings << "CClientNetEngine::ParseSpawnBonus: X-coordinate not in map (" << x << ")" << endl;
1732 return;
1733 }
1734
1735 if (y > (int)client->cMap->GetHeight() || y < 0) {
1736 warnings << "CClientNetEngine::ParseSpawnBonus: Y-coordinate not in map (" << y << ")" << endl;
1737 return;
1738 }
1739
1740 CVec p = CVec( (float)x, (float)y );
1741
1742 client->cBonuses[id].Spawn(p, type, wpn, client->cGameScript.get());
1743 client->cMap->CarveHole(SPAWN_HOLESIZE,p,cClient->getGameLobby()->features[FT_InfiniteMap]);
1744
1745 SpawnEntity(ENT_SPAWN,0,p,CVec(0,0),Color(),NULL);
1746 }
1747
1748
1749 // TODO: Rename this to ParseTimeUpdate (?)
1750 ///////////////////
1751 // Parse a tag update packet
ParseTagUpdate(CBytestream * bs)1752 void CClientNetEngine::ParseTagUpdate(CBytestream *bs)
1753 {
1754 if (!client->bGameReady || client->bGameOver) {
1755 warnings << "CClientNetEngine::ParseTagUpdate: not playing - ignoring" << endl;
1756 return;
1757 }
1758
1759 CWorm* target = getWorm(client, bs, "ParseTagUpdate", Skip<sizeof(float)>);
1760 if(!target) return;
1761 TimeDiff time = TimeDiff(bs->readFloat());
1762
1763 if (client->tGameInfo.iGeneralGameType != GMT_TIME) {
1764 warnings << "CClientNetEngine::ParseTagUpdate: game mode is not tag - ignoring" << endl;
1765 return;
1766 }
1767
1768 // Set all the worms 'tag' property to false
1769 CWorm *w = client->cRemoteWorms;
1770 for(int i=0;i<MAX_WORMS;i++,w++) {
1771 if(w->isUsed())
1772 w->setTagIT(false);
1773 }
1774
1775 // Tag the worm
1776 target->setTagIT(true);
1777 target->setTagTime(time);
1778
1779 // Log it
1780 log_worm_t *l = client->GetLogWorm(target->getID());
1781 if (l) {
1782 for (int i=0; i < client->tGameLog->iNumWorms; i++)
1783 client->tGameLog->tWorms[i].bTagIT = false;
1784
1785 l->fTagTime = time;
1786 l->bTagIT = true;
1787 }
1788 }
1789
1790
1791 ///////////////////
1792 // Parse client-ready packet
ParseCLReady(CBytestream * bs)1793 void CClientNetEngine::ParseCLReady(CBytestream *bs)
1794 {
1795 int numworms = bs->readByte();
1796
1797 if((numworms < 0 || numworms > MAX_PLAYERS) && tLX->iGameType != GME_LOCAL) {
1798 // bad packet
1799 hints << "CClientNetEngine::ParseCLReady: invalid numworms (" << numworms << ")" << endl;
1800 bs->SkipAll();
1801 return;
1802 }
1803
1804
1805 for(short i=0;i<numworms;i++) {
1806 if(bs->isPosAtEnd()) {
1807 hints << "CClientNetEngine::ParseCLReady: package messed up" << endl;
1808 return;
1809 }
1810
1811 byte id = bs->readByte();
1812
1813 if(id >= MAX_WORMS) {
1814 hints << "CClientNetEngine::ParseCLReady: bad worm ID (" << int(id) << ")" << endl;
1815 bs->SkipAll();
1816 return;
1817 }
1818
1819 if(!client->cRemoteWorms) {
1820 warnings << "Client: got CLReady with uninit worms" << endl;
1821 // Skip the info and if end of packet, just end
1822 if (CWorm::skipWeapons(bs)) break;
1823 continue;
1824 }
1825
1826 CWorm* w = &client->cRemoteWorms[id];
1827
1828 w->setGameReady(true);
1829
1830 // Read the weapon info
1831 notes << "Client:ParseCLReady: ";
1832 w->readWeapons(bs);
1833
1834 }
1835
1836 client->bUpdateScore = true; // Change the ingame scoreboard
1837 }
1838
1839
1840 ///////////////////
1841 // Parse an update-lobby packet, when worms got ready/notready
ParseUpdateLobby(CBytestream * bs)1842 void CClientNetEngine::ParseUpdateLobby(CBytestream *bs)
1843 {
1844 int numworms = bs->readByte();
1845 bool ready = bs->readBool();
1846
1847 if (numworms < 0 || numworms > MAX_WORMS) {
1848 warnings << "CClientNetEngine::ParseUpdateLobby: invalid strange numworms value (" << numworms << ")" << endl;
1849
1850 // Skip to get the right position in stream
1851 bs->Skip(numworms);
1852 bs->Skip(numworms);
1853 return;
1854 }
1855
1856 for(short i=0;i<numworms;i++) {
1857 byte id = bs->readByte();
1858 int team = MAX(0,MIN(3,(int)bs->readByte()));
1859
1860 if( id >= MAX_WORMS) {
1861 warnings << "CClientNetEngine::ParseUpdateLobby: invalid worm ID (" << id << ")" << endl;
1862 continue;
1863 }
1864
1865 CWorm* w = &client->cRemoteWorms[id];
1866 if(w) {
1867 w->setLobbyReady(ready);
1868 w->setTeam(team);
1869 }
1870 }
1871
1872 // Update lobby
1873 DeprecatedGUI::bJoin_Update = true;
1874 DeprecatedGUI::bHost_Update = true;
1875 }
1876
1877 ///////////////////
1878 // Parse a worms-out (named 'client-left' before) packet
ParseWormsOut(CBytestream * bs)1879 void CClientNetEngine::ParseWormsOut(CBytestream *bs)
1880 {
1881 byte numworms = bs->readByte();
1882
1883 if(numworms < 1 || numworms > MAX_PLAYERS) {
1884 // bad packet
1885 hints << "CClientNetEngine::ParseWormsOut: bad numworms count (" << int(numworms) << ")" << endl;
1886
1887 // Skip to the right position
1888 bs->Skip(numworms);
1889
1890 return;
1891 }
1892
1893
1894 for(int i=0;i<numworms;i++) {
1895 byte id = bs->readByte();
1896
1897 if( id >= MAX_WORMS) {
1898 hints << "CClientNetEngine::ParseWormsOut: invalid worm ID (" << int(id) << ")" << endl;
1899 continue;
1900 }
1901
1902 CWorm *w = &client->cRemoteWorms[id];
1903 if(!w->isUsed()) {
1904 warnings << "ParseWormsOut: worm " << int(id) << " is not used anymore" << endl;
1905 continue;
1906 }
1907
1908 if(!w->getLocal()) { // Server kicks local worms using S2C_DROPPED, this packet cannot be used for it
1909
1910 // Log this
1911 if (client->tGameLog) {
1912 log_worm_t *l = client->GetLogWorm(id);
1913 if (l) {
1914 l->bLeft = true;
1915 l->fTimeLeft = client->serverTime();
1916 }
1917 }
1918
1919 client->RemoveWorm(id);
1920
1921 } else {
1922 hints << "Warning: server says we've left but that is not true" << endl;
1923 }
1924 }
1925
1926 DeprecatedGUI::bJoin_Update = true;
1927 DeprecatedGUI::bHost_Update = true;
1928
1929 client->UpdateScoreboard();
1930 }
1931
1932
1933 ///////////////////
1934 // Parse an 'update-worms' packet
ParseUpdateWorms(CBytestream * bs)1935 void CClientNetEngine::ParseUpdateWorms(CBytestream *bs)
1936 {
1937 byte count = bs->readByte();
1938 if (count > MAX_WORMS) {
1939 hints << "CClientNetEngine::ParseUpdateWorms: invalid worm count (" << count << ")" << endl;
1940
1941 // Skip to the right position
1942 for (byte i=0;i<count;i++) {
1943 bs->Skip(1);
1944 CWorm::skipPacketState(bs);
1945 }
1946
1947 return;
1948 }
1949
1950 if(!client->bGameReady || !client->cMap || !client->cMap->isLoaded()) {
1951 // We could receive an update if we didn't got the preparegame package yet.
1952 // This is because all the data about the preparegame could be sent in multiple packages
1953 // and each reliable package can contain a worm update.
1954
1955 // Skip to the right position
1956 for (byte i=0;i<count;i++) {
1957 bs->Skip(1);
1958 CWorm::skipPacketState(bs);
1959 }
1960
1961 return;
1962 }
1963
1964 for(byte i=0;i<count;i++) {
1965 byte id = bs->readByte();
1966
1967 if (id >= MAX_WORMS) {
1968 hints << "CClientNetEngine::ParseUpdateWorms: invalid worm ID (" << id << ")" << endl;
1969 if (CWorm::skipPacketState(bs)) { // Skip not to lose the right position
1970 break;
1971 }
1972 continue;
1973 }
1974
1975 // TODO: what is with that check? remove if outdated
1976 /*if (!cRemoteWorms[id].isUsed()) {
1977 i--;
1978 continue;
1979 }*/
1980
1981 client->cRemoteWorms[id].readPacketState(bs,client->cRemoteWorms);
1982
1983 }
1984
1985 DeprecatedGUI::bJoin_Update = true;
1986 DeprecatedGUI::bHost_Update = true;
1987 }
1988
1989 ///////////////////
1990 // Parse an 'update game lobby' packet
ParseUpdateLobbyGame(CBytestream * bs)1991 void CClientNetEngine::ParseUpdateLobbyGame(CBytestream *bs)
1992 {
1993 if (client->iNetStatus != NET_CONNECTED) {
1994 notes << "CClientNetEngine::ParseUpdateLobbyGame: not in lobby - ignoring" << endl;
1995
1996 // Skip to get the right position
1997 bs->Skip(1);
1998 bs->SkipString();
1999 bs->SkipString();
2000 bs->SkipString();
2001 bs->Skip(8); // All other info
2002
2003 return;
2004 }
2005
2006 FILE *fp = NULL;
2007
2008 client->tGameInfo.iMaxPlayers = bs->readByte();
2009 client->tGameInfo.sMapFile = bs->readString();
2010 client->tGameInfo.sModName = bs->readString();
2011 client->tGameInfo.sModDir = bs->readString();
2012 client->tGameInfo.iGeneralGameType = bs->readByte();
2013 if( client->tGameInfo.iGeneralGameType > GMT_MAX || client->tGameInfo.iGeneralGameType < 0 )
2014 client->tGameInfo.iGeneralGameType = GMT_NORMAL;
2015 client->tGameInfo.sGameMode = "";
2016 client->tGameInfo.gameMode = NULL;
2017 client->tGameInfo.iLives = bs->readInt16();
2018 client->tGameInfo.iKillLimit = bs->readInt16();
2019 client->tGameInfo.fTimeLimit = -100;
2020 client->tGameInfo.iLoadingTime = bs->readInt16();
2021 client->tGameInfo.bBonusesOn = bs->readBool();
2022
2023 client->tGameInfo.features[FT_GameSpeed] = 1.0f;
2024 client->tGameInfo.bForceRandomWeapons = false;
2025 client->tGameInfo.bSameWeaponsAsHostWorm = false;
2026
2027 // Check if we have the level & mod
2028 client->bHaveMod = true;
2029
2030 // Does the level file exist
2031 std::string MapName = CMap::GetLevelName(client->tGameInfo.sMapFile);
2032 client->bHaveMap = MapName != "";
2033
2034 // Convert the map filename to map name
2035 if (client->bHaveMap) {
2036 client->tGameInfo.sMapName = MapName;
2037 }
2038
2039 // Does the 'script.lgs' file exist in the mod dir?
2040 fp = OpenGameFile(client->tGameInfo.sModDir + "/script.lgs", "rb");
2041 if(!fp)
2042 client->bHaveMod = false;
2043 else
2044 fclose(fp);
2045
2046 foreach( Feature*, f, Array(featureArray,featureArrayLen()) ) {
2047 client->tGameInfo.features[f->get()] = f->get()->unsetValue;
2048 }
2049
2050 DeprecatedGUI::bJoin_Update = true;
2051 DeprecatedGUI::bHost_Update = true;
2052 }
2053
ParseUpdateLobbyGame(CBytestream * bs)2054 void CClientNetEngineBeta7::ParseUpdateLobbyGame(CBytestream *bs)
2055 {
2056 CClientNetEngine::ParseUpdateLobbyGame(bs);
2057
2058 client->tGameInfo.features[FT_GameSpeed] = bs->readFloat();
2059 client->tGameInfo.bForceRandomWeapons = bs->readBool();
2060 client->tGameInfo.bSameWeaponsAsHostWorm = bs->readBool();
2061 }
2062
ParseUpdateLobbyGame(CBytestream * bs)2063 void CClientNetEngineBeta9::ParseUpdateLobbyGame(CBytestream *bs)
2064 {
2065 CClientNetEngineBeta7::ParseUpdateLobbyGame(bs);
2066
2067 client->tGameInfo.fTimeLimit = bs->readFloat();
2068 if(client->tGameInfo.fTimeLimit < 0) client->tGameInfo.fTimeLimit = -1;
2069
2070 ParseFeatureSettings(bs);
2071
2072 client->tGameInfo.sGameMode = bs->readString();
2073 client->tGameInfo.gameMode = GameMode(client->tGameInfo.sGameMode);
2074 }
2075
2076
2077 ///////////////////
2078 // Parse a 'worm down' packet (Worm died)
ParseWormDown(CBytestream * bs)2079 void CClientNetEngine::ParseWormDown(CBytestream *bs)
2080 {
2081 // Don't allow anyone to kill us in lobby
2082 if (!client->bGameReady) {
2083 notes << "CClientNetEngine::ParseWormDown: not playing - ignoring" << endl;
2084 bs->Skip(1); // ID
2085 return;
2086 }
2087
2088 byte id = bs->readByte();
2089
2090 if(id < MAX_WORMS) {
2091 // If the respawn time is 0, the worm can be spawned even before the simulation is done
2092 // Therefore the check for isAlive in the simulation does not work in all cases
2093 // Because of that, we unattach the rope here, just to be sure
2094 if (client->cRemoteWorms[id].getHookedWorm())
2095 client->cRemoteWorms[id].getHookedWorm()->getNinjaRope()->UnAttachPlayer(); // HINT: hookedWorm is reset here (set to NULL)
2096
2097 client->cRemoteWorms[id].setAlive(false);
2098 //client->cRemoteWorms[id].setDeaths(client->cRemoteWorms[id].getDeaths()+1);
2099 if (client->cRemoteWorms[id].getLocal() && client->cRemoteWorms[id].getType() == PRF_HUMAN)
2100 client->cRemoteWorms[id].clearInput();
2101
2102 // Make a death sound
2103 int s = GetRandomInt(2);
2104 StartSound( sfxGame.smpDeath[s], client->cRemoteWorms[id].getPos(), client->cRemoteWorms[id].getLocal(), -1, client->cLocalWorms[0]);
2105
2106 // Spawn some giblets
2107 CWorm* w = &client->cRemoteWorms[id];
2108
2109 for(short n=0;n<7;n++)
2110 SpawnEntity(ENT_GIB,0,w->getPos(),CVec(GetRandomNum()*80,GetRandomNum()*80),Color(),w->getGibimg());
2111
2112 // Blood
2113 float amount = 50.0f * ((float)tLXOptions->iBloodAmount / 100.0f);
2114 for(int i=0;i<amount;i++) {
2115 float sp = GetRandomNum()*100+50;
2116 SpawnEntity(ENT_BLOODDROPPER,0,w->getPos(),CVec(GetRandomNum()*sp,GetRandomNum()*sp),Color(128,0,0),NULL);
2117 SpawnEntity(ENT_BLOOD,0,w->getPos(),CVec(GetRandomNum()*sp,GetRandomNum()*sp),Color(200,0,0),NULL);
2118 SpawnEntity(ENT_BLOOD,0,w->getPos(),CVec(GetRandomNum()*sp,GetRandomNum()*sp),Color(128,0,0),NULL);
2119 }
2120 } else {
2121 warnings << "CClientNetEngine::ParseWormDown: invalid worm ID (" << id << ")" << endl;
2122 }
2123
2124 // Someone has been killed, log it
2125 if (client->iLastVictim != -1) {
2126 log_worm_t *l_vict = client->GetLogWorm(client->iLastVictim);
2127 log_worm_t *l_kill = l_vict;
2128
2129 // If we haven't received killer's update score, it has been a suicide
2130 if (client->iLastKiller != -1)
2131 l_kill = client->GetLogWorm(client->iLastKiller);
2132
2133 if (l_kill && l_vict) {
2134 // HINT: lives and kills are updated in ParseScoreUpdate
2135
2136 // Suicide
2137 if (l_kill == l_vict) {
2138 l_vict->iSuicides++;
2139 }
2140
2141 // Teamkill
2142 else if (client->cRemoteWorms[client->iLastKiller].getTeam() ==
2143 client->cRemoteWorms[client->iLastVictim].getTeam()) {
2144 l_kill->iTeamKills++;
2145 l_vict->iTeamDeaths++;
2146 }
2147 }
2148 }
2149
2150 // Reset
2151 client->iLastVictim = client->iLastKiller = -1;
2152 }
2153
2154
2155 ///////////////////
2156 // Parse a 'server left' packet
ParseServerLeaving(CBytestream * bs)2157 void CClientNetEngine::ParseServerLeaving(CBytestream *bs)
2158 {
2159 // Set the server error details
2160
2161 if (tLX->iGameType != GME_JOIN) {
2162 warnings << "Got local server leaving packet, ignoring..." << endl;
2163 return;
2164 }
2165 // Not so much an error, but rather a disconnection of communication between us & server
2166 client->bServerError = true;
2167 client->strServerErrorMsg = "Server has quit";
2168
2169 // Log
2170 if (tLXOptions->bLogConvos && convoLogger)
2171 convoLogger->leaveServer();
2172
2173 NotifyUserOnEvent();
2174 }
2175
2176
2177 ///////////////////
2178 // Parse a 'single shot' packet
ParseSingleShot(CBytestream * bs)2179 void CClientNetEngine::ParseSingleShot(CBytestream *bs)
2180 {
2181 if(!client->canSimulate()) {
2182 if(client->bGameReady)
2183 notes << "CClientNetEngine::ParseSingleShot: game over - ignoring" << endl;
2184 CShootList::skipSingle(bs, client->getServerVersion()); // Skip to get to the correct position
2185 return;
2186 }
2187
2188 client->cShootList.readSingle(bs, client->getServerVersion(), client->cGameScript.get()->GetNumWeapons() - 1);
2189
2190 // Process the shots
2191 client->ProcessServerShotList();
2192
2193 }
2194
2195
2196 ///////////////////
2197 // Parse a 'multi shot' packet
ParseMultiShot(CBytestream * bs)2198 void CClientNetEngine::ParseMultiShot(CBytestream *bs)
2199 {
2200 if(!client->canSimulate()) {
2201 if(client->bGameReady)
2202 notes << "CClientNetEngine::ParseMultiShot: game over - ignoring" << endl;
2203 CShootList::skipMulti(bs, client->getServerVersion()); // Skip to get to the correct position
2204 return;
2205 }
2206
2207 client->cShootList.readMulti(bs, client->getServerVersion(), client->cGameScript.get()->GetNumWeapons() - 1);
2208
2209 // Process the shots
2210 client->ProcessServerShotList();
2211 }
2212
2213
2214 ///////////////////
2215 // Update the worms stats
ParseUpdateStats(CBytestream * bs)2216 void CClientNetEngine::ParseUpdateStats(CBytestream *bs)
2217 {
2218 byte num = bs->readByte();
2219 if (num > MAX_PLAYERS)
2220 warnings << "CClientNetEngine::ParseUpdateStats: invalid worm count (" << num << ") - clamping" << endl;
2221
2222 short oldnum = num;
2223 num = (byte)MIN(num,MAX_PLAYERS);
2224
2225 short i;
2226 for(i=0; i<num; i++)
2227 if (client->getWorm(i)) {
2228 if (client->getWorm(i)->getLocal())
2229 client->bShouldRepaintInfo = true;
2230
2231 client->getWorm(i)->readStatUpdate(bs);
2232 }
2233
2234 // Skip if there were some clamped worms
2235 for (i=0;i<oldnum-num;i++)
2236 if (CWorm::skipStatUpdate(bs))
2237 break;
2238 }
2239
2240
2241 ///////////////////
2242 // Parse a 'destroy bonus' packet
ParseDestroyBonus(CBytestream * bs)2243 void CClientNetEngine::ParseDestroyBonus(CBytestream *bs)
2244 {
2245 byte id = bs->readByte();
2246
2247 if (!client->bGameReady) {
2248 warnings << "CClientNetEngine::ParseDestroyBonus: Ignoring, the game is not running." << endl;
2249 return;
2250 }
2251
2252 if( id < MAX_BONUSES )
2253 client->cBonuses[id].setUsed(false);
2254 else
2255 warnings << "CClientNetEngine::ParseDestroyBonus: invalid bonus ID (" << id << ")" << endl;
2256 }
2257
2258
2259 ///////////////////
2260 // Parse a 'goto lobby' packet
ParseGotoLobby(CBytestream *)2261 void CClientNetEngine::ParseGotoLobby(CBytestream *)
2262 {
2263 notes << "Client: received gotoLobby signal" << endl;
2264
2265 // TODO: Why did we have that code? In hosting mode, we should always trust the server.
2266 // Even worse, the check is not fully correct. client->bGameOver means that the game is over.
2267 /*
2268 if (tLX->iGameType != GME_JOIN) {
2269 if (!tLX->bQuitEngine) {
2270 warnings << "we should go to lobby but should not quit the game, ignoring game over signal" << endl;
2271 return;
2272 }
2273 }
2274 */
2275
2276 // in lobby we need the events again
2277 client->tSocket->setWithEvents(true);
2278
2279 // Do a minor clean up
2280 client->MinorClear();
2281
2282 // Hide the console
2283 Con_Hide();
2284
2285 DeprecatedGUI::Menu_FloatingOptionsShutdown();
2286
2287
2288 if(tLX->iGameType == GME_JOIN) {
2289
2290 // Tell server my worms aren't ready
2291 CBytestream bs;
2292 bs.Clear();
2293 bs.writeByte(C2S_UPDATELOBBY);
2294 bs.writeByte(0);
2295 client->cNetChan->AddReliablePacketToSend(bs);
2296
2297 // Goto the join lobby
2298 GotoJoinLobby();
2299 }
2300
2301 client->ShutdownLog();
2302
2303 if( GetGlobalIRC() )
2304 GetGlobalIRC()->setAwayMessage("Server: " + client->getServerName());
2305
2306 }
2307
2308
2309 ///////////////////
2310 // Parse a 'dropped' packet
ParseDropped(CBytestream * bs)2311 void CClientNetEngine::ParseDropped(CBytestream *bs)
2312 {
2313 // Set the server error details
2314
2315 // Ignore if we are hosting/local, it's a nonsense
2316 if (tLX->iGameType != GME_JOIN) {
2317 warnings << "Got dropped from local server (" << bs->readString() << "), ignoring" << endl;
2318 return;
2319 }
2320
2321 // Not so much an error, but a message why we were dropped
2322 client->bServerError = true;
2323 client->strServerErrorMsg = Utf8String(bs->readString());
2324
2325 // Log
2326 if (tLXOptions->bLogConvos && convoLogger)
2327 convoLogger->logMessage(client->strServerErrorMsg, TXT_NETWORK);
2328 }
2329
2330 // Server sent us some file
ParseSendFile(CBytestream * bs)2331 void CClientNetEngine::ParseSendFile(CBytestream *bs)
2332 {
2333
2334 client->fLastFileRequestPacketReceived = tLX->currentTime;
2335 if( client->getUdpFileDownloader()->receive(bs) )
2336 {
2337 if( CUdpFileDownloader::isPathValid( client->getUdpFileDownloader()->getFilename() ) &&
2338 ! IsFileAvailable( client->getUdpFileDownloader()->getFilename() ) &&
2339 client->getUdpFileDownloader()->isFinished() )
2340 {
2341 // Server sent us some file we don't have - okay, save it
2342 FILE * ff=OpenGameFile( client->getUdpFileDownloader()->getFilename(), "wb" );
2343 if( ff == NULL )
2344 {
2345 errors << "CClientNetEngine::ParseSendFile(): cannot write file " << client->getUdpFileDownloader()->getFilename() << endl;
2346 return;
2347 };
2348 fwrite( client->getUdpFileDownloader()->getData().data(), 1, client->getUdpFileDownloader()->getData().size(), ff );
2349 fclose(ff);
2350
2351 if( client->getUdpFileDownloader()->getFilename().find("levels/") == 0 &&
2352 IsFileAvailable( "levels/" + client->tGameInfo.sMapFile ) )
2353 {
2354 client->bDownloadingMap = false;
2355 client->bWaitingForMap = false;
2356 client->FinishMapDownloads();
2357 client->sMapDownloadName = "";
2358
2359 DeprecatedGUI::bJoin_Update = true;
2360 DeprecatedGUI::bHost_Update = true;
2361 }
2362 if( client->getUdpFileDownloader()->getFilename().find("skins/") == 0 )
2363 {
2364 // Loads skin from disk automatically on next frame
2365 DeprecatedGUI::bJoin_Update = true;
2366 DeprecatedGUI::bHost_Update = true;
2367 }
2368 if( ! client->bHaveMod &&
2369 client->getUdpFileDownloader()->getFilename().find( client->tGameInfo.sModDir ) == 0 &&
2370 IsFileAvailable(client->tGameInfo.sModDir + "/script.lgs", false) )
2371 {
2372 client->bDownloadingMod = false;
2373 client->bWaitingForMod = false;
2374 client->FinishModDownloads();
2375 client->sModDownloadName = "";
2376
2377 DeprecatedGUI::bJoin_Update = true;
2378 DeprecatedGUI::bHost_Update = true;
2379 }
2380
2381 client->getUdpFileDownloader()->requestFilesPending(); // Immediately request another file
2382 client->fLastFileRequest = tLX->currentTime;
2383
2384 }
2385 else
2386 if( client->getUdpFileDownloader()->getFilename() == "STAT_ACK:" &&
2387 client->getUdpFileDownloader()->getFileInfo().size() > 0 &&
2388 ! client->bHaveMod &&
2389 client->getUdpFileDownloader()->isFinished() )
2390 {
2391 // Got filenames list of mod dir - push "script.lgs" to the end of list to download all other data before
2392 uint f;
2393 for( f=0; f<client->getUdpFileDownloader()->getFileInfo().size(); f++ )
2394 {
2395 if( client->getUdpFileDownloader()->getFileInfo()[f].filename.find( client->tGameInfo.sModDir ) == 0 &&
2396 ! IsFileAvailable( client->getUdpFileDownloader()->getFileInfo()[f].filename ) &&
2397 stringcaserfind( client->getUdpFileDownloader()->getFileInfo()[f].filename, "/script.lgs" ) != std::string::npos )
2398 {
2399 client->getUdpFileDownloader()->requestFile( client->getUdpFileDownloader()->getFileInfo()[f].filename, true );
2400 client->fLastFileRequest = tLX->currentTime + 1.5f; // Small delay so server will be able to send all the info
2401 client->iModDownloadingSize = client->getUdpFileDownloader()->getFilesPendingSize();
2402 }
2403 }
2404 for( f=0; f<client->getUdpFileDownloader()->getFileInfo().size(); f++ )
2405 {
2406 if( client->getUdpFileDownloader()->getFileInfo()[f].filename.find( client->tGameInfo.sModDir ) == 0 &&
2407 ! IsFileAvailable( client->getUdpFileDownloader()->getFileInfo()[f].filename ) &&
2408 stringcaserfind( client->getUdpFileDownloader()->getFileInfo()[f].filename, "/script.lgs" ) == std::string::npos )
2409 {
2410 client->getUdpFileDownloader()->requestFile( client->getUdpFileDownloader()->getFileInfo()[f].filename, true );
2411 client->fLastFileRequest = tLX->currentTime + 1.5f; // Small delay so server will be able to send all the info
2412 client->iModDownloadingSize = client->getUdpFileDownloader()->getFilesPendingSize();
2413 }
2414 }
2415 }
2416 }
2417 if( client->getUdpFileDownloader()->isReceiving() )
2418 {
2419 // Speed up download - server will send next packet when receives ping, or once in 0.5 seconds
2420 CBytestream bs;
2421 bs.writeByte(C2S_SENDFILE);
2422 client->getUdpFileDownloader()->sendPing( &bs );
2423 client->cNetChan->AddReliablePacketToSend(bs);
2424 }
2425 }
2426
ParseReportDamage(CBytestream * bs)2427 void CClientNetEngineBeta9::ParseReportDamage(CBytestream *bs)
2428 {
2429 int id = bs->readByte();
2430 float damage = bs->readFloat();
2431 int offenderId = bs->readByte();
2432
2433 if( !client->bGameReady )
2434 return;
2435 if( id < 0 || id >= MAX_WORMS || offenderId < 0 || offenderId >= MAX_WORMS )
2436 return;
2437 CWorm *w = & client->getRemoteWorms()[id];
2438 CWorm *offender = & client->getRemoteWorms()[offenderId];
2439
2440 if( ! w->isUsed() || ! offender->isUsed() )
2441 return;
2442
2443 w->getDamageReport()[offender->getID()].damage += damage;
2444 w->getDamageReport()[offender->getID()].lastTime = tLX->currentTime;
2445 w->injure(damage); // Calculate correct healthbar
2446 // Update worm damage count (it gets updated in UPDATESCORE packet, we do local calculations here, but they are wrong if we connected during game)
2447 //notes << "CClientNetEngineBeta9::ParseReportDamage() offender " << offender->getID() << " dmg " << damage << " victim " << id << endl;
2448 offender->addDamage( damage, w, client->tGameInfo );
2449 }
2450
ParseScoreUpdate(CBytestream * bs)2451 void CClientNetEngineBeta9::ParseScoreUpdate(CBytestream *bs)
2452 {
2453 short id = bs->readInt(1);
2454
2455 if(id >= 0 && id < MAX_WORMS) {
2456 log_worm_t *l = client->GetLogWorm(id);
2457
2458 int lives = MAX<int>((int)bs->readInt16(), WRM_UNLIM);
2459 if (lives != WRM_OUT && lives != WRM_UNLIM && tLXOptions->tGameInfo.iLives < 0 &&
2460 client->getServerVersion() >= OLXRcVersion(0,58,5) && client->getServerVersion() < OLXBetaVersion(0,59,0)) {
2461 client->cRemoteWorms[id].setDeaths( lives ); // Matches with infinite lives will show deaths in the scoreboard
2462 } else {
2463 client->cRemoteWorms[id].setLives( lives );
2464 }
2465
2466 client->cRemoteWorms[id].setKills( bs->readInt(4) );
2467 float damage = bs->readFloat();
2468 if( client->cRemoteWorms[id].getDamage() != damage )
2469 {
2470 // Occurs pretty often, don't spam console, still it should be the same on client and server
2471 //warnings << "CClientNetEngineBeta9::ParseScoreUpdate(): damage for worm " << client->cRemoteWorms[id].getName() << " is " << client->cRemoteWorms[id].getDamage() << " server sent us " << damage << endl;
2472 }
2473 client->cRemoteWorms[id].setDamage( damage );
2474
2475
2476 if (client->cRemoteWorms[id].getLocal())
2477 client->bShouldRepaintInfo = true;
2478
2479 // Logging
2480 if (l) {
2481 // Check if the stats changed
2482 bool stats_changed = false;
2483 if (l->iLives != client->cRemoteWorms[id].getLives()) {
2484 l->iLives = client->cRemoteWorms[id].getLives();
2485 client->iLastVictim = id;
2486 stats_changed = true;
2487 }
2488
2489 if (l->iKills != client->cRemoteWorms[id].getScore()) {
2490 l->iKills = client->cRemoteWorms[id].getScore();
2491 client->iLastKiller = id;
2492 stats_changed = true;
2493 }
2494
2495 // If the update was sent but no changes made -> this is a killer that made a teamkill
2496 // See CServer::ParseDeathPacket for more info
2497 if (!stats_changed)
2498 client->iLastKiller = id;
2499 }
2500 }
2501 else
2502 {
2503 // do this to get the right position in net stream
2504 bs->Skip(6);
2505 }
2506
2507 client->UpdateScoreboard();
2508
2509 DeprecatedGUI::bJoin_Update = true;
2510 DeprecatedGUI::bHost_Update = true;
2511 };
2512
ParseHideWorm(CBytestream * bs)2513 void CClientNetEngineBeta9::ParseHideWorm(CBytestream *bs)
2514 {
2515 int id = bs->readByte();
2516 int forworm = bs->readByte();
2517 bool hide = bs->readBool();
2518 bool immediate = bs->readBool(); // Immediate hiding (no animation)
2519
2520 // Check
2521 if (id < 0 || id >= MAX_WORMS) {
2522 errors << "ParseHideWorm: invalid worm ID " << id << endl;
2523 return;
2524 }
2525
2526 // Check
2527 if (forworm < 0 || forworm >= MAX_WORMS) {
2528 errors << "ParseHideWorm: invalid forworm ID " << forworm << endl;
2529 return;
2530 }
2531
2532 // Get the worm
2533 CWorm *w = client->getRemoteWorms() + id;
2534 if (!client->getRemoteWorms() || !w->isUsed()) {
2535 errors << "ParseHideWorm: the worm " << id << " does not exist" << endl;
2536 return;
2537 }
2538
2539 w->setAlive(true); // We won't get SpawnWorm packet from H&S server
2540 if (!hide && !immediate) // Show sparkles only when worm is discovered, or else we'll know where it has been respawned
2541 SpawnEntity(ENT_SPAWN,0,w->getPos(),CVec(0,0),Color(),NULL); // Spawn some sparkles, looks good
2542
2543 // Hide or show the worm
2544 if (hide)
2545 w->Hide(forworm, immediate);
2546 else
2547 w->Show(forworm, immediate);
2548 }
2549
ParseFlagInfo(CBytestream * bs)2550 void CClientNetEngine::ParseFlagInfo(CBytestream* bs) {
2551 warnings << "Client: got flaginfo from too old " << client->cServerVersion.asString() << " server" << endl;
2552 bs->SkipAll(); // screwed up
2553 }
2554
ParseFlagInfo(CBytestream * bs)2555 void CClientNetEngineBeta9::ParseFlagInfo(CBytestream* bs) {
2556 if(client->m_flagInfo == NULL) {
2557 warnings << "Client: Got flaginfo with flaginfo unset" << endl;
2558 FlagInfo::skipUpdate(bs);
2559 return;
2560 }
2561
2562 client->m_flagInfo->readUpdate(bs);
2563 }
2564
ParseWormProps(CBytestream * bs)2565 void CClientNetEngine::ParseWormProps(CBytestream* bs) {
2566 warnings << "Client: got worm properties from too old " << client->cServerVersion.asString() << " server" << endl;
2567 bs->SkipAll(); // screwed up
2568 }
2569
ParseWormProps(CBytestream * bs)2570 void CClientNetEngineBeta9::ParseWormProps(CBytestream* bs) {
2571 CWorm* w = getWorm(client, bs, "ParseWormProps", Skip<2*sizeof(float)+1>);
2572 if(!w) return;
2573
2574 bs->ResetBitPos();
2575 bool canUseNinja = bs->readBit();
2576 bool canAirJump = bs->readBit();
2577 bs->SkipRestBits(); // WARNING: remove this when we read 8 bits
2578 float speedFactor = bs->readFloat();
2579 float damageFactor = bs->readFloat();
2580 float shieldFactor = bs->readFloat();
2581
2582 w->setSpeedFactor(speedFactor);
2583 w->setDamageFactor(damageFactor);
2584 w->setShieldFactor(shieldFactor);
2585 w->setCanUseNinja(canUseNinja);
2586 w->setCanAirJump(canAirJump);
2587 }
2588
ParseSelectWeapons(CBytestream * bs)2589 void CClientNetEngine::ParseSelectWeapons(CBytestream* bs) {
2590 warnings << "Client: got worm select weapons from too old " << client->cServerVersion.asString() << " server" << endl;
2591 bs->SkipAll(); // screwed up
2592 }
2593
ParseSelectWeapons(CBytestream * bs)2594 void CClientNetEngineBeta9::ParseSelectWeapons(CBytestream* bs) {
2595 CWorm* w = getWorm(client, bs, "ParseSelectWeapons");
2596 if(!w) return;
2597
2598 w->setWeaponsReady(false);
2599 if(client->OwnsWorm(w->getID())) {
2600 notes << "server sends us SelectWeapons for worm " << w->getID() << endl;
2601 client->setStatus(NET_CONNECTED); // well, that means that we are in weapon selection...
2602 client->bReadySent = false;
2603 w->reinitInputHandler();
2604 w->initWeaponSelection();
2605 }
2606 }
2607
2608