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