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