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