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 // Server class - Sending
13 // Created 1/7/02
14 // Jason Boettcher
15
16 #include <vector>
17
18
19 #include "LieroX.h"
20 #include "CServer.h"
21 #include "Debug.h"
22 #include "StringUtils.h"
23 #include "CServerConnection.h"
24 #include "CServerNetEngine.h"
25 #include "Protocol.h"
26 #include "CWorm.h"
27 #include "Timer.h"
28 #include "Consts.h"
29 #include "CChannel.h"
30 #include "CMap.h"
31 #ifdef DEBUG
32 #include "MathLib.h"
33 #endif
34 #include "CGameMode.h"
35
36 // declare them only locally here as nobody really should use them explicitly
37 std::string OldLxCompatibleString(const std::string &Utf8String);
38
39
40 ///////////////////
41 // Send a client a packet
SendPacket(CBytestream * bs)42 void CServerNetEngine::SendPacket(CBytestream *bs)
43 {
44 if(cl->getStatus() == NET_DISCONNECTED || cl->getStatus() == NET_ZOMBIE)
45 return;
46
47 cl->getChannel()->AddReliablePacketToSend(*bs);
48 }
49
50 ///////////////////
51 // Send all the clients a packet
SendGlobalPacket(CBytestream * bs)52 void GameServer::SendGlobalPacket(CBytestream *bs)
53 {
54 // Assume reliable
55 CServerConnection *cl = cClients;
56 for(int c = 0; c < MAX_CLIENTS; c++, cl++) {
57 if(cl->getStatus() == NET_DISCONNECTED || cl->getStatus() == NET_ZOMBIE) continue;
58 if(cl->getNetEngine() == NULL) continue;
59 cl->getNetEngine()->SendPacket(bs);
60 }
61 }
62
SendGlobalPacket(CBytestream * bs,const Version & minVersion)63 void GameServer::SendGlobalPacket(CBytestream *bs, const Version& minVersion)
64 {
65 // Assume reliable
66 CServerConnection *cl = cClients;
67 for(int c = 0; c < MAX_CLIENTS; c++, cl++) {
68 if(cl->getStatus() == NET_DISCONNECTED || cl->getStatus() == NET_ZOMBIE) continue;
69 if(cl->getNetEngine() == NULL) continue;
70 if(cl->getClientVersion() < minVersion) continue;
71 cl->getNetEngine()->SendPacket(bs);
72 }
73 }
74
75 // TODO: This function is designed wrong. this->cl should be the receiver
76 // and the parameter should be the ready client.
SendClientReady(CServerConnection * receiver)77 void CServerNetEngine::SendClientReady(CServerConnection* receiver) {
78 // Let everyone know this client is ready to play
79
80 //if(server->serverChoosesWeapons()) {
81 // We don't necessarily have to send the weapons, we send them directly
82 // (e.g. in cloneWeaponsToAllWorms() or in PrepareWorm()).
83
84 // But the client doesn't set CWorm::bGameReady=true, so we still
85 // have to send this.
86 //}
87
88 if ((receiver && receiver->getClientVersion() >= OLXBetaVersion(8)) || cl->getNumWorms() <= 2) {
89 CBytestream bytes;
90 bytes.writeByte(S2C_CLREADY);
91 bytes.writeByte(cl->getNumWorms());
92 for (int i = 0;i < cl->getNumWorms();i++) {
93 if(!cl->getWorm(i) || !cl->getWorm(i)->isUsed()) {
94 errors << "SendClientReady: local worm nr " << i << " is wrong" << endl;
95 goto SendReadySeperatly; // we cannot send them together
96 }
97
98 // Send the weapon info here (also contains id)
99 cl->getWorm(i)->writeWeapons(&bytes);
100 }
101
102 if(receiver)
103 receiver->getNetEngine()->SendPacket(&bytes);
104 else
105 server->SendGlobalPacket(&bytes);
106
107 } else { // old client && numworms > 2
108
109 // Note: LX56 assumes that a client can have only 2 worms.
110
111 SendReadySeperatly:
112
113 for (int i = 0; i < cl->getNumWorms(); i++) {
114 if(!cl->getWorm(i) || !cl->getWorm(i)->isUsed()) {
115 errors << "SendClientReady: local worm nr " << i << " is wrong" << endl;
116 continue;
117 }
118
119 CBytestream bytes;
120 bytes.writeByte(S2C_CLREADY);
121 bytes.writeByte(1);
122 cl->getWorm(i)->writeWeapons(&bytes);
123 if(receiver)
124 receiver->getNetEngine()->SendPacket(&bytes);
125 else
126 server->SendGlobalPacket(&bytes);
127 }
128 }
129 }
130
WritePrepareGame(CBytestream * bs)131 void CServerNetEngine::WritePrepareGame(CBytestream *bs)
132 {
133 bs->writeByte(S2C_PREPAREGAME);
134 // TODO: if that is always false, why do we have a variable for it?
135 bs->writeBool(server->bRandomMap); // Always false as of now
136 if(!server->bRandomMap)
137 bs->writeString("levels/" + tLXOptions->tGameInfo.sMapFile);
138
139 // Game info
140 bs->writeInt(server->getGameMode()->GeneralGameType(),1);
141 bs->writeInt16((tLXOptions->tGameInfo.iLives < 0) ? WRM_UNLIM : tLXOptions->tGameInfo.iLives);
142 bs->writeInt16(tLXOptions->tGameInfo.iKillLimit);
143 bs->writeInt16((int)(server->getGameMode()->TimeLimit() / 60.0f));
144 bs->writeInt16(tLXOptions->tGameInfo.iLoadingTime);
145 bs->writeBool(tLXOptions->tGameInfo.bBonusesOn);
146 bs->writeBool(tLXOptions->tGameInfo.bShowBonusName);
147 if(server->getGameMode()->GeneralGameType() == GMT_TIME)
148 bs->writeInt16(tLXOptions->tGameInfo.iTagLimit);
149 bs->writeString(tLXOptions->tGameInfo.sModDir);
150
151 server->cWeaponRestrictions.sendList(bs, server->cGameScript.get());
152 }
153
SendPrepareGame()154 void CServerNetEngine::SendPrepareGame()
155 {
156 CBytestream bs;
157 WritePrepareGame(&bs);
158 SendPacket( &bs );
159 }
160
SendHideWorm(CWorm * worm,int forworm,bool show,bool immediate)161 void CServerNetEngine::SendHideWorm(CWorm *worm, int forworm, bool show, bool immediate)
162 {
163 // For old clients we move the worm out of the map and kill it
164
165 if(cl->getNumWorms() == 0 || cl->getWorm(0)->getID() != forworm)
166 // ignore it
167 return;
168
169 CBytestream bs;
170
171 // Hide the worm
172 if (!show) {
173 //
174 // Update the position
175 //
176
177 if (cServer->getState() == SVS_PLAYING) {
178 bs.writeByte(S2C_UPDATEWORMS);
179 bs.writeByte(1); // Worm count
180 bs.writeByte(worm->getID());
181 bs.write2Int12(-20, -20); // Position
182 bs.writeInt(0, 1); // Angle
183 bs.writeByte(0); // Flags
184 bs.writeByte(0); // Weapon
185
186 // Velocity
187 if(cl->getClientVersion() >= OLXBetaVersion(5)) {
188 bs.writeInt16(0);
189 bs.writeInt16(0);
190 }
191
192 // Send it reliably, this update is necessary
193 SendPacket(&bs);
194
195 //
196 // Kill
197 //
198 SendWormDied(worm);
199 }
200
201 // Show the worm
202 } else {
203 SendSpawnWorm(worm, worm->getPos());
204 }
205 }
206
WritePrepareGame(CBytestream * bs)207 void CServerNetEngineBeta7::WritePrepareGame(CBytestream *bs)
208 {
209 CServerNetEngine::WritePrepareGame(bs);
210
211 bs->writeFloat( tLXOptions->tGameInfo.features[FT_GameSpeed] );
212
213 // Set random weapons for spectating client, so it will skip weapon selection screen
214 // Never do this for local client, local client must know correct state of serverChoosesWeapons!
215 // TODO: it's hacky, don't have any ideas now how to make it nice
216 bool spectate = cl->getNumWorms() > 0 && !cl->isLocalClient();
217 if(spectate)
218 for(int i = 0; i < cl->getNumWorms(); ++i)
219 if(cl->getWorm(i) && !cl->getWorm(i)->isSpectating()) {
220 spectate = false;
221 break;
222 }
223
224 bs->writeBool( server->serverChoosesWeapons() || spectate );
225
226 // We send random weapons from server in GameServer::StartGame()
227 // TODO: Where are we doing that?
228 }
229
WritePrepareGame(CBytestream * bs)230 void CServerNetEngineBeta9::WritePrepareGame(CBytestream *bs)
231 {
232 CServerNetEngineBeta7::WritePrepareGame(bs);
233
234 bs->writeFloat(server->getGameMode()->TimeLimit() / 60.0f);
235 WriteFeatureSettings(bs);
236 bs->writeString(server->getGameMode()->Name());
237
238 // TODO: shouldn't this be somewhere in the clear function?
239 cDamageReport.clear(); // In case something left from prev game
240 }
241
242
243 ///////////////////
244 // Send all the clients a string of text
SendGlobalText(const std::string & text,int type)245 void GameServer::SendGlobalText(const std::string& text, int type) {
246 if(!cClients) {
247 errors << "GS:SendGlobalText: clients not initialised" << endl;
248 return;
249 }
250
251 CServerConnection *cl = cClients;
252 for(short c = 0; c < MAX_CLIENTS; c++, cl++) {
253 if(cl->getStatus() == NET_DISCONNECTED || cl->getStatus() == NET_ZOMBIE)
254 continue;
255
256 cl->getNetEngine()->SendText(text, type);
257 }
258 }
259
260
261 ///////////////////
262 // Send a client a string of text
SendText(const std::string & text,int type)263 void CServerNetEngine::SendText(const std::string& text, int type)
264 {
265 CBytestream bs;
266
267 std::string nohtml_text = StripHtmlTags(text);
268
269 std::vector<std::string> split = splitstring(nohtml_text, 63, server->iState == SVS_LOBBY ? 600 : 300, tLX->cFont);
270
271 for (std::vector<std::string>::const_iterator it = split.begin(); it != split.end(); it++) {
272 // Send it
273 bs.writeByte(S2C_TEXT);
274 bs.writeInt(type, 1);
275 bs.writeString(OldLxCompatibleString(*it));
276 }
277
278 SendPacket(&bs);
279 }
280
SendText(const std::string & text,int type)281 void CServerNetEngineBeta3::SendText(const std::string& text, int type)
282 {
283 // For beta 3 - beta 7 (no HTML support but unlimited length support)
284 CBytestream bs;
285
286 std::string nohtml_text = StripHtmlTags(text);
287
288 bs.writeByte(S2C_TEXT);
289 bs.writeInt(type, 1);
290 bs.writeString(OldLxCompatibleString(nohtml_text));
291
292 SendPacket(&bs);
293 }
294
SendHideWorm(CWorm * worm,int forworm,bool show,bool immediate)295 void CServerNetEngineBeta3::SendHideWorm(CWorm *worm, int forworm, bool show, bool immediate)
296 {
297 CServerNetEngine::SendHideWorm(worm, show, immediate); // Just the same as for old LX
298 }
299
SendText(const std::string & text,int type)300 void CServerNetEngineBeta8::SendText(const std::string& text, int type)
301 {
302 // For beta 8+ (HTML support)
303 CBytestream bs;
304
305 bs.writeByte(S2C_TEXT);
306 bs.writeInt(type, 1);
307 bs.writeString(OldLxCompatibleString(text));
308
309 SendPacket(&bs);
310 }
311
SendChatCommandCompletionSolution(const std::string & startStr,const std::string & solution)312 void CServerNetEngineBeta7::SendChatCommandCompletionSolution(const std::string& startStr, const std::string& solution) {
313 CBytestream bs;
314
315 bs.writeByte(S2C_CHATCMDCOMPLSOL);
316 bs.writeString(startStr);
317 bs.writeString(solution);
318
319 SendPacket(&bs);
320 }
321
SendChatCommandCompletionList(const std::string & startStr,const std::list<std::string> & solutions)322 void CServerNetEngineBeta7::SendChatCommandCompletionList(const std::string& startStr, const std::list<std::string>& solutions) {
323 // the solutions are for the last parameter of the command (or the command itself if no param is given)!
324 CBytestream bs;
325
326 bs.writeByte(S2C_CHATCMDCOMPLLST);
327 bs.writeString(startStr);
328 bs.writeInt((uint)solutions.size(), 4);
329 for(std::list<std::string>::const_iterator it = solutions.begin(); it != solutions.end(); ++it)
330 bs.writeString(*it);
331
332 SendPacket(&bs);
333 }
334
335 // send S2C_WORMSOUT
SendWormsOut(const std::list<byte> & ids)336 void CServerNetEngine::SendWormsOut(const std::list<byte>& ids) {
337 if(ids.size() == 0) return; // ignore
338
339 CBytestream bs;
340 bs.writeByte(S2C_WORMSOUT);
341 bs.writeByte(ids.size());
342
343 for(std::list<byte>::const_iterator it = ids.begin(); it != ids.end(); ++it)
344 bs.writeByte(*it);
345
346 SendPacket(&bs);
347 }
348
349 // WARNING: When using this, be sure that we also drop the specific client. This is
350 // needed because the local worm amount of the client is different from ours in
351 // the meanwhile and it would screw up the network.
SendWormsOut(const std::list<byte> & ids)352 void GameServer::SendWormsOut(const std::list<byte>& ids)
353 {
354 for(int c = 0; c < MAX_CLIENTS; c++) {
355 CServerConnection* cl = &cClients[c];
356 if (cl->getStatus() == NET_DISCONNECTED || cl->getStatus() == NET_ZOMBIE)
357 continue;
358
359 cl->getNetEngine()->SendWormsOut(ids);
360 }
361 }
362
363 ///////////////////
364 // Update all the client about the playing worms
365 // Returns true if we sent an update
SendUpdate()366 bool GameServer::SendUpdate()
367 {
368 // Delays for different net speeds
369 static const float shootDelay[] = {0.010f, 0.005f, 0.0f, 0.0f};
370
371 //
372 // Get the update packets for each worm that needs it and save them
373 //
374 std::list<CWorm *> worms_to_update;
375 CWorm *w = cWorms;
376 {
377 int i, j;
378 for (i = j = 0; j < iNumPlayers && i < MAX_WORMS; i++, w++) {
379 if (!w->isUsed())
380 continue;
381
382 // HINT: this can happen when a new client joins during game and has not selected weapons yet
383 if (w->getClient())
384 if (!w->getClient()->getGameReady())
385 continue;
386
387 ++j;
388
389 // w is an own server-side copy of the worm-structure,
390 // therefore we don't get problems by using the same checkPacketNeeded as client is also using
391 if (w->checkPacketNeeded()) {
392 worms_to_update.push_back(w);
393 }
394 }
395 }
396
397 size_t uploadAmount = 0;
398
399 {
400 int last = lastClientSendData;
401 for (int i = 0; i < MAX_CLIENTS; i++) {
402 CServerConnection* cl = &cClients[ (i + lastClientSendData + 1) % MAX_CLIENTS ]; // fairly distribute the packets over the clients
403
404 if (cl->getStatus() == NET_DISCONNECTED || cl->getStatus() == NET_ZOMBIE)
405 continue;
406
407 // HINT: happens when clients join during game and haven't selected their weapons yet
408 // HINT 2: this should be valid for beta 9+ though so the user can see others playing while selecting weapons
409 if (cl->getClientVersion() < OLXBetaVersion(0,58,1))
410 if (!cl->getGameReady())
411 continue;
412
413 // Check if we have gone over the bandwidth rating for the client
414 // If we have, just don't send a packet this frame
415 if( !checkBandwidth(cl) ) {
416 // We have gone over the bandwidth for the client, don't send a message this frame
417 //hints << "Over bandwidth for client " << i << endl;
418 continue;
419 }
420
421 if(!cl->isLocalClient()) {
422 // check our server bandwidth
423 static Rate<100,5000> blockRate; // only for debug output
424 static Rate<100,5000> blockRateAbs; // only for debug output
425 blockRateAbs.addData(tLX->currentTime, 1);
426 if(!checkUploadBandwidth(GetUpload() /* + uploadAmount */)) {
427 // we have gone over our own bandwidth for non-local clients
428 blockRate.addData(tLX->currentTime, 1);
429 static AbsTime lastMessageTime = tLX->currentTime;
430 if(tLX->currentTime - lastMessageTime > 30.0) {
431 notes << "we got over the max upload bandwidth" << endl;
432 notes << " current upload is " << GetUpload() << " bytes/sec (last 2 secs)" << endl;
433 notes << " current short upload is " << GetUpload(0.1f) << " bytes/sec (last 0.1 sec)" << endl;
434 notes << " upload amount of this frame is " << uploadAmount << " bytes" << endl;
435 if(blockRateAbs.getRate() > 0)
436 notes << " current block/update rate is " << float(100.0f * blockRate.getRate() / blockRateAbs.getRate()) << " % (last 5 secs)" << endl;
437 lastMessageTime = tLX->currentTime;
438 }
439 continue;
440 }
441 }
442
443 CBytestream update_packets; // Contains all the update packets except the one from this client
444
445 byte num_worms = 0;
446
447 // Send all the _other_ worms details
448 {
449 std::list<CWorm*>::const_iterator w_it = worms_to_update.begin();
450 for(; w_it != worms_to_update.end(); w_it++) {
451 CWorm* w = *w_it;
452
453 // Check if this client owns the worm
454 if(cl->OwnsWorm(w->getID()))
455 continue;
456
457 // Give the game mode a chance to override sending a packet (might reduce data sent)
458 if(!getGameMode()->NeedUpdate(cl, w))
459 continue;
460
461 ++num_worms;
462
463 CBytestream bytes;
464 bytes.writeByte(w->getID());
465 w->writePacket(&bytes, true, cl);
466
467 // Send out the update
468 update_packets.Append(&bytes);
469 }
470 }
471
472 CBytestream *bs = cl->getUnreliable();
473 size_t oldBsPos = bs->GetPos();
474
475 // Write the packets to the unreliable bytestream
476 bs->writeByte(S2C_UPDATEWORMS);
477 bs->writeByte(num_worms);
478 bs->Append(&update_packets);
479
480 // Write out a stat packet
481 {
482 bool need_send = false;
483 {
484 for (short j=0; j < cl->getNumWorms(); j++)
485 if (cl->getWorm(j)->checkStatePacketNeeded()) {
486 cl->getWorm(j)->updateStatCheckVariables();
487 need_send = true;
488 break;
489 }
490 }
491
492 // Only if necessary
493 if (need_send) {
494 bs->writeByte( S2C_UPDATESTATS );
495 bs->writeByte( cl->getNumWorms() );
496 for(short j = 0; j < cl->getNumWorms(); j++)
497 cl->getWorm(j)->writeStatUpdate(bs);
498 }
499 }
500
501 if(!cl->isLocalClient())
502 uploadAmount += (bs->GetPos() - oldBsPos);
503
504 // Send the shootlist (reliable)
505 CShootList *sh = cl->getShootList();
506 float delay = shootDelay[cl->getNetSpeed()];
507
508 if(tLX->currentTime - sh->getStartTime() > delay && sh->getNumShots() > 0) {
509 CBytestream shootBs;
510
511 // Send the shootlist
512 if( sh->writePacket(&shootBs, cl->getClientVersion()) )
513 sh->Clear();
514
515 if(!cl->isLocalClient())
516 uploadAmount += shootBs.GetLength();
517
518 cl->getChannel()->AddReliablePacketToSend(shootBs);
519 }
520
521 // TODO: that doesn't update uploadAmount
522 cl->getNetEngine()->SendReportDamage();
523
524 if(!cl->isLocalClient())
525 last = i;
526 }
527
528 lastClientSendData = last;
529 }
530
531 // All good
532 return true;
533 }
534
SendWeapons()535 void CServerNetEngine::SendWeapons()
536 {
537 CBytestream bs;
538
539 CWorm* w = server->cWorms;
540 for(int i = 0; i < MAX_WORMS; i++, w++) {
541 if(!w->isUsed())
542 continue;
543 if(!w->getWeaponsReady())
544 continue;
545 bs.writeByte(S2C_WORMWEAPONINFO);
546 w->writeWeapons(&bs);
547 }
548
549 SendPacket(&bs);
550 }
551
552 ///////////////////
553 // Send weapons to the client, or, if client is NULL, send to all clients
SendWeapons(CServerConnection * cl)554 void GameServer::SendWeapons(CServerConnection* cl)
555 {
556 if(cl)
557 cl->getNetEngine()->SendWeapons();
558 else
559 for(int c = 0; c < MAX_CLIENTS; c++)
560 cClients[c].getNetEngine()->SendWeapons();
561 }
562
563 // Send weapons of particular worm to everyone
SendWeapons(CWorm * w)564 void GameServer::SendWeapons(CWorm* w)
565 {
566 if(!w->isUsed())
567 return;
568 if(!w->getWeaponsReady())
569 return;
570
571 CBytestream bs;
572 bs.writeByte(S2C_WORMWEAPONINFO);
573 w->writeWeapons(&bs);
574 SendGlobalPacket(&bs);
575 }
576
577 ///////////////////
578 // Tells all clients that the worm is now tagged
SendWormTagged(CWorm * w)579 void GameServer::SendWormTagged(CWorm *w)
580 {
581 // Check
582 if (!w) {
583 errors << "A NULL worm passed to GameServer::SendWormTagged" << endl;
584 return;
585 }
586
587 // Build the packet
588 CBytestream bs;
589 bs.writeByte(S2C_TAGUPDATE);
590 bs.writeInt(w->getID(), 1);
591 bs.writeFloat((float)w->getTagTime().seconds());
592
593 // Send
594 SendGlobalPacket(&bs);
595 }
596
597 ///////////////////
598 // Check if we have gone over the clients bandwidth rating
599 // Returns true if we are under the bandwidth
checkBandwidth(CServerConnection * cl)600 bool GameServer::checkBandwidth(CServerConnection *cl)
601 {
602 // Don't bother checking if the client is on the same comp as the server
603 if( tLX->iGameType == GME_LOCAL )
604 return true;
605 if(cl->getNetSpeed() == 3) // local
606 return true;
607
608
609 // Modem, ISDN, LAN, local
610 // (Bytes per second)
611 const float Rates[4] = {2500, 7500, 10000, 50000};
612
613 // Are we over the clients bandwidth rate?
614 if(cl->getChannel()->getOutgoingRate() > Rates[cl->getNetSpeed()]) {
615
616 // Don't send the packet
617 return false;
618 }
619
620 // All ok
621 return true;
622 }
623
624 // true means we can send further data
checkUploadBandwidth(float fCurUploadRate)625 bool GameServer::checkUploadBandwidth(float fCurUploadRate) {
626 if( tLX->iGameType == GME_LOCAL )
627 return true;
628
629 float fMaxRate = getMaxUploadBandwidth();
630
631 {
632 static bool didShowMessageAlready = false;
633 if(!didShowMessageAlready)
634 notes << "using max upload rate " << (fMaxRate / 1024.0f) << " kb/sec" << endl;
635 didShowMessageAlready = true;
636 }
637
638 return fCurUploadRate < fMaxRate;
639 }
640
WriteFeatureSettings(CBytestream * bs)641 void CServerNetEngineBeta9::WriteFeatureSettings(CBytestream* bs) {
642 int ftC = featureArrayLen();
643 assert(ftC < 256*256);
644 CBytestream bs1;
645 int sendCount = 0;
646 foreach( Feature*, f, Array(featureArray,ftC) )
647 {
648 if( f->get()->group < GIG_GameModeSpecific_Start ||
649 f->get()->group == cServer->getGameMode()->getGameInfoGroupInOptions() )
650 {
651 if( tLXOptions->tGameInfo.features.hostGet(f->get()) == f->get()->unsetValue ) // Do not send a feature if it has default value = LX56 behavior
652 continue;
653 sendCount ++;
654 bs1.writeString( f->get()->name );
655 bs1.writeString( f->get()->humanReadableName );
656 bs1.writeVar( tLXOptions->tGameInfo.features.hostGet(f->get()) );
657 bs1.writeBool( tLXOptions->tGameInfo.features.olderClientsSupportSetting(f->get()) );
658 }
659 }
660 bs->writeInt(sendCount, 2);
661 bs->Append(&bs1);
662 }
663
SendUpdateLobbyGame()664 void CServerNetEngine::SendUpdateLobbyGame()
665 {
666 CBytestream bs;
667 WriteUpdateLobbyGame(&bs);
668 SendPacket(&bs);
669 }
670
WriteUpdateLobbyGame(CBytestream * bs)671 void CServerNetEngine::WriteUpdateLobbyGame(CBytestream *bs)
672 {
673 bs->writeByte(S2C_UPDATELOBBYGAME);
674 bs->writeByte(MAX(tLXOptions->tGameInfo.iMaxPlayers,server->getNumPlayers())); // This fixes the player disappearing in lobby
675 bs->writeString(tLXOptions->tGameInfo.sMapFile);
676 bs->writeString(tLXOptions->tGameInfo.sModName);
677 bs->writeString(tLXOptions->tGameInfo.sModDir);
678 bs->writeByte(server->getGameMode()->GeneralGameType());
679 bs->writeInt16((tLXOptions->tGameInfo.iLives < 0) ? WRM_UNLIM : tLXOptions->tGameInfo.iLives);
680 bs->writeInt16(tLXOptions->tGameInfo.iKillLimit);
681 bs->writeInt16(tLXOptions->tGameInfo.iLoadingTime);
682 bs->writeByte(tLXOptions->tGameInfo.bBonusesOn);
683 }
684
WriteUpdateLobbyGame(CBytestream * bs)685 void CServerNetEngineBeta7::WriteUpdateLobbyGame(CBytestream *bs)
686 {
687 CServerNetEngine::WriteUpdateLobbyGame(bs);
688 bs->writeFloat(tLXOptions->tGameInfo.features[FT_GameSpeed]);
689 bs->writeBool(tLXOptions->tGameInfo.bForceRandomWeapons);
690 bs->writeBool(tLXOptions->tGameInfo.bSameWeaponsAsHostWorm);
691 }
692
WriteUpdateLobbyGame(CBytestream * bs)693 void CServerNetEngineBeta9::WriteUpdateLobbyGame(CBytestream *bs)
694 {
695 CServerNetEngineBeta7::WriteUpdateLobbyGame(bs);
696 bs->writeFloat(server->getGameMode()->TimeLimit() / 60.0f);
697 CServerNetEngineBeta9::WriteFeatureSettings(bs);
698 bs->writeString(server->getGameMode()->Name());
699 }
700
701
702 ///////////////////
703 // Send an update of the game details in the lobby
UpdateGameLobby()704 void GameServer::UpdateGameLobby()
705 {
706 if(getGameMode() == NULL) {
707 errors << "Trying to play a non-existant gamemode" << endl;
708 tLXOptions->tGameInfo.gameMode = GameMode(GM_DEATHMATCH);
709 }
710
711 // Read map/mod name from map/mod file
712 tLXOptions->tGameInfo.sMapName = CMap::GetLevelName(tLXOptions->tGameInfo.sMapFile);
713 CGameScript::CheckFile(tLXOptions->tGameInfo.sModDir, tLXOptions->tGameInfo.sModName);
714
715 m_clientsNeedLobbyUpdate = true;
716 m_clientsNeedLobbyUpdateTime = tLX->currentTime;
717 }
718
SendUpdateLobby(CServerConnection * target)719 void CServerNetEngine::SendUpdateLobby(CServerConnection *target)
720 {
721 CBytestream bytestr;
722
723 CServerConnection *cl = server->cClients;
724 for(short c=0; c<MAX_CLIENTS; c++,cl++)
725 {
726 if( target )
727 {
728 cl = target;
729 if( c != 0 )
730 break;
731 }
732
733 if( cl->getStatus() == NET_DISCONNECTED || cl->getStatus() == NET_ZOMBIE )
734 continue;
735
736 // Set the client worms lobby ready state
737 bool ready = false;
738 for(short i=0; i < cl->getNumWorms(); i++) {
739 ready = cl->getWorm(i)->getLobbyReady();
740 }
741
742 // Let all the worms know about the new lobby state
743 if (cl->getNumWorms() <= 2) { // Have to do this way because of bug in LX 0.56
744 bytestr.writeByte(S2C_UPDATELOBBY);
745 bytestr.writeByte(cl->getNumWorms());
746 bytestr.writeByte(ready);
747 for(short i=0; i<cl->getNumWorms(); i++) {
748 bytestr.writeByte(cl->getWorm(i)->getID());
749 bytestr.writeByte(cl->getWorm(i)->getTeam());
750 }
751 } else {
752 int written = 0;
753 while (written < cl->getNumWorms()) {
754 bytestr.writeByte(S2C_UPDATELOBBY);
755 bytestr.writeByte(1);
756 bytestr.writeByte(ready);
757 bytestr.writeByte(cl->getWorm(written)->getID());
758 bytestr.writeByte(cl->getWorm(written)->getTeam());
759 written++;
760 }
761 }
762 }
763 SendPacket(&bytestr);
764 }
765
766 ////////////////////////
767 // Hide a worm at receiver's screen
SendHideWorm(CWorm * worm,int forworm,bool show,bool immediate)768 void CServerNetEngineBeta9::SendHideWorm(CWorm *worm, int forworm, bool show, bool immediate)
769 {
770 if (!worm) {
771 errors << "Invalid worm or receiver in SendHideWorm" << endl;
772 return;
773 }
774
775 if(forworm < 0 || forworm >= MAX_WORMS) {
776 errors << "Invalid forworm in SendHideWorm" << endl;
777 return;
778 }
779
780 CBytestream bs;
781 bs.writeByte(S2C_HIDEWORM);
782 bs.writeByte(worm->getID());
783 bs.writeByte(forworm);
784 bs.writeBool(!show); // True - hide, false - show
785 bs.writeBool(immediate); // True - immediate (no animation), false - animation
786 SendPacket(&bs);
787 }
788
789 ///////////////////
790 // Send updates for all the worm lobby states
SendWormLobbyUpdate(CServerConnection * receiver,CServerConnection * target)791 void GameServer::SendWormLobbyUpdate(CServerConnection* receiver, CServerConnection *target)
792 {
793 if(receiver)
794 receiver->getNetEngine()->SendUpdateLobby(target);
795 else
796 for( int i = 0; i < MAX_CLIENTS; i++ ) {
797 if(!cClients[i].isUsed()) continue;
798 cClients[i].getNetEngine()->SendUpdateLobby(target);
799 }
800 }
801
802
803 ///////////////////
804 // Tell all the clients that we're disconnecting
SendDisconnect()805 void GameServer::SendDisconnect()
806 {
807 CServerConnection *cl = cClients;
808 if (!cl) // means we already shut down
809 return;
810
811 CBytestream bs;
812 bs.writeByte(S2C_LEAVING);
813
814 for(short c=0; c<MAX_CLIENTS; c++,cl++) {
815 if(cl->getStatus() == NET_DISCONNECTED)
816 continue;
817
818 // Send it out-of-bounds 3 times to make sure all the clients received it
819 for(short i=0; i<3; i++)
820 cl->getChannel()->Transmit(&bs);
821 }
822 }
823
824
825 ///////////////////
826 // Update the worm name, skin, colour etc
SendUpdateWorm(CWorm * w)827 void CServerNetEngine::SendUpdateWorm( CWorm* w )
828 {
829 CBytestream bytestr;
830 bytestr.writeByte(S2C_WORMINFO);
831 bytestr.writeInt(w->getID(), 1);
832 w->writeInfo(&bytestr);
833 SendPacket(&bytestr);
834 }
835
836 // Version required to show question mark on damage popup number for older clients
SendUpdateWorm(CWorm * w)837 void CServerNetEngineBeta9::SendUpdateWorm( CWorm* w )
838 {
839 CBytestream bytestr;
840 bytestr.writeByte(S2C_WORMINFO);
841 bytestr.writeInt(w->getID(), 1);
842 w->writeInfo(&bytestr);
843 bytestr.writeString(w->getClient()->getClientVersion().asString());
844 SendPacket(&bytestr);
845 }
846
UpdateWorm(CWorm * w)847 void GameServer::UpdateWorm(CWorm* w)
848 {
849 for(int i = 0; i < MAX_CLIENTS; i++)
850 cClients[i].getNetEngine()->SendUpdateWorm(w);
851 }
852
853 ///////////////////
854 // Update the worm names, skins, colours etc
UpdateWorms()855 void GameServer::UpdateWorms()
856 {
857 CWorm* w = cWorms;
858 for(int i = 0; i < MAX_WORMS; i++, w++) {
859 if(!w->isUsed())
860 continue;
861 UpdateWorm(w);
862 }
863 }
864
865 #ifdef FUZZY_ERROR_TESTING
866 ///////////////
867 // Used for testing network stability
SendRandomPacket()868 void GameServer::SendRandomPacket()
869 {
870 CBytestream bs;
871 int random_length = GetRandomInt(50);
872 for (int i=0; i < random_length; i++)
873 bs.writeByte((uchar)GetRandomInt(255));
874
875 CServerConnection* cl = cClients;
876 for(short c=0; c<MAX_CLIENTS; c++,cl++) {
877 if(cl->getStatus() == NET_DISCONNECTED || cl->getStatus() == NET_ZOMBIE)
878 continue;
879
880 // don't send these random clients to the local client
881 if(cl->isLocalClient())
882 continue;
883
884 cl->SendPacket(&bs);
885 }
886 }
887 #endif
888
889 static const float pingCoeff = 1.5f; // Send another packet in minPing/pingCoeff milliseconds
890 static const int minPingDefault = 200;
891
SendFiles()892 int CServerNetEngineBeta5::SendFiles()
893 {
894 if(cl->getStatus() == NET_DISCONNECTED || cl->getStatus() == NET_ZOMBIE)
895 return 0;
896 int ping = 0;
897 // That's a bit floody algorithm, it can be optimized I think
898 if( cl->getUdpFileDownloader()->isSending() &&
899 ( cl->getChannel()->getBufferEmpty() ||
900 ( ! cl->getChannel()->getBufferFull() &&
901 cl->getChannel()->getPing() != 0 &&
902 tLX->currentTime - cl->getLastFileRequestPacketReceived() <= cl->getChannel()->getPing()/1000.0f / pingCoeff )) )
903 {
904 cl->setLastFileRequestPacketReceived( tLX->currentTime );
905 CBytestream bs;
906 bs.writeByte(S2C_SENDFILE);
907 cl->getUdpFileDownloader()->send(&bs);
908 cl->getNetEngine()->SendPacket( &bs );
909 ping = minPingDefault; // Default assumed ping
910 if( cl->getChannel()->getPing() != 0 )
911 ping = cl->getChannel()->getPing();
912 }
913 return ping;
914 }
915
SendFiles()916 void GameServer::SendFiles()
917 {
918 // To keep sending packets if no acknowledge received from client -
919 // process will pause for a long time otherwise, 'cause we're in GUI part
920 bool startTimer = false;
921 int minPing = minPingDefault;
922
923 for(int c = 0; c < MAX_CLIENTS; c++)
924 {
925 if(!cClients[c].getNetEngine()) continue;
926 int ping = cClients[c].getNetEngine()->SendFiles();
927 if( ping > 0 )
928 {
929 startTimer = true;
930 if( minPing > ping )
931 minPing = ping;
932 }
933 }
934
935 if( startTimer )
936 Timer( "GS::SendFiles", null, NULL, (Uint32)(minPing / pingCoeff), true ).startHeadless();
937 }
938
SendEmptyWeaponsOnRespawn(CWorm * Worm)939 void GameServer::SendEmptyWeaponsOnRespawn( CWorm * Worm )
940 {
941 CBytestream bs;
942 CServerConnection * cl = Worm->getClient();
943 if(cl == NULL) {
944 errors << "GS::SendEmptyWeaponsOnRespawn: client of worm " << Worm->getID() << ":" << Worm->getName() << " is unset" << endl;
945 DumpConnections();
946 return;
947 }
948 int i, j, curWeapon = Worm->getCurrentWeapon();
949 for( i = 0; i < 5; i++ )
950 {
951 Worm->getWeapon(i)->Charge=0;
952 Worm->getWeapon(i)->Reloading=1;
953 }
954 for( i=0; i<5; i++ )
955 {
956 if( i != curWeapon )
957 {
958 Worm->setCurrentWeapon(i);
959 bs.writeByte( S2C_UPDATESTATS );
960 bs.writeByte( cl->getNumWorms() );
961 for( j = 0; j < cl->getNumWorms(); j++ )
962 cl->getWorm(j)->writeStatUpdate(&bs);
963 }
964 }
965 Worm->setCurrentWeapon(curWeapon);
966 bs.writeByte( S2C_UPDATESTATS );
967 bs.writeByte( cl->getNumWorms() );
968 for( j = 0; j < cl->getNumWorms(); j++ )
969 cl->getWorm(j)->writeStatUpdate(&bs);
970 cl->getNetEngine()->SendPacket(&bs);
971 }
972
SendSpawnWorm(CWorm * Worm,CVec pos)973 void CServerNetEngine::SendSpawnWorm(CWorm *Worm, CVec pos)
974 {
975 server->getWorms()[Worm->getID()].setSpawnedOnce();
976
977 CBytestream bs;
978 bs.writeByte(S2C_SPAWNWORM);
979 bs.writeInt(Worm->getID(), 1);
980 bs.writeInt( (int)pos.x, 2);
981 bs.writeInt( (int)pos.y, 2);
982
983 SendPacket(&bs);
984 }
985
SendWormDied(CWorm * Worm)986 void CServerNetEngine::SendWormDied(CWorm *Worm)
987 {
988 CBytestream bs;
989 bs.writeByte(S2C_WORMDOWN);
990 bs.writeInt(Worm->getID(), 1);
991
992 SendPacket(&bs);
993 }
994
SendWormScore(CWorm * Worm)995 void CServerNetEngine::SendWormScore(CWorm *Worm)
996 {
997 CBytestream bs;
998 bs.writeByte(S2C_SCOREUPDATE);
999 bs.writeInt(Worm->getID(), 1);
1000 bs.writeInt16(Worm->getLives());
1001
1002 // Overflow hack
1003 if (Worm->getScore() > 255)
1004 bs.writeInt(255, 1);
1005 else
1006 bs.writeInt(Worm->getScore() > 0 ? Worm->getScore() : 0, 1);
1007
1008 SendPacket(&bs);
1009 }
1010
SendWormScore(CWorm * Worm)1011 void CServerNetEngineBeta9::SendWormScore(CWorm *Worm)
1012 {
1013 // If we have some damage reports in buffer send them first so clients won't sum up updated damage score and reported damage packet sent later
1014 SendReportDamage(true);
1015
1016 CBytestream bs;
1017 bs.writeByte(S2C_SCOREUPDATE);
1018 bs.writeInt(Worm->getID(), 1);
1019 if (tLXOptions->tGameInfo.iLives < 0 && Worm->getLives() == WRM_UNLIM &&
1020 cl->getClientVersion() >= OLXRcVersion(0,58,5) && cl->getClientVersion() < OLXBetaVersion(0,59,0)) {
1021 bs.writeInt16(Worm->getDeaths()); // Send deaths count for matches with unlimited lives, 0.58_rc5 and up, and I'm too lazy to intoduce another CServerNetEngine class
1022 } else {
1023 bs.writeInt16(Worm->getLives()); // Still int16 to allow WRM_OUT parsing (maybe I'm wrong though)
1024 }
1025 bs.writeInt(Worm->getScore(), 4); // Negative kills are allowed
1026 bs.writeFloat(Worm->getDamage());
1027
1028 SendPacket(&bs);
1029 }
1030
QueueReportDamage(int victim,float damage,int offender)1031 void CServerNetEngineBeta9::QueueReportDamage(int victim, float damage, int offender)
1032 {
1033 // Buffer up all damage and send it once per 0.1 second for LAN nettype, or once per 0.3 seconds for modem
1034 std::pair< int, int > dmgPair = std::make_pair( victim, offender );
1035 if( cDamageReport.count( dmgPair ) == 0 )
1036 cDamageReport[ dmgPair ] = 0;
1037 cDamageReport[ dmgPair ] += damage;
1038
1039 SendReportDamage();
1040 }
1041
SendReportDamage(bool flush)1042 void CServerNetEngineBeta9::SendReportDamage(bool flush)
1043 {
1044 if( ! flush && tLX->currentTime - fLastDamageReportSent < 0.1f * ( NST_LOCAL - cl->getNetSpeed() ) )
1045 return;
1046
1047 CBytestream bs;
1048
1049 for( std::map< std::pair< int, int >, float > :: iterator it = cDamageReport.begin();
1050 it != cDamageReport.end(); it++ )
1051 {
1052 int victim = it->first.first;
1053 int offender = it->first.second;
1054 float damage = it->second;
1055 bs.writeByte(S2C_REPORTDAMAGE);
1056 bs.writeByte(victim);
1057 bs.writeFloat(damage);
1058 bs.writeByte(offender);
1059 }
1060
1061 SendPacket(&bs);
1062
1063 cDamageReport.clear();
1064 fLastDamageReportSent = tLX->currentTime;
1065 }
1066
SendTeamScoreUpdate()1067 void CServerNetEngineBeta9::SendTeamScoreUpdate() {
1068 // only do this in a team game
1069 if(server->getGameMode()->GeneralGameType() != GMT_TEAMS) return;
1070
1071 CBytestream bs;
1072 bs.writeByte(S2C_TEAMSCOREUPDATE);
1073 bs.writeByte(server->getGameMode()->GameTeams());
1074 for(int i = 0; i < server->getGameMode()->GameTeams(); ++i) {
1075 bs.writeInt16(server->getGameMode()->TeamScores(i));
1076 }
1077 SendPacket(&bs);
1078 }
1079
SendTeamScoreUpdate()1080 void GameServer::SendTeamScoreUpdate() {
1081 for(int c = 0; c < MAX_CLIENTS; c++) {
1082 if(cClients[c].getStatus() == NET_CONNECTED)
1083 cClients[c].getNetEngine()->SendTeamScoreUpdate();
1084 }
1085 }
1086
SendWormProperties(bool onlyIfNotDef)1087 void CServerNetEngine::SendWormProperties(bool onlyIfNotDef) {
1088 CWorm* w = server->getWorms();
1089 for(int i = 0; i < MAX_WORMS; ++i, ++w) {
1090 if(!w->isUsed()) continue;
1091 if(onlyIfNotDef && isWormPropertyDefault(w)) continue;
1092
1093 SendWormProperties(w);
1094 }
1095 }
1096
SendWormProperties(CWorm * worm)1097 void CServerNetEngine::SendWormProperties(CWorm* worm) {
1098 if(!worm->isUsed()) {
1099 warnings << "SendWormProperties called for unused worm" << endl;
1100 return;
1101 }
1102
1103 if(isWormPropertyDefault(worm)) return; // ok, don't give a warning in that case
1104
1105 warnings << "SendWormProperties cannot be used for clients with <Beta9 (" << cl->debugName() << ")" << endl;
1106 }
1107
SendWormProperties(CWorm * worm)1108 void CServerNetEngineBeta9::SendWormProperties(CWorm* worm) {
1109 if(!worm->isUsed()) {
1110 warnings << "SendWormProperties called for unused worm" << endl;
1111 return;
1112 }
1113
1114 CBytestream bs;
1115 bs.writeByte(S2C_SETWORMPROPS);
1116 bs.writeByte(worm->getID());
1117 bs.writeBit(worm->canUseNinja());
1118 bs.writeBit(worm->canAirJump());
1119 bs.writeFloat(worm->speedFactor());
1120 bs.writeFloat(worm->damageFactor());
1121 bs.writeFloat(worm->shieldFactor());
1122 SendPacket(&bs);
1123 }
1124
isWormPropertyDefault(CWorm * worm)1125 bool CServerNetEngine::isWormPropertyDefault(CWorm* worm) {
1126 // defaults are set in CWorm::Prepare
1127 return
1128 worm->speedFactor() == 1.0f &&
1129 worm->damageFactor() == 1.0f &&
1130 worm->shieldFactor() == 1.0f &&
1131 worm->canUseNinja() &&
1132 !worm->canAirJump();
1133 }
1134
SendSelectWeapons(CWorm * worm)1135 void CServerNetEngine::SendSelectWeapons(CWorm* worm) {
1136 warnings << "SendSelectWeapons not supported for " << cl->debugName() << endl;
1137 }
1138
SendSelectWeapons(CWorm * worm)1139 void CServerNetEngineBeta9::SendSelectWeapons(CWorm* worm) {
1140 if(!worm->isUsed()) {
1141 warnings << "SendSelectWeapons called for unused worm" << endl;
1142 return;
1143 }
1144
1145 CBytestream bs;
1146 bs.writeByte(S2C_SELECTWEAPONS);
1147 bs.writeByte(worm->getID());
1148 SendPacket(&bs);
1149 }
1150
SetWormSpeedFactor(int wormID,float f)1151 void GameServer::SetWormSpeedFactor(int wormID, float f) {
1152 if(wormID < 0 || wormID >= MAX_WORMS || !cWorms[wormID].isUsed()) {
1153 warnings << "SetWormSpeedFactor: worm " << wormID << " is invalid" << endl;
1154 return;
1155 }
1156
1157 if(cWorms[wormID].speedFactor() == f) return; // nothing need to be changed
1158
1159 cWorms[wormID].setSpeedFactor(f);
1160
1161 for(int c = 0; c < MAX_CLIENTS; c++) {
1162 if(cClients[c].getStatus() == NET_CONNECTED)
1163 cClients[c].getNetEngine()->SendWormProperties(&cWorms[wormID]);
1164 }
1165 }
1166
SetWormCanUseNinja(int wormID,bool b)1167 void GameServer::SetWormCanUseNinja(int wormID, bool b) {
1168 if(wormID < 0 || wormID >= MAX_WORMS || !cWorms[wormID].isUsed()) {
1169 warnings << "SetWormCanUseNinja: worm " << wormID << " is invalid" << endl;
1170 return;
1171 }
1172
1173 if(cWorms[wormID].canUseNinja() == b) return; // nothing need to be changed
1174
1175 cWorms[wormID].setCanUseNinja(b);
1176
1177 for(int c = 0; c < MAX_CLIENTS; c++) {
1178 if(cClients[c].getStatus() == NET_CONNECTED)
1179 cClients[c].getNetEngine()->SendWormProperties(&cWorms[wormID]);
1180 }
1181 }
1182
SetWormDamageFactor(int wormID,float f)1183 void GameServer::SetWormDamageFactor(int wormID, float f) {
1184 if(wormID < 0 || wormID >= MAX_WORMS || !cWorms[wormID].isUsed()) {
1185 warnings << "SetWormDamageFactor: worm " << wormID << " is invalid" << endl;
1186 return;
1187 }
1188
1189 if(cWorms[wormID].damageFactor() == f) return; // nothing need to be changed
1190
1191 cWorms[wormID].setDamageFactor(f);
1192
1193 for(int c = 0; c < MAX_CLIENTS; c++) {
1194 if(cClients[c].getStatus() == NET_CONNECTED)
1195 cClients[c].getNetEngine()->SendWormProperties(&cWorms[wormID]);
1196 }
1197 }
1198
SetWormShieldFactor(int wormID,float f)1199 void GameServer::SetWormShieldFactor(int wormID, float f) {
1200 if(wormID < 0 || wormID >= MAX_WORMS || !cWorms[wormID].isUsed()) {
1201 warnings << "SetWormDamageFactor: worm " << wormID << " is invalid" << endl;
1202 return;
1203 }
1204
1205 if(cWorms[wormID].shieldFactor() == f) return; // nothing need to be changed
1206
1207 cWorms[wormID].setShieldFactor(f);
1208
1209 for(int c = 0; c < MAX_CLIENTS; c++) {
1210 if(cClients[c].getStatus() == NET_CONNECTED)
1211 cClients[c].getNetEngine()->SendWormProperties(&cWorms[wormID]);
1212 }
1213 }
1214
SetWormCanAirJump(int wormID,bool b)1215 void GameServer::SetWormCanAirJump(int wormID, bool b) {
1216 if(wormID < 0 || wormID >= MAX_WORMS || !cWorms[wormID].isUsed()) {
1217 warnings << "SetWormCanAirJump: worm " << wormID << " is invalid" << endl;
1218 return;
1219 }
1220
1221 if(cWorms[wormID].canAirJump() == b) return; // nothing need to be changed
1222
1223 cWorms[wormID].setCanAirJump(b);
1224
1225 for(int c = 0; c < MAX_CLIENTS; c++) {
1226 if(cClients[c].getStatus() == NET_CONNECTED)
1227 cClients[c].getNetEngine()->SendWormProperties(&cWorms[wormID]);
1228 }
1229 }
1230