Proof of concept for new net game client
authorunc0rr
Sat, 11 May 2019 22:39:55 +0200
changeset 14915 a3ad06ac390e
parent 14914 9ab78e08a34c
child 14916 8750530bf7e7
Proof of concept for new net game client
qmlfrontend/CMakeLists.txt
qmlfrontend/Page1.qml
qmlfrontend/Page1Form.ui.qml
qmlfrontend/main.cpp
qmlfrontend/net_session.cpp
qmlfrontend/net_session.h
--- a/qmlfrontend/CMakeLists.txt	Sat May 11 20:43:12 2019 +0200
+++ b/qmlfrontend/CMakeLists.txt	Sat May 11 22:39:55 2019 +0200
@@ -17,6 +17,7 @@
     "preview_image_provider.cpp" "preview_image_provider.h"
     "engine_interface.h"
     "preview_acceptor.cpp" "preview_acceptor.h"
+    "net_session.cpp" "net_session.h"
     )
 
-target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick)
+target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Network Qt5::Quick)
--- a/qmlfrontend/Page1.qml	Sat May 11 20:43:12 2019 +0200
+++ b/qmlfrontend/Page1.qml	Sat May 11 22:39:55 2019 +0200
@@ -5,6 +5,7 @@
   focus: true
 
   property HWEngine hwEngine
+  property NetSession netSession
 
   Component {
     id: hwEngineComponent
@@ -17,6 +18,15 @@
     }
   }
 
+  Component {
+    id: netSessionComponent
+
+    NetSession {
+      nickname: "test0272"
+      url: "hwnet://gameserver.hedgewars.org:46632"
+    }
+  }
+
   Component.onCompleted: {
     hwEngine = hwEngineComponent.createObject()
   }
@@ -40,6 +50,10 @@
       hwEngine.getPreview()
     }
   }
+  netButton.onClicked: {
+    netSession = netSessionComponent.createObject()
+    netSession.open()
+  }
 
   Keys.onPressed: {
     if (event.key === Qt.Key_Enter)
--- a/qmlfrontend/Page1Form.ui.qml	Sat May 11 20:43:12 2019 +0200
+++ b/qmlfrontend/Page1Form.ui.qml	Sat May 11 22:39:55 2019 +0200
@@ -5,11 +5,13 @@
 import Hedgewars.Engine 1.0
 
 Item {
+  id: element
   property alias button1: button1
   property alias previewImage: previewImage
   property alias gameButton: gameButton
   width: 1024
   height: 800
+  property alias netButton: netButton
   property alias tickButton: tickButton
   property alias gameView: gameView
 
@@ -80,4 +82,13 @@
       Layout.fillHeight: true
     }
   }
+
+  Button {
+    id: netButton
+    text: qsTr("Net")
+    anchors.bottom: parent.bottom
+    anchors.bottomMargin: 8
+    anchors.left: parent.left
+    anchors.leftMargin: 8
+  }
 }
--- a/qmlfrontend/main.cpp	Sat May 11 20:43:12 2019 +0200
+++ b/qmlfrontend/main.cpp	Sat May 11 22:39:55 2019 +0200
@@ -6,6 +6,7 @@
 #include "engine_interface.h"
 #include "game_view.h"
 #include "hwengine.h"
+#include "net_session.h"
 #include "preview_acceptor.h"
 
 static QObject* previewacceptor_singletontype_provider(
@@ -32,6 +33,7 @@
       previewacceptor_singletontype_provider);
   qmlRegisterType<HWEngine>("Hedgewars.Engine", 1, 0, "HWEngine");
   qmlRegisterType<GameView>("Hedgewars.Engine", 1, 0, "GameView");
+  qmlRegisterType<NetSession>("Hedgewars.Engine", 1, 0, "NetSession");
   qmlRegisterUncreatableType<EngineInstance>("Hedgewars.Engine", 1, 0,
                                              "EngineInstance",
                                              "Create by HWEngine run methods");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/net_session.cpp	Sat May 11 22:39:55 2019 +0200
@@ -0,0 +1,137 @@
+#include "net_session.h"
+
+NetSession::NetSession(QObject *parent) : QObject(parent) {}
+
+QUrl NetSession::url() const { return m_url; }
+
+QAbstractSocket::SocketState NetSession::state() const {
+  if (m_socket)
+    return m_socket->state();
+  else
+    return QAbstractSocket::UnconnectedState;
+}
+
+void NetSession::open() {
+  m_socket.reset(new QTcpSocket());
+
+  connect(m_socket.data(), &QAbstractSocket::stateChanged, this,
+          &NetSession::stateChanged);
+  connect(m_socket.data(), &QTcpSocket::readyRead, this,
+          &NetSession::onReadyRead);
+
+  m_socket->connectToHost(m_url.host(),
+                          static_cast<quint16>(m_url.port(46631)));
+}
+
+QString NetSession::nickname() const { return m_nickname; }
+
+QString NetSession::password() const { return m_password; }
+
+NetSession::SessionState NetSession::sessionState() const {
+  return m_sessionState;
+}
+
+void NetSession::setUrl(const QUrl &url) {
+  if (m_url == url) return;
+
+  m_url = url;
+  emit urlChanged(m_url);
+}
+
+void NetSession::setNickname(const QString &nickname) {
+  if (m_nickname == nickname) return;
+
+  m_nickname = nickname;
+  emit nicknameChanged(m_nickname);
+}
+
+void NetSession::setPassword(const QString &password) {
+  if (m_password == password) return;
+
+  m_password = password;
+  emit passwordChanged(m_password);
+}
+
+void NetSession::close() {
+  if (!m_socket.isNull()) {
+    m_socket->disconnectFromHost();
+    m_socket.clear();
+
+    setSessionState(NotConnected);
+  }
+}
+
+void NetSession::onReadyRead() {
+  while (m_socket->canReadLine()) {
+    auto line = QString::fromUtf8(m_socket->readLine().simplified());
+
+    if (line.isEmpty()) {
+      parseNetMessage(m_buffer);
+      m_buffer.clear();
+    } else {
+      m_buffer.append(line);
+    }
+  }
+}
+
+void NetSession::parseNetMessage(const QStringList &message) {
+  if (message.isEmpty()) {
+    qWarning() << "Empty net message received";
+    return;
+  }
+
+  qDebug() << "[SERVER]" << message;
+
+  using Handler = std::function<void(NetSession *, const QStringList &)>;
+  static QMap<QString, Handler> commandsMap{
+      {"CONNECTED", &NetSession::handleConnected},
+      {"PING", &NetSession::handlePing},
+      {"BYE", &NetSession::handleBye}};
+
+  auto handler =
+      commandsMap.value(message[0], &NetSession::handleUnknownCommand);
+
+  handler(this, message.mid(1));
+}
+
+void NetSession::handleConnected(const QStringList &parameters) {
+  setSessionState(Login);
+}
+
+void NetSession::handlePing(const QStringList &parameters) {
+  send("PONG", parameters);
+}
+
+void NetSession::handleBye(const QStringList &parameters) { close(); }
+
+void NetSession::handleUnknownCommand(const QStringList &parameters) {
+  Q_UNUSED(parameters);
+
+  qWarning() << "Command is not recognized";
+}
+
+void NetSession::send(const QString &message) { send(QStringList(message)); }
+
+void NetSession::send(const QString &message, const QString &param) {
+  send(QStringList{message, param});
+}
+
+void NetSession::send(const QString &message, const QStringList &parameters) {
+  send(QStringList(message) + parameters);
+}
+
+void NetSession::send(const QStringList &message) {
+  Q_ASSERT(!m_socket.isNull());
+
+  qDebug() << "[CLIENT]" << message;
+
+  m_socket->write(message.join('\n').toUtf8() + "\n\n");
+}
+
+void NetSession::setSessionState(NetSession::SessionState sessionState) {
+  if (m_sessionState == sessionState) return;
+
+  m_sessionState = sessionState;
+
+  emit sessionStateChanged(sessionState);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/qmlfrontend/net_session.h	Sat May 11 22:39:55 2019 +0200
@@ -0,0 +1,70 @@
+#ifndef NET_SESSION_H
+#define NET_SESSION_H
+
+#include <QSharedPointer>
+#include <QSslSocket>
+#include <QStringList>
+#include <QUrl>
+
+class NetSession : public QObject {
+  Q_OBJECT
+
+  // clang-format off
+  Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
+  Q_PROPERTY(QAbstractSocket::SocketState state READ state NOTIFY stateChanged)
+  Q_PROPERTY(QString nickname READ nickname WRITE setNickname NOTIFY nicknameChanged)
+  Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
+  Q_PROPERTY(SessionState sessionState READ sessionState NOTIFY sessionStateChanged)
+  // clang-format on
+
+ public:
+  enum SessionState { NotConnected, Login, Lobby, Room, Game };
+  Q_ENUMS(SessionState)
+
+  explicit NetSession(QObject *parent = nullptr);
+
+  QUrl url() const;
+  QAbstractSocket::SocketState state() const;
+
+  Q_INVOKABLE void open();
+
+  QString nickname() const;
+  QString password() const;
+  SessionState sessionState() const;
+
+ public slots:
+  void setUrl(const QUrl &url);
+  void setNickname(const QString &nickname);
+  void setPassword(const QString &password);
+  void close();
+
+ signals:
+  void urlChanged(const QUrl url);
+  void stateChanged(QAbstractSocket::SocketState state);
+  void nicknameChanged(const QString &nickname);
+  void passwordChanged(const QString &password);
+  void sessionStateChanged(SessionState sessionState);
+
+ private slots:
+  void onReadyRead();
+  void parseNetMessage(const QStringList &message);
+  void handleConnected(const QStringList &parameters);
+  void handlePing(const QStringList &parameters);
+  void handleBye(const QStringList &parameters);
+  void handleUnknownCommand(const QStringList &parameters);
+  void send(const QString &message);
+  void send(const QString &message, const QString &param);
+  void send(const QString &message, const QStringList &parameters);
+  void send(const QStringList &message);
+  void setSessionState(SessionState sessionState);
+
+ private:
+  QUrl m_url;
+  QSharedPointer<QTcpSocket> m_socket;
+  QString m_nickname;
+  QString m_password;
+  QStringList m_buffer;
+  SessionState m_sessionState;
+};
+
+#endif  // NET_SESSION_H