Client list support (#365)

* Boilerplate structure for playerlist

* Change id, character and area to private with get/set

* WIP push

* Restructured the project entirely

* Implemented player list

* Build against project-akashi.pro

* Updated coverage location

* Copy gcov files from the proper path

* Update coverage to copy files

* Coverage update.

* Update main.yml

* Disabled coverage for the time being

* Reworked player list implementation, ...

* Reworked player list implementation
  * No longer rely on JSON
* Introduced moderation packets: ban, kick
  * A kick is a duration of 0
  * A ban is a duration between -1 (permanent) and anything above 0
* Packet ZZ has been modified and now include a client id field for client-specific reports
* Ban duration is now explicit.

* Tweak to ban duration calculation

* Resolve failing ZZ test

---------

Co-authored-by: Salanto <62221668+Salanto@users.noreply.github.com>
This commit is contained in:
Leifa 2024-07-14 15:47:58 +02:00 committed by GitHub
parent 1edc80a0bc
commit 27ef14fa78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 259 additions and 187 deletions

View File

@ -47,6 +47,7 @@ SOURCES += \
src/config_manager.cpp \ src/config_manager.cpp \
src/db_manager.cpp \ src/db_manager.cpp \
src/discord.cpp \ src/discord.cpp \
src/packet/packet_pr.cpp \
src/packets.cpp \ src/packets.cpp \
src/playerstateobserver.cpp \ src/playerstateobserver.cpp \
src/server.cpp \ src/server.cpp \
@ -68,10 +69,10 @@ SOURCES += \
src/packet/packet_de.cpp \ src/packet/packet_de.cpp \
src/packet/packet_ee.cpp \ src/packet/packet_ee.cpp \
src/packet/packet_hp.cpp \ src/packet/packet_hp.cpp \
src/packet/packet_ma.cpp \
src/packet/packet_mc.cpp \ src/packet/packet_mc.cpp \
src/packet/packet_ms.cpp \ src/packet/packet_ms.cpp \
src/packet/packet_pe.cpp \ src/packet/packet_pe.cpp \
src/packet/packet_pl.cpp \
src/packet/packet_pw.cpp \ src/packet/packet_pw.cpp \
src/packet/packet_rc.cpp \ src/packet/packet_rc.cpp \
src/packet/packet_rd.cpp \ src/packet/packet_rd.cpp \
@ -92,6 +93,7 @@ HEADERS += src/aoclient.h \
src/data_types.h \ src/data_types.h \
src/db_manager.h \ src/db_manager.h \
src/discord.h \ src/discord.h \
src/packet/packet_pr.h \
src/playerstateobserver.h \ src/playerstateobserver.h \
src/server.h \ src/server.h \
src/typedefs.h \ src/typedefs.h \
@ -113,10 +115,10 @@ HEADERS += src/aoclient.h \
src/packet/packet_de.h \ src/packet/packet_de.h \
src/packet/packet_ee.h \ src/packet/packet_ee.h \
src/packet/packet_hp.h \ src/packet/packet_hp.h \
src/packet/packet_ma.h \
src/packet/packet_mc.h \ src/packet/packet_mc.h \
src/packet/packet_ms.h \ src/packet/packet_ms.h \
src/packet/packet_pe.h \ src/packet/packet_pe.h \
src/packet/packet_pl.h \
src/packet/packet_pw.h \ src/packet/packet_pw.h \
src/packet/packet_rc.h \ src/packet/packet_rc.h \
src/packet/packet_rd.h \ src/packet/packet_rd.h \

View File

@ -77,7 +77,7 @@ void AOClient::cmdBan(int argc, QStringList argv)
l_ban_duration = QDateTime::fromSecsSinceEpoch(l_ban.time).addSecs(l_ban.duration).toString("MM/dd/yyyy, hh:mm"); l_ban_duration = QDateTime::fromSecsSinceEpoch(l_ban.time).addSecs(l_ban.duration).toString("MM/dd/yyyy, hh:mm");
} }
else { else {
l_ban_duration = "The heat death of the universe."; l_ban_duration = "Permanently.";
} }
int l_ban_id = server->getDatabaseManager()->getBanID(l_ban.ip); int l_ban_id = server->getDatabaseManager()->getBanID(l_ban.ip);
l_client->sendPacket("KB", {l_ban.reason + "\nID: " + QString::number(l_ban_id) + "\nUntil: " + l_ban_duration}); l_client->sendPacket("KB", {l_ban.reason + "\nID: " + QString::number(l_ban_id) + "\nUntil: " + l_ban_duration});

View File

@ -28,10 +28,11 @@
#include "packet/packet_hi.h" #include "packet/packet_hi.h"
#include "packet/packet_hp.h" #include "packet/packet_hp.h"
#include "packet/packet_id.h" #include "packet/packet_id.h"
#include "packet/packet_ma.h"
#include "packet/packet_mc.h" #include "packet/packet_mc.h"
#include "packet/packet_ms.h" #include "packet/packet_ms.h"
#include "packet/packet_pe.h" #include "packet/packet_pe.h"
#include "packet/packet_pl.h" #include "packet/packet_pr.h"
#include "packet/packet_pw.h" #include "packet/packet_pw.h"
#include "packet/packet_rc.h" #include "packet/packet_rc.h"
#include "packet/packet_rd.h" #include "packet/packet_rd.h"
@ -46,6 +47,8 @@ AOPacket::AOPacket(QStringList p_contents) :
{ {
} }
AOPacket::~AOPacket() {}
const QStringList AOPacket::getContent() const QStringList AOPacket::getContent()
{ {
return m_content; return m_content;
@ -132,8 +135,8 @@ void AOPacket::registerPackets()
PacketFactory::registerClass<PacketRM>("RM"); PacketFactory::registerClass<PacketRM>("RM");
PacketFactory::registerClass<PacketRT>("RT"); PacketFactory::registerClass<PacketRT>("RT");
PacketFactory::registerClass<PacketSetcase>("SETCASE"); PacketFactory::registerClass<PacketSetcase>("SETCASE");
PacketFactory::registerClass<PacketMA>("MA");
PacketFactory::registerClass<PacketZZ>("ZZ"); PacketFactory::registerClass<PacketZZ>("ZZ");
PacketFactory::registerClass<PacketPL>("PL"); PacketFactory::registerClass<PacketPR>("PR");
PacketFactory::registerClass<PacketPLU>("PLU");
PacketFactory::registerClass<PacketPU>("PU"); PacketFactory::registerClass<PacketPU>("PU");
} }

View File

@ -43,7 +43,7 @@ class AOPacket
/** /**
* @brief Destructor for the AOPacket * @brief Destructor for the AOPacket
*/ */
~AOPacket(){}; virtual ~AOPacket();
/** /**
* @brief Returns the current content of the packet * @brief Returns the current content of the packet

View File

@ -40,7 +40,7 @@ void PacketHI::handlePacket(AreaData *area, AOClient &client) const
ban_duration = QDateTime::fromSecsSinceEpoch(ban.second.time).addSecs(ban.second.duration).toString("MM/dd/yyyy, hh:mm"); ban_duration = QDateTime::fromSecsSinceEpoch(ban.second.time).addSecs(ban.second.duration).toString("MM/dd/yyyy, hh:mm");
} }
else { else {
ban_duration = "The heat death of the universe."; ban_duration = "Permanently.";
} }
client.sendPacket("BD", {"Reason: " + ban.second.reason + "\nBan ID: " + QString::number(ban.second.id) + "\nUntil: " + ban_duration}); client.sendPacket("BD", {"Reason: " + ban.second.reason + "\nBan ID: " + QString::number(ban.second.id) + "\nUntil: " + ban_duration});
client.m_socket->close(); client.m_socket->close();

115
src/packet/packet_ma.cpp Normal file
View File

@ -0,0 +1,115 @@
#include "packet_ma.h"
#include "config_manager.h"
#include "db_manager.h"
#include "server.h"
PacketMA::PacketMA(QStringList &contents) :
AOPacket(contents)
{
}
PacketInfo PacketMA::getPacketInfo() const
{
PacketInfo info{
.acl_permission = ACLRole::Permission::NONE,
.min_args = 3,
.header = "MA"};
return info;
}
void PacketMA::handlePacket(AreaData *area, AOClient &client) const
{
if (!client.m_authenticated) {
client.sendServerMessage("You are not logged in!");
return;
}
int client_id = m_content.at(0).toInt();
int duration = qMax(m_content.at(1).toInt(), -1);
QString reason = m_content.at(2);
bool is_kick = duration == 0;
if (is_kick) {
if (!client.checkPermission(ACLRole::KICK)) {
client.sendServerMessage("You do not have permission to kick users.");
return;
}
}
else {
if (!client.checkPermission(ACLRole::BAN)) {
client.sendServerMessage("You do not have permission to ban users.");
return;
}
}
AOClient *target = client.getServer()->getClientByID(client_id);
if (target == nullptr) {
client.sendServerMessage("User not found.");
return;
}
QString moderator_name;
if (ConfigManager::authType() == DataTypes::AuthType::ADVANCED) {
moderator_name = client.m_moderator_name;
}
else {
moderator_name = "Moderator";
}
QList<AOClient *> clients = client.getServer()->getClientsByIpid(target->m_ipid);
if (is_kick) {
for (AOClient *subclient : clients) {
subclient->sendPacket("KK", {reason});
subclient->m_socket->close();
}
Q_EMIT client.logKick(moderator_name, target->m_ipid, reason);
client.sendServerMessage("Kicked " + QString::number(clients.size()) + " client(s) with ipid " + target->m_ipid + " for reason: " + reason);
}
else {
DBManager::BanInfo ban;
ban.ip = target->m_remote_ip;
ban.ipid = target->m_ipid;
ban.moderator = moderator_name;
ban.reason = reason;
ban.time = QDateTime::currentDateTime().toSecsSinceEpoch();
QString timestamp;
if (duration == -1) {
ban.duration = -2;
timestamp = "permanently";
}
else {
ban.duration = duration * 60;
timestamp = QDateTime::fromSecsSinceEpoch(ban.time).addSecs(ban.duration).toString("MM/dd/yyyy, hh:mm");
}
for (AOClient *subclient : clients) {
ban.hdid = subclient->m_hwid;
client.getServer()->getDatabaseManager()->addBan(ban);
subclient->sendPacket("KB", {reason});
subclient->m_socket->close();
}
if (ban.duration == -2) {
timestamp = "permanently";
}
else {
timestamp = QString::number(ban.time + ban.duration);
}
Q_EMIT client.logBan(moderator_name, target->m_ipid, timestamp, reason);
client.sendServerMessage("Banned " + QString::number(clients.size()) + " client(s) with ipid " + target->m_ipid + " for reason: " + reason);
int ban_id = client.getServer()->getDatabaseManager()->getBanID(ban.ip);
if (ConfigManager::discordBanWebhookEnabled()) {
Q_EMIT client.getServer()->banWebhookRequest(ban.ipid, ban.moderator, timestamp, ban.reason, ban_id);
}
}
}

11
src/packet/packet_ma.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include "network/aopacket.h"
class PacketMA : public AOPacket
{
public:
PacketMA(QStringList &contents);
virtual PacketInfo getPacketInfo() const;
virtual void handlePacket(AreaData *area, AOClient &client) const;
};

View File

@ -1,88 +0,0 @@
#include "packet_pl.h"
#include <QJsonDocument>
PacketPL::PacketPL(QStringList &contents) :
AOPacket(contents)
{}
PacketPL::PacketPL(const QList<PlayerData> &f_player_list) :
AOPacket(QStringList{""})
{
QJsonArray player_list_json;
for (const PlayerData &player : f_player_list) {
QJsonObject player_json;
player_json["id"] = player.id;
player_json["name"] = player.name;
player_json["character"] = player.character;
player_json["character_name"] = player.character_name;
player_json["area_id"] = player.area_id;
player_list_json.append(player_json);
}
setContentField(0, QJsonDocument(player_list_json).toJson(QJsonDocument::Compact));
}
PacketInfo PacketPL::getPacketInfo() const { return PacketInfo{.acl_permission = ACLRole::NONE, .min_args = 1, .header = "PL"}; }
void PacketPL::handlePacket(AreaData *area, AOClient &client) const
{
Q_UNUSED(area);
Q_UNUSED(client);
}
PacketPLU::PacketPLU(QStringList &contents) :
AOPacket(contents)
{}
PacketPLU::PacketPLU(int f_id, UpdateType f_type) :
AOPacket(QStringList{""})
{
QJsonObject data_json;
data_json["id"] = f_id;
data_json["type"] = f_type;
setContentField(0, QJsonDocument(data_json).toJson(QJsonDocument::Compact));
}
PacketInfo PacketPLU::getPacketInfo() const { return PacketInfo{.acl_permission = ACLRole::NONE, .min_args = 1, .header = "PLU"}; }
void PacketPLU::handlePacket(AreaData *area, AOClient &client) const
{
Q_UNUSED(area);
Q_UNUSED(client);
}
PacketPU::PacketPU(QStringList &contents) :
AOPacket(contents)
{}
PacketPU::PacketPU(int f_id, DataType f_type, const QString &f_data) :
AOPacket(QStringList{""})
{
QJsonObject data_json;
data_json["id"] = f_id;
data_json["type"] = f_type;
data_json["data"] = f_data;
setContentField(0, QJsonDocument(data_json).toJson(QJsonDocument::Compact));
}
PacketPU::PacketPU(int f_id, DataType f_type, int f_data) :
PacketPU(f_id, f_type, QString::number(f_data))
{
}
PacketInfo PacketPU::getPacketInfo() const
{
return PacketInfo{.acl_permission = ACLRole::NONE, .min_args = 1, .header = "PU"};
}
void PacketPU::handlePacket(AreaData *area, AOClient &client) const
{
Q_UNUSED(area);
Q_UNUSED(client);
}

View File

@ -1,59 +0,0 @@
#pragma once
#include "network/aopacket.h"
#include <QByteArray>
#include <QJsonArray>
#include <QJsonObject>
#include <QList>
class PacketPL : public AOPacket
{
public:
struct PlayerData
{
int id;
QString name;
QString character;
QString character_name;
int area_id = -1;
};
PacketPL(QStringList &contents);
PacketPL(const QList<PlayerData> &f_player_list);
PacketInfo getPacketInfo() const override;
void handlePacket(AreaData *area, AOClient &client) const override;
};
class PacketPLU : public AOPacket
{
public:
enum UpdateType
{
AddPlayerUpdate,
RemovePlayerUpdate,
};
PacketPLU(QStringList &contents);
PacketPLU(int f_id, UpdateType f_type);
PacketInfo getPacketInfo() const override;
void handlePacket(AreaData *area, AOClient &client) const override;
};
class PacketPU : public AOPacket
{
public:
enum DataType
{
NameData,
CharacterData,
CharacterNameData,
AreaIdData,
};
PacketPU(QStringList &contents);
PacketPU(int f_id, DataType f_type, const QString &f_data);
PacketPU(int f_id, DataType f_type, int f_data);
PacketInfo getPacketInfo() const override;
void handlePacket(AreaData *area, AOClient &client) const override;
};

41
src/packet/packet_pr.cpp Normal file
View File

@ -0,0 +1,41 @@
#include "packet_pr.h"
PacketPR::PacketPR(QStringList &contents) :
AOPacket(contents)
{}
PacketPR::PacketPR(int f_id, UPDATE_TYPE f_update) :
AOPacket(QStringList{QString::number(f_id), QString::number(f_update)})
{}
PacketInfo PacketPR::getPacketInfo() const { return PacketInfo{.acl_permission = ACLRole::NONE, .min_args = 2, .header = "PR"}; }
void PacketPR::handlePacket(AreaData *area, AOClient &client) const
{
Q_UNUSED(area);
Q_UNUSED(client);
}
PacketPU::PacketPU(QStringList &contents) :
AOPacket(contents)
{}
PacketPU::PacketPU(int f_id, DATA_TYPE f_type, const QString &f_data) :
AOPacket(QStringList{QString::number(f_id), QString::number(f_type), f_data})
{}
PacketPU::PacketPU(int f_id, DATA_TYPE f_type, int f_data) :
PacketPU(f_id, f_type, QString::number(f_data))
{
}
PacketInfo PacketPU::getPacketInfo() const
{
return PacketInfo{.acl_permission = ACLRole::NONE, .min_args = 3, .header = "PU"};
}
void PacketPU::handlePacket(AreaData *area, AOClient &client) const
{
Q_UNUSED(area);
Q_UNUSED(client);
}

38
src/packet/packet_pr.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include "network/aopacket.h"
#include <QString>
class PacketPR : public AOPacket
{
public:
enum UPDATE_TYPE
{
ADD,
REMOVE,
};
PacketPR(QStringList &contents);
PacketPR(int f_id, UPDATE_TYPE f_update);
PacketInfo getPacketInfo() const override;
void handlePacket(AreaData *area, AOClient &client) const override;
};
class PacketPU : public AOPacket
{
public:
enum DATA_TYPE
{
NAME,
CHARACTER,
CHARACTER_NAME,
AREA_ID,
};
PacketPU(QStringList &contents);
PacketPU(int f_id, DATA_TYPE f_type, const QString &f_data);
PacketPU(int f_id, DATA_TYPE f_type, int f_data);
PacketInfo getPacketInfo() const override;
void handlePacket(AreaData *area, AOClient &client) const override;
};

View File

@ -14,7 +14,7 @@ PacketInfo PacketZZ::getPacketInfo() const
{ {
PacketInfo info{ PacketInfo info{
.acl_permission = ACLRole::Permission::NONE, .acl_permission = ACLRole::Permission::NONE,
.min_args = 0, .min_args = 2,
.header = "ZZ"}; .header = "ZZ"};
return info; return info;
} }
@ -29,10 +29,14 @@ void PacketZZ::handlePacket(AreaData *area, AOClient &client) const
QString l_modcallNotice = "!!!MODCALL!!!\nArea: " + l_areaName + "\nCaller: " + l_name + "\n"; QString l_modcallNotice = "!!!MODCALL!!!\nArea: " + l_areaName + "\nCaller: " + l_name + "\n";
if (m_content.size() > 0 && !m_content[0].isEmpty()) int target_id = m_content.at(1).toInt();
if (target_id != -1) {
AOClient *target = client.getServer()->getClientByID(target_id);
if (target) {
l_modcallNotice.append("Regarding: " + target->name() + "\n");
}
}
l_modcallNotice.append("Reason: " + m_content[0]); l_modcallNotice.append("Reason: " + m_content[0]);
else
l_modcallNotice.append("No reason given.");
const QVector<AOClient *> l_clients = client.getServer()->getClients(); const QVector<AOClient *> l_clients = client.getServer()->getClients();
for (AOClient *l_client : l_clients) { for (AOClient *l_client : l_clients) {
@ -47,6 +51,15 @@ void PacketZZ::handlePacket(AreaData *area, AOClient &client) const
l_name = client.character(); l_name = client.character();
QString l_areaName = area->name(); QString l_areaName = area->name();
emit client.getServer()->modcallWebhookRequest(l_name, l_areaName, m_content.value(0), client.getServer()->getAreaBuffer(l_areaName));
QString webhook_reason = m_content.value(0);
if (target_id != -1) {
AOClient *target = client.getServer()->getClientByID(target_id);
if (target) {
webhook_reason.append(" (Regarding: " + target->name() + ")");
}
}
emit client.getServer()->modcallWebhookRequest(l_name, l_areaName, webhook_reason, client.getServer()->getAreaBuffer(l_areaName));
} }
} }

View File

@ -10,4 +10,5 @@ class PacketZZ : public AOPacket
virtual PacketInfo getPacketInfo() const; virtual PacketInfo getPacketInfo() const;
virtual void handlePacket(AreaData *area, AOClient &client) const; virtual void handlePacket(AreaData *area, AOClient &client) const;
}; };
#endif #endif

View File

@ -10,7 +10,7 @@ void PlayerStateObserver::registerClient(AOClient *client)
{ {
Q_ASSERT(!m_client_list.contains(client)); Q_ASSERT(!m_client_list.contains(client));
PacketPLU packet(client->clientId(), PacketPLU::AddPlayerUpdate); PacketPR packet(client->clientId(), PacketPR::ADD);
sendToClientList(packet); sendToClientList(packet);
m_client_list.append(client); m_client_list.append(client);
@ -20,20 +20,18 @@ void PlayerStateObserver::registerClient(AOClient *client)
connect(client, &AOClient::characterNameChanged, this, &PlayerStateObserver::notifyCharacterNameChanged); connect(client, &AOClient::characterNameChanged, this, &PlayerStateObserver::notifyCharacterNameChanged);
connect(client, &AOClient::areaIdChanged, this, &PlayerStateObserver::notifyAreaIdChanged); connect(client, &AOClient::areaIdChanged, this, &PlayerStateObserver::notifyAreaIdChanged);
{ // provide the player list to the new client QList<AOPacket *> packets;
QList<PacketPL::PlayerData> data_list;
for (AOClient *i_client : qAsConst(m_client_list)) { for (AOClient *i_client : qAsConst(m_client_list)) {
PacketPL::PlayerData data; packets.append(new PacketPR(i_client->clientId(), PacketPR::ADD));
data.id = i_client->clientId(); packets.append(new PacketPU(i_client->clientId(), PacketPU::NAME, i_client->name()));
data.name = i_client->name(); packets.append(new PacketPU(i_client->clientId(), PacketPU::CHARACTER, i_client->character()));
data.character = i_client->character(); packets.append(new PacketPU(i_client->clientId(), PacketPU::CHARACTER_NAME, i_client->characterName()));
data.character_name = i_client->characterName(); packets.append(new PacketPU(i_client->clientId(), PacketPU::AREA_ID, i_client->areaId()));
data.area_id = i_client->areaId();
data_list.append(data);
} }
PacketPL packet(data_list); for (AOPacket *packet : qAsConst(packets)) {
client->sendPacket(&packet); client->sendPacket(packet);
delete packet;
} }
} }
@ -45,7 +43,7 @@ void PlayerStateObserver::unregisterClient(AOClient *client)
m_client_list.removeAll(client); m_client_list.removeAll(client);
PacketPLU packet(client->clientId(), PacketPLU::RemovePlayerUpdate); PacketPR packet(client->clientId(), PacketPR::REMOVE);
sendToClientList(packet); sendToClientList(packet);
} }
@ -58,24 +56,20 @@ void PlayerStateObserver::sendToClientList(const AOPacket &packet)
void PlayerStateObserver::notifyNameChanged(const QString &name) void PlayerStateObserver::notifyNameChanged(const QString &name)
{ {
qDebug() << "PlayerStateObserver::notifyNameChanged" << qobject_cast<AOClient *>(sender())->clientId() << name; sendToClientList(PacketPU(qobject_cast<AOClient *>(sender())->clientId(), PacketPU::NAME, name));
sendToClientList(PacketPU(qobject_cast<AOClient *>(sender())->clientId(), PacketPU::NameData, name));
} }
void PlayerStateObserver::notifyCharacterChanged(const QString &character) void PlayerStateObserver::notifyCharacterChanged(const QString &character)
{ {
qDebug() << "PlayerStateObserver::notifyCharacterChanged" << qobject_cast<AOClient *>(sender())->clientId() << character; sendToClientList(PacketPU(qobject_cast<AOClient *>(sender())->clientId(), PacketPU::CHARACTER, character));
sendToClientList(PacketPU(qobject_cast<AOClient *>(sender())->clientId(), PacketPU::CharacterData, character));
} }
void PlayerStateObserver::notifyCharacterNameChanged(const QString &characterName) void PlayerStateObserver::notifyCharacterNameChanged(const QString &characterName)
{ {
qDebug() << "PlayerStateObserver::notifyCharacterNameChanged" << qobject_cast<AOClient *>(sender())->clientId() << characterName; sendToClientList(PacketPU(qobject_cast<AOClient *>(sender())->clientId(), PacketPU::CHARACTER_NAME, characterName));
sendToClientList(PacketPU(qobject_cast<AOClient *>(sender())->clientId(), PacketPU::CharacterNameData, characterName));
} }
void PlayerStateObserver::notifyAreaIdChanged(int areaId) void PlayerStateObserver::notifyAreaIdChanged(int areaId)
{ {
qDebug() << "PlayerStateObserver::notifyAreaIdChanged" << qobject_cast<AOClient *>(sender())->clientId() << areaId; sendToClientList(PacketPU(qobject_cast<AOClient *>(sender())->clientId(), PacketPU::AREA_ID, areaId));
sendToClientList(PacketPU(qobject_cast<AOClient *>(sender())->clientId(), PacketPU::AreaIdData, areaId));
} }

View File

@ -1,7 +1,8 @@
#pragma once #pragma once
#include "akashidefs.h"
#include "aoclient.h" #include "aoclient.h"
#include "packet/packet_pl.h" #include "packet/packet_pr.h"
#include <QList> #include <QList>
#include <QObject> #include <QObject>

View File

@ -184,7 +184,7 @@ void Server::clientConnected()
ban_duration = QDateTime::fromSecsSinceEpoch(ban.second.time).addSecs(ban.second.duration).toString("MM/dd/yyyy, hh:mm"); ban_duration = QDateTime::fromSecsSinceEpoch(ban.second.time).addSecs(ban.second.duration).toString("MM/dd/yyyy, hh:mm");
} }
else { else {
ban_duration = "The heat death of the universe."; ban_duration = "Permanently.";
} }
AOPacket *ban_reason = PacketFactory::createPacket("BD", {"Reason: " + ban.second.reason + "\nBan ID: " + QString::number(ban.second.id) + "\nUntil: " + ban_duration}); AOPacket *ban_reason = PacketFactory::createPacket("BD", {"Reason: " + ban.second.reason + "\nBan ID: " + QString::number(ban.second.id) + "\nUntil: " + ban_duration});
socket->sendTextMessage(ban_reason->toUtf8()); socket->sendTextMessage(ban_reason->toUtf8());

View File

@ -116,7 +116,7 @@ void Packet::createPacketSubclass_data()
<< 7; << 7;
QTest::newRow("ZZ") << "ZZ#" QTest::newRow("ZZ") << "ZZ#"
<< "ZZ" << "ZZ"
<< 0; << 2;
} }
void Packet::createPacketSubclass() void Packet::createPacketSubclass()