forked from aya/aya
Initial commit
This commit is contained in:
66
client/player/CMakeLists.txt
Normal file
66
client/player/CMakeLists.txt
Normal file
@@ -0,0 +1,66 @@
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
add_executable(Player
|
||||
resources/qt.qrc
|
||||
|
||||
src/main.cpp
|
||||
|
||||
src/Application.cpp
|
||||
src/Application.hpp
|
||||
src/AvatarViewService.cpp
|
||||
src/AvatarViewService.hpp
|
||||
src/Document.cpp
|
||||
src/Document.hpp
|
||||
src/FunctionMarshaller.cpp
|
||||
src/FunctionMarshaller.hpp
|
||||
src/GameVerbs.cpp
|
||||
src/GameVerbs.hpp
|
||||
src/LauncherView.cpp
|
||||
src/LauncherView.hpp
|
||||
src/RenderJob.cpp
|
||||
src/RenderJob.hpp
|
||||
src/View.cpp
|
||||
src/View.hpp
|
||||
src/Window.cpp
|
||||
src/Window.hpp
|
||||
${CLIENT_DIR}/common/AppSettings.cpp
|
||||
${CLIENT_DIR}/common/AppSettings.hpp
|
||||
${CLIENT_DIR}/common/GrayChatBar.hpp
|
||||
${CLIENT_DIR}/common/GrayChatBar.cpp
|
||||
${CLIENT_DIR}/common/SDLGameController.cpp
|
||||
${CLIENT_DIR}/common/SDLGameController.hpp
|
||||
)
|
||||
|
||||
target_link_libraries(Player
|
||||
3D
|
||||
AppPlayer
|
||||
Core
|
||||
RakNet
|
||||
BulletPhysics
|
||||
NetworkPlayer
|
||||
Graphics
|
||||
)
|
||||
|
||||
if(AYA_OS_WINDOWS)
|
||||
target_sources(Player PRIVATE
|
||||
resources/winrc.h
|
||||
resources/script.rc
|
||||
)
|
||||
|
||||
set_target_properties(Player PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
windeployqt(Player)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(Player PRIVATE QT_NO_KEYWORDS)
|
||||
|
||||
target_include_directories(Player PRIVATE src resources)
|
||||
set_target_properties(Player PROPERTIES OUTPUT_NAME "Aya.Player")
|
||||
|
||||
add_custom_command(TARGET Player POST_BUILD
|
||||
COMMENT "Copying runtime files to build directory"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${RUNTIME_FILES}"
|
||||
"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
|
||||
)
|
||||
BIN
client/player/resources/icon.ico
Normal file
BIN
client/player/resources/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
5
client/player/resources/qt.qrc
Normal file
5
client/player/resources/qt.qrc
Normal file
@@ -0,0 +1,5 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file>icon.ico</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
49
client/player/resources/script.rc
Normal file
49
client/player/resources/script.rc
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "winrc.h"
|
||||
|
||||
#if defined(__MINGW64__) || defined(__MINGW32__)
|
||||
// MinGW-w64, MinGW
|
||||
#if defined(__has_include) && __has_include(<winres.h>)
|
||||
#include <winres.h>
|
||||
#else
|
||||
#include <afxres.h>
|
||||
#include <winresrc.h>
|
||||
#endif
|
||||
#else
|
||||
// MSVC, Windows SDK
|
||||
#include <winres.h>
|
||||
#endif
|
||||
|
||||
IDI_ICON1 ICON APP_ICON
|
||||
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VERSION_RESOURCE
|
||||
PRODUCTVERSION VERSION_RESOURCE
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x4L
|
||||
FILETYPE 0x1L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", APP_ORGANIZATION
|
||||
VALUE "FileDescription", APP_DESCRIPTION
|
||||
VALUE "FileVersion", VERSION_RESOURCE_STR
|
||||
VALUE "LegalCopyright", APP_COPYRIGHT
|
||||
VALUE "ProductName", APP_NAME
|
||||
VALUE "ProductVersion", VERSION_RESOURCE_STR
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", PRODUCT_LANGUAGE, PRODUCT_CHARSET
|
||||
END
|
||||
END
|
||||
24
client/player/resources/winrc.h
Normal file
24
client/player/resources/winrc.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#define VERSION_MAJOR_MINOR_STR AYA_VERSION_MAJOR_STR "." AYA_VERSION_MINOR_STR
|
||||
#define VERSION_MAJOR_MINOR_PATCH_STR VERSION_MAJOR_MINOR_STR "." AYA_VERSION_PATCH_STR
|
||||
#ifdef AYA_VERSION_TYPE
|
||||
#define VERSION_FULL_STR VERSION_MAJOR_MINOR_PATCH_STR "-" AYA_VERSION_TYPE
|
||||
#else
|
||||
#define VERSION_FULL_STR VERSION_MAJOR_MINOR_PATCH_STR
|
||||
#endif
|
||||
#define VERSION_RESOURCE AYA_VERSION_MAJOR, AYA_VERSION_MINOR, AYA_VERSION_PATCH, 0
|
||||
#define VERSION_RESOURCE_STR VERSION_FULL_STR "\0"
|
||||
|
||||
/*
|
||||
* These properties are part of VarFileInfo.
|
||||
* For more information, please see: https://learn.microsoft.com/en-us/windows/win32/menurc/varfileinfo-block
|
||||
*/
|
||||
#define PRODUCT_LANGUAGE 0x0409 // en-US
|
||||
#define PRODUCT_CHARSET 1200 // Unicode
|
||||
|
||||
#define APP_ICON "icon.ico"
|
||||
#define APP_NAME AYA_PROJECT_NAME "\0"
|
||||
#define APP_DESCRIPTION AYA_PROJECT_NAME " Player\0"
|
||||
#define APP_ORGANIZATION AYA_PROJECT_NAME "\0"
|
||||
#define APP_COPYRIGHT AYA_PROJECT_NAME " License\0"
|
||||
472
client/player/src/Application.cpp
Normal file
472
client/player/src/Application.cpp
Normal file
@@ -0,0 +1,472 @@
|
||||
#include "Application.hpp"
|
||||
#include "DataModel/GameBasicSettings.hpp"
|
||||
#include "Reflection/Reflection.hpp"
|
||||
#include "Utility/AyaService.hpp"
|
||||
#include "Utility/StandardOut.hpp"
|
||||
#include "View.hpp"
|
||||
#include "DataModel/Game.hpp"
|
||||
#include "FunctionMarshaller.hpp"
|
||||
#include "Document.hpp"
|
||||
#include "Utility/Statistics.hpp"
|
||||
#include "DataModel/ContentProvider.hpp"
|
||||
#include "Render/VisualEngine.hpp"
|
||||
#include "Xml/XmlSerializer.hpp"
|
||||
#include "Reflection/ReflectionMetadata.hpp"
|
||||
|
||||
#include "boost/any.hpp"
|
||||
#include "boost/intrusive/list.hpp"
|
||||
#include "Xml/WebParser.hpp"
|
||||
#include "DataModel/Stats.hpp"
|
||||
#include "DataModel/TeleportService.hpp"
|
||||
#include "DataModel/DebugSettings.hpp"
|
||||
#include "API.hpp"
|
||||
#include "Utility/HttpAsync.hpp"
|
||||
#include "AvatarViewService.hpp"
|
||||
#include "Script/CoreScript.hpp"
|
||||
#include "Script/ScriptContext.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <QMessageBox>
|
||||
#include <QSettings>
|
||||
|
||||
#include "AppSettings.hpp"
|
||||
#include "winrc.h"
|
||||
|
||||
FASTFLAG(PlaceLauncherUsePOST)
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
AYA_REGISTER_CLASS(AvatarViewService);
|
||||
|
||||
Application::Application()
|
||||
{
|
||||
gameReady = false;
|
||||
launchMode = Play;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
class PrintfLogger
|
||||
{
|
||||
Aya::signals::scoped_connection messageConnection;
|
||||
HANDLE handle;
|
||||
Aya::spin_mutex mutex;
|
||||
|
||||
public:
|
||||
PrintfLogger()
|
||||
: handle(GetStdHandle(STD_OUTPUT_HANDLE))
|
||||
{
|
||||
messageConnection = Aya::StandardOut::singleton()->messageOut.connect(boost::bind(&PrintfLogger::onMessage, this, boost::placeholders::_1));
|
||||
}
|
||||
|
||||
protected:
|
||||
void onMessage(const Aya::StandardOutMessage& message)
|
||||
{
|
||||
Aya::spin_mutex::scoped_lock lock(mutex);
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm* timeinfo = localtime(&now);
|
||||
char buffer[30];
|
||||
strftime(buffer, sizeof(buffer), "[%m/%d/%Y %I:%M:%S %p]", timeinfo);
|
||||
|
||||
SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
|
||||
printf("%s ", buffer);
|
||||
|
||||
const char* levelStr = "UNKNOWN";
|
||||
WORD levelColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
||||
|
||||
switch (message.type)
|
||||
{
|
||||
case Aya::MESSAGE_OUTPUT:
|
||||
levelColor = FOREGROUND_BLUE | FOREGROUND_INTENSITY;
|
||||
levelStr = "OUTPUT";
|
||||
break;
|
||||
case Aya::MESSAGE_INFO:
|
||||
levelColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
||||
levelStr = "INFO";
|
||||
break;
|
||||
case Aya::MESSAGE_WARNING:
|
||||
levelColor = FOREGROUND_RED | FOREGROUND_GREEN;
|
||||
levelStr = "WARNING";
|
||||
break;
|
||||
case Aya::MESSAGE_ERROR:
|
||||
levelColor = FOREGROUND_RED | FOREGROUND_INTENSITY;
|
||||
levelStr = "ERROR";
|
||||
break;
|
||||
}
|
||||
|
||||
SetConsoleTextAttribute(handle, levelColor);
|
||||
printf("[%s] ", levelStr);
|
||||
|
||||
// white
|
||||
SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
|
||||
printf("%s\n", message.message.c_str());
|
||||
|
||||
// reset
|
||||
SetConsoleTextAttribute(handle, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
static std::string readStringValue(shared_ptr<const Reflection::ValueTable> jsonResult, std::string name)
|
||||
{
|
||||
Reflection::ValueTable::const_iterator itData = jsonResult->find(name);
|
||||
if (itData != jsonResult->end())
|
||||
{
|
||||
return itData->second.get<std::string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(Aya::format("Unexpected string result for %s", name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static int readIntValue(shared_ptr<const Reflection::ValueTable> jsonResult, std::string name)
|
||||
{
|
||||
Reflection::ValueTable::const_iterator itData = jsonResult->find(name);
|
||||
if (itData != jsonResult->end())
|
||||
{
|
||||
return itData->second.get<int>();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(Aya::format("Unexpected int result for %s", name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
Application::RequestPlaceInfoResult Application::requestPlaceInfo(
|
||||
const std::string url, std::string& authenticationUrl, std::string& ticket, std::string& scriptUrl) const
|
||||
{
|
||||
try
|
||||
{
|
||||
std::string response;
|
||||
if (FFlag::PlaceLauncherUsePOST)
|
||||
{
|
||||
std::istringstream input("");
|
||||
Aya::Http(url).post(input, Aya::Http::kContentTypeDefaultUnspecified, false, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
Aya::Http(url).get(response);
|
||||
}
|
||||
|
||||
std::stringstream jsonStream;
|
||||
jsonStream << response;
|
||||
shared_ptr<const Reflection::ValueTable> jsonResult(Aya::make_shared<const Reflection::ValueTable>());
|
||||
bool parseResult = WebParser::parseJSONTable(jsonStream.str(), jsonResult);
|
||||
if (parseResult)
|
||||
{
|
||||
int status = readIntValue(jsonResult, "status");
|
||||
if (status == 2)
|
||||
{
|
||||
authenticationUrl = readStringValue(jsonResult, "authenticationUrl");
|
||||
ticket = readStringValue(jsonResult, "authenticationTicket");
|
||||
scriptUrl = readStringValue(jsonResult, "joinScriptUrl");
|
||||
return SUCCESS;
|
||||
}
|
||||
else if (status == 6)
|
||||
return GAME_FULL;
|
||||
else if (status == 10)
|
||||
return USER_LEFT;
|
||||
else
|
||||
{
|
||||
// 0 or 1 is not an error - it is a sign that we should wait
|
||||
if (status == 0 || status == 1)
|
||||
return RETRY;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
StandardOut::singleton()->printf(MESSAGE_ERROR, "Exception when requesting place info: %s. ", e.what());
|
||||
}
|
||||
|
||||
return FAILED;
|
||||
}
|
||||
|
||||
static HttpFuture fetchJoinScriptAsync(const std::string& url)
|
||||
{
|
||||
if (ContentProvider::isUrl(url) && Aya::Network::isTrustedContent(url.c_str()))
|
||||
{
|
||||
return HttpAsync::getWithRetries(url, 5);
|
||||
}
|
||||
else
|
||||
{
|
||||
// silent error is harder to hack
|
||||
return boost::shared_future<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::Initialize(OgreWidget* window)
|
||||
{
|
||||
mainWindow = window;
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_SYSTEM, "Welcome to %s Player v%s!", AYA_PROJECT_NAME, VERSION_FULL_STR);
|
||||
|
||||
// should set to governor baseurl, initial setup ask for master server url?
|
||||
QSettings settings;
|
||||
std::string url = settings.value("JsHelpers/masterServerUrl").toString().toStdString();
|
||||
|
||||
Game::globalInit(false);
|
||||
|
||||
std::string authenticationUrl;
|
||||
std::string authenticationTicket;
|
||||
std::string scriptUrl;
|
||||
bool scriptIsPlaceLauncher = false;
|
||||
|
||||
if (vm.count("authenticationUrl") > 0 && vm.count("authenticationTicket") > 0 && vm.count("joinScriptUrl") > 0)
|
||||
{
|
||||
authenticationUrl = vm["authenticationUrl"].as<std::string>();
|
||||
authenticationTicket = vm["authenticationTicket"].as<std::string>();
|
||||
scriptUrl = vm["joinScriptUrl"].as<std::string>();
|
||||
|
||||
if (vm.find("browserTrackerId") != vm.end())
|
||||
{
|
||||
Stats::setBrowserTrackerId(vm["browserTrackerId"].as<std::string>());
|
||||
|
||||
TeleportService::SetBrowserTrackerId(vm["browserTrackerId"].as<std::string>());
|
||||
}
|
||||
|
||||
std::string lowerScriptUrl = scriptUrl;
|
||||
std::transform(lowerScriptUrl.begin(), lowerScriptUrl.end(), lowerScriptUrl.begin(), tolower);
|
||||
if (lowerScriptUrl.find("placelauncher.ashx") != std::string::npos)
|
||||
{
|
||||
launchMode = Play_Protocol;
|
||||
scriptIsPlaceLauncher = true;
|
||||
}
|
||||
}
|
||||
|
||||
marshaller = FunctionMarshaller::GetWindow();
|
||||
TaskScheduler::singleton().setThreadCount(TaskSchedulerSettings::singleton().getThreadPoolConfig());
|
||||
|
||||
if (scriptUrl != "")
|
||||
{
|
||||
HttpFuture joinScriptResult;
|
||||
if (!scriptIsPlaceLauncher)
|
||||
joinScriptResult = fetchJoinScriptAsync(scriptUrl);
|
||||
|
||||
InitializeNewGame(joinScriptResult, GameBasicSettings::VirtualVersion::VERSION_2016);
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<DataModel> Application::getDM()
|
||||
{
|
||||
return currentDocument->getGame()->getDataModel();
|
||||
}
|
||||
|
||||
void Application::InitializeNewGame(HttpFuture& scriptResult, GameBasicSettings::VirtualVersion vv)
|
||||
{
|
||||
gameReady = true;
|
||||
InitializeNewGame(vv);
|
||||
|
||||
if (boost::shared_ptr<Aya::DataModel> datamodel = currentDocument->getGame()->getDataModel())
|
||||
{
|
||||
currentDocument->startedSignal.connect(boost::bind(&Application::onDocumentStarted, this, _1));
|
||||
datamodel->submitTask(boost::bind(&Document::StartResult, currentDocument.get(), scriptResult, launchMode, false), DataModelJob::Write);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::SendScript(std::string script, GameBasicSettings::VirtualVersion virtualVersion)
|
||||
{
|
||||
gameReady = true;
|
||||
if (boost::shared_ptr<Aya::DataModel> datamodel = currentDocument->getGame()->getDataModel())
|
||||
{
|
||||
currentDocument->startedSignal.connect(boost::bind(&Application::onDocumentStarted, this, _1));
|
||||
datamodel->submitTask(boost::bind(&Document::Start, currentDocument.get(), script, launchMode, false), DataModelJob::Write);
|
||||
datamodel->setInitialVersion(virtualVersion);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::InitializeNewGame(GameBasicSettings::VirtualVersion vv)
|
||||
{
|
||||
mainWindow->setVisible(true);
|
||||
|
||||
if (currentDocument)
|
||||
{
|
||||
currentDocument->PrepareShutdown();
|
||||
mainView->Stop();
|
||||
currentDocument->Shutdown();
|
||||
currentDocument.reset();
|
||||
}
|
||||
|
||||
currentDocument.reset(new Document());
|
||||
currentDocument->Initialize(mainWindow, true, true, true, vv);
|
||||
mainView.reset(new View(mainWindow));
|
||||
mainView->setOgreWindow(dynamic_cast<OgreWindow*>(mainWindow->getOgreWindow()));
|
||||
mainView->Start(currentDocument->getGame());
|
||||
|
||||
initVerbs();
|
||||
}
|
||||
|
||||
void Application::InitializeNewEmptyGame()
|
||||
{
|
||||
mainWindow->setVisible(true);
|
||||
|
||||
if (currentDocument)
|
||||
{
|
||||
currentDocument->PrepareShutdown();
|
||||
mainView->Stop();
|
||||
currentDocument->Shutdown();
|
||||
currentDocument.reset();
|
||||
}
|
||||
|
||||
Document* document = new Document();
|
||||
|
||||
currentDocument.reset(document);
|
||||
currentDocument->Initialize(mainWindow, true, false, false, GameBasicSettings::VirtualVersion::VERSION_2012);
|
||||
mainView.reset(new View(mainWindow));
|
||||
mainView->Start(currentDocument->getGame());
|
||||
|
||||
shared_ptr<Aya::DataModel> dataModel = currentDocument->getGame()->getDataModel();
|
||||
|
||||
try
|
||||
{
|
||||
if (boost::optional<ProtectedString> source = CoreScript::fetchSource("AvatarView"))
|
||||
{
|
||||
if (ScriptContext* sc = Aya::ServiceProvider::create<ScriptContext>(dataModel.get()))
|
||||
{
|
||||
sc->executeInNewThread(Aya::Security::RobloxGameScript_, *source, "AvatarView");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
StandardOut::singleton()->printf(MESSAGE_ERROR, "AvatarView Error: %s", e.what());
|
||||
}
|
||||
|
||||
initVerbs();
|
||||
}
|
||||
|
||||
void Application::sendInputEvent(shared_ptr<InputObject> obj)
|
||||
{
|
||||
currentDocument->sendInputObject(obj);
|
||||
}
|
||||
|
||||
void Application::onResize(int x, int y)
|
||||
{
|
||||
mainView->onResize(x, y);
|
||||
}
|
||||
|
||||
void Application::loadAppSettings()
|
||||
{
|
||||
this->settings = new AppSettings(QCoreApplication::applicationDirPath().toStdString());
|
||||
|
||||
if (!settings->load())
|
||||
{
|
||||
// show error
|
||||
QMessageBox::warning(
|
||||
nullptr, "Error", "Failed to load application settings from AppSettings.ini. Make sure that the file exists and is free of any errors.");
|
||||
this->shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::parseCommandLine(int argc, char** argv)
|
||||
{
|
||||
std::vector<std::string> args;
|
||||
for (int i = 0; i < argc; i++)
|
||||
args.push_back(argv[i]);
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
po::options_description desc("Options");
|
||||
desc.add_options()("help,?", "produce help message")("globalBasicSettingsPath,g", po::value<std::string>(),
|
||||
"path to GlobalBasicSettings_(n).xml")("version,v", "print version string")("id", po::value<int>(), "id of the place to join")("content,c",
|
||||
po::value<std::string>(), "path to the content directory")("authenticationUrl,a", po::value<std::string>(), "authentication url from server")(
|
||||
"authenticationTicket,t", po::value<std::string>(), "game session ticket from server")("joinScriptUrl,j", po::value<std::string>(),
|
||||
"url of launch script from server")("browserTrackerId,b", po::value<std::string>(), "browser tracking id from website")(
|
||||
"waitEvent,w", po::value<std::string>(), "window is invisible until this named event is signaled")(
|
||||
"API", po::value<std::string>(), "output API file")("dmp,d", "upload crash dmp")("play", "specifies the launching of a game")(
|
||||
"app", "specifies the launching of an app")("fast", "uses fast startup path")("console", "developer console output")(
|
||||
"nochromium", "disable cef initialization")("httpClientSettings,S", "use website for clientsettings")("vite", "use local vite server")
|
||||
|
||||
;
|
||||
|
||||
po::store(po::command_line_parser(args).options(desc).run(), vm);
|
||||
|
||||
if (vm.count("console") > 0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
AllocConsole();
|
||||
freopen("CONOUT$", "w", stdout);
|
||||
freopen("CONOUT$", "w", stderr);
|
||||
freopen("CONIN$", "r", stdin);
|
||||
|
||||
static boost::scoped_ptr<PrintfLogger> standardOutLog(new PrintfLogger());
|
||||
#else
|
||||
StandardOut::singleton()->printf(MESSAGE_WARNING, "--console is not currently available on non-Windows platforms.");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (vm.count("help") || args.size() == 0)
|
||||
{
|
||||
std::basic_stringstream<char> options;
|
||||
desc.print(options);
|
||||
printf("%s", options.str().c_str());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (vm.count("API") > 0)
|
||||
{
|
||||
std::string fileName = vm["API"].as<std::string>();
|
||||
std::ofstream stream(fileName.c_str());
|
||||
Aya::Reflection::Metadata::writeEverything(stream);
|
||||
exit(-1);
|
||||
}
|
||||
if (vm.count("content"))
|
||||
{
|
||||
std::string contentDir(vm["content"].as<std::string>());
|
||||
ContentProvider::setAssetFolder(contentDir.c_str());
|
||||
}
|
||||
|
||||
// used to determine how we will initialize datamodel
|
||||
if (vm.count("play"))
|
||||
launchMode = Play;
|
||||
}
|
||||
|
||||
void Application::onDocumentStarted(bool isTeleport)
|
||||
{
|
||||
printf("onDocumentStarted\n");
|
||||
}
|
||||
|
||||
void Application::initVerbs()
|
||||
{
|
||||
DataModel* dm = currentDocument->getGame()->getDataModel().get();
|
||||
leaveGameVerb.reset(new LeaveGameVerb(*mainView, dm));
|
||||
Aya::ViewBase* gfx = mainView->GetGfxView();
|
||||
toggleFullscreenVerb.reset(new ToggleFullscreenVerb(*mainView, dm));
|
||||
}
|
||||
|
||||
void Application::shutdownVerbs()
|
||||
{
|
||||
toggleFullscreenVerb.reset();
|
||||
leaveGameVerb.reset();
|
||||
}
|
||||
|
||||
void Application::prepareToShutdown()
|
||||
{
|
||||
if (currentDocument) // KILL
|
||||
currentDocument->PrepareShutdown();
|
||||
|
||||
if (mainView)
|
||||
mainView->AboutToShutdown();
|
||||
}
|
||||
|
||||
void Application::shutdown()
|
||||
{
|
||||
shutdownVerbs();
|
||||
|
||||
if (mainView)
|
||||
{ // STOP
|
||||
mainView->Stop();
|
||||
mainView.reset();
|
||||
}
|
||||
|
||||
if (currentDocument)
|
||||
{
|
||||
currentDocument->Shutdown();
|
||||
currentDocument.reset();
|
||||
}
|
||||
|
||||
Aya::GlobalBasicSettings::singleton()->saveState();
|
||||
Game::globalExit();
|
||||
}
|
||||
} // namespace Aya
|
||||
96
client/player/src/Application.hpp
Normal file
96
client/player/src/Application.hpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
#include "intrusive_ptr_target.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
#include "atomic.hpp"
|
||||
#include "boost/scoped_ptr.hpp"
|
||||
#include "View.hpp"
|
||||
#include "Document.hpp"
|
||||
|
||||
#include "GameVerbs.hpp"
|
||||
|
||||
#include "boost/program_options.hpp"
|
||||
|
||||
#include "Window.hpp"
|
||||
#include "AppSettings.hpp"
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
// more forward declarations
|
||||
class Game;
|
||||
class UserInput;
|
||||
class RenderJob;
|
||||
class LeaveGameVerb;
|
||||
class ToggleFullscreenVerb;
|
||||
class FunctionMarshaller;
|
||||
class Document;
|
||||
struct StandardOutMessage;
|
||||
class View;
|
||||
|
||||
namespace Tasks
|
||||
{
|
||||
class Sequence;
|
||||
}
|
||||
|
||||
class Application
|
||||
{
|
||||
boost::scoped_ptr<ToggleFullscreenVerb> toggleFullscreenVerb;
|
||||
boost::scoped_ptr<LeaveGameVerb> leaveGameVerb;
|
||||
boost::scoped_ptr<Document> currentDocument;
|
||||
OgreWidget* mainWindow;
|
||||
FunctionMarshaller* marshaller;
|
||||
boost::scoped_ptr<View> mainView;
|
||||
LaunchMode launchMode;
|
||||
boost::program_options::variables_map vm;
|
||||
std::string joinScript;
|
||||
bool gameReady;
|
||||
|
||||
void HandleSDLMessage();
|
||||
|
||||
public:
|
||||
enum RequestPlaceInfoResult
|
||||
{
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
RETRY,
|
||||
GAME_FULL,
|
||||
USER_LEFT,
|
||||
};
|
||||
|
||||
|
||||
Application();
|
||||
|
||||
void parseCommandLine(int argc, char** argv);
|
||||
|
||||
bool isGameReady()
|
||||
{
|
||||
return gameReady;
|
||||
};
|
||||
|
||||
RequestPlaceInfoResult requestPlaceInfo(const std::string url, std::string& authenticationUrl, std::string& ticket, std::string& scriptUrl) const;
|
||||
bool requestPlaceInfo(int placeId, std::string& authenticationUrl, std::string& ticket, std::string& scriptUrl) const;
|
||||
|
||||
void initVerbs();
|
||||
void shutdownVerbs();
|
||||
void loadAppSettings();
|
||||
void sendInputEvent(shared_ptr<InputObject> obj);
|
||||
shared_ptr<DataModel> getDM();
|
||||
void Initialize(OgreWidget* window);
|
||||
void InitializeNewGame(HttpFuture& scriptResult, GameBasicSettings::VirtualVersion vv);
|
||||
void InitializeNewGame(GameBasicSettings::VirtualVersion vv);
|
||||
void InitializeNewEmptyGame();
|
||||
void SendScript(std::string script, GameBasicSettings::VirtualVersion virtualVersion);
|
||||
void onResize(int x, int y);
|
||||
void onDocumentStarted(bool isTeleport);
|
||||
|
||||
void prepareToShutdown();
|
||||
void shutdown();
|
||||
|
||||
boost::program_options::variables_map getVm()
|
||||
{
|
||||
return vm;
|
||||
}
|
||||
|
||||
AppSettings* settings;
|
||||
};
|
||||
} // namespace Aya
|
||||
48
client/player/src/AvatarViewService.cpp
Normal file
48
client/player/src/AvatarViewService.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "AvatarViewService.hpp"
|
||||
#include "LauncherView.hpp"
|
||||
#include "Reflection/Reflection.hpp"
|
||||
#include "Utility/StandardOut.hpp"
|
||||
#include <QString>
|
||||
namespace Aya
|
||||
{
|
||||
static Reflection::BoundFuncDesc<AvatarViewService, void(std::string)> avs_setColor(
|
||||
&AvatarViewService::setColor, "SetColor", "colorJSON", Security::None);
|
||||
static Reflection::BoundFuncDesc<AvatarViewService, std::string()> avs_getColor(&AvatarViewService::getColors, "GetColor", Security::None);
|
||||
|
||||
const char* const sAvatarViewService = "AvatarViewService";
|
||||
|
||||
static Reflection::EventDesc<AvatarViewService, void(int64_t, std::string)> event_assetWornSignal(
|
||||
&AvatarViewService::assetWornSignal, "AssetWorn", "assetId", "assetType");
|
||||
|
||||
AvatarViewService::AvatarViewService()
|
||||
{
|
||||
setName(sAvatarViewService);
|
||||
}
|
||||
|
||||
AvatarViewService::~AvatarViewService() {}
|
||||
|
||||
void AvatarViewService::setJsHelpers(JsHelpers* helpers)
|
||||
{
|
||||
this->helpers = helpers;
|
||||
}
|
||||
|
||||
void AvatarViewService::setColor(std::string json)
|
||||
{
|
||||
if (!this->helpers)
|
||||
throw std::runtime_error("helpers invalid");
|
||||
|
||||
this->helpers->setBodyColorJson(QString::fromStdString(json));
|
||||
// this->helpers->onMapPicked()
|
||||
}
|
||||
|
||||
std::string AvatarViewService::getColors()
|
||||
{
|
||||
if (!this->helpers)
|
||||
throw std::runtime_error("helpers invalid");
|
||||
|
||||
QString json = this->helpers->getBodyColorJson();
|
||||
return json.toUtf8().constData();
|
||||
;
|
||||
// this->helpers->onMapPicked()
|
||||
}
|
||||
} // namespace Aya
|
||||
28
client/player/src/AvatarViewService.hpp
Normal file
28
client/player/src/AvatarViewService.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "Tree/Instance.hpp"
|
||||
#include "Tree/Service.hpp"
|
||||
#include "LauncherView.hpp"
|
||||
#include "Utility/BrickColor.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
extern const char* const sAvatarViewService;
|
||||
|
||||
class AvatarViewService
|
||||
: public DescribedCreatable<AvatarViewService, Instance, sAvatarViewService, Reflection::ClassDescriptor::RUNTIME_LOCAL>
|
||||
, public Service
|
||||
{
|
||||
typedef DescribedCreatable<AvatarViewService, Instance, sAvatarViewService, Reflection::ClassDescriptor::RUNTIME_LOCAL> Super;
|
||||
|
||||
JsHelpers* helpers;
|
||||
public:
|
||||
AvatarViewService();
|
||||
virtual ~AvatarViewService();
|
||||
|
||||
void setJsHelpers(JsHelpers* helpers);
|
||||
Aya::signal<void(int64_t, std::string)> assetWornSignal;
|
||||
Aya::signal<void(std::string, BrickColor)> setBodyColorSignal;
|
||||
|
||||
void setColor(std::string json);
|
||||
std::string getColors();
|
||||
};
|
||||
} // namespace Aya
|
||||
250
client/player/src/Document.cpp
Normal file
250
client/player/src/Document.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
#include "DataModel/GameBasicSettings.hpp"
|
||||
|
||||
|
||||
#include "Document.hpp"
|
||||
#include "FunctionMarshaller.hpp"
|
||||
#include "GameVerbs.hpp"
|
||||
#include "Utility/AyaService.hpp"
|
||||
#include "Script/ScriptContext.hpp"
|
||||
#include "Utility/Statistics.hpp"
|
||||
|
||||
|
||||
#include "DataModel/ContentProvider.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "DataModel/DebugSettings.hpp"
|
||||
|
||||
#include "DataModel/Game.hpp"
|
||||
|
||||
#include "DataModel/GuiService.hpp"
|
||||
|
||||
#include "DataModel/HackDefines.hpp"
|
||||
|
||||
#include "DataModel/UserInputService.hpp"
|
||||
|
||||
#include "DataModel/UserController.hpp"
|
||||
|
||||
#include "API.hpp"
|
||||
|
||||
#include "InitializationError.hpp"
|
||||
#include "View.hpp"
|
||||
#include <string>
|
||||
|
||||
LOGGROUP(PlayerShutdownLuaTimeoutSeconds)
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
Document::Document()
|
||||
: marshaller(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
Document::~Document() {}
|
||||
|
||||
void Document::StartResult(HttpFuture& scriptResult, const LaunchMode launchMode, bool isTeleport)
|
||||
{
|
||||
printf("Document::startResult\n");
|
||||
|
||||
startedSignal(isTeleport);
|
||||
executeScript(scriptResult, launchMode);
|
||||
}
|
||||
|
||||
void Document::Start(std::string script, const LaunchMode launchMode, bool isTeleport)
|
||||
{
|
||||
printf("Document::start\n");
|
||||
|
||||
startedSignal(isTeleport);
|
||||
|
||||
{
|
||||
Security::Impersonator impersonate(Security::COM);
|
||||
if (script[0] == '{')
|
||||
game->configurePlayer(Security::COM, script, launchMode);
|
||||
else
|
||||
{
|
||||
if (shared_ptr<DataModel> dm = game->getDataModel())
|
||||
{
|
||||
ScriptContext* context = dm->create<ScriptContext>();
|
||||
ProtectedString ps = ProtectedString::fromTrustedSource(script);
|
||||
context->executeInNewThread(Security::COM, ps, "Start Game");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void setUiMessageImpl(shared_ptr<DataModel> dm, const std::string& message)
|
||||
{
|
||||
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "setUiMessageImpl %s", message.c_str());
|
||||
|
||||
if (message.length() > 0)
|
||||
{
|
||||
dm->setUiMessage(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
StandardOut::singleton()->printf(MESSAGE_SENSITIVE, "cleared setUiMessageImpl %s", message.c_str());
|
||||
dm->clearUiMessage();
|
||||
}
|
||||
|
||||
if (GuiService* gs = dm->create<GuiService>())
|
||||
gs->setUiMessage(GuiService::UIMESSAGE_INFO, message);
|
||||
}
|
||||
|
||||
void Document::SetUiMessage(const std::string& message)
|
||||
{
|
||||
if (shared_ptr<DataModel> dm = game->getDataModel())
|
||||
{
|
||||
dm->submitTask(boost::bind(setUiMessageImpl, dm, message), DataModelJob::Write);
|
||||
}
|
||||
}
|
||||
|
||||
void Document::PrepareShutdown()
|
||||
{
|
||||
// give scripts a deadline to finish
|
||||
if (game && game->getDataModel())
|
||||
if (FLog::PlayerShutdownLuaTimeoutSeconds > 0)
|
||||
if (ScriptContext* scriptContext = game->getDataModel()->find<ScriptContext>())
|
||||
scriptContext->setTimeout(FLog::PlayerShutdownLuaTimeoutSeconds);
|
||||
}
|
||||
|
||||
|
||||
void Document::Shutdown()
|
||||
{
|
||||
if (marshaller)
|
||||
FunctionMarshaller::ReleaseWindow(marshaller);
|
||||
|
||||
if (game)
|
||||
{
|
||||
game->shutdown();
|
||||
game.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Document::configureDataModelServices(bool useChat, Aya::DataModel* dataModel)
|
||||
{
|
||||
if (!dataModel)
|
||||
return;
|
||||
|
||||
DataModel::LegacyLock lock(dataModel, DataModelJob::Write);
|
||||
|
||||
|
||||
// Inform the UserInputService what kind of input we are providing (this may have to change if we use this with windows 8 touch devices)
|
||||
if (UserInputService* userInputService = dataModel->find<UserInputService>())
|
||||
{
|
||||
userInputService->setKeyboardEnabled(true);
|
||||
userInputService->setMouseEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Document::Initialize(
|
||||
OgreWidget* hWnd, bool useChat, bool shouldShowLoadingScreen, bool shouldShowCorescripts, GameBasicSettings::VirtualVersion vv)
|
||||
{
|
||||
marshaller = FunctionMarshaller::GetWindow();
|
||||
game.reset(new Aya::SecurePlayerGame(NULL, GetBaseURL().c_str(), shouldShowLoadingScreen, true, vv));
|
||||
|
||||
AyaService* ayaService = ServiceProvider::create<AyaService>(game->getDataModel().get());
|
||||
ayaService->shouldShowCorescripts = shouldShowCorescripts;
|
||||
|
||||
configureDataModelServices(useChat, game->getDataModel().get());
|
||||
|
||||
DataModel::LegacyLock lock(game->getDataModel().get(), DataModelJob::Write);
|
||||
|
||||
if (FLog::PlayerShutdownLuaTimeoutSeconds > 0)
|
||||
game->getDataModel()->create<ScriptContext>();
|
||||
|
||||
game->getDataModel().get()->gameLoadedSignal.connect(boost::bind(&Document::gameIsLoaded, this));
|
||||
}
|
||||
|
||||
void Document::sendInputObject(shared_ptr<InputObject> object)
|
||||
{
|
||||
if (Aya::UserInputService* userInputService = Aya::ServiceProvider::find<Aya::UserInputService>(game->getDataModel().get()))
|
||||
userInputService->fireInputEvent(object, NULL);
|
||||
}
|
||||
void Document::gameIsLoaded()
|
||||
{
|
||||
// MainLogManager::getMainLogManager()->setGameLoaded();
|
||||
}
|
||||
|
||||
// Executes the 'script' as part of the game initialization.
|
||||
void Document::executeScript(HttpFuture& scriptResult, const LaunchMode launchMode) const
|
||||
{
|
||||
shared_ptr<Aya::DataModel> dataModel = game->getDataModel();
|
||||
|
||||
Security::Impersonator impersonate(Security::COM);
|
||||
std::string data;
|
||||
|
||||
try
|
||||
{
|
||||
data = scriptResult.get();
|
||||
printf("%s\n", data.c_str());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::string err = Aya::format("Exception occured in Document::executeScript: %s", e.what());
|
||||
// LogManager::ReportEvent(EVENTLOG_ERROR_TYPE, err.c_str());
|
||||
|
||||
if (GuiService* gs = dataModel->create<GuiService>())
|
||||
gs->setUiMessage(GuiService::UIMESSAGE_INFO, "Unable to join game. Please try again later.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ProtectedString verifiedSource;
|
||||
try
|
||||
{
|
||||
verifiedSource = ProtectedString::fromTrustedSource(data);
|
||||
ContentProvider::verifyScriptSignature(verifiedSource, true);
|
||||
}
|
||||
catch (std::bad_alloc& e)
|
||||
{
|
||||
std::string err = Aya::format("Exception occured in Document::executeScript: %s", e.what());
|
||||
// LogManager::ReportEvent(EVENTLOG_ERROR_TYPE, err.c_str());
|
||||
throw;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
std::string err = Aya::format("Exception occured in Document::executeScript: %s", e.what());
|
||||
// LogManager::ReportEvent(EVENTLOG_ERROR_TYPE, err.c_str());
|
||||
// SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, AYA_PROJECT_NAME " error", err.c_str(), NULL);
|
||||
}
|
||||
|
||||
if (dataModel->isClosed())
|
||||
return;
|
||||
|
||||
int firstNewLineIndex = data.find("\r\n");
|
||||
if (data[firstNewLineIndex + 2] == '{')
|
||||
{
|
||||
game->configurePlayer(Security::COM, data.substr(firstNewLineIndex + 2), launchMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScriptContext* context = dataModel->create<ScriptContext>();
|
||||
context->executeInNewThread(Security::COM, verifiedSource, "Start Game");
|
||||
}
|
||||
}
|
||||
|
||||
void Document::executeScript(std::string& data) const
|
||||
{
|
||||
shared_ptr<Aya::DataModel> dataModel = game->getDataModel();
|
||||
Security::Impersonator impersonate(Security::COM);
|
||||
|
||||
if (dataModel->isClosed())
|
||||
return;
|
||||
|
||||
ProtectedString verifiedSource;
|
||||
verifiedSource = ProtectedString::fromTrustedSource("\r\n" + data);
|
||||
ContentProvider::verifyScriptSignature(verifiedSource, true);
|
||||
|
||||
ScriptContext* context = dataModel->create<ScriptContext>();
|
||||
context->executeInNewThread(Security::COM, verifiedSource, "Start Avatar View");
|
||||
}
|
||||
|
||||
FunctionMarshaller* Document::GetMarshaller() const
|
||||
{
|
||||
return marshaller;
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
71
client/player/src/Document.hpp
Normal file
71
client/player/src/Document.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include "DataModel/GameBasicSettings.hpp"
|
||||
#include "intrusive_ptr_target.hpp"
|
||||
|
||||
#include "signal.hpp"
|
||||
|
||||
#include "Utility/HttpAsync.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "Window.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
enum LaunchMode
|
||||
{
|
||||
Play,
|
||||
Play_Protocol,
|
||||
Build,
|
||||
Edit
|
||||
};
|
||||
|
||||
// Forward declarations
|
||||
class FunctionMarshaller;
|
||||
class Game;
|
||||
class View;
|
||||
class PlayerConfigurer;
|
||||
|
||||
// Class responsible for the game state
|
||||
class Document
|
||||
{
|
||||
public:
|
||||
Aya::signal<void(bool)> startedSignal;
|
||||
|
||||
Document();
|
||||
~Document();
|
||||
|
||||
void Initialize(OgreWidget* hWnd, bool useChat, bool shouldShowLoadingScreen = true, bool shouldShowCorescripts = true,
|
||||
GameBasicSettings::VirtualVersion vv = GameBasicSettings::VERSION_2016);
|
||||
void StartResult(HttpFuture& scriptResult, const LaunchMode launchMode, bool isTelport);
|
||||
void Start(std::string script, const LaunchMode launchMode, bool isTelport); // TODO: be able to parse lua scripts
|
||||
void executeScript(std::string& data) const;
|
||||
|
||||
void Shutdown();
|
||||
void SetUiMessage(const std::string& message);
|
||||
void PrepareShutdown(); // call before destroying the view
|
||||
void sendInputObject(shared_ptr<InputObject> obj);
|
||||
FunctionMarshaller* GetMarshaller() const;
|
||||
boost::shared_ptr<Game> getGame()
|
||||
{
|
||||
return game;
|
||||
}
|
||||
|
||||
private:
|
||||
FunctionMarshaller* marshaller;
|
||||
|
||||
// The game to run.
|
||||
boost::shared_ptr<Game> game;
|
||||
|
||||
// Executes the 'script' as part of the game initialization.
|
||||
void executeScript(HttpFuture& scriptResult, const LaunchMode launchMode) const;
|
||||
|
||||
void configureDataModelServices(bool useChat, Aya::DataModel* dataModel);
|
||||
|
||||
void dataModelDidRestart();
|
||||
void dataModelWillShutdown();
|
||||
void gameIsLoaded();
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
175
client/player/src/FunctionMarshaller.cpp
Normal file
175
client/player/src/FunctionMarshaller.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
// THIS FILE PURPOSE BUILT FOR SDL AND TRUCKS
|
||||
|
||||
#include "FunctionMarshaller.hpp"
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
#include "Utility/StandardOut.hpp"
|
||||
|
||||
#include "boost.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QWidget>
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
using namespace Aya;
|
||||
|
||||
const QEvent::Type Aya::TYPE_FUNCTION_MARSHALLER = static_cast<QEvent::Type>(QEvent::registerEventType());
|
||||
|
||||
FunctionMarshallerEvent::FunctionMarshallerEvent(void* closure)
|
||||
: QEvent(TYPE_FUNCTION_MARSHALLER)
|
||||
{
|
||||
this->closure = closure;
|
||||
}
|
||||
|
||||
FunctionMarshaller::FunctionMarshaller(DWORD threadID)
|
||||
: refCount(0)
|
||||
{
|
||||
this->threadID = threadID;
|
||||
}
|
||||
|
||||
FunctionMarshaller::~FunctionMarshaller()
|
||||
{
|
||||
boost::function<void()>* f;
|
||||
while (asyncCalls.pop_if_present(f))
|
||||
delete f;
|
||||
|
||||
AYAASSERT(threadID == GetCurrentThreadId());
|
||||
|
||||
#ifdef _DEBUG
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
|
||||
AYAASSERT(refCount == 0);
|
||||
// Nobody is using this window
|
||||
AYAASSERT(staticData().windows.find(threadID) == staticData().windows.end());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
FunctionMarshaller* FunctionMarshaller::GetWindow()
|
||||
{
|
||||
// Share a common FunctionMarshaller in a given Thread
|
||||
|
||||
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
|
||||
DWORD threadID = GetCurrentThreadId();
|
||||
std::map<DWORD, FunctionMarshaller*>::iterator find = staticData().windows.find(threadID);
|
||||
if (find != staticData().windows.end())
|
||||
{
|
||||
// We already created a window, so use it again
|
||||
find->second->refCount++;
|
||||
return find->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new window
|
||||
FunctionMarshaller* window = new FunctionMarshaller(threadID);
|
||||
staticData().windows[threadID] = window;
|
||||
window->refCount++;
|
||||
return window;
|
||||
}
|
||||
}
|
||||
|
||||
void FunctionMarshaller::ReleaseWindow(FunctionMarshaller* window)
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
|
||||
window->refCount--;
|
||||
if (window->refCount == 0)
|
||||
{
|
||||
// Nobody is using this window
|
||||
staticData().windows.erase(window->threadID);
|
||||
// window->DestroyWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void FunctionMarshaller::handleAppEvent(void* pClosure)
|
||||
{
|
||||
FunctionMarshaller::Closure* closure = (FunctionMarshaller::Closure*)pClosure;
|
||||
Aya::CEvent* pWaitEvent = closure->waitEvent;
|
||||
try
|
||||
{
|
||||
boost::function<void()>* pF = closure->f;
|
||||
(*pF)();
|
||||
|
||||
delete pF;
|
||||
delete closure;
|
||||
}
|
||||
catch (Aya::base_exception& e)
|
||||
{
|
||||
StandardOut::singleton()->print(Aya::MESSAGE_ERROR, e);
|
||||
closure->errorMessage = e.what();
|
||||
}
|
||||
|
||||
|
||||
// If a task is waiting on an event, set it
|
||||
if (pWaitEvent)
|
||||
{
|
||||
pWaitEvent->Set();
|
||||
}
|
||||
}
|
||||
|
||||
void FunctionMarshaller::freeAppEvent(void* pClosure)
|
||||
{
|
||||
FunctionMarshaller::Closure* closure = (FunctionMarshaller::Closure*)pClosure;
|
||||
boost::function<void()>* pF = closure->f;
|
||||
delete pF;
|
||||
delete closure;
|
||||
}
|
||||
|
||||
extern QApplication* qtAppPtr;
|
||||
|
||||
void FunctionMarshaller::Execute(boost::function<void()> job, CEvent* waitEvent)
|
||||
{
|
||||
if (threadID == GetCurrentThreadId())
|
||||
{
|
||||
job();
|
||||
}
|
||||
else
|
||||
{
|
||||
Closure* pClosure = new Closure;
|
||||
pClosure->f = new boost::function<void()>(job);
|
||||
pClosure->waitEvent = waitEvent;
|
||||
|
||||
/*SDL_Event event;
|
||||
event.type = SDL_EVENT_USER;
|
||||
event.user.code = 0xc10554e;
|
||||
event.user.data1 = pClosure;
|
||||
SDL_PushEvent(&event);*/
|
||||
|
||||
const QWidgetList topLevelWidgets = QApplication::topLevelWidgets();
|
||||
qtAppPtr->postEvent(topLevelWidgets.at(0), new FunctionMarshallerEvent(pClosure));
|
||||
}
|
||||
}
|
||||
|
||||
void FunctionMarshaller::Submit(boost::function<void()> job)
|
||||
{
|
||||
Closure* pClosure = new Closure;
|
||||
pClosure->f = new boost::function<void()>(job);
|
||||
pClosure->waitEvent = nullptr;
|
||||
|
||||
/*SDL_Event event;
|
||||
event.type = SDL_EVENT_USER;
|
||||
event.user.code = 0xc10554e;
|
||||
event.user.data1 = pClosure;
|
||||
SDL_PushEvent(&event);*/
|
||||
|
||||
const QWidgetList topLevelWidgets = QApplication::topLevelWidgets();
|
||||
qtAppPtr->postEvent(topLevelWidgets.at(0), new FunctionMarshallerEvent(pClosure));
|
||||
}
|
||||
|
||||
void FunctionMarshaller::ProcessMessages() {}
|
||||
|
||||
/*
|
||||
void FunctionMarshaller::OnFinalMessage(HWND hWnd)
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
*/
|
||||
|
||||
FunctionMarshaller::StaticData::~StaticData()
|
||||
{
|
||||
// for (std::map<DWORD, FunctionMarshaller*>::iterator iter = windows.begin(); iter != windows.end(); ++iter)
|
||||
// iter->second->DestroyWindow();
|
||||
}
|
||||
78
client/player/src/FunctionMarshaller.hpp
Normal file
78
client/player/src/FunctionMarshaller.hpp
Normal file
@@ -0,0 +1,78 @@
|
||||
// This file is used for two targets:
|
||||
// 1. Used in Mac Roblox Player app, this is the place where actual file resides
|
||||
// 2. Used in Qt version of Roblox Studio as a soft link from above
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include "threadsafe.hpp"
|
||||
|
||||
#include "CEvent.hpp"
|
||||
#include <QEvent>
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
extern const QEvent::Type TYPE_FUNCTION_MARSHALLER;
|
||||
class FunctionMarshallerEvent : public QEvent
|
||||
{
|
||||
void* closure;
|
||||
|
||||
public:
|
||||
explicit FunctionMarshallerEvent(void* closure);
|
||||
void* getClosure()
|
||||
{
|
||||
return closure;
|
||||
}
|
||||
};
|
||||
|
||||
// A very handy class for marshalling a function across Windows threads (sync and async)
|
||||
class FunctionMarshaller
|
||||
{
|
||||
public:
|
||||
private:
|
||||
struct StaticData
|
||||
{
|
||||
std::map<DWORD, FunctionMarshaller*> windows;
|
||||
boost::recursive_mutex windowsCriticalSection; // TODO: Would non-recursive be safe here?
|
||||
~StaticData();
|
||||
};
|
||||
SAFE_STATIC(StaticData, staticData)
|
||||
|
||||
Aya::safe_queue<boost::function<void()>*> asyncCalls;
|
||||
int refCount;
|
||||
DWORD threadID;
|
||||
FunctionMarshaller(DWORD threadID);
|
||||
|
||||
|
||||
~FunctionMarshaller();
|
||||
|
||||
public:
|
||||
// TODO: Wrap with a reference counter and then remove ~StaticData() cleanup code and remove ReleaseWindow()
|
||||
static FunctionMarshaller* GetWindow();
|
||||
static void ReleaseWindow(FunctionMarshaller* window);
|
||||
|
||||
static void handleAppEvent(void* pClosure);
|
||||
static void freeAppEvent(void* pClosure);
|
||||
|
||||
struct Closure
|
||||
{
|
||||
boost::function<void()>* f;
|
||||
std::string errorMessage;
|
||||
Aya::CEvent* waitEvent;
|
||||
};
|
||||
|
||||
void Execute(boost::function<void()> job, CEvent* waitEvent);
|
||||
void Submit(boost::function<void()> job);
|
||||
|
||||
// Call this only from the Window's thread
|
||||
void ProcessMessages();
|
||||
|
||||
// virtual void OnFinalMessage(HWND hWnd);
|
||||
|
||||
private:
|
||||
// LRESULT OnEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
|
||||
// LRESULT OnAsyncEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
|
||||
};
|
||||
|
||||
|
||||
} // namespace Aya
|
||||
72
client/player/src/GameVerbs.cpp
Normal file
72
client/player/src/GameVerbs.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "intrusive_ptr_target.hpp"
|
||||
|
||||
#include "GameVerbs.hpp"
|
||||
|
||||
#include "Document.hpp"
|
||||
#include "FunctionMarshaller.hpp"
|
||||
#include "DataModel/RenderSettingsItem.hpp"
|
||||
#include "Utility/FileSystem.hpp"
|
||||
|
||||
#include "Utility/Http.hpp"
|
||||
|
||||
#include "Utility/Statistics.hpp"
|
||||
|
||||
#include "DataModel/Game.hpp"
|
||||
|
||||
#include "DataModel/GameBasicSettings.hpp"
|
||||
|
||||
#include "View.hpp"
|
||||
#include "Application.hpp"
|
||||
|
||||
extern OgreWindow* qtOgreWidget;
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
LeaveGameVerb::LeaveGameVerb(View& view, VerbContainer* container)
|
||||
: Verb(container, "Exit")
|
||||
, v(view)
|
||||
{
|
||||
}
|
||||
|
||||
void LeaveGameVerb::doIt(IDataState* dataState)
|
||||
{
|
||||
// MainLogManager::getMainLogManager()->setLeaveGame();
|
||||
FunctionMarshaller::GetWindow()->Submit(
|
||||
[this]
|
||||
{
|
||||
v.CloseWindow();
|
||||
|
||||
OgreWindow* _ogreWindow = dynamic_cast<OgreWindow*>(qtOgreWidget);
|
||||
if (!_ogreWindow)
|
||||
return;
|
||||
|
||||
GrayChatBar* textInput = dynamic_cast<OgreWindow*>(qtOgreWidget)->getTextInput();
|
||||
textInput->setVisible(false);
|
||||
});
|
||||
}
|
||||
|
||||
ToggleFullscreenVerb::ToggleFullscreenVerb(View& view, VerbContainer* container)
|
||||
: Verb(container, "ToggleFullScreen")
|
||||
, view(view)
|
||||
{
|
||||
}
|
||||
|
||||
bool ToggleFullscreenVerb::isChecked() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ToggleFullscreenVerb::isEnabled() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void ToggleFullscreenVerb::doIt(Aya::IDataState* dataState)
|
||||
{
|
||||
FASTLOG(FLog::Verbs, "Gui:ToggleFullscreen");
|
||||
|
||||
// view.SetFullscreen(!view.IsFullscreen());
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
43
client/player/src/GameVerbs.hpp
Normal file
43
client/player/src/GameVerbs.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "Tree/Verb.hpp"
|
||||
|
||||
#include "CEvent.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class Game;
|
||||
class View;
|
||||
class ViewBase;
|
||||
class Document;
|
||||
class VideoControl;
|
||||
class WebBrowserDialog;
|
||||
class Application;
|
||||
|
||||
// Request to leave the game. Results in process shutdown.
|
||||
class LeaveGameVerb : public Verb
|
||||
{
|
||||
View& v;
|
||||
|
||||
public:
|
||||
LeaveGameVerb(View& v, VerbContainer* container);
|
||||
virtual ~LeaveGameVerb() {}
|
||||
virtual void doIt(IDataState* dataState);
|
||||
};
|
||||
|
||||
// Request to toggle fullscreen
|
||||
class ToggleFullscreenVerb : public Aya::Verb
|
||||
{
|
||||
private:
|
||||
View& view;
|
||||
|
||||
public:
|
||||
ToggleFullscreenVerb(View& view, VerbContainer* container);
|
||||
virtual bool isChecked() const;
|
||||
virtual bool isEnabled() const;
|
||||
virtual void doIt(Aya::IDataState* dataState);
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
17
client/player/src/InitializationError.hpp
Normal file
17
client/player/src/InitializationError.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class initialization_error : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
initialization_error(const char* const errorMessage)
|
||||
: std::runtime_error(errorMessage)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
656
client/player/src/LauncherView.cpp
Normal file
656
client/player/src/LauncherView.cpp
Normal file
@@ -0,0 +1,656 @@
|
||||
#include "LauncherView.hpp"
|
||||
#include "Utility/BrickColor.hpp"
|
||||
#include "AvatarViewService.hpp"
|
||||
#include "Tree/Service.hpp"
|
||||
#include "Application.hpp"
|
||||
#include "Window.hpp"
|
||||
#include "DataModel/ContentProvider.hpp"
|
||||
#include "Utility/Statistics.hpp"
|
||||
#include <QWebEngineScript>
|
||||
#include <QWebEngineScriptCollection>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QDialog>
|
||||
#include <QVBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QProgressDialog>
|
||||
#include <QFrame>
|
||||
#include <QWebEngineSettings>
|
||||
#include <QSettings>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QTabWidget>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QApplication>
|
||||
#include <QNetworkRequest>
|
||||
#include <QCheckBox>
|
||||
#include <qnetworkrequest.h>
|
||||
#include <QString>
|
||||
#include <qsettings.h>
|
||||
#include "winrc.h"
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
LauncherView::LauncherView(QWidget* parent)
|
||||
: QWebEngineView(parent)
|
||||
{
|
||||
channel = new QWebChannel(page());
|
||||
page()->setWebChannel(channel);
|
||||
setContextMenuPolicy(Qt::NoContextMenu);
|
||||
jsHelpers = new JsHelpers();
|
||||
channel->registerObject(QStringLiteral("transport"), jsHelpers);
|
||||
|
||||
QFile apiFile(":/qtwebchannel/qwebchannel.js"); // load the API from the resources
|
||||
if (!apiFile.open(QIODevice::ReadOnly))
|
||||
qDebug() << "Couldn't load Qt's QWebChannel API!";
|
||||
QString apiScript = QString::fromLatin1(apiFile.readAll());
|
||||
apiFile.close();
|
||||
|
||||
QWebEngineScript script;
|
||||
script.setSourceCode(apiScript);
|
||||
script.setName("qtwebchannel.js");
|
||||
script.setWorldId(QWebEngineScript::MainWorld);
|
||||
script.setInjectionPoint(QWebEngineScript::DocumentCreation);
|
||||
script.setRunsOnSubFrames(false);
|
||||
page()->scripts().insert(script);
|
||||
|
||||
std::string assetFolderPath = Aya::ContentProvider::assetFolder();
|
||||
QString qAssetFolderPath = QString::fromStdString(assetFolderPath);
|
||||
#ifdef AYA_TEST_BUILD
|
||||
QUrl url = QUrl("http://localhost:5173");
|
||||
#else
|
||||
QUrl url = QUrl::fromLocalFile(qAssetFolderPath + "/app/index.html");
|
||||
#endif
|
||||
|
||||
QWebEngineSettings* settings = page()->settings();
|
||||
|
||||
settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
|
||||
settings->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, true);
|
||||
settings->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);
|
||||
settings->setAttribute(QWebEngineSettings::JavascriptEnabled, true);
|
||||
settings->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true);
|
||||
settings->setAttribute(QWebEngineSettings::Accelerated2dCanvasEnabled, true);
|
||||
|
||||
#ifdef AYA_TEST_BUILD
|
||||
// Enable DevTools
|
||||
settings->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, true);
|
||||
|
||||
#endif
|
||||
|
||||
page()->load(url);
|
||||
page()->setBackgroundColor(Qt::transparent); // to see avatar when customizing
|
||||
}
|
||||
|
||||
JsHelpers::JsHelpers(QWidget* parentWidget)
|
||||
{
|
||||
reloadSettings();
|
||||
|
||||
netManager = new QNetworkAccessManager();
|
||||
this->parentWidget = parentWidget;
|
||||
|
||||
QSettings settings;
|
||||
selectedFilePath = settings.value("JsHelpers/selectedFilePath").toString().toStdString();
|
||||
}
|
||||
|
||||
void JsHelpers::reloadSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
masterServerAuthorization = settings.value("JsHelpers/masterServerAuthorization", "").toString().toStdString();
|
||||
masterServerUrl = settings.value("JsHelpers/masterServerUrl").toString().toStdString();
|
||||
robloSecurityCookie = settings.value("JsHelpers/robloSecurityCookie").toString().toStdString();
|
||||
currentServerAuthorization = "";
|
||||
}
|
||||
|
||||
extern QApplication* qtAppPtr;
|
||||
|
||||
int JsHelpers::getCompileTime()
|
||||
{
|
||||
return AYA_BUILD_TIMESTAMP;
|
||||
}
|
||||
|
||||
QString JsHelpers::getPlatformName()
|
||||
{
|
||||
return AYA_PLATFORM_NAME;
|
||||
}
|
||||
|
||||
QString JsHelpers::getCompilerName()
|
||||
{
|
||||
return AYA_COMPILER_ID;
|
||||
}
|
||||
|
||||
QString JsHelpers::getVersion()
|
||||
{
|
||||
return VERSION_FULL_STR;
|
||||
}
|
||||
|
||||
bool JsHelpers::isUsingInstance()
|
||||
{
|
||||
return IsUsingInstance();
|
||||
}
|
||||
|
||||
QString JsHelpers::getInstanceURL()
|
||||
{
|
||||
return QString::fromStdString(GetBaseURL());
|
||||
}
|
||||
|
||||
QString JsHelpers::getInstanceName()
|
||||
{
|
||||
return QString::fromStdString(GetInstanceName());
|
||||
}
|
||||
|
||||
QString JsHelpers::getInstanceMotd()
|
||||
{
|
||||
return QString::fromStdString(GetInstanceMotd());
|
||||
}
|
||||
|
||||
|
||||
QString JsHelpers::getMasterServerURL()
|
||||
{
|
||||
if (masterServerUrl == "")
|
||||
{
|
||||
QMessageBox::critical(parentWidget, "Request Error", "There is no master server set. Use Advanced Settings to select one.");
|
||||
return QString();
|
||||
}
|
||||
|
||||
return QString::fromStdString(masterServerUrl);
|
||||
}
|
||||
|
||||
void JsHelpers::setSetting(QString setting, QString value)
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup("JsHelpers");
|
||||
settings.setValue(setting, value);
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
void JsHelpers::setMasterServerURL(QString url)
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue("JsHelpers/masterServerUrl", url);
|
||||
}
|
||||
|
||||
void JsHelpers::setRobloSecurityCookie(QString cookie)
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue("JsHelpers/robloSecurityCookie", cookie);
|
||||
}
|
||||
|
||||
QString JsHelpers::getRobloSecurityCookie()
|
||||
{
|
||||
if (robloSecurityCookie == "")
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
return QString::fromStdString(robloSecurityCookie);
|
||||
}
|
||||
|
||||
QString JsHelpers::grabServers()
|
||||
{
|
||||
if (masterServerUrl == "")
|
||||
{
|
||||
QMessageBox::critical(parentWidget, "Request Error", "There is no master server set. Use Advanced Settings to select one.");
|
||||
return QString();
|
||||
}
|
||||
|
||||
QUrl url = QUrl::fromUserInput(QString::fromStdString(masterServerUrl) + "/ping");
|
||||
printf("%s\n", url.toString().toStdString().c_str());
|
||||
|
||||
if (!url.isValid())
|
||||
{
|
||||
QMessageBox::critical(parentWidget, "Request Error", "The URL for the master server is invalid.");
|
||||
return QString();
|
||||
}
|
||||
|
||||
QNetworkRequest req = QNetworkRequest();
|
||||
if (masterServerAuthorization != "")
|
||||
{
|
||||
req.setRawHeader("Authorization", QByteArray(masterServerAuthorization.c_str(), masterServerAuthorization.length()));
|
||||
}
|
||||
req.setUrl(url);
|
||||
|
||||
QNetworkReply* reply = netManager->get(req);
|
||||
|
||||
QNetworkReply::NetworkError error; // = reply->error();
|
||||
|
||||
QProgressDialog progress("Grabbing servers...", "Cancel", 0, 1, parentWidget);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
|
||||
while (!reply->isFinished())
|
||||
{
|
||||
progress.setValue(0);
|
||||
progress.update();
|
||||
|
||||
qtAppPtr->processEvents();
|
||||
}
|
||||
|
||||
progress.close();
|
||||
|
||||
error = reply->error();
|
||||
if (error)
|
||||
{
|
||||
printf("Error %i '%s'\n", error, reply->errorString().toStdString().c_str());
|
||||
QMessageBox::critical(parentWidget, "Request Error", "Aya could not reach the selected master server.");
|
||||
}
|
||||
QByteArray responseB = reply->readAll();
|
||||
QString response(responseB);
|
||||
printf("%s\n", response.toStdString().c_str());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
QString JsHelpers::userName()
|
||||
{
|
||||
QSettings settings;
|
||||
QString username = settings.value("JsHelpers/Username").toString();
|
||||
if (username.isEmpty())
|
||||
{
|
||||
QString sysUsername = qEnvironmentVariable("USER");
|
||||
if (sysUsername.isEmpty())
|
||||
sysUsername = qEnvironmentVariable("USERNAME");
|
||||
settings.setValue("JsHelpers/Username", sysUsername);
|
||||
return sysUsername;
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
QString JsHelpers::characterAppearanceJSON()
|
||||
{
|
||||
QSettings settings;
|
||||
QString json = settings.value("JsHelpers/CharApp").toString();
|
||||
if (json.isEmpty())
|
||||
{
|
||||
settings.setValue("JsHelpers/CharApp", "[]");
|
||||
return QString("[]");
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
void JsHelpers::setBodyColorJson(QString json)
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue("JsHelpers/BodyColors", json);
|
||||
}
|
||||
|
||||
QString JsHelpers::getBodyColorJson()
|
||||
{
|
||||
QSettings settings;
|
||||
return settings.value("JsHelpers/BodyColors").toString();
|
||||
}
|
||||
|
||||
void JsHelpers::setCharacterAppearanceJSON(QString json)
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue("JsHelpers/CharApp", json);
|
||||
}
|
||||
|
||||
void JsHelpers::setUserName(QString username)
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue("JsHelpers/Username", username);
|
||||
}
|
||||
|
||||
extern OgreWindow* qtOgreWidget;
|
||||
|
||||
void JsHelpers::enableChatBarWidget()
|
||||
{
|
||||
OgreWindow* _ogreWindow = dynamic_cast<OgreWindow*>(qtOgreWidget);
|
||||
if (!_ogreWindow)
|
||||
return;
|
||||
|
||||
GrayChatBar* textInput = dynamic_cast<OgreWindow*>(qtOgreWidget)->getTextInput();
|
||||
textInput->setVisible(true);
|
||||
}
|
||||
|
||||
void JsHelpers::launchStudio()
|
||||
{
|
||||
OgreWindow* _ogreWindow = dynamic_cast<OgreWindow*>(qtOgreWidget);
|
||||
if (!_ogreWindow)
|
||||
return;
|
||||
|
||||
QString studioPath = "Aya.Studio";
|
||||
#ifdef _WIN32
|
||||
studioPath += ".exe";
|
||||
#endif
|
||||
if (QFile::exists(studioPath))
|
||||
{
|
||||
QProcess::startDetached(studioPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(parentWidget, "Cannot launch Studio", "Aya Studio was not found in the current directory");
|
||||
}
|
||||
}
|
||||
|
||||
void JsHelpers::openMapPickerDialog()
|
||||
{
|
||||
QString filename = QFileDialog::getOpenFileName(nullptr, tr("Open Place File"), QString(), tr("Level (*.rbxl)"));
|
||||
selectedFilePath = filename.toStdString();
|
||||
QSettings settings;
|
||||
settings.setValue("JsHelpers/selectedFilePath", filename);
|
||||
Q_EMIT onMapPicked(filename);
|
||||
}
|
||||
|
||||
class MotdDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QWebEngineView* webView;
|
||||
QPushButton* okButton;
|
||||
|
||||
void okButtonPressed()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
public:
|
||||
MotdDialog(QString motd, QWidget* parent = NULL)
|
||||
: QDialog(parent)
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
setMinimumSize(0, 0);
|
||||
|
||||
QSettings qsettings;
|
||||
restoreGeometry(qsettings.value("Player/MotdGeometry").toByteArray());
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
webView = new QWebEngineView();
|
||||
webView->setHtml(motd);
|
||||
webView->setMinimumSize(300, 200);
|
||||
QWebEngineSettings* settings = webView->page()->settings();
|
||||
|
||||
settings->setAttribute(QWebEngineSettings::JavascriptEnabled, false);
|
||||
settings->setAttribute(QWebEngineSettings::JavascriptCanOpenWindows, false);
|
||||
settings->setAttribute(QWebEngineSettings::JavascriptCanAccessClipboard, false);
|
||||
settings->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);
|
||||
settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, false);
|
||||
settings->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, false);
|
||||
settings->setAttribute(QWebEngineSettings::WebGLEnabled, false);
|
||||
settings->setAttribute(QWebEngineSettings::Accelerated2dCanvasEnabled, false);
|
||||
settings->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, false);
|
||||
settings->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, false);
|
||||
settings->setAttribute(QWebEngineSettings::JavascriptCanPaste, false);
|
||||
settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, false);
|
||||
layout->addWidget(webView);
|
||||
|
||||
okButton = new QPushButton();
|
||||
okButton->setText("OK");
|
||||
okButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
layout->addWidget(okButton);
|
||||
|
||||
setWindowTitle("Message of the day");
|
||||
|
||||
connect(okButton, &QPushButton::pressed, this, &MotdDialog::okButtonPressed);
|
||||
}
|
||||
|
||||
~MotdDialog()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue("Player/MotdGeometry", saveGeometry());
|
||||
}
|
||||
};
|
||||
|
||||
void JsHelpers::presentMotd(QString motd)
|
||||
{
|
||||
MotdDialog* d = new MotdDialog(motd, parentWidget);
|
||||
d->show();
|
||||
}
|
||||
|
||||
bool JsHelpers::readPassword(const QString& type, bool read)
|
||||
{
|
||||
const QString MASTER_SERVER = "masterServer";
|
||||
const QString CURRENT_SERVER = "serverPassword";
|
||||
|
||||
if (read)
|
||||
{
|
||||
bool ok;
|
||||
QString key = QInputDialog::getText(parentWidget, tr("Enter key"), tr("Enter key"), QLineEdit::Password, QString(), &ok);
|
||||
if (ok)
|
||||
{
|
||||
if (type == MASTER_SERVER)
|
||||
{
|
||||
QSettings settings;
|
||||
masterServerAuthorization = key.toStdString();
|
||||
settings.setValue("JsHelpers/masterServerAuthorization", key);
|
||||
}
|
||||
else if (type == CURRENT_SERVER)
|
||||
{
|
||||
currentServerAuthorization = key.toStdString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type == CURRENT_SERVER)
|
||||
{
|
||||
currentServerAuthorization = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type == MASTER_SERVER)
|
||||
{
|
||||
return !masterServerAuthorization.empty();
|
||||
}
|
||||
else if (type == CURRENT_SERVER)
|
||||
{
|
||||
return !masterServerAuthorization.empty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool JsHelpers::hasServer()
|
||||
{
|
||||
QString fname;
|
||||
#ifdef _WIN32
|
||||
fname = "Aya.Server.exe";
|
||||
#else
|
||||
fname = "Aya.Server";
|
||||
#endif
|
||||
return (QFile::exists(fname));
|
||||
}
|
||||
|
||||
extern Aya::Application* appPtr;
|
||||
|
||||
void JsHelpers::wearAsset(int64_t assetId, QString category)
|
||||
{
|
||||
// TODO: at a later date
|
||||
/*QUrl url = QUrl::fromUserInput(QString::fromStdString(masterServerUrl) + "/wear");
|
||||
printf("%s\n", url.toString().toStdString().c_str());
|
||||
|
||||
if (!url.isValid())
|
||||
{
|
||||
QMessageBox::critical(parentWidget, "Request Error", "The URL for the master server is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
QUrlQuery postData;
|
||||
postData.addQueryItem("assetId", assetId);
|
||||
QNetworkRequest req = QNetworkRequest();
|
||||
if (masterServerAuthorization != "")
|
||||
{
|
||||
req.setRawHeader("Authorization", QByteArray(masterServerAuthorization.c_str(), masterServerAuthorization.length()));
|
||||
}
|
||||
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
req.setUrl(url);
|
||||
|
||||
QNetworkReply* reply = netManager->post(req, postData.toString(QUrl::FullyEncoded).toUtf8());
|
||||
connect(reply, &QNetworkReply::finished, this, [=](){
|
||||
if(reply->error()) {
|
||||
QMessageBox::critical(parentWidget, "Request Error", reply->errorString());
|
||||
}
|
||||
}); */
|
||||
|
||||
|
||||
Aya::AvatarViewService* avatarViewService = Aya::ServiceProvider::create<Aya::AvatarViewService>(appPtr->getDM().get());
|
||||
avatarViewService->assetWornSignal(assetId, category.toStdString());
|
||||
}
|
||||
|
||||
void JsHelpers::setBodyColor(QString bodyPart, int bodyColor)
|
||||
{
|
||||
Aya::AvatarViewService* avatarViewService = Aya::ServiceProvider::create<Aya::AvatarViewService>(appPtr->getDM().get());
|
||||
avatarViewService->setBodyColorSignal(bodyPart.toStdString(), Aya::BrickColor(bodyColor));
|
||||
}
|
||||
|
||||
class SettingsDialog : public QDialog
|
||||
{
|
||||
struct SettingsEntry
|
||||
{
|
||||
QString name; // visual
|
||||
QString property; //
|
||||
QString type;
|
||||
int tab;
|
||||
QWidget* widget;
|
||||
|
||||
QString filePathValue; // path value, used only in `file' mode
|
||||
};
|
||||
|
||||
std::vector<SettingsEntry> entries;
|
||||
|
||||
void addEntry(QString name, QString property, QString type, int tab = 0)
|
||||
{
|
||||
SettingsEntry e;
|
||||
e.name = name;
|
||||
e.property = property;
|
||||
e.type = type;
|
||||
e.tab = tab;
|
||||
entries.push_back(e);
|
||||
};
|
||||
|
||||
QTabWidget* tabWidget;
|
||||
QDialogButtonBox* buttonBox;
|
||||
|
||||
void accept()
|
||||
{
|
||||
QSettings settings;
|
||||
for (auto& e : entries)
|
||||
{
|
||||
if (e.type == "password" || e.type == "text")
|
||||
{
|
||||
settings.setValue(e.property, ((QLineEdit*)e.widget)->text());
|
||||
}
|
||||
else if (e.type == "bool")
|
||||
{
|
||||
Qt::CheckState cs = ((QCheckBox*)e.widget)->checkState();
|
||||
settings.setValue(e.property, cs == Qt::Checked);
|
||||
}
|
||||
else if (e.type == "file")
|
||||
{
|
||||
settings.setValue(e.property, e.filePathValue);
|
||||
}
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void openButton() {}
|
||||
|
||||
public:
|
||||
SettingsDialog(QWidget* parent = NULL)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
tabWidget = new QTabWidget;
|
||||
|
||||
QWidget* generalTab = new QWidget();
|
||||
QWidget* serverTab = new QWidget();
|
||||
QWidget* creditsTab = new QWidget();
|
||||
tabWidget->addTab(generalTab, tr("General"));
|
||||
tabWidget->addTab(serverTab, tr("Server"));
|
||||
tabWidget->addTab(creditsTab, tr("Credits"));
|
||||
|
||||
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::accept);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(tabWidget);
|
||||
mainLayout->addWidget(buttonBox);
|
||||
setLayout(mainLayout);
|
||||
|
||||
QVBoxLayout* generalLayout = new QVBoxLayout;
|
||||
QVBoxLayout* serverLayout = new QVBoxLayout;
|
||||
|
||||
std::vector<QVBoxLayout*> tgtLayouts;
|
||||
tgtLayouts.push_back(generalLayout);
|
||||
tgtLayouts.push_back(serverLayout);
|
||||
|
||||
generalLayout->addStretch(1);
|
||||
generalLayout->setAlignment(Qt::AlignTop);
|
||||
generalTab->setLayout(generalLayout);
|
||||
serverLayout->addStretch(1);
|
||||
serverLayout->setAlignment(Qt::AlignTop);
|
||||
serverTab->setLayout(serverLayout);
|
||||
|
||||
// HOW TO ADD NEW TABS:
|
||||
// create a QWidget for your tab
|
||||
// create a box layout for the QWidget
|
||||
// add it to tgtLayouts, then use the tab parameter of addEntry to set which settings get put where
|
||||
|
||||
addEntry("Master Server URL", "JsHelpers/masterServerUrl", "text");
|
||||
addEntry("Master Server Key", "JsHelpers/masterServerAuthorization", "password");
|
||||
addEntry("Host Server Password", "JsHelpers/serverHostPassword", "password");
|
||||
|
||||
addEntry("Server MOTD Preview Use Place File", "JsHelpers/serverMotdPreviewUsePlaceFile", "bool", 1);
|
||||
addEntry("Server MOTD Preview", "JsHelpers/serverMotdPreview", "text", 1);
|
||||
addEntry("Server MOTD File (*.html, *.txt)", "JsHelpers/serverMotdFile", "file", 1);
|
||||
|
||||
QSettings settings;
|
||||
for (auto& e : entries)
|
||||
{
|
||||
QLabel* label = new QLabel(e.name);
|
||||
QVariant val = settings.value(e.property);
|
||||
tgtLayouts[e.tab]->addWidget(label);
|
||||
e.widget = NULL;
|
||||
if (e.type == "password" || e.type == "text")
|
||||
{
|
||||
QLineEdit* ed = new QLineEdit(val.toString());
|
||||
if (e.type == "password")
|
||||
ed->setEchoMode(QLineEdit::Password);
|
||||
e.widget = ed;
|
||||
}
|
||||
else if (e.type == "bool")
|
||||
{
|
||||
QCheckBox* cb = new QCheckBox();
|
||||
cb->setCheckState(val.toBool() ? Qt::Checked : Qt::Unchecked);
|
||||
e.widget = cb;
|
||||
}
|
||||
else if (e.type == "file")
|
||||
{
|
||||
QPushButton* pb = new QPushButton();
|
||||
pb->setText("Select File");
|
||||
QObject::connect(pb, &QPushButton::clicked,
|
||||
[this, &e](bool checked)
|
||||
{
|
||||
e.filePathValue = QFileDialog::getOpenFileName(this, tr("Select File"), QString(), e.name);
|
||||
});
|
||||
e.widget = pb;
|
||||
} // TODO: file picker
|
||||
if (e.widget)
|
||||
tgtLayouts[e.tab]->addWidget(e.widget);
|
||||
}
|
||||
|
||||
QFile creditsFile(":/credits.txt"); // load the credits from the resources
|
||||
if (!creditsFile.open(QIODevice::ReadOnly))
|
||||
qDebug() << "Couldn't load Credits!";
|
||||
QString credits = QString::fromLatin1(creditsFile.readAll());
|
||||
creditsFile.close();
|
||||
|
||||
QLabel* tbdLabel = new QLabel(credits);
|
||||
tbdLabel->setWordWrap(true);
|
||||
QVBoxLayout* creditsLayout = new QVBoxLayout;
|
||||
creditsLayout->setAlignment(Qt::AlignTop);
|
||||
creditsLayout->addWidget(tbdLabel);
|
||||
creditsTab->setLayout(creditsLayout);
|
||||
}
|
||||
};
|
||||
|
||||
void JsHelpers::openSettings()
|
||||
{
|
||||
SettingsDialog* d = new SettingsDialog();
|
||||
d->exec();
|
||||
reloadSettings();
|
||||
}
|
||||
|
||||
#include "LauncherView.moc"
|
||||
123
client/player/src/LauncherView.hpp
Normal file
123
client/player/src/LauncherView.hpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWebEngineView>
|
||||
#include <QtWebChannel>
|
||||
#include <QObject>
|
||||
#include <qwebengineview.h>
|
||||
#include <thread>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
class JsHelpers : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QWidget* parentWidget;
|
||||
QNetworkAccessManager* netManager;
|
||||
|
||||
std::string masterServerAuthorization;
|
||||
std::string currentServerAuthorization;
|
||||
std::string masterServerUrl;
|
||||
std::string selectedFilePath;
|
||||
std::string robloSecurityCookie;
|
||||
|
||||
void reloadSettings();
|
||||
|
||||
public:
|
||||
std::string getCurrentServerAuthorization()
|
||||
{
|
||||
return currentServerAuthorization;
|
||||
};
|
||||
std::string getFilePath()
|
||||
{
|
||||
return selectedFilePath;
|
||||
}
|
||||
|
||||
JsHelpers(QWidget* parentWidget = NULL);
|
||||
public Q_SLOTS:
|
||||
// send game configuration to aya; can use lua script
|
||||
void launchGame(const QString& gameConfiguration, const int& virtualVersion)
|
||||
{
|
||||
Q_EMIT onLaunchingGame(gameConfiguration, virtualVersion);
|
||||
};
|
||||
void startAvatarView()
|
||||
{
|
||||
Q_EMIT onStartAvatarView();
|
||||
};
|
||||
void stopAvatarView()
|
||||
{
|
||||
Q_EMIT onStopAvatarView();
|
||||
};
|
||||
|
||||
// open settings panel
|
||||
void openSettings();
|
||||
// return JSON string from server endpoint (should be set after using openSettings)
|
||||
QString grabServers();
|
||||
QString getMasterServerURL();
|
||||
QString getRobloSecurityCookie();
|
||||
// get current username or logged in username
|
||||
QString userName();
|
||||
QString characterAppearanceJSON();
|
||||
void setSetting(QString setting, QString value);
|
||||
void setMasterServerURL(QString url);
|
||||
void setRobloSecurityCookie(QString cookie);
|
||||
void setUserName(QString string);
|
||||
void enableChatBarWidget();
|
||||
void setCharacterAppearanceJSON(QString json);
|
||||
void launchStudio();
|
||||
int getCompileTime();
|
||||
QString getPlatformName();
|
||||
QString getCompilerName();
|
||||
QString getVersion();
|
||||
bool isUsingInstance();
|
||||
QString getInstanceName();
|
||||
QString getInstanceMotd();
|
||||
QString getInstanceURL();
|
||||
// get current map
|
||||
QString currentMap()
|
||||
{
|
||||
return QString::fromStdString(selectedFilePath);
|
||||
}
|
||||
|
||||
// selects map
|
||||
void openMapPickerDialog();
|
||||
// present motd for server
|
||||
void presentMotd(QString motd);
|
||||
// start rccservice in another thread and connect using lua script
|
||||
void startRcc(const QString& serverConfiguration, int port, int virtualVersion, bool forceVirtualVersion)
|
||||
{
|
||||
Q_EMIT onStartRcc(serverConfiguration, port, virtualVersion, forceVirtualVersion);
|
||||
};
|
||||
// reads to password type if read is true
|
||||
// returns true if type is set, returns false if type is blank
|
||||
// types: "masterServer", "serverPassword"
|
||||
bool readPassword(const QString& type, bool read);
|
||||
bool hasServer();
|
||||
|
||||
void wearAsset(int64_t assetId, QString category);
|
||||
void setBodyColorJson(QString json);
|
||||
QString getBodyColorJson();
|
||||
void setBodyColor(QString bodyPart, int bodyColor);
|
||||
Q_SIGNALS:
|
||||
void onStartAvatarView();
|
||||
void onStopAvatarView();
|
||||
void onLaunchingGame(const QString& gameConfiguration, const int& virtualVersion);
|
||||
void onMapPicked(QString& filePicked);
|
||||
void onStartRcc(const QString& serverConfiguration, int port, int virtualVersion, bool forceVirtualVersion);
|
||||
};
|
||||
|
||||
class LauncherView : public QWebEngineView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QWebChannel* channel;
|
||||
JsHelpers* jsHelpers;
|
||||
|
||||
|
||||
public:
|
||||
LauncherView(QWidget* parent);
|
||||
|
||||
JsHelpers* getJsHelpers()
|
||||
{
|
||||
return jsHelpers;
|
||||
}
|
||||
};
|
||||
219
client/player/src/RenderJob.cpp
Normal file
219
client/player/src/RenderJob.cpp
Normal file
@@ -0,0 +1,219 @@
|
||||
#include "RenderJob.hpp"
|
||||
|
||||
#include "FunctionMarshaller.hpp"
|
||||
#include "Base/ViewBase.hpp"
|
||||
#include "Base/FrameRateManager.hpp"
|
||||
#include "API.hpp"
|
||||
|
||||
#include "Players.hpp"
|
||||
|
||||
#include "Log.hpp"
|
||||
|
||||
#include "SystemUtil.hpp"
|
||||
|
||||
#include "DataModel/RenderSettingsItem.hpp"
|
||||
#include "DataModel/HackDefines.hpp"
|
||||
|
||||
#include "DataModel/ModelInstance.hpp"
|
||||
|
||||
#include "DataModel/Workspace.hpp"
|
||||
|
||||
#include "View.hpp"
|
||||
|
||||
#include "boost/format.hpp"
|
||||
#include "boost/weak_ptr.hpp"
|
||||
|
||||
FASTFLAG(RenderLowLatencyLoop)
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
RenderJob::RenderJob(View* robloxView, FunctionMarshaller* marshaller, boost::shared_ptr<DataModel> dataModel)
|
||||
: BaseRenderJob(CRenderSettingsItem::singleton().getMinFrameRate(), CRenderSettingsItem::singleton().getMaxFrameRate(), dataModel)
|
||||
, robloxView(robloxView)
|
||||
, marshaller(marshaller)
|
||||
, stopped(0)
|
||||
, prepareBeginEvent(false)
|
||||
, prepareEndEvent(false)
|
||||
{
|
||||
}
|
||||
|
||||
void RenderJob::stop()
|
||||
{
|
||||
stopped = 1;
|
||||
}
|
||||
|
||||
|
||||
Time::Interval RenderJob::timeSinceLastRender() const
|
||||
{
|
||||
return Time::now<Time::Fast>() - lastRenderTime;
|
||||
}
|
||||
|
||||
Time::Interval RenderJob::sleepTime(const Stats& stats)
|
||||
{
|
||||
if (isAwake)
|
||||
return computeStandardSleepTime(stats, maxFrameRate);
|
||||
else
|
||||
return Aya::Time::Interval::max();
|
||||
}
|
||||
|
||||
|
||||
static void remoteCheatHelper(boost::weak_ptr<DataModel> weakDataModel)
|
||||
{
|
||||
boost::shared_ptr<DataModel> dataModel = weakDataModel.lock();
|
||||
|
||||
if (dataModel)
|
||||
{
|
||||
// this will send special item to server and server will kick user off
|
||||
Network::getSystemUrlLocal(dataModel.get());
|
||||
}
|
||||
}
|
||||
|
||||
static void reportHacker(boost::weak_ptr<DataModel> weakDataModel, const char* stat)
|
||||
{
|
||||
if (boost::shared_ptr<DataModel> dataModel = weakDataModel.lock())
|
||||
{
|
||||
if (Network::Players* players = dataModel->find<Network::Players>())
|
||||
{
|
||||
if (Network::Player* player = players->getLocalPlayer())
|
||||
{
|
||||
player->reportStat(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderJob::scheduleRender(weak_ptr<RenderJob> selfWeak, ViewBase* view, double timeJobStart)
|
||||
{
|
||||
shared_ptr<RenderJob> self = selfWeak.lock();
|
||||
if (!self)
|
||||
return;
|
||||
|
||||
self->prepareBeginEvent.Wait();
|
||||
|
||||
view->renderPrepare(self.get());
|
||||
|
||||
self->prepareEndEvent.Set();
|
||||
|
||||
view->renderPerform(timeJobStart);
|
||||
|
||||
self->wake();
|
||||
}
|
||||
|
||||
static void scheduleRenderPerform(const weak_ptr<RenderJob>& selfWeak, ViewBase* view, double timeJobStart)
|
||||
{
|
||||
if (shared_ptr<RenderJob> self = selfWeak.lock())
|
||||
{
|
||||
view->renderPerform(timeJobStart);
|
||||
self->wake();
|
||||
}
|
||||
}
|
||||
|
||||
TaskScheduler::StepResult RenderJob::stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
boost::shared_ptr<DataModel> dm = robloxView->getDataModel();
|
||||
if (!dm || stopped)
|
||||
return TaskScheduler::Done;
|
||||
|
||||
|
||||
double timeJobStart = Time::nowFastSec();
|
||||
|
||||
ViewBase* view = robloxView->GetGfxView();
|
||||
|
||||
if (!FFlag::RenderLowLatencyLoop)
|
||||
{
|
||||
Aya::DataModel::scoped_write_request request(dm.get());
|
||||
|
||||
const double renderDelta = timeSinceLastRender().seconds();
|
||||
|
||||
lastRenderTime = Aya::Time::now<Aya::Time::Fast>();
|
||||
isAwake = false;
|
||||
|
||||
marshaller->Submit(boost::bind(&scheduleRender, weak_from(this), view, timeJobStart));
|
||||
|
||||
dm->renderStep(renderDelta);
|
||||
|
||||
prepareBeginEvent.Set();
|
||||
prepareEndEvent.Wait(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
DataModel::scoped_write_request request(robloxView->getDataModel().get());
|
||||
|
||||
float secondsElapsed = robloxView->GetGfxView()->getFrameRateManager()->GetFrameTimeStats().getLatest() / 1000.f;
|
||||
|
||||
dm->renderStep(secondsElapsed);
|
||||
|
||||
isAwake = false;
|
||||
marshaller->Execute(boost::bind(&ViewBase::renderPrepare, view, this), NULL);
|
||||
|
||||
lastRenderTime = Time::now<Time::Fast>();
|
||||
}
|
||||
|
||||
{
|
||||
marshaller->Submit(boost::bind(&scheduleRenderPerform, weak_from(this), view, timeJobStart));
|
||||
}
|
||||
}
|
||||
|
||||
return TaskScheduler::Stepped;
|
||||
}
|
||||
|
||||
// Return information (via IMetric interface, probable asker was DataModel)
|
||||
std::string RenderJob::getMetric(const std::string& metric) const
|
||||
{
|
||||
if (metric == "Graphics Mode")
|
||||
return Aya::Reflection::EnumDesc<CRenderSettings::GraphicsMode>::singleton().convertToString(robloxView->GetLatchedGraphicsMode());
|
||||
|
||||
if (metric == "Render")
|
||||
{
|
||||
boost::format fmt("%.1f/s %d%%");
|
||||
fmt % averageStepsPerSecond() % (int)(100.0 * averageDutyCycle());
|
||||
return fmt.str();
|
||||
}
|
||||
|
||||
ViewBase* view = robloxView->GetGfxView();
|
||||
Aya::FrameRateManager* frm = view ? view->getFrameRateManager() : 0;
|
||||
if (frm)
|
||||
{
|
||||
if (metric == "FRM")
|
||||
return (frm && frm->IsBlockCullingEnabled()) ? "On" : "Off";
|
||||
if (metric == "Anti-Aliasing")
|
||||
return (frm && frm->getAntialiasingMode() == CRenderSettings::AntialiasingOn) ? "On" : "Off";
|
||||
}
|
||||
|
||||
// If we got here it means we were explicitly asked for information we do
|
||||
// not have.
|
||||
AYAASSERT(0);
|
||||
return "?";
|
||||
}
|
||||
|
||||
// Return information (via IMetric interface, probable asker was DataModel)
|
||||
double RenderJob::getMetricValue(const std::string& metric) const
|
||||
{
|
||||
ViewBase* view = robloxView->GetGfxView();
|
||||
|
||||
if (metric == "Render Duty")
|
||||
return averageDutyCycle();
|
||||
if (metric == "Render FPS")
|
||||
return averageStepsPerSecond();
|
||||
if (metric == "Render Job Time")
|
||||
return averageStepTime();
|
||||
if (metric == "Render Nominal FPS")
|
||||
return 1000.0 / view->getFrameRateManager()->GetRenderTimeAverage();
|
||||
if (metric == "Delta Between Renders")
|
||||
return view->getMetricValue(metric);
|
||||
if (metric == "Total Render")
|
||||
return view->getMetricValue(metric);
|
||||
if (metric == "Present Time")
|
||||
return view->getMetricValue(metric);
|
||||
if (metric == "GPU Delay")
|
||||
return view->getMetricValue(metric);
|
||||
|
||||
// If we got here it means we were explicitly asked for information we do
|
||||
// not have.
|
||||
AYAASSERT(0);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
} // namespace Aya
|
||||
57
client/player/src/RenderJob.hpp
Normal file
57
client/player/src/RenderJob.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/weak_ptr.hpp>
|
||||
|
||||
#include "DataModel/BaseRenderJob.hpp"
|
||||
|
||||
#include "DataModel/DataModel.hpp"
|
||||
|
||||
#include "time.hpp"
|
||||
|
||||
#include "TaskScheduler.hpp"
|
||||
|
||||
#include "TaskScheduler.Job.hpp"
|
||||
|
||||
#include "Utility/IMetric.hpp"
|
||||
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
class FunctionMarshaller;
|
||||
class View;
|
||||
class ViewBase;
|
||||
|
||||
// This job calls ViewBase::render(), which needs to be done exclusive to the
|
||||
// DataModel. This is why it has the DataModelJob::Render enum, which
|
||||
// prevents concurrent writes to DataModel. It also needs to run in the view's
|
||||
// thread for OpenGL.
|
||||
// TODO: Can Ogre be modified to not require the thread?
|
||||
class RenderJob
|
||||
: public BaseRenderJob
|
||||
, public IMetric
|
||||
{
|
||||
FunctionMarshaller* marshaller;
|
||||
View* robloxView;
|
||||
volatile int stopped;
|
||||
|
||||
CEvent prepareBeginEvent;
|
||||
CEvent prepareEndEvent;
|
||||
|
||||
static void scheduleRender(weak_ptr<RenderJob> selfWeak, ViewBase* view, double timeJobStart);
|
||||
|
||||
public:
|
||||
RenderJob(View* robloxView, FunctionMarshaller* marshaller, boost::shared_ptr<DataModel> dataModel);
|
||||
|
||||
Time::Interval timeSinceLastRender() const;
|
||||
Time::Interval sleepTime(const Stats& stats);
|
||||
|
||||
virtual TaskScheduler::StepResult stepDataModelJob(const Stats& stats);
|
||||
|
||||
virtual std::string getMetric(const std::string& metric) const;
|
||||
virtual double getMetricValue(const std::string& metric) const;
|
||||
|
||||
void stop();
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
283
client/player/src/View.cpp
Normal file
283
client/player/src/View.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
|
||||
#include "DataModel/DebugSettings.hpp"
|
||||
|
||||
#include "DataModel/GameBasicSettings.hpp"
|
||||
|
||||
#include "Script/ScriptContext.hpp"
|
||||
#include "FunctionMarshaller.hpp"
|
||||
#include <qguiapplication_platform.h>
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#include "Coordinator.hpp"
|
||||
|
||||
#include "RenderJob.hpp"
|
||||
#include "DataModel/RenderSettingsItem.hpp"
|
||||
#include "Utility/ScopedAssign.hpp"
|
||||
|
||||
#include "DataModel/Game.hpp"
|
||||
|
||||
#include "DataModel/UserController.hpp"
|
||||
|
||||
#include "InitializationError.hpp"
|
||||
#include "View.hpp"
|
||||
#include "format_string.hpp"
|
||||
|
||||
|
||||
#include "Log.hpp"
|
||||
#include "SystemUtil.hpp"
|
||||
#include "Render/VisualEngine.hpp"
|
||||
|
||||
LOGGROUP(PlayerShutdownLuaTimeoutSeconds)
|
||||
LOGGROUP(RobloxWndInit)
|
||||
FASTFLAGVARIABLE(GraphicsReportingInitErrorsToGAEnabled, true)
|
||||
FASTFLAGVARIABLE(UseNewAppBridgeInputWindows, false)
|
||||
FASTFLAGVARIABLE(GraphicsEnableBGFX, false)
|
||||
DYNAMIC_FASTFLAGVARIABLE(FullscreenRefocusingFix, false)
|
||||
|
||||
namespace
|
||||
{
|
||||
OgreWidget* SetFocusWrapper(OgreWidget* hwnd)
|
||||
{
|
||||
return hwnd;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
static const char* kSavedScreenSizeRegistryKey = "HKEY_CURRENT_USER\\Software\\Kiseki\\Kiseki\\Settings\\RobloxPlayerV4WindowSizeAndPosition";
|
||||
|
||||
View::View(OgreWidget* h)
|
||||
: marshaller(NULL)
|
||||
{
|
||||
#ifdef __linux__
|
||||
QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface();
|
||||
context.hWnd = native->nativeResourceForWindow("surface", h);
|
||||
context.display = native->nativeResourceForIntegration("display");
|
||||
#else
|
||||
context.hWnd = (HWND)h->winId();
|
||||
#endif
|
||||
context.width = h->width();
|
||||
context.height = h->height();
|
||||
marshaller = FunctionMarshaller::GetWindow();
|
||||
// window = dynamic_cast<OgreWindow*>(h->parentWidget());
|
||||
|
||||
initializeView();
|
||||
}
|
||||
|
||||
View::~View()
|
||||
{
|
||||
AYAASSERT(!this->game && "Call Stop() before shutting down!");
|
||||
view.reset();
|
||||
|
||||
if (marshaller)
|
||||
FunctionMarshaller::ReleaseWindow(marshaller);
|
||||
}
|
||||
|
||||
|
||||
void View::AboutToShutdown() {}
|
||||
|
||||
void View::initializeView()
|
||||
{
|
||||
ViewBase::InitPluginModules();
|
||||
|
||||
char* rgLogSuffix[5];
|
||||
memset(rgLogSuffix, 0, sizeof(rgLogSuffix));
|
||||
rgLogSuffix[(size_t)CRenderSettings::BGFX] = "gfx_bgfx";
|
||||
rgLogSuffix[(size_t)CRenderSettings::OpenGL] = "gfx_gl";
|
||||
|
||||
std::vector<CRenderSettings::GraphicsMode> modes;
|
||||
|
||||
Aya::CRenderSettings::GraphicsMode graphicsMode = CRenderSettingsItem::singleton().getLatchedGraphicsMode();
|
||||
|
||||
// this is very experimental right now
|
||||
if (FFlag::GraphicsEnableBGFX)
|
||||
modes.push_back(CRenderSettings::BGFX);
|
||||
|
||||
modes.push_back(CRenderSettings::OpenGL);
|
||||
|
||||
std::string lastMessage;
|
||||
bool success = false;
|
||||
size_t modei = 0;
|
||||
printf("%i modes\n", modes.size());
|
||||
while (!success && modei < modes.size())
|
||||
{
|
||||
graphicsMode = modes[modei];
|
||||
try
|
||||
{
|
||||
view.reset(ViewBase::CreateView(graphicsMode, &context, &CRenderSettingsItem::singleton()));
|
||||
view->initResources();
|
||||
|
||||
success = true;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
// d9mz - gutted dx, so we don't need to report this anymore
|
||||
printf("Mode %d failed: \"%s\"\n", graphicsMode, e.what());
|
||||
lastMessage += e.what();
|
||||
lastMessage += " | ";
|
||||
|
||||
modei++;
|
||||
if (modei < modes.size())
|
||||
{
|
||||
printf("Trying mode %d...\n", modes[modei]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
throw initialization_error("Failed to initialize view");
|
||||
}
|
||||
|
||||
AYAASSERT(view);
|
||||
initializeSizes();
|
||||
}
|
||||
|
||||
void View::resetScheduler()
|
||||
{
|
||||
TaskScheduler& taskScheduler = TaskScheduler::singleton();
|
||||
taskScheduler.add(renderJob);
|
||||
}
|
||||
|
||||
void View::initializeJobs()
|
||||
{
|
||||
shared_ptr<DataModel> dataModel = game->getDataModel();
|
||||
renderJob.reset(new RenderJob(this, marshaller, dataModel));
|
||||
}
|
||||
|
||||
void View::initializeInput()
|
||||
{
|
||||
/*userInput.reset(new UserInput(GetHWnd(), game, this));
|
||||
|
||||
if (userInput)
|
||||
{
|
||||
DataModel::LegacyLock lock(game->getDataModel(), DataModelJob::Write);
|
||||
|
||||
ControllerService* service = ServiceProvider::create<ControllerService>(game->getDataModel().get());
|
||||
service->setHardwareDevice(userInput.get());
|
||||
}*/
|
||||
}
|
||||
|
||||
void View::RemoveJobs()
|
||||
{
|
||||
if (renderJob)
|
||||
{
|
||||
boost::function<void()> callback = boost::bind(&FunctionMarshaller::ProcessMessages, marshaller);
|
||||
TaskScheduler::singleton().removeBlocking(renderJob, callback);
|
||||
}
|
||||
|
||||
// RenderJob is sure to be completed at this point, since removeBlocking returned - but it might have marshalled
|
||||
// renderPerform asynchronously before exiting, which means that we might still have a callback that uses this view
|
||||
// in the marshaller queue.
|
||||
// This makes sure that all pending marshalled events are processed to avoid a use after free.
|
||||
marshaller->ProcessMessages();
|
||||
|
||||
// All render processing is complete; it's safe to reset job pointers now
|
||||
renderJob.reset();
|
||||
}
|
||||
|
||||
shared_ptr<DataModel> View::getDataModel()
|
||||
{
|
||||
return game ? game->getDataModel() : shared_ptr<DataModel>();
|
||||
}
|
||||
|
||||
CRenderSettings::GraphicsMode View::GetLatchedGraphicsMode()
|
||||
{
|
||||
return CRenderSettingsItem::singleton().getLatchedGraphicsMode();
|
||||
}
|
||||
|
||||
void View::initializeSizes()
|
||||
{
|
||||
if (GetHWnd() == NULL)
|
||||
{
|
||||
// LogManager::ReportEvent(EVENTLOG_WARNING_TYPE, "Attempt to initialize monitor sizes without valid HWND");
|
||||
return;
|
||||
}
|
||||
G3D::Vector2int16 currentDisplaySize = G3D::Vector2int16(800, 600);
|
||||
|
||||
G3D::Vector2int16 fullscreenSize, windowSize;
|
||||
|
||||
Aya::CRenderSettings::ResolutionPreset preference = CRenderSettingsItem::singleton().getResolutionPreference();
|
||||
if (preference == Aya::CRenderSettings::ResolutionAuto)
|
||||
{
|
||||
fullscreenSize = currentDisplaySize;
|
||||
}
|
||||
else
|
||||
{
|
||||
const Aya::CRenderSettings::RESOLUTIONENTRY& res = CRenderSettingsItem::singleton().getResolutionPreset(preference);
|
||||
fullscreenSize.x = res.width;
|
||||
fullscreenSize.y = res.height;
|
||||
}
|
||||
|
||||
// validate mode
|
||||
windowSize = fullscreenSize;
|
||||
windowSize.x = std::min((int)windowSize.x, (int)currentDisplaySize.x);
|
||||
windowSize.y = std::min((int)windowSize.y, (int)currentDisplaySize.y);
|
||||
|
||||
CRenderSettingsItem::singleton().setWindowSize(windowSize);
|
||||
CRenderSettingsItem::singleton().setFullscreenSize(fullscreenSize);
|
||||
}
|
||||
|
||||
void View::onResize(int x, int y)
|
||||
{
|
||||
printf("%i, %i\n", x, y);
|
||||
view->onResize(x, y);
|
||||
}
|
||||
|
||||
void View::unbindWorkspace()
|
||||
{
|
||||
shared_ptr<DataModel> dm = getDataModel();
|
||||
DataModel::LegacyLock lock(dm, DataModelJob::Write);
|
||||
view->bindWorkspace(boost::shared_ptr<DataModel>());
|
||||
}
|
||||
|
||||
void View::bindWorkspace()
|
||||
{
|
||||
shared_ptr<DataModel> dm = getDataModel();
|
||||
DataModel::LegacyLock lock(dm, DataModelJob::Write);
|
||||
view->bindWorkspace(game->getDataModel());
|
||||
view->buildGui();
|
||||
}
|
||||
|
||||
void View::Start(const shared_ptr<Game>& game)
|
||||
{
|
||||
AYAASSERT(!this->game);
|
||||
this->game = game;
|
||||
|
||||
bindWorkspace();
|
||||
initializeJobs();
|
||||
initializeInput(); // NOTE: have to do this here, Input requires datamodel access
|
||||
resetScheduler();
|
||||
|
||||
// ensure keyboard is in focus (DE6272)
|
||||
// if (userInput)
|
||||
// userInput->setKeyboardDesired(true);
|
||||
}
|
||||
|
||||
void View::Stop()
|
||||
{
|
||||
AYAASSERT(this->game);
|
||||
this->RemoveJobs();
|
||||
|
||||
if (game && game->getDataModel())
|
||||
if (Aya::ControllerService* service = Aya::ServiceProvider::create<Aya::ControllerService>(game->getDataModel().get()))
|
||||
service->setHardwareDevice(NULL);
|
||||
|
||||
// if (userInput)
|
||||
// {
|
||||
// userInput->removeJobs();
|
||||
// userInput.reset();
|
||||
// }
|
||||
|
||||
unbindWorkspace();
|
||||
|
||||
game.reset();
|
||||
}
|
||||
|
||||
void View::CloseWindow()
|
||||
{
|
||||
window->closeGame();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Aya
|
||||
84
client/player/src/View.hpp
Normal file
84
client/player/src/View.hpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include "Base/ViewBase.hpp"
|
||||
#include "Window.hpp"
|
||||
|
||||
namespace Aya
|
||||
{
|
||||
|
||||
// Forward declarations
|
||||
class FunctionMarshaller;
|
||||
class Game;
|
||||
struct OSContext;
|
||||
class RenderJob;
|
||||
class ViewBase;
|
||||
|
||||
namespace Tasks
|
||||
{
|
||||
class Sequence;
|
||||
}
|
||||
|
||||
// Class responsible for the game view
|
||||
class View
|
||||
{
|
||||
OgreWindow* window;
|
||||
public:
|
||||
View(OgreWidget* h);
|
||||
~View();
|
||||
|
||||
void setOgreWindow(OgreWindow* wnd) { window = wnd; }
|
||||
|
||||
void AboutToShutdown();
|
||||
|
||||
void Start(const boost::shared_ptr<Game>& game);
|
||||
void Stop();
|
||||
|
||||
void ShowWindow();
|
||||
|
||||
void CloseWindow();
|
||||
|
||||
OgreWidget* GetHWnd() const
|
||||
{
|
||||
return static_cast<OgreWidget*>(context.hWnd);
|
||||
}
|
||||
|
||||
// TODO: refactor verbs so this isn't needed
|
||||
ViewBase* GetGfxView() const
|
||||
{
|
||||
return view.get();
|
||||
}
|
||||
|
||||
CRenderSettings::GraphicsMode GetLatchedGraphicsMode();
|
||||
|
||||
boost::shared_ptr<DataModel> getDataModel();
|
||||
|
||||
void onResize(int x, int y);
|
||||
|
||||
private:
|
||||
// View references, but doesn't own the game
|
||||
boost::shared_ptr<Game> game;
|
||||
|
||||
// The OS context used by the view.
|
||||
OSContext context;
|
||||
|
||||
// The view into the game world.
|
||||
boost::scoped_ptr<Aya::ViewBase> view;
|
||||
|
||||
boost::shared_ptr<Tasks::Sequence> sequence;
|
||||
boost::shared_ptr<RenderJob> renderJob;
|
||||
FunctionMarshaller* marshaller;
|
||||
|
||||
void initializeSizes();
|
||||
|
||||
void bindWorkspace();
|
||||
void unbindWorkspace();
|
||||
|
||||
void initializeView();
|
||||
void initializeInput();
|
||||
void resetScheduler();
|
||||
|
||||
void initializeJobs();
|
||||
void RemoveJobs();
|
||||
};
|
||||
|
||||
} // namespace Aya
|
||||
978
client/player/src/Window.cpp
Normal file
978
client/player/src/Window.cpp
Normal file
@@ -0,0 +1,978 @@
|
||||
#include "Window.hpp"
|
||||
#include <QSurfaceFormat>
|
||||
#include "DataModel/GameBasicSettings.hpp"
|
||||
#include "FunctionMarshaller.hpp"
|
||||
#include "Application.hpp"
|
||||
#include "DataModel/UserInputService.hpp"
|
||||
#include "DataModel/PlayerGui.hpp"
|
||||
#include "Players.hpp"
|
||||
#include "API.hpp"
|
||||
#include "Tree/Service.hpp"
|
||||
#include "Utility/AyaService.hpp"
|
||||
#include "AvatarViewService.hpp"
|
||||
#include "Utility/StandardOut.hpp"
|
||||
#include "DataModel/Lighting.hpp"
|
||||
#include "DataModel/Workspace.hpp"
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QWheelEvent>
|
||||
#include <QApplication>
|
||||
#include <QVBoxLayout>
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <qnamespace.h>
|
||||
|
||||
#ifdef __linux
|
||||
#include <sys/select.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
extern QApplication* qtAppPtr;
|
||||
extern Aya::Application* appPtr;
|
||||
|
||||
OgreWidget::OgreWidget(QWidget* ogreWindow, QScreen* parent)
|
||||
: QWindow(parent)
|
||||
{
|
||||
this->ogreWindow = ogreWindow;
|
||||
|
||||
disableClosures = false;
|
||||
|
||||
setMinimumSize(QSize(100, 100));
|
||||
setMaximumSize(QSize(3800, 2100));
|
||||
|
||||
setCursor(Qt::BlankCursor);
|
||||
|
||||
firstClosure = false;
|
||||
|
||||
connect(dynamic_cast<OgreWindow*>(ogreWindow)->getTextInput(), &GrayChatBar::enteredText, this, &OgreWidget::onChatBarEnteredText);
|
||||
}
|
||||
|
||||
void OgreWidget::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QSize size = event->size();
|
||||
appPtr->onResize(size.width(), size.height());
|
||||
}
|
||||
|
||||
void OgreWidget::onChatBarEnteredText(const QString& _text)
|
||||
{
|
||||
if (Aya::Network::Players* players = Aya::ServiceProvider::create<Aya::Network::Players>(appPtr->getDM().get()))
|
||||
{
|
||||
try
|
||||
{
|
||||
std::string text = _text.toStdString();
|
||||
boost::trim(text);
|
||||
|
||||
players->chat(text);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
printf("%s\n", e.what());
|
||||
}
|
||||
// return focus to the ogre widget...
|
||||
// setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
OgreWindow::OgreWindow(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
QWidget* innerWidget = new QWidget(this);
|
||||
QHBoxLayout* innerLayout = new QHBoxLayout(innerWidget);
|
||||
launcherView = new LauncherView(innerWidget);
|
||||
|
||||
setWindowIcon(QIcon(":/icon.ico"));
|
||||
|
||||
textInput = new GrayChatBar(this);
|
||||
myOgreWidget = new OgreWidget(this, QGuiApplication::primaryScreen());
|
||||
// myOgreWidget->setSurfaceType(QWindow::RasterSurface);
|
||||
myOgreWidget->create();
|
||||
myOgreWidgetContainer = QWidget::createWindowContainer(myOgreWidget);
|
||||
|
||||
myOgreWidgetContainer->setAttribute(Qt::WA_PaintOnScreen, true);
|
||||
myOgreWidgetContainer->setAttribute(Qt::WA_NativeWindow, true);
|
||||
myOgreWidgetContainer->setAttribute(Qt::WA_DontCreateNativeAncestors, true);
|
||||
myOgreWidgetContainer->setAttribute(Qt::WA_OpaquePaintEvent, true);
|
||||
myOgreWidgetContainer->setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
myOgreWidgetContainer->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
myOgreWidgetContainer->setContextMenuPolicy(Qt::PreventContextMenu);
|
||||
myOgreWidgetContainer->setMouseTracking(true);
|
||||
myOgreWidgetContainer->setFocusPolicy(Qt::StrongFocus);
|
||||
myOgreWidgetContainer->setAutoFillBackground(true);
|
||||
myOgreWidgetContainer->setAcceptDrops(true);
|
||||
|
||||
setMinimumSize(800, 600);
|
||||
setMaximumSize(800, 600);
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
myOgreWidgetContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
textInput->setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
innerLayout->addWidget(launcherView, 2);
|
||||
innerLayout->addWidget(myOgreWidgetContainer, 1);
|
||||
innerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
innerLayout->setSpacing(0);
|
||||
|
||||
layout->addWidget(innerWidget, 1);
|
||||
layout->setSpacing(0);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
layout->addWidget(textInput, 1);
|
||||
|
||||
launcherView->setVisible(false);
|
||||
|
||||
Aya::GameBasicSettings::singleton().virtualVersionChangedSignal.connect(boost::bind(&OgreWindow::handleVirtualVersionChange, this, _1));
|
||||
connect(launcherView->getJsHelpers(), &JsHelpers::onStartAvatarView, this, &OgreWindow::startAvatarView);
|
||||
connect(launcherView->getJsHelpers(), &JsHelpers::onStopAvatarView, this, &OgreWindow::stopAvatarView);
|
||||
connect(launcherView->getJsHelpers(), &JsHelpers::onLaunchingGame, this, &OgreWindow::launchGame);
|
||||
connect(launcherView->getJsHelpers(), &JsHelpers::onStartRcc, this, &OgreWindow::startRcc);
|
||||
|
||||
QSettings settings;
|
||||
restoreGeometry(settings.value("Player/Geometry").toByteArray());
|
||||
}
|
||||
|
||||
OgreWindow::~OgreWindow()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.setValue("Player/Geometry", saveGeometry());
|
||||
|
||||
if (runRcc)
|
||||
shutdownRcc();
|
||||
}
|
||||
|
||||
void OgreWindow::closeGame()
|
||||
{
|
||||
printf("OgreWindow::closeGame\n");
|
||||
|
||||
|
||||
myOgreWidgetContainer->setVisible(false);
|
||||
appPtr->prepareToShutdown();
|
||||
appPtr->shutdown();
|
||||
|
||||
if (runRcc)
|
||||
shutdownRcc();
|
||||
|
||||
setMaximumSize(800, 600);
|
||||
|
||||
launcherView->setVisible(true);
|
||||
launcherView->repaint();
|
||||
|
||||
#ifdef _WIN32
|
||||
HWND hwnd = (HWND)winId();
|
||||
LONG style = GetWindowLong(hwnd, GWL_STYLE) & ~(WS_MAXIMIZEBOX | WS_SIZEBOX);
|
||||
|
||||
SetWindowLong(hwnd, GWL_STYLE, style);
|
||||
SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
|
||||
#endif
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint);
|
||||
}
|
||||
|
||||
void OgreWindow::closeEvent(QCloseEvent* ev)
|
||||
{
|
||||
appPtr->prepareToShutdown();
|
||||
qtAppPtr->quit();
|
||||
}
|
||||
|
||||
void OgreWindow::shutdownRcc()
|
||||
{
|
||||
if (!runRcc)
|
||||
return;
|
||||
|
||||
runRcc = false;
|
||||
if (rccThread.joinable())
|
||||
rccThread.join();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
std::wstring MultiByteToWideString(const std::string& str)
|
||||
{
|
||||
int size_needed = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), NULL, 0);
|
||||
std::wstring wstrTo(size_needed, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), &wstrTo[0], size_needed);
|
||||
return wstrTo;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define CAPTURE_SERVER_STDOUT
|
||||
|
||||
void OgreWindow::rccThreadProc(std::atomic<bool>& runAtomic, std::string password, OgreWindow* window, int port, int virtualVersion)
|
||||
{
|
||||
LauncherView* lview = window->getLauncherView();
|
||||
JsHelpers* jhp = lview->getJsHelpers();
|
||||
|
||||
Aya::GameBasicSettings::VirtualVersion vv = static_cast<Aya::GameBasicSettings::VirtualVersion>(virtualVersion);
|
||||
|
||||
std::string vvStr = std::to_string(virtualVersion);
|
||||
std::vector<char*> argv;
|
||||
#ifdef _WIN32
|
||||
// argv.push_back("Aya.Server.exe");
|
||||
#else
|
||||
argv.push_back("Aya.Server");
|
||||
#endif
|
||||
argv.push_back("--contentPath");
|
||||
argv.push_back("content");
|
||||
argv.push_back("--port");
|
||||
argv.push_back("-1"); // disable SOAP http port
|
||||
argv.push_back("--virtualVersion");
|
||||
argv.push_back((char*)vvStr.c_str()); // disable SOAP http port
|
||||
argv.push_back("--localServer");
|
||||
argv.push_back("--noconsole");
|
||||
if (!password.empty())
|
||||
{
|
||||
argv.push_back("--password");
|
||||
argv.push_back((char*)password.c_str());
|
||||
}
|
||||
|
||||
// TODO: msHost, msName, msMotdPreview, msMotdFile
|
||||
QSettings settings;
|
||||
std::string key = settings.value("JsHelpers/masterServerAuthorization").toString().toStdString();
|
||||
std::string url = settings.value("JsHelpers/masterServerUrl").toString().toStdString();
|
||||
std::string place = jhp->getFilePath();
|
||||
std::string username = settings.value("JsHelpers/Username").toString().toStdString();
|
||||
if (url != "")
|
||||
{
|
||||
argv.push_back("--masterServerUrl");
|
||||
argv.push_back((char*)url.c_str());
|
||||
argv.push_back("--baseUrl");
|
||||
argv.push_back((char*)url.c_str());
|
||||
if (key != "")
|
||||
{
|
||||
argv.push_back("--masterServerAuthorization");
|
||||
argv.push_back((char*)key.c_str());
|
||||
}
|
||||
}
|
||||
if (place != "")
|
||||
{
|
||||
place = "\"file://" + place + "\"";
|
||||
argv.push_back("--localServerPlace");
|
||||
argv.push_back((char*)place.c_str());
|
||||
}
|
||||
argv.push_back("--localServerPort");
|
||||
std::string _port = std::to_string(port);
|
||||
argv.push_back((char*)_port.c_str());
|
||||
argv.push_back("--msHost");
|
||||
argv.push_back((char*)username.c_str());
|
||||
|
||||
Aya::StandardOut::singleton()->printf(Aya::MESSAGE_INFO, "dmkmm: %s", username.c_str());
|
||||
|
||||
|
||||
#if defined(__linux__) && !defined(__APPLE__)
|
||||
FILE* xterm = fopen("/usr/bin/xterm", "r");
|
||||
if (!xterm)
|
||||
{
|
||||
throw std::runtime_error("You do not have xterm installed, or it is not readable by your current user");
|
||||
}
|
||||
fclose(xterm);
|
||||
|
||||
pid_t pid;
|
||||
pid = fork();
|
||||
if (pid == -1)
|
||||
{
|
||||
throw std::runtime_error("fork() returned -1");
|
||||
}
|
||||
else if (pid == 0)
|
||||
{
|
||||
pthread_setname_np(pthread_self(), "Server Execution Thread");
|
||||
|
||||
char currentPath[PATH_MAX];
|
||||
char serverPath[PATH_MAX];
|
||||
std::string cmdline;
|
||||
|
||||
getcwd(currentPath, PATH_MAX);
|
||||
snprintf(serverPath, PATH_MAX, "%s/Aya.Server", currentPath);
|
||||
|
||||
argv[0] = serverPath;
|
||||
for (auto p : argv)
|
||||
cmdline += std::string(p) + " ";
|
||||
printf("%s\n", cmdline.c_str());
|
||||
|
||||
argv.clear();
|
||||
|
||||
argv.push_back("xterm");
|
||||
argv.push_back("-e");
|
||||
argv.push_back((char*)cmdline.c_str());
|
||||
|
||||
std::string xterm_cmdline = "";
|
||||
for (auto p : argv)
|
||||
xterm_cmdline += std::string(p) + " ";
|
||||
printf("%s\n", xterm_cmdline.c_str());
|
||||
|
||||
snprintf(serverPath, PATH_MAX, "/usr/bin/xterm");
|
||||
|
||||
argv.push_back(NULL);
|
||||
|
||||
int r = execv(serverPath, argv.data());
|
||||
|
||||
printf("Error (%s) %i\n", serverPath, r);
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_setname_np(pthread_self(), "Server Manager Thread");
|
||||
|
||||
while (runAtomic)
|
||||
{
|
||||
sleep(1);
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
kill(pid, SIGKILL);
|
||||
printf("Sent sigkill to pid %i\n", pid);
|
||||
}
|
||||
#elif defined(AYA_OS_WINDOWS)
|
||||
STARTUPINFOW si;
|
||||
PROCESS_INFORMATION pi;
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
HANDLE hReadPipe, hWritePipe;
|
||||
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
// Set up the security attributes struct
|
||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa.bInheritHandle = TRUE;
|
||||
sa.lpSecurityDescriptor = NULL;
|
||||
#ifdef CAPTURE_SERVER_STDOUT
|
||||
// Create a pipe for the child process's STDOUT
|
||||
if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0))
|
||||
{
|
||||
throw std::runtime_error("CreatePipe failed");
|
||||
}
|
||||
|
||||
// Ensure the read handle to the pipe for STDOUT is not inherited
|
||||
SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, 0);
|
||||
|
||||
si.hStdError = hWritePipe;
|
||||
si.hStdOutput = hWritePipe;
|
||||
si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
#endif
|
||||
wchar_t currentPath[MAX_PATH];
|
||||
wchar_t serverPath[MAX_PATH];
|
||||
GetCurrentDirectoryW(MAX_PATH, currentPath);
|
||||
_snwprintf_s(serverPath, MAX_PATH, _TRUNCATE, L"%s\\Aya.Server.exe", currentPath);
|
||||
|
||||
wchar_t cmdLine[MAX_PATH];
|
||||
|
||||
std::string args;
|
||||
for (const auto& arg : argv)
|
||||
{
|
||||
args += arg;
|
||||
args += " ";
|
||||
}
|
||||
std::wstring widestr = MultiByteToWideString(args);
|
||||
|
||||
std::wstring fullCommandLine = serverPath;
|
||||
fullCommandLine += L" ";
|
||||
fullCommandLine += MultiByteToWideString(args);
|
||||
|
||||
printf("args %ls", fullCommandLine.c_str());
|
||||
int dwProcess = CreateProcessW(serverPath, // Module name
|
||||
fullCommandLine.data(), // Command line
|
||||
NULL, // Process handle not inheritable
|
||||
NULL, // Thread handle not inheritable
|
||||
TRUE, // Set handle inheritance to TRUE
|
||||
CREATE_UNICODE_ENVIRONMENT, // Creation flags
|
||||
NULL, // Use parent's environment block
|
||||
NULL, // Use parent's starting directory
|
||||
&si, // Pointer to STARTUPINFO structure
|
||||
&pi // Pointer to PROCESS_INFORMATION structure
|
||||
);
|
||||
if (dwProcess)
|
||||
{
|
||||
wprintf(L"Process created successfully with PID %lu\n", pi.dwProcessId);
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD dwLastErrorCode = GetLastError();
|
||||
wprintf(L"CreateProcessW failed with error code: %d\n", dwLastErrorCode);
|
||||
}
|
||||
// Set thread name
|
||||
SetThreadDescription(GetCurrentThread(), L"Server Manager Thread");
|
||||
#ifdef CAPTURE_SERVER_STDOUT
|
||||
char buffer[4096];
|
||||
DWORD bytesRead;
|
||||
DWORD bytesAvail;
|
||||
#endif
|
||||
while (runAtomic)
|
||||
{
|
||||
#ifdef CAPTURE_SERVER_STDOUT
|
||||
// Check if there's any output to read
|
||||
if (PeekNamedPipe(hReadPipe, NULL, 0, NULL, &bytesAvail, NULL) && bytesAvail > 0)
|
||||
{
|
||||
if (ReadFile(hReadPipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL) && bytesRead > 0)
|
||||
{
|
||||
buffer[bytesRead] = '\0';
|
||||
// Convert to wide string for Unicode-correct output
|
||||
int wideSize = MultiByteToWideChar(CP_UTF8, 0, buffer, -1, NULL, 0);
|
||||
std::wstring wideStr(wideSize, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, buffer, -1, &wideStr[0], wideSize);
|
||||
wprintf(L"%s", wideStr.c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Sleep(1000); // Sleep for 1 second
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
#ifdef CAPTURE_SERVER_STDOUT
|
||||
CloseHandle(hReadPipe);
|
||||
CloseHandle(hWritePipe);
|
||||
#endif
|
||||
|
||||
// Terminate the child process
|
||||
TerminateProcess(pi.hProcess, 0);
|
||||
wprintf(L"Terminated process with PID %lu\n", pi.dwProcessId);
|
||||
|
||||
// Close process and thread handles
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
#elif defined(__APPLE__)
|
||||
/// Show Message Box TTo User displaying different options of fruits and produce to choose from.
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void OgreWindow::startRcc(const QString& serverConfiguration, int port, int virtualVersion, bool forceVirtualVersion)
|
||||
{
|
||||
Aya::GameBasicSettings::VirtualVersion vv = static_cast<Aya::GameBasicSettings::VirtualVersion>(virtualVersion);
|
||||
|
||||
myOgreWidget->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
|
||||
|
||||
enterWidget();
|
||||
|
||||
QSettings settings;
|
||||
std::string password = settings.value("JsHelpers/serverHostPassword").toString().toStdString();
|
||||
|
||||
runRcc = true;
|
||||
rccThread = std::thread(&OgreWindow::rccThreadProc, std::ref(runRcc), password, this, port, virtualVersion);
|
||||
|
||||
appPtr->InitializeNewGame(vv);
|
||||
appPtr->SendScript(serverConfiguration.toStdString(), vv);
|
||||
|
||||
Aya::AyaService::localServer = true;
|
||||
|
||||
if (password != "")
|
||||
{
|
||||
Aya::Network::setPassword(password.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void OgreWindow::launchGame(const QString& gameConfiguration, const int& virtualVersion)
|
||||
{ // will be static casted to enum.
|
||||
Aya::GameBasicSettings::VirtualVersion vv = static_cast<Aya::GameBasicSettings::VirtualVersion>(virtualVersion);
|
||||
|
||||
myOgreWidget->show();
|
||||
myOgreWidget->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
|
||||
|
||||
enterWidget();
|
||||
|
||||
appPtr->InitializeNewGame(vv);
|
||||
appPtr->SendScript(gameConfiguration.toStdString(), vv);
|
||||
|
||||
std::string password = launcherView->getJsHelpers()->getCurrentServerAuthorization();
|
||||
if (password != "")
|
||||
{
|
||||
Aya::Network::setPassword(password.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void OgreWindow::startAvatarView()
|
||||
{
|
||||
myOgreWidget->show();
|
||||
myOgreWidgetContainer->setMaximumWidth(QWIDGETSIZE_MAX);
|
||||
myOgreWidget->setCursor(Qt::OpenHandCursor);
|
||||
|
||||
isShowingAvatarPage = true;
|
||||
|
||||
appPtr->InitializeNewEmptyGame();
|
||||
|
||||
Aya::Workspace* w = Aya::ServiceProvider::find<Aya::Workspace>(appPtr->getDM().get());
|
||||
w->setImageServerView(false);
|
||||
w->getCamera()->zoom(-2);
|
||||
|
||||
Aya::AvatarViewService* avs = Aya::ServiceProvider::create<Aya::AvatarViewService>(appPtr->getDM().get());
|
||||
avs->setJsHelpers(launcherView->getJsHelpers());
|
||||
|
||||
Aya::Lighting* lighting = Aya::ServiceProvider::create<Aya::Lighting>(appPtr->getDM().get());
|
||||
lighting->suppressSky(true);
|
||||
lighting->setClearAlpha(0);
|
||||
}
|
||||
|
||||
void OgreWindow::stopAvatarView()
|
||||
{
|
||||
Aya::AvatarViewService* avs = Aya::ServiceProvider::create<Aya::AvatarViewService>(appPtr->getDM().get());
|
||||
avs->setJsHelpers(NULL);
|
||||
|
||||
myOgreWidgetContainer->setVisible(false);
|
||||
|
||||
appPtr->prepareToShutdown();
|
||||
appPtr->shutdown();
|
||||
|
||||
launcherView->setVisible(true);
|
||||
|
||||
isShowingAvatarPage = false;
|
||||
}
|
||||
|
||||
void OgreWindow::enterWidget()
|
||||
{
|
||||
printf("OgreWindow::enterWidget\n");
|
||||
launcherView->setVisible(false);
|
||||
}
|
||||
|
||||
void OgreWindow::hideOgreWidget()
|
||||
{
|
||||
printf("OgreWindow::hideOgreWidget\n");
|
||||
|
||||
launcherView->setVisible(true);
|
||||
myOgreWidgetContainer->setVisible(false);
|
||||
}
|
||||
|
||||
void OgreWindow::handleVirtualVersionChange(Aya::GameBasicSettings::VirtualVersion version)
|
||||
{
|
||||
if (version == Aya::GameBasicSettings::VERSION_2012)
|
||||
{
|
||||
if (Aya::StarterGuiService* gui = Aya::ServiceProvider::create<Aya::StarterGuiService>(appPtr->getDM().get()))
|
||||
{
|
||||
if (gui->getCoreGuiEnabled(Aya::StarterGuiService::COREGUI_CHATGUI) && !isShowingAvatarPage)
|
||||
{
|
||||
textInput->setVisible(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
textInput->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool OgreWindow::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == Aya::TYPE_FUNCTION_MARSHALLER)
|
||||
{
|
||||
return myOgreWidget->event(event);
|
||||
}
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
void OgreWindow::changeEvent(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::PaletteChange)
|
||||
{
|
||||
QPalette palette = this->palette();
|
||||
QString mode = palette.color(QPalette::Window).lightness() < 128 ? "dark" : "light";
|
||||
launcherView->page()->runJavaScript(QString("document.documentElement.setAttribute('data-mode', '%1');").arg(mode));
|
||||
}
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
// SCARY LAND OF HANDLING EVENTS
|
||||
|
||||
static int translateKeyModifiers(Qt::KeyboardModifiers state, const QString& text)
|
||||
{
|
||||
// Convert modifier combos to a single integer.
|
||||
int result = 0;
|
||||
if ((state & Qt::ShiftModifier) && (text.size() == 0 || !text.at(0).isPrint() || text.at(0).isLetter() || text.at(0).isSpace()))
|
||||
result |= Qt::SHIFT;
|
||||
if (state & Qt::ControlModifier)
|
||||
result |= Qt::CTRL;
|
||||
if (state & Qt::MetaModifier)
|
||||
result |= Qt::META;
|
||||
if (state & Qt::AltModifier)
|
||||
result |= Qt::ALT;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Aya::KeyCode keyCodeTOUIKeyCode(int keyCode)
|
||||
{
|
||||
/// Handle all albhabets
|
||||
if (keyCode >= 65 && keyCode <= 90)
|
||||
return Aya::KeyCode(keyCode - Qt::Key_A + 'a');
|
||||
|
||||
// Handle Numbers
|
||||
if (keyCode >= Qt::Key_0 && keyCode <= Qt::Key_9)
|
||||
return Aya::KeyCode(keyCode);
|
||||
|
||||
// Handle F1 to F15
|
||||
if (keyCode >= Qt::Key_F1 && keyCode <= Qt::Key_F15)
|
||||
return Aya::KeyCode(keyCode - 0xFFFF16);
|
||||
|
||||
|
||||
Aya::KeyCode rbxKey = Aya::SDLK_UNKNOWN;
|
||||
|
||||
// Handle Special Unordered Keys Here
|
||||
switch (keyCode)
|
||||
{
|
||||
case Qt::Key_CapsLock:
|
||||
rbxKey = Aya::SDLK_CAPSLOCK;
|
||||
break;
|
||||
case Qt::Key_Backspace:
|
||||
rbxKey = Aya::SDLK_BACKSPACE;
|
||||
break;
|
||||
case Qt::Key_Up:
|
||||
rbxKey = Aya::SDLK_UP;
|
||||
break;
|
||||
case Qt::Key_Down:
|
||||
rbxKey = Aya::SDLK_DOWN;
|
||||
break;
|
||||
case Qt::Key_Left:
|
||||
rbxKey = Aya::SDLK_LEFT;
|
||||
break;
|
||||
case Qt::Key_Right:
|
||||
rbxKey = Aya::SDLK_RIGHT;
|
||||
break;
|
||||
case Qt::Key_Insert:
|
||||
rbxKey = Aya::SDLK_INSERT;
|
||||
break;
|
||||
case Qt::Key_Delete:
|
||||
rbxKey = Aya::SDLK_DELETE;
|
||||
break;
|
||||
case Qt::Key_Home:
|
||||
rbxKey = Aya::SDLK_HOME;
|
||||
break;
|
||||
case Qt::Key_End:
|
||||
rbxKey = Aya::SDLK_END;
|
||||
break;
|
||||
case Qt::Key_PageUp:
|
||||
rbxKey = Aya::SDLK_PAGEUP;
|
||||
break;
|
||||
case Qt::Key_PageDown:
|
||||
rbxKey = Aya::SDLK_PAGEDOWN;
|
||||
break;
|
||||
case Qt::Key_Space:
|
||||
rbxKey = Aya::SDLK_SPACE;
|
||||
break;
|
||||
case Qt::Key_Control:
|
||||
rbxKey = Aya::SDLK_LCTRL;
|
||||
break;
|
||||
case Qt::Key_Alt:
|
||||
rbxKey = Aya::SDLK_LALT;
|
||||
break;
|
||||
case Qt::Key_Shift:
|
||||
rbxKey = Aya::SDLK_LSHIFT;
|
||||
break;
|
||||
case Qt::Key_Escape:
|
||||
rbxKey = Aya::SDLK_ESCAPE;
|
||||
break;
|
||||
case Qt::Key_Minus:
|
||||
rbxKey = Aya::SDLK_MINUS;
|
||||
break;
|
||||
case Qt::Key_Equal:
|
||||
rbxKey = Aya::SDLK_EQUALS;
|
||||
break;
|
||||
case Qt::Key_Tab:
|
||||
rbxKey = Aya::SDLK_TAB;
|
||||
break;
|
||||
case Qt::Key_Backtab:
|
||||
{
|
||||
rbxKey = Aya::SDLK_TAB;
|
||||
break;
|
||||
}
|
||||
case Qt::Key_BracketLeft:
|
||||
rbxKey = Aya::SDLK_LEFTBRACKET;
|
||||
break;
|
||||
case Qt::Key_BracketRight:
|
||||
rbxKey = Aya::SDLK_RIGHTBRACKET;
|
||||
break;
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
rbxKey = Aya::SDLK_RETURN;
|
||||
break;
|
||||
case Qt::Key_Semicolon:
|
||||
rbxKey = Aya::SDLK_SEMICOLON;
|
||||
break;
|
||||
case Qt::Key_QuoteLeft:
|
||||
rbxKey = Aya::SDLK_BACKQUOTE;
|
||||
break;
|
||||
case Qt::Key_Apostrophe:
|
||||
rbxKey = Aya::SDLK_QUOTE;
|
||||
break;
|
||||
case Qt::Key_QuoteDbl:
|
||||
rbxKey = Aya::SDLK_QUOTEDBL;
|
||||
break;
|
||||
case Qt::Key_Backslash:
|
||||
rbxKey = Aya::SDLK_BACKSLASH;
|
||||
break;
|
||||
case Qt::Key_Comma:
|
||||
rbxKey = Aya::SDLK_COMMA;
|
||||
break;
|
||||
case Qt::Key_Period:
|
||||
rbxKey = Aya::SDLK_PERIOD;
|
||||
break;
|
||||
case Qt::Key_Slash:
|
||||
rbxKey = Aya::SDLK_SLASH;
|
||||
break;
|
||||
case Qt::Key_multiply:
|
||||
rbxKey = Aya::SDLK_KP_MULTIPLY;
|
||||
break;
|
||||
case Qt::Key_NumLock:
|
||||
rbxKey = Aya::SDLK_NUMLOCK;
|
||||
break;
|
||||
case Qt::Key_ScrollLock:
|
||||
rbxKey = Aya::SDLK_SCROLLOCK;
|
||||
break;
|
||||
case Qt::Key_Asterisk:
|
||||
rbxKey = Aya::SDLK_ASTERISK;
|
||||
break;
|
||||
case Qt::Key_Plus:
|
||||
rbxKey = Aya::SDLK_PLUS;
|
||||
break;
|
||||
}
|
||||
|
||||
return rbxKey;
|
||||
}
|
||||
|
||||
static Aya::ModCode modifiersToUIModCode(int modifier)
|
||||
{
|
||||
unsigned int modCode = 0;
|
||||
|
||||
if (modifier & Qt::ShiftModifier)
|
||||
{
|
||||
modCode = modCode | Aya::KMOD_LSHIFT;
|
||||
}
|
||||
if (modifier & Qt::ControlModifier)
|
||||
{
|
||||
modCode = modCode | Aya::KMOD_LCTRL;
|
||||
}
|
||||
if (modifier & Qt::AltModifier)
|
||||
{
|
||||
modCode = modCode | Aya::KMOD_LALT;
|
||||
}
|
||||
|
||||
return (Aya::ModCode)modCode;
|
||||
}
|
||||
|
||||
void OgreWidget::sendMouseButtonEvents(QMouseEvent* me, QPoint delta, QPoint position)
|
||||
{
|
||||
Aya::InputObject::UserInputType t;
|
||||
Qt::MouseButton mb = me->button();
|
||||
|
||||
Aya::InputObject::UserInputType type;
|
||||
switch (mb)
|
||||
{
|
||||
case Qt::LeftButton:
|
||||
type = Aya::InputObject::TYPE_MOUSEBUTTON1;
|
||||
break;
|
||||
case Qt::RightButton:
|
||||
type = Aya::InputObject::TYPE_MOUSEBUTTON2;
|
||||
break;
|
||||
case Qt::MiddleButton:
|
||||
type = Aya::InputObject::TYPE_MOUSEBUTTON3;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
OgreWindow* _ogreWindow = qobject_cast<OgreWindow*>(ogreWindow);
|
||||
Aya::InputObject::UserInputState state;
|
||||
switch (me->type())
|
||||
{
|
||||
case QEvent::MouseButtonPress:
|
||||
state = Aya::InputObject::INPUT_STATE_BEGIN;
|
||||
if (_ogreWindow->isShowingAvatarPage)
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
break;
|
||||
case QEvent::MouseButtonRelease:
|
||||
state = Aya::InputObject::INPUT_STATE_END;
|
||||
if (_ogreWindow->isShowingAvatarPage)
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
boost::shared_ptr<Aya::InputObject> inputObject1 = boost::make_shared<Aya::InputObject>(
|
||||
type, state, Aya::Vector3(position.x(), position.y(), 0), Aya::Vector3(delta.x(), delta.y(), 0), appPtr->getDM().get());
|
||||
|
||||
appPtr->sendInputEvent(inputObject1);
|
||||
|
||||
oldMouseButtons = me->buttons();
|
||||
}
|
||||
|
||||
bool OgreWidget::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == Aya::TYPE_FUNCTION_MARSHALLER)
|
||||
{
|
||||
if (disableClosures)
|
||||
return false;
|
||||
setVisible(true);
|
||||
|
||||
Aya::FunctionMarshaller::GetWindow()->handleAppEvent((static_cast<Aya::FunctionMarshallerEvent*>(event))->getClosure());
|
||||
firstClosure = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!firstClosure)
|
||||
return false;
|
||||
|
||||
OgreWindow* _ogreWindow = dynamic_cast<OgreWindow*>(ogreWindow);
|
||||
if (!_ogreWindow)
|
||||
return false;
|
||||
|
||||
if (!isVisible())
|
||||
return false;
|
||||
|
||||
GrayChatBar* textInput = dynamic_cast<OgreWindow*>(ogreWindow)->getTextInput();
|
||||
|
||||
if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
|
||||
{
|
||||
QKeyEvent* ke = static_cast<QKeyEvent*>(event);
|
||||
|
||||
Aya::InputObject::UserInputState state;
|
||||
switch (ke->type())
|
||||
{
|
||||
case QEvent::KeyPress:
|
||||
state = Aya::InputObject::INPUT_STATE_BEGIN;
|
||||
break;
|
||||
case QEvent::KeyRelease:
|
||||
state = Aya::InputObject::INPUT_STATE_END;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ke->key() == Qt::Key_Slash && textInput->isVisible())
|
||||
{
|
||||
textInput->setFocus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ke->key() == Qt::Key_Tab && ke->modifiers() & Qt::AltModifier)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ke->isAutoRepeat())
|
||||
return false;
|
||||
|
||||
|
||||
int keyCode = ke->key() | translateKeyModifiers(ke->modifiers(), ke->text());
|
||||
std::string text = ke->text().toStdString();
|
||||
boost::shared_ptr<Aya::InputObject> inputObject =
|
||||
boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_KEYBOARD, state, keyCodeTOUIKeyCode(ke->key()),
|
||||
modifiersToUIModCode(ke->modifiers()), (text.length() > 0) ? text.c_str()[0] : NULL, appPtr->getDM().get());
|
||||
|
||||
appPtr->sendInputEvent(inputObject);
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease)
|
||||
{
|
||||
QMouseEvent* me = static_cast<QMouseEvent*>(event);
|
||||
|
||||
QPoint delta = me->pos() - lastPosition;
|
||||
|
||||
lastPosition = me->pos();
|
||||
QPoint position = me->pos();
|
||||
|
||||
sendMouseButtonEvents(me, delta, position);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (event->type() == QEvent::MouseMove)
|
||||
{
|
||||
QMouseEvent* me = static_cast<QMouseEvent*>(event);
|
||||
|
||||
QPoint savedLastPosition = lastPosition;
|
||||
QPoint delta = me->pos() - lastPosition;
|
||||
|
||||
if (delta.isNull())
|
||||
return true;
|
||||
|
||||
float scale = Aya::GameBasicSettings::singleton().getMouseSensitivity();
|
||||
lastPositionFrac += delta * scale;
|
||||
QPoint newPos = lastPosition + lastPositionFrac;
|
||||
delta = newPos - lastPosition;
|
||||
lastPositionFrac -= delta;
|
||||
|
||||
lastPosition = newPos;
|
||||
QPoint position = me->pos();
|
||||
|
||||
QSize _size = size();
|
||||
QPoint center = QPoint(_size.width() / 2, _size.height() / 2);
|
||||
|
||||
if (Aya::UserInputService* userInputService = Aya::ServiceProvider::find<Aya::UserInputService>(appPtr->getDM().get()))
|
||||
{
|
||||
switch (userInputService->getMouseWrapMode())
|
||||
{
|
||||
case Aya::UserInputService::WRAP_NONE: // WRAP_NONE - keep mouse where it is on right click drag
|
||||
{
|
||||
boost::shared_ptr<Aya::InputObject> inputObject = boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_MOUSEMOVEMENT,
|
||||
Aya::InputObject::INPUT_STATE_CHANGE, Aya::Vector3(savedLastPosition.x(), savedLastPosition.y(), 0),
|
||||
Aya::Vector3(delta.x(), delta.y(), 0), appPtr->getDM().get());
|
||||
|
||||
appPtr->sendInputEvent(inputObject);
|
||||
}
|
||||
break;
|
||||
case Aya::UserInputService::WRAP_NONEANDCENTER: // WRAP_NONEANDCENTER - keep mouse at the center of the screen
|
||||
{
|
||||
boost::shared_ptr<Aya::InputObject> inputObject =
|
||||
boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_MOUSEMOVEMENT, Aya::InputObject::INPUT_STATE_CHANGE,
|
||||
Aya::Vector3(center.x(), center.y(), 0), Aya::Vector3(delta.x(), delta.y(), 0), appPtr->getDM().get());
|
||||
|
||||
appPtr->sendInputEvent(inputObject);
|
||||
}
|
||||
break;
|
||||
case Aya::UserInputService::WRAP_AUTO: // WRAP_AUTO - control wrapping based on whatever... this is where we wrap for fullscreen
|
||||
// chatbox (which has code still inside of UserInputUtil::wrapFullScreen)
|
||||
default:
|
||||
{
|
||||
boost::shared_ptr<Aya::InputObject> inputObject =
|
||||
boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_MOUSEMOVEMENT, Aya::InputObject::INPUT_STATE_CHANGE,
|
||||
Aya::Vector3(position.x(), position.y(), 0), Aya::Vector3(delta.x(), delta.y(), 0), appPtr->getDM().get());
|
||||
|
||||
appPtr->sendInputEvent(inputObject);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
boost::shared_ptr<Aya::InputObject> inputObject2 =
|
||||
boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_MOUSEDELTA, Aya::InputObject::INPUT_STATE_CHANGE,
|
||||
Aya::Vector3(position.x(), position.y(), 0), Aya::Vector3(delta.x(), delta.y(), 0), appPtr->getDM().get());
|
||||
|
||||
appPtr->sendInputEvent(inputObject2);
|
||||
|
||||
if (Aya::UserInputService* userInputService = Aya::ServiceProvider::find<Aya::UserInputService>(appPtr->getDM().get()))
|
||||
{
|
||||
OgreWindow* _ogreWindow = dynamic_cast<OgreWindow*>(ogreWindow);
|
||||
switch (userInputService->getMouseWrapMode())
|
||||
{ // TODO: WRAP_CENTER, WRAP_HYBRID, WRAP_AUTO
|
||||
case Aya::UserInputService::WRAP_NONE:
|
||||
QCursor::setPos(mapToGlobal(savedLastPosition));
|
||||
lastPosition = savedLastPosition;
|
||||
break;
|
||||
case Aya::UserInputService::WRAP_NONEANDCENTER:
|
||||
{
|
||||
QCursor::setPos(mapToGlobal(center));
|
||||
lastPosition = center;
|
||||
}
|
||||
break;
|
||||
case Aya::UserInputService::WRAP_AUTO:
|
||||
default:
|
||||
QCursor::setPos(mapToGlobal(position));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (event->type() == QEvent::Wheel)
|
||||
{
|
||||
QWheelEvent* we = static_cast<QWheelEvent*>(event);
|
||||
QPointF currentPos = we->position();
|
||||
QPoint angleDelta = we->angleDelta();
|
||||
boost::shared_ptr<Aya::InputObject> inputObject =
|
||||
boost::make_shared<Aya::InputObject>(Aya::InputObject::TYPE_MOUSEWHEEL, Aya::InputObject::INPUT_STATE_CHANGE,
|
||||
Aya::Vector3(currentPos.x(), currentPos.y(), angleDelta.y()), Aya::Vector3(0, 0, 0), appPtr->getDM().get());
|
||||
|
||||
appPtr->sendInputEvent(inputObject);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
107
client/player/src/Window.hpp
Normal file
107
client/player/src/Window.hpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
#include "intrusive_ptr_target.hpp"
|
||||
|
||||
#include <QWindow>
|
||||
#include <QWidget>
|
||||
#include <QEvent>
|
||||
#include <QPoint>
|
||||
|
||||
#include "GrayChatBar.hpp"
|
||||
#include "LauncherView.hpp"
|
||||
#include "DataModel/GameBasicSettings.hpp"
|
||||
|
||||
// This will not work unless if it is made by an OgreWindow
|
||||
class OgreWidget : public QWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QPoint lastPosition;
|
||||
QPoint lastPositionFrac;
|
||||
bool firstClosure;
|
||||
bool disableClosures;
|
||||
Qt::MouseButtons oldMouseButtons;
|
||||
QWidget* ogreWindow;
|
||||
|
||||
bool textInputCanGrabFocus;
|
||||
|
||||
void sendMouseButtonEvents(QMouseEvent* event, QPoint delta, QPoint position);
|
||||
void onChatBarEnteredText(const QString& text);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
public:
|
||||
explicit OgreWidget(QWidget* ogreWindow, QScreen* parent);
|
||||
|
||||
bool event(QEvent* event);
|
||||
QWidget* getOgreWindow()
|
||||
{
|
||||
return ogreWindow;
|
||||
}
|
||||
|
||||
|
||||
bool setClosuresDisabled(bool b)
|
||||
{
|
||||
disableClosures = b;
|
||||
};
|
||||
QPaintEngine* paintEngine() const
|
||||
{
|
||||
return nullptr; // Disable QPainter
|
||||
}
|
||||
};
|
||||
|
||||
class OgreWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QWidget* myOgreWidgetContainer;
|
||||
OgreWidget* myOgreWidget;
|
||||
GrayChatBar* textInput;
|
||||
LauncherView* launcherView;
|
||||
|
||||
std::thread rccThread;
|
||||
std::atomic<bool> runRcc;
|
||||
|
||||
|
||||
void launchGame(const QString& gameConfiguration, const int& virtualVersion);
|
||||
void stopAvatarView();
|
||||
void startAvatarView();
|
||||
void startRcc(const QString& serverConfiguration, int port, int virtualVersion, bool forceVirtualVersion);
|
||||
void wearAsset(int64_t assetId, const QString& category);
|
||||
|
||||
static void rccThreadProc(std::atomic<bool>& runAtomic, std::string password, OgreWindow* window, int port, int virtualVersion);
|
||||
|
||||
virtual void closeEvent(QCloseEvent* ev);
|
||||
|
||||
public:
|
||||
explicit OgreWindow(QWidget* parent);
|
||||
~OgreWindow();
|
||||
void shutdownRcc();
|
||||
void closeGame(); // called by view gameverbs
|
||||
|
||||
OgreWidget* getOgreWidget()
|
||||
{
|
||||
return myOgreWidget;
|
||||
};
|
||||
|
||||
QWidget* getOgreWidgetContainer()
|
||||
{
|
||||
return myOgreWidgetContainer;
|
||||
};
|
||||
GrayChatBar* getTextInput()
|
||||
{
|
||||
return textInput;
|
||||
}
|
||||
LauncherView* getLauncherView()
|
||||
{
|
||||
return launcherView;
|
||||
}
|
||||
|
||||
bool event(QEvent* event);
|
||||
void changeEvent(QEvent* event);
|
||||
void enterWidget();
|
||||
void hideOgreWidget();
|
||||
bool isShowingAvatarPage = false;
|
||||
|
||||
void handleVirtualVersionChange(Aya::GameBasicSettings::VirtualVersion version);
|
||||
};
|
||||
109
client/player/src/main.cpp
Normal file
109
client/player/src/main.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "Application.hpp"
|
||||
#include "InitializationError.hpp"
|
||||
#include "FunctionMarshaller.hpp"
|
||||
#include "Utility/Statistics.hpp"
|
||||
#include "DataModel/FastLogSettings.hpp"
|
||||
#include "Utility/MicrophoneInput.hpp"
|
||||
#include "Utility/CefIntegration.hpp"
|
||||
#include "CoordinateFrame.hpp"
|
||||
#include <boost/format.hpp>
|
||||
#include <stdlib.h>
|
||||
#include <QSurfaceFormat>
|
||||
#include <QApplication>
|
||||
#include "Window.hpp"
|
||||
|
||||
Aya::Application* appPtr;
|
||||
QApplication* qtAppPtr;
|
||||
OgreWindow* qtOgreWidget;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
qputenv("QT_XCB_GL_INTEGRATION", "none");
|
||||
#ifdef AYA_TEST_BUILD
|
||||
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--remote-debugging-port=9222 --remote-allow-origins=*");
|
||||
#endif
|
||||
|
||||
QApplication qtApp(argc, argv);
|
||||
|
||||
qtAppPtr = &qtApp;
|
||||
QApplication::setApplicationName(AYA_PROJECT_NAME);
|
||||
Aya::Application app;
|
||||
appPtr = &app;
|
||||
|
||||
app.loadAppSettings();
|
||||
|
||||
try
|
||||
{
|
||||
app.parseCommandLine(argc, argv);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
printf("app.parseCommandLine: %s\n", e.what());
|
||||
}
|
||||
|
||||
// need client settings here before we create window
|
||||
std::string clientSettingsString;
|
||||
FetchClientSettingsData(CLIENT_APP_SETTINGS_STRING, CLIENT_SETTINGS_API_KEY, &clientSettingsString);
|
||||
// Apply client settings
|
||||
LoadClientSettingsFromString(CLIENT_APP_SETTINGS_STRING, clientSettingsString, &Aya::ClientAppSettings::singleton());
|
||||
|
||||
OgreWindow widget(NULL);
|
||||
widget.setFocus();
|
||||
widget.show();
|
||||
widget.setWindowTitle("Aya");
|
||||
|
||||
qtOgreWidget = &widget;
|
||||
|
||||
try
|
||||
{
|
||||
app.Initialize(widget.getOgreWidget());
|
||||
}
|
||||
catch (const Aya::initialization_error& e)
|
||||
{
|
||||
printf("Error initailzie: %s", e.what());
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if defined(ENABLE_CHROMIUM_FRAMES)
|
||||
if (app.getVm().count("nochromium") == 0)
|
||||
{
|
||||
Aya::CefIntegration::initialize();
|
||||
}
|
||||
#endif
|
||||
#if defined(ENABLE_VOICE_CHAT)
|
||||
Aya::MicrophoneInput::initialize();
|
||||
#endif
|
||||
|
||||
int r = -1;
|
||||
try
|
||||
{
|
||||
if (app.isGameReady())
|
||||
widget.enterWidget();
|
||||
else
|
||||
widget.hideOgreWidget();
|
||||
r = qtApp.exec();
|
||||
}
|
||||
catch (std::runtime_error const& exp)
|
||||
{
|
||||
printf("Error: %s\n", exp.what());
|
||||
}
|
||||
|
||||
if (r != 0)
|
||||
{
|
||||
printf("Error: %d", r);
|
||||
}
|
||||
|
||||
printf("Shutting down...\n");
|
||||
|
||||
widget.shutdownRcc();
|
||||
app.shutdown();
|
||||
|
||||
#if defined(ENABLE_VOICE_CHAT)
|
||||
Aya::MicrophoneInput::shutdown();
|
||||
#endif
|
||||
#if defined(ENABLE_CHROMIUM_FRAMES)
|
||||
Aya::CefIntegration::shutdown();
|
||||
#endif
|
||||
|
||||
return r;
|
||||
}
|
||||
Reference in New Issue
Block a user