Implemented basic chat functionality

This commit is contained in:
2020-07-19 01:01:04 +02:00
parent fcadff65e2
commit 76b485d9dc
11 changed files with 118 additions and 78 deletions

View File

@@ -1,25 +1,36 @@
#include "chathelper.h" #include "chathelper.h"
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonArray>
QJsonObject normalTextObj(const QString &text) QJsonObject ChatPart::toObject() const
{ {
return QJsonObject { { "text", text } }; QJsonObject obj;
} if (text.has_value())
obj["text"] = *text;
QJsonObject boldTextObj(const QString &text) if (bold.has_value())
{ obj["bold"] = *bold;
QJsonObject obj = normalTextObj(text); if (italic.has_value())
obj["bold"] = true; obj["italic"] = *italic;
if (underlined.has_value())
obj["underlined"] = *underlined;
if (strikethrough.has_value())
obj["strikethrough"] = *strikethrough;
if (obfuscated.has_value())
obj["obfuscated"] = *obfuscated;
if (color.has_value())
obj["color"] = *color;
if (!extra.empty())
{
QJsonArray arr;
for (const auto &sub : extra)
arr.append(sub.toObject());
obj["extra"] = arr;
}
return obj; return obj;
} }
QString normalText(const QString &text) QString ChatPart::toString() const
{ {
return QJsonDocument{normalTextObj(text)}.toJson(); return QJsonDocument{toObject()}.toJson(QJsonDocument::Compact);
}
QString boldText(const QString &text)
{
return QJsonDocument{boldTextObj(text)}.toJson();
} }

View File

@@ -1,10 +1,21 @@
#pragma once #pragma once
#include <optional>
#include <vector>
#include <QString> #include <QString>
#include <QJsonObject> #include <QJsonObject>
QJsonObject normalTextObj(const QString &text); struct ChatPart {
QJsonObject boldTextObj(const QString &text); std::optional<QString> text;
std::optional<bool> bold;
std::optional<bool> italic;
std::optional<bool> underlined;
std::optional<bool> strikethrough;
std::optional<bool> obfuscated;
std::optional<QString> color;
std::vector<ChatPart> extra;
QString normalText(const QString &text); QJsonObject toObject() const;
QString boldText(const QString &text); QString toString() const;
};

View File

@@ -8,8 +8,6 @@
ClosedClient::ClosedClient(std::unique_ptr<QTcpSocket> &&socket, Server &server) : ClosedClient::ClosedClient(std::unique_ptr<QTcpSocket> &&socket, Server &server) :
QObject{&server}, m_socket{std::move(socket)}, m_server{server}, m_dataStream{m_socket.get()} QObject{&server}, m_socket{std::move(socket)}, m_server{server}, m_dataStream{m_socket.get()}
{ {
m_socket->setParent(this);
connect(m_socket.get(), &QIODevice::readyRead, this, &ClosedClient::readyRead); connect(m_socket.get(), &QIODevice::readyRead, this, &ClosedClient::readyRead);
connect(m_socket.get(), &QAbstractSocket::disconnected, this, &QObject::deleteLater); connect(m_socket.get(), &QAbstractSocket::disconnected, this, &QObject::deleteLater);

View File

@@ -9,8 +9,6 @@
HandshakingClient::HandshakingClient(std::unique_ptr<QTcpSocket> &&socket, Server &server) : HandshakingClient::HandshakingClient(std::unique_ptr<QTcpSocket> &&socket, Server &server) :
QObject{&server}, m_socket{std::move(socket)}, m_server{server}, m_dataStream{m_socket.get()} QObject{&server}, m_socket{std::move(socket)}, m_server{server}, m_dataStream{m_socket.get()}
{ {
m_socket->setParent(this);
connect(m_socket.get(), &QIODevice::readyRead, this, &HandshakingClient::readyRead); connect(m_socket.get(), &QIODevice::readyRead, this, &HandshakingClient::readyRead);
connect(m_socket.get(), &QAbstractSocket::disconnected, this, &QObject::deleteLater); connect(m_socket.get(), &QAbstractSocket::disconnected, this, &QObject::deleteLater);
} }

View File

@@ -8,8 +8,6 @@
LoginClient::LoginClient(std::unique_ptr<QTcpSocket> &&socket, Server &server) : LoginClient::LoginClient(std::unique_ptr<QTcpSocket> &&socket, Server &server) :
QObject{&server}, m_socket{std::move(socket)}, m_server{server}, m_dataStream{m_socket.get()} QObject{&server}, m_socket{std::move(socket)}, m_server{server}, m_dataStream{m_socket.get()}
{ {
m_socket->setParent(this);
connect(m_socket.get(), &QIODevice::readyRead, this, &LoginClient::readyRead); connect(m_socket.get(), &QIODevice::readyRead, this, &LoginClient::readyRead);
connect(m_socket.get(), &QAbstractSocket::disconnected, this, &QObject::deleteLater); connect(m_socket.get(), &QAbstractSocket::disconnected, this, &QObject::deleteLater);
} }
@@ -67,7 +65,7 @@ void LoginClient::readPacket(packets::login::serverbound::PacketType type, const
packet.serialize(m_dataStream); packet.serialize(m_dataStream);
} }
m_dataStream.setDevice({}); m_dataStream.setDevice({});
new PlayClient{std::move(m_socket), m_server}; new PlayClient{name, std::move(m_socket), m_server};
deleteLater(); deleteLater();
break; break;
} }

View File

@@ -61,6 +61,11 @@ void packets::login::clientbound::LoginSuccess::serialize(McDataStream &stream)
stream.writeRawData(buffer.constData(), buffer.length()); stream.writeRawData(buffer.constData(), buffer.length());
} }
packets::play::serverbound::ChatMessage::ChatMessage(McDataStream &stream)
{
message = stream.readVar<QString>();
}
packets::play::serverbound::ClientSettings::ClientSettings(McDataStream &stream) packets::play::serverbound::ClientSettings::ClientSettings(McDataStream &stream)
{ {
locale = stream.readVar<QString>(); locale = stream.readVar<QString>();

View File

@@ -116,9 +116,15 @@ namespace packets {
namespace serverbound { namespace serverbound {
Q_NAMESPACE Q_NAMESPACE
enum class PacketType { ClientSettings = 0x04, InteractEntity = 0x0E, PluginMessage = 0x0A }; enum class PacketType { ChatMessage = 0x02, ClientSettings = 0x04, InteractEntity = 0x0E, PluginMessage = 0x0A };
Q_ENUM_NS(PacketType) Q_ENUM_NS(PacketType)
struct ChatMessage {
ChatMessage(McDataStream &stream);
QString message;
};
struct ClientSettings { struct ClientSettings {
ClientSettings(McDataStream &stream); ClientSettings(McDataStream &stream);

View File

@@ -8,12 +8,12 @@
#include "chathelper.h" #include "chathelper.h"
#include "chunkhelper.h" #include "chunkhelper.h"
PlayClient::PlayClient(std::unique_ptr<QTcpSocket> &&socket, Server &server) : PlayClient::PlayClient(const QString &name, std::unique_ptr<QTcpSocket> &&socket, Server &server) :
QObject{&server}, m_socket{std::move(socket)}, m_server{server}, m_dataStream{m_socket.get()} QObject{&server}, m_name{name}, m_socket{std::move(socket)}, m_server{server}, m_dataStream{m_socket.get()}
{ {
m_server.add(*this); m_server.add(*this);
m_socket->setParent(this); emit distributeChatMessage(ChatPart{.extra={ChatPart{.text=m_name,.color="red"},ChatPart{.text=tr(" joined the game!")}}}.toString());
connect(m_socket.get(), &QIODevice::readyRead, this, &PlayClient::readyRead); connect(m_socket.get(), &QIODevice::readyRead, this, &PlayClient::readyRead);
connect(m_socket.get(), &QAbstractSocket::disconnected, this, &QObject::deleteLater); connect(m_socket.get(), &QAbstractSocket::disconnected, this, &QObject::deleteLater);
@@ -52,22 +52,22 @@ PlayClient::PlayClient(std::unique_ptr<QTcpSocket> &&socket, Server &server) :
packet.fieldOfViewModifier = 60.; packet.fieldOfViewModifier = 60.;
packet.serialize(m_dataStream); packet.serialize(m_dataStream);
} }
{ // {
packets::play::clientbound::ChunkData packet; // packets::play::clientbound::ChunkData packet;
packet.fullChunk = true; // packet.fullChunk = true;
packet.primaryBitMask = 0b11000; // send only 2 chunks // packet.primaryBitMask = 0b11000; // send only 2 chunks
packet.data += createChunkSection(); // packet.data += createChunkSection();
packet.data += createChunkSection(); // packet.data += createChunkSection();
packet.data += createBiomes(); // we are in Overworld // packet.data += createBiomes(); // we are in Overworld
for (int y = -3; y <= 3; y++) // for (int y = -3; y <= 3; y++)
for (int x = -3; x <= 3; x++) // for (int x = -3; x <= 3; x++)
{ // {
packet.chunkX = x; // packet.chunkX = x;
packet.chunkY = y; // packet.chunkY = y;
packet.serialize(m_dataStream); // packet.serialize(m_dataStream);
} // }
} // }
// { // {
// packets::play::clientbound::SpawnMob packet; // packets::play::clientbound::SpawnMob packet;
// packet.entityId = 2; // packet.entityId = 2;
@@ -88,10 +88,14 @@ PlayClient::PlayClient(std::unique_ptr<QTcpSocket> &&socket, Server &server) :
PlayClient::~PlayClient() PlayClient::~PlayClient()
{ {
qDebug() << "called" << m_socket->readAll();
emit distributeChatMessage(ChatPart{.extra={ChatPart{.text=m_name,.color="red"},ChatPart{.text=tr(" left the game!")}}}.toString());
m_server.remove(*this); m_server.remove(*this);
} }
void PlayClient::keepAlive() void PlayClient::tick()
{ {
const auto now = QDateTime::currentDateTime(); const auto now = QDateTime::currentDateTime();
if (!m_lastKeepAliveSent.isValid() || m_lastKeepAliveSent.secsTo(now) >= 1) if (!m_lastKeepAliveSent.isValid() || m_lastKeepAliveSent.secsTo(now) >= 1)
@@ -101,24 +105,13 @@ void PlayClient::keepAlive()
packet.serialize(m_dataStream); packet.serialize(m_dataStream);
m_lastKeepAliveSent = now; m_lastKeepAliveSent = now;
} }
}
void PlayClient::sendChatMessage()
{
const auto now = QDateTime::currentDateTime();
if (!m_lastChatMessage.isValid() || m_lastChatMessage.secsTo(now) >= 2) if (!m_lastChatMessage.isValid() || m_lastChatMessage.secsTo(now) >= 2)
{ {
packets::play::clientbound::ChatMessage packet; sendChatMessage(ChatPart{.text=tr("Time chaged to %0").arg(QTime::currentTime().toString())}.toString());
packet.jsonData = normalText(tr("Time chaged to %0").arg(QTime::currentTime().toString()));
packet.position = packets::play::clientbound::ChatMessage::Chat;
packet.serialize(m_dataStream);
m_lastChatMessage = now; m_lastChatMessage = now;
} }
}
void PlayClient::randomizeStats()
{
const auto now = QDateTime::currentDateTime();
if (!m_lastStats.isValid() || m_lastStats.msecsTo(now) >= 500) if (!m_lastStats.isValid() || m_lastStats.msecsTo(now) >= 500)
{ {
{ {
@@ -138,15 +131,11 @@ void PlayClient::randomizeStats()
m_lastStats = now; m_lastStats = now;
} }
}
void PlayClient::trialDisconnect()
{
const auto now = QDateTime::currentDateTime();
if (m_connectedSince.secsTo(now) >= 30) if (m_connectedSince.secsTo(now) >= 30)
{ {
packets::play::clientbound::Disconnect packet; packets::play::clientbound::Disconnect packet;
packet.reason = boldText("Your trial has ended."); packet.reason = ChatPart{.text="Your trial has ended.",.bold=true}.toString();
packet.serialize(m_dataStream); packet.serialize(m_dataStream);
m_socket->flush(); m_socket->flush();
m_dataStream.setDevice({}); m_dataStream.setDevice({});
@@ -155,6 +144,14 @@ void PlayClient::trialDisconnect()
} }
} }
void PlayClient::sendChatMessage(const QString &message)
{
packets::play::clientbound::ChatMessage packet;
packet.jsonData = message;
packet.position = packets::play::clientbound::ChatMessage::Chat;
packet.serialize(m_dataStream);
}
void PlayClient::readyRead() void PlayClient::readyRead()
{ {
while(m_socket && m_socket->bytesAvailable()) while(m_socket && m_socket->bytesAvailable())
@@ -189,7 +186,20 @@ void PlayClient::readPacket(packets::play::serverbound::PacketType type, const Q
switch(type) switch(type)
{ {
using namespace packets::play; using namespace packets::play;
case serverbound::PacketType::ClientSettings: using namespace serverbound;
case PacketType::ChatMessage:
{
qDebug() << type;
{
serverbound::ChatMessage packet{dataStream};
qDebug() << "message" << packet.message;
const auto formatted = ChatPart{.extra={ChatPart{.text=m_name,.color="red"},ChatPart{.text=": " + packet.message}}}.toString();
emit distributeChatMessage(formatted);
sendChatMessage(formatted);
}
break;
}
case PacketType::ClientSettings:
{ {
qDebug() << type; qDebug() << type;
{ {

View File

@@ -15,13 +15,16 @@ class PlayClient : public QObject
Q_OBJECT Q_OBJECT
public: public:
explicit PlayClient(std::unique_ptr<QTcpSocket> &&socket, Server &server); explicit PlayClient(const QString &name, std::unique_ptr<QTcpSocket> &&socket, Server &server);
~PlayClient() override; ~PlayClient() override;
void keepAlive(); void tick();
void sendChatMessage();
void randomizeStats(); signals:
void trialDisconnect(); void distributeChatMessage(const QString &message);
public slots:
void sendChatMessage(const QString &message);
private slots: private slots:
void readyRead(); void readyRead();
@@ -29,6 +32,7 @@ private slots:
private: private:
void readPacket(packets::play::serverbound::PacketType type, const QByteArray &buffer); void readPacket(packets::play::serverbound::PacketType type, const QByteArray &buffer);
const QString m_name;
std::unique_ptr<QTcpSocket> m_socket; std::unique_ptr<QTcpSocket> m_socket;
Server &m_server; Server &m_server;

View File

@@ -23,6 +23,12 @@ Server::Server(QObject *parent) :
void Server::add(PlayClient &playClient) void Server::add(PlayClient &playClient)
{ {
for (auto otherClient : m_playClients)
{
connect(&playClient, &PlayClient::distributeChatMessage, otherClient, &PlayClient::sendChatMessage);
connect(otherClient, &PlayClient::distributeChatMessage, &playClient, &PlayClient::sendChatMessage);
}
m_playClients.insert(&playClient); m_playClients.insert(&playClient);
} }
@@ -34,12 +40,7 @@ void Server::remove(PlayClient &playClient)
void Server::timeout() void Server::timeout()
{ {
for (auto client : m_playClients) for (auto client : m_playClients)
{ client->tick();
client->keepAlive();
client->sendChatMessage();
client->randomizeStats();
client->trialDisconnect();
}
} }
void Server::newConnection() void Server::newConnection()

File diff suppressed because one or more lines are too long