Address second part of review comments
This commit is contained in:
parent
6c8e456185
commit
4b404191cf
|
@ -15,27 +15,40 @@ namespace AnnounceMultiplayerRoom {
|
||||||
|
|
||||||
using MacAddress = std::array<u8, 6>;
|
using MacAddress = std::array<u8, 6>;
|
||||||
|
|
||||||
struct Room {
|
|
||||||
struct Member {
|
struct Member {
|
||||||
std::string username;
|
std::string username;
|
||||||
std::string nickname;
|
std::string nickname;
|
||||||
|
std::string display_name;
|
||||||
std::string avatar_url;
|
std::string avatar_url;
|
||||||
MacAddress mac_address;
|
MacAddress mac_address;
|
||||||
std::string game_name;
|
std::string game_name;
|
||||||
u64 game_id;
|
u64 game_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct RoomInformation {
|
||||||
|
std::string name; ///< Name of the server
|
||||||
|
std::string description; ///< Server description
|
||||||
|
u32 member_slots; ///< Maximum number of members in this room
|
||||||
|
u16 port; ///< The port of this room
|
||||||
|
std::string preferred_game; ///< Game to advertise that you want to play
|
||||||
|
u64 preferred_game_id; ///< Title ID for the advertised game
|
||||||
|
std::string host_username; ///< Forum username of the host
|
||||||
|
bool enable_yuzu_mods; ///< Allow yuzu Moderators to moderate on this room
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GameInfo {
|
||||||
|
std::string name{""};
|
||||||
|
u64 id{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Room {
|
||||||
|
RoomInformation information;
|
||||||
|
|
||||||
std::string id;
|
std::string id;
|
||||||
std::string verify_UID; ///< UID used for verification
|
std::string verify_UID; ///< UID used for verification
|
||||||
std::string name;
|
|
||||||
std::string description;
|
|
||||||
std::string owner;
|
|
||||||
std::string ip;
|
std::string ip;
|
||||||
u16 port;
|
|
||||||
u32 max_player;
|
|
||||||
u32 net_version;
|
u32 net_version;
|
||||||
bool has_password;
|
bool has_password;
|
||||||
std::string preferred_game;
|
|
||||||
u64 preferred_game_id;
|
|
||||||
|
|
||||||
std::vector<Member> members;
|
std::vector<Member> members;
|
||||||
};
|
};
|
||||||
|
@ -71,9 +84,7 @@ public:
|
||||||
* @param game_id The title id of the game the player plays
|
* @param game_id The title id of the game the player plays
|
||||||
* @param game_name The name of the game the player plays
|
* @param game_name The name of the game the player plays
|
||||||
*/
|
*/
|
||||||
virtual void AddPlayer(const std::string& username, const std::string& nickname,
|
virtual void AddPlayer(const Member& member) = 0;
|
||||||
const std::string& avatar_url, const MacAddress& mac_address,
|
|
||||||
const u64 game_id, const std::string& game_name) = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the data in the announce service. Re-register the room when required.
|
* Updates the data in the announce service. Re-register the room when required.
|
||||||
|
@ -116,9 +127,7 @@ public:
|
||||||
const u16 /*port*/, const u32 /*max_player*/, const u32 /*net_version*/,
|
const u16 /*port*/, const u32 /*max_player*/, const u32 /*net_version*/,
|
||||||
const bool /*has_password*/, const std::string& /*preferred_game*/,
|
const bool /*has_password*/, const std::string& /*preferred_game*/,
|
||||||
const u64 /*preferred_game_id*/) override {}
|
const u64 /*preferred_game_id*/) override {}
|
||||||
void AddPlayer(const std::string& /*username*/, const std::string& /*nickname*/,
|
void AddPlayer(const Member& /*member*/) override {}
|
||||||
const std::string& /*avatar_url*/, const MacAddress& /*mac_address*/,
|
|
||||||
const u64 /*game_id*/, const std::string& /*game_name*/) override {}
|
|
||||||
WebService::WebResult Update() override {
|
WebService::WebResult Update() override {
|
||||||
return WebService::WebResult{WebService::WebResult::Code::NoWebservice,
|
return WebService::WebResult{WebService::WebResult::Code::NoWebservice,
|
||||||
"WebService is missing", ""};
|
"WebService is missing", ""};
|
||||||
|
|
|
@ -88,15 +88,14 @@ AnnounceMultiplayerSession::~AnnounceMultiplayerSession() {
|
||||||
|
|
||||||
void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room> room) {
|
void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room> room) {
|
||||||
Network::RoomInformation room_information = room->GetRoomInformation();
|
Network::RoomInformation room_information = room->GetRoomInformation();
|
||||||
std::vector<Network::Room::Member> memberlist = room->GetRoomMemberList();
|
std::vector<AnnounceMultiplayerRoom::Member> memberlist = room->GetRoomMemberList();
|
||||||
backend->SetRoomInformation(
|
backend->SetRoomInformation(
|
||||||
room_information.name, room_information.description, room_information.port,
|
room_information.name, room_information.description, room_information.port,
|
||||||
room_information.member_slots, Network::network_version, room->HasPassword(),
|
room_information.member_slots, Network::network_version, room->HasPassword(),
|
||||||
room_information.preferred_game, room_information.preferred_game_id);
|
room_information.preferred_game, room_information.preferred_game_id);
|
||||||
backend->ClearPlayers();
|
backend->ClearPlayers();
|
||||||
for (const auto& member : memberlist) {
|
for (const auto& member : memberlist) {
|
||||||
backend->AddPlayer(member.username, member.nickname, member.avatar_url, member.mac_address,
|
backend->AddPlayer(member);
|
||||||
member.game_info.id, member.game_info.name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1066,8 +1066,8 @@ Room::BanList Room::GetBanList() const {
|
||||||
return {room_impl->username_ban_list, room_impl->ip_ban_list};
|
return {room_impl->username_ban_list, room_impl->ip_ban_list};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Room::Member> Room::GetRoomMemberList() const {
|
std::vector<Member> Room::GetRoomMemberList() const {
|
||||||
std::vector<Room::Member> member_list;
|
std::vector<Member> member_list;
|
||||||
std::lock_guard lock(room_impl->member_mutex);
|
std::lock_guard lock(room_impl->member_mutex);
|
||||||
for (const auto& member_impl : room_impl->members) {
|
for (const auto& member_impl : room_impl->members) {
|
||||||
Member member;
|
Member member;
|
||||||
|
@ -1076,7 +1076,8 @@ std::vector<Room::Member> Room::GetRoomMemberList() const {
|
||||||
member.display_name = member_impl.user_data.display_name;
|
member.display_name = member_impl.user_data.display_name;
|
||||||
member.avatar_url = member_impl.user_data.avatar_url;
|
member.avatar_url = member_impl.user_data.avatar_url;
|
||||||
member.mac_address = member_impl.mac_address;
|
member.mac_address = member_impl.mac_address;
|
||||||
member.game_info = member_impl.game_info;
|
member.game_name = member_impl.game_info.name;
|
||||||
|
member.game_id = member_impl.game_info.id;
|
||||||
member_list.push_back(member);
|
member_list.push_back(member);
|
||||||
}
|
}
|
||||||
return member_list;
|
return member_list;
|
||||||
|
|
|
@ -8,11 +8,17 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "common/announce_multiplayer_room.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "network/verify_user.h"
|
#include "network/verify_user.h"
|
||||||
|
|
||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
|
using AnnounceMultiplayerRoom::GameInfo;
|
||||||
|
using AnnounceMultiplayerRoom::MacAddress;
|
||||||
|
using AnnounceMultiplayerRoom::Member;
|
||||||
|
using AnnounceMultiplayerRoom::RoomInformation;
|
||||||
|
|
||||||
constexpr u32 network_version = 1; ///< The version of this Room and RoomMember
|
constexpr u32 network_version = 1; ///< The version of this Room and RoomMember
|
||||||
|
|
||||||
constexpr u16 DefaultRoomPort = 24872;
|
constexpr u16 DefaultRoomPort = 24872;
|
||||||
|
@ -24,23 +30,6 @@ static constexpr u32 MaxConcurrentConnections = 254;
|
||||||
|
|
||||||
constexpr std::size_t NumChannels = 1; // Number of channels used for the connection
|
constexpr std::size_t NumChannels = 1; // Number of channels used for the connection
|
||||||
|
|
||||||
struct RoomInformation {
|
|
||||||
std::string name; ///< Name of the server
|
|
||||||
std::string description; ///< Server description
|
|
||||||
u32 member_slots; ///< Maximum number of members in this room
|
|
||||||
u16 port; ///< The port of this room
|
|
||||||
std::string preferred_game; ///< Game to advertise that you want to play
|
|
||||||
u64 preferred_game_id; ///< Title ID for the advertised game
|
|
||||||
std::string host_username; ///< Forum username of the host
|
|
||||||
bool enable_yuzu_mods; ///< Allow yuzu Moderators to moderate on this room
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GameInfo {
|
|
||||||
std::string name{""};
|
|
||||||
u64 id{0};
|
|
||||||
};
|
|
||||||
|
|
||||||
using MacAddress = std::array<u8, 6>;
|
|
||||||
/// A special MAC address that tells the room we're joining to assign us a MAC address
|
/// A special MAC address that tells the room we're joining to assign us a MAC address
|
||||||
/// automatically.
|
/// automatically.
|
||||||
constexpr MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
constexpr MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||||
|
@ -95,15 +84,6 @@ public:
|
||||||
Closed, ///< The room is not opened and can not accept connections.
|
Closed, ///< The room is not opened and can not accept connections.
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Member {
|
|
||||||
std::string nickname; ///< The nickname of the member.
|
|
||||||
std::string username; ///< The web services username of the member. Can be empty.
|
|
||||||
std::string display_name; ///< The web services display name of the member. Can be empty.
|
|
||||||
std::string avatar_url; ///< Url to the member's avatar. Can be empty.
|
|
||||||
GameInfo game_info; ///< The current game of the member
|
|
||||||
MacAddress mac_address; ///< The assigned mac address of the member.
|
|
||||||
};
|
|
||||||
|
|
||||||
Room();
|
Room();
|
||||||
~Room();
|
~Room();
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,15 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <boost/serialization/vector.hpp>
|
#include <boost/serialization/vector.hpp>
|
||||||
|
#include "common/announce_multiplayer_room.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "network/room.h"
|
#include "network/room.h"
|
||||||
|
|
||||||
namespace Network {
|
namespace Network {
|
||||||
|
|
||||||
|
using AnnounceMultiplayerRoom::GameInfo;
|
||||||
|
using AnnounceMultiplayerRoom::RoomInformation;
|
||||||
|
|
||||||
/// Information about the received WiFi packets.
|
/// Information about the received WiFi packets.
|
||||||
/// Acts as our own 802.11 header.
|
/// Acts as our own 802.11 header.
|
||||||
struct WifiPacket {
|
struct WifiPacket {
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
namespace AnnounceMultiplayerRoom {
|
namespace AnnounceMultiplayerRoom {
|
||||||
|
|
||||||
static void to_json(nlohmann::json& json, const Room::Member& member) {
|
static void to_json(nlohmann::json& json, const Member& member) {
|
||||||
if (!member.username.empty()) {
|
if (!member.username.empty()) {
|
||||||
json["username"] = member.username;
|
json["username"] = member.username;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ static void to_json(nlohmann::json& json, const Room::Member& member) {
|
||||||
json["gameId"] = member.game_id;
|
json["gameId"] = member.game_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void from_json(const nlohmann::json& json, Room::Member& member) {
|
static void from_json(const nlohmann::json& json, Member& member) {
|
||||||
member.nickname = json.at("nickname").get<std::string>();
|
member.nickname = json.at("nickname").get<std::string>();
|
||||||
member.game_name = json.at("gameName").get<std::string>();
|
member.game_name = json.at("gameName").get<std::string>();
|
||||||
member.game_id = json.at("gameId").get<u64>();
|
member.game_id = json.at("gameId").get<u64>();
|
||||||
|
@ -37,14 +37,14 @@ static void from_json(const nlohmann::json& json, Room::Member& member) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void to_json(nlohmann::json& json, const Room& room) {
|
static void to_json(nlohmann::json& json, const Room& room) {
|
||||||
json["port"] = room.port;
|
json["port"] = room.information.port;
|
||||||
json["name"] = room.name;
|
json["name"] = room.information.name;
|
||||||
if (!room.description.empty()) {
|
if (!room.information.description.empty()) {
|
||||||
json["description"] = room.description;
|
json["description"] = room.information.description;
|
||||||
}
|
}
|
||||||
json["preferredGameName"] = room.preferred_game;
|
json["preferredGameName"] = room.information.preferred_game;
|
||||||
json["preferredGameId"] = room.preferred_game_id;
|
json["preferredGameId"] = room.information.preferred_game_id;
|
||||||
json["maxPlayers"] = room.max_player;
|
json["maxPlayers"] = room.information.member_slots;
|
||||||
json["netVersion"] = room.net_version;
|
json["netVersion"] = room.net_version;
|
||||||
json["hasPassword"] = room.has_password;
|
json["hasPassword"] = room.has_password;
|
||||||
if (room.members.size() > 0) {
|
if (room.members.size() > 0) {
|
||||||
|
@ -56,22 +56,22 @@ static void to_json(nlohmann::json& json, const Room& room) {
|
||||||
static void from_json(const nlohmann::json& json, Room& room) {
|
static void from_json(const nlohmann::json& json, Room& room) {
|
||||||
room.verify_UID = json.at("externalGuid").get<std::string>();
|
room.verify_UID = json.at("externalGuid").get<std::string>();
|
||||||
room.ip = json.at("address").get<std::string>();
|
room.ip = json.at("address").get<std::string>();
|
||||||
room.name = json.at("name").get<std::string>();
|
room.information.name = json.at("name").get<std::string>();
|
||||||
try {
|
try {
|
||||||
room.description = json.at("description").get<std::string>();
|
room.information.description = json.at("description").get<std::string>();
|
||||||
} catch (const nlohmann::detail::out_of_range&) {
|
} catch (const nlohmann::detail::out_of_range&) {
|
||||||
room.description = "";
|
room.information.description = "";
|
||||||
LOG_DEBUG(Network, "Room \'{}\' doesn't contain a description", room.name);
|
LOG_DEBUG(Network, "Room \'{}\' doesn't contain a description", room.information.name);
|
||||||
}
|
}
|
||||||
room.owner = json.at("owner").get<std::string>();
|
room.information.host_username = json.at("owner").get<std::string>();
|
||||||
room.port = json.at("port").get<u16>();
|
room.information.port = json.at("port").get<u16>();
|
||||||
room.preferred_game = json.at("preferredGameName").get<std::string>();
|
room.information.preferred_game = json.at("preferredGameName").get<std::string>();
|
||||||
room.preferred_game_id = json.at("preferredGameId").get<u64>();
|
room.information.preferred_game_id = json.at("preferredGameId").get<u64>();
|
||||||
room.max_player = json.at("maxPlayers").get<u32>();
|
room.information.member_slots = json.at("maxPlayers").get<u32>();
|
||||||
room.net_version = json.at("netVersion").get<u32>();
|
room.net_version = json.at("netVersion").get<u32>();
|
||||||
room.has_password = json.at("hasPassword").get<bool>();
|
room.has_password = json.at("hasPassword").get<bool>();
|
||||||
try {
|
try {
|
||||||
room.members = json.at("players").get<std::vector<Room::Member>>();
|
room.members = json.at("players").get<std::vector<Member>>();
|
||||||
} catch (const nlohmann::detail::out_of_range& e) {
|
} catch (const nlohmann::detail::out_of_range& e) {
|
||||||
LOG_DEBUG(Network, "Out of range {}", e.what());
|
LOG_DEBUG(Network, "Out of range {}", e.what());
|
||||||
}
|
}
|
||||||
|
@ -85,26 +85,16 @@ void RoomJson::SetRoomInformation(const std::string& name, const std::string& de
|
||||||
const u16 port, const u32 max_player, const u32 net_version,
|
const u16 port, const u32 max_player, const u32 net_version,
|
||||||
const bool has_password, const std::string& preferred_game,
|
const bool has_password, const std::string& preferred_game,
|
||||||
const u64 preferred_game_id) {
|
const u64 preferred_game_id) {
|
||||||
room.name = name;
|
room.information.name = name;
|
||||||
room.description = description;
|
room.information.description = description;
|
||||||
room.port = port;
|
room.information.port = port;
|
||||||
room.max_player = max_player;
|
room.information.member_slots = max_player;
|
||||||
room.net_version = net_version;
|
room.net_version = net_version;
|
||||||
room.has_password = has_password;
|
room.has_password = has_password;
|
||||||
room.preferred_game = preferred_game;
|
room.information.preferred_game = preferred_game;
|
||||||
room.preferred_game_id = preferred_game_id;
|
room.information.preferred_game_id = preferred_game_id;
|
||||||
}
|
}
|
||||||
void RoomJson::AddPlayer(const std::string& username_, const std::string& nickname_,
|
void RoomJson::AddPlayer(const AnnounceMultiplayerRoom::Member& member) {
|
||||||
const std::string& avatar_url,
|
|
||||||
const AnnounceMultiplayerRoom::MacAddress& mac_address, const u64 game_id,
|
|
||||||
const std::string& game_name) {
|
|
||||||
AnnounceMultiplayerRoom::Room::Member member;
|
|
||||||
member.username = username_;
|
|
||||||
member.nickname = nickname_;
|
|
||||||
member.avatar_url = avatar_url;
|
|
||||||
member.mac_address = mac_address;
|
|
||||||
member.game_id = game_id;
|
|
||||||
member.game_name = game_name;
|
|
||||||
room.members.push_back(member);
|
room.members.push_back(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,10 +24,7 @@ public:
|
||||||
const u32 max_player, const u32 net_version, const bool has_password,
|
const u32 max_player, const u32 net_version, const bool has_password,
|
||||||
const std::string& preferred_game,
|
const std::string& preferred_game,
|
||||||
const u64 preferred_game_id) override;
|
const u64 preferred_game_id) override;
|
||||||
void AddPlayer(const std::string& username_, const std::string& nickname_,
|
void AddPlayer(const AnnounceMultiplayerRoom::Member& member) override;
|
||||||
const std::string& avatar_url,
|
|
||||||
const AnnounceMultiplayerRoom::MacAddress& mac_address, const u64 game_id,
|
|
||||||
const std::string& game_name) override;
|
|
||||||
WebResult Update() override;
|
WebResult Update() override;
|
||||||
WebResult Register() override;
|
WebResult Register() override;
|
||||||
void ClearPlayers() override;
|
void ClearPlayers() override;
|
||||||
|
|
|
@ -2,8 +2,16 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <system_error>
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||||
|
#endif
|
||||||
#include <jwt/jwt.hpp>
|
#include <jwt/jwt.hpp>
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <system_error>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "web_service/verify_user_jwt.h"
|
#include "web_service/verify_user_jwt.h"
|
||||||
#include "web_service/web_backend.h"
|
#include "web_service/web_backend.h"
|
||||||
|
|
|
@ -214,7 +214,7 @@ void Lobby::OnRefreshLobby() {
|
||||||
for (int r = 0; r < game_list->rowCount(); ++r) {
|
for (int r = 0; r < game_list->rowCount(); ++r) {
|
||||||
auto index = game_list->index(r, 0);
|
auto index = game_list->index(r, 0);
|
||||||
auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong();
|
auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong();
|
||||||
if (game_id != 0 && room.preferred_game_id == game_id) {
|
if (game_id != 0 && room.information.preferred_game_id == game_id) {
|
||||||
smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>();
|
smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,20 +231,21 @@ void Lobby::OnRefreshLobby() {
|
||||||
auto first_item = new LobbyItem();
|
auto first_item = new LobbyItem();
|
||||||
auto row = QList<QStandardItem*>({
|
auto row = QList<QStandardItem*>({
|
||||||
first_item,
|
first_item,
|
||||||
new LobbyItemName(room.has_password, QString::fromStdString(room.name)),
|
new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)),
|
||||||
new LobbyItemGame(room.preferred_game_id, QString::fromStdString(room.preferred_game),
|
new LobbyItemGame(room.information.preferred_game_id,
|
||||||
smdh_icon),
|
QString::fromStdString(room.information.preferred_game), smdh_icon),
|
||||||
new LobbyItemHost(QString::fromStdString(room.owner), QString::fromStdString(room.ip),
|
new LobbyItemHost(QString::fromStdString(room.information.host_username),
|
||||||
room.port, QString::fromStdString(room.verify_UID)),
|
QString::fromStdString(room.ip), room.information.port,
|
||||||
new LobbyItemMemberList(members, room.max_player),
|
QString::fromStdString(room.verify_UID)),
|
||||||
|
new LobbyItemMemberList(members, room.information.member_slots),
|
||||||
});
|
});
|
||||||
model->appendRow(row);
|
model->appendRow(row);
|
||||||
// To make the rows expandable, add the member data as a child of the first column of the
|
// 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
|
// rows with people in them and have qt set them to colspan after the model is finished
|
||||||
// resetting
|
// resetting
|
||||||
if (!room.description.empty()) {
|
if (!room.information.description.empty()) {
|
||||||
first_item->appendRow(
|
first_item->appendRow(
|
||||||
new LobbyItemDescription(QString::fromStdString(room.description)));
|
new LobbyItemDescription(QString::fromStdString(room.information.description)));
|
||||||
}
|
}
|
||||||
if (!room.members.empty()) {
|
if (!room.members.empty()) {
|
||||||
first_item->appendRow(new LobbyItemExpandedMemberList(members));
|
first_item->appendRow(new LobbyItemExpandedMemberList(members));
|
||||||
|
|
Loading…
Reference in New Issue