server.cpp
1 //------------------------------------------------------------------------------
2 // server.cpp
3 //------------------------------------------------------------------------------
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 // 02110-1301 USA
19 //
20 //------------------------------------------------------------------------------
21 // Copyright (C) 2009 Braden "Blzut3" Obrzut <admin@maniacsvault.net>
22 //------------------------------------------------------------------------------
23 #include "server.h"
24 
25 #include "configuration/doomseekerconfig.h"
26 #include "configuration/queryspeed.h"
27 #include "log.h"
28 #include "pathfinder/pathfinder.h"
29 #include "pathfinder/wadpathfinder.h"
30 #include "plugins/engineplugin.h"
31 #include "serverapi/exefile.h"
32 #include "serverapi/gameclientrunner.h"
33 #include "serverapi/gameexeretriever.h"
34 #include "serverapi/message.h"
35 #include "serverapi/playerslist.h"
36 #include "serverapi/tooltips/tooltipgenerator.h"
37 #include "strings.hpp"
38 #include <cassert>
39 #include <QElapsedTimer>
40 #include <QUdpSocket>
41 
43 
44 DClass<Server>
45 {
46 public:
47  PrivData()
48  {
49  gameMode = GameMode::mkCooperative();
50  }
51 
58  bool bKnown;
59 
68  bool bPingIsSet;
69 
70  bool bSecure;
71  QString country;
73  unsigned int ping;
74  bool custom;
75  QList<DMFlagsSection> dmFlags;
76  QString email;
77  QElapsedTimer lastRefreshClock;
78  QString iwad;
79  bool lan;
80  bool locked;
81  bool lockedInGame;
82  QStringList mapList;
83  QString mapName;
84  unsigned short maxClients;
85  unsigned short maxPlayers;
86  QString motd;
87  QString name;
88  QElapsedTimer pingClock;
90  bool randomMapRotation;
91  Server::Response response;
92  QList<int> scores;
93  unsigned int scoreLimit;
94  unsigned short timeLeft;
95  unsigned short timeLimit;
96  unsigned char skill;
97  bool testingServer;
98  QString version;
99  QList<PWad> wads;
100  QString webSite;
101  QStringList additionalWebSites;
107  bool bIsRefreshing;
108  QHostAddress address;
109  QHostInfo host;
110  unsigned short port;
114  int triesLeft;
115  QWeakPointer<Server> self;
116 
117  QString (Server::*customDetails)();
118  QByteArray (Server::*createSendRequest)();
119  Server::Response (Server::*readRequest)(const QByteArray &);
120 };
121 
122 DPointeredNoCopy(Server)
123 
124 
125 
126 QString Server::teamNames[] =
127 {
128  "Blue",
129  "Red",
130  "Green",
131  "Gold"
132 };
133 
134 Server::Server(const QHostAddress &address, unsigned short port)
135  : QObject()
136 {
137  d->address = address;
138  d->port = port;
139  d->bIsRefreshing = false;
140  d->lan = false;
141  d->locked = false;
142  d->lockedInGame = false;
143  d->testingServer = false;
144  d->triesLeft = 0;
145  d->maxClients = 0;
146  d->maxPlayers = 0;
147  d->name = tr("<< ERROR >>");
148  d->response = RESPONSE_NO_RESPONSE_YET;
149  d->scoreLimit = 0;
150  d->timeLeft = 0;
151  d->timeLimit = 0;
152  d->ping = 999;
153  for (int i = 0; i < MAX_TEAMS; ++i)
154  {
155  d->scores << 0;
156  }
157  d->bSecure = false;
158  d->randomMapRotation = false;
159  d->skill = 3;
160  d->bKnown = false;
161  d->custom = false;
162  d->lastRefreshClock.invalidate();
163 
164  set_customDetails(&Server::customDetails_default);
165  set_createSendRequest(&Server::createSendRequest_default);
166  set_readRequest(&Server::readRequest_default);
167 
168  if (gConfig.doomseeker.bLookupHosts)
169  {
170  lookupHost();
171  }
172 }
173 
174 Server::~Server()
175 {
176  clearDMFlags();
177 }
178 
179 POLYMORPHIC_DEFINE(QString, Server, customDetails, (), ())
180 POLYMORPHIC_DEFINE(QByteArray, Server, createSendRequest, (), ())
181 POLYMORPHIC_DEFINE(Server::Response, Server, readRequest, (const QByteArray &data), (data))
182 
183 void Server::addPlayer(const Player &player)
184 {
185  d->players << player;
186 }
187 
188 void Server::addWad(const PWad &wad)
189 {
190  d->wads << wad;
191 }
192 
193 const QHostAddress &Server::address() const
194 {
195  return d->address;
196 }
197 
198 QString Server::addressWithPort() const
199 {
200  return QString("%1:%2").arg(address().toString()).arg(port());
201 }
202 
203 QStringList Server::allWadNames() const
204 {
205  QStringList result;
206  if (!d->iwad.trimmed().isEmpty())
207  {
208  result << d->iwad;
209  }
210  for (const PWad &wad : d->wads)
211  {
212  result << wad.name();
213  }
214  return result;
215 }
216 
217 bool Server::anyWadnameContains(const QString &text, Qt::CaseSensitivity cs) const
218 {
219  if (d->iwad.contains(text, cs))
220  {
221  return true;
222  }
223 
224  for (int j = 0; j < numWads(); ++j)
225  {
226  const PWad &pwad = wad(j);
227  if (pwad.name().contains(text, cs))
228  {
229  return true;
230  }
231  }
232  return false;
233 }
234 
235 void Server::clearDMFlags()
236 {
237  d->dmFlags.clear();
238 }
239 
240 QByteArray Server::createSendRequest_default()
241 {
242  assert(0 && "Server::createSendRequest() is not implemented");
243  return QByteArray();
244 }
245 
247 {
248  d->players.clear();
249 }
250 
252 {
253  d->wads.clear();
254 }
255 
257 {
258  auto f = new ExeFile();
259  // TODO: Figure out a way so that plugins don't have to reset following
260  // values if they don't change:
261  f->setProgramName(plugin()->data()->name);
262  f->setExeTypeName(tr("client"));
263  f->setConfigKey("BinaryPath");
264  return f;
265 }
266 
267 const QString &Server::country() const
268 {
269  return d->country;
270 }
271 
272 QString Server::customDetails_default()
273 {
274  return "";
275 }
276 
277 const QList<DMFlagsSection> &Server::dmFlags() const
278 {
279  return d->dmFlags;
280 }
281 
282 const QString &Server::email() const
283 {
284  return d->email;
285 }
286 
287 QString Server::engineName() const
288 {
289  if (plugin() != nullptr)
290  {
291  return plugin()->data()->name;
292  }
293  else
294  {
295  return tr("Undefined");
296  }
297 }
298 
300 {
301  return d->gameMode;
302 }
303 
305 {
306  return new GameClientRunner(self());
307 }
308 
309 const QString &Server::gameVersion() const
310 {
311  return d->version;
312 }
313 
314 QString Server::hostName(bool forceAddress) const
315 {
316  if (!forceAddress && gConfig.doomseeker.bLookupHosts &&
317  d->host.error() == QHostInfo::NoError && d->host.lookupId() != -1)
318  {
319  return QString("%1:%2").arg(d->host.hostName()).arg(port());
320  }
321  return QString("%1:%2").arg(address().toString()).arg(port());
322 }
323 
324 const QPixmap &Server::icon() const
325 {
326  return plugin()->icon();
327 }
328 
329 bool Server::isCustom() const
330 {
331  return d->custom;
332 }
333 
334 bool Server::isEmpty() const
335 {
336  return d->players.numClients() == 0;
337 }
338 
339 bool Server::isFull() const
340 {
341  return d->players.numClients() == maxClients();
342 }
343 
344 bool Server::isKnown() const
345 {
346  return d->bKnown;
347 }
348 
350 {
351  return isLocked() || isLockedInGame();
352 }
353 
354 bool Server::isLocked() const
355 {
356  return d->locked;
357 }
358 
360 {
361  return d->lockedInGame;
362 }
363 
365 {
366  return d->randomMapRotation;
367 }
368 
370 {
371  return d->bIsRefreshing;
372 }
373 
374 bool Server::isSecure() const
375 {
376  return d->bSecure;
377 }
378 
379 bool Server::isSpecial() const
380 {
381  return isLan() || isCustom();
382 }
383 
385 {
386  return d->testingServer;
387 }
388 
389 const QString &Server::iwad() const
390 {
391  return d->iwad;
392 }
393 
395 {
396  return d->response;
397 }
398 
400 {
401  QHostInfo::lookupHost(address().toString(), this,
402  SLOT(setHostName(QHostInfo)));
403 }
404 
405 const QStringList &Server::mapList() const
406 {
407  return d->mapList;
408 }
409 
410 const QString &Server::map() const
411 {
412  return d->mapName;
413 }
414 
415 unsigned short Server::maxClients() const
416 {
417  return d->maxClients;
418 }
419 
420 unsigned short Server::maxPlayers() const
421 {
422  return d->maxPlayers;
423 }
424 
425 QList<GameCVar> Server::modifiers() const
426 {
427  return QList<GameCVar>();
428 }
429 
430 const QString &Server::motd() const
431 {
432  return d->motd;
433 }
434 
435 const QString &Server::name() const
436 {
437  return d->name;
438 }
439 
441 {
442  int returnValue = numTotalSlots() - d->players.numClients();
443  return (returnValue < 0) ? 0 : returnValue;
444 }
445 
447 {
448  int returnValue = d->maxPlayers - d->players.numClients();
449  return (returnValue < 0) ? 0 : returnValue;
450 }
451 
453 {
454  int returnValue = numFreeClientSlots() - numFreeJoinSlots();
455  return (returnValue < 0) ? 0 : returnValue;
456 }
457 
458 unsigned int Server::ping() const
459 {
460  return d->ping;
461 }
462 
463 const Player &Server::player(int index) const
464 {
465  return d->players[index];
466 }
467 
469 {
470  return d->players;
471 }
472 
473 unsigned short Server::port() const
474 {
475  return d->port;
476 }
477 
479 {
480  return readRequest(data);
481 }
482 
483 Server::Response Server::readRequest_default(const QByteArray &data)
484 {
485  Q_UNUSED(data);
486  assert(0 && "Server::readRequest(const QByteArray&) is not implemented.");
487  return RESPONSE_BAD;
488 }
489 
491 {
492  d->bIsRefreshing = true;
493 
494  emit begunRefreshing(ServerPtr(self()));
495  d->triesLeft = gConfig.doomseeker.querySpeed().attemptsPerServer;
496  // Limit the maximum number of tries
497  d->triesLeft = qMin(d->triesLeft, QuerySpeed::MAX_ATTEMPTS_PER_SERVER);
498  // Sanity.
499  d->triesLeft = qMax(d->triesLeft, 1);
500 }
501 
503 {
504  d->lastRefreshClock.start();
505  setResponse(response);
506  if (!d->bPingIsSet)
507  {
508  // Set the current ping, if plugin didn't do so already.
509  d->ping = d->pingClock.elapsed();
510  d->bPingIsSet = true;
511  }
512  d->bIsRefreshing = false;
513  d->iwad = d->iwad.toLower();
514  emit updated(self(), response);
515 }
516 
517 unsigned int Server::score(int team) const
518 {
519  return d->scores[team];
520 }
521 
522 unsigned int Server::scoreLimit() const
523 {
524  return d->scoreLimit;
525 }
526 
527 const QList<int> &Server::scores() const
528 {
529  return d->scores;
530 }
531 
533 {
534  return d->scores;
535 }
536 
537 QWeakPointer<Server> Server::self() const
538 {
539  return d->self;
540 }
541 
542 bool Server::sendRefreshQuery(QUdpSocket *socket)
543 {
544  if (d->triesLeft <= 0)
545  {
547  return false;
548  }
549  --d->triesLeft;
550 
551  QByteArray request = createSendRequest();
552  if (request.isEmpty())
553  {
555  return false;
556  }
557 
558  d->bPingIsSet = false;
559  d->pingClock.start();
560 
561  socket->writeDatagram(request, address(), port());
562 
563  return true;
564 }
565 
566 void Server::setCountry(const QString &country)
567 {
568  d->country = country.trimmed().toUpper();
569 }
570 
571 void Server::setCustom(bool custom)
572 {
573  d->custom = custom;
574 }
575 
576 void Server::setDmFlags(const QList<DMFlagsSection> &dmFlags)
577 {
578  d->dmFlags = dmFlags;
579 }
580 
581 void Server::setEmail(const QString &email)
582 {
583  d->email = email;
584 }
585 
587 {
588  d->gameMode = gameMode;
589 }
590 
591 void Server::setGameVersion(const QString &version)
592 {
593  d->version = version;
594 }
595 
596 void Server::setHostName(QHostInfo host)
597 {
598  d->host = host;
599  if (!d->bIsRefreshing)
600  emit updated(self(), lastResponse());
601 }
602 
603 void Server::setIwad(const QString &iwad)
604 {
605  d->iwad = iwad;
606 }
607 
608 void Server::setLocked(bool locked)
609 {
610  d->locked = locked;
611 }
612 
613 void Server::setLockedInGame(bool locked)
614 {
615  d->lockedInGame = locked;
616 }
617 
618 void Server::setMapList(const QStringList &mapList)
619 {
620  d->mapList = mapList;
621 }
622 
623 void Server::setMap(const QString &mapName)
624 {
625  d->mapName = mapName;
626 }
627 
628 void Server::setMaxClients(unsigned short maxClients)
629 {
630  d->maxClients = maxClients;
631 }
632 
633 void Server::setMaxPlayers(unsigned short maxPlayers)
634 {
635  d->maxPlayers = maxPlayers;
636 }
637 
638 void Server::setMotd(const QString &motd)
639 {
640  d->motd = motd;
641 }
642 
643 void Server::setName(const QString &serverName)
644 {
645  d->name = serverName;
646  // Don't let servers occupy more than one row with newline chars.
647  d->name.replace('\n', ' ').replace('\r', ' ');
648 }
649 
650 void Server::setPing(unsigned int ping)
651 {
652  d->ping = ping;
653 }
654 
656 {
657  d->bPingIsSet = b;
658 }
659 
660 void Server::setPort(unsigned short i)
661 {
662  d->port = i;
663 }
664 
665 void Server::setRandomMapRotation(bool randomMapRotation)
666 {
667  d->randomMapRotation = randomMapRotation;
668 }
669 
670 void Server::setResponse(Response response)
671 {
672  d->response = response;
673  if (response == RESPONSE_GOOD)
674  {
675  d->bKnown = true;
676  }
677  else if (response == RESPONSE_BAD || response == RESPONSE_BANNED || response == RESPONSE_TIMEOUT)
678  {
679  d->bKnown = false;
680  }
681 }
682 
683 void Server::setScores(const QList<int> &scores)
684 {
685  d->scores = scores;
686 }
687 
688 void Server::setScoreLimit(unsigned int serverScoreLimit)
689 {
690  d->scoreLimit = serverScoreLimit;
691 }
692 
693 void Server::setSecure(bool bSecure)
694 {
695  d->bSecure = bSecure;
696 }
697 
698 void Server::setSelf(const QWeakPointer<Server> &self)
699 {
700  d->self = self;
701 }
702 
703 void Server::setTestingServer(bool b)
704 {
705  d->testingServer = b;
706 }
707 
708 void Server::setTimeLeft(unsigned short serverTimeLeft)
709 {
710  d->timeLeft = serverTimeLeft;
711 }
712 
713 void Server::setTimeLimit(unsigned short serverTimeLimit)
714 {
715  d->timeLimit = serverTimeLimit;
716 }
717 
718 void Server::setSkill(unsigned char skill)
719 {
720  d->skill = skill;
721 }
722 
723 void Server::setWads(const QList<PWad> &wads)
724 {
725  d->wads = wads;
726 }
727 
728 void Server::setWebSite(const QString &webSite)
729 {
730  d->webSite = webSite;
731 }
732 
733 void Server::setAdditionalWebSites(const QStringList &list)
734 {
735  d->additionalWebSites = list;
736 }
737 
738 QRgb Server::teamColor(int team) const
739 {
740  switch (team)
741  {
742  case Player::TEAM_BLUE:
743  return qRgb(0, 0, 255);
744  case Player::TEAM_RED:
745  return qRgb(255, 0, 0);
746  case Player::TEAM_GREEN:
747  return qRgb(0, 255, 0);
748  case Player::TEAM_GOLD:
749  return qRgb(255, 255, 0);
750  default: break;
751  }
752  return qRgb(0, 255, 0);
753 }
754 
755 QString Server::teamName(int team) const
756 {
757  return team < MAX_TEAMS && team >= 0 ? teamNames[team] : "";
758 }
759 
760 unsigned short Server::timeLeft() const
761 {
762  return d->timeLeft;
763 }
764 
765 unsigned short Server::timeLimit() const
766 {
767  return d->timeLimit;
768 }
769 
771 {
772  if (d->lastRefreshClock.isValid())
773  {
774  return d->lastRefreshClock.elapsed();
775  }
776  else
777  {
778  return -1;
779  }
780 }
781 
783 {
784  return new TooltipGenerator(self());
785 }
786 
787 unsigned char Server::skill() const
788 {
789  return d->skill;
790 }
791 
792 const PWad &Server::wad(int index) const
793 {
794  return wads()[index];
795 }
796 
798 {
799  PathFinder pathFinder;
800  #ifdef Q_OS_WIN32
801  {
802  GameExeRetriever exeRetriever(*plugin()->gameExe());
803  Message msg;
804  pathFinder.addPrioritySearchDir(exeRetriever.pathToOfflineExe(msg));
805  }
806  #endif
807  if (isTestingServer())
808  {
809  QScopedPointer<ExeFile> exeFile(clientExe());
810  Message msg;
811  pathFinder.addPrioritySearchDir(exeFile->pathToExe(msg));
812  }
813  return pathFinder;
814 }
815 
816 const QList<PWad> &Server::wads() const
817 {
818  return d->wads;
819 }
820 
821 const QString &Server::webSite() const
822 {
823  return d->webSite;
824 }
825 
826 const QStringList &Server::additionalWebSites() const
827 {
828  return d->additionalWebSites;
829 }
830 
831 QStringList Server::allWebSites() const
832 {
833  QStringList sites = additionalWebSites();
834  sites.prepend(webSite());
835  return sites;
836 }
837 
838 bool Server::isLan() const
839 {
840  return d->lan;
841 }
842 
843 void Server::setLan(bool b)
844 {
845  d->lan = b;
846 }