Merge pull request #4468 from citra-emu/multiplayer-v4/main
Multiplayer version 4
This commit is contained in:
@@ -99,6 +99,8 @@ add_executable(citra-qt
|
||||
multiplayer/lobby.cpp
|
||||
multiplayer/message.h
|
||||
multiplayer/message.cpp
|
||||
multiplayer/moderation_dialog.cpp
|
||||
multiplayer/moderation_dialog.h
|
||||
multiplayer/state.cpp
|
||||
multiplayer/state.h
|
||||
multiplayer/validation.h
|
||||
@@ -135,6 +137,7 @@ set(UIS
|
||||
multiplayer/chat_room.ui
|
||||
multiplayer/client_room.ui
|
||||
multiplayer/host_room.ui
|
||||
multiplayer/moderation_dialog.ui
|
||||
aboutdialog.ui
|
||||
cheats.ui
|
||||
hotkeys.ui
|
||||
@@ -228,6 +231,10 @@ if (USE_DISCORD_PRESENCE)
|
||||
target_compile_definitions(citra-qt PRIVATE -DUSE_DISCORD_PRESENCE)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
target_compile_definitions(citra-qt PRIVATE -DENABLE_WEB_SERVICE)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
install(TARGETS citra-qt RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
|
||||
endif()
|
||||
|
@@ -329,6 +329,22 @@ void Config::ReadValues() {
|
||||
}
|
||||
UISettings::values.max_player = ReadSetting("max_player", 8).toUInt();
|
||||
UISettings::values.game_id = ReadSetting("game_id", 0).toULongLong();
|
||||
UISettings::values.room_description = ReadSetting("room_description", "").toString();
|
||||
// Read ban list back
|
||||
size = qt_config->beginReadArray("username_ban_list");
|
||||
UISettings::values.ban_list.first.resize(size);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
qt_config->setArrayIndex(i);
|
||||
UISettings::values.ban_list.first[i] = ReadSetting("username").toString().toStdString();
|
||||
}
|
||||
qt_config->endArray();
|
||||
size = qt_config->beginReadArray("ip_ban_list");
|
||||
UISettings::values.ban_list.second.resize(size);
|
||||
for (int i = 0; i < size; ++i) {
|
||||
qt_config->setArrayIndex(i);
|
||||
UISettings::values.ban_list.second[i] = ReadSetting("ip").toString().toStdString();
|
||||
}
|
||||
qt_config->endArray();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->endGroup();
|
||||
@@ -533,6 +549,20 @@ void Config::SaveValues() {
|
||||
WriteSetting("host_type", UISettings::values.host_type, 0);
|
||||
WriteSetting("max_player", UISettings::values.max_player, 8);
|
||||
WriteSetting("game_id", UISettings::values.game_id, 0);
|
||||
WriteSetting("room_description", UISettings::values.room_description, "");
|
||||
// Write ban list
|
||||
qt_config->beginWriteArray("username_ban_list");
|
||||
for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) {
|
||||
qt_config->setArrayIndex(i);
|
||||
WriteSetting("username", QString::fromStdString(UISettings::values.ban_list.first[i]));
|
||||
}
|
||||
qt_config->endArray();
|
||||
qt_config->beginWriteArray("ip_ban_list");
|
||||
for (std::size_t i = 0; i < UISettings::values.ban_list.second.size(); ++i) {
|
||||
qt_config->setArrayIndex(i);
|
||||
WriteSetting("ip", QString::fromStdString(UISettings::values.ban_list.second[i]));
|
||||
}
|
||||
qt_config->endArray();
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->endGroup();
|
||||
|
@@ -5,6 +5,8 @@
|
||||
#include <array>
|
||||
#include <future>
|
||||
#include <QColor>
|
||||
#include <QDesktopServices>
|
||||
#include <QFutureWatcher>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QLocale>
|
||||
@@ -12,6 +14,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QMetaType>
|
||||
#include <QTime>
|
||||
#include <QUrl>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include "citra_qt/game_list_p.h"
|
||||
#include "citra_qt/multiplayer/chat_room.h"
|
||||
@@ -19,6 +22,9 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "core/announce_multiplayer_session.h"
|
||||
#include "ui_chat_room.h"
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
#include "web_service/web_backend.h"
|
||||
#endif
|
||||
|
||||
class ChatMessage {
|
||||
public:
|
||||
@@ -27,24 +33,60 @@ public:
|
||||
QLocale locale;
|
||||
timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat);
|
||||
nickname = QString::fromStdString(chat.nickname);
|
||||
username = QString::fromStdString(chat.username);
|
||||
message = QString::fromStdString(chat.message);
|
||||
|
||||
// Check for user pings
|
||||
QString cur_nickname, cur_username;
|
||||
if (auto room = Network::GetRoomMember().lock()) {
|
||||
cur_nickname = QString::fromStdString(room->GetNickname());
|
||||
cur_username = QString::fromStdString(room->GetUsername());
|
||||
}
|
||||
if (message.contains(QString("@").append(cur_nickname)) ||
|
||||
(!cur_username.isEmpty() && message.contains(QString("@").append(cur_username)))) {
|
||||
|
||||
contains_ping = true;
|
||||
} else {
|
||||
contains_ping = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainsPing() const {
|
||||
return contains_ping;
|
||||
}
|
||||
|
||||
/// Format the message using the players color
|
||||
QString GetPlayerChatMessage(u16 player) const {
|
||||
auto color = player_color[player % 16];
|
||||
return QString("[%1] <font color='%2'><%3></font> %4")
|
||||
.arg(timestamp, color, nickname.toHtmlEscaped(), message.toHtmlEscaped());
|
||||
QString name;
|
||||
if (username.isEmpty() || username == nickname) {
|
||||
name = nickname;
|
||||
} else {
|
||||
name = QString("%1 (%2)").arg(nickname, username);
|
||||
}
|
||||
|
||||
QString style;
|
||||
if (ContainsPing()) {
|
||||
// Add a background color to these messages
|
||||
style = QString("background-color: %1").arg(ping_color);
|
||||
}
|
||||
|
||||
return QString("[%1] <font color='%2'><%3></font> <font style='%4' "
|
||||
"color='#000000'>%5</font>")
|
||||
.arg(timestamp, color, name.toHtmlEscaped(), style, message.toHtmlEscaped());
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::array<const char*, 16> player_color = {
|
||||
{"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222",
|
||||
"#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "FFFF00"}};
|
||||
static constexpr char ping_color[] = "#FFFF00";
|
||||
|
||||
QString timestamp;
|
||||
QString nickname;
|
||||
QString username;
|
||||
QString message;
|
||||
bool contains_ping;
|
||||
};
|
||||
|
||||
class StatusMessage {
|
||||
@@ -57,37 +99,69 @@ public:
|
||||
}
|
||||
|
||||
QString GetSystemChatMessage() const {
|
||||
return QString("[%1] <font color='%2'><i>%3</i></font>")
|
||||
.arg(timestamp, system_color, message);
|
||||
return QString("[%1] <font color='%2'>* %3</font>").arg(timestamp, system_color, message);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr const char system_color[] = "#888888";
|
||||
static constexpr const char system_color[] = "#FF8C00";
|
||||
QString timestamp;
|
||||
QString message;
|
||||
};
|
||||
|
||||
class PlayerListItem : public QStandardItem {
|
||||
public:
|
||||
static const int NicknameRole = Qt::UserRole + 1;
|
||||
static const int UsernameRole = Qt::UserRole + 2;
|
||||
static const int AvatarUrlRole = Qt::UserRole + 3;
|
||||
static const int GameNameRole = Qt::UserRole + 4;
|
||||
|
||||
PlayerListItem() = default;
|
||||
explicit PlayerListItem(const std::string& nickname, const std::string& username,
|
||||
const std::string& avatar_url, const std::string& game_name) {
|
||||
setEditable(false);
|
||||
setData(QString::fromStdString(nickname), NicknameRole);
|
||||
setData(QString::fromStdString(username), UsernameRole);
|
||||
setData(QString::fromStdString(avatar_url), AvatarUrlRole);
|
||||
if (game_name.empty()) {
|
||||
setData(QObject::tr("Not playing a game"), GameNameRole);
|
||||
} else {
|
||||
setData(QString::fromStdString(game_name), GameNameRole);
|
||||
}
|
||||
}
|
||||
|
||||
QVariant data(int role) const override {
|
||||
if (role != Qt::DisplayRole) {
|
||||
return QStandardItem::data(role);
|
||||
}
|
||||
QString name;
|
||||
const QString nickname = data(NicknameRole).toString();
|
||||
const QString username = data(UsernameRole).toString();
|
||||
if (username.isEmpty() || username == nickname) {
|
||||
name = nickname;
|
||||
} else {
|
||||
name = QString("%1 (%2)").arg(nickname, username);
|
||||
}
|
||||
return QString("%1\n %2").arg(name, data(GameNameRole).toString());
|
||||
}
|
||||
};
|
||||
|
||||
ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ChatRoom>()) {
|
||||
ui->setupUi(this);
|
||||
|
||||
// set the item_model for player_view
|
||||
enum {
|
||||
COLUMN_NAME,
|
||||
COLUMN_GAME,
|
||||
COLUMN_COUNT, // Number of columns
|
||||
};
|
||||
|
||||
player_list = new QStandardItemModel(ui->player_view);
|
||||
ui->player_view->setModel(player_list);
|
||||
ui->player_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
player_list->insertColumns(0, COLUMN_COUNT);
|
||||
player_list->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
|
||||
player_list->setHeaderData(COLUMN_GAME, Qt::Horizontal, tr("Game"));
|
||||
// set a header to make it look better though there is only one column
|
||||
player_list->insertColumns(0, 1);
|
||||
player_list->setHeaderData(0, Qt::Horizontal, tr("Members"));
|
||||
|
||||
ui->chat_history->document()->setMaximumBlockCount(max_chat_lines);
|
||||
|
||||
// register the network structs to use in slots and signals
|
||||
qRegisterMetaType<Network::ChatEntry>();
|
||||
qRegisterMetaType<Network::StatusMessageEntry>();
|
||||
qRegisterMetaType<Network::RoomInformation>();
|
||||
qRegisterMetaType<Network::RoomMember::State>();
|
||||
|
||||
@@ -95,7 +169,12 @@ ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::C
|
||||
if (auto member = Network::GetRoomMember().lock()) {
|
||||
member->BindOnChatMessageRecieved(
|
||||
[this](const Network::ChatEntry& chat) { emit ChatReceived(chat); });
|
||||
member->BindOnStatusMessageReceived(
|
||||
[this](const Network::StatusMessageEntry& status_message) {
|
||||
emit StatusMessageReceived(status_message);
|
||||
});
|
||||
connect(this, &ChatRoom::ChatReceived, this, &ChatRoom::OnChatReceive);
|
||||
connect(this, &ChatRoom::StatusMessageReceived, this, &ChatRoom::OnStatusMessageReceive);
|
||||
} else {
|
||||
// TODO (jroweboy) network was not initialized?
|
||||
}
|
||||
@@ -110,6 +189,10 @@ ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::C
|
||||
|
||||
ChatRoom::~ChatRoom() = default;
|
||||
|
||||
void ChatRoom::SetModPerms(bool is_mod) {
|
||||
has_mod_perms = is_mod;
|
||||
}
|
||||
|
||||
void ChatRoom::RetranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
@@ -127,6 +210,21 @@ void ChatRoom::AppendChatMessage(const QString& msg) {
|
||||
ui->chat_history->append(msg);
|
||||
}
|
||||
|
||||
void ChatRoom::SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname) {
|
||||
if (auto room = Network::GetRoomMember().lock()) {
|
||||
auto members = room->GetMemberInformation();
|
||||
auto it = std::find_if(members.begin(), members.end(),
|
||||
[&nickname](const Network::RoomMember::MemberInformation& member) {
|
||||
return member.nickname == nickname;
|
||||
});
|
||||
if (it == members.end()) {
|
||||
NetworkMessage::ShowError(NetworkMessage::NO_SUCH_USER);
|
||||
return;
|
||||
}
|
||||
room->SendModerationRequest(type, nickname);
|
||||
}
|
||||
}
|
||||
|
||||
bool ChatRoom::ValidateMessage(const std::string& msg) {
|
||||
return !msg.empty();
|
||||
}
|
||||
@@ -157,7 +255,8 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
|
||||
auto members = room->GetMemberInformation();
|
||||
auto it = std::find_if(members.begin(), members.end(),
|
||||
[&chat](const Network::RoomMember::MemberInformation& member) {
|
||||
return member.nickname == chat.nickname;
|
||||
return member.nickname == chat.nickname &&
|
||||
member.username == chat.username;
|
||||
});
|
||||
if (it == members.end()) {
|
||||
LOG_INFO(Network, "Chat message received from unknown player. Ignoring it.");
|
||||
@@ -170,13 +269,48 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
|
||||
}
|
||||
auto player = std::distance(members.begin(), it);
|
||||
ChatMessage m(chat);
|
||||
if (m.ContainsPing()) {
|
||||
emit UserPinged();
|
||||
}
|
||||
AppendChatMessage(m.GetPlayerChatMessage(player));
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_message) {
|
||||
QString name;
|
||||
if (status_message.username.empty() || status_message.username == status_message.nickname) {
|
||||
name = QString::fromStdString(status_message.nickname);
|
||||
} else {
|
||||
name = QString("%1 (%2)").arg(QString::fromStdString(status_message.nickname),
|
||||
QString::fromStdString(status_message.username));
|
||||
}
|
||||
QString message;
|
||||
switch (status_message.type) {
|
||||
case Network::IdMemberJoin:
|
||||
message = tr("%1 has joined").arg(name);
|
||||
break;
|
||||
case Network::IdMemberLeave:
|
||||
message = tr("%1 has left").arg(name);
|
||||
break;
|
||||
case Network::IdMemberKicked:
|
||||
message = tr("%1 has been kicked").arg(name);
|
||||
break;
|
||||
case Network::IdMemberBanned:
|
||||
message = tr("%1 has been banned").arg(name);
|
||||
break;
|
||||
case Network::IdAddressUnbanned:
|
||||
message = tr("%1 has been unbanned").arg(name);
|
||||
break;
|
||||
}
|
||||
if (!message.isEmpty())
|
||||
AppendStatusMessage(message);
|
||||
}
|
||||
|
||||
void ChatRoom::OnSendChat() {
|
||||
if (auto room = Network::GetRoomMember().lock()) {
|
||||
if (room->GetState() != Network::RoomMember::State::Joined) {
|
||||
if (room->GetState() != Network::RoomMember::State::Joined &&
|
||||
room->GetState() != Network::RoomMember::State::Moderator) {
|
||||
|
||||
return;
|
||||
}
|
||||
auto message = ui->chat_message->text().toStdString();
|
||||
@@ -184,12 +318,14 @@ void ChatRoom::OnSendChat() {
|
||||
return;
|
||||
}
|
||||
auto nick = room->GetNickname();
|
||||
Network::ChatEntry chat{nick, message};
|
||||
auto username = room->GetUsername();
|
||||
Network::ChatEntry chat{nick, username, message};
|
||||
|
||||
auto members = room->GetMemberInformation();
|
||||
auto it = std::find_if(members.begin(), members.end(),
|
||||
[&chat](const Network::RoomMember::MemberInformation& member) {
|
||||
return member.nickname == chat.nickname;
|
||||
return member.nickname == chat.nickname &&
|
||||
member.username == chat.username;
|
||||
});
|
||||
if (it == members.end()) {
|
||||
LOG_INFO(Network, "Cannot find self in the player list when sending a message.");
|
||||
@@ -202,20 +338,64 @@ void ChatRoom::OnSendChat() {
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRoom::UpdateIconDisplay() {
|
||||
for (int row = 0; row < player_list->invisibleRootItem()->rowCount(); ++row) {
|
||||
QStandardItem* item = player_list->invisibleRootItem()->child(row);
|
||||
const std::string avatar_url =
|
||||
item->data(PlayerListItem::AvatarUrlRole).toString().toStdString();
|
||||
if (icon_cache.count(avatar_url)) {
|
||||
item->setData(icon_cache.at(avatar_url), Qt::DecorationRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRoom::SetPlayerList(const Network::RoomMember::MemberList& member_list) {
|
||||
// TODO(B3N30): Remember which row is selected
|
||||
player_list->removeRows(0, player_list->rowCount());
|
||||
for (const auto& member : member_list) {
|
||||
if (member.nickname.empty())
|
||||
continue;
|
||||
QList<QStandardItem*> l;
|
||||
std::vector<std::string> elements = {member.nickname, member.game_info.name};
|
||||
for (const auto& item : elements) {
|
||||
QStandardItem* child = new QStandardItem(QString::fromStdString(item));
|
||||
child->setEditable(false);
|
||||
l.append(child);
|
||||
QStandardItem* name_item = new PlayerListItem(member.nickname, member.username,
|
||||
member.avatar_url, member.game_info.name);
|
||||
|
||||
if (!icon_cache.count(member.avatar_url)) {
|
||||
// Emplace a default question mark icon as avatar
|
||||
icon_cache.emplace(member.avatar_url, QIcon::fromTheme("no_avatar").pixmap(48));
|
||||
if (!member.avatar_url.empty()) {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
// Start a request to get the member's avatar
|
||||
const QUrl url(QString::fromStdString(member.avatar_url));
|
||||
QFuture<std::string> future = QtConcurrent::run([url] {
|
||||
WebService::Client client(
|
||||
QString("%1://%2").arg(url.scheme(), url.host()).toStdString(), "", "");
|
||||
auto result = client.GetImage(url.path().toStdString(), true);
|
||||
if (result.returned_data.empty()) {
|
||||
LOG_ERROR(WebService, "Failed to get avatar");
|
||||
}
|
||||
return result.returned_data;
|
||||
});
|
||||
auto* future_watcher = new QFutureWatcher<std::string>(this);
|
||||
connect(future_watcher, &QFutureWatcher<std::string>::finished, this,
|
||||
[this, future_watcher, avatar_url = member.avatar_url] {
|
||||
const std::string result = future_watcher->result();
|
||||
if (result.empty())
|
||||
return;
|
||||
QPixmap pixmap;
|
||||
if (!pixmap.loadFromData(reinterpret_cast<const u8*>(result.data()),
|
||||
result.size()))
|
||||
return;
|
||||
icon_cache[avatar_url] = pixmap.scaled(48, 48, Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
// Update all the displayed icons with the new icon_cache
|
||||
UpdateIconDisplay();
|
||||
});
|
||||
future_watcher->setFuture(future);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
player_list->invisibleRootItem()->appendRow(l);
|
||||
name_item->setData(icon_cache.at(member.avatar_url), Qt::DecorationRole);
|
||||
|
||||
player_list->invisibleRootItem()->appendRow(name_item);
|
||||
}
|
||||
// TODO(B3N30): Restore row selection
|
||||
}
|
||||
@@ -230,33 +410,73 @@ void ChatRoom::PopupContextMenu(const QPoint& menu_location) {
|
||||
if (!item.isValid())
|
||||
return;
|
||||
|
||||
std::string nickname = player_list->item(item.row())->text().toStdString();
|
||||
if (auto room = Network::GetRoomMember().lock()) {
|
||||
// You can't block yourself
|
||||
if (nickname == room->GetNickname())
|
||||
return;
|
||||
}
|
||||
std::string nickname =
|
||||
player_list->item(item.row())->data(PlayerListItem::NicknameRole).toString().toStdString();
|
||||
|
||||
QMenu context_menu;
|
||||
QAction* block_action = context_menu.addAction(tr("Block Player"));
|
||||
|
||||
block_action->setCheckable(true);
|
||||
block_action->setChecked(block_list.count(nickname) > 0);
|
||||
QString username = player_list->item(item.row())->data(PlayerListItem::UsernameRole).toString();
|
||||
if (!username.isEmpty()) {
|
||||
QAction* view_profile_action = context_menu.addAction(tr("View Profile"));
|
||||
connect(view_profile_action, &QAction::triggered, [username] {
|
||||
QDesktopServices::openUrl(
|
||||
QString("https://community.citra-emu.org/u/%1").arg(username));
|
||||
});
|
||||
}
|
||||
|
||||
connect(block_action, &QAction::triggered, [this, nickname] {
|
||||
if (block_list.count(nickname)) {
|
||||
block_list.erase(nickname);
|
||||
} else {
|
||||
std::string cur_nickname;
|
||||
if (auto room = Network::GetRoomMember().lock()) {
|
||||
cur_nickname = room->GetNickname();
|
||||
}
|
||||
|
||||
if (nickname != cur_nickname) { // You can't block yourself
|
||||
QAction* block_action = context_menu.addAction(tr("Block Player"));
|
||||
|
||||
block_action->setCheckable(true);
|
||||
block_action->setChecked(block_list.count(nickname) > 0);
|
||||
|
||||
connect(block_action, &QAction::triggered, [this, nickname] {
|
||||
if (block_list.count(nickname)) {
|
||||
block_list.erase(nickname);
|
||||
} else {
|
||||
QMessageBox::StandardButton result = QMessageBox::question(
|
||||
this, tr("Block Player"),
|
||||
tr("When you block a player, you will no longer receive chat messages from "
|
||||
"them.<br><br>Are you sure you would like to block %1?")
|
||||
.arg(QString::fromStdString(nickname)),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (result == QMessageBox::Yes)
|
||||
block_list.emplace(nickname);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (has_mod_perms && nickname != cur_nickname) { // You can't kick or ban yourself
|
||||
context_menu.addSeparator();
|
||||
|
||||
QAction* kick_action = context_menu.addAction(tr("Kick"));
|
||||
QAction* ban_action = context_menu.addAction(tr("Ban"));
|
||||
|
||||
connect(kick_action, &QAction::triggered, [this, nickname] {
|
||||
QMessageBox::StandardButton result =
|
||||
QMessageBox::question(this, tr("Kick Player"),
|
||||
tr("Are you sure you would like to <b>kick</b> %1?")
|
||||
.arg(QString::fromStdString(nickname)),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (result == QMessageBox::Yes)
|
||||
SendModerationRequest(Network::IdModKick, nickname);
|
||||
});
|
||||
connect(ban_action, &QAction::triggered, [this, nickname] {
|
||||
QMessageBox::StandardButton result = QMessageBox::question(
|
||||
this, tr("Block Player"),
|
||||
tr("When you block a player, you will no longer receive chat messages from "
|
||||
"them.<br><br>Are you sure you would like to block %1?")
|
||||
this, tr("Ban Player"),
|
||||
tr("Are you sure you would like to <b>kick and ban</b> %1?\n\nThis would "
|
||||
"ban both their forum username and their IP address.")
|
||||
.arg(QString::fromStdString(nickname)),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (result == QMessageBox::Yes)
|
||||
block_list.emplace(nickname);
|
||||
}
|
||||
});
|
||||
SendModerationRequest(Network::IdModBan, nickname);
|
||||
});
|
||||
}
|
||||
|
||||
context_menu.exec(ui->player_view->viewport()->mapToGlobal(menu_location));
|
||||
}
|
||||
|
@@ -36,9 +36,12 @@ public:
|
||||
void AppendStatusMessage(const QString& msg);
|
||||
~ChatRoom();
|
||||
|
||||
void SetModPerms(bool is_mod);
|
||||
|
||||
public slots:
|
||||
void OnRoomUpdate(const Network::RoomInformation& info);
|
||||
void OnChatReceive(const Network::ChatEntry&);
|
||||
void OnStatusMessageReceive(const Network::StatusMessageEntry&);
|
||||
void OnSendChat();
|
||||
void OnChatTextChanged();
|
||||
void PopupContextMenu(const QPoint& menu_location);
|
||||
@@ -47,16 +50,25 @@ public slots:
|
||||
|
||||
signals:
|
||||
void ChatReceived(const Network::ChatEntry&);
|
||||
void StatusMessageReceived(const Network::StatusMessageEntry&);
|
||||
void UserPinged();
|
||||
|
||||
private:
|
||||
static constexpr u32 max_chat_lines = 1000;
|
||||
void AppendChatMessage(const QString&);
|
||||
bool ValidateMessage(const std::string&);
|
||||
void SendModerationRequest(Network::RoomMessageTypes type, const std::string& nickname);
|
||||
void UpdateIconDisplay();
|
||||
|
||||
bool has_mod_perms = false;
|
||||
QStandardItemModel* player_list;
|
||||
std::unique_ptr<Ui::ChatRoom> ui;
|
||||
std::unordered_set<std::string> block_list;
|
||||
std::unordered_map<std::string, QPixmap> icon_cache;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Network::ChatEntry);
|
||||
Q_DECLARE_METATYPE(Network::StatusMessageEntry);
|
||||
Q_DECLARE_METATYPE(Network::RoomInformation);
|
||||
Q_DECLARE_METATYPE(Network::RoomMember::State);
|
||||
Q_DECLARE_METATYPE(Network::RoomMember::Error);
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>607</width>
|
||||
<width>807</width>
|
||||
<height>432</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
@@ -13,6 +13,7 @@
|
||||
#include "citra_qt/game_list_p.h"
|
||||
#include "citra_qt/multiplayer/client_room.h"
|
||||
#include "citra_qt/multiplayer/message.h"
|
||||
#include "citra_qt/multiplayer/moderation_dialog.h"
|
||||
#include "citra_qt/multiplayer/state.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/announce_multiplayer_session.h"
|
||||
@@ -33,6 +34,8 @@ ClientRoomWindow::ClientRoomWindow(QWidget* parent)
|
||||
connect(this, &ClientRoomWindow::RoomInformationChanged, this,
|
||||
&ClientRoomWindow::OnRoomUpdate);
|
||||
connect(this, &ClientRoomWindow::StateChanged, this, &::ClientRoomWindow::OnStateChange);
|
||||
// Update the state
|
||||
OnStateChange(member->GetState());
|
||||
} else {
|
||||
// TODO (jroweboy) network was not initialized?
|
||||
}
|
||||
@@ -40,11 +43,25 @@ ClientRoomWindow::ClientRoomWindow(QWidget* parent)
|
||||
connect(ui->disconnect, &QPushButton::pressed, [this] { Disconnect(); });
|
||||
ui->disconnect->setDefault(false);
|
||||
ui->disconnect->setAutoDefault(false);
|
||||
connect(ui->moderation, &QPushButton::clicked, [this] {
|
||||
ModerationDialog dialog(this);
|
||||
dialog.exec();
|
||||
});
|
||||
ui->moderation->setDefault(false);
|
||||
ui->moderation->setAutoDefault(false);
|
||||
connect(ui->chat, &ChatRoom::UserPinged, this, &ClientRoomWindow::ShowNotification);
|
||||
UpdateView();
|
||||
}
|
||||
|
||||
ClientRoomWindow::~ClientRoomWindow() = default;
|
||||
|
||||
void ClientRoomWindow::SetModPerms(bool is_mod) {
|
||||
ui->chat->SetModPerms(is_mod);
|
||||
ui->moderation->setVisible(is_mod);
|
||||
ui->moderation->setDefault(false);
|
||||
ui->moderation->setAutoDefault(false);
|
||||
}
|
||||
|
||||
void ClientRoomWindow::RetranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
ui->chat->RetranslateUi();
|
||||
@@ -55,9 +72,12 @@ void ClientRoomWindow::OnRoomUpdate(const Network::RoomInformation& info) {
|
||||
}
|
||||
|
||||
void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) {
|
||||
if (state == Network::RoomMember::State::Joined) {
|
||||
if (state == Network::RoomMember::State::Joined ||
|
||||
state == Network::RoomMember::State::Moderator) {
|
||||
|
||||
ui->chat->Clear();
|
||||
ui->chat->AppendStatusMessage(tr("Connected"));
|
||||
SetModPerms(state == Network::RoomMember::State::Moderator);
|
||||
}
|
||||
UpdateView();
|
||||
}
|
||||
@@ -82,6 +102,7 @@ void ClientRoomWindow::UpdateView() {
|
||||
.arg(QString::fromStdString(information.name))
|
||||
.arg(memberlist.size())
|
||||
.arg(information.member_slots));
|
||||
ui->description->setText(QString::fromStdString(information.description));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -26,10 +26,12 @@ public slots:
|
||||
signals:
|
||||
void RoomInformationChanged(const Network::RoomInformation&);
|
||||
void StateChanged(const Network::RoomMember::State&);
|
||||
void ShowNotification();
|
||||
|
||||
private:
|
||||
void Disconnect();
|
||||
void UpdateView();
|
||||
void SetModPerms(bool is_mod);
|
||||
|
||||
QStandardItemModel* player_list;
|
||||
std::unique_ptr<Ui::ClientRoom> ui;
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>607</width>
|
||||
<width>807</width>
|
||||
<height>432</height>
|
||||
</rect>
|
||||
</property>
|
||||
@@ -21,6 +21,13 @@
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="description">
|
||||
<property name="text">
|
||||
<string>Room Description</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
@@ -34,6 +41,16 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="moderation">
|
||||
<property name="text">
|
||||
<string>Moderation...</string>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="disconnect">
|
||||
<property name="text">
|
||||
|
@@ -15,6 +15,7 @@
|
||||
#include "citra_qt/multiplayer/state.h"
|
||||
#include "citra_qt/multiplayer/validation.h"
|
||||
#include "citra_qt/ui_settings.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
#include "core/settings.h"
|
||||
#include "network/network.h"
|
||||
#include "ui_direct_connect.h"
|
||||
@@ -62,7 +63,7 @@ void DirectConnectWindow::Connect() {
|
||||
// Prevent the user from trying to join a room while they are already joining.
|
||||
if (member->GetState() == Network::RoomMember::State::Joining) {
|
||||
return;
|
||||
} else if (member->GetState() == Network::RoomMember::State::Joined) {
|
||||
} else if (member->IsConnected()) {
|
||||
// And ask if they want to leave the room if they are already in one.
|
||||
if (!NetworkMessage::WarnDisconnect()) {
|
||||
return;
|
||||
@@ -97,6 +98,7 @@ void DirectConnectWindow::Connect() {
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
auto port = UISettings::values.port.toUInt();
|
||||
room_member->Join(ui->nickname->text().toStdString(),
|
||||
Service::CFG::GetConsoleIdHash(Core::System::GetInstance()),
|
||||
ui->ip->text().toStdString().c_str(), port, 0,
|
||||
Network::NoPreferredMac, ui->password->text().toStdString().c_str());
|
||||
}
|
||||
@@ -120,7 +122,9 @@ void DirectConnectWindow::OnConnection() {
|
||||
EndConnecting();
|
||||
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
if (room_member->GetState() == Network::RoomMember::State::Joined) {
|
||||
if (room_member->GetState() == Network::RoomMember::State::Joined ||
|
||||
room_member->GetState() == Network::RoomMember::State::Moderator) {
|
||||
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
@@ -19,8 +19,12 @@
|
||||
#include "citra_qt/ui_settings.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/announce_multiplayer_session.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
#include "core/settings.h"
|
||||
#include "ui_host_room.h"
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
#include "web_service/verify_user_jwt.h"
|
||||
#endif
|
||||
|
||||
HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
|
||||
std::shared_ptr<Core::AnnounceMultiplayerSession> session)
|
||||
@@ -69,6 +73,7 @@ HostRoomWindow::HostRoomWindow(QWidget* parent, QStandardItemModel* list,
|
||||
if (index != -1) {
|
||||
ui->game_list->setCurrentIndex(index);
|
||||
}
|
||||
ui->room_description->setText(UISettings::values.room_description);
|
||||
}
|
||||
|
||||
HostRoomWindow::~HostRoomWindow() = default;
|
||||
@@ -77,6 +82,21 @@ void HostRoomWindow::RetranslateUi() {
|
||||
ui->retranslateUi(this);
|
||||
}
|
||||
|
||||
std::unique_ptr<Network::VerifyUser::Backend> HostRoomWindow::CreateVerifyBackend(
|
||||
bool use_validation) const {
|
||||
std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
|
||||
if (use_validation) {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
verify_backend = std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url);
|
||||
#else
|
||||
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
|
||||
#endif
|
||||
} else {
|
||||
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
|
||||
}
|
||||
return verify_backend;
|
||||
}
|
||||
|
||||
void HostRoomWindow::Host() {
|
||||
if (!ui->username->hasAcceptableInput()) {
|
||||
NetworkMessage::ShowError(NetworkMessage::USERNAME_NOT_VALID);
|
||||
@@ -93,7 +113,7 @@ void HostRoomWindow::Host() {
|
||||
if (auto member = Network::GetRoomMember().lock()) {
|
||||
if (member->GetState() == Network::RoomMember::State::Joining) {
|
||||
return;
|
||||
} else if (member->GetState() == Network::RoomMember::State::Joined) {
|
||||
} else if (member->IsConnected()) {
|
||||
auto parent = static_cast<MultiplayerState*>(parentWidget());
|
||||
if (!parent->OnCloseRoom()) {
|
||||
close();
|
||||
@@ -106,9 +126,17 @@ void HostRoomWindow::Host() {
|
||||
auto game_id = ui->game_list->currentData(GameListItemPath::ProgramIdRole).toLongLong();
|
||||
auto port = ui->port->isModified() ? ui->port->text().toInt() : Network::DefaultRoomPort;
|
||||
auto password = ui->password->text().toStdString();
|
||||
const bool is_public = ui->host_type->currentIndex() == 0;
|
||||
Network::Room::BanList ban_list{};
|
||||
if (ui->load_ban_list->isChecked()) {
|
||||
ban_list = UISettings::values.ban_list;
|
||||
}
|
||||
if (auto room = Network::GetRoom().lock()) {
|
||||
bool created = room->Create(ui->room_name->text().toStdString(), "", port, password,
|
||||
ui->max_player->value(), game_name.toStdString(), game_id);
|
||||
bool created = room->Create(ui->room_name->text().toStdString(),
|
||||
ui->room_description->toPlainText().toStdString(), "", port,
|
||||
password, ui->max_player->value(),
|
||||
Settings::values.citra_username, game_name.toStdString(),
|
||||
game_id, CreateVerifyBackend(is_public), ban_list);
|
||||
if (!created) {
|
||||
NetworkMessage::ShowError(NetworkMessage::COULD_NOT_CREATE_ROOM);
|
||||
LOG_ERROR(Network, "Could not create room!");
|
||||
@@ -116,8 +144,34 @@ void HostRoomWindow::Host() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
member->Join(ui->username->text().toStdString(), "127.0.0.1", port, 0,
|
||||
Network::NoPreferredMac, password);
|
||||
// Start the announce session if they chose Public
|
||||
if (is_public) {
|
||||
if (auto session = announce_multiplayer_session.lock()) {
|
||||
// Register the room first to ensure verify_UID is present when we connect
|
||||
session->Register();
|
||||
session->Start();
|
||||
} else {
|
||||
LOG_ERROR(Network, "Starting announce session failed");
|
||||
}
|
||||
}
|
||||
std::string token;
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
if (is_public) {
|
||||
WebService::Client client(Settings::values.web_api_url, Settings::values.citra_username,
|
||||
Settings::values.citra_token);
|
||||
if (auto room = Network::GetRoom().lock()) {
|
||||
token = client.GetExternalJWT(room->GetVerifyUID()).returned_data;
|
||||
}
|
||||
if (token.empty()) {
|
||||
LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
|
||||
} else {
|
||||
LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
member->Join(ui->username->text().toStdString(),
|
||||
Service::CFG::GetConsoleIdHash(Core::System::GetInstance()), "127.0.0.1", port,
|
||||
0, Network::NoPreferredMac, password, token);
|
||||
|
||||
// Store settings
|
||||
UISettings::values.room_nickname = ui->username->text();
|
||||
@@ -130,25 +184,10 @@ void HostRoomWindow::Host() {
|
||||
UISettings::values.room_port = (ui->port->isModified() && !ui->port->text().isEmpty())
|
||||
? ui->port->text()
|
||||
: QString::number(Network::DefaultRoomPort);
|
||||
UISettings::values.room_description = ui->room_description->toPlainText();
|
||||
Settings::Apply();
|
||||
OnConnection();
|
||||
}
|
||||
}
|
||||
|
||||
void HostRoomWindow::OnConnection() {
|
||||
ui->host->setEnabled(true);
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
if (room_member->GetState() == Network::RoomMember::State::Joining) {
|
||||
// Start the announce session if they chose Public
|
||||
if (ui->host_type->currentIndex() == 0) {
|
||||
if (auto session = announce_multiplayer_session.lock()) {
|
||||
session->Start();
|
||||
} else {
|
||||
LOG_ERROR(Network, "Starting announce session failed");
|
||||
}
|
||||
}
|
||||
close();
|
||||
}
|
||||
ui->host->setEnabled(true);
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -26,6 +26,10 @@ class ComboBoxProxyModel;
|
||||
|
||||
class ChatMessage;
|
||||
|
||||
namespace Network::VerifyUser {
|
||||
class Backend;
|
||||
};
|
||||
|
||||
class HostRoomWindow : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -36,15 +40,9 @@ public:
|
||||
|
||||
void RetranslateUi();
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* Handler for connection status changes. Launches the chat window if successful or
|
||||
* displays an error
|
||||
*/
|
||||
void OnConnection();
|
||||
|
||||
private:
|
||||
void Host();
|
||||
std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) const;
|
||||
|
||||
std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
|
||||
QStandardItemModel* game_list;
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>607</width>
|
||||
<height>165</height>
|
||||
<height>211</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -131,6 +131,34 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Room Description</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="room_description"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="load_ban_list">
|
||||
<property name="text">
|
||||
<string>Load Previous Ban List</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="rightMargin">
|
||||
|
@@ -15,8 +15,12 @@
|
||||
#include "citra_qt/multiplayer/validation.h"
|
||||
#include "citra_qt/ui_settings.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
#include "core/settings.h"
|
||||
#include "network/network.h"
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
#include "web_service/web_backend.h"
|
||||
#endif
|
||||
|
||||
Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
|
||||
std::shared_ptr<Core::AnnounceMultiplayerSession> session)
|
||||
@@ -105,7 +109,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
||||
// Prevent the user from trying to join a room while they are already joining.
|
||||
if (member->GetState() == Network::RoomMember::State::Joining) {
|
||||
return;
|
||||
} else if (member->GetState() == Network::RoomMember::State::Joined) {
|
||||
} else if (member->IsConnected()) {
|
||||
// And ask if they want to leave the room if they are already in one.
|
||||
if (!NetworkMessage::WarnDisconnect()) {
|
||||
return;
|
||||
@@ -135,11 +139,27 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
||||
const std::string ip =
|
||||
proxy->data(connection_index, LobbyItemHost::HostIPRole).toString().toStdString();
|
||||
int port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt();
|
||||
const std::string verify_UID =
|
||||
proxy->data(connection_index, LobbyItemHost::HostVerifyUIDRole).toString().toStdString();
|
||||
|
||||
// attempt to connect in a different thread
|
||||
QFuture<void> f = QtConcurrent::run([nickname, ip, port, password] {
|
||||
QFuture<void> f = QtConcurrent::run([nickname, ip, port, password, verify_UID] {
|
||||
std::string token;
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
if (!Settings::values.citra_username.empty() && !Settings::values.citra_token.empty()) {
|
||||
WebService::Client client(Settings::values.web_api_url, Settings::values.citra_username,
|
||||
Settings::values.citra_token);
|
||||
token = client.GetExternalJWT(verify_UID).returned_data;
|
||||
if (token.empty()) {
|
||||
LOG_ERROR(WebService, "Could not get external JWT, verification may fail");
|
||||
} else {
|
||||
LOG_INFO(WebService, "Successfully requested external JWT: size={}", token.size());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredMac, password);
|
||||
room_member->Join(nickname, Service::CFG::GetConsoleIdHash(Core::System::GetInstance()),
|
||||
ip.c_str(), port, 0, Network::NoPreferredMac, password, token);
|
||||
}
|
||||
});
|
||||
watcher->setFuture(f);
|
||||
@@ -191,7 +211,8 @@ void Lobby::OnRefreshLobby() {
|
||||
QList<QVariant> members;
|
||||
for (auto member : room.members) {
|
||||
QVariant var;
|
||||
var.setValue(LobbyMember{QString::fromStdString(member.name), member.game_id,
|
||||
var.setValue(LobbyMember{QString::fromStdString(member.username),
|
||||
QString::fromStdString(member.nickname), member.game_id,
|
||||
QString::fromStdString(member.game_name)});
|
||||
members.append(var);
|
||||
}
|
||||
@@ -203,14 +224,18 @@ void Lobby::OnRefreshLobby() {
|
||||
new LobbyItemGame(room.preferred_game_id, QString::fromStdString(room.preferred_game),
|
||||
smdh_icon),
|
||||
new LobbyItemHost(QString::fromStdString(room.owner), QString::fromStdString(room.ip),
|
||||
room.port),
|
||||
room.port, QString::fromStdString(room.verify_UID)),
|
||||
new LobbyItemMemberList(members, room.max_player),
|
||||
});
|
||||
model->appendRow(row);
|
||||
// To make the rows expandable, add the member data as a child of the first column of the
|
||||
// rows with people in them and have qt set them to colspan after the model is finished
|
||||
// resetting
|
||||
if (room.members.size() > 0) {
|
||||
if (!room.description.empty()) {
|
||||
first_item->appendRow(
|
||||
new LobbyItemDescription(QString::fromStdString(room.description)));
|
||||
}
|
||||
if (!room.members.empty()) {
|
||||
first_item->appendRow(new LobbyItemExpandedMemberList(members));
|
||||
}
|
||||
}
|
||||
@@ -226,8 +251,8 @@ void Lobby::OnRefreshLobby() {
|
||||
// Set the member list child items to span all columns
|
||||
for (int i = 0; i < proxy->rowCount(); i++) {
|
||||
auto parent = model->item(i, 0);
|
||||
if (parent->hasChildren()) {
|
||||
ui->room_list->setFirstColumnSpanned(0, proxy->index(i, 0), true);
|
||||
for (int j = 0; j < parent->rowCount(); j++) {
|
||||
ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -55,6 +55,31 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class LobbyItemDescription : public LobbyItem {
|
||||
public:
|
||||
static const int DescriptionRole = Qt::UserRole + 1;
|
||||
|
||||
LobbyItemDescription() = default;
|
||||
explicit LobbyItemDescription(QString description) {
|
||||
setData(description, DescriptionRole);
|
||||
}
|
||||
|
||||
QVariant data(int role) const override {
|
||||
if (role != Qt::DisplayRole) {
|
||||
return LobbyItem::data(role);
|
||||
}
|
||||
auto description = data(DescriptionRole).toString();
|
||||
description.prepend("Description: ");
|
||||
return description;
|
||||
}
|
||||
|
||||
bool operator<(const QStandardItem& other) const override {
|
||||
return data(DescriptionRole)
|
||||
.toString()
|
||||
.localeAwareCompare(other.data(DescriptionRole).toString()) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
class LobbyItemGame : public LobbyItem {
|
||||
public:
|
||||
static const int TitleIDRole = Qt::UserRole + 1;
|
||||
@@ -95,12 +120,14 @@ public:
|
||||
static const int HostUsernameRole = Qt::UserRole + 1;
|
||||
static const int HostIPRole = Qt::UserRole + 2;
|
||||
static const int HostPortRole = Qt::UserRole + 3;
|
||||
static const int HostVerifyUIDRole = Qt::UserRole + 4;
|
||||
|
||||
LobbyItemHost() = default;
|
||||
explicit LobbyItemHost(QString username, QString ip, u16 port) {
|
||||
explicit LobbyItemHost(QString username, QString ip, u16 port, QString verify_UID) {
|
||||
setData(username, HostUsernameRole);
|
||||
setData(ip, HostIPRole);
|
||||
setData(port, HostPortRole);
|
||||
setData(verify_UID, HostVerifyUIDRole);
|
||||
}
|
||||
|
||||
QVariant data(int role) const override {
|
||||
@@ -121,12 +148,17 @@ class LobbyMember {
|
||||
public:
|
||||
LobbyMember() = default;
|
||||
LobbyMember(const LobbyMember& other) = default;
|
||||
explicit LobbyMember(QString username, u64 title_id, QString game_name)
|
||||
: username(std::move(username)), title_id(title_id), game_name(std::move(game_name)) {}
|
||||
explicit LobbyMember(QString username, QString nickname, u64 title_id, QString game_name)
|
||||
: username(std::move(username)), nickname(std::move(nickname)), title_id(title_id),
|
||||
game_name(std::move(game_name)) {}
|
||||
~LobbyMember() = default;
|
||||
|
||||
QString GetUsername() const {
|
||||
return username;
|
||||
QString GetName() const {
|
||||
if (username.isEmpty() || username == nickname) {
|
||||
return nickname;
|
||||
} else {
|
||||
return QString("%1 (%2)").arg(nickname, username);
|
||||
}
|
||||
}
|
||||
u64 GetTitleId() const {
|
||||
return title_id;
|
||||
@@ -137,6 +169,7 @@ public:
|
||||
|
||||
private:
|
||||
QString username;
|
||||
QString nickname;
|
||||
u64 title_id;
|
||||
QString game_name;
|
||||
};
|
||||
@@ -195,10 +228,9 @@ public:
|
||||
out += '\n';
|
||||
const auto& m = member.value<LobbyMember>();
|
||||
if (m.GetGameName().isEmpty()) {
|
||||
out += QString(QObject::tr("%1 is not playing a game")).arg(m.GetUsername());
|
||||
out += QString(QObject::tr("%1 is not playing a game")).arg(m.GetName());
|
||||
} else {
|
||||
out +=
|
||||
QString(QObject::tr("%1 is playing %2")).arg(m.GetUsername(), m.GetGameName());
|
||||
out += QString(QObject::tr("%1 is playing %2")).arg(m.GetName(), m.GetGameName());
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
|
@@ -12,8 +12,8 @@ const ConnectionError USERNAME_NOT_VALID(
|
||||
QT_TR_NOOP("Username is not valid. Must be 4 to 20 alphanumeric characters."));
|
||||
const ConnectionError ROOMNAME_NOT_VALID(
|
||||
QT_TR_NOOP("Room name is not valid. Must be 4 to 20 alphanumeric characters."));
|
||||
const ConnectionError USERNAME_IN_USE(
|
||||
QT_TR_NOOP("Username is already in use. Please choose another."));
|
||||
const ConnectionError USERNAME_NOT_VALID_SERVER(
|
||||
QT_TR_NOOP("Username is already in use or not valid. Please choose another."));
|
||||
const ConnectionError IP_ADDRESS_NOT_VALID(QT_TR_NOOP("IP is not a valid IPv4 address."));
|
||||
const ConnectionError PORT_NOT_VALID(QT_TR_NOOP("Port must be a number between 0 to 65535."));
|
||||
const ConnectionError NO_INTERNET(
|
||||
@@ -22,6 +22,8 @@ const ConnectionError UNABLE_TO_CONNECT(
|
||||
QT_TR_NOOP("Unable to connect to the host. Verify that the connection settings are correct. If "
|
||||
"you still cannot connect, contact the room host and verify that the host is "
|
||||
"properly configured with the external port forwarded."));
|
||||
const ConnectionError ROOM_IS_FULL(
|
||||
QT_TR_NOOP("Unable to connect to the room because it is already full."));
|
||||
const ConnectionError COULD_NOT_CREATE_ROOM(
|
||||
QT_TR_NOOP("Creating a room failed. Please retry. Restarting Citra might be necessary."));
|
||||
const ConnectionError HOST_BANNED(
|
||||
@@ -34,8 +36,16 @@ const ConnectionError WRONG_PASSWORD(QT_TR_NOOP("Incorrect password."));
|
||||
const ConnectionError GENERIC_ERROR(
|
||||
QT_TR_NOOP("An unknown error occured. If this error continues to occur, please open an issue"));
|
||||
const ConnectionError LOST_CONNECTION(QT_TR_NOOP("Connection to room lost. Try to reconnect."));
|
||||
const ConnectionError HOST_KICKED(QT_TR_NOOP("You have been kicked by the room host."));
|
||||
const ConnectionError MAC_COLLISION(
|
||||
QT_TR_NOOP("MAC address is already in use. Please choose another."));
|
||||
const ConnectionError CONSOLE_ID_COLLISION(QT_TR_NOOP(
|
||||
"Your Console ID conflicted with someone else's in the room.\n\nPlease go to Emulation "
|
||||
"> Configure > System to regenerate your Console ID."));
|
||||
const ConnectionError PERMISSION_DENIED(
|
||||
QT_TR_NOOP("You do not have enough permission to perform this action."));
|
||||
const ConnectionError NO_SUCH_USER(QT_TR_NOOP(
|
||||
"The user you are trying to kick/ban could not be found.\nThey may have left the room."));
|
||||
|
||||
static bool WarnMessage(const std::string& title, const std::string& text) {
|
||||
return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()),
|
||||
|
@@ -20,20 +20,27 @@ private:
|
||||
std::string err;
|
||||
};
|
||||
|
||||
/// When the nickname is considered invalid by the client
|
||||
extern const ConnectionError USERNAME_NOT_VALID;
|
||||
extern const ConnectionError ROOMNAME_NOT_VALID;
|
||||
extern const ConnectionError USERNAME_IN_USE;
|
||||
/// When the nickname is considered invalid by the room server
|
||||
extern const ConnectionError USERNAME_NOT_VALID_SERVER;
|
||||
extern const ConnectionError IP_ADDRESS_NOT_VALID;
|
||||
extern const ConnectionError PORT_NOT_VALID;
|
||||
extern const ConnectionError NO_INTERNET;
|
||||
extern const ConnectionError UNABLE_TO_CONNECT;
|
||||
extern const ConnectionError ROOM_IS_FULL;
|
||||
extern const ConnectionError COULD_NOT_CREATE_ROOM;
|
||||
extern const ConnectionError HOST_BANNED;
|
||||
extern const ConnectionError WRONG_VERSION;
|
||||
extern const ConnectionError WRONG_PASSWORD;
|
||||
extern const ConnectionError GENERIC_ERROR;
|
||||
extern const ConnectionError LOST_CONNECTION;
|
||||
extern const ConnectionError HOST_KICKED;
|
||||
extern const ConnectionError MAC_COLLISION;
|
||||
extern const ConnectionError CONSOLE_ID_COLLISION;
|
||||
extern const ConnectionError PERMISSION_DENIED;
|
||||
extern const ConnectionError NO_SUCH_USER;
|
||||
|
||||
/**
|
||||
* Shows a standard QMessageBox with a error message
|
||||
|
113
src/citra_qt/multiplayer/moderation_dialog.cpp
Normal file
113
src/citra_qt/multiplayer/moderation_dialog.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QStandardItem>
|
||||
#include <QStandardItemModel>
|
||||
#include "citra_qt/multiplayer/moderation_dialog.h"
|
||||
#include "network/network.h"
|
||||
#include "network/room_member.h"
|
||||
#include "ui_moderation_dialog.h"
|
||||
|
||||
namespace Column {
|
||||
enum {
|
||||
SUBJECT,
|
||||
TYPE,
|
||||
COUNT,
|
||||
};
|
||||
}
|
||||
|
||||
ModerationDialog::ModerationDialog(QWidget* parent)
|
||||
: QDialog(parent), ui(std::make_unique<Ui::ModerationDialog>()) {
|
||||
ui->setupUi(this);
|
||||
|
||||
qRegisterMetaType<Network::Room::BanList>();
|
||||
|
||||
if (auto member = Network::GetRoomMember().lock()) {
|
||||
callback_handle_status_message = member->BindOnStatusMessageReceived(
|
||||
[this](const Network::StatusMessageEntry& status_message) {
|
||||
emit StatusMessageReceived(status_message);
|
||||
});
|
||||
connect(this, &ModerationDialog::StatusMessageReceived, this,
|
||||
&ModerationDialog::OnStatusMessageReceived);
|
||||
callback_handle_ban_list = member->BindOnBanListReceived(
|
||||
[this](const Network::Room::BanList& ban_list) { emit BanListReceived(ban_list); });
|
||||
connect(this, &ModerationDialog::BanListReceived, this, &ModerationDialog::PopulateBanList);
|
||||
}
|
||||
|
||||
// Initialize the UI
|
||||
model = new QStandardItemModel(ui->ban_list_view);
|
||||
model->insertColumns(0, Column::COUNT);
|
||||
model->setHeaderData(Column::SUBJECT, Qt::Horizontal, tr("Subject"));
|
||||
model->setHeaderData(Column::TYPE, Qt::Horizontal, tr("Type"));
|
||||
|
||||
ui->ban_list_view->setModel(model);
|
||||
|
||||
// Load the ban list in background
|
||||
LoadBanList();
|
||||
|
||||
connect(ui->refresh, &QPushButton::clicked, this, [this] { LoadBanList(); });
|
||||
connect(ui->unban, &QPushButton::clicked, this, [this] {
|
||||
auto index = ui->ban_list_view->currentIndex();
|
||||
SendUnbanRequest(model->item(index.row(), 0)->text());
|
||||
});
|
||||
connect(ui->ban_list_view, &QTreeView::clicked, [this] { ui->unban->setEnabled(true); });
|
||||
}
|
||||
|
||||
ModerationDialog::~ModerationDialog() {
|
||||
if (callback_handle_status_message) {
|
||||
if (auto room = Network::GetRoomMember().lock()) {
|
||||
room->Unbind(callback_handle_status_message);
|
||||
}
|
||||
}
|
||||
|
||||
if (callback_handle_ban_list) {
|
||||
if (auto room = Network::GetRoomMember().lock()) {
|
||||
room->Unbind(callback_handle_ban_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModerationDialog::LoadBanList() {
|
||||
if (auto room = Network::GetRoomMember().lock()) {
|
||||
ui->refresh->setEnabled(false);
|
||||
ui->refresh->setText(tr("Refreshing"));
|
||||
ui->unban->setEnabled(false);
|
||||
room->RequestBanList();
|
||||
}
|
||||
}
|
||||
|
||||
void ModerationDialog::PopulateBanList(const Network::Room::BanList& ban_list) {
|
||||
model->removeRows(0, model->rowCount());
|
||||
for (const auto& username : ban_list.first) {
|
||||
QStandardItem* subject_item = new QStandardItem(QString::fromStdString(username));
|
||||
QStandardItem* type_item = new QStandardItem(tr("Forum Username"));
|
||||
model->invisibleRootItem()->appendRow({subject_item, type_item});
|
||||
}
|
||||
for (const auto& ip : ban_list.second) {
|
||||
QStandardItem* subject_item = new QStandardItem(QString::fromStdString(ip));
|
||||
QStandardItem* type_item = new QStandardItem(tr("IP Address"));
|
||||
model->invisibleRootItem()->appendRow({subject_item, type_item});
|
||||
}
|
||||
for (int i = 0; i < Column::COUNT - 1; ++i) {
|
||||
ui->ban_list_view->resizeColumnToContents(i);
|
||||
}
|
||||
ui->refresh->setEnabled(true);
|
||||
ui->refresh->setText(tr("Refresh"));
|
||||
ui->unban->setEnabled(false);
|
||||
}
|
||||
|
||||
void ModerationDialog::SendUnbanRequest(const QString& subject) {
|
||||
if (auto room = Network::GetRoomMember().lock()) {
|
||||
room->SendModerationRequest(Network::IdModUnban, subject.toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
void ModerationDialog::OnStatusMessageReceived(const Network::StatusMessageEntry& status_message) {
|
||||
if (status_message.type != Network::IdMemberBanned &&
|
||||
status_message.type != Network::IdAddressUnbanned)
|
||||
return;
|
||||
|
||||
// Update the ban list for ban/unban
|
||||
LoadBanList();
|
||||
}
|
42
src/citra_qt/multiplayer/moderation_dialog.h
Normal file
42
src/citra_qt/multiplayer/moderation_dialog.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <QDialog>
|
||||
#include "network/room.h"
|
||||
#include "network/room_member.h"
|
||||
|
||||
namespace Ui {
|
||||
class ModerationDialog;
|
||||
}
|
||||
|
||||
class QStandardItemModel;
|
||||
|
||||
class ModerationDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ModerationDialog(QWidget* parent = nullptr);
|
||||
~ModerationDialog();
|
||||
|
||||
signals:
|
||||
void StatusMessageReceived(const Network::StatusMessageEntry&);
|
||||
void BanListReceived(const Network::Room::BanList&);
|
||||
|
||||
private:
|
||||
void LoadBanList();
|
||||
void PopulateBanList(const Network::Room::BanList& ban_list);
|
||||
void SendUnbanRequest(const QString& subject);
|
||||
void OnStatusMessageReceived(const Network::StatusMessageEntry& status_message);
|
||||
|
||||
std::unique_ptr<Ui::ModerationDialog> ui;
|
||||
QStandardItemModel* model;
|
||||
Network::RoomMember::CallbackHandle<Network::StatusMessageEntry> callback_handle_status_message;
|
||||
Network::RoomMember::CallbackHandle<Network::Room::BanList> callback_handle_ban_list;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Network::Room::BanList);
|
84
src/citra_qt/multiplayer/moderation_dialog.ui
Normal file
84
src/citra_qt/multiplayer/moderation_dialog.ui
Normal file
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ModerationDialog</class>
|
||||
<widget class="QDialog" name="ModerationDialog">
|
||||
<property name="windowTitle">
|
||||
<string>Moderation</string>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="ban_list_group_box">
|
||||
<property name="title">
|
||||
<string>Ban List</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refresh">
|
||||
<property name="text">
|
||||
<string>Refreshing</string>
|
||||
</property>
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="unban">
|
||||
<property name="text">
|
||||
<string>Unban</string>
|
||||
</property>
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="ban_list_view"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ModerationDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
</connection>
|
||||
</connections>
|
||||
<resources/>
|
||||
</ui>
|
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QIcon>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardItemModel>
|
||||
@@ -13,6 +14,7 @@
|
||||
#include "citra_qt/multiplayer/lobby.h"
|
||||
#include "citra_qt/multiplayer/message.h"
|
||||
#include "citra_qt/multiplayer/state.h"
|
||||
#include "citra_qt/ui_settings.h"
|
||||
#include "citra_qt/util/clickable_label.h"
|
||||
#include "common/announce_multiplayer_room.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -27,9 +29,13 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
|
||||
[this](const Network::RoomMember::State& state) { emit NetworkStateChanged(state); });
|
||||
connect(this, &MultiplayerState::NetworkStateChanged, this,
|
||||
&MultiplayerState::OnNetworkStateChanged);
|
||||
error_callback_handle = member->BindOnError(
|
||||
[this](const Network::RoomMember::Error& error) { emit NetworkError(error); });
|
||||
connect(this, &MultiplayerState::NetworkError, this, &MultiplayerState::OnNetworkError);
|
||||
}
|
||||
|
||||
qRegisterMetaType<Network::RoomMember::State>();
|
||||
qRegisterMetaType<Network::RoomMember::Error>();
|
||||
qRegisterMetaType<Common::WebResult>();
|
||||
announce_multiplayer_session = std::make_shared<Core::AnnounceMultiplayerSession>();
|
||||
announce_multiplayer_session->BindErrorCallback(
|
||||
@@ -44,6 +50,13 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
|
||||
|
||||
connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
|
||||
connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
|
||||
|
||||
connect(static_cast<QApplication*>(QApplication::instance()), &QApplication::focusChanged, this,
|
||||
[this](QWidget* /*old*/, QWidget* now) {
|
||||
if (client_room && client_room->isAncestorOf(now)) {
|
||||
HideNotification();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MultiplayerState::~MultiplayerState() {
|
||||
@@ -52,6 +65,12 @@ MultiplayerState::~MultiplayerState() {
|
||||
member->Unbind(state_callback_handle);
|
||||
}
|
||||
}
|
||||
|
||||
if (error_callback_handle) {
|
||||
if (auto member = Network::GetRoomMember().lock()) {
|
||||
member->Unbind(error_callback_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerState::Close() {
|
||||
@@ -70,7 +89,9 @@ void MultiplayerState::retranslateUi() {
|
||||
|
||||
if (current_state == Network::RoomMember::State::Uninitialized) {
|
||||
status_text->setText(tr("Not Connected. Click here to find a room!"));
|
||||
} else if (current_state == Network::RoomMember::State::Joined) {
|
||||
} else if (current_state == Network::RoomMember::State::Joined ||
|
||||
current_state == Network::RoomMember::State::Moderator) {
|
||||
|
||||
status_text->setText(tr("Connected"));
|
||||
} else {
|
||||
status_text->setText(tr("Not Connected"));
|
||||
@@ -88,35 +109,10 @@ void MultiplayerState::retranslateUi() {
|
||||
|
||||
void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) {
|
||||
LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state));
|
||||
bool is_connected = false;
|
||||
switch (state) {
|
||||
case Network::RoomMember::State::LostConnection:
|
||||
NetworkMessage::ShowError(NetworkMessage::LOST_CONNECTION);
|
||||
break;
|
||||
case Network::RoomMember::State::CouldNotConnect:
|
||||
NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT);
|
||||
break;
|
||||
case Network::RoomMember::State::NameCollision:
|
||||
NetworkMessage::ShowError(NetworkMessage::USERNAME_IN_USE);
|
||||
break;
|
||||
case Network::RoomMember::State::MacCollision:
|
||||
NetworkMessage::ShowError(NetworkMessage::MAC_COLLISION);
|
||||
break;
|
||||
case Network::RoomMember::State::WrongPassword:
|
||||
NetworkMessage::ShowError(NetworkMessage::WRONG_PASSWORD);
|
||||
break;
|
||||
case Network::RoomMember::State::WrongVersion:
|
||||
NetworkMessage::ShowError(NetworkMessage::WRONG_VERSION);
|
||||
break;
|
||||
case Network::RoomMember::State::Error:
|
||||
NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT);
|
||||
break;
|
||||
case Network::RoomMember::State::Joined:
|
||||
is_connected = true;
|
||||
if (state == Network::RoomMember::State::Joined ||
|
||||
state == Network::RoomMember::State::Moderator) {
|
||||
|
||||
OnOpenNetworkRoom();
|
||||
break;
|
||||
}
|
||||
if (is_connected) {
|
||||
status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16));
|
||||
status_text->setText(tr("Connected"));
|
||||
leave_room->setEnabled(true);
|
||||
@@ -131,6 +127,51 @@ void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& s
|
||||
current_state = state;
|
||||
}
|
||||
|
||||
void MultiplayerState::OnNetworkError(const Network::RoomMember::Error& error) {
|
||||
LOG_DEBUG(Frontend, "Network Error: {}", Network::GetErrorStr(error));
|
||||
switch (error) {
|
||||
case Network::RoomMember::Error::LostConnection:
|
||||
NetworkMessage::ShowError(NetworkMessage::LOST_CONNECTION);
|
||||
break;
|
||||
case Network::RoomMember::Error::HostKicked:
|
||||
NetworkMessage::ShowError(NetworkMessage::HOST_KICKED);
|
||||
break;
|
||||
case Network::RoomMember::Error::CouldNotConnect:
|
||||
NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT);
|
||||
break;
|
||||
case Network::RoomMember::Error::NameCollision:
|
||||
NetworkMessage::ShowError(NetworkMessage::USERNAME_NOT_VALID_SERVER);
|
||||
break;
|
||||
case Network::RoomMember::Error::MacCollision:
|
||||
NetworkMessage::ShowError(NetworkMessage::MAC_COLLISION);
|
||||
break;
|
||||
case Network::RoomMember::Error::ConsoleIdCollision:
|
||||
NetworkMessage::ShowError(NetworkMessage::CONSOLE_ID_COLLISION);
|
||||
break;
|
||||
case Network::RoomMember::Error::RoomIsFull:
|
||||
NetworkMessage::ShowError(NetworkMessage::ROOM_IS_FULL);
|
||||
break;
|
||||
case Network::RoomMember::Error::WrongPassword:
|
||||
NetworkMessage::ShowError(NetworkMessage::WRONG_PASSWORD);
|
||||
break;
|
||||
case Network::RoomMember::Error::WrongVersion:
|
||||
NetworkMessage::ShowError(NetworkMessage::WRONG_VERSION);
|
||||
break;
|
||||
case Network::RoomMember::Error::HostBanned:
|
||||
NetworkMessage::ShowError(NetworkMessage::HOST_BANNED);
|
||||
break;
|
||||
case Network::RoomMember::Error::UnknownError:
|
||||
NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT);
|
||||
break;
|
||||
case Network::RoomMember::Error::PermissionDenied:
|
||||
NetworkMessage::ShowError(NetworkMessage::PERMISSION_DENIED);
|
||||
break;
|
||||
case Network::RoomMember::Error::NoSuchUser:
|
||||
NetworkMessage::ShowError(NetworkMessage::NO_SUCH_USER);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerState::OnAnnounceFailed(const Common::WebResult& result) {
|
||||
announce_multiplayer_session->Stop();
|
||||
QMessageBox::warning(
|
||||
@@ -144,7 +185,11 @@ void MultiplayerState::OnAnnounceFailed(const Common::WebResult& result) {
|
||||
}
|
||||
|
||||
void MultiplayerState::UpdateThemedIcons() {
|
||||
if (current_state == Network::RoomMember::State::Joined) {
|
||||
if (show_notification) {
|
||||
status_icon->setPixmap(QIcon::fromTheme("connected_notification").pixmap(16));
|
||||
} else if (current_state == Network::RoomMember::State::Joined ||
|
||||
current_state == Network::RoomMember::State::Moderator) {
|
||||
|
||||
status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16));
|
||||
} else {
|
||||
status_icon->setPixmap(QIcon::fromTheme("disconnected").pixmap(16));
|
||||
@@ -185,6 +230,10 @@ bool MultiplayerState::OnCloseRoom() {
|
||||
if (room->GetState() != Network::Room::State::Open) {
|
||||
return true;
|
||||
}
|
||||
// Save ban list
|
||||
if (auto room = Network::GetRoom().lock()) {
|
||||
UISettings::values.ban_list = std::move(room->GetBanList());
|
||||
}
|
||||
room->Destroy();
|
||||
announce_multiplayer_session->Stop();
|
||||
LOG_DEBUG(Frontend, "Closed the room (as a server)");
|
||||
@@ -192,11 +241,28 @@ bool MultiplayerState::OnCloseRoom() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void MultiplayerState::ShowNotification() {
|
||||
if (client_room && client_room->isAncestorOf(QApplication::focusWidget()))
|
||||
return; // Do not show notification if the chat window currently has focus
|
||||
show_notification = true;
|
||||
QApplication::alert(nullptr);
|
||||
status_icon->setPixmap(QIcon::fromTheme("connected_notification").pixmap(16));
|
||||
status_text->setText(tr("New Messages Received"));
|
||||
}
|
||||
|
||||
void MultiplayerState::HideNotification() {
|
||||
show_notification = false;
|
||||
status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16));
|
||||
status_text->setText(tr("Connected"));
|
||||
}
|
||||
|
||||
void MultiplayerState::OnOpenNetworkRoom() {
|
||||
if (auto member = Network::GetRoomMember().lock()) {
|
||||
if (member->IsConnected()) {
|
||||
if (client_room == nullptr) {
|
||||
client_room = new ClientRoomWindow(this);
|
||||
connect(client_room, &ClientRoomWindow::ShowNotification, this,
|
||||
&MultiplayerState::ShowNotification);
|
||||
}
|
||||
BringWidgetToFront(client_room);
|
||||
return;
|
||||
|
@@ -40,6 +40,7 @@ public:
|
||||
|
||||
public slots:
|
||||
void OnNetworkStateChanged(const Network::RoomMember::State& state);
|
||||
void OnNetworkError(const Network::RoomMember::Error& error);
|
||||
void OnViewLobby();
|
||||
void OnCreateRoom();
|
||||
bool OnCloseRoom();
|
||||
@@ -47,9 +48,12 @@ public slots:
|
||||
void OnDirectConnectToRoom();
|
||||
void OnAnnounceFailed(const Common::WebResult&);
|
||||
void UpdateThemedIcons();
|
||||
void ShowNotification();
|
||||
void HideNotification();
|
||||
|
||||
signals:
|
||||
void NetworkStateChanged(const Network::RoomMember::State&);
|
||||
void NetworkError(const Network::RoomMember::Error&);
|
||||
void AnnounceFailed(const Common::WebResult&);
|
||||
|
||||
private:
|
||||
@@ -64,7 +68,11 @@ private:
|
||||
QAction* show_room;
|
||||
std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
|
||||
Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized;
|
||||
bool has_mod_perms = false;
|
||||
Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle;
|
||||
Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle;
|
||||
|
||||
bool show_notification = false;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(Common::WebResult);
|
||||
|
@@ -5,6 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <QByteArray>
|
||||
#include <QMetaType>
|
||||
@@ -109,6 +111,8 @@ struct Values {
|
||||
QString room_port;
|
||||
uint host_type;
|
||||
qulonglong game_id;
|
||||
QString room_description;
|
||||
std::pair<std::vector<std::string>, std::vector<std::string>> ban_list;
|
||||
|
||||
// logging
|
||||
bool show_console;
|
||||
|
Reference in New Issue
Block a user