mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
389 lines
15 KiB
C++
389 lines
15 KiB
C++
#include "stdafx.h"
|
|
#include "PlayersDataManager.h"
|
|
|
|
#include <QPointer>
|
|
#include <QUrl>
|
|
#include <QPainter>
|
|
|
|
#include "V8DataModel/DataModel.h"
|
|
#include "Network/Players.h"
|
|
#include "Network/Player.h"
|
|
#include "V8Xml/WebParser.h"
|
|
#include "util/BrickColor.h"
|
|
|
|
#include "RobloxSettings.h"
|
|
#include "QtUtilities.h"
|
|
#include "RobloxGameExplorer.h"
|
|
|
|
FASTFLAGVARIABLE(TeamCreateReduceCopiesSelectionSet, true)
|
|
|
|
static const int kAvatarImageSize = 32;
|
|
static const int kMaxColors = 128;
|
|
|
|
static const QString kPlayersColorSettingKey = "Players Color";
|
|
static const QString kPlayerIdKey = "PlayerId";
|
|
static const QString kColorValueKey = "ColorVal";
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
// PlayersDataManager
|
|
//--------------------------------------------------------------------------------------------
|
|
PlayersDataManager::PlayersDataManager(QObject* parent, boost::shared_ptr<RBX::DataModel> dataModel)
|
|
: QObject(parent)
|
|
, m_pDataModel(dataModel)
|
|
, m_CurrentColorCount(0)
|
|
{
|
|
qRegisterMetaType<PlayersColorCollection >();
|
|
qRegisterMetaTypeStreamOperators<PlayersColorCollection >("PlayersColorCollection");
|
|
|
|
loadPlayersColorSettings();
|
|
|
|
RBXASSERT(m_pDataModel->currentThreadHasWriteLock());
|
|
if (RBX::Network::Players* players = m_pDataModel->find<RBX::Network::Players>())
|
|
{
|
|
for (auto& playerInstance : *(players->getPlayers()))
|
|
{
|
|
onPlayerAdded(playerInstance);
|
|
}
|
|
players->playerAddedSignal.connect(boost::bind(&PlayersDataManager::onPlayerAdded, this, _1));
|
|
players->playerRemovingSignal.connect(boost::bind(&PlayersDataManager::onPlayerRemoving, this, _1));
|
|
}
|
|
}
|
|
|
|
PlayersDataManager::~PlayersDataManager()
|
|
{
|
|
savePlayersColorSettings();
|
|
}
|
|
|
|
void PlayersDataManager::getPlayerAvatar(int playerId, QObject* resultListener, const char* callbackName)
|
|
{
|
|
QSharedPointer<AvatarLoader> avatarLoader = getAvatarLoader(playerId);
|
|
if (avatarLoader && avatarLoader->hasFinishedLoading())
|
|
{
|
|
QMetaObject::invokeMethod(resultListener, callbackName, Qt::AutoConnection, Q_ARG(QImage, avatarLoader->getAvatar()));
|
|
}
|
|
else
|
|
{
|
|
if (!avatarLoader)
|
|
{
|
|
avatarLoader = QSharedPointer<AvatarLoader>(new AvatarLoader(playerId, getPlayerColor(playerId)), &QObject::deleteLater);
|
|
m_AvatarLoaders.push_back(avatarLoader);
|
|
}
|
|
|
|
// HACK: convert callback name to SLOT
|
|
QString slot = QString("1%1(const QImage&)").arg(callbackName);
|
|
connect(avatarLoader.data(), SIGNAL(avatarLoaded(const QImage&)), resultListener, qPrintable(slot));
|
|
}
|
|
}
|
|
|
|
static void invokePlayerDataLoaded(QPointer<PlayersDataManager> playersDataManager, RBX::HttpFuture future)
|
|
{
|
|
if (playersDataManager.isNull())
|
|
return;
|
|
QMetaObject::invokeMethod(playersDataManager.data(), "onPlayerNameLoaded", Qt::QueuedConnection, Q_ARG(RBX::HttpFuture, future));
|
|
|
|
}
|
|
void PlayersDataManager::getPlayerName(int playerId, QObject* resultListener, const char* callbackName)
|
|
{
|
|
if (m_PlayersInfo.contains(playerId))
|
|
{
|
|
QMetaObject::invokeMethod(resultListener, callbackName, Qt::AutoConnection, Q_ARG(int, playerId), Q_ARG(QString, m_PlayersInfo.value(playerId)));
|
|
}
|
|
else
|
|
{
|
|
QString str = QString("%1/Users/%2").arg(RobloxSettings::getApiBaseURL()).arg(playerId);
|
|
RBX::HttpAsync::get(str.toStdString()).then(boost::bind(&invokePlayerDataLoaded, QPointer<PlayersDataManager>(this), _1));
|
|
|
|
// HACK: convert callback name to SLOT
|
|
QString slot = QString("1%1(int, const QString&)").arg(callbackName);
|
|
connect(this, SIGNAL(playerNameLoaded(int, const QString&)), resultListener, qPrintable(slot));
|
|
}
|
|
}
|
|
|
|
int PlayersDataManager::getPlayerColor(int playerId)
|
|
{
|
|
PlayersColorCollection::const_iterator iter = m_PlayersColor.find(playerId);
|
|
if (iter != m_PlayersColor.end())
|
|
return iter.value();
|
|
|
|
m_PlayersColor[playerId] = getNextUnassignedColor();
|
|
return m_PlayersColor[playerId];
|
|
}
|
|
|
|
shared_ptr<const RBX::Instances> PlayersDataManager::getLastKnownSelection(int playerId)
|
|
{
|
|
PlayerSelectionMap::iterator iter = m_lastKnownSelectionByPlayerId.find(playerId);
|
|
if (iter != m_lastKnownSelectionByPlayerId.end())
|
|
return iter->second;
|
|
|
|
// return empty value if the key doesn't exist
|
|
static shared_ptr<const RBX::Instances> emptyInstances(new RBX::Instances());
|
|
return emptyInstances;
|
|
}
|
|
|
|
bool PlayersDataManager::isLocalPlayer(boost::shared_ptr<RBX::Network::Player> player)
|
|
{
|
|
RBX::Network::Players* players = m_pDataModel->find<RBX::Network::Players>();
|
|
return (players && players->getLocalPlayer() == player.get());
|
|
}
|
|
|
|
boost::shared_ptr<RBX::Network::Player> PlayersDataManager::getPlayer(const QString& playerName, Qt::CaseSensitivity cs)
|
|
{
|
|
if (!playerName.isEmpty())
|
|
{
|
|
RBX::Network::Players* players = m_pDataModel->find<RBX::Network::Players>();
|
|
if (players)
|
|
{
|
|
boost::shared_ptr<const RBX::Instances> playersList = players->getPlayers();
|
|
for (RBX::Instances::const_iterator iter = playersList->begin(); iter != playersList->end(); ++iter)
|
|
{
|
|
RBX::Network::Player* player = boost::polymorphic_downcast<RBX::Network::Player*>(iter->get());
|
|
if (QString::compare(player->getName().c_str(), playerName, cs) == 0)
|
|
return shared_from(player);
|
|
}
|
|
}
|
|
}
|
|
|
|
return shared_ptr<RBX::Network::Player>();
|
|
}
|
|
|
|
QSharedPointer<AvatarLoader> PlayersDataManager::getAvatarLoader(int playerId)
|
|
{
|
|
QListIterator<QSharedPointer<AvatarLoader> > iter(m_AvatarLoaders);
|
|
for (int ii = 0; ii < m_AvatarLoaders.size(); ++ii)
|
|
{
|
|
if (m_AvatarLoaders.at(ii)->getPlayerId() == playerId)
|
|
return m_AvatarLoaders.at(ii);
|
|
}
|
|
|
|
return QSharedPointer<AvatarLoader>();
|
|
}
|
|
|
|
void PlayersDataManager::onPlayerAdded(shared_ptr<RBX::Instance> playerInstance)
|
|
{
|
|
if (shared_ptr<RBX::Network::Player> player = RBX::Instance::fastSharedDynamicCast<RBX::Network::Player>(playerInstance))
|
|
{
|
|
player->cloudEditSelectionChanged.connect(boost::bind(&PlayersDataManager::onCloudEditSelectionChanged,
|
|
this, player->getUserID(), _1));
|
|
}
|
|
}
|
|
|
|
void PlayersDataManager::onPlayerRemoving(shared_ptr<RBX::Instance> playerInstance)
|
|
{
|
|
if (shared_ptr<RBX::Network::Player> player = RBX::Instance::fastSharedDynamicCast<RBX::Network::Player>(playerInstance))
|
|
{
|
|
m_lastKnownSelectionByPlayerId.erase(player->getUserID());
|
|
// remove selections for the removing player
|
|
Q_EMIT cloudEditSelectionChanged(player->getUserID());
|
|
}
|
|
}
|
|
|
|
void PlayersDataManager::onCloudEditSelectionChanged(int playerId, shared_ptr<const RBX::Reflection::ValueArray>& newSelection)
|
|
{
|
|
shared_ptr<RBX::Instances> inst(new RBX::Instances());
|
|
inst->reserve(newSelection->size());
|
|
for (auto& var : *newSelection)
|
|
{
|
|
if (var.isType<shared_ptr<RBX::Instance> >())
|
|
{
|
|
if (shared_ptr<RBX::Instance> i = var.cast<shared_ptr<RBX::Instance> >())
|
|
{
|
|
inst->push_back(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_lastKnownSelectionByPlayerId[playerId] = inst;
|
|
Q_EMIT cloudEditSelectionChanged(playerId);
|
|
}
|
|
|
|
/*
|
|
Players color data present in registry:
|
|
|
|
RobloxStudio
|
|
|__
|
|
Players Color
|
|
|__ gametest2.robloxlabs.com
|
|
| |_ PlayersColorCollection (will be having player id and color assigned)
|
|
|
|
|
|__ roblox.com
|
|
|_ PlayersColorCollection
|
|
*/
|
|
|
|
static QString getHostName()
|
|
{
|
|
QUrl url(RobloxSettings::getBaseURL());
|
|
return url.host().mid(url.host().indexOf(QString(".")) + 1);
|
|
}
|
|
|
|
void PlayersDataManager::loadPlayersColorSettings()
|
|
{
|
|
RobloxSettings settings;
|
|
settings.beginGroup(kPlayersColorSettingKey);
|
|
m_PlayersColor = settings.value(getHostName()).value<PlayersColorCollection >();
|
|
settings.endGroup();
|
|
}
|
|
|
|
void PlayersDataManager::savePlayersColorSettings()
|
|
{
|
|
RobloxSettings settings;
|
|
settings.beginGroup(kPlayersColorSettingKey);
|
|
settings.setValue(getHostName(), QVariant::fromValue<PlayersColorCollection >(m_PlayersColor));
|
|
settings.endGroup();
|
|
}
|
|
|
|
int PlayersDataManager::getNextUnassignedColor()
|
|
{
|
|
using namespace RBX;
|
|
static int colorsIndex[kMaxColors] = { BrickColor::brick_45, BrickColor::brick_331, BrickColor::roblox_1020, BrickColor::brick_334,
|
|
BrickColor::brick_335, BrickColor::brick_21, BrickColor::roblox_1006, BrickColor::roblox_1019,
|
|
BrickColor::brick_28, BrickColor::brick_337, BrickColor::roblox_1014, BrickColor::brick_307,
|
|
BrickColor::roblox_1030, BrickColor::roblox_1009, BrickColor::brick_352, BrickColor::roblox_1016,
|
|
BrickColor::roblox_1029, BrickColor::brick_119, BrickColor::brick_359, BrickColor::brick_361,
|
|
BrickColor::brick_194, BrickColor::brick_365, BrickColor::brick_328, BrickColor::roblox_1017,
|
|
BrickColor::roblox_1032, BrickColor::brick_11, BrickColor::brick_324, BrickColor::brick_322,
|
|
BrickColor::brick_321, BrickColor::brick_314, BrickColor::brick_318, BrickColor::brick_316,
|
|
BrickColor::brick_315, BrickColor::brick_332, BrickColor::brick_313, BrickColor::brick_23,
|
|
BrickColor::brick_340, BrickColor::roblox_1021, BrickColor::brick_306, BrickColor::roblox_1018,
|
|
BrickColor::roblox_1011, BrickColor::brick_26, BrickColor::brick_141, BrickColor::brick_329,
|
|
BrickColor::roblox_1004, BrickColor::brick_333, BrickColor::brick_226, BrickColor::brick_336,
|
|
BrickColor::brick_338, BrickColor::brick_133, BrickColor::brick_341, BrickColor::brick_9,
|
|
BrickColor::brick_344, BrickColor::brick_105, BrickColor::brick_348, BrickColor::brick_125,
|
|
BrickColor::brick_192, BrickColor::brick_353, BrickColor::brick_5, BrickColor::brick_355,
|
|
BrickColor::brick_357, BrickColor::brick_360, BrickColor::brick_362, BrickColor::brick_363,
|
|
BrickColor::roblox_1003, BrickColor::roblox_1028, BrickColor::brick_350, BrickColor::roblox_1015,
|
|
BrickColor::roblox_1026, BrickColor::brick_325, BrickColor::brick_153, BrickColor::roblox_1008,
|
|
BrickColor::roblox_1013, BrickColor::brick_319, BrickColor::brick_151, BrickColor::roblox_1023,
|
|
BrickColor::roblox_1027, BrickColor::brick_37, BrickColor::roblox_1010, BrickColor::brick_135,
|
|
BrickColor::brick_309, BrickColor::brick_347, BrickColor::brick_302, BrickColor::brick_304,
|
|
BrickColor::roblox_1012, BrickColor::brick_301, BrickColor::brick_330, BrickColor::brick_343,
|
|
BrickColor::brick_24, BrickColor::brick_217, BrickColor::brick_342, BrickColor::roblox_1007,
|
|
BrickColor::brick_106, BrickColor::roblox_1001, BrickColor::roblox_1025, BrickColor::brick_345,
|
|
BrickColor::brick_346, BrickColor::brick_349, BrickColor::brick_101, BrickColor::brick_351,
|
|
BrickColor::brick_354, BrickColor::brick_18, BrickColor::brick_356, BrickColor::brick_358,
|
|
BrickColor::brick_38, BrickColor::brick_1, BrickColor::brick_364, BrickColor::brick_208,
|
|
BrickColor::brick_29, BrickColor::brick_327, BrickColor::roblox_1002, BrickColor::brick_320,
|
|
BrickColor::brick_323, BrickColor::brick_339, BrickColor::brick_104, BrickColor::roblox_1024,
|
|
BrickColor::brick_317, BrickColor::roblox_1031, BrickColor::brick_311, BrickColor::roblox_1022,
|
|
BrickColor::brick_312, BrickColor::brick_102, BrickColor::brick_310, BrickColor::brick_308,
|
|
BrickColor::brick_305, BrickColor::brick_199, BrickColor::brick_303, BrickColor::brick_107 };
|
|
|
|
return colorsIndex[m_PlayersColor.size() % kMaxColors];
|
|
}
|
|
|
|
void PlayersDataManager::onPlayerNameLoaded(RBX::HttpFuture future)
|
|
{
|
|
EntityProperties playerProp;
|
|
playerProp.setFromJsonFuture(future);
|
|
|
|
boost::optional<std::string> playerName = playerProp.get<std::string>("Username");
|
|
boost::optional<int> playerId = playerProp.get<int>("Id");
|
|
|
|
if (playerName.is_initialized() && playerId.is_initialized() && !playerName.get().empty())
|
|
{
|
|
m_PlayersInfo.insert(playerId.get(), QString::fromStdString(playerName.get()));
|
|
Q_EMIT playerNameLoaded(playerId.get(), QString::fromStdString(playerName.get()));
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------
|
|
// AvatarLoader
|
|
//--------------------------------------------------------------------------------------------
|
|
static void invokeHeadshotThumbnailDataLoaded(QPointer<AvatarLoader> avatarLoader, int playerId, RBX::HttpFuture future)
|
|
{
|
|
if (avatarLoader.isNull())
|
|
return;
|
|
QMetaObject::invokeMethod(avatarLoader.data(), "headshotThumbnailDataLoaded", Qt::QueuedConnection,
|
|
Q_ARG(int, playerId), Q_ARG(RBX::HttpFuture, future));
|
|
}
|
|
|
|
static void invokeHeadshotThumbnailLoaded(QPointer<AvatarLoader> avatarLoader, int playerId, RBX::HttpFuture future)
|
|
{
|
|
if (avatarLoader.isNull())
|
|
return;
|
|
QMetaObject::invokeMethod(avatarLoader.data(), "headshotThumbnailLoaded", Qt::QueuedConnection,
|
|
Q_ARG(int, playerId), Q_ARG(RBX::HttpFuture, future));
|
|
}
|
|
|
|
AvatarLoader::AvatarLoader(int playerId, int backgroundColor)
|
|
: m_PlayerId(playerId)
|
|
, m_BackgroundColor(backgroundColor)
|
|
, m_bFinishedLoading(false)
|
|
{
|
|
// request player item update
|
|
QString str = QString("%1/thumbnail/avatar-headshot?userid=%2").arg(RobloxSettings::getBaseURL()).arg(playerId);
|
|
RBX::HttpAsync::get(str.toStdString()).then(boost::bind(&invokeHeadshotThumbnailDataLoaded, QPointer<AvatarLoader>(this), playerId, _1));
|
|
}
|
|
|
|
void AvatarLoader::headshotThumbnailDataLoaded(int playerId, RBX::HttpFuture future)
|
|
{
|
|
boost::optional<std::string> thumnailUri;
|
|
|
|
try
|
|
{
|
|
std::string result = future.get();
|
|
boost::shared_ptr<const RBX::Reflection::ValueTable> table;
|
|
if (RBX::WebParser::parseJSONTable(future.get(), table) && table->size() > 0)
|
|
{
|
|
RBX::Reflection::ValueTable::const_iterator finalStrIter = table->find("Final");
|
|
if (finalStrIter != table->end() && finalStrIter->second.isType<bool>() && finalStrIter->second.get<bool>())
|
|
{
|
|
RBX::Reflection::ValueTable::const_iterator thumnailUrlIter = table->find("Url");
|
|
if (thumnailUrlIter != table->end() && thumnailUrlIter->second.isString())
|
|
thumnailUri = thumnailUrlIter->second.get<std::string>();
|
|
}
|
|
else
|
|
{
|
|
RBX::Reflection::ValueTable::const_iterator RetryUrlIter = table->find("RetryUrl");
|
|
if (RetryUrlIter != table->end() && RetryUrlIter->second.isString())
|
|
thumnailUri = RetryUrlIter->second.get<std::string>();
|
|
}
|
|
}
|
|
}
|
|
catch (const RBX::base_exception& )
|
|
{
|
|
|
|
}
|
|
|
|
// TODO: in case of error shall we again try to reload avatar?
|
|
if (thumnailUri.is_initialized() && !thumnailUri.get().empty())
|
|
{
|
|
RBX::HttpOptions options;
|
|
options.setExternal(true);
|
|
RBX::HttpAsync::get(thumnailUri.get(), options).then(boost::bind(&invokeHeadshotThumbnailLoaded, QPointer<AvatarLoader>(this), playerId, _1));
|
|
}
|
|
}
|
|
|
|
void AvatarLoader::headshotThumbnailLoaded(int playerId, RBX::HttpFuture future)
|
|
{
|
|
std::string imageContent;
|
|
try
|
|
{
|
|
imageContent = future.get();
|
|
}
|
|
catch (const RBX::base_exception&)
|
|
{
|
|
|
|
}
|
|
|
|
if (!imageContent.empty())
|
|
{
|
|
QImage image;
|
|
if (image.loadFromData((uchar*)imageContent.c_str(), imageContent.size()))
|
|
{
|
|
// set the image background here itself, so the same image can be used in both Creators panel and Chat widget
|
|
QImage resultingImage(QSize(kAvatarImageSize,kAvatarImageSize), image.format());
|
|
resultingImage.fill(QtUtilities::toQColor(RBX::BrickColor(m_BackgroundColor).color3()));
|
|
QPainter painter(&resultingImage);
|
|
painter.drawImage(0, 0, image.scaled(QSize(kAvatarImageSize,kAvatarImageSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
|
// we are done with image drawing, assign the resulting image
|
|
m_PlayerAvatar = resultingImage;
|
|
}
|
|
}
|
|
|
|
m_bFinishedLoading = true;
|
|
// let connected slots to update image
|
|
Q_EMIT avatarLoaded(m_PlayerAvatar);
|
|
// remove connection
|
|
disconnect(SIGNAL(avatarLoaded(const QImage&)));
|
|
}
|