mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
1025 lines
32 KiB
C++
1025 lines
32 KiB
C++
#include "stdafx.h"
|
|
#include "GameConfigurer.h"
|
|
|
|
#include "v8datamodel/DataModel.h"
|
|
#include "v8datamodel/UserInputService.h"
|
|
#include "v8datamodel/Workspace.h"
|
|
#include "v8datamodel/PhysicsSettings.h"
|
|
#include "v8datamodel/CookiesEngineService.h"
|
|
#include "v8datamodel/DebugSettings.h"
|
|
#include "v8datamodel/ChangeHistory.h"
|
|
#include "v8datamodel/InsertService.h"
|
|
#include "v8datamodel/SocialService.h"
|
|
#include "v8datamodel/GamePassService.h"
|
|
#include "v8datamodel/MarketplaceService.h"
|
|
#include "v8datamodel/TimerService.h"
|
|
#include "v8datamodel/Visit.h"
|
|
#include "v8datamodel/DebugSettings.h"
|
|
#include "v8datamodel/GuiService.h"
|
|
#include "v8datamodel/Value.h"
|
|
#include "v8datamodel/PlayerGui.h"
|
|
#include "v8datamodel/HttpService.h"
|
|
#include "V8DataModel/Folder.h"
|
|
#include "V8DataModel/ScreenGui.h"
|
|
#include "V8Datamodel/ContentProvider.h"
|
|
#include "v8datamodel/MegaCluster.h"
|
|
#include "v8datamodel/TeleportService.h"
|
|
#include "v8datamodel/HttpRbxApiService.h"
|
|
#include "util/Statistics.h"
|
|
#include "RobloxServicesTools.h"
|
|
#include "script/Script.h"
|
|
#include "script/ScriptContext.h"
|
|
#include "script/ModuleScript.h"
|
|
#include "script/CoreScript.h"
|
|
#include "v8xml/WebParser.h"
|
|
#include "util/ScriptInformationProvider.h"
|
|
#include "util/RobloxGoogleAnalytics.h"
|
|
#include "Client.h"
|
|
#include "ClientReplicator.h"
|
|
#include "Marker.h"
|
|
#include "GamePerfMonitor.h"
|
|
#include "Network/Players.h"
|
|
#include "util/rbxrandom.h"
|
|
#include "../win/sharedlauncher.h"
|
|
#include <vector>
|
|
|
|
#include "StringConv.h"
|
|
|
|
#include "script/LuaVM.h"
|
|
|
|
#include "boost/tokenizer.hpp"
|
|
#include "boost/algorithm/string.hpp"
|
|
#include "XStudioBuild.h"
|
|
|
|
FASTSTRINGVARIABLE(SocialServiceFriendUrl, "Game/LuaWebService/HandleSocialRequest.ashx?method=IsFriendsWith&playerid=%d&userid=%d")
|
|
FASTSTRINGVARIABLE(SocialServiceBestFriendUrl, "Game/LuaWebService/HandleSocialRequest.ashx?method=IsBestFriendsWith&playerid=%d&userid=%d")
|
|
FASTSTRINGVARIABLE(SocialServiceGroupUrl, "Game/LuaWebService/HandleSocialRequest.ashx?method=IsInGroup&playerid=%d&groupid=%d")
|
|
FASTSTRINGVARIABLE(SocialServiceGroupRankUrl, "Game/LuaWebService/HandleSocialRequest.ashx?method=GetGroupRank&playerid=%d&groupid=%d")
|
|
FASTSTRINGVARIABLE(SocialServiceGroupRoleUrl, "Game/LuaWebService/HandleSocialRequest.ashx?method=GetGroupRole&playerid=%d&groupid=%d")
|
|
FASTSTRINGVARIABLE(GamePassServicePlayerHasPassUrl, "Game/GamePass/GamePassHandler.ashx?Action=HasPass&UserID=%d&PassID=%d")
|
|
FASTSTRINGVARIABLE(MobileJoinRateFormatUrl, "Game/JoinRate.ashx?st=%d&i=%d&p=%d&c=%s&r=%s&d=%d&b=%d&platform=%s")
|
|
FASTFLAG(UseBuildGenericGameUrl)
|
|
|
|
DYNAMIC_FASTINTVARIABLE(JoinInfluxHundredthsPercentage, 0)
|
|
|
|
FASTFLAGVARIABLE(ClientABTestingEnabled, true)
|
|
|
|
DYNAMIC_FASTFLAG(UseR15Character)
|
|
|
|
using namespace RBX;
|
|
|
|
void GameConfigurer::parseArgs(const std::string& args)
|
|
{
|
|
parameters = rbx::make_shared<const Reflection::ValueTable>();
|
|
WebParser::parseJSONTable(args, parameters);
|
|
}
|
|
|
|
int GameConfigurer::getParamInt(const std::string& key)
|
|
{
|
|
RBX::Reflection::ValueTable::const_iterator iter = parameters->find(key);
|
|
if (iter != parameters->end())
|
|
return iter->second.get<int>();
|
|
|
|
return 0;
|
|
}
|
|
|
|
std::string GameConfigurer::getParamString(const std::string& key)
|
|
{
|
|
RBX::Reflection::ValueTable::const_iterator iter = parameters->find(key);
|
|
if (iter != parameters->end())
|
|
return iter->second.get<std::string>();
|
|
|
|
return std::string();
|
|
}
|
|
|
|
bool GameConfigurer::getParamBool(const std::string& key)
|
|
{
|
|
RBX::Reflection::ValueTable::const_iterator iter = parameters->find(key);
|
|
if (iter != parameters->end())
|
|
return iter->second.get<bool>();
|
|
|
|
return false;
|
|
}
|
|
|
|
void GameConfigurer::registerPlay(const std::string& key, int userId, int placeId)
|
|
{
|
|
if (!getParamBool("CookieStoreEnabled"))
|
|
return;
|
|
|
|
if (CookiesService* cs = dataModel->create<CookiesService>())
|
|
{
|
|
if (cs->GetValue(key).empty())
|
|
{
|
|
std::string value = RBX::format("{{ \"userId\" : %d, \"placeId\" : %d, \"os\" : \"%s\" }}", userId, placeId, DebugSettings::singleton().osPlatform().c_str());
|
|
cs->SetValue(key, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameConfigurer::setupUrls()
|
|
{
|
|
std::string baseUrl = getParamString("BaseUrl");
|
|
dataModel->create<InsertService>();
|
|
|
|
if (FFlag::UseBuildGenericGameUrl)
|
|
{
|
|
SocialService* socialService = dataModel->create<SocialService>();
|
|
socialService->setFriendUrl(BuildGenericGameUrl(baseUrl, FString::SocialServiceFriendUrl));
|
|
socialService->setBestFriendUrl(BuildGenericGameUrl(baseUrl, FString::SocialServiceBestFriendUrl));
|
|
socialService->setGroupUrl(BuildGenericGameUrl(baseUrl, FString::SocialServiceGroupUrl));
|
|
socialService->setGroupRankUrl(BuildGenericGameUrl(baseUrl, FString::SocialServiceGroupRankUrl));
|
|
socialService->setGroupRoleUrl(BuildGenericGameUrl(baseUrl, FString::SocialServiceGroupRoleUrl));
|
|
dataModel->create<GamePassService>()->setPlayerHasPassUrl(BuildGenericGameUrl(baseUrl, FString::GamePassServicePlayerHasPassUrl));
|
|
}
|
|
else
|
|
{
|
|
SocialService* socialService = dataModel->create<SocialService>();
|
|
socialService->setFriendUrl(baseUrl + FString::SocialServiceFriendUrl);
|
|
socialService->setBestFriendUrl(baseUrl + FString::SocialServiceBestFriendUrl);
|
|
socialService->setGroupUrl(baseUrl + FString::SocialServiceGroupUrl);
|
|
socialService->setGroupRankUrl(baseUrl + FString::SocialServiceGroupRankUrl);
|
|
socialService->setGroupRoleUrl(baseUrl + FString::SocialServiceGroupRoleUrl);
|
|
dataModel->create<GamePassService>()->setPlayerHasPassUrl(baseUrl + FString::GamePassServicePlayerHasPassUrl);
|
|
}
|
|
}
|
|
|
|
PlayerConfigurer::PlayerConfigurer() :
|
|
testing(true),
|
|
logAnalytics(false),
|
|
isTouchDevice(false),
|
|
connectResolved(false),
|
|
connectionFailed(false),
|
|
loadResolved(false),
|
|
joinResolved(false),
|
|
playResolved(true),
|
|
waitingForCharacter(false),
|
|
startTime(0),
|
|
playStartTime(0),
|
|
launchMode(-1)
|
|
{
|
|
}
|
|
|
|
PlayerConfigurer::~PlayerConfigurer()
|
|
{
|
|
// make sure analytics are reported
|
|
analyticsPoints.report("ClientJoin", DFInt::JoinInfluxHundredthsPercentage);
|
|
|
|
for (auto& c: connections)
|
|
c.disconnect();
|
|
}
|
|
|
|
void PlayerConfigurer::ifSeleniumThenSetCookie(const std::string& key, const std::string& value)
|
|
{
|
|
if (getParamBool("SeleniumTestMode"))
|
|
{
|
|
if (CookiesService* cs = dataModel->create<CookiesService>())
|
|
{
|
|
cs->SetValue(key, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlayerConfigurer::showErrorWindow(const std::string& message, const std::string& errorType, const std::string& errorCategory)
|
|
{
|
|
if (!loadResolved || !joinResolved)
|
|
{
|
|
double duration = G3D::System::time() - startTime;
|
|
|
|
if (!loadResolved)
|
|
{
|
|
loadResolved = true;
|
|
reportDuration("GameLoad", "Failure", duration, false);
|
|
}
|
|
|
|
if (!joinResolved)
|
|
{
|
|
joinResolved = true;
|
|
reportDuration("GameJoin", errorCategory, duration, false);
|
|
}
|
|
|
|
// log error to GA
|
|
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "JoinFailurePlace", RBX::format("%s_%d", errorType.c_str(), getParamInt("PlaceId")).c_str());
|
|
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "JoinFailureIP", RBX::format("%s_%s", errorType.c_str(), getParamString("MachineAddress").c_str()).c_str());
|
|
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "JoinFailureVendor", RBX::format("%s_%d", errorType.c_str(), getParamInt("VendorId")).c_str());
|
|
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "JoinFailureDataCenter", RBX::format("%s_%d", errorType.c_str(), getParamInt("DataCenterId")).c_str());
|
|
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "GameJoin", "Failure");
|
|
|
|
try
|
|
{
|
|
Analytics::EphemeralCounter::reportCounter("JoinFailure-"+errorType, 1);
|
|
}
|
|
catch (RBX::base_exception& e)
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_ERROR, "http failed in showErrorWindow: %s", e.what());
|
|
}
|
|
}
|
|
else if (!playResolved)
|
|
{
|
|
double duration = G3D::System::time() - playStartTime;
|
|
playResolved = true;
|
|
reportDuration("GameDuration", errorCategory, duration, false);
|
|
|
|
try
|
|
{
|
|
Analytics::EphemeralCounter::reportCounter("GameDisconnect-"+errorType, 1);
|
|
}
|
|
catch (RBX::base_exception& e)
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_ERROR, "http failed in showErrorWindow: %s", e.what());
|
|
}
|
|
}
|
|
|
|
if (GuiService*gs = dataModel->create<GuiService>())
|
|
{
|
|
if (errorType != "Kick" || gs->getErrorMessage() == "")
|
|
gs->setErrorMessage(message);
|
|
}
|
|
|
|
dataModel->setUiMessage(message);
|
|
}
|
|
|
|
static void ignoreResponse(std::string*, std::exception*)
|
|
{
|
|
}
|
|
|
|
void PlayerConfigurer::reportError(const std::string& error, const std::string& msg)
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_INFO, "***ERROR*** %s %s", error.c_str(), msg.c_str());
|
|
if (!testing)
|
|
{
|
|
RBX::Visit* visit = dataModel->create<RBX::Visit>();
|
|
visit->setUploadUrl("");
|
|
}
|
|
|
|
Network::Client* client = dataModel->create<Network::Client>();
|
|
client->disconnect();
|
|
|
|
if (TimerService* timer = dataModel->create<TimerService>()) {
|
|
std::string errorMsg = RBX::format("Error: %s", error.c_str());
|
|
timer->delay(boost::bind(&PlayerConfigurer::showErrorWindow, this, errorMsg, msg, "Other"), 4.0);
|
|
}
|
|
}
|
|
|
|
void PlayerConfigurer::reportCounter(const std::string& counterNamesCSV, bool blocking)
|
|
{
|
|
try
|
|
{
|
|
Analytics::EphemeralCounter::reportCountersCSV(counterNamesCSV, blocking);
|
|
}
|
|
catch (RBX::base_exception& e)
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_INFO, "reportCounter failed: %s", e.what());
|
|
}
|
|
|
|
}
|
|
|
|
void PlayerConfigurer::reportStats(const std::string& category, float value)
|
|
{
|
|
Analytics::EphemeralCounter::reportStats(category, value, false);
|
|
}
|
|
|
|
void PlayerConfigurer::reportDuration(const std::string& category, const std::string& result, double duration, bool blocking)
|
|
{
|
|
int durationInMsec = G3D::iFloor(duration * 1000);
|
|
int bytesReceived = -1;
|
|
|
|
Instance* client = dataModel->create<Network::Client>();
|
|
if (client->numChildren() > 0)
|
|
{
|
|
if (Network::Replicator* replicator = Instance::fastDynamicCast<Network::Replicator>(client->getChild(0)))
|
|
bytesReceived = replicator->getMetricValue("Total Bytes Received");
|
|
}
|
|
|
|
try
|
|
{
|
|
// need to keep this until JoinRate.ashx no longer needs to track iOS join success
|
|
// web uses this data for mobile sort to remove games from the sort that don't work on the specific device
|
|
#if defined(RBX_PLATFORM_IOS) || defined(__ANDROID__)
|
|
|
|
std::string url;
|
|
if (FFlag::UseBuildGenericGameUrl)
|
|
{
|
|
url = RBX::format(BuildGenericGameUrl(getParamString("BaseUrl"),FString::MobileJoinRateFormatUrl).c_str(),
|
|
getParamInt("VendorId"),
|
|
getParamInt("UserId"),
|
|
getParamInt("PlaceId"),
|
|
category.c_str(),
|
|
result.c_str(),
|
|
durationInMsec,
|
|
bytesReceived,
|
|
DebugSettings::singleton().osPlatform().c_str());
|
|
}
|
|
else
|
|
{
|
|
url = RBX::format("%sGame/JoinRate.ashx?st=%d&i=%d&p=%d&c=%s&r=%s&d=%d&b=%d&platform=%s",
|
|
getParamString("BaseUrl").c_str(),
|
|
getParamInt("VendorId"),
|
|
getParamInt("UserId"),
|
|
getParamInt("PlaceId"),
|
|
category.c_str(),
|
|
result.c_str(),
|
|
durationInMsec,
|
|
bytesReceived,
|
|
DebugSettings::singleton().osPlatform().c_str());
|
|
}
|
|
|
|
Http http(url);
|
|
http.get(ignoreResponse);
|
|
#endif
|
|
// new endpoint
|
|
reportStats(category + "_" + result + "_Duration", durationInMsec);
|
|
reportStats(category + "_" + result + "_BytesReceived", bytesReceived);
|
|
|
|
if (DFInt::JoinInfluxHundredthsPercentage > 0)
|
|
{
|
|
RBX::Analytics::InfluxDb::Points durationAnalyticsPoint;
|
|
durationAnalyticsPoint.addPoint("Category", category.c_str());
|
|
durationAnalyticsPoint.addPoint("Result", result.c_str());
|
|
durationAnalyticsPoint.addPoint("Duration", duration);
|
|
durationAnalyticsPoint.report("GameDuration", DFInt::JoinInfluxHundredthsPercentage);
|
|
}
|
|
|
|
std::string dataCenterId = RBX::format("%d", getParamInt("DataCenterId"));
|
|
|
|
// for kick rate by data center
|
|
if (category == "GameDuration" && result == "Kick")
|
|
reportStats("KickByDatacenter_" + dataCenterId, durationInMsec);
|
|
|
|
// game join and game load by data center
|
|
if ((category == "GameJoin" || category == "GameLoad") && result == "Success")
|
|
{
|
|
reportStats(category + "ByDatacenter_" + dataCenterId, durationInMsec);
|
|
analyticsPoints.addPoint(category+"BytesReceived", bytesReceived);
|
|
}
|
|
}
|
|
catch (RBX::base_exception& e)
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_INFO, "reportDuration failed: %s", e.what());
|
|
}
|
|
}
|
|
|
|
void PlayerConfigurer::requestCharacter(shared_ptr<Network::Replicator> replicator, shared_ptr<bool> isWaiting)
|
|
{
|
|
(*isWaiting) = false;
|
|
|
|
dataModel->setUiMessage("Requesting character");
|
|
|
|
if (!loadResolved)
|
|
{
|
|
loadResolved = true;
|
|
double duration = G3D::System::time() - startTime;
|
|
reportDuration("GameLoad", "Success", duration, false);
|
|
analyticsPoints.addPoint("GameLoad", RBX::Time::nowFastSec());
|
|
}
|
|
|
|
try
|
|
{
|
|
replicator->requestCharacter();
|
|
dataModel->setUiMessage("Waiting for character");
|
|
waitingForCharacter = true;
|
|
}
|
|
catch (RBX::base_exception& e)
|
|
{
|
|
reportError(e.what(), "W4C");
|
|
}
|
|
}
|
|
|
|
void periodicallyZoomExtents(weak_ptr<DataModel> dmWeak, shared_ptr<bool> isWaiting)
|
|
{
|
|
shared_ptr<DataModel> dm = dmWeak.lock();
|
|
|
|
if (dm && *isWaiting)
|
|
{
|
|
dm->getWorkspace()->zoomToExtents();
|
|
|
|
dm->create<TimerService>()->delay(boost::bind(&periodicallyZoomExtents, dmWeak, isWaiting), 0.5);
|
|
}
|
|
}
|
|
|
|
void PlayerConfigurer::onGameClose()
|
|
{
|
|
double duration = G3D::System::time() - startTime;
|
|
|
|
if (!connectResolved)
|
|
{
|
|
reportDuration("GameConnect", "Failure", duration, true);
|
|
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "GameJoin", "Failure");
|
|
}
|
|
else if (!loadResolved || !joinResolved)
|
|
{
|
|
if (!loadResolved)
|
|
{
|
|
loadResolved = true;
|
|
reportDuration("GameLoad", "Cancel", duration, true);
|
|
}
|
|
|
|
if (!joinResolved)
|
|
{
|
|
joinResolved = true;
|
|
reportDuration("GameJoin", "Cancel", duration, true);
|
|
}
|
|
|
|
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "GameJoin", "Cancel");
|
|
}
|
|
else if (!playResolved)
|
|
{
|
|
duration = G3D::System::time() - playStartTime;
|
|
playResolved = true;
|
|
reportDuration("GameDuration", "Success", duration, true);
|
|
reportDuration("GameDuration_"+DebugSettings::singleton().osPlatform(), "Success", duration, true);
|
|
Stats::reportGameStatus("LeftGame");
|
|
}
|
|
|
|
}
|
|
|
|
void PlayerConfigurer::onDisconnection(const std::string& peer, bool lostConnection)
|
|
{
|
|
if (peer.length() > 0)
|
|
{
|
|
if (lostConnection)
|
|
{
|
|
if (!connectionFailed)
|
|
showErrorWindow("You have lost the connection to the game", "LostConnection", "LostConnection");
|
|
}
|
|
else
|
|
{
|
|
if (!connectionFailed)
|
|
{
|
|
showErrorWindow("This game has shut down", "Kick", "Kick");
|
|
if (dataModel)
|
|
{
|
|
Network::Players *players = dataModel->find<Network::Players>();
|
|
if (players)
|
|
{
|
|
Network::Player *p = players->getLocalPlayer();
|
|
if (p)
|
|
{
|
|
Instance *ps = p->findFirstChildByName("PlayerScripts");
|
|
if (ps)
|
|
ps->destroy();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
std::string url = RBX::format("%s&disconnect=true", getParamString("PingUrl").c_str());
|
|
#if defined(RBX_PLATFORM_DURANGO)
|
|
HttpAsync::get(url);
|
|
#else
|
|
dataModel->httpGet(url, true);
|
|
#endif
|
|
}
|
|
catch (RBX::base_exception&)
|
|
{
|
|
// don't care
|
|
}
|
|
}
|
|
|
|
void PlayerConfigurer::onConnectionAccepted(std::string url, shared_ptr<Instance> replicator)
|
|
{
|
|
connectResolved = true;
|
|
reportDuration("GameConnect", "Success", G3D::System::time() - startTime, false);
|
|
analyticsPoints.addPoint("Connect", RBX::Time::nowFastSec());
|
|
|
|
shared_ptr<bool> waitingForMarker(new bool(true));
|
|
|
|
try
|
|
{
|
|
if (!testing)
|
|
{
|
|
RBX::Visit* visit = dataModel->create<RBX::Visit>();
|
|
visit->setPing(getParamString("PingUrl"), getParamInt("PingInterval"));
|
|
}
|
|
|
|
if (!getParamBool("GenerateTeleportJoin"))
|
|
dataModel->setUiMessageBrickCount();
|
|
else
|
|
dataModel->setUiMessage("Teleporting...");
|
|
|
|
boost::shared_ptr<Network::ClientReplicator> rep = Instance::fastSharedDynamicCast<Network::ClientReplicator>(replicator);
|
|
|
|
connections.push_back(rep->disconnectionSignal.connect(boost::bind(&PlayerConfigurer::onDisconnection, this, _1, _2)));
|
|
connections.push_back(rep->receivedGlobalsSignal.connect(boost::bind(&PlayerConfigurer::onReceivedGlobals, this)));
|
|
|
|
connections.push_back(rep->gameLoadedSignal.connect(boost::bind(&PlayerConfigurer::onGameLoaded, this, waitingForMarker)));
|
|
}
|
|
catch (RBX::base_exception& e)
|
|
{
|
|
StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "onConnectionAccepted failed: %s", e.what());
|
|
reportError(e.what(), "ConnectionAccepted");
|
|
}
|
|
|
|
dataModel->create<TimerService>()->delay(boost::bind(&periodicallyZoomExtents, weak_from(dataModel), waitingForMarker), 0);
|
|
}
|
|
|
|
void PlayerConfigurer::onConnectionFailed(const std::string& remoteAddress, int errorCode, const std::string& errorMsg)
|
|
{
|
|
connectionFailed = true;
|
|
std::string msg = RBX::format("Failed to connect to the Game. (ID = %d: %s)", errorCode, errorMsg.c_str());
|
|
std::string errorType = RBX::format("ID%d", errorCode);
|
|
showErrorWindow(msg, errorType, "Other");
|
|
}
|
|
|
|
void PlayerConfigurer::onConnectionRejected()
|
|
{
|
|
showErrorWindow("This game is not available. Please try another", "WrongVersion", "WrongVersion");
|
|
}
|
|
|
|
void PlayerConfigurer::onReceivedGlobals()
|
|
{
|
|
analyticsPoints.addPoint("ReceivedGlobals", RBX::Time::nowFastSec());
|
|
}
|
|
|
|
void PlayerConfigurer::onGameLoaded(boost::shared_ptr<bool> isWaiting)
|
|
{
|
|
*isWaiting = false;
|
|
|
|
if (!loadResolved)
|
|
{
|
|
loadResolved = true;
|
|
double duration = G3D::System::time() - startTime;
|
|
reportDuration("GameLoad", "Success", duration, false);
|
|
analyticsPoints.addPoint("GameLoad", RBX::Time::nowFastSec());
|
|
}
|
|
|
|
try
|
|
{
|
|
dataModel->setUiMessage("Waiting for character");
|
|
waitingForCharacter = true;
|
|
}
|
|
catch (RBX::base_exception& e)
|
|
{
|
|
reportError(e.what(), "W4C");
|
|
}
|
|
}
|
|
|
|
void PlayerConfigurer::onPlayerIdled(double time)
|
|
{
|
|
if (time > 1200)
|
|
{
|
|
showErrorWindow(RBX::format("You were disconnected for being idle %d minutes", (int)(time/60)), "Idle", "Idle");
|
|
|
|
if (Network::Client* client = dataModel->find<Network::Client>())
|
|
client->disconnect();
|
|
}
|
|
}
|
|
|
|
void PlayerConfigurer::setMessage(const std::string& msg)
|
|
{
|
|
if (!getParamBool("GenerateTeleportJoin"))
|
|
dataModel->setUiMessage(msg);
|
|
else
|
|
dataModel->setUiMessage("Teleporting...");
|
|
}
|
|
|
|
void PlayerConfigurer::configure(RBX::Security::Identities identity, DataModel* dm, const std::string& args, int lm, const char* vrDevice)
|
|
{
|
|
startTime = G3D::System::time();
|
|
analyticsPoints.addPoint("Start", RBX::Time::nowFastSec());
|
|
|
|
dataModel = dm;
|
|
launchMode = lm;
|
|
|
|
// Client-side A/B testing setup
|
|
// urls to try:
|
|
// https://api.gametest1.pizzaboxer.fun/users/get-experiment-enrollments
|
|
// https://api.gametest1.pizzaboxer.fun/users/get-studio-experiment-enrollments
|
|
|
|
std::string baseUrl = RBX::ContentProvider::getUnsecureApiBaseUrl(GetBaseURL());
|
|
|
|
// begin fetching now
|
|
RBX::HttpFuture abTest1, abTest2;
|
|
if (FFlag::ClientABTestingEnabled)
|
|
{
|
|
abTest1 = FetchABTestDataAsync(baseUrl + "users/get-experiment-enrollments");
|
|
abTest2 = FetchABTestDataAsync(baseUrl + "users/get-studio-experiment-enrollments");
|
|
}
|
|
|
|
RBX::Security::Impersonator impersonate(identity);
|
|
|
|
parseArgs(args);
|
|
|
|
Http::gameID = getParamString("GameId");
|
|
Analytics::setUserId(getParamInt("UserId"));
|
|
TeleportService::SetBrowserTrackerId(getParamString("BrowserTrackerId"));
|
|
|
|
reportCounter("GameJoinStart,GameJoinStart"+DebugSettings::singleton().osPlatform(), false);
|
|
RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "GameJoin", "Start");
|
|
Stats::reportGameStatus("JoiningGame");
|
|
|
|
testing = (getParamString("ClientTicket").length() == 0);
|
|
logAnalytics = (rand() % 100 == 1);
|
|
|
|
if (DFFlag::UseR15Character)
|
|
dataModel->create<Network::Client>();
|
|
|
|
dataModel->setPlaceID(getParamInt("PlaceId"), getParamBool("IsRobloxPlace"));
|
|
int universeId = getParamInt("UniverseId");
|
|
dataModel->setUniverseId(universeId);
|
|
dataModel->create<HttpService>();
|
|
isTouchDevice = dm->create<UserInputService>()->getTouchEnabled();
|
|
|
|
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "! Joining game '%s' place %d at %s", getParamString("GameId").c_str(), getParamInt("PlaceId"), getParamString("MachineAddress").c_str());
|
|
|
|
connections.push_back(dataModel->closingSignal.connect(boost::bind(&PlayerConfigurer::onGameClose, this)));
|
|
|
|
dataModel->create<ChangeHistoryService>()->setEnabled(false);
|
|
dataModel->create<ContentProvider>()->setThreadPool(16);
|
|
|
|
setupUrls();
|
|
|
|
DataModel::CreatorType creatorType;
|
|
Reflection::EnumDesc<DataModel::CreatorType>::singleton().convertToValue(getParamString("CreatorTypeEnum").c_str(), creatorType);
|
|
dataModel->setCreatorID(getParamInt("CreatorId"), creatorType);
|
|
|
|
Network::Players* players = dataModel->create<Network::Players>();
|
|
Network::Players::ChatOption chatOption;
|
|
Reflection::EnumDesc<Network::Players::ChatOption>::singleton().convertToValue(getParamString("ChatStyle").c_str(), chatOption);
|
|
players->setChatOption(chatOption);
|
|
|
|
if (!DFFlag::UseR15Character)
|
|
dataModel->create<Network::Client>();
|
|
dataModel->create<Visit>();
|
|
|
|
ifSeleniumThenSetCookie("SeleniumTest1", "Started join script");
|
|
|
|
try
|
|
{
|
|
setMessage("Connecting to Server");
|
|
|
|
Network::Client* client = dm->create<Network::Client>();
|
|
|
|
connections.push_back(client->connectionAcceptedSignal.connect(boost::bind(&PlayerConfigurer::onConnectionAccepted, this, _1, _2)));
|
|
connections.push_back(client->connectionRejectedSignal.connect(boost::bind(&PlayerConfigurer::onConnectionRejected, this)));
|
|
connections.push_back(client->connectionFailedSignal.connect(boost::bind(&PlayerConfigurer::onConnectionFailed, this, _1, _2, _3)));
|
|
|
|
client->setTicket(getParamString("ClientTicket"));
|
|
|
|
ifSeleniumThenSetCookie("SeleniumTest2", "Successfully connected to server");
|
|
|
|
client->setGameSessionID(getParamString("SessionId"));
|
|
|
|
shared_ptr<Network::Player> player =
|
|
Instance::fastSharedDynamicCast<Network::Player>(client->playerConnect(getParamInt("UserId"), getParamString("MachineAddress"), getParamInt("ServerPort"), getParamInt("ClientPort"), -1));
|
|
|
|
if (vrDevice)
|
|
Network::Player::prop_VRDevice.setValue(player.get(), vrDevice);
|
|
|
|
// prepare callback for when the Character appears
|
|
playerChangedConnection = player->propertyChangedSignal.connect(boost::bind(&PlayerConfigurer::onPlayerChanged, this, _1));
|
|
|
|
registerPlay(getParamString("CookieStoreFirstTimePlayKey"), getParamInt("UserId"), getParamInt("PlaceId"));
|
|
if (TimerService* timer = dataModel->create<TimerService>()) {
|
|
timer->delay(boost::bind(&PlayerConfigurer::registerPlay, this, getParamString("CookieStoreFiveMinutePlayKey"), getParamInt("UserId"), getParamInt("PlaceId")), 60*5.0);
|
|
}
|
|
|
|
player->setSuperSafeChat(getParamBool("SuperSafeChat"));
|
|
player->setUnder13(getParamBool("IsUnknownOrUnder13"));
|
|
Network::Player::MembershipType membershipType;
|
|
Reflection::EnumDesc<Network::Player::MembershipType>::singleton().convertToValue(getParamString("MembershipType").c_str(), membershipType);
|
|
player->setMembershipType(membershipType);
|
|
player->setAccountAge(getParamInt("AccountAge"));
|
|
|
|
connections.push_back(player->idledSignal.connect(boost::bind(&PlayerConfigurer::onPlayerIdled, this, _1)));
|
|
|
|
try
|
|
{
|
|
player->setName(getParamString("UserName"));
|
|
}
|
|
catch (RBX::base_exception&)
|
|
{
|
|
// don't care, happens when called from studio cmd bar
|
|
}
|
|
|
|
player->setCharacterAppearance(getParamString("CharacterAppearance"));
|
|
player->setFollowUserId(getParamInt("FollowUserId"));
|
|
|
|
if (!testing)
|
|
{
|
|
RBX::Visit* visit = dataModel->create<RBX::Visit>();
|
|
visit->setUploadUrl("");
|
|
}
|
|
}
|
|
catch (RBX::base_exception& e)
|
|
{
|
|
reportError(e.what(), "CreatePlayer");
|
|
}
|
|
|
|
ifSeleniumThenSetCookie("SeleniumTest3", "Successfully created player");
|
|
|
|
if (!testing)
|
|
{
|
|
gamePerfMonitor.reset(new GamePerfMonitor(getParamString("BaseUrl"), getParamString("GameId"), getParamInt("PlaceId"), getParamInt("UserId")));
|
|
gamePerfMonitor->start(dataModel);
|
|
}
|
|
|
|
|
|
dataModel->setScreenshotSEOInfo(getParamString("ScreenShotInfo"));
|
|
dataModel->setVideoSEOInfo(getParamString("VideoInfo"));
|
|
|
|
ifSeleniumThenSetCookie("SeleniumTest4", "Finished join");
|
|
|
|
if (FFlag::ClientABTestingEnabled)
|
|
{
|
|
try
|
|
{
|
|
LoadABTestFromString(abTest1.get());
|
|
}
|
|
catch(const std::exception& e1)
|
|
{
|
|
try
|
|
{
|
|
LoadABTestFromString(abTest2.get());
|
|
}
|
|
catch(const std::exception& e2)
|
|
{
|
|
FASTLOG2(FLog::Error, "Failed to load AB test data from both URLS: [%s] [%s]", e1.what(), e2.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlayerConfigurer::onPlayerChanged(const Reflection::PropertyDescriptor* propertyDescriptor)
|
|
{
|
|
if (propertyDescriptor->name == "Character")
|
|
{
|
|
dataModel->clearUiMessage();
|
|
waitingForCharacter = false;
|
|
|
|
playerChangedConnection.disconnect();
|
|
|
|
if (!joinResolved)
|
|
{
|
|
joinResolved = true;
|
|
reportDuration("GameJoin", "Success", G3D::System::time() - startTime, false);
|
|
analyticsPoints.addPoint("GameJoin", RBX::Time::nowFastSec());
|
|
|
|
std::string successCounterByOsAndLaunchType = "GameJoinSuccess_" + DebugSettings::singleton().osPlatform();
|
|
if (launchMode == SharedLauncher::Play)
|
|
{
|
|
successCounterByOsAndLaunchType += "_Plugin";
|
|
analyticsPoints.addPoint("Mode", "Play");
|
|
}
|
|
else if (launchMode == SharedLauncher::Play_Protocol)
|
|
{
|
|
successCounterByOsAndLaunchType += "_Protocol";
|
|
analyticsPoints.addPoint("Mode", "ProtocolPlay");
|
|
}
|
|
|
|
reportCounter("GameJoinSuccess,GameJoin"+DebugSettings::singleton().osPlatform()+","+successCounterByOsAndLaunchType, false);
|
|
Stats::reportGameStatus("InGame");
|
|
|
|
if (Network::Players* players = dataModel->find<Network::Players>())
|
|
analyticsPoints.addPoint("CharacterAutoSpawn", players->getCharacterAutoSpawnProperty());
|
|
else
|
|
analyticsPoints.addPoint("CharacterAutoSpawn", false);
|
|
|
|
MegaClusterInstance *megaCluster = Instance::fastDynamicCast<MegaClusterInstance>(dataModel->getWorkspace()->getTerrain());
|
|
bool hasTerrain = megaCluster && megaCluster->isAllocated();
|
|
analyticsPoints.addPoint("HasTerrain", hasTerrain);
|
|
analyticsPoints.addPoint("StreamingEnabled", dataModel->getWorkspace()->getNetworkStreamingEnabled());
|
|
|
|
if (TeleportService::getPreviousPlaceId() > 0)
|
|
analyticsPoints.addPoint("Mode", "Teleport", true);
|
|
|
|
// send to influxdb
|
|
analyticsPoints.report("ClientJoin", DFInt::JoinInfluxHundredthsPercentage);
|
|
|
|
playStartTime = G3D::System::time();
|
|
playResolved = false;
|
|
}
|
|
|
|
if (gamePerfMonitor)
|
|
{
|
|
// delay collecting network stats by 2 mins after character is resolved
|
|
if (TimerService* timer = dataModel->create<TimerService>()) {
|
|
timer->delay(boost::bind(&GamePerfMonitor::setPostDiagStats, gamePerfMonitor, true), 2*60);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool isHidden(const boost::filesystem::path &p)
|
|
{
|
|
boost::filesystem::path name = p.filename();
|
|
if(name != ".." &&
|
|
name != "." &&
|
|
name.c_str()[0] == '.')
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool StudioConfigurer::findModulesAndLoad(const std::string& baseModulePath, const boost::filesystem::path& dir_path, boost::unordered_map<std::string, ProtectedString>& coreModules)
|
|
{
|
|
if ( !boost::filesystem::exists( dir_path ) )
|
|
return false;
|
|
|
|
boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
|
|
for ( boost::filesystem::directory_iterator itr( dir_path ); itr != end_itr; ++itr )
|
|
{
|
|
if ( is_directory(itr->status()) && !isHidden(itr->path()) )
|
|
{
|
|
if ( findModulesAndLoad(baseModulePath, itr->path(), coreModules) )
|
|
return true;
|
|
}
|
|
|
|
else if ( is_regular_file(itr->status()) )
|
|
{
|
|
boost::filesystem::path currentPath = itr->path();
|
|
std::string filePath = currentPath.string();
|
|
|
|
if (filePath.find(".lua") != std::string::npos)
|
|
{
|
|
size_t pathLocation = filePath.find(baseModulePath);
|
|
if (pathLocation != std::string::npos)
|
|
{
|
|
filePath = filePath.substr(pathLocation + baseModulePath.length() + 1);
|
|
filePath = filePath.substr(0,filePath.length() - 4);
|
|
}
|
|
|
|
//load module
|
|
if (!filePath.empty())
|
|
{
|
|
ProtectedString source = RBX::CoreScript::fetchSource("/Modules/" + filePath).get();
|
|
if (!source.empty())
|
|
{
|
|
coreModules[filePath] = source;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void StudioConfigurer::loadCoreModules()
|
|
{
|
|
RBX::CoreGuiService* coreGuiService = RBX::ServiceProvider::find<RBX::CoreGuiService>(dataModel);
|
|
|
|
if (!coreGuiService)
|
|
{
|
|
return;
|
|
}
|
|
|
|
shared_ptr<RBX::ScreenGui> robloxScreenGui = coreGuiService->getRobloxScreenGui();
|
|
if (!robloxScreenGui)
|
|
{
|
|
return;
|
|
}
|
|
|
|
shared_ptr<RBX::Folder> moduleScriptFolder = RBX::Creatable<Instance>::create<RBX::Folder>();
|
|
moduleScriptFolder->setName("Modules");
|
|
moduleScriptFolder->setRobloxLocked(true);
|
|
moduleScriptFolder->setParent(RBX::Instance::fastDynamicCast<RBX::Instance>(robloxScreenGui.get()));
|
|
|
|
boost::unordered_map<std::string, ProtectedString> coreModules;
|
|
|
|
if (LuaVM::canCompileScripts())
|
|
{
|
|
const std::string path = RBX::BaseScript::hasCoreScriptReplacements() ? RBX::BaseScript::adminScriptsPath + "/Modules" :
|
|
ContentProvider::assetFolder() + "scripts/Modules";
|
|
|
|
boost::filesystem::path filePath(path);
|
|
if ( !boost::filesystem::exists(filePath) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
findModulesAndLoad(filePath.string(), filePath, coreModules);
|
|
}
|
|
else
|
|
{
|
|
boost::unordered_map<std::string, std::string> byteCodeModules = LuaVM::getBytecodeCoreModules();
|
|
for (boost::unordered_map<std::string, std::string>::iterator iter = byteCodeModules.begin(); iter != byteCodeModules.end(); ++iter)
|
|
{
|
|
coreModules[RBX::rot13((*iter).first)] = ProtectedString::fromBytecode((*iter).second);
|
|
}
|
|
}
|
|
|
|
for (boost::unordered_map<std::string, ProtectedString>::iterator iter = coreModules.begin(); iter != coreModules.end(); ++iter)
|
|
{
|
|
shared_ptr<ModuleScript> moduleScript = RBX::Creatable<Instance>::create<ModuleScript>();
|
|
|
|
std::string name = (*iter).first;
|
|
shared_ptr<RBX::Folder> lastFolder = moduleScriptFolder;
|
|
|
|
boost::filesystem::path namePath(name);
|
|
if (namePath.has_parent_path())
|
|
{
|
|
const std::string delimiter = "\\";
|
|
|
|
while (name.find("/") != std::string::npos)
|
|
{
|
|
name.replace(name.find("/"), 1, delimiter);
|
|
}
|
|
|
|
size_t pos = 0;
|
|
int level = 0;
|
|
std::string token;
|
|
while ((pos = name.find(delimiter)) != std::string::npos)
|
|
{
|
|
token = name.substr(0, pos);
|
|
name.erase(0, pos + delimiter.length());
|
|
|
|
if (Instance* findFolder = moduleScriptFolder->findFirstChildByNameRecursive(token))
|
|
{
|
|
if (RBX::Folder* folderInstance = Instance::fastDynamicCast<RBX::Folder>(findFolder))
|
|
{
|
|
lastFolder = shared_from(folderInstance);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
shared_ptr<RBX::Folder> newFolder = RBX::Creatable<Instance>::create<RBX::Folder>();
|
|
newFolder->setName(token);
|
|
newFolder->setRobloxLocked(true);
|
|
if (level == 0)
|
|
{
|
|
newFolder->setParent(moduleScriptFolder.get());
|
|
}
|
|
else
|
|
{
|
|
newFolder->setParent(lastFolder.get());
|
|
}
|
|
lastFolder = newFolder;
|
|
}
|
|
level++;
|
|
}
|
|
}
|
|
|
|
moduleScript->setName(name);
|
|
moduleScript->setSource((*iter).second);
|
|
moduleScript->setRobloxLocked(true);
|
|
|
|
moduleScript->setParent(lastFolder.get());
|
|
}
|
|
}
|
|
|
|
void StudioConfigurer::configure(RBX::Security::Identities identity, DataModel* dm, const std::string& args, int launchMode, const char* vrDevice)
|
|
{
|
|
dataModel = dm;
|
|
parseArgs(args);
|
|
|
|
std::string baseUrl = getParamString("BaseUrl");
|
|
|
|
dataModel->create<InsertService>();
|
|
|
|
setupUrls();
|
|
|
|
// do not load core modules for cloud edit
|
|
if (Network::Players::isCloudEdit(dataModel))
|
|
return;
|
|
|
|
if (Network::Players::frontendProcessing(dataModel))
|
|
loadCoreModules();
|
|
|
|
#if defined(RBX_PLATFORM_DURANGO)
|
|
if (ScriptContext* scriptContext = dataModel->create<ScriptContext>())
|
|
{
|
|
if(starterScript.empty())
|
|
starterScript = "StarterScript";
|
|
scriptContext->addCoreScriptLocal(starterScript, shared_ptr<Instance>());
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if defined(RBX_STUDIO_BUILD) && ENABLE_XBOX_STUDIO_BUILD
|
|
if (ScriptContext* scriptContext = dataModel->create<ScriptContext>())
|
|
{
|
|
starterScript = "XStarterScript";
|
|
scriptContext->addCoreScriptLocal(starterScript, shared_ptr<Instance>());
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
|
|
// this will be called in case of old play solo
|
|
if (ScriptContext* scriptContext = dataModel->create<ScriptContext>())
|
|
{
|
|
if (Network::Players::backendProcessing(dataModel))
|
|
{
|
|
starterScript = "ServerStarterScript";
|
|
scriptContext->addCoreScriptLocal(starterScript, shared_ptr<Instance>());
|
|
}
|
|
|
|
if (Network::Players::frontendProcessing(dataModel))
|
|
{
|
|
starterScript = "StarterScript";
|
|
scriptContext->addCoreScriptLocal(starterScript, shared_ptr<Instance>());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|