lobby_moc.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. /*
  2. * lobby_moc.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "main.h"
  12. #include "lobby_moc.h"
  13. #include "ui_lobby_moc.h"
  14. #include "lobbyroomrequest_moc.h"
  15. #include "../mainwindow_moc.h"
  16. #include "../modManager/cmodlist.h"
  17. #include "../../lib/CConfigHandler.h"
  18. Lobby::Lobby(QWidget *parent) :
  19. QWidget(parent),
  20. ui(new Ui::Lobby)
  21. {
  22. ui->setupUi(this);
  23. connect(&socketLobby, SIGNAL(text(QString)), this, SLOT(sysMessage(QString)));
  24. connect(&socketLobby, SIGNAL(receive(QString)), this, SLOT(dispatchMessage(QString)));
  25. connect(&socketLobby, SIGNAL(disconnect()), this, SLOT(onDisconnected()));
  26. QString hostString("%1:%2");
  27. hostString = hostString.arg(QString::fromStdString(settings["launcher"]["lobbyUrl"].String()));
  28. hostString = hostString.arg(settings["launcher"]["lobbyPort"].Integer());
  29. ui->serverEdit->setText(hostString);
  30. ui->userEdit->setText(QString::fromStdString(settings["launcher"]["lobbyUsername"].String()));
  31. ui->kickButton->setVisible(false);
  32. }
  33. Lobby::~Lobby()
  34. {
  35. delete ui;
  36. }
  37. QMap<QString, QString> Lobby::buildModsMap() const
  38. {
  39. QMap<QString, QString> result;
  40. QObject * mainWindow = qApp->activeWindow();
  41. if(!mainWindow)
  42. mainWindow = parent();
  43. if(!mainWindow)
  44. return result; //probably something is really wrong here
  45. while(mainWindow->parent())
  46. mainWindow = mainWindow->parent();
  47. const auto & modlist = qobject_cast<MainWindow*>(mainWindow)->getModList();
  48. for(auto & modname : modlist.getModList())
  49. {
  50. auto mod = modlist.getMod(modname);
  51. if(mod.isEnabled())
  52. {
  53. result[modname] = mod.getValue("version").toString();
  54. }
  55. }
  56. return result;
  57. }
  58. bool Lobby::isModAvailable(const QString & modName, const QString & modVersion) const
  59. {
  60. QObject * mainWindow = qApp->activeWindow();
  61. while(mainWindow->parent())
  62. mainWindow = mainWindow->parent();
  63. const auto & modlist = qobject_cast<MainWindow*>(mainWindow)->getModList();
  64. if(!modlist.hasMod(modName))
  65. return false;
  66. auto mod = modlist.getMod(modName);
  67. return (mod.isInstalled () || mod.isAvailable()) && (mod.getValue("version") == modVersion);
  68. }
  69. void Lobby::serverCommand(const ServerCommand & command) try
  70. {
  71. //initialize variables outside of switch block
  72. const QString statusPlaceholder("%1 %2\n");
  73. const auto & args = command.arguments;
  74. int amount, tagPoint;
  75. QString joinStr;
  76. switch(command.command)
  77. {
  78. case SRVERROR:
  79. protocolAssert(args.size());
  80. chatMessage("System error", args[0], true);
  81. if(authentificationStatus == AuthStatus::AUTH_NONE)
  82. authentificationStatus = AuthStatus::AUTH_ERROR;
  83. break;
  84. case CREATED:
  85. protocolAssert(args.size());
  86. hostSession = args[0];
  87. session = args[0];
  88. sysMessage("new session started");
  89. break;
  90. case SESSIONS:
  91. protocolAssert(args.size());
  92. amount = args[0].toInt();
  93. protocolAssert(amount * 4 == (args.size() - 1));
  94. ui->sessionsTable->setRowCount(amount);
  95. tagPoint = 1;
  96. for(int i = 0; i < amount; ++i)
  97. {
  98. QTableWidgetItem * sessionNameItem = new QTableWidgetItem(args[tagPoint++]);
  99. ui->sessionsTable->setItem(i, 0, sessionNameItem);
  100. int playersJoined = args[tagPoint++].toInt();
  101. int playersTotal = args[tagPoint++].toInt();
  102. QTableWidgetItem * sessionPlayerItem = new QTableWidgetItem(QString("%1/%2").arg(playersJoined).arg(playersTotal));
  103. ui->sessionsTable->setItem(i, 1, sessionPlayerItem);
  104. QTableWidgetItem * sessionProtectedItem = new QTableWidgetItem();
  105. bool isPrivate = (args[tagPoint++] == "True");
  106. sessionProtectedItem->setData(Qt::UserRole, isPrivate);
  107. if(isPrivate)
  108. sessionProtectedItem->setIcon(QIcon("icons:room-private.png"));
  109. ui->sessionsTable->setItem(i, 2, sessionProtectedItem);
  110. }
  111. break;
  112. case JOINED:
  113. case KICKED:
  114. protocolAssert(args.size() == 2);
  115. joinStr = (command.command == JOINED ? "%1 joined to the session %2" : "%1 left session %2");
  116. if(args[1] == username)
  117. {
  118. ui->buttonReady->setText("Ready");
  119. ui->chat->clear(); //cleanup the chat
  120. sysMessage(joinStr.arg("you", args[0]));
  121. session = args[0];
  122. ui->stackedWidget->setCurrentWidget(command.command == JOINED ? ui->roomPage : ui->sessionsPage);
  123. }
  124. else
  125. {
  126. sysMessage(joinStr.arg(args[1], args[0]));
  127. }
  128. break;
  129. case MODS: {
  130. protocolAssert(args.size() > 0);
  131. amount = args[0].toInt();
  132. protocolAssert(amount * 2 == (args.size() - 1));
  133. tagPoint = 1;
  134. ui->modsList->clear();
  135. auto enabledMods = buildModsMap();
  136. for(int i = 0; i < amount; ++i, tagPoint += 2)
  137. {
  138. if(enabledMods.contains(args[tagPoint]))
  139. {
  140. if(enabledMods[args[tagPoint]] == args[tagPoint + 1])
  141. enabledMods.remove(args[tagPoint]);
  142. else
  143. ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-update.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1])));
  144. }
  145. else if(isModAvailable(args[tagPoint], args[tagPoint + 1]))
  146. ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-enabled.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1])));
  147. else
  148. ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-delete.png"), QString("%1 (v%2)").arg(args[tagPoint], args[tagPoint + 1])));
  149. }
  150. for(auto & remainMod : enabledMods.keys())
  151. {
  152. ui->modsList->addItem(new QListWidgetItem(QIcon("icons:mod-disabled.png"), QString("%1 (v%2)").arg(remainMod, enabledMods[remainMod])));
  153. }
  154. if(!ui->modsList->count())
  155. ui->modsList->addItem("No issues detected");
  156. break;
  157. }
  158. case CLIENTMODS: {
  159. protocolAssert(args.size() > 1);
  160. amount = args[1].toInt();
  161. protocolAssert(amount * 2 == (args.size() - 2));
  162. tagPoint = 2;
  163. break;
  164. }
  165. case STATUS:
  166. protocolAssert(args.size() > 0);
  167. amount = args[0].toInt();
  168. protocolAssert(amount * 2 == (args.size() - 1));
  169. tagPoint = 1;
  170. ui->playersList->clear();
  171. for(int i = 0; i < amount; ++i, tagPoint += 2)
  172. {
  173. if(args[tagPoint + 1] == "True")
  174. ui->playersList->addItem(new QListWidgetItem(QIcon("icons:mod-enabled.png"), args[tagPoint]));
  175. else
  176. ui->playersList->addItem(new QListWidgetItem(QIcon("icons:mod-disabled.png"), args[tagPoint]));
  177. if(args[tagPoint] == username)
  178. if(args[tagPoint + 1] == "True")
  179. ui->buttonReady->setText("Not ready");
  180. else
  181. ui->buttonReady->setText("Ready");
  182. }
  183. break;
  184. case START: {
  185. protocolAssert(args.size() == 1);
  186. //actually start game
  187. gameArgs << "--lobby";
  188. gameArgs << "--lobby-address" << serverUrl;
  189. gameArgs << "--lobby-port" << QString::number(serverPort);
  190. gameArgs << "--uuid" << args[0];
  191. startGame(gameArgs);
  192. break;
  193. }
  194. case HOST: {
  195. protocolAssert(args.size() == 2);
  196. gameArgs << "--lobby-host";
  197. gameArgs << "--lobby-uuid" << args[0];
  198. gameArgs << "--lobby-connections" << args[1];
  199. break;
  200. }
  201. case CHAT: {
  202. protocolAssert(args.size() > 1);
  203. QString msg;
  204. for(int i = 1; i < args.size(); ++i)
  205. msg += args[i];
  206. chatMessage(args[0], msg);
  207. break;
  208. }
  209. default:
  210. sysMessage("Unknown server command");
  211. }
  212. if(authentificationStatus == AuthStatus::AUTH_ERROR)
  213. {
  214. socketLobby.disconnectServer();
  215. }
  216. else
  217. {
  218. authentificationStatus = AuthStatus::AUTH_OK;
  219. ui->newButton->setEnabled(true);
  220. }
  221. }
  222. catch(const ProtocolError & e)
  223. {
  224. chatMessage("System error", e.what(), true);
  225. }
  226. void Lobby::dispatchMessage(QString txt) try
  227. {
  228. if(txt.isEmpty())
  229. return;
  230. QStringList parseTags = txt.split(":>>");
  231. protocolAssert(parseTags.size() > 1 && parseTags[0].isEmpty() && !parseTags[1].isEmpty());
  232. for(int c = 1; c < parseTags.size(); ++c)
  233. {
  234. QStringList parseArgs = parseTags[c].split(":");
  235. protocolAssert(parseArgs.size() > 1);
  236. auto ctype = ProtocolStrings.key(parseArgs[0]);
  237. parseArgs.pop_front();
  238. ServerCommand cmd(ctype, parseArgs);
  239. serverCommand(cmd);
  240. }
  241. }
  242. catch(const ProtocolError & e)
  243. {
  244. chatMessage("System error", e.what(), true);
  245. }
  246. void Lobby::onDisconnected()
  247. {
  248. authentificationStatus = AuthStatus::AUTH_NONE;
  249. ui->stackedWidget->setCurrentWidget(ui->sessionsPage);
  250. ui->connectButton->setChecked(false);
  251. ui->serverEdit->setEnabled(true);
  252. ui->userEdit->setEnabled(true);
  253. ui->newButton->setEnabled(false);
  254. ui->joinButton->setEnabled(false);
  255. ui->sessionsTable->clear();
  256. }
  257. void Lobby::chatMessage(QString title, QString body, bool isSystem)
  258. {
  259. QTextCharFormat fmtBody, fmtTitle;
  260. fmtTitle.setFontWeight(QFont::Bold);
  261. if(isSystem)
  262. fmtBody.setFontWeight(QFont::DemiBold);
  263. QTextCursor curs(ui->chat->document());
  264. curs.movePosition(QTextCursor::End);
  265. curs.insertText(title + ": ", fmtTitle);
  266. curs.insertText(body + "\n", fmtBody);
  267. ui->chat->ensureCursorVisible();
  268. }
  269. void Lobby::sysMessage(QString body)
  270. {
  271. chatMessage("System", body, true);
  272. }
  273. void Lobby::protocolAssert(bool expr)
  274. {
  275. if(!expr)
  276. throw ProtocolError("Protocol error");
  277. }
  278. void Lobby::on_messageEdit_returnPressed()
  279. {
  280. socketLobby.send(ProtocolStrings[MESSAGE].arg(ui->messageEdit->text()));
  281. ui->messageEdit->clear();
  282. }
  283. void Lobby::on_connectButton_toggled(bool checked)
  284. {
  285. if(checked)
  286. {
  287. authentificationStatus = AuthStatus::AUTH_NONE;
  288. username = ui->userEdit->text();
  289. const int connectionTimeout = settings["launcher"]["connectionTimeout"].Integer();
  290. auto serverStrings = ui->serverEdit->text().split(":");
  291. if(serverStrings.size() != 2)
  292. {
  293. QMessageBox::critical(this, "Connection error", "Server address must have the format URL:port");
  294. return;
  295. }
  296. serverUrl = serverStrings[0];
  297. serverPort = serverStrings[1].toInt();
  298. Settings node = settings.write["launcher"];
  299. node["lobbyUrl"].String() = serverUrl.toStdString();
  300. node["lobbyPort"].Integer() = serverPort;
  301. node["lobbyUsername"].String() = username.toStdString();
  302. ui->serverEdit->setEnabled(false);
  303. ui->userEdit->setEnabled(false);
  304. sysMessage("Connecting to " + serverUrl + ":" + QString::number(serverPort));
  305. //show text immediately
  306. ui->chat->repaint();
  307. qApp->processEvents();
  308. socketLobby.connectServer(serverUrl, serverPort, username, connectionTimeout);
  309. }
  310. else
  311. {
  312. ui->serverEdit->setEnabled(true);
  313. ui->userEdit->setEnabled(true);
  314. socketLobby.disconnectServer();
  315. }
  316. }
  317. void Lobby::on_newButton_clicked()
  318. {
  319. new LobbyRoomRequest(socketLobby, "", buildModsMap(), this);
  320. }
  321. void Lobby::on_joinButton_clicked()
  322. {
  323. auto * item = ui->sessionsTable->item(ui->sessionsTable->currentRow(), 0);
  324. if(item)
  325. {
  326. auto isPrivate = ui->sessionsTable->item(ui->sessionsTable->currentRow(), 2)->data(Qt::UserRole).toBool();
  327. if(isPrivate)
  328. new LobbyRoomRequest(socketLobby, item->text(), buildModsMap(), this);
  329. else
  330. socketLobby.requestJoinSession(item->text(), "", buildModsMap());
  331. }
  332. }
  333. void Lobby::on_buttonLeave_clicked()
  334. {
  335. socketLobby.requestLeaveSession(session);
  336. }
  337. void Lobby::on_buttonReady_clicked()
  338. {
  339. if(ui->buttonReady->text() == "Ready")
  340. ui->buttonReady->setText("Not ready");
  341. else
  342. ui->buttonReady->setText("Ready");
  343. socketLobby.requestReadySession(session);
  344. }
  345. void Lobby::on_sessionsTable_itemSelectionChanged()
  346. {
  347. auto selection = ui->sessionsTable->selectedItems();
  348. ui->joinButton->setEnabled(!selection.empty());
  349. }
  350. void Lobby::on_playersList_currentRowChanged(int currentRow)
  351. {
  352. ui->kickButton->setVisible(ui->playersList->currentItem()
  353. && currentRow > 0
  354. && ui->playersList->currentItem()->text() != username);
  355. }
  356. void Lobby::on_kickButton_clicked()
  357. {
  358. if(ui->playersList->currentItem() && ui->playersList->currentItem()->text() != username)
  359. socketLobby.send(ProtocolStrings[KICK].arg(ui->playersList->currentItem()->text()));
  360. }