mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
GEEKING
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// CMCrashReporter.h
|
||||
// CMCrashReporter-App
|
||||
//
|
||||
// Created by Jelle De Laender on 20/01/08.
|
||||
// Copyright 2008 CodingMammoth. All rights reserved.
|
||||
// Copyright 2010 CodingMammoth. Revision. All rights reserved.
|
||||
//
|
||||
// Current version: 1.1 (September 2010)
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "CMCrashReporterGlobal.h"
|
||||
|
||||
@interface CMCrashReporter : NSObject {
|
||||
|
||||
}
|
||||
+ (void)check;
|
||||
+ (NSArray *)getReports;
|
||||
@end
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// CMCrashReporter.m
|
||||
// CMCrashReporter-App
|
||||
//
|
||||
// Created by Jelle De Laender on 20/01/08.
|
||||
// Copyright 2008 CodingMammoth. All rights reserved.
|
||||
// Copyright 2010 CodingMammoth. Revision. All rights reserved.
|
||||
//
|
||||
// Modified by ROBLOX 2011
|
||||
|
||||
#import "CMCrashReporter.h"
|
||||
|
||||
@implementation CMCrashReporter
|
||||
|
||||
+ (void) submitFile:(NSString *)file
|
||||
{
|
||||
NSMutableData* regData = [[NSMutableData alloc] initWithCapacity:100];
|
||||
NSString* s = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
|
||||
[regData appendData:[s dataUsingEncoding:NSASCIIStringEncoding]];
|
||||
|
||||
NSURL *url = [NSURL URLWithString:[CMCrashReporterGlobal crashReportURL]];
|
||||
NSMutableURLRequest* post = [NSMutableURLRequest requestWithURL:url];
|
||||
[post setHTTPMethod: @"POST"];
|
||||
[post setHTTPBody:regData];
|
||||
|
||||
NSURLResponse* response;
|
||||
NSError* error;
|
||||
#warning TODO: Async
|
||||
NSData* result = [NSURLConnection sendSynchronousRequest:post returningResponse:&response error:&error];
|
||||
NSString *res = [[[NSString alloc] initWithData:result encoding:NSASCIIStringEncoding] autorelease];
|
||||
NSString *compare = [res stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
BOOL success = ([compare isEqualToString:@"ok"]);
|
||||
if (success)
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:file])
|
||||
{
|
||||
NSError *error = nil;
|
||||
[[NSFileManager defaultManager] removeItemAtPath:file error:&error];
|
||||
}
|
||||
}
|
||||
|
||||
+(void)check
|
||||
{
|
||||
#warning TODO: Turn on CMCrashReporter
|
||||
#if 0
|
||||
NSUserDefaults *defaults = [[NSUserDefaultsController sharedUserDefaultsController] defaults];
|
||||
if ([CMCrashReporterGlobal checkOnCrashes] && ![defaults boolForKey:@"CMCrashReporterIgnoreCrashes"]) {
|
||||
NSArray *reports = [CMCrashReporter getReports];
|
||||
if ([reports count] > 0) {
|
||||
int max = MIN([CMCrashReporterGlobal numberOfMaximumReports],[reports count]);
|
||||
|
||||
if (max == 0) max = [reports count];
|
||||
|
||||
for (int i = 0; i < max; i++)
|
||||
[CMCrashReporter submitFile:[reports objectAtIndex:i]];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
+(NSArray *)getReports
|
||||
{
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
|
||||
if ([CMCrashReporterGlobal isRunningLeopard]) {
|
||||
// (Snow) Leopard format is AppName_Year_Month_Day
|
||||
NSString *file;
|
||||
NSString *path = [@"~/Library/Logs/CrashReporter/" stringByExpandingTildeInPath];
|
||||
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
||||
|
||||
NSMutableArray *array = [NSMutableArray array];
|
||||
while (file = [dirEnum nextObject])
|
||||
if ([file hasPrefix:[CMCrashReporterGlobal appName]])
|
||||
[array addObject:[[NSString stringWithFormat:@"~/Library/Logs/CrashReporter/%@",file] stringByExpandingTildeInPath]];
|
||||
|
||||
return array;
|
||||
} else {
|
||||
// Tiger Formet is AppName.crash.log
|
||||
NSString *path = [[NSString stringWithFormat:@"~/Library/Logs/CrashReporter/%@.crash.log",[CMCrashReporterGlobal appName]] stringByExpandingTildeInPath];
|
||||
if ([fileManager fileExistsAtPath:path]) return [NSArray arrayWithObject:path];
|
||||
else return nil;
|
||||
}
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// CMCrashReporter.m
|
||||
// CMCrashReporter-App
|
||||
//
|
||||
// Created by Jelle De Laender on 20/01/08.
|
||||
// Copyright 2008 CodingMammoth. All rights reserved.
|
||||
// Copyright 2010 CodingMammoth. Revision. All rights reserved.
|
||||
//
|
||||
// Modified by ROBLOX 2011
|
||||
|
||||
void uploadAndDeletFileAsync(const char* url, const char* file);
|
||||
|
||||
#import "CMCrashReporter.h"
|
||||
|
||||
@implementation CMCrashReporter
|
||||
|
||||
+ (void) submitFile:(NSString *)file
|
||||
{
|
||||
NSLog(@"submitFile: %@", file);
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:file] == NO)
|
||||
return;
|
||||
|
||||
NSString* base = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"RbxBaseUrl"];
|
||||
NSString* crashURL = [base stringByAppendingString:@"error/dmp.ashx"];
|
||||
uploadAndDeletFileAsync(
|
||||
[crashURL cStringUsingEncoding:NSUTF8StringEncoding],
|
||||
[file cStringUsingEncoding:NSUTF8StringEncoding]
|
||||
);
|
||||
}
|
||||
|
||||
+(void)check
|
||||
{
|
||||
NSUserDefaults *defaults = [[NSUserDefaultsController sharedUserDefaultsController] defaults];
|
||||
if ([CMCrashReporterGlobal checkOnCrashes] && ![defaults boolForKey:@"CMCrashReporterIgnoreCrashes"]) {
|
||||
NSArray *reports = [CMCrashReporter getReports];
|
||||
if ([reports count] > 0) {
|
||||
int max = MIN([CMCrashReporterGlobal numberOfMaximumReports],[reports count]);
|
||||
|
||||
if (max == 0) max = [reports count];
|
||||
|
||||
int i;
|
||||
for (i = 0; i < max; i++)
|
||||
[CMCrashReporter submitFile:[reports objectAtIndex:i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+(NSArray *)getReports
|
||||
{
|
||||
// (Snow) Leopard format is AppName_Year_Month_Day
|
||||
NSString *prefix = @"RobloxPlayer";
|
||||
NSMutableArray *array = [NSMutableArray array];
|
||||
|
||||
{
|
||||
NSString *path = [@"~/Library/Logs/CrashReporter/" stringByExpandingTildeInPath];
|
||||
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
||||
|
||||
NSString *file;
|
||||
while (file = [dirEnum nextObject])
|
||||
if ([file hasPrefix:prefix])
|
||||
[array addObject:[[NSString stringWithFormat:@"~/Library/Logs/CrashReporter/%@",file] stringByExpandingTildeInPath]];
|
||||
}
|
||||
|
||||
if (false)
|
||||
{
|
||||
NSString *path = [@"~/Library/Logs/DiagnosticReports/" stringByExpandingTildeInPath];
|
||||
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
||||
|
||||
NSString *file;
|
||||
while (file = [dirEnum nextObject])
|
||||
if ([file hasPrefix:prefix])
|
||||
[array addObject:[[NSString stringWithFormat:@"~/Library/Logs/DiagnosticReports/%@",file] stringByExpandingTildeInPath]];
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// CMCrashReporterGlobal.h
|
||||
// CMCrashReporter-App
|
||||
//
|
||||
// Created by Jelle De Laender on 20/01/08.
|
||||
// Copyright 2008 CodingMammoth. All rights reserved.
|
||||
// Copyright 2010 CodingMammoth. Revision. All rights reserved.
|
||||
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
@interface CMCrashReporterGlobal : NSObject {
|
||||
|
||||
}
|
||||
|
||||
+ (NSString *)appName;
|
||||
+ (NSString *)version;
|
||||
|
||||
+ (BOOL)checkOnCrashes;
|
||||
|
||||
+ (NSString *)osVersion;
|
||||
|
||||
+ (int)numberOfMaximumReports;
|
||||
@end
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// CMCrashReporterGlobal.m
|
||||
// CMCrashReporter-App
|
||||
//
|
||||
// Created by Jelle De Laender on 20/01/08.
|
||||
// Copyright 2008 CodingMammoth. All rights reserved.
|
||||
// Copyright 2010 CodingMammoth. Revision. All rights reserved.
|
||||
//
|
||||
// Modified by ROBLOX 2011
|
||||
|
||||
|
||||
#import "CMCrashReporterGlobal.h"
|
||||
|
||||
|
||||
@implementation CMCrashReporterGlobal
|
||||
|
||||
+ (NSString *)appName
|
||||
{
|
||||
return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
|
||||
}
|
||||
|
||||
+ (NSString *)version
|
||||
{
|
||||
return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
|
||||
}
|
||||
|
||||
+ (int)numberOfMaximumReports {
|
||||
if (! [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CMMaxReports"])
|
||||
return 0;
|
||||
|
||||
return [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CMMaxReports"] intValue];
|
||||
}
|
||||
|
||||
|
||||
+ (BOOL)checkOnCrashes
|
||||
{
|
||||
// Integration for later
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSString *)osVersion
|
||||
{
|
||||
return [[NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]
|
||||
objectForKey:@"ProductVersion"];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,152 @@
|
||||
// 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
|
||||
|
||||
#include "FunctionMarshaller.h"
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
#include "util/StandardOut.h"
|
||||
#include "rbx/boost.hpp"
|
||||
#include "Roblox.h"
|
||||
|
||||
using namespace RBX;
|
||||
|
||||
FunctionMarshaller::FunctionMarshaller(DWORD threadID)
|
||||
:refCount(0)
|
||||
{
|
||||
this->threadID = threadID;
|
||||
}
|
||||
|
||||
FunctionMarshaller::~FunctionMarshaller()
|
||||
{
|
||||
boost::function<void()>* f;
|
||||
while (asyncCalls.pop_if_present(f))
|
||||
delete f;
|
||||
|
||||
RBXASSERT(threadID == GetCurrentThreadId());
|
||||
|
||||
#ifdef _DEBUG
|
||||
{
|
||||
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
|
||||
RBXASSERT (refCount==0);
|
||||
// Nobody is using this window
|
||||
RBXASSERT (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;
|
||||
RBX::CEvent *pWaitEvent = closure->waitEvent;
|
||||
try
|
||||
{
|
||||
boost::function<void()>* pF = closure->f;
|
||||
(*pF)();
|
||||
|
||||
delete pF;
|
||||
delete closure;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
StandardOut::singleton()->print(RBX::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;
|
||||
}
|
||||
|
||||
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;
|
||||
Roblox::sendAppEvent(pClosure);
|
||||
}
|
||||
}
|
||||
|
||||
void FunctionMarshaller::Submit(boost::function<void()> job)
|
||||
{
|
||||
Closure *pClosure = new Closure;
|
||||
pClosure->f = new boost::function<void()>(job);
|
||||
pClosure->waitEvent = NULL;
|
||||
Roblox::postAppEvent(pClosure);
|
||||
}
|
||||
|
||||
void FunctionMarshaller::ProcessMessages()
|
||||
{
|
||||
Roblox::processAppEvents();
|
||||
}
|
||||
|
||||
/*
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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
|
||||
|
||||
#ifdef _WIN32
|
||||
// This code is platform-specific
|
||||
#error
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include "rbx/threadsafe.h"
|
||||
#include "rbx/CEvent.h"
|
||||
|
||||
namespace RBX {
|
||||
|
||||
// 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)
|
||||
|
||||
rbx::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;
|
||||
RBX::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);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* GameVerbs.cpp
|
||||
* MacClient
|
||||
*
|
||||
* Created by Tony on 2/2/11.
|
||||
* Copyright 2011 __MyCompanyName__. All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GameVerbs.h"
|
||||
#include "RobloxView.h"
|
||||
|
||||
LeaveGameVerb::LeaveGameVerb(RobloxView *pRobloxView, VerbContainer* container) :
|
||||
Verb(container, "Exit")
|
||||
,robloxView(pRobloxView)
|
||||
{
|
||||
}
|
||||
|
||||
void LeaveGameVerb::doIt(RBX::IDataState* dataState)
|
||||
{
|
||||
if (robloxView)
|
||||
{
|
||||
robloxView->leaveGame();
|
||||
}
|
||||
}
|
||||
|
||||
ShutdownClientVerb::ShutdownClientVerb(RobloxView *pRobloxView, VerbContainer* container) :
|
||||
Verb( container, "ShutdownClient")
|
||||
,robloxView(pRobloxView)
|
||||
{
|
||||
}
|
||||
|
||||
void ShutdownClientVerb::doIt(RBX::IDataState* dataState)
|
||||
{
|
||||
if (robloxView)
|
||||
{
|
||||
robloxView->shutdownClient();
|
||||
}
|
||||
}
|
||||
|
||||
ToggleFullscreenVerb::ToggleFullscreenVerb(RobloxView *pRobloxView, VerbContainer* container, VideoControl* videoControl) :
|
||||
Verb(container, "ToggleFullScreen")
|
||||
, videoControl(videoControl)
|
||||
,robloxView(pRobloxView)
|
||||
{}
|
||||
|
||||
void ToggleFullscreenVerb::doIt(RBX::IDataState* dataState)
|
||||
{
|
||||
if (robloxView)
|
||||
{
|
||||
robloxView->toggleFullScreen();
|
||||
}
|
||||
}
|
||||
|
||||
bool ToggleFullscreenVerb::isEnabled() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* GameVerbs.h
|
||||
* MacClient
|
||||
*
|
||||
* Created by Tony on 2/2/11.
|
||||
* Copyright 2011 __MyCompanyName__. All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "v8tree/Verb.h"
|
||||
using namespace RBX;
|
||||
|
||||
class LeaveGameVerb : public RBX::Verb
|
||||
{
|
||||
class RobloxView *robloxView;
|
||||
|
||||
public :
|
||||
|
||||
|
||||
LeaveGameVerb(class RobloxView *pRobloxView, VerbContainer* container);
|
||||
|
||||
virtual void doIt(RBX::IDataState* dataState);
|
||||
|
||||
};
|
||||
|
||||
class ShutdownClientVerb : public RBX::Verb
|
||||
{
|
||||
class RobloxView *robloxView;
|
||||
|
||||
public :
|
||||
|
||||
ShutdownClientVerb( class RobloxView *pRobloxView, VerbContainer* container );
|
||||
|
||||
virtual void doIt(RBX::IDataState* dataState);
|
||||
};
|
||||
|
||||
class ToggleFullscreenVerb : public RBX::Verb
|
||||
{
|
||||
|
||||
private:
|
||||
class VideoControl* videoControl;
|
||||
class RobloxView *robloxView;
|
||||
|
||||
public:
|
||||
|
||||
ToggleFullscreenVerb(class RobloxView *pRobloxView, VerbContainer* container, VideoControl* videoControl);
|
||||
virtual void doIt(RBX::IDataState* dataState);
|
||||
virtual bool isEnabled() const;
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// RBXWindow.h
|
||||
// MacClient
|
||||
//
|
||||
// Created by Ben Tkacheff on 9/30/13.
|
||||
//
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface RBXWindow : NSWindow
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// RBXWindow.m
|
||||
// MacClient
|
||||
//
|
||||
// Created by Ben Tkacheff on 9/30/13.
|
||||
//
|
||||
//
|
||||
|
||||
#import "RBXWindow.h"
|
||||
|
||||
@implementation RBXWindow
|
||||
|
||||
// necessary override for when game window becomes borderless (otherwise it is never key)
|
||||
- (BOOL)canBecomeKeyWindow
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// RbxWebView.h
|
||||
// MacClient
|
||||
//
|
||||
// Created by Ben Tkacheff on 9/25/13.
|
||||
//
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ROBLOX_APP_SHUTDOWN_CODE_LEAVE_GAME,
|
||||
ROBLOX_APP_SHUTDOWN_CODE_WINDOW_CLOSE,
|
||||
ROBLOX_APP_SHUTDOWN_CODE_QUIT
|
||||
} RobloxAppShutdownCode;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GAMETYPE_PLAY,
|
||||
GAMETYPE_PLAY_PROTOCOL
|
||||
} RobloxGameType;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
RETRY,
|
||||
GAME_FULL,
|
||||
USER_LEFT,
|
||||
} RequestPlaceInfoResult;
|
||||
|
||||
@interface RbxWebView : NSWindowController
|
||||
{
|
||||
WebView *webView;
|
||||
}
|
||||
|
||||
-(id) initWithWindowNibName:(NSString*) name;
|
||||
-(void) dealloc;
|
||||
|
||||
@property (weak) IBOutlet WebView *webView;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// RbxWebView.m
|
||||
// MacClient
|
||||
//
|
||||
// Created by Ben Tkacheff on 9/25/13.
|
||||
//
|
||||
//
|
||||
|
||||
#import "RbxWebView.h"
|
||||
#import "RobloxPlayerAppDelegate.h"
|
||||
|
||||
#include "RobloxView.h"
|
||||
#include "v8datamodel/GuiService.h"
|
||||
#include "v8datamodel/Datamodel.h"
|
||||
|
||||
FASTSTRING(ClientExternalBrowserUserAgent)
|
||||
|
||||
@implementation RbxWebView
|
||||
|
||||
@synthesize webView;
|
||||
|
||||
-(id) initWithWindowNibName:(NSString*) name
|
||||
{
|
||||
if(self = [super initWithWindowNibName:name])
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(windowWillClose:)
|
||||
name:NSWindowWillCloseNotification
|
||||
object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
|
||||
static void doInformDataModelUrlWindowClose(RBX::DataModel* dataModel)
|
||||
{
|
||||
if (RBX::GuiService* guiService = dataModel->find<RBX::GuiService>())
|
||||
guiService->urlWindowClosed();
|
||||
}
|
||||
|
||||
- (void) informDataModelUrlWindowClose:(RBX::DataModel*) dataModel
|
||||
{
|
||||
if(dataModel)
|
||||
dataModel->submitTask(boost::bind(&doInformDataModelUrlWindowClose,dataModel), RBX::DataModelJob::Write);
|
||||
}
|
||||
|
||||
- (void)windowDidLoad
|
||||
{
|
||||
NSString *versionString;
|
||||
NSDictionary * sv = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
|
||||
versionString = [sv objectForKey:@"ProductVersion"];
|
||||
|
||||
[self.webView setCustomUserAgent:[NSString stringWithFormat:@"Mozilla/5.0 (Macintosh; Intel Mac OS X %@) AppleWebKit/536.25 (KHTML, like Gecko) %s",versionString, FString::ClientExternalBrowserUserAgent.c_str()]];
|
||||
}
|
||||
|
||||
|
||||
- (void)windowWillClose:(NSNotification *)notification
|
||||
{
|
||||
NSWindow* closingWindow = (NSWindow*)notification.object;
|
||||
if (closingWindow && closingWindow == self.window )
|
||||
{
|
||||
RobloxView* robloxView = NULL;
|
||||
|
||||
RobloxPlayerAppDelegate *appDelegate = (RobloxPlayerAppDelegate*)[NSApp delegate];
|
||||
if(!appDelegate)
|
||||
return;
|
||||
if(!appDelegate.robloxView)
|
||||
return;
|
||||
|
||||
robloxView = appDelegate.robloxView;
|
||||
|
||||
if (robloxView)
|
||||
{
|
||||
[self informDataModelUrlWindowClose:robloxView->getDataModel().get()];
|
||||
}
|
||||
|
||||
[self.window resignFirstResponder];
|
||||
[self.window resignMainWindow];
|
||||
|
||||
[self.webView setResourceLoadDelegate:nil];
|
||||
|
||||
[self autorelease];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,169 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4514" systemVersion="13A603" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
|
||||
<dependencies>
|
||||
<deployment defaultVersion="1060" identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4514"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
<connections>
|
||||
<outlet property="delegate" destination="847" id="849"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application"/>
|
||||
<customObject id="371" customClass="NSFontManager"/>
|
||||
<menu title="Main Menu" systemMenu="main" id="29">
|
||||
<items>
|
||||
<menuItem title="Roblox" id="56">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Roblox" systemMenu="apple" id="57">
|
||||
<items>
|
||||
<menuItem title="About Roblox" id="58">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="orderFrontStandardAboutPanel:" target="-2" id="142"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="236"/>
|
||||
<menuItem title="Hide Roblox" keyEquivalent="h" id="134">
|
||||
<connections>
|
||||
<action selector="hide:" target="-1" id="367"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Hide Others" keyEquivalent="h" id="145">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="hideOtherApplications:" target="-1" id="368"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="149"/>
|
||||
<menuItem title="Quit Roblox" keyEquivalent="q" id="136">
|
||||
<connections>
|
||||
<action selector="terminate:" target="-1" id="369"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Edit" id="1014">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Edit" id="1015">
|
||||
<items>
|
||||
<menuItem title="Undo" keyEquivalent="z" id="1016">
|
||||
<connections>
|
||||
<action selector="undo:" target="-1" id="1082"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Redo" keyEquivalent="Z" id="1017">
|
||||
<connections>
|
||||
<action selector="redo:" target="-1" id="1076"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="1018"/>
|
||||
<menuItem title="Cut" keyEquivalent="x" id="1019">
|
||||
<connections>
|
||||
<action selector="cut:" target="-1" id="1062"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy" keyEquivalent="c" id="1020">
|
||||
<connections>
|
||||
<action selector="copy:" target="-1" id="1061"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste" keyEquivalent="v" id="1021">
|
||||
<connections>
|
||||
<action selector="paste:" target="-1" id="1067"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Delete" id="1023">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="delete:" target="-1" id="1077"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Select All" keyEquivalent="a" id="1024">
|
||||
<connections>
|
||||
<action selector="selectAll:" target="-1" id="1080"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Test" id="83">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Test" id="81">
|
||||
<items>
|
||||
<menuItem title="Open…" keyEquivalent="o" id="72">
|
||||
<connections>
|
||||
<action selector="openFile:" target="847" id="859"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Crash" id="935">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="crash:" target="-1" id="937"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Window" id="19">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Window" systemMenu="window" id="24">
|
||||
<items>
|
||||
<menuItem title="Minimize" keyEquivalent="m" id="23">
|
||||
<connections>
|
||||
<action selector="performMiniaturize:" target="-1" id="37"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Zoom" id="239">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="performZoom:" target="-1" id="240"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="92"/>
|
||||
<menuItem title="Bring All to Front" id="5">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="arrangeInFront:" target="-1" id="39"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
<window title="Roblox" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" visibleAtLaunch="NO" frameAutosaveName="Main Window" animationBehavior="default" id="843" customClass="RBXWindow">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="300" y="300" width="1000" height="665"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1178"/>
|
||||
<value key="minSize" type="size" width="800" height="600"/>
|
||||
<view key="contentView" id="844">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="665"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<customView id="850" customClass="RobloxOgreView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1000" height="665"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</customView>
|
||||
</subviews>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="847" id="897"/>
|
||||
</connections>
|
||||
</window>
|
||||
<customObject id="847" customClass="RobloxPlayerAppDelegate">
|
||||
<connections>
|
||||
<outlet property="mainMenu" destination="29" id="896"/>
|
||||
<outlet property="mainView" destination="844" id="1116"/>
|
||||
<outlet property="ogreView" destination="850" id="851"/>
|
||||
<outlet property="webPlayer" destination="855" id="856"/>
|
||||
<outlet property="window" destination="843" id="uIW-gx-V8c"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="855" customClass="RobloxWebPlayer"/>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9060" systemVersion="14F1021" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9060"/>
|
||||
<plugIn identifier="com.apple.WebKitIBPlugin" version="9060"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="RbxWebView">
|
||||
<connections>
|
||||
<outlet property="webView" destination="E3k-K2-jx9" id="qr1-ZM-P7H"/>
|
||||
<outlet property="window" destination="tlP-T8-JXC" id="09L-rq-dOQ"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="ROBLOX" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" oneShot="NO" animationBehavior="default" id="tlP-T8-JXC">
|
||||
<windowStyleMask key="styleMask" titled="YES" texturedBackground="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="760" y="331" width="940" height="650"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1058"/>
|
||||
<view key="contentView" id="UPg-5a-BO3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="940" height="650"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<webView id="E3k-K2-jx9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1024" height="650"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<webPreferences key="preferences" defaultFontSize="12" defaultFixedFontSize="12" javaScriptCanOpenWindowsAutomatically="NO">
|
||||
<nil key="identifier"/>
|
||||
</webPreferences>
|
||||
</webView>
|
||||
</subviews>
|
||||
</view>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
Binary file not shown.
@@ -0,0 +1,217 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Roblox</title>
|
||||
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
|
||||
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js"
|
||||
type="text/javascript"></script>
|
||||
|
||||
</head>
|
||||
<body id="BODY1" style="padding: 0; margin: 0">
|
||||
<!-- Main -->
|
||||
<div id="Main" style="position: absolute; top: 0; left: 0; bottom: 0; right: 0; margin-bottom: 40px;
|
||||
margin-right: 200px; padding: 0 1px 1px 0">
|
||||
</div>
|
||||
<!-- TODO: nuke the Panel div when we don't need it for debugging -->
|
||||
<div id="Panel" style="position: absolute; top: 0; right: 0; bottom: 0; width: 200px;
|
||||
margin-bottom: 40px; overflow: auto; border-left-style: solid; border-width: 1px">
|
||||
<div id="outputPanel" style="width: 100%">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var traces = new Array();
|
||||
var traceIndex = 19;
|
||||
function trace(message) {
|
||||
var div = $('<div></div>')
|
||||
div.text(message);
|
||||
div.appendTo(outputPanel);
|
||||
|
||||
if (traces.length < 20)
|
||||
traces.push(div);
|
||||
else {
|
||||
traceIndex = traceIndex + 1;
|
||||
if (traceIndex == 20)
|
||||
traceIndex = 0;
|
||||
traces[traceIndex].remove();
|
||||
traces[traceIndex] = div;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</div>
|
||||
<!-- Taskbar -->
|
||||
<div id="Taskbar" style="background-color: Window; padding: 5px; height: 30px; position: absolute;
|
||||
left: 0; right: 0; bottom: 0; position: absolute; bottom: 0; left: 0; border-width: 1px;
|
||||
border-top-style: solid">
|
||||
<div id="CommonButtons" style="float: right; margin: 5px">
|
||||
<button onclick="window.external.IsFullscreen = !window.external.IsFullscreen; return false">
|
||||
Fullscreen</button>
|
||||
<button onclick="focusContent(); window.external.Quit(); return false">
|
||||
Quit</button>
|
||||
</div>
|
||||
<div id="block" style="float: right; background-color: #abc; width: 20px; height: 20px;
|
||||
margin: 5px">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function animateGrow() {
|
||||
$(block).animate({ width: "+=20px" }, { duration: "slow", complete: animateShrink });
|
||||
}
|
||||
function animateShrink() {
|
||||
$(block).animate({ width: "-=20px" }, { duration: "slow", complete: animateGrow });
|
||||
}
|
||||
$(animateGrow);
|
||||
</script>
|
||||
|
||||
<div id="GameButtons" style="display: inline">
|
||||
<button onclick="roblox3D.DoVerb('ReportAbuse'); return false" style="display: inline">
|
||||
Report Abuse</button>
|
||||
<button onclick="showWeb(); return false">
|
||||
Leave Game</button>
|
||||
<div id="FPS" style="display: inline">
|
||||
</div>
|
||||
</div>
|
||||
<div id="WebButtons" style="display: none">
|
||||
<button id="playButton" onclick="play(); return false" style="display: inline">
|
||||
Play</button>
|
||||
<button id="Button2" onclick="createView(100); return false" style="display: inline">
|
||||
CreateView 100</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Scripts -->
|
||||
|
||||
<script type="text/javascript">
|
||||
var content;
|
||||
|
||||
function focusContent() {
|
||||
// TODO: This code is ugly
|
||||
if (document.getElementById('roblox3D'))
|
||||
document.getElementById('roblox3D').focus();
|
||||
else if (document.getElementById('robloxFrame'))
|
||||
document.getElementById('robloxFrame').focus();
|
||||
}
|
||||
|
||||
var windowed = true;
|
||||
function create3D(callback) {
|
||||
if (content)
|
||||
$(content).remove();
|
||||
$(WebButtons).hide('slow', function() {
|
||||
Main.innerHTML = '<object id="roblox3D" classid="CLSID:D7EB14E2-66D2-4A6E-A50A-666CBE7A7621" width="100%" height="100%"><param name="windowed" value="' + (windowed ? 'true' : 'false') + '"></object>';
|
||||
windowed = !windowed;
|
||||
content = $(roblox3D);
|
||||
|
||||
roblox3D.WhenReady({
|
||||
success: function(result) {
|
||||
focusContent();
|
||||
callback();
|
||||
$(GameButtons).show();
|
||||
},
|
||||
error: showWeb
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createView(count) {
|
||||
if (count == 0)
|
||||
return;
|
||||
create3D(function() {
|
||||
window.setTimeout(function() {
|
||||
trace("CreateView " + count);
|
||||
showWeb(function() {
|
||||
window.setTimeout(function() {
|
||||
createView(count - 1);
|
||||
}, 1000);
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
function getRobloxBrowserUrl() {
|
||||
var iframe = document.getElementById("robloxFrame");
|
||||
return iframe.contentWindow.location.href;
|
||||
}
|
||||
|
||||
// Displays the web browser in an ifram
|
||||
function showWeb(callback) {
|
||||
if (document.getElementById('roblox3D'))
|
||||
roblox3D.ShutDown();
|
||||
if (content)
|
||||
$(content).remove();
|
||||
$(GameButtons).hide('slow', function() {
|
||||
content = $('<iframe id="robloxFrame" width="100%" frameborder="0" />');
|
||||
|
||||
var loaded = false;
|
||||
// Define a callback to handle iframe loads and to update the Play button
|
||||
$(content).load(function() {
|
||||
if (loaded)
|
||||
return;
|
||||
loaded = true;
|
||||
|
||||
$(WebButtons).show('slow');
|
||||
|
||||
if (callback)
|
||||
callback();
|
||||
});
|
||||
|
||||
// TODO: Replace "http://www.watrbx.wtf/" with proper domain
|
||||
$(content).attr('src', "http://www.watrbx.wtf/Games.aspx");
|
||||
$(content).appendTo(Main);
|
||||
|
||||
// Handle resize events:
|
||||
$(content).height($(Main).height());
|
||||
// TODO: This only works if the window resizes. If other elements in the layout changes
|
||||
// then it won't get notified
|
||||
$(window).resize(function() {
|
||||
$(content).height($(Main).height());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function errorAlert(message, stack) {
|
||||
var s = message + '\n' + stack;
|
||||
alert(s);
|
||||
}
|
||||
|
||||
function bindFPS() {
|
||||
roblox3D.Lua("f = ... while true do local render = stats():FindFirstChild('Render') if render then f(render['3D CPU Total']:GetValueString()) end wait(1) end", {
|
||||
args: [function(value) {
|
||||
if (value != '?' && roblox3D.IsWindowless)
|
||||
value = value + " windowless";
|
||||
FPS.innerText = value;
|
||||
} ],
|
||||
error: errorAlert
|
||||
});
|
||||
}
|
||||
|
||||
// Loads a the 3D view and then loads a game based on what was displayed in the iframe
|
||||
function play() {
|
||||
// TODO: getRobloxBrowserUrl().match() to find the place ID
|
||||
// TODO: Replace "http://www.watrbx.wtf/" with proper domain
|
||||
var gameUrl = 'http://www.watrbx.wtf/Asset/?id=1437';
|
||||
|
||||
create3D(function() {
|
||||
bindFPS();
|
||||
return;
|
||||
// TODO: Replace "http://www.watrbx.wtf/" with proper domain
|
||||
// TODO: Replace visit script with multiplayer join (should be done with jquery or json or something)
|
||||
roblox3D.Lua("gameUrl = ... game:Load(gameUrl)", {
|
||||
args: [gameUrl],
|
||||
success: function() {
|
||||
roblox3D.Lua("http://www.watrbx.wtf/game/visit.ashx");
|
||||
},
|
||||
error: function(message, stack) { alert(stack); }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Nuke this when the Panel div goes away
|
||||
window.external.onStandardOut = function(type, message) { trace(message); };
|
||||
|
||||
// Startup:
|
||||
$(play);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Roblox</title>
|
||||
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"></script>
|
||||
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js"
|
||||
type="text/javascript"></script>
|
||||
|
||||
</head>
|
||||
<body id="BODY1" style="padding: 0; margin: 0">
|
||||
<!-- Main -->
|
||||
<!-- TODO: nuke the Panel div when we don't need it for debugging -->
|
||||
<div id="WebButtons">
|
||||
<button id="playButton" onclick="play(); return false" style="display: inline">
|
||||
Play</button>
|
||||
</div>
|
||||
<div id="outputPanel" style="width: 100%">
|
||||
output
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var traces = new Array();
|
||||
var traceIndex = 19;
|
||||
function trace(message) {
|
||||
var div = $('<div></div>')
|
||||
div.text(message);
|
||||
div.appendTo(outputPanel);
|
||||
|
||||
if (traces.length < 20)
|
||||
traces.push(div);
|
||||
else {
|
||||
traceIndex = traceIndex + 1;
|
||||
if (traceIndex == 20)
|
||||
traceIndex = 0;
|
||||
traces[traceIndex].remove();
|
||||
traces[traceIndex] = div;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Scripts -->
|
||||
|
||||
<script type="text/javascript">
|
||||
function play() {
|
||||
trace("playing!");
|
||||
//window.external.StartGame();
|
||||
trace(window.external.helloWorld());
|
||||
trace(window.external.IsRobloxAppIDE);
|
||||
trace(window.external.InstallHost);
|
||||
|
||||
window.external.StartGame(
|
||||
'2D86BB6A7DE07B38CCE1D50A191BAB5FCF43A5BA1FCF1EF9B7A7F775F70EF41A6150828C9A7D61BA1B09326F124D37939899147D8992519A2DB3980A901E14736B3E3AD064BE8EF2BB1A356EC92AF9C4432FAF5BF0F51AA6CEF00210E7D6007DE205D298CB7F65822A0CD52C04515B484318ADDF389345797D33DCAA1B74472159A4BC2F3168067F177E0F5853074F83C335E0C06E0B80F19CE02E954FBDB91828C41DC31700D53E9832D664EB7D719CBBAD708D7882A8513FB6E570D38D9906A5C02C363D24B6FF78D47EF2020FC1A1EB152819',
|
||||
'http://sitetest.watrbx.wtf//Login/Negotiate.ashx',
|
||||
'http://sitetest.watrbx.wtf//Game/visit.ashx?PlaceID=2562360&upload=2562360'
|
||||
);
|
||||
|
||||
trace("played!");
|
||||
}
|
||||
function go() {
|
||||
trace("hi there!");
|
||||
}
|
||||
$(go);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
This program uses the Graphics3D Library ("G3D", http://g3d-cpp.sf.net)
|
||||
|
||||
NONE OF THESE LIBRARIES REQUIRE *THIS* PROGRAM TO BE OPEN SOURCE.
|
||||
|
||||
This program uses the Graphics3D Library ("G3D", http://g3d-cpp.sf.net), which
|
||||
is licensed under the "BSD" Open Source license. The Graphics3D library
|
||||
source code is Copyright © 2000-2004, Morgan McGuire, All rights reserved.
|
||||
|
||||
|
||||
------------------
|
||||
|
||||
|
||||
FMOD Sound System, copyright © Firelight Technologies Pty, Ltd., 1994-2007.
|
||||
|
||||
|
||||
------------------
|
||||
|
||||
|
||||
Roblox uses the Lua language and code. The Lua portion of the product is
|
||||
under the following license ("Software" refers to Lua, not ROBLOX):
|
||||
|
||||
Copyright © 1994Ð2010 Lua.org, PUC-Rio.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
------------------
|
||||
|
||||
|
||||
Google Breakpad:
|
||||
|
||||
Copyright (c) 2006, Google Inc.
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,2 @@
|
||||
Plugin=RenderSystem_GL
|
||||
Plugin=Plugin_ParticleFX
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" id="www-roblox-com">
|
||||
<head>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<p>ROBLOX</p>
|
||||
<button onclick="window.external.Play()">
|
||||
Play</button>
|
||||
<button onclick="window.external.Load()">
|
||||
Load</button>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* RobloxCpp.cpp
|
||||
* MacClient
|
||||
*
|
||||
* Created by Tony on 1/26/11.
|
||||
* Copyright 2011 __MyCompanyName__. All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "Roblox.h"
|
||||
|
||||
#undef max
|
||||
#undef min
|
||||
|
||||
|
||||
#include "v8datamodel/datamodel.h"
|
||||
#include "v8datamodel/workspace.h"
|
||||
#include "v8datamodel/partinstance.h"
|
||||
|
||||
#include "v8datamodel/factoryregistration.h"
|
||||
#include "Util/FileSystem.h"
|
||||
#include "Util/Http.h"
|
||||
#include "Util/Profiling.h"
|
||||
#include "Util/Statistics.h"
|
||||
#include "Util/MD5Hasher.h"
|
||||
#include "util/RobloxGoogleAnalytics.h"
|
||||
#include "v8datamodel/game.h"
|
||||
#include "v8datamodel/GameSettings.h"
|
||||
#include "v8datamodel/DebugSettings.h"
|
||||
#include "v8datamodel/PhysicsSettings.h"
|
||||
#include "v8datamodel/ContentProvider.h"
|
||||
#include "v8datamodel/GlobalSettings.h"
|
||||
#include "script/ScriptContext.h"
|
||||
#include "script/LuaSettings.h"
|
||||
#include "network/api.h"
|
||||
|
||||
#include "rbx/ProcessPerfCounter.h"
|
||||
#include "rbx/Profiler.h"
|
||||
|
||||
#include "Gui/ProfanityFilter.h"
|
||||
|
||||
#include "boost/filesystem.hpp"
|
||||
|
||||
#include "MachineConfiguration.h"
|
||||
#include <string>
|
||||
|
||||
FASTFLAG(GoogleAnalyticsTrackingEnabled)
|
||||
DYNAMIC_LOGGROUP(GoogleAnalyticsTracking)
|
||||
LOGGROUP(Network)
|
||||
LOGGROUP(PlayerShutdownLuaTimeoutSeconds)
|
||||
|
||||
bool Roblox::initialized = false;
|
||||
std::string Roblox::assetFolder;
|
||||
Roblox::RunMode Roblox::runMode = Roblox::RUN_FILE;
|
||||
rbx::signals::scoped_connection messageOutConnection;
|
||||
|
||||
RBX::ClientAppSettings Roblox::appSettings;
|
||||
|
||||
static boost::shared_ptr<RBX::Game> preloadedGame;
|
||||
static boost::mutex preloadedGameMutex;
|
||||
static bool preloadShuttingDown = false;
|
||||
|
||||
static boost::thread releaseGameThread;
|
||||
|
||||
extern "C" {
|
||||
void writeFastLogDumpHelper(const char *fileName, int numEntries)
|
||||
{
|
||||
FLog::WriteFastLogDump(fileName, numEntries);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
static void do_preloadGame(bool isApp)
|
||||
{
|
||||
// Avoid having 2 DataModels open at once
|
||||
// It should be safe, but may as well not play with fire
|
||||
releaseGameThread.join();
|
||||
|
||||
boost::mutex::scoped_lock lock(preloadedGameMutex);
|
||||
if (preloadShuttingDown)
|
||||
return;
|
||||
if (!preloadedGame)
|
||||
{
|
||||
RBX::Time start = RBX::Time::now<RBX::Time::Fast>();
|
||||
preloadedGame.reset(new RBX::SecurePlayerGame(NULL, ::GetBaseURL().c_str()));
|
||||
RBX::Time stop = RBX::Time::now<RBX::Time::Fast>();
|
||||
double secs = (stop - start).seconds();
|
||||
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "Preloaded Game %gsec", secs);
|
||||
}
|
||||
else
|
||||
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "Already preloaded Game");
|
||||
}
|
||||
|
||||
void Roblox::preloadGame(bool isApp)
|
||||
{
|
||||
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "Requesting preload Game");
|
||||
boost::thread(boost::bind(&do_preloadGame, isApp));
|
||||
}
|
||||
|
||||
extern std::string macBundlePath();
|
||||
|
||||
bool Roblox::globalInit(bool isApp)
|
||||
{
|
||||
if (initialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
RBX::Game::globalInit(false);
|
||||
|
||||
std::string fileRobloxPlayer = macBundlePath() + "/Contents/MacOS/RobloxPlayer";
|
||||
|
||||
// hash may have been overridden on command line, otherwise calculate from executable
|
||||
if (RBX::DataModel::hash.length() == 0)
|
||||
{
|
||||
RBX::DataModel::hash = RBX::CollectMd5Hash(fileRobloxPlayer);
|
||||
}
|
||||
|
||||
RBX::ContentProvider::setAssetFolder(assetFolder.c_str());
|
||||
|
||||
// Reset synchronized flags, they should be set by the server
|
||||
FLog::ResetSynchronizedVariablesState();
|
||||
|
||||
{
|
||||
bool useCurl = rand() % 100 < RBX::ClientAppSettings::singleton().GetValueHttpUseCurlPercentageMacClient();
|
||||
FASTLOG1(FLog::Network, "Use CURL = %d", useCurl);
|
||||
RBX::Http::SetUseCurl(useCurl);
|
||||
|
||||
RBX::Http::SetUseStatistics(true);
|
||||
}
|
||||
|
||||
{
|
||||
int lottery = rand() % 100;
|
||||
FASTLOG1(DFLog::GoogleAnalyticsTracking, "Google analytics lottery number = %d", lottery);
|
||||
// initialize google analytics
|
||||
if (FFlag::GoogleAnalyticsTrackingEnabled && (lottery < RBX::ClientAppSettings::singleton().GetValueGoogleAnalyticsLoadPlayer()))
|
||||
{
|
||||
RBX::RobloxGoogleAnalytics::setCanUseAnalytics();
|
||||
|
||||
RBX::RobloxGoogleAnalytics::init(RBX::ClientAppSettings::singleton().GetValueGoogleAnalyticsAccountPropertyIDPlayer(),
|
||||
RBX::ClientAppSettings::singleton().GetValueGoogleAnalyticsThreadPoolMaxScheduleSize());
|
||||
}
|
||||
}
|
||||
|
||||
// must be after globalInit, where http pool is initialized
|
||||
RBX::postMachineConfiguration(::GetBaseURL().c_str(), 0);
|
||||
|
||||
messageOutConnection = RBX::StandardOut::singleton()->messageOut.connect(&onMessageOut);
|
||||
|
||||
RBX::GlobalAdvancedSettings::singleton()->loadState("");
|
||||
{
|
||||
RBX::Security::Impersonator impersonate(RBX::Security::RobloxGameScript_);
|
||||
RBX::GlobalBasicSettings::singleton()->loadState("");
|
||||
}
|
||||
|
||||
RBX::Profiler::onThreadCreate("Main");
|
||||
|
||||
// Initialize the TaskScheduler (after loading configs)
|
||||
RBX::TaskScheduler::singleton().setThreadCount(RBX::TaskSchedulerSettings::singleton().getThreadPoolConfig());
|
||||
|
||||
initialized = true;
|
||||
|
||||
preloadGame(isApp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::shared_ptr<RBX::Game> Roblox::getpreloadedGame(const bool isApp)
|
||||
{
|
||||
boost::mutex::scoped_lock lock(preloadedGameMutex);
|
||||
if (!preloadedGame)
|
||||
{
|
||||
RBX::Time start = RBX::Time::now<RBX::Time::Fast>();
|
||||
preloadedGame.reset(new RBX::SecurePlayerGame(NULL, ::GetBaseURL().c_str()));
|
||||
RBX::Time stop = RBX::Time::now<RBX::Time::Fast>();
|
||||
double secs = (stop - start).seconds();
|
||||
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "Loaded Game %gsec", secs);
|
||||
}
|
||||
boost::shared_ptr<RBX::Game> temp = preloadedGame;
|
||||
preloadedGame.reset();
|
||||
|
||||
if (FLog::PlayerShutdownLuaTimeoutSeconds > 0)
|
||||
temp->getDataModel()->create<RBX::ScriptContext>();
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void releaseGame(boost::shared_ptr<RBX::Game> game)
|
||||
{
|
||||
RBX::GlobalBasicSettings::singleton()->saveState();
|
||||
game->shutdown();
|
||||
}
|
||||
|
||||
void Roblox::relinquishGame(boost::shared_ptr<RBX::Game>& game)
|
||||
{
|
||||
releaseGameThread.join();
|
||||
|
||||
// Defer deletion of DataModel for later (so that we don't block)
|
||||
releaseGameThread = boost::thread(boost::bind(&releaseGame, game));
|
||||
|
||||
game.reset();
|
||||
}
|
||||
|
||||
|
||||
void Roblox::globalShutdown()
|
||||
{
|
||||
{
|
||||
boost::mutex::scoped_lock lock(preloadedGameMutex);
|
||||
preloadShuttingDown = true;
|
||||
if (preloadedGame)
|
||||
preloadedGame->shutdown(); // Don't rely on destructor, in case somebody else holds a reference
|
||||
preloadedGame.reset();
|
||||
}
|
||||
|
||||
// Don't exit until Game has been shut down. Otherwise we might get crashes in static destructors
|
||||
releaseGameThread.join();
|
||||
|
||||
if (!initialized)
|
||||
return;
|
||||
|
||||
RBX::Game::globalExit();
|
||||
messageOutConnection.disconnect();
|
||||
}
|
||||
|
||||
|
||||
void doCrash()
|
||||
{
|
||||
RBXCRASH();
|
||||
}
|
||||
|
||||
void Roblox::testCrash()
|
||||
{
|
||||
boost::shared_ptr<RBX::Instance> i = RBX::Creatable<RBX::Instance>::create<RBX::PartInstance>();
|
||||
i->propertyChangedSignal.connect(boost::bind(&doCrash));
|
||||
i->setName("blah");
|
||||
|
||||
}
|
||||
|
||||
void Roblox::setArgs(const char *gameFolder, const char *runMode)
|
||||
{
|
||||
Roblox::assetFolder = std::string(gameFolder) + "/content/";
|
||||
|
||||
if (strcmp(runMode,"c") == 0){
|
||||
Roblox::runMode = RUN_CLIENT;
|
||||
}
|
||||
else if (strcmp(runMode,"s") == 0){
|
||||
Roblox::runMode = RUN_SERVER;
|
||||
}
|
||||
else {
|
||||
Roblox::runMode = RUN_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void setRobloxArgs(const char *gameFolder, const char *runMode)
|
||||
{
|
||||
Roblox::setArgs(gameFolder, runMode);
|
||||
}
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
void Roblox::onMessageOut(const RBX::StandardOutMessage& message)
|
||||
{
|
||||
switch (message.type)
|
||||
{
|
||||
case RBX::MESSAGE_INFO:
|
||||
printf("INFO: %s\n", message.message.c_str());
|
||||
break;
|
||||
case RBX::MESSAGE_WARNING:
|
||||
printf("WARNING: %s\n", message.message.c_str());
|
||||
break;
|
||||
case RBX::MESSAGE_ERROR:
|
||||
printf("ERROR: %s\n", message.message.c_str());
|
||||
break;
|
||||
default:
|
||||
printf("%s\n", message.message.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void handler(std::exception* ex, std::string file)
|
||||
{
|
||||
if (ex)
|
||||
return;
|
||||
std::remove(file.c_str());
|
||||
}
|
||||
|
||||
void uploadAndDeletFileAsync(const char* url, const char* file)
|
||||
{
|
||||
boost::shared_ptr<std::fstream> data(new std::fstream(file, std::ios_base::in | std::ios_base::binary));
|
||||
size_t begin = data->tellg();
|
||||
data->seekg (0, std::ios::end);
|
||||
size_t end = data->tellg();
|
||||
if (end > begin)
|
||||
{
|
||||
data->seekg (0, std::ios::beg);
|
||||
|
||||
std::string version;
|
||||
while (*data)
|
||||
{
|
||||
char buff[255];
|
||||
data->getline(buff, 155);
|
||||
std::string line = buff;
|
||||
|
||||
if (line.substr(0, 8) == "Version:")
|
||||
{
|
||||
// "Version: 0.34.0.107 (107)"
|
||||
for (size_t i = 8; i < line.size(); ++i)
|
||||
if (line[i] != ' ')
|
||||
{
|
||||
line = line.substr(i);
|
||||
break;
|
||||
}
|
||||
|
||||
// "0.34.0.107 (107)"
|
||||
for (size_t i = 0; i < line.size(); ++i)
|
||||
if (line[i] == ' ')
|
||||
{
|
||||
line = line.substr(0, i);
|
||||
// "0.34.0.107"
|
||||
while (true)
|
||||
{
|
||||
int j = line.find('.');
|
||||
if (j == std::string::npos)
|
||||
break;
|
||||
line = line.replace(j, 1, ",%20");
|
||||
}
|
||||
// "0,%2034,%200,%20107"
|
||||
version = line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data->seekg (0, std::ios::beg);
|
||||
|
||||
boost::filesystem::path p(file);
|
||||
std::string filename = "log_";
|
||||
// extract file "guid"
|
||||
filename += p.filename().string().substr(13, 17);
|
||||
filename += "%20";
|
||||
filename += version;
|
||||
filename += ".crash";
|
||||
|
||||
std::string fullUrl = url;
|
||||
fullUrl += "?filename=" + filename;
|
||||
|
||||
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "Uploading %s", fullUrl.c_str());
|
||||
RBX::Http(fullUrl).post(data, RBX::Http::kContentTypeUrlEncoded, true, boost::bind(handler, _2, p.string()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Roblox.h
|
||||
* MacClient
|
||||
*
|
||||
* Created by Tony on 1/26/11.
|
||||
* Copyright 2011 __MyCompanyName__. All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Util/StandardOut.h"
|
||||
#include <v8datamodel/TeleportService.h>
|
||||
#include <semaphore.h>
|
||||
#include <v8datamodel/FastLogSettings.h>
|
||||
|
||||
extern void callTerminateApp();
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
class Game;
|
||||
}
|
||||
|
||||
class Roblox
|
||||
{
|
||||
public :
|
||||
|
||||
enum RunMode {
|
||||
RUN_CLIENT,
|
||||
RUN_SERVER,
|
||||
RUN_FILE,
|
||||
RUN_DEVELOPER,
|
||||
};
|
||||
|
||||
private :
|
||||
static boost::scoped_ptr<boost::thread> singleRunningInstance;
|
||||
static sem_t *uniqSemaphore;
|
||||
static bool needTerminateCall;
|
||||
|
||||
static shared_ptr<RBX::TeleportCallback> callback;
|
||||
static void *theInstance;
|
||||
static bool initialized;
|
||||
static std::string assetFolder;
|
||||
static RunMode runMode;
|
||||
static RBX::ClientAppSettings appSettings;
|
||||
|
||||
static void onMessageOut(const RBX::StandardOutMessage& message);
|
||||
static void globalShutdown();
|
||||
|
||||
static void terminateWaiter();
|
||||
static bool isOtherRunning();
|
||||
static void terminateOther();
|
||||
public :
|
||||
|
||||
static bool globalInit(bool isApp);
|
||||
static bool initInstance(void *instance, bool isApp);
|
||||
static void shutdownInstance();
|
||||
static void releaseTerminateWaiter();
|
||||
|
||||
static void setArgs(const char *gameFolder, const char *runMode);
|
||||
|
||||
static void sendAppEvent(void *pClosure);
|
||||
static void postAppEvent(void *pClosure);
|
||||
static void processAppEvents();
|
||||
|
||||
static void addLogToBreakpad(const char* log);
|
||||
static void addBreakPadKeyValue(const char* key, int value);
|
||||
|
||||
|
||||
// Specific handlers for verbs events etc.
|
||||
static void handleLeaveGame(void *appWindow);
|
||||
static void handleShutdownClient(void *appWindow);
|
||||
static void handleToggleFullScreen(void *appWindow);
|
||||
static bool inFullScreenMode(void *appWindow);
|
||||
|
||||
static RunMode getRunMode() { return runMode; }
|
||||
|
||||
static void preloadGame(bool isApp);
|
||||
|
||||
static boost::shared_ptr<RBX::Game> getpreloadedGame(const bool isApp);
|
||||
static void relinquishGame(boost::shared_ptr<RBX::Game>& game);
|
||||
|
||||
static void testCrash();
|
||||
|
||||
};
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* Roblox.cpp
|
||||
* MacClient
|
||||
*
|
||||
* Created by Tony on 1/26/11.
|
||||
* Copyright 2011 __MyCompanyName__. All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "RobloxPlayerAppDelegate.h"
|
||||
#import "CMCrashReporter.h"
|
||||
|
||||
#include "Roblox.h"
|
||||
#include "FunctionMarshaller.h"
|
||||
#include "rbx/CEvent.h"
|
||||
#include "rbx/log.h"
|
||||
#include "util/guid.h"
|
||||
#include "Util/FileSystem.h"
|
||||
#include "RobloxView.h"
|
||||
#include "v8datamodel/TeleportCallback.h"
|
||||
#include "FastLog.h"
|
||||
#include <semaphore.h>
|
||||
|
||||
const std::string& GetBaseURL();
|
||||
|
||||
void callTerminateApp()
|
||||
{
|
||||
[NSApp terminate:nil];
|
||||
}
|
||||
|
||||
void *Roblox::theInstance = NULL;
|
||||
boost::scoped_ptr<boost::thread> Roblox::singleRunningInstance;
|
||||
sem_t *Roblox::uniqSemaphore = NULL;
|
||||
bool Roblox::needTerminateCall = true;
|
||||
|
||||
shared_ptr<RBX::TeleportCallback> Roblox::callback;
|
||||
|
||||
// This function will locate the path to our application on OS X,
|
||||
// unlike windows you cannot rely on the current working directory
|
||||
// for locating your configuration files and resources.
|
||||
std::string macBundlePath()
|
||||
{
|
||||
char path[1024];
|
||||
CFBundleRef mainBundle = CFBundleGetMainBundle();
|
||||
assert(mainBundle);
|
||||
|
||||
CFURLRef mainBundleURL = CFBundleCopyBundleURL(mainBundle);
|
||||
assert(mainBundleURL);
|
||||
|
||||
CFStringRef cfStringRef = CFURLCopyFileSystemPath( mainBundleURL, kCFURLPOSIXPathStyle);
|
||||
assert(cfStringRef);
|
||||
|
||||
CFStringGetCString(cfStringRef, path, 1024, kCFStringEncodingASCII);
|
||||
|
||||
CFRelease(mainBundleURL);
|
||||
CFRelease(cfStringRef);
|
||||
|
||||
return std::string(path);
|
||||
}
|
||||
|
||||
class LogProvider : public RBX::ILogProvider
|
||||
{
|
||||
boost::thread_specific_ptr<RBX::Log> log;
|
||||
boost::filesystem::path logDir;
|
||||
boost::mutex breakPadLogMutex;
|
||||
int breakpadCount;
|
||||
std::vector<RBX::Log*> fastLogChannels;
|
||||
std::string logGuid;
|
||||
static RBX::mutex fastLogChannelsLock;
|
||||
|
||||
static LogProvider* mainLogManager;
|
||||
public:
|
||||
LogProvider()
|
||||
:breakpadCount(0)
|
||||
{
|
||||
RBX::Guid::generateRBXGUID(logGuid);
|
||||
logGuid = logGuid.substr(3, 6);
|
||||
logDir = RBX::FileSystem::getLogsDirectory();
|
||||
|
||||
std::cout << "Writing logs to " << logDir.string() << " with GUID " + logGuid << '\n';
|
||||
|
||||
mainLogManager = this;
|
||||
|
||||
FLog::SetExternalLogFunc(LogProvider::FastLogMessage);
|
||||
}
|
||||
|
||||
~LogProvider()
|
||||
{
|
||||
// Do not delete Log files on Mac Player exit
|
||||
// Look for Log under /tmp/roblox.xyz/Roblox/Logs
|
||||
// tmp folder is auto cleaned on restart of Mac
|
||||
//RBX::FileSystem::clearCacheDirectory("Logs");
|
||||
}
|
||||
|
||||
virtual RBX::Log* provideLog()
|
||||
{
|
||||
RBX::Log* result = log.get();
|
||||
if (!result)
|
||||
{
|
||||
std::string name = RBX::get_thread_name();
|
||||
boost::filesystem::path logFile = logDir / ("log_" + logGuid + ".txt");
|
||||
|
||||
result = new RBX::Log(logFile.c_str(), name.c_str());
|
||||
log.reset(result);
|
||||
|
||||
boost::mutex::scoped_lock lock(breakPadLogMutex);
|
||||
if (breakpadCount++ < 10)
|
||||
{
|
||||
Roblox::addLogToBreakpad(logFile.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void FastLogMessage(FLog::Channel id, const char* message) {
|
||||
|
||||
RBX::mutex::scoped_lock lock(fastLogChannelsLock);
|
||||
|
||||
if(mainLogManager)
|
||||
{
|
||||
if(id >= mainLogManager->fastLogChannels.size())
|
||||
mainLogManager->fastLogChannels.resize(id+1, NULL);
|
||||
|
||||
if(mainLogManager->fastLogChannels[id] == NULL)
|
||||
{
|
||||
char temp[20];
|
||||
snprintf(temp, 19, "log_%s_%u.txt", mainLogManager->logGuid.c_str(), id);
|
||||
temp[19] = 0;
|
||||
boost::filesystem::path logFile = mainLogManager->logDir / temp;
|
||||
|
||||
mainLogManager->fastLogChannels[id] = new RBX::Log(logFile.c_str(), "Log Channel");
|
||||
Roblox::addLogToBreakpad(logFile.c_str());
|
||||
}
|
||||
|
||||
mainLogManager->fastLogChannels[id]->writeEntry(RBX::Log::Information, message);
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteFastLogDump(){
|
||||
RBX::mutex::scoped_lock lock(fastLogChannelsLock);
|
||||
|
||||
if(mainLogManager)
|
||||
{
|
||||
boost::filesystem::path logFile = mainLogManager->logDir / ("log_" + mainLogManager->logGuid + "_dump.txt");
|
||||
|
||||
FLog::WriteFastLogDump(logFile.c_str(), 2048);
|
||||
Roblox::addLogToBreakpad(logFile.c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void MacWriteFastLogDump()
|
||||
{
|
||||
LogProvider::WriteFastLogDump();
|
||||
}
|
||||
|
||||
LogProvider* LogProvider::mainLogManager = NULL;
|
||||
RBX::mutex LogProvider::fastLogChannelsLock;
|
||||
|
||||
static LogProvider logProvider;
|
||||
|
||||
class Teleporter : public RBX::TeleportCallback
|
||||
{
|
||||
void *_instance;
|
||||
public:
|
||||
Teleporter(void *instance) { _instance = instance; }
|
||||
|
||||
virtual void doTeleport(const std::string &url, const std::string &ticket, const std::string &script)
|
||||
{
|
||||
RobloxView *view = nil;
|
||||
|
||||
RobloxPlayerAppDelegate *appDelegate = (RobloxPlayerAppDelegate*)_instance;
|
||||
view = [appDelegate robloxView];
|
||||
|
||||
view->stopJobs();
|
||||
view->marshalTeleport(url, ticket, script);
|
||||
}
|
||||
|
||||
virtual bool isTeleportEnabled() const { return true; }
|
||||
};
|
||||
|
||||
void Roblox::addLogToBreakpad(const char* log)
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
NSResponder *responder = (NSResponder *) theInstance;
|
||||
NSString* s = [NSString stringWithFormat:@"%s", log];
|
||||
[responder performSelectorOnMainThread:@selector(addLogFile:)
|
||||
withObject:s waitUntilDone:NO];
|
||||
[pool release];
|
||||
}
|
||||
|
||||
void Roblox::addBreakPadKeyValue(const char* key, int value)
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
NSResponder *responder = (NSResponder *) theInstance;
|
||||
NSString* s = [NSString stringWithFormat:@"%s", key];
|
||||
NSString* v = [NSString stringWithFormat:@"%d", value];
|
||||
|
||||
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:s, @"key", v, @"value", nil];
|
||||
[responder performSelectorOnMainThread:@selector(addBreakPadKeyValueFromDictionary:)
|
||||
withObject:info waitUntilDone:NO];
|
||||
[pool release];
|
||||
|
||||
}
|
||||
|
||||
static char uniqPlayerId[] = "/RobloxPlayerUniq";
|
||||
|
||||
bool Roblox::isOtherRunning()
|
||||
{
|
||||
pid_t id = getpid();
|
||||
///NSLog(@"isOtherRunning - pid = %i trying to open semaphore", id);
|
||||
sem_t *sem = sem_open(uniqPlayerId, O_CREAT | O_EXCL, 0666, 0);
|
||||
if (sem != SEM_FAILED)
|
||||
{
|
||||
NSLog(@"isOtherRunning - pid = %i waiting on semaphore", id);
|
||||
uniqSemaphore = sem; // I guess we need atomic echange????
|
||||
sem_wait(sem);
|
||||
uniqSemaphore = NULL;
|
||||
|
||||
NSLog(@"isOtherRunning - pid = %i closing semaphore", id);
|
||||
sem_close(sem);
|
||||
sem_unlink(uniqPlayerId);
|
||||
return false;
|
||||
}
|
||||
//NSLog(@"isOtherRunning - pid = %i semaphore open failed", id);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Roblox::terminateOther()
|
||||
{
|
||||
sem_t *sem = sem_open(uniqPlayerId, 0);
|
||||
if (sem != SEM_FAILED)
|
||||
{
|
||||
//NSLog(@"terminateOther - pid = %i semaphore is opened", id);
|
||||
sem_post(sem);
|
||||
sem_close(sem);
|
||||
}
|
||||
}
|
||||
|
||||
void Roblox::terminateWaiter()
|
||||
{
|
||||
pid_t id = getpid();
|
||||
NSLog(@"Roblox::terminateWaiter - pid = %i", id);
|
||||
while(isOtherRunning())
|
||||
{
|
||||
terminateOther();
|
||||
usleep(1000*100); //sleep 100 msecs
|
||||
}
|
||||
|
||||
if (needTerminateCall)
|
||||
{
|
||||
NSLog(@"Roblox::terminateWaiter - pid = %i, terminating app", id);
|
||||
[NSApp terminate:nil];
|
||||
}
|
||||
}
|
||||
|
||||
bool Roblox::initInstance(void *instance, bool isApp)
|
||||
{
|
||||
singleRunningInstance.reset(new boost::thread(&Roblox::terminateWaiter));
|
||||
|
||||
theInstance = instance;
|
||||
|
||||
callback = shared_ptr<RBX::TeleportCallback>(new Teleporter(instance));
|
||||
RBX::TeleportService::SetCallback(callback.get());
|
||||
RBX::TeleportService::SetBaseUrl(::GetBaseURL().c_str());
|
||||
|
||||
RBX::Log::setLogProvider(&logProvider);
|
||||
|
||||
if(globalInit(isApp))
|
||||
{
|
||||
[CMCrashReporter check];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Roblox::releaseTerminateWaiter()
|
||||
{
|
||||
if (uniqSemaphore != NULL)
|
||||
{
|
||||
pid_t id = getpid();
|
||||
NSLog(@"Roblox::shutdownInstance - pid = %i, thread is still waiting, signaling semaphore", id);
|
||||
|
||||
needTerminateCall = false;
|
||||
sem_post(uniqSemaphore);
|
||||
}
|
||||
}
|
||||
|
||||
void Roblox::shutdownInstance()
|
||||
{
|
||||
releaseTerminateWaiter();
|
||||
|
||||
globalShutdown();
|
||||
|
||||
theInstance = NULL;
|
||||
}
|
||||
|
||||
// FunctionMarshaller help - posts worker thread message to UI thread and deals with it next time through
|
||||
void Roblox::sendAppEvent(void *pClosure)
|
||||
{
|
||||
NSGraphicsContext *context = nil;
|
||||
NSInteger windowNumber = 0;
|
||||
NSPoint location;
|
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
NSEvent *ev = [[NSEvent otherEventWithType:NSApplicationDefined location:location modifierFlags:0 timestamp:0 windowNumber:windowNumber context:context
|
||||
subtype:0 data1:(NSInteger)pClosure data2:0] retain];
|
||||
|
||||
|
||||
RBX::CEvent *waitEvent = ((RBX::FunctionMarshaller::Closure *) pClosure)->waitEvent;
|
||||
BOOL waitFlag = (waitEvent == NULL);
|
||||
|
||||
// TBD: Make sure the marshallFunction: selector actually exists!
|
||||
NSResponder *responder = (NSResponder *) theInstance;
|
||||
[responder performSelectorOnMainThread:@selector(marshallFunction:) withObject:ev waitUntilDone:waitFlag];
|
||||
|
||||
if (waitEvent)
|
||||
{
|
||||
waitEvent->Wait();
|
||||
}
|
||||
|
||||
[pool release];
|
||||
}
|
||||
|
||||
void Roblox::postAppEvent(void *pClosure)
|
||||
{
|
||||
NSGraphicsContext *context = nil;
|
||||
NSInteger windowNumber = 0;
|
||||
NSPoint location;
|
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
NSEvent *ev = [[NSEvent otherEventWithType:NSApplicationDefined location:location modifierFlags:0 timestamp:0 windowNumber:windowNumber context:context
|
||||
subtype:0 data1:(NSInteger)pClosure data2:0] retain];
|
||||
|
||||
|
||||
// TBD: Make sure the marshallFunction: selector actually exists!
|
||||
NSResponder *responder = (NSResponder *) theInstance;
|
||||
[responder performSelectorOnMainThread:@selector(marshallFunction:) withObject:ev waitUntilDone:NO];
|
||||
|
||||
[pool release];
|
||||
}
|
||||
|
||||
void Roblox::processAppEvents()
|
||||
{
|
||||
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) == kCFRunLoopRunHandledSource)
|
||||
;
|
||||
}
|
||||
|
||||
void Roblox::handleLeaveGame(void *appWindow)
|
||||
{
|
||||
RobloxPlayerAppDelegate *delegate = (RobloxPlayerAppDelegate *) appWindow;
|
||||
[delegate handleLeaveGame];
|
||||
}
|
||||
|
||||
void Roblox::handleShutdownClient(void *appWindow)
|
||||
{
|
||||
RobloxPlayerAppDelegate *delegate = (RobloxPlayerAppDelegate *) appWindow;
|
||||
[delegate handleShutdownClient];
|
||||
}
|
||||
|
||||
void Roblox::handleToggleFullScreen(void *appWindow)
|
||||
{
|
||||
RobloxPlayerAppDelegate *delegate = (RobloxPlayerAppDelegate *) appWindow;
|
||||
[delegate handleToggleFullScreen];
|
||||
}
|
||||
|
||||
bool Roblox::inFullScreenMode(void *appWindow)
|
||||
{
|
||||
RobloxPlayerAppDelegate *delegate = (RobloxPlayerAppDelegate *) appWindow;
|
||||
|
||||
bool fullscreen = [delegate inFullScreenMode];
|
||||
return fullscreen;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// RobloxOgreView.h
|
||||
// MacClient
|
||||
//
|
||||
// Created by Tony on 12/13/10.
|
||||
// Copyright 2010 Roblox. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface RobloxOgreView : NSView
|
||||
{
|
||||
class RobloxView *robloxView;
|
||||
NSResponder *appDelegate;
|
||||
NSTrackingArea* trackingArea;
|
||||
|
||||
BOOL cursorHidden;
|
||||
BOOL controlKeyWasDown;
|
||||
BOOL fullScreen;
|
||||
|
||||
CGPoint virtualMousePosition;
|
||||
|
||||
NSRect nonFullScreenRect;
|
||||
NSInteger nonFullScreenWindowLevel;
|
||||
}
|
||||
|
||||
@property (assign) BOOL cursorHidden;
|
||||
@property (assign) BOOL controlKeyWasDown;
|
||||
@property (assign) BOOL fullScreen;
|
||||
|
||||
-(id)initWithFrame:(NSRect)f;
|
||||
|
||||
-(CGPoint) getVirtualCursorPos;
|
||||
-(void) setVirtualCursorPos:(CGPoint) newPos;
|
||||
|
||||
-(void) mouseDown:(NSEvent *)event;
|
||||
-(void) mouseDragged:(NSEvent *)event;
|
||||
-(void) mouseMoved:(NSEvent *)event;
|
||||
-(void) mouseUp:(NSEvent *)event;
|
||||
|
||||
-(void) rightMouseDown:(NSEvent *)event;
|
||||
-(void) rightMouseDragged:(NSEvent *)event;
|
||||
-(void) rightMouseUp:(NSEvent *)event;
|
||||
|
||||
-(void) otherMouseDown:(NSEvent *)event;
|
||||
-(void) otherMouseDragged:(NSEvent *)event;
|
||||
-(void) otherMouseUp:(NSEvent *)event;
|
||||
|
||||
-(void) mouseEntered:(NSEvent *)theEvent;
|
||||
-(void) mouseExited:(NSEvent *)theEvent;
|
||||
|
||||
-(void) keyDown:(NSEvent *)event;
|
||||
-(void) keyUp:(NSEvent *)event;
|
||||
-(void) flagsChanged:(NSEvent *)event;
|
||||
|
||||
-(void) scrollWheel:(NSEvent *)theEvent;
|
||||
|
||||
-(void) setRobloxView:(class RobloxView *)rbxview;
|
||||
-(void) setAppDelegate:(NSResponder *)appDelegate;
|
||||
|
||||
-(BOOL) cursorInViewBounds;
|
||||
-(void) showCursor;
|
||||
-(void) hideCursor;
|
||||
|
||||
-(void) toggleFullScreen;
|
||||
-(BOOL) inFullScreenMode;
|
||||
-(void) viewDidEndLiveResize;
|
||||
|
||||
-(void) finishFullScreenChange;
|
||||
|
||||
- (float) titleBarHeight;
|
||||
|
||||
- (void) setCursorPositionToVirtualPositionAndShow;
|
||||
- (BOOL) isMouseOverVisibleWindow;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,836 @@
|
||||
//
|
||||
// RobloxOgreView.m
|
||||
// MacClient
|
||||
//
|
||||
// Created by Tony on 12/13/10.
|
||||
// Copyright 2010 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RobloxOgreView.h"
|
||||
#import "RobloxView.h"
|
||||
#import "RobloxPlayerAppDelegate.h"
|
||||
#import "RBXWindow.h"
|
||||
#import <ApplicationServices/ApplicationServices.h>
|
||||
#import <Carbon/Carbon.h>
|
||||
|
||||
#include "G3D/G3DMath.h"
|
||||
|
||||
#include "v8datamodel/GameBasicSettings.h"
|
||||
|
||||
#define MOUSE_OFFSCREEN_POSITION CGPointMake(-5000,-5000)
|
||||
#define TRACKING_WIDTH_OFFSET 5.0f
|
||||
|
||||
DYNAMIC_FASTFLAGVARIABLE(MiddleMouseButtonEvent, true)
|
||||
@implementation RobloxOgreView
|
||||
|
||||
@synthesize cursorHidden;
|
||||
@synthesize controlKeyWasDown;
|
||||
@synthesize fullScreen;
|
||||
|
||||
- (id)initWithFrame:(NSRect)f;
|
||||
{
|
||||
if (self = [super initWithFrame:f])
|
||||
{
|
||||
robloxView = nil;
|
||||
appDelegate = nil;
|
||||
cursorHidden = NO;
|
||||
controlKeyWasDown = NO;
|
||||
fullScreen = NO;
|
||||
|
||||
virtualMousePosition = MOUSE_OFFSCREEN_POSITION;
|
||||
|
||||
[self updateTrackingAreas];
|
||||
|
||||
[self setPostsBoundsChangedNotifications: YES];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(windowDidBecomeKey:)
|
||||
name:NSWindowDidBecomeKeyNotification
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(windowDidResignKey:)
|
||||
name:NSWindowDidResignKeyNotification
|
||||
object:nil];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(frameChangedNotification:)
|
||||
name:NSViewFrameDidChangeNotification
|
||||
object:nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// returns the default titlebar height for OSX applications
|
||||
- (float) titleBarHeight
|
||||
{
|
||||
NSRect frame = NSMakeRect (0, 0, 100, 100);
|
||||
|
||||
NSRect contentRect = [NSWindow contentRectForFrameRect: frame
|
||||
styleMask: NSTitledWindowMask];
|
||||
|
||||
return (frame.size.height - contentRect.size.height);
|
||||
}
|
||||
|
||||
-(void)updateTrackingAreas
|
||||
{
|
||||
[super updateTrackingAreas];
|
||||
|
||||
if(trackingArea)
|
||||
{
|
||||
[self removeTrackingArea:trackingArea];
|
||||
[trackingArea release];
|
||||
}
|
||||
|
||||
// TRACKING_WIDTH_OFFSET allows the mouse to enter a few pixels on each side of the application, otherwise mouse can get
|
||||
// trapped at a window resizing state, which can cause issues with tracking mouse events
|
||||
NSRect screenBounds = [self bounds];
|
||||
screenBounds = NSMakeRect(screenBounds.origin.x + TRACKING_WIDTH_OFFSET,screenBounds.origin.y,screenBounds.size.width - (TRACKING_WIDTH_OFFSET * 2.0f),screenBounds.size.height);
|
||||
|
||||
trackingArea = [ [NSTrackingArea alloc] initWithRect:screenBounds
|
||||
options:NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:trackingArea];
|
||||
|
||||
NSPoint mouseLocation = [[self window] mouseLocationOutsideOfEventStream];
|
||||
mouseLocation = [self convertPoint: mouseLocation
|
||||
fromView: nil];
|
||||
|
||||
if (NSPointInRect(mouseLocation, [self bounds]))
|
||||
[self mouseEntered: nil];
|
||||
else
|
||||
[self mouseExited: nil];
|
||||
}
|
||||
|
||||
- (void) updateBounds
|
||||
{
|
||||
NSRect bounds = [self bounds];
|
||||
|
||||
if (robloxView)
|
||||
robloxView->setBounds(bounds.size.width, bounds.size.height);
|
||||
}
|
||||
|
||||
- (void) frameChangedNotification: (NSNotification *) notification
|
||||
{
|
||||
[self updateBounds];
|
||||
}
|
||||
|
||||
inline void flipY(RobloxOgreView *view, NSPoint &pt)
|
||||
{
|
||||
BOOL flipped = [view isFlipped];
|
||||
|
||||
if (!flipped)
|
||||
{
|
||||
NSRect bounds = [view bounds];
|
||||
CGFloat height = bounds.size.height;
|
||||
|
||||
pt.y = height - pt.y;
|
||||
}
|
||||
}
|
||||
|
||||
-(void) setCursorPositionToVirtualPositionAndShow
|
||||
{
|
||||
if(!CGPointEqualToPoint(virtualMousePosition, MOUSE_OFFSCREEN_POSITION))
|
||||
{
|
||||
NSRect usableScreenRect = [[NSScreen mainScreen] frame];
|
||||
|
||||
NSPoint outsidePoint = NSMakePoint(virtualMousePosition.x + self.window.frame.origin.x,
|
||||
virtualMousePosition.y + usableScreenRect.size.height - self.window.frame.origin.y - self.window.frame.size.height + [self titleBarHeight]);
|
||||
[self showCursor];
|
||||
|
||||
// this code stops the "freezing" of 250 ms when warping cursor outside of Roblox window
|
||||
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
|
||||
CGEventSourceSetLocalEventsSuppressionInterval(source, 0.0);
|
||||
CGAssociateMouseAndMouseCursorPosition(0);
|
||||
CGWarpMouseCursorPosition(CGPointMake(outsidePoint.x,outsidePoint.y));
|
||||
CGAssociateMouseAndMouseCursorPosition(1);
|
||||
CFRelease(source);
|
||||
}
|
||||
else
|
||||
[self showCursor];
|
||||
}
|
||||
|
||||
-(void) setVirtualCursorPos:(CGPoint) newPos
|
||||
{
|
||||
if(cursorHidden)
|
||||
{
|
||||
virtualMousePosition = newPos;
|
||||
|
||||
// check to see if we have gone out of bounds, if so we need to turn off software rendering of mouse
|
||||
if( ![self inFullScreenMode] && !NSPointInRect( NSMakePoint(virtualMousePosition.x,virtualMousePosition.y), [self bounds]) )
|
||||
[self setCursorPositionToVirtualPositionAndShow];
|
||||
}
|
||||
}
|
||||
|
||||
-(CGPoint) getVirtualCursorPos
|
||||
{
|
||||
return virtualMousePosition;
|
||||
}
|
||||
|
||||
-(BOOL) cursorInViewBounds
|
||||
{
|
||||
if( [self inFullScreenMode] )
|
||||
return YES;
|
||||
|
||||
NSPoint screenPos = NSMakePoint(virtualMousePosition.x,virtualMousePosition.y);
|
||||
//NSLog(@"screenPos is (%f,%f), bounds are %@",virtualMousePosition.x,virtualMousePosition.y, NSStringFromRect( [self bounds] ) );
|
||||
|
||||
return NSPointInRect(screenPos, [self bounds]);
|
||||
}
|
||||
|
||||
- (void) hideCursor
|
||||
{
|
||||
if (cursorHidden)
|
||||
return;
|
||||
|
||||
[NSCursor hide];
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
|
||||
cursorHidden = YES;
|
||||
}
|
||||
|
||||
- (void) showCursor
|
||||
{
|
||||
if (!cursorHidden)
|
||||
return;
|
||||
|
||||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
[NSCursor unhide];
|
||||
|
||||
virtualMousePosition = MOUSE_OFFSCREEN_POSITION; // kinda hack: put sw cursor offscreen
|
||||
|
||||
cursorHidden = NO;
|
||||
}
|
||||
|
||||
-(void) finishFullScreenChange
|
||||
{
|
||||
// Get our new dimensions, others will need them
|
||||
[self updateBounds];
|
||||
|
||||
// We don't seem to get enter/exit events after entering/exiting full screen
|
||||
if (![self cursorInViewBounds])
|
||||
[self mouseExited:nil];
|
||||
|
||||
[self.window makeFirstResponder:self];
|
||||
}
|
||||
|
||||
- (void) toggleFullScreen
|
||||
{
|
||||
fullScreen = !fullScreen;
|
||||
|
||||
if (fullScreen)
|
||||
{
|
||||
NSWindow* wnd = [self window];
|
||||
|
||||
nonFullScreenWindowLevel = wnd.level;
|
||||
nonFullScreenRect = wnd.frame;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(),^{
|
||||
[wnd setStyleMask:NSBorderlessWindowMask];
|
||||
[wnd setFrame:[NSScreen mainScreen].frame display:YES];
|
||||
[wnd setLevel:NSStatusWindowLevel];
|
||||
|
||||
[self finishFullScreenChange];
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch_async(dispatch_get_main_queue(),^{
|
||||
NSWindow *wnd = [self window];
|
||||
|
||||
[wnd setStyleMask:NSResizableWindowMask | NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask];
|
||||
[wnd setFrame:nonFullScreenRect display:YES];
|
||||
[wnd setLevel:nonFullScreenWindowLevel];
|
||||
|
||||
[self finishFullScreenChange];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL) inFullScreenMode
|
||||
{
|
||||
return fullScreen;
|
||||
}
|
||||
|
||||
- (void)windowDidResignKey:(NSNotification *)notification
|
||||
{
|
||||
NSWindow* keyWindow = (NSWindow*)[notification object];
|
||||
|
||||
if(keyWindow == [self window])
|
||||
[self resignFirstResponder];
|
||||
else
|
||||
[self.window makeFirstResponder:nil];
|
||||
}
|
||||
|
||||
- (void)windowDidBecomeKey:(NSNotification *)notification
|
||||
{
|
||||
NSWindow* keyWindow = (NSWindow*)[notification object];
|
||||
BOOL firstResponderIsMainWindow = [[keyWindow firstResponder] isMemberOfClass:[RBXWindow class]];
|
||||
|
||||
if(keyWindow == [self window] || firstResponderIsMainWindow)
|
||||
[self becomeFirstResponder];
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
if (robloxView)
|
||||
robloxView->handleFocus(true);
|
||||
|
||||
if(!cursorHidden)
|
||||
{
|
||||
NSPoint loc = [NSEvent mouseLocation];
|
||||
NSRect usableScreenRect = [[[NSScreen screens] objectAtIndex:0] visibleFrame];
|
||||
|
||||
loc.y = usableScreenRect.size.height - loc.y - [self titleBarHeight];
|
||||
|
||||
NSRect localWindowRect = [self bounds];
|
||||
NSRect localWindowInAbsolute = NSMakeRect(_window.frame.origin.x, usableScreenRect.size.height - _window.frame.origin.y - _window.frame.size.height,
|
||||
localWindowRect.size.width, localWindowRect.size.height);
|
||||
|
||||
if( NSPointInRect(loc,localWindowInAbsolute) )
|
||||
{
|
||||
[self hideCursor];
|
||||
virtualMousePosition = CGPointMake(loc.x - localWindowInAbsolute.origin.x, loc.y - localWindowInAbsolute.origin.y);
|
||||
|
||||
if(robloxView)
|
||||
robloxView->handleMouseInside(true);
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
if (robloxView)
|
||||
robloxView->handleFocus(false);
|
||||
|
||||
[self setCursorPositionToVirtualPositionAndShow];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
-(void) setRobloxView:(RobloxView *)rbxview
|
||||
{
|
||||
robloxView = rbxview;
|
||||
|
||||
if (robloxView)
|
||||
{
|
||||
// Tell Roblox window how big it is early
|
||||
[self updateBounds];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clean up Ogre stuff
|
||||
//[self setOgreWindow:nil];
|
||||
|
||||
// make sure cursor shows up etc
|
||||
[self showCursor];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
-(void) setAppDelegate:(NSResponder *)appDel
|
||||
{
|
||||
self->appDelegate = appDel;
|
||||
|
||||
if (RBX::GameBasicSettings::singleton().getFullScreen() && !fullScreen)
|
||||
{
|
||||
[self toggleFullScreen];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (void)viewDidEndLiveResize
|
||||
{
|
||||
[self updateBounds];
|
||||
}
|
||||
|
||||
static RBX::KeyCode getModifierKeyCode(unsigned short keyCode)
|
||||
{
|
||||
if(keyCode == kVK_CapsLock)
|
||||
{
|
||||
return RBX::SDLK_CAPSLOCK;
|
||||
}
|
||||
if (keyCode == kVK_Shift || keyCode == kVK_RightShift)
|
||||
{
|
||||
return RBX::SDLK_LSHIFT;
|
||||
}
|
||||
if (keyCode == kVK_Control || keyCode == kVK_RightControl)
|
||||
{
|
||||
return RBX::SDLK_LCTRL;
|
||||
}
|
||||
if (keyCode == kVK_Option || keyCode == kVK_RightOption)
|
||||
{
|
||||
return RBX::SDLK_LALT;
|
||||
}
|
||||
|
||||
return RBX::SDLK_UNKNOWN;
|
||||
}
|
||||
|
||||
static RBX::KeyCode keyCodeTOUIKeyCode(unsigned int c, unsigned int keyCode, NSUInteger modifiers)
|
||||
{
|
||||
// Look for keypad-related, translate (somehow) to numeric keypad codes
|
||||
// right now it seems to always behave as if Num Lock is on, sending the number
|
||||
// ASCII chars instead of the arrows etc.
|
||||
if (modifiers & NSNumericPadKeyMask)
|
||||
{
|
||||
}
|
||||
|
||||
if (c == 127)
|
||||
return RBX::SDLK_BACKSPACE;
|
||||
|
||||
// try ASCII table. RBX keycodes in ASCII range are mapped exactly
|
||||
if (c <= 127)
|
||||
{
|
||||
// for A-Z we'll need the lower case versions
|
||||
if (isalpha(c))
|
||||
{
|
||||
c = tolower(c);
|
||||
}
|
||||
|
||||
//NSLog([NSString stringWithFormat:@"keyCodeTOUIKeyCode %d", c]);
|
||||
return (RBX::KeyCode) c;
|
||||
}
|
||||
|
||||
RBX::KeyCode rbxKey = RBX::SDLK_UNKNOWN;
|
||||
|
||||
// Now try the gnarlier key codes. The arrows, function keys etc. are mapped to unicode chars in the higher range
|
||||
switch(c)
|
||||
{
|
||||
case NSUpArrowFunctionKey :
|
||||
rbxKey = RBX::SDLK_UP;
|
||||
break;
|
||||
case NSDownArrowFunctionKey :
|
||||
rbxKey = RBX::SDLK_DOWN;
|
||||
break;
|
||||
case NSLeftArrowFunctionKey :
|
||||
rbxKey = RBX::SDLK_LEFT;
|
||||
break;
|
||||
case NSRightArrowFunctionKey :
|
||||
rbxKey = RBX::SDLK_RIGHT;
|
||||
break;
|
||||
case NSF1FunctionKey :
|
||||
rbxKey = RBX::SDLK_F1;
|
||||
break;
|
||||
case NSF2FunctionKey :
|
||||
rbxKey = RBX::SDLK_F2;
|
||||
break;
|
||||
case NSF3FunctionKey :
|
||||
rbxKey = RBX::SDLK_F3;
|
||||
break;
|
||||
case NSF4FunctionKey :
|
||||
rbxKey = RBX::SDLK_F4;
|
||||
break;
|
||||
case NSF5FunctionKey :
|
||||
rbxKey = RBX::SDLK_F5;
|
||||
break;
|
||||
case NSF6FunctionKey :
|
||||
rbxKey = RBX::SDLK_F6;
|
||||
break;
|
||||
case NSF7FunctionKey :
|
||||
rbxKey = RBX::SDLK_F7;
|
||||
break;
|
||||
case NSF8FunctionKey :
|
||||
rbxKey = RBX::SDLK_F8;
|
||||
break;
|
||||
case NSF9FunctionKey :
|
||||
rbxKey = RBX::SDLK_F9;
|
||||
break;
|
||||
case NSF10FunctionKey :
|
||||
rbxKey = RBX::SDLK_F10;
|
||||
break;
|
||||
case NSF11FunctionKey :
|
||||
rbxKey = RBX::SDLK_F11;
|
||||
break;
|
||||
case NSF12FunctionKey :
|
||||
rbxKey = RBX::SDLK_F12;
|
||||
break;
|
||||
case NSF13FunctionKey :
|
||||
rbxKey = RBX::SDLK_F13;
|
||||
break;
|
||||
case NSF14FunctionKey :
|
||||
rbxKey = RBX::SDLK_F14;
|
||||
break;
|
||||
case NSF15FunctionKey :
|
||||
rbxKey = RBX::SDLK_F15;
|
||||
break;
|
||||
case NSInsertFunctionKey :
|
||||
rbxKey = RBX::SDLK_INSERT;
|
||||
break;
|
||||
case NSDeleteFunctionKey :
|
||||
rbxKey = RBX::SDLK_DELETE;
|
||||
break;
|
||||
case NSHomeFunctionKey :
|
||||
rbxKey = RBX::SDLK_HOME;
|
||||
break;
|
||||
case NSEndFunctionKey :
|
||||
rbxKey = RBX::SDLK_END;
|
||||
break;
|
||||
case NSPageUpFunctionKey :
|
||||
rbxKey = RBX::SDLK_PAGEUP;
|
||||
break;
|
||||
case NSPageDownFunctionKey :
|
||||
rbxKey = RBX::SDLK_PAGEDOWN;
|
||||
break;
|
||||
default :
|
||||
rbxKey = RBX::SDLK_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
|
||||
return rbxKey;
|
||||
}
|
||||
|
||||
static bool modifierKeyPressed(unsigned short keyCode, NSUInteger modifiers)
|
||||
{
|
||||
if ( (keyCode == kVK_CapsLock) && (modifiers & NSAlphaShiftKeyMask) )
|
||||
return true;
|
||||
if ( (keyCode == kVK_Shift) && (modifiers & NSShiftKeyMask) )
|
||||
return true;
|
||||
if ( (keyCode == kVK_Control) && (modifiers & NSControlKeyMask) )
|
||||
return true;
|
||||
if ( (keyCode == kVK_Option) && (modifiers & NSAlternateKeyMask) )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static RBX::ModCode modifiersToUIModCode(NSUInteger modifiers)
|
||||
{
|
||||
unsigned int modCode = 0;
|
||||
|
||||
if( modifiers & NSAlphaShiftKeyMask )
|
||||
modCode = modCode | RBX::KMOD_CAPS;
|
||||
if (modifiers & NSShiftKeyMask)
|
||||
modCode = modCode | RBX::KMOD_LSHIFT;
|
||||
if (modifiers & NSControlKeyMask)
|
||||
modCode = modCode | RBX::KMOD_LCTRL;
|
||||
if (modifiers & NSAlternateKeyMask)
|
||||
modCode = modCode | RBX::KMOD_LALT;
|
||||
if (modifiers & NSCommandKeyMask)
|
||||
modCode = modCode | RBX::KMOD_LMETA;
|
||||
|
||||
return (RBX::ModCode) modCode;
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)theEvent
|
||||
{
|
||||
if (robloxView && [[NSApplication sharedApplication] isActive] && self.window.isKeyWindow)
|
||||
{
|
||||
[self hideCursor];
|
||||
|
||||
NSPoint loc;
|
||||
if (theEvent)
|
||||
loc = [theEvent locationInWindow];
|
||||
else
|
||||
loc = [[self window] mouseLocationOutsideOfEventStream];
|
||||
|
||||
NSPoint myloc = [self convertPoint:loc fromView:nil];
|
||||
flipY(self, myloc);
|
||||
|
||||
virtualMousePosition = CGPointMake(myloc.x, myloc.y);
|
||||
|
||||
robloxView->handleMouseInside(true);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)theEvent
|
||||
{
|
||||
if (robloxView && [[NSApplication sharedApplication] isActive])
|
||||
{
|
||||
//NSLog(@"mouseExited inside");
|
||||
|
||||
[self showCursor];
|
||||
|
||||
robloxView->handleMouseInside(false);
|
||||
}
|
||||
}
|
||||
|
||||
-(BOOL) isMouseOverVisibleWindow
|
||||
{
|
||||
NSPoint absMousePos = NSMakePoint(virtualMousePosition.x + self.window.frame.origin.x,
|
||||
virtualMousePosition.y + self.window.frame.origin.y);
|
||||
NSArray* windows = [[NSApplication sharedApplication] windows];
|
||||
|
||||
for(NSWindow* aWindow in windows)
|
||||
{
|
||||
if (aWindow != self.window &&
|
||||
[aWindow isVisible] &&
|
||||
aWindow.level >= self.window.level &&
|
||||
NSPointInRect(absMousePos, aWindow.frame))
|
||||
{
|
||||
[aWindow makeKeyAndOrderFront:self];
|
||||
[aWindow makeFirstResponder:nil];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
-(void)mouseMoved:(NSEvent *)event
|
||||
{
|
||||
BOOL firstResponderIsMainWindow = [[self.window firstResponder] isMemberOfClass:[RBXWindow class]];
|
||||
|
||||
if (robloxView &&
|
||||
[[NSApplication sharedApplication] isActive] &&
|
||||
[[NSApplication sharedApplication] keyWindow] == self.window &&
|
||||
([self.window firstResponder] == self || firstResponderIsMainWindow) &&
|
||||
[self cursorInViewBounds])
|
||||
{
|
||||
// if we can see some other one of our windows open, give it focus
|
||||
if ([self isMouseOverVisibleWindow])
|
||||
return;
|
||||
|
||||
[[self window] makeFirstResponder:self];
|
||||
|
||||
NSPoint loc = [event locationInWindow];
|
||||
NSPoint myloc = [self convertPoint:loc fromView:nil];
|
||||
flipY(self, myloc);
|
||||
|
||||
if(!cursorHidden)
|
||||
[self hideCursor];
|
||||
|
||||
// NSLog(@"mouseMoved at %@ (window) %@ (view) (%f,%f) (delta)", NSStringFromPoint(loc), NSStringFromPoint(myloc),[event deltaX],[event deltaY]);
|
||||
|
||||
if (robloxView)
|
||||
robloxView->handleMouse(RobloxView::MOUSE_MOVE, [event deltaX], [event deltaY], modifiersToUIModCode([event modifierFlags]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-(void)mouseDown:(NSEvent *)event
|
||||
{
|
||||
if (event.modifierFlags & NSControlKeyMask)
|
||||
{
|
||||
controlKeyWasDown = YES;
|
||||
return [self rightMouseDown:event];
|
||||
}
|
||||
|
||||
NSPoint loc = [event locationInWindow];
|
||||
NSPoint myloc = [self convertPoint:loc fromView:nil];
|
||||
|
||||
// flip mouse coords from Y-up to Y-down
|
||||
flipY(self, myloc);
|
||||
|
||||
if(!cursorHidden)
|
||||
{
|
||||
[self hideCursor];
|
||||
|
||||
virtualMousePosition = CGPointMake(myloc.x, myloc.y);
|
||||
robloxView->handleMouseInside(true);
|
||||
}
|
||||
|
||||
//NSLog(@"mouseDown at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
|
||||
|
||||
if (robloxView)
|
||||
robloxView->handleMouse(RobloxView::MOUSE_LEFT_BUTTON_DOWN, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
|
||||
}
|
||||
|
||||
-(void)mouseUp:(NSEvent *)event
|
||||
{
|
||||
if (event.modifierFlags & NSControlKeyMask || controlKeyWasDown)
|
||||
{
|
||||
controlKeyWasDown = false;
|
||||
return [self rightMouseUp:event];
|
||||
}
|
||||
|
||||
NSPoint loc = [event locationInWindow];
|
||||
NSPoint myloc = [self convertPoint:loc fromView:nil];
|
||||
|
||||
// NSLog(@"mouseUp at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
|
||||
|
||||
// possibly flip mouse coords from Y-up to Y-down
|
||||
flipY(self, myloc);
|
||||
|
||||
if (robloxView)
|
||||
robloxView->handleMouse(RobloxView::MOUSE_LEFT_BUTTON_UP, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
|
||||
}
|
||||
|
||||
-(void)mouseDragged:(NSEvent *)event
|
||||
{
|
||||
if (event.modifierFlags & NSControlKeyMask || controlKeyWasDown)
|
||||
return [self rightMouseDragged:event];
|
||||
|
||||
NSPoint loc = [event locationInWindow];
|
||||
NSPoint myloc = [self convertPoint:loc fromView:nil];
|
||||
|
||||
//NSLog(@"mouseDragged at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
|
||||
|
||||
// possibly flip mouse coords from Y-up to Y-down
|
||||
flipY(self, myloc);
|
||||
|
||||
if (robloxView)
|
||||
robloxView->handleMouse(RobloxView::MOUSE_MOVE, [event deltaX], [event deltaY], modifiersToUIModCode([event modifierFlags]));
|
||||
}
|
||||
|
||||
-(void)rightMouseDown:(NSEvent *)event
|
||||
{
|
||||
[[self window] makeFirstResponder:self];
|
||||
|
||||
NSPoint loc = [event locationInWindow];
|
||||
NSPoint myloc = [self convertPoint:loc fromView:nil];
|
||||
|
||||
//NSLog(@"mouseDown at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
|
||||
|
||||
// possibly flip mouse coords from Y-up to Y-down
|
||||
flipY(self, myloc);
|
||||
|
||||
if (robloxView)
|
||||
robloxView->handleMouse(RobloxView::MOUSE_RIGHT_BUTTON_DOWN, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
|
||||
}
|
||||
|
||||
-(void)rightMouseDragged:(NSEvent *)event
|
||||
{
|
||||
if (robloxView)
|
||||
robloxView->handleMouse(RobloxView::MOUSE_MOVE, [event deltaX], [event deltaY], modifiersToUIModCode([event modifierFlags]));
|
||||
}
|
||||
|
||||
-(void)rightMouseUp:(NSEvent *)event
|
||||
{
|
||||
NSPoint loc = [event locationInWindow];
|
||||
NSPoint myloc = [self convertPoint:loc fromView:nil];
|
||||
|
||||
// NSLog(@"mouseUp at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
|
||||
|
||||
// possibly flip mouse coords from Y-up to Y-down
|
||||
flipY(self, myloc);
|
||||
|
||||
if (robloxView)
|
||||
robloxView->handleMouse(RobloxView::MOUSE_RIGHT_BUTTON_UP, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
|
||||
}
|
||||
|
||||
-(void)otherMouseDown:(NSEvent *)event
|
||||
{
|
||||
if (DFFlag::MiddleMouseButtonEvent)
|
||||
{
|
||||
if ([event buttonNumber] == (NSInteger)2)
|
||||
{
|
||||
[[self window] makeFirstResponder:self];
|
||||
|
||||
NSPoint loc = [event locationInWindow];
|
||||
NSPoint myloc = [self convertPoint:loc fromView:nil];
|
||||
|
||||
//NSLog(@"mouseDown at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
|
||||
|
||||
// possibly flip mouse coords from Y-up to Y-down
|
||||
flipY(self, myloc);
|
||||
|
||||
if (robloxView)
|
||||
robloxView->handleMouse(RobloxView::MOUSE_MIDDLE_BUTTON_DOWN, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void)otherMouseDragged:(NSEvent *)event
|
||||
{
|
||||
if (DFFlag::MiddleMouseButtonEvent)
|
||||
{
|
||||
if (robloxView)
|
||||
robloxView->handleMouse(RobloxView::MOUSE_MOVE, [event deltaX], [event deltaY], modifiersToUIModCode([event modifierFlags]));
|
||||
}
|
||||
}
|
||||
|
||||
-(void)otherMouseUp:(NSEvent *)event
|
||||
{
|
||||
if (DFFlag::MiddleMouseButtonEvent)
|
||||
{
|
||||
if ([event buttonNumber] == (NSInteger)2)
|
||||
{
|
||||
NSPoint loc = [event locationInWindow];
|
||||
NSPoint myloc = [self convertPoint:loc fromView:nil];
|
||||
|
||||
// NSLog(@"mouseUp at %@ (window) %@ (view)", NSStringFromPoint(loc), NSStringFromPoint(myloc));
|
||||
|
||||
// possibly flip mouse coords from Y-up to Y-down
|
||||
flipY(self, myloc);
|
||||
|
||||
if (robloxView)
|
||||
robloxView->handleMouse(RobloxView::MOUSE_MIDDLE_BUTTON_UP, (int) myloc.x, (int) myloc.y, modifiersToUIModCode([event modifierFlags]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void)keyDown:(NSEvent *)event
|
||||
{
|
||||
if( ![event isARepeat])
|
||||
{
|
||||
NSString *chars = [event characters];
|
||||
unsigned short keyCode = [event keyCode];
|
||||
NSUInteger modifiers = [event modifierFlags];
|
||||
|
||||
if (robloxView)
|
||||
{
|
||||
NSUInteger len = [chars length];
|
||||
if (len > 0)
|
||||
{
|
||||
RBX::KeyCode rbxKey = keyCodeTOUIKeyCode([chars characterAtIndex:0], keyCode, modifiers);
|
||||
robloxView->handleKey(RobloxView::KEY_DOWN, rbxKey, modifiersToUIModCode(modifiers));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(void)flagsChanged:(NSEvent *)event
|
||||
{
|
||||
RBX::KeyCode modKeyCode = getModifierKeyCode([event keyCode]);
|
||||
RBX::ModCode UIModCode = modifiersToUIModCode([event modifierFlags]);
|
||||
|
||||
if( modifierKeyPressed([event keyCode], [event modifierFlags]) )
|
||||
robloxView->handleKey(RobloxView::KEY_DOWN, modKeyCode, UIModCode);
|
||||
else
|
||||
robloxView->handleKey(RobloxView::KEY_UP, modKeyCode, UIModCode);
|
||||
}
|
||||
|
||||
-(void)keyUp:(NSEvent *)event
|
||||
{
|
||||
if(![event isARepeat])
|
||||
{
|
||||
NSString *chars = [event characters];
|
||||
NSUInteger modifiers = [event modifierFlags];
|
||||
|
||||
if (robloxView)
|
||||
{
|
||||
NSUInteger len = [chars length];
|
||||
if (len > 0)
|
||||
{
|
||||
RBX::KeyCode keyCode = keyCodeTOUIKeyCode([chars characterAtIndex:0], [event keyCode], modifiers);
|
||||
robloxView->handleKey(RobloxView::KEY_UP, keyCode, modifiersToUIModCode(modifiers));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define RBXDELTAY 120.f
|
||||
|
||||
-(void)scrollWheel:(NSEvent *)theEvent
|
||||
{
|
||||
// Get scroll info
|
||||
CGFloat deltaY = [theEvent deltaY];
|
||||
CGFloat deltaX = [theEvent deltaX];
|
||||
// NSLog(@"Got a scroll deltaX:%g deltaY:%g deltaZ:%g", deltaX, deltaY, deltaZ);
|
||||
if (robloxView)
|
||||
{
|
||||
//NSLog (@"Transducing deltaY to %g", rbxDeltaY);
|
||||
if (deltaY)
|
||||
{
|
||||
// Convert (bizarre) Cocoa deltaY values to (even more bizarre) Windows values
|
||||
float rbxDeltaY = deltaY > 0.f ? RBXDELTAY : -RBXDELTAY;
|
||||
robloxView->handleScrollWheel(rbxDeltaY, virtualMousePosition.x, virtualMousePosition.y);
|
||||
}
|
||||
// Horizontal scrolling reports as Delta in X-Axis
|
||||
else if (deltaX)
|
||||
{
|
||||
float rbxDeltaX = deltaX > 0.f ? RBXDELTAY : -RBXDELTAY;
|
||||
robloxView->handleScrollWheel(rbxDeltaX, virtualMousePosition.x, virtualMousePosition.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,4 @@
|
||||
// RobloxPlayer.cpp : Defines the entry point for the console application.
|
||||
//
|
||||
|
||||
// NOTE: Contents of this file have been gutted and moved to various other files and classes. Let's remove it from source control soon -- TP
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// SimplePlayerMacAppDelegate.h
|
||||
// SimplePlayerMac
|
||||
//
|
||||
// Created by Tony on 10/26/10.
|
||||
// Copyright 2010 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Breakpad.h>
|
||||
|
||||
#import "RobloxOgreView.h"
|
||||
|
||||
#include "rbx/signal.h"
|
||||
#import "RbxWebView.h"
|
||||
#import "RBXWindow.h"
|
||||
|
||||
class RobloxView;
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
class DataModel;
|
||||
}
|
||||
|
||||
@interface RobloxPlayerAppDelegate : NSResponder <NSWindowDelegate>
|
||||
{
|
||||
RBXWindow *window;
|
||||
NSView *mainView;
|
||||
RbxWebView *rbxWebView;
|
||||
RobloxOgreView *ogreView;
|
||||
RobloxView *robloxView;
|
||||
NSMenu *mainMenu;
|
||||
BOOL running;
|
||||
BOOL quitOnLeave;
|
||||
RobloxAppShutdownCode shutdownCode;
|
||||
BreakpadRef breakpad;
|
||||
rbx::signals::scoped_connection openUrlConnection;
|
||||
rbx::signals::scoped_connection closeUrlConnection;
|
||||
RobloxGameType gameType;
|
||||
}
|
||||
|
||||
-(id) init;
|
||||
|
||||
- (BOOL) checkUpdater:(BOOL) showUpdateOptionsDialog;
|
||||
|
||||
-(void)marshallFunction:(NSEvent *)evt;
|
||||
|
||||
-(void)addLogFile:(NSString *)file;
|
||||
-(void)addBreakPadKeyValue:(NSString *)breakPadKey withValue:(NSString *)value;
|
||||
-(void)addDbgInfoToBreakPad;
|
||||
|
||||
-(void)applicationDidFinishLaunching:(NSNotification *)aNotification;
|
||||
-(NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
|
||||
-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication;
|
||||
-(void)applicationWillTerminate:(NSNotification *)aNotification;
|
||||
|
||||
-(void)windowWillClose:(NSNotification *)notification;
|
||||
|
||||
-(void)ShutdownDataModel;
|
||||
-(void)leaveGame;
|
||||
-(void)handleLeaveGame;
|
||||
-(BOOL)requestShutdownClient:(RobloxAppShutdownCode)code;
|
||||
-(void)handleShutdownClient;
|
||||
|
||||
-(void)handleToggleFullScreen;
|
||||
-(bool)inFullScreenMode;
|
||||
|
||||
-(void)teleport:(NSString *)ticket withAuthentication:(NSString *)url withScript:(NSString *)script;
|
||||
-(void)StartGame:(NSString *)ticket withAuthentication:(NSString *)url withScript:(NSString *)script;
|
||||
|
||||
bool requestPlaceInfo(int testPlaceID, std::string& authenticationUrl, std::string& ticket, std::string& scriptUrl);
|
||||
RequestPlaceInfoResult requestPlaceInfo(const std::string& placeLauncherUrl, std::string& authenticationUrl, std::string& ticket, std::string& scriptUrl);
|
||||
|
||||
-(void) openUrlWindow:(std::string) url;
|
||||
|
||||
- (IBAction)crash:(id)sender;
|
||||
|
||||
-(void) setupGameServices;
|
||||
-(void) setupDataModelServices:(RBX::DataModel*) dataModel;
|
||||
|
||||
+(void) reportRenderViewInitError:(const char *)message;
|
||||
|
||||
@property (assign) IBOutlet RBXWindow *window;
|
||||
@property (assign) IBOutlet NSView *mainView;
|
||||
|
||||
@property (readwrite, assign) RobloxView *robloxView;
|
||||
@property (assign) IBOutlet RobloxOgreView *ogreView;
|
||||
@property (assign) IBOutlet NSMenu *mainMenu;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,749 @@
|
||||
#include "RobloxView.h"
|
||||
#include "Roblox.h"
|
||||
|
||||
#include "GfxBase/ViewBase.h"
|
||||
#include "v8datamodel/datamodel.h"
|
||||
#include "v8datamodel/workspace.h"
|
||||
#include "v8datamodel/camera.h"
|
||||
#include "v8datamodel/game.h"
|
||||
#include "v8datamodel/InputObject.h"
|
||||
#include "v8datamodel/GuiService.h"
|
||||
#include "FunctionMarshaller.h"
|
||||
#include "Util/StandardOut.h"
|
||||
#include "Util/FileSystem.h"
|
||||
#include "rbx/SystemUtil.h"
|
||||
#include "rbx/Tasks/Coordinator.h"
|
||||
#include "UserInput.h"
|
||||
#include "Util/IMetric.h"
|
||||
#include "Util/Object.h"
|
||||
#include "GfxBase/RenderSettings.h"
|
||||
#include "GfxBase/FrameRateManager.h"
|
||||
#include "v8datamodel/BaseRenderJob.h"
|
||||
#include "v8datamodel/UserController.h"
|
||||
#include "v8datamodel/UserInputService.h"
|
||||
#include "Util/Statistics.h"
|
||||
#include "v8datamodel/ContentProvider.h"
|
||||
#include "script/ScriptContext.h"
|
||||
#include "v8xml/Serializer.h"
|
||||
#include "rbx/CEvent.h"
|
||||
#include "GameVerbs.h"
|
||||
#include "Network/Players.h"
|
||||
#include "../ClientBase/RenderSettingsItem.h"
|
||||
|
||||
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
|
||||
#include "V8DataModel/GameBasicSettings.h"
|
||||
|
||||
LOGGROUP(PlayerShutdownLuaTimeoutSeconds)
|
||||
|
||||
// This job calls ViewBase::render(), which needs to be done exclusive to the DataModel.
|
||||
// This is why it has the RBX::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 RobloxView::RenderJob : public RBX::BaseRenderJob
|
||||
, public RBX::IMetric
|
||||
{
|
||||
RBX::FunctionMarshaller* marshaller;
|
||||
weak_ptr<RBX::DataModel> dataModel;
|
||||
RBX::ViewBase* view;
|
||||
RBX::CEvent renderEvent;
|
||||
|
||||
public:
|
||||
RenderJob(RBX::ViewBase* view, RBX::FunctionMarshaller* marshaller, shared_ptr<RBX::DataModel> dataModel)
|
||||
:RBX::BaseRenderJob(CRenderSettingsItem::singleton().getMinFrameRate(), CRenderSettingsItem::singleton().getMaxFrameRate(), dataModel),
|
||||
view(view),
|
||||
dataModel(dataModel),
|
||||
marshaller(marshaller),
|
||||
renderEvent(false)
|
||||
{
|
||||
cyclicExecutive = true;
|
||||
}
|
||||
|
||||
|
||||
RBX::Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
if (isAwake)
|
||||
return computeStandardSleepTime(stats, CRenderSettingsItem::singleton().getMaxFrameRate());
|
||||
else
|
||||
return RBX::Time::Interval::max();
|
||||
}
|
||||
|
||||
|
||||
static void scheduleRenderPerform(const weak_ptr<RenderJob>& selfWeak, ViewBase* view, double timeJobStart)
|
||||
{
|
||||
if (shared_ptr<RenderJob> self = selfWeak.lock())
|
||||
{
|
||||
view->renderPerform(timeJobStart);
|
||||
self->wake();
|
||||
}
|
||||
}
|
||||
|
||||
void doDataModelRenderStep(DataModel* dm, const float secondsElapsed)
|
||||
{
|
||||
dm->renderStep(secondsElapsed);
|
||||
isAwake = false;
|
||||
marshaller->Execute(boost::bind(&RBX::ViewBase::renderPrepare, view, this), &renderEvent);
|
||||
}
|
||||
|
||||
virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
DataModel* dm = view->getDataModel();
|
||||
if (!dm)
|
||||
return RBX::TaskScheduler::Done;
|
||||
|
||||
double timeJobStart = RBX::Time::nowFastSec();
|
||||
try
|
||||
{
|
||||
const float secondsElapsed = view->getFrameRateManager()->GetFrameTimeStats().getLatest() / 1000.f;
|
||||
lastRenderTime = RBX::Time::now<RBX::Time::Fast>();
|
||||
// TODO: Can we fix Ogre so that it can be called from this thread, rather than marshalled?
|
||||
|
||||
RBX::DataModel::scoped_write_request request(dm);
|
||||
doDataModelRenderStep(dm, secondsElapsed);
|
||||
|
||||
marshaller->Submit(boost::bind(&scheduleRenderPerform, weak_from(this), view, timeJobStart));
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e);
|
||||
}
|
||||
return RBX::TaskScheduler::Stepped;
|
||||
}
|
||||
|
||||
void abortRender()
|
||||
{
|
||||
renderEvent.Set();
|
||||
}
|
||||
|
||||
// IMetric
|
||||
/*override*/ double getMetricValue(const std::string& metric) const
|
||||
{
|
||||
RBX::FrameRateManager* frm = view ? view->getFrameRateManager() : 0;
|
||||
|
||||
if (metric == "Render FPS") {
|
||||
return averageStepsPerSecond();
|
||||
}
|
||||
|
||||
if (metric == "Render Duty") {
|
||||
return averageDutyCycle();
|
||||
}
|
||||
|
||||
if (metric == "Render Nominal FPS") {
|
||||
return frm ? 1000.0 / frm->GetRenderTimeAverage() : 0.0;
|
||||
}
|
||||
|
||||
if(metric == "Delta Between Renders")
|
||||
{
|
||||
return view->getMetricValue(metric);
|
||||
}
|
||||
else if (metric == "Total Render")
|
||||
{
|
||||
return view->getMetricValue(metric);
|
||||
}
|
||||
else if (metric == "Present Time")
|
||||
{
|
||||
return view->getMetricValue(metric);
|
||||
}
|
||||
else if (metric == "GPU Delay")
|
||||
{
|
||||
return view->getMetricValue(metric);
|
||||
}
|
||||
else if (metric == "Video Memory")
|
||||
{
|
||||
return (double)RBX::SystemUtil::getVideoMemory();
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
|
||||
/*override*/ std::string getMetric(const std::string& metric) const
|
||||
{
|
||||
if (! view ) {
|
||||
return "No View";
|
||||
}
|
||||
|
||||
if (metric == "Graphics Mode") {
|
||||
return ""; // RBX::Reflection::EnumDesc<CRenderSettings::GraphicsMode>::singleton().convertToString(graphicsMode);
|
||||
}
|
||||
|
||||
RBX::FrameRateManager* frm = view ? view->getFrameRateManager() : 0;
|
||||
|
||||
|
||||
if (metric == "FRM") {
|
||||
return (frm && frm->IsBlockCullingEnabled()) ? "On" : "Off";
|
||||
}
|
||||
|
||||
if (metric == "Anti-Aliasing") {
|
||||
return (frm && frm->getAntialiasingMode() == RBX::CRenderSettings::AntialiasingOn) ? "On" : "Off";
|
||||
}
|
||||
|
||||
RBXASSERT(0);
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
class RobloxView::UserInputJob : public RBX::DataModelJob
|
||||
{
|
||||
RobloxView* wnd;
|
||||
RBX::DataModel* dataModel;
|
||||
public:
|
||||
UserInputJob(RobloxView* wnd, shared_ptr<RBX::DataModel> dataModel)
|
||||
:RBX::DataModelJob("UserInput", RBX::DataModelJob::Write, true, dataModel, RBX::Time::Interval(0)),
|
||||
wnd(wnd),
|
||||
dataModel(dataModel.get())
|
||||
{
|
||||
}
|
||||
|
||||
RBX::Time::Interval sleepTime(const Stats& stats)
|
||||
{
|
||||
return computeStandardSleepTime(stats, 60);
|
||||
}
|
||||
|
||||
virtual Job::Error error(const Stats& stats)
|
||||
{
|
||||
Job::Error result = computeStandardError(stats, 60);
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
RBX::DataModel::scoped_write_request request(dataModel);
|
||||
if(wnd)
|
||||
wnd->processInput();
|
||||
return RBX::TaskScheduler::Stepped;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
static RBX::InputObject::UserInputType viewEventToUserInputType(RobloxView::EventType event)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case RobloxView::MOUSE_MOVE:
|
||||
return RBX::InputObject::TYPE_MOUSEMOVEMENT;
|
||||
case RobloxView::MOUSE_LEFT_BUTTON_DOWN:
|
||||
case RobloxView::MOUSE_LEFT_BUTTON_UP: // intentional fall thru
|
||||
return RBX::InputObject::TYPE_MOUSEBUTTON1;
|
||||
case RobloxView::MOUSE_RIGHT_BUTTON_DOWN:
|
||||
case RobloxView::MOUSE_RIGHT_BUTTON_UP: // intentional fall thru
|
||||
return RBX::InputObject::TYPE_MOUSEBUTTON2;
|
||||
case RobloxView::MOUSE_MIDDLE_BUTTON_DOWN:
|
||||
case RobloxView::MOUSE_MIDDLE_BUTTON_UP: // intentional fall thru
|
||||
return RBX::InputObject::TYPE_MOUSEBUTTON3;
|
||||
case RobloxView::KEY_UP:
|
||||
case RobloxView::KEY_DOWN: // intentional fall thru
|
||||
return RBX::InputObject::TYPE_KEYBOARD;
|
||||
default:
|
||||
return RBX::InputObject::TYPE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static RBX::InputObject::UserInputState viewEventToUserInputState(RobloxView::EventType event)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case RobloxView::MOUSE_MOVE:
|
||||
return RBX::InputObject::INPUT_STATE_CHANGE;
|
||||
case RobloxView::MOUSE_LEFT_BUTTON_DOWN:
|
||||
case RobloxView::MOUSE_RIGHT_BUTTON_DOWN:
|
||||
case RobloxView::MOUSE_MIDDLE_BUTTON_DOWN:
|
||||
case RobloxView::KEY_DOWN: // intentional fall thru
|
||||
return RBX::InputObject::INPUT_STATE_BEGIN;
|
||||
case RobloxView::MOUSE_LEFT_BUTTON_UP:
|
||||
case RobloxView::MOUSE_RIGHT_BUTTON_UP:
|
||||
case RobloxView::MOUSE_MIDDLE_BUTTON_UP:
|
||||
case RobloxView::KEY_UP: // intentional fall thru
|
||||
return RBX::InputObject::INPUT_STATE_END;
|
||||
default:
|
||||
return RBX::InputObject::INPUT_STATE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::processInput()
|
||||
{
|
||||
/*if (userInput && userInput.get())
|
||||
if(userInput->getWrapMode() == RBX::UserInputBase::WRAP_HYBRID)
|
||||
userInput->ProcessUserInputMessage(RBX::UIEvent::NO_EVENT,0,0);*/
|
||||
}
|
||||
|
||||
// Request a shutdown of the client
|
||||
Boolean RobloxView::requestShutdownClient()
|
||||
{
|
||||
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
|
||||
{
|
||||
// give scripts a deadline to finish
|
||||
if (FLog::PlayerShutdownLuaTimeoutSeconds > 0)
|
||||
if (ScriptContext* scriptContext = game->getDataModel()->find<ScriptContext>())
|
||||
scriptContext->setTimeout(FLog::PlayerShutdownLuaTimeoutSeconds);
|
||||
|
||||
DataModel::RequestShutdownResult requestResult = dataModel->requestShutdown();
|
||||
return requestResult == DataModel::CLOSE_REQUEST_HANDLED;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RobloxView::handleUserMessage(EventType event, unsigned int wParam, unsigned int lParam)
|
||||
{
|
||||
if (userInput)
|
||||
{
|
||||
userInput->PostUserInputMessage(viewEventToUserInputType(event), viewEventToUserInputState(event), wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::handleFocus(bool focus)
|
||||
{
|
||||
if (userInput)
|
||||
{
|
||||
userInput->PostUserInputMessage(RBX::InputObject::TYPE_FOCUS, focus ? RBX::InputObject::INPUT_STATE_BEGIN : RBX::InputObject::INPUT_STATE_END, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::handleMouseInside(bool inside)
|
||||
{
|
||||
if (userInput)
|
||||
{
|
||||
if (inside)
|
||||
userInput->onMouseInside();
|
||||
else
|
||||
userInput->onMouseLeave();
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::handleMouse(EventType event, int x, int y, unsigned int modifiers)
|
||||
{
|
||||
if (userInput)
|
||||
{
|
||||
userInput->PostUserInputMessage(viewEventToUserInputType(event), viewEventToUserInputState(event), modifiers, MAKEXYLPARAM((uint) x, (uint) y));
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::handleKey(EventType event, RBX::KeyCode keyCode, RBX::ModCode modifiers)
|
||||
{
|
||||
if (userInput)
|
||||
{
|
||||
userInput->PostUserInputMessage(viewEventToUserInputType(event), viewEventToUserInputState(event), keyCode, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::handleScrollWheel(float delta, int x, int y)
|
||||
{
|
||||
if (userInput)
|
||||
{
|
||||
userInput->PostUserInputMessage(RBX::InputObject::TYPE_MOUSEWHEEL, RBX::InputObject::INPUT_STATE_CHANGE, delta, MAKEXYLPARAM((uint) x, (uint) y));
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::leaveGame()
|
||||
{
|
||||
marshaller->Submit(boost::bind(&RobloxView::handleLeaveGame, this));
|
||||
}
|
||||
|
||||
void RobloxView::handleLeaveGame()
|
||||
{
|
||||
Roblox::handleLeaveGame(appWindow);
|
||||
}
|
||||
|
||||
void RobloxView::shutdownClient()
|
||||
{
|
||||
marshaller->Submit(boost::bind(&RobloxView::handleShutdownClient, this));
|
||||
}
|
||||
|
||||
void RobloxView::handleShutdownClient()
|
||||
{
|
||||
Roblox::handleShutdownClient(appWindow);
|
||||
}
|
||||
|
||||
void RobloxView::toggleFullScreen()
|
||||
{
|
||||
if(getDataModel())
|
||||
getDataModel()->submitTask(boost::bind(&RobloxView::handleToggleFullScreen, this), RBX::DataModelJob::Write);
|
||||
}
|
||||
|
||||
void RobloxView::handleToggleFullScreen()
|
||||
{
|
||||
Roblox::handleToggleFullScreen(appWindow);
|
||||
RBX::GameBasicSettings::singleton().setFullScreen(Roblox::inFullScreenMode(appWindow));
|
||||
}
|
||||
|
||||
|
||||
extern std::string macBundlePath();
|
||||
|
||||
static RBX::ViewBase* createGameWindow(void *wnd)
|
||||
{
|
||||
// static initialization:
|
||||
static boost::once_flag flag = BOOST_ONCE_INIT;
|
||||
boost::call_once(&RBX::ViewBase::InitPluginModules, flag);
|
||||
|
||||
RBX::OSContext context;
|
||||
context.hWnd = wnd;
|
||||
context.width = 800;
|
||||
context.height = 600;
|
||||
|
||||
RBX::CRenderSettings& settings = CRenderSettingsItem::singleton();
|
||||
RBX::CRenderSettings::GraphicsMode mode = RBX::CRenderSettings::OpenGL;
|
||||
RBX::ViewBase* rbxView = RBX::ViewBase::CreateView(mode, &context, &settings);
|
||||
rbxView->initResources();
|
||||
|
||||
return rbxView;
|
||||
}
|
||||
|
||||
class DummyVerb : public RBX::Verb {
|
||||
public:
|
||||
DummyVerb(VerbContainer* container, const char* name)
|
||||
:Verb(container, name)
|
||||
{
|
||||
}
|
||||
virtual bool isEnabled() const {return false;}
|
||||
|
||||
virtual void doIt(RBX::IDataState* dataState) {}
|
||||
};
|
||||
|
||||
RobloxView::RobloxView(void *viewwnd, void *appwnd, boost::shared_ptr<RBX::Game> game)
|
||||
:view(createGameWindow(viewwnd))
|
||||
,appWindow(appwnd)
|
||||
,viewWindow(viewwnd)
|
||||
,userInput(new UserInput(this, game))
|
||||
,game(game)
|
||||
,leaveGameVerb(new LeaveGameVerb(this, game->getDataModel().get()))
|
||||
,fullscreenVerb(new ToggleFullscreenVerb(this, game->getDataModel().get(), NULL))
|
||||
,studioVerb(new DummyVerb(game->getDataModel().get(), "TogglePlayMode"))
|
||||
,screenshotVerb(new DummyVerb(game->getDataModel().get(), "Screenshot"))
|
||||
,shutdownClientVerb(new ShutdownClientVerb(this, game->getDataModel().get()))
|
||||
,marshaller(RBX::FunctionMarshaller::GetWindow())
|
||||
{
|
||||
shared_ptr<RBX::DataModel> dataModel = game->getDataModel();
|
||||
|
||||
placeIDChangeConnection = dataModel->propertyChangedSignal.connect(boost::bind(&RobloxView::onPlaceIDChanged, this, _1));
|
||||
|
||||
// Bind to the Workspace
|
||||
bindToWorkspace();
|
||||
|
||||
// Create the rendering jobs
|
||||
renderJob = shared_ptr<RenderJob>(new RenderJob(view.get(), marshaller, dataModel));
|
||||
|
||||
// Create the user input job
|
||||
userInputJob = shared_ptr<UserInputJob>(new UserInputJob(this, dataModel));
|
||||
|
||||
defineConcurrencyRules();
|
||||
|
||||
RBX::TaskScheduler::singleton().add(renderJob);
|
||||
if (userInputJob)
|
||||
{
|
||||
RBX::TaskScheduler::singleton().add(userInputJob);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RobloxView::onPlaceIDChanged(const RBX::Reflection::PropertyDescriptor* desc)
|
||||
{
|
||||
bool placeIDChanged = desc->name=="PlaceId";
|
||||
|
||||
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
|
||||
if(placeIDChanged && dataModel->getPlaceID() > 0)
|
||||
Roblox::addBreakPadKeyValue("Place0", dataModel->getPlaceID());
|
||||
}
|
||||
|
||||
|
||||
void RobloxView::defineConcurrencyRules()
|
||||
{
|
||||
{
|
||||
// Force viewUpdateJob and renderJob to happen serially
|
||||
boost::shared_ptr<RBX::Tasks::Coordinator> sequence(new RBX::Tasks::ExclusiveSequence());
|
||||
renderJob->addCoordinator(sequence);
|
||||
}
|
||||
|
||||
if (CRenderSettingsItem::singleton().isSynchronizedWithPhysics)
|
||||
{
|
||||
// Force rendering and physics to happen in lock-step
|
||||
sequence.reset(new RBX::Tasks::Sequence());
|
||||
renderJob->addCoordinator(sequence);
|
||||
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
|
||||
dataModel->create<RBX::RunService>()->getPhysicsJob()->addCoordinator(sequence);
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::doUnbindWorkspace()
|
||||
{
|
||||
if(view)
|
||||
view->bindWorkspace(boost::shared_ptr<RBX::DataModel>());
|
||||
}
|
||||
|
||||
void RobloxView::initializeInput()
|
||||
{
|
||||
userInput.reset(new UserInput(this, game));
|
||||
|
||||
if(userInput)
|
||||
{
|
||||
DataModel::LegacyLock lock(game->getDataModel(), DataModelJob::Write);
|
||||
|
||||
ControllerService* service = ServiceProvider::create<ControllerService>(game->getDataModel().get());
|
||||
service->setHardwareDevice(userInput.get());
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::stopJobs()
|
||||
{
|
||||
if (renderJob)
|
||||
{
|
||||
renderJob->abortRender();
|
||||
|
||||
boost::function<void()> callback = boost::bind(&RBX::FunctionMarshaller::ProcessMessages, marshaller);
|
||||
RBX::TaskScheduler::singleton().removeBlocking(renderJob, callback);
|
||||
}
|
||||
|
||||
if (userInputJob)
|
||||
{
|
||||
boost::function<void()> callback = boost::bind(&FunctionMarshaller::ProcessMessages, marshaller);
|
||||
TaskScheduler::singleton().removeBlocking(userInputJob, 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();
|
||||
userInputJob.reset();
|
||||
}
|
||||
|
||||
void RobloxView::doShutdownDataModel()
|
||||
{
|
||||
if(game)
|
||||
{
|
||||
{
|
||||
RBX::DataModel::LegacyLock lock(game->getDataModel(), RBX::DataModelJob::Write);
|
||||
|
||||
marshaller->Submit(boost::bind(&RobloxView::doUnbindWorkspace, this));
|
||||
marshaller->ProcessMessages();
|
||||
|
||||
if(shared_ptr<DataModel> dataModel = game->getDataModel())
|
||||
{
|
||||
if (RBX::RunService* runService = dataModel->find<RBX::RunService>())
|
||||
runService->stopTasks();
|
||||
|
||||
if(RBX::ControllerService* service = RBX::ServiceProvider::create<RBX::ControllerService>(dataModel.get()))
|
||||
service->setHardwareDevice(NULL);
|
||||
}
|
||||
|
||||
if (sequence)
|
||||
{
|
||||
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
|
||||
if (RBX::RunService* rs = dataModel->find<RBX::RunService>())
|
||||
rs->getPhysicsJob()->removeCoordinator(sequence);
|
||||
}
|
||||
|
||||
stopJobs();
|
||||
|
||||
marshaller->ProcessMessages();
|
||||
|
||||
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
|
||||
{
|
||||
userInput.reset();
|
||||
|
||||
leaveGameVerb.reset();
|
||||
fullscreenVerb.reset();
|
||||
studioVerb.reset();
|
||||
screenshotVerb.reset();
|
||||
shutdownClientVerb.reset();
|
||||
}
|
||||
|
||||
game->shutdown();
|
||||
}
|
||||
|
||||
game->shutdown();
|
||||
}
|
||||
|
||||
// marshaller has not finished unbinding workspace, wait until this is finished
|
||||
if(view->getDataModel())
|
||||
{
|
||||
while (view->getDataModel())
|
||||
{
|
||||
sleep(0.016667f);
|
||||
}
|
||||
}
|
||||
|
||||
RBXASSERT(!view->getDataModel());
|
||||
}
|
||||
|
||||
RobloxView::~RobloxView(void)
|
||||
{
|
||||
if (sequence)
|
||||
{
|
||||
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
|
||||
if (RBX::RunService* rs = dataModel->find<RBX::RunService>())
|
||||
rs->getPhysicsJob()->removeCoordinator(sequence);
|
||||
}
|
||||
|
||||
stopJobs();
|
||||
|
||||
if( shared_ptr<RBX::DataModel> dataModel = game->getDataModel() )
|
||||
{
|
||||
RBX::DataModel::LegacyLock lock(dataModel, RBX::DataModelJob::Write);
|
||||
|
||||
RBX::ControllerService* service = RBX::ServiceProvider::create<RBX::ControllerService>(dataModel.get());
|
||||
service->setHardwareDevice(NULL);
|
||||
|
||||
view->bindWorkspace(boost::shared_ptr<RBX::DataModel>());
|
||||
|
||||
userInput.reset();
|
||||
|
||||
leaveGameVerb.reset();
|
||||
fullscreenVerb.reset();
|
||||
studioVerb.reset();
|
||||
screenshotVerb.reset();
|
||||
shutdownClientVerb.reset();
|
||||
|
||||
}
|
||||
|
||||
RBX::FunctionMarshaller::ReleaseWindow(marshaller);
|
||||
|
||||
// First destroy the view before closing the DataModel
|
||||
view.reset();
|
||||
|
||||
Roblox::relinquishGame(game);
|
||||
}
|
||||
|
||||
void RobloxView::bindToWorkspace()
|
||||
{
|
||||
shared_ptr<RBX::DataModel> dataModel = game->getDataModel();
|
||||
if(!dataModel)
|
||||
return;
|
||||
|
||||
// Note that this code needs to be thread-sensitive
|
||||
RBX::DataModel::LegacyLock lock(dataModel, RBX::DataModelJob::Write);
|
||||
|
||||
view->bindWorkspace(dataModel);
|
||||
view->buildGui();
|
||||
|
||||
if (userInput)
|
||||
{
|
||||
RBX::ControllerService* service = RBX::ServiceProvider::create<RBX::ControllerService>(dataModel.get());
|
||||
service->setHardwareDevice(userInput.get());
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::setBounds(unsigned int width, unsigned int height)
|
||||
{
|
||||
this->width = width; this->height = height;
|
||||
if (view)
|
||||
{
|
||||
view->onResize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
static void executeScript(boost::shared_ptr<RBX::Game> game, std::string urlScript, bool isApp, bool isFromProtocolHandler)
|
||||
{
|
||||
RBX::Security::Impersonator impersonate(RBX::Security::COM);
|
||||
|
||||
std::ostringstream data;
|
||||
if (RBX::ContentProvider::isUrl(urlScript))
|
||||
{
|
||||
RBX::DataModel::LegacyLock lock(game->getDataModel(), RBX::DataModelJob::Write);
|
||||
std::auto_ptr<std::istream> stream(RBX::ServiceProvider::create<RBX::ContentProvider>(game->getDataModel().get())->getContent(RBX::ContentId(urlScript.c_str())));
|
||||
boost::iostreams::copy(*stream, data);
|
||||
}
|
||||
else
|
||||
return; // silent failure is harder to hack :)
|
||||
|
||||
RBX::ProtectedString verifiedSource;
|
||||
|
||||
try
|
||||
{
|
||||
verifiedSource = ProtectedString::fromTrustedSource(data.str());
|
||||
ContentProvider::verifyScriptSignature(verifiedSource, true);
|
||||
}
|
||||
catch(std::bad_alloc&)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch(std::exception&)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
shared_ptr<RBX::DataModel> dm = game->getDataModel();
|
||||
|
||||
if(!dm)
|
||||
return;
|
||||
|
||||
RBX::DataModel::LegacyLock lock(dm, RBX::DataModelJob::Write);
|
||||
|
||||
if (dm->isClosed())
|
||||
return;
|
||||
|
||||
if (urlScript.find("join.ashx"))
|
||||
{
|
||||
std::string data = verifiedSource.getSource();
|
||||
int firstNewLineIndex = data.find("\r\n");
|
||||
if (data[firstNewLineIndex+2] == '{')
|
||||
{
|
||||
// TODO: create shared enum between PC and Mac
|
||||
// Values taken from SharedLauncher::LaunchMode
|
||||
int launchMode = 0;
|
||||
if (isFromProtocolHandler)
|
||||
launchMode = 1;
|
||||
|
||||
game->configurePlayer(RBX::Security::COM, data.substr(firstNewLineIndex+2), launchMode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
RBX::ScriptContext* context = dm->create<RBX::ScriptContext>();
|
||||
context->executeInNewThread(RBX::Security::COM, verifiedSource, "Start Game");
|
||||
}
|
||||
|
||||
boost::shared_ptr<RBX::Game> RobloxView::startGame(std::string urlScript, const bool isApp) {
|
||||
|
||||
boost::shared_ptr<RBX::Game> game(Roblox::getpreloadedGame(isApp));
|
||||
boost::thread(boost::bind(&executeScript, game, urlScript, isApp, false));
|
||||
return game;
|
||||
}
|
||||
|
||||
RobloxView *RobloxView::start_game(void *wnd, void *appwnd, std::string urlScript, const bool isApp)
|
||||
{
|
||||
boost::shared_ptr<RBX::Game> game = startGame(urlScript, isApp);
|
||||
|
||||
return new RobloxView(wnd, appwnd, game);
|
||||
}
|
||||
|
||||
RobloxView *RobloxView::init_game(void *wnd, void* appwnd, const bool isApp)
|
||||
{
|
||||
boost::shared_ptr<RBX::Game> game(Roblox::getpreloadedGame(isApp));
|
||||
return new RobloxView(wnd, appwnd, game);
|
||||
}
|
||||
|
||||
void RobloxView::executeJoinScript(const std::string& urlScript, const bool isApp, const bool isFromProtocolHandler)
|
||||
{
|
||||
boost::thread(boost::bind(&executeScript, game, urlScript, isApp, isFromProtocolHandler));
|
||||
}
|
||||
|
||||
void RobloxView::setUIMessage(const std::string& message)
|
||||
{
|
||||
if (shared_ptr<DataModel> dm = game->getDataModel())
|
||||
dm->submitTask(boost::bind(&RobloxView::setUIMessage_, this, message), DataModelJob::Write);
|
||||
}
|
||||
|
||||
void RobloxView::setUIMessage_(const std::string& message)
|
||||
{
|
||||
if (shared_ptr<DataModel> dm = game->getDataModel())
|
||||
{
|
||||
if (message.length() > 0)
|
||||
{
|
||||
dm->setUiMessage(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
dm->clearUiMessage();
|
||||
}
|
||||
|
||||
if (GuiService*gs = dm->create<GuiService>())
|
||||
gs->setUiMessage(GuiService::UIMESSAGE_INFO, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
#pragma once
|
||||
|
||||
#include "boost/shared_ptr.hpp"
|
||||
#include "boost/scoped_ptr.hpp"
|
||||
#include "boost/thread.hpp"
|
||||
#include "v8datamodel/game.h"
|
||||
#include "Util/KeyCode.h"
|
||||
#include "G3D/Vector2.h"
|
||||
#include "rbx/signal.h"
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
class DataModel;
|
||||
class ViewBase;
|
||||
class FunctionMarshaller;
|
||||
namespace Tasks
|
||||
{
|
||||
class Sequence;
|
||||
}
|
||||
|
||||
namespace Reflection
|
||||
{
|
||||
class PropertyDescriptor;
|
||||
}
|
||||
}
|
||||
|
||||
class UserInput;
|
||||
|
||||
class RobloxView
|
||||
{
|
||||
|
||||
boost::scoped_ptr<RBX::ViewBase> view;
|
||||
boost::shared_ptr<RBX::Game> game;
|
||||
boost::scoped_ptr<UserInput> userInput;
|
||||
boost::scoped_ptr<class LeaveGameVerb> leaveGameVerb;
|
||||
boost::scoped_ptr<RBX::Verb> fullscreenVerb;
|
||||
boost::scoped_ptr<RBX::Verb> studioVerb;
|
||||
boost::scoped_ptr<RBX::Verb> screenshotVerb;
|
||||
boost::shared_ptr<class ShutdownClientVerb> shutdownClientVerb;
|
||||
|
||||
RBX::FunctionMarshaller* marshaller;
|
||||
|
||||
rbx::signals::scoped_connection placeIDChangeConnection;
|
||||
|
||||
void *viewWindow;
|
||||
void *appWindow;
|
||||
|
||||
boost::shared_ptr<RBX::Tasks::Sequence> sequence;
|
||||
|
||||
class RenderJob;
|
||||
class UserInputJob;
|
||||
boost::shared_ptr<RenderJob> renderJob;
|
||||
boost::shared_ptr<UserInputJob> userInputJob;
|
||||
|
||||
static boost::shared_ptr<RobloxView> rbxView;
|
||||
|
||||
static boost::shared_ptr<RBX::Game> startGame(std::string urlScript, const bool isApp);
|
||||
|
||||
void doTeleport(std::string url, std::string ticket, std::string script);
|
||||
|
||||
void onPlaceIDChanged(const RBX::Reflection::PropertyDescriptor* desc);
|
||||
|
||||
void doUnbindWorkspace();
|
||||
|
||||
void initializeInput();
|
||||
|
||||
public:
|
||||
|
||||
// N.B.: These are clones of other Roblox defines, need to be here to get around Objective-C vs. Cpp issues.
|
||||
// The values aren't literally duplicated, they are transduced by switch statements. So this is safe-- but annoying. --TP
|
||||
enum EventType { NO_EVENT,
|
||||
MOUSE_RIGHT_BUTTON_DOWN,
|
||||
MOUSE_RIGHT_BUTTON_UP,
|
||||
MOUSE_LEFT_BUTTON_DOWN,
|
||||
MOUSE_LEFT_BUTTON_UP,
|
||||
MOUSE_MOVE,
|
||||
MOUSE_DELTA,
|
||||
MOUSE_IDLE, // during runtime, this event is guaranteed every step if no other mouse event
|
||||
MOUSE_WHEEL_FORWARD,
|
||||
MOUSE_WHEEL_BACKWARD,
|
||||
KEY_DOWN,
|
||||
KEY_UP,
|
||||
KILLFOCUS,
|
||||
SETFOCUS,
|
||||
// these can come later - for now, when moving out of a window,
|
||||
// just make sure a mouse up comes along if the mouse was previously down...??
|
||||
// MOUSE_DOWN_DOUBLE_CLICK,
|
||||
// MOUSE_MOVE_OUT_WINDOW
|
||||
MOUSE_MIDDLE_BUTTON_DOWN,
|
||||
MOUSE_MIDDLE_BUTTON_UP
|
||||
};
|
||||
|
||||
RobloxView(void *viewwnd, void *appwnd, boost::shared_ptr<RBX::Game> game);
|
||||
~RobloxView(void);
|
||||
|
||||
void handleUserMessage(EventType event, unsigned int wParam, unsigned int lParam);
|
||||
void handleFocus(bool focus);
|
||||
void handleMouseInside(bool inside);
|
||||
void handleMouse(EventType event, int x, int y, unsigned int modifiers);
|
||||
void handleKey(EventType event, RBX::KeyCode keyCode, RBX::ModCode modifiers);
|
||||
void handleScrollWheel(float delta, int x, int y);
|
||||
void processInput();
|
||||
|
||||
// request a shutdown from the game
|
||||
Boolean requestShutdownClient();
|
||||
|
||||
// Some Cocoa/windowing help (methods defined in RobloxView.mm)
|
||||
void getCursorPos(G3D::Vector2 *pPos);
|
||||
void setCursorPos(G3D::Vector2 pPos);
|
||||
void marshalTeleport(std::string url, std::string ticket, std::string script);
|
||||
|
||||
// Game verbs
|
||||
void leaveGame();
|
||||
void handleLeaveGame();
|
||||
void shutdownClient();
|
||||
void handleShutdownClient();
|
||||
void toggleFullScreen();
|
||||
void handleToggleFullScreen();
|
||||
|
||||
bool isFullscreenMode();
|
||||
|
||||
void doShutdownDataModel();
|
||||
|
||||
void stopJobs();
|
||||
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
void setBounds(unsigned int width, unsigned int height);
|
||||
|
||||
static RobloxView *start_game(void *wnd, void *appwnd, std::string urlScript, const bool isApp);
|
||||
static RobloxView *init_game(void *wnd, void* appwnd, const bool isApp);
|
||||
|
||||
void executeJoinScript(const std::string& urlScript, const bool isApp, const bool isFromProtocolHandler);
|
||||
void setUIMessage(const std::string& message);
|
||||
|
||||
boost::shared_ptr<RBX::DataModel> getDataModel() { return game ? game->getDataModel() : boost::shared_ptr<RBX::DataModel>(); }
|
||||
private:
|
||||
void bindToWorkspace();
|
||||
void defineConcurrencyRules();
|
||||
void setUIMessage_(const std::string& message);
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// RobloxView.mm
|
||||
// MacClient
|
||||
//
|
||||
// Created by Tony on 2/9/11.
|
||||
// Copyright 2011 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <ApplicationServices/ApplicationServices.h>
|
||||
|
||||
#import "RobloxOgreView.h"
|
||||
|
||||
#include "RobloxView.h"
|
||||
#include "FunctionMarshaller.h"
|
||||
#include "RobloxPlayerAppDelegate.h"
|
||||
|
||||
inline void flipY(NSView *view, NSPoint &pt)
|
||||
{
|
||||
BOOL flipped = [view isFlipped];
|
||||
|
||||
if (!flipped)
|
||||
{
|
||||
NSRect bounds = [view bounds];
|
||||
CGFloat height = bounds.size.height;
|
||||
|
||||
pt.y = height - pt.y;
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::getCursorPos(G3D::Vector2 *pPos)
|
||||
{
|
||||
if(RobloxOgreView* ogreView = (RobloxOgreView*) viewWindow)
|
||||
{
|
||||
CGPoint virtualCursorPos = [ogreView getVirtualCursorPos];
|
||||
pPos->x = virtualCursorPos.x;
|
||||
pPos->y = virtualCursorPos.y;
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxView::setCursorPos(G3D::Vector2 pPos)
|
||||
{
|
||||
if(RobloxOgreView* ogreView = (RobloxOgreView*) viewWindow)
|
||||
[ogreView setVirtualCursorPos:CGPointMake(G3D::iRound(pPos.x), G3D::iRound(pPos.y))];
|
||||
}
|
||||
|
||||
void RobloxView::marshalTeleport(std::string url, std::string ticket, std::string script)
|
||||
{
|
||||
marshaller->Submit(boost::bind(&RobloxView::doTeleport, this, url, ticket, script));
|
||||
}
|
||||
|
||||
void RobloxView::doTeleport(std::string url, std::string ticket, std::string script)
|
||||
{
|
||||
RobloxPlayerAppDelegate *appDelegate = (RobloxPlayerAppDelegate*)appWindow;
|
||||
|
||||
NSString *ticketString = [NSString stringWithCString:ticket.c_str() encoding:[NSString defaultCStringEncoding]];
|
||||
NSString *urlString = [NSString stringWithCString:url.c_str() encoding:[NSString defaultCStringEncoding]];
|
||||
NSString *scriptString = [NSString stringWithCString:script.c_str() encoding:[NSString defaultCStringEncoding]];
|
||||
|
||||
[appDelegate teleport:ticketString withAuthentication:urlString withScript:scriptString];
|
||||
}
|
||||
|
||||
bool RobloxView::isFullscreenMode()
|
||||
{
|
||||
if(RobloxOgreView* ogreView = (RobloxOgreView*) viewWindow)
|
||||
return ogreView.fullScreen;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* StdAfx.h
|
||||
* MacClient
|
||||
*
|
||||
* Created by David York on 7/3/12.
|
||||
* Copyright 2012 ROBLOX. All rights reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
#include "Util/Rect.h"
|
||||
#include "Util/NavKeys.h"
|
||||
#include "Util/Standardout.h"
|
||||
#include "Util/NavKeys.h"
|
||||
#include "v8datamodel/ContentProvider.h"
|
||||
#include "V8DataModel/Workspace.h"
|
||||
#include "V8DataModel/GameSettings.h"
|
||||
#include "V8DataModel/UserInputService.h"
|
||||
#include "RbxG3D/RbxTime.h"
|
||||
#include "G3D/System.h"
|
||||
#include "UserInput.h"
|
||||
#include "V8DataModel/GameSettings.h"
|
||||
#include "V8DataModel/GameBasicSettings.h"
|
||||
#include "RobloxView.h"
|
||||
#include "V8DataModel/SleepingJob.h"
|
||||
#include "V8DataModel/DataModelJob.h"
|
||||
|
||||
#define DXINPUT_TRACE __noop
|
||||
|
||||
DYNAMIC_FASTFLAGVARIABLE(MouseDeltaWhenNotMouseLocked, false)
|
||||
|
||||
FASTFLAG(UserAllCamerasInLua)
|
||||
|
||||
namespace RBX
|
||||
{
|
||||
class DataModel;
|
||||
}
|
||||
|
||||
class UserInputJob : public RBX::SleepingJob
|
||||
{
|
||||
private:
|
||||
UserInput* const userInput;
|
||||
|
||||
void processQueue()
|
||||
{
|
||||
Event event;
|
||||
while(queue.pop_if_present(event))
|
||||
{
|
||||
userInput->ProcessUserInputMessage(event.eventType, event.eventState, event.wParam, event.lParam);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
struct Event
|
||||
{
|
||||
RBX::InputObject::UserInputType eventType;
|
||||
RBX::InputObject::UserInputState eventState;
|
||||
unsigned int wParam;
|
||||
unsigned int lParam;
|
||||
};
|
||||
rbx::safe_queue<Event> queue;
|
||||
|
||||
UserInputJob(UserInput* userInput, shared_ptr<RBX::DataModelArbiter> arbiter)
|
||||
:RBX::SleepingJob("UserInput", RBX::DataModelJob::Write, false, arbiter, RBX::Time::Interval(0.01), 60)
|
||||
,userInput(userInput)
|
||||
{}
|
||||
|
||||
virtual RBX::TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
|
||||
{
|
||||
RBX::DataModel::scoped_write_request request( userInput->game->getDataModel().get() );
|
||||
processQueue();
|
||||
|
||||
sleep();
|
||||
|
||||
return RBX::TaskScheduler::Stepped;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
UserInput::UserInput(RobloxView *wnd, shared_ptr<RBX::Game> newGame)
|
||||
: externallyForcedKeyDown(0),
|
||||
isMouseCaptured(false),
|
||||
isMouseInside(false),
|
||||
wnd(wnd),
|
||||
leftMouseButtonDown(false),
|
||||
rightMouseDown(false),
|
||||
autoMouseMove(false),
|
||||
posToWrapTo(0,0),
|
||||
wrapping(false)
|
||||
{
|
||||
setGame(newGame);
|
||||
}
|
||||
|
||||
|
||||
void UserInput::setGame(shared_ptr<RBX::Game> newGame)
|
||||
{
|
||||
game = newGame;
|
||||
if(shared_ptr<RBX::DataModel> dataModel = game->getDataModel())
|
||||
{
|
||||
renderStepConnection.disconnect();
|
||||
|
||||
job.reset(new UserInputJob(this, dataModel));
|
||||
RBX::TaskScheduler::singleton().add(job);
|
||||
|
||||
sdlGameController = shared_ptr<SDLGameController>(new SDLGameController(dataModel));
|
||||
|
||||
if (RBX::RunService* runService = RBX::ServiceProvider::create<RBX::RunService>(dataModel.get()))
|
||||
{
|
||||
renderStepConnection = runService->earlyRenderSignal.connect(boost::bind(&UserInput::processInput, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UserInput::~UserInput()
|
||||
{
|
||||
sdlGameController.reset();
|
||||
|
||||
RBX::TaskScheduler::singleton().removeBlocking(job);
|
||||
}
|
||||
|
||||
void UserInput::PostUserInputMessage(RBX::InputObject::UserInputType eventType, RBX::InputObject::UserInputState eventState, unsigned int wParam, unsigned int lParam)
|
||||
{
|
||||
BufferedEvent bufferedEvent;
|
||||
bufferedEvent.userInputType = eventType;
|
||||
bufferedEvent.userInputState = eventState;
|
||||
bufferedEvent.wParam = wParam;
|
||||
bufferedEvent.lParam = lParam;
|
||||
|
||||
boost::mutex::scoped_lock lock(bufferMutex);
|
||||
|
||||
bufferedEvents.push_back(bufferedEvent);
|
||||
}
|
||||
|
||||
void UserInput::processInput()
|
||||
{
|
||||
std::vector<BufferedEvent> tempVec;
|
||||
{
|
||||
boost::mutex::scoped_lock lock(bufferMutex);
|
||||
bufferedEvents.swap(tempVec);
|
||||
}
|
||||
|
||||
for (std::vector<BufferedEvent>::iterator iter = tempVec.begin(); iter != tempVec.end(); ++iter)
|
||||
{
|
||||
ProcessUserInputMessage(iter->userInputType, iter->userInputState, iter->wParam, iter->lParam);
|
||||
}
|
||||
}
|
||||
|
||||
extern void MacWriteFastLogDump();
|
||||
|
||||
void UserInput::ProcessUserInputMessage(RBX::InputObject::UserInputType eventType, RBX::InputObject::UserInputState eventState, unsigned int wParam, unsigned int lParam)
|
||||
{
|
||||
RobloxCritSecLoc lock(diSection, "ProcessUserInputMessage");
|
||||
|
||||
bool cursorMoved = false;
|
||||
bool leftMouseUp = false;
|
||||
RBX::Vector2 wrapMouseDelta;
|
||||
G3D::Vector2 mouseDelta;
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case RBX::InputObject::TYPE_KEYBOARD:
|
||||
{
|
||||
if (wParam == RBX::SDLK_F8 && lParam == RBX::KMOD_NONE)
|
||||
MacWriteFastLogDump();
|
||||
|
||||
const RBX::ModCode modCode = (RBX::ModCode) lParam;
|
||||
const bool keyDown = (eventState == RBX::InputObject::INPUT_STATE_BEGIN);
|
||||
const char key = RBX::UserInputService::getModifiedKey((RBX::KeyCode) wParam,(RBX::ModCode) lParam);
|
||||
|
||||
if(shared_ptr<RBX::DataModel> dataModel = game->getDataModel())
|
||||
if(RBX::UserInputService* userInputService = RBX::ServiceProvider::find<RBX::UserInputService>(dataModel.get()))
|
||||
userInputService->setKeyState((RBX::KeyCode) wParam, modCode, key, keyDown);
|
||||
|
||||
RBX::KeyCode keyCode = (RBX::KeyCode) wParam;
|
||||
|
||||
if(eventState == RBX::InputObject::INPUT_STATE_END && keyboardInputObjects.find(keyCode) != keyboardInputObjects.end())
|
||||
{
|
||||
shared_ptr<RBX::InputObject> keyInput = keyboardInputObjects[keyCode];
|
||||
keyInput->setInputState(RBX::InputObject::INPUT_STATE_END);
|
||||
keyInput->mod = (RBX::ModCode) lParam;
|
||||
keyInput->modifiedKey = key;
|
||||
|
||||
sendEvent(keyInput);
|
||||
keyboardInputObjects.erase(keyCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
shared_ptr<RBX::InputObject> keyInput = RBX::Creatable<RBX::Instance>::create<RBX::InputObject>(eventType, eventState, keyCode, (RBX::ModCode) lParam, key, game->getDataModel().get());
|
||||
keyboardInputObjects[keyCode] = keyInput;
|
||||
sendEvent(keyInput);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case RBX::InputObject::TYPE_MOUSEBUTTON1:
|
||||
case RBX::InputObject::TYPE_MOUSEBUTTON2:
|
||||
case RBX::InputObject::TYPE_MOUSEBUTTON3: // Intentional fall thru (todo: lets clean this up)
|
||||
{
|
||||
RBX::InputObject event(eventType, eventState, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0), game->getDataModel().get());
|
||||
RBXASSERT(event.isLeftMouseDownEvent() || event.isLeftMouseUpEvent() || event.isRightMouseDownEvent() || event.isRightMouseUpEvent() || event.isMiddleMouseDownEvent() || event.isMiddleMouseUpEvent());
|
||||
|
||||
G3D::Vector2 pos((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
|
||||
sendMouseEvent(eventType, eventState, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0));
|
||||
|
||||
if ( event.isLeftMouseDownEvent() )
|
||||
{
|
||||
leftMouseButtonDown = true;
|
||||
leftMouseUp = false;
|
||||
isMouseCaptured = true;
|
||||
}
|
||||
if ( event.isLeftMouseUpEvent() )
|
||||
{
|
||||
leftMouseButtonDown = false;
|
||||
leftMouseUp = true;
|
||||
isMouseCaptured = false;
|
||||
}
|
||||
if ( event.isRightMouseDownEvent() )
|
||||
rightMouseDown = true;
|
||||
if ( event.isRightMouseUpEvent() )
|
||||
rightMouseDown = false;
|
||||
|
||||
break;
|
||||
}
|
||||
case RBX::InputObject::TYPE_MOUSEMOVEMENT:
|
||||
{
|
||||
mouseDelta = G3D::Vector2((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
|
||||
|
||||
autoMouseMove = false;
|
||||
cursorMoved = true;
|
||||
|
||||
accumulatedFractionalMouseDelta += mouseDelta * RBX::GameBasicSettings::singleton().getMouseSensitivity();
|
||||
mouseDelta.x = (int) accumulatedFractionalMouseDelta.x;
|
||||
mouseDelta.y = (int) accumulatedFractionalMouseDelta.y;
|
||||
accumulatedFractionalMouseDelta -= mouseDelta;
|
||||
|
||||
doWrapMouse(mouseDelta, wrapMouseDelta);
|
||||
|
||||
break;
|
||||
}
|
||||
case RBX::InputObject::TYPE_MOUSEWHEEL:
|
||||
{
|
||||
int zDelta = (int) wParam; // wheel rotation
|
||||
G3D::Vector2 pos((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
|
||||
sendMouseEvent(RBX::InputObject::TYPE_MOUSEWHEEL,
|
||||
RBX::InputObject::INPUT_STATE_CHANGE,
|
||||
RBX::Vector3(pos.x,pos.y,zDelta),
|
||||
RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0));
|
||||
break;
|
||||
}
|
||||
case RBX::InputObject::TYPE_FOCUS:
|
||||
{
|
||||
if(eventState == RBX::InputObject::INPUT_STATE_BEGIN)
|
||||
DXINPUT_TRACE(std::string("UserInput::WM_SETFOCUS\n").c_str());
|
||||
else
|
||||
DXINPUT_TRACE(std::string("UserInput::WM_KILLFOCUS\n").c_str());
|
||||
|
||||
shared_ptr<RBX::InputObject> event = RBX::Creatable<RBX::Instance>::create<RBX::InputObject>(RBX::InputObject::TYPE_FOCUS,eventState,game->getDataModel().get());
|
||||
sendEvent(event);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/////////// DONE PROCESSING ////////////////
|
||||
shared_ptr<RBX::DataModel> dataModel = game->getDataModel();
|
||||
|
||||
if (wrapMouseDelta != RBX::Vector2::zero())
|
||||
{
|
||||
if (RBX::Workspace* workspace = dataModel->getWorkspace())
|
||||
{
|
||||
if (!FFlag::UserAllCamerasInLua || !workspace->getCamera()->hasClientPlayer())
|
||||
{
|
||||
if ( workspace->getCamera()->getCameraType() != RBX::Camera::CUSTOM_CAMERA )
|
||||
{
|
||||
workspace->onWrapMouse(wrapMouseDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendMouseEvent(RBX::InputObject::TYPE_MOUSEDELTA, RBX::InputObject::INPUT_STATE_CHANGE, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0));
|
||||
sendMouseEvent(RBX::InputObject::TYPE_MOUSEMOVEMENT, RBX::InputObject::INPUT_STATE_CHANGE, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0));
|
||||
}
|
||||
else if (cursorMoved)
|
||||
{
|
||||
if (DFFlag::MouseDeltaWhenNotMouseLocked)
|
||||
{
|
||||
sendMouseEvent(RBX::InputObject::TYPE_MOUSEMOVEMENT, RBX::InputObject::INPUT_STATE_CHANGE, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(mouseDelta.x, mouseDelta.y, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
sendMouseEvent(RBX::InputObject::TYPE_MOUSEMOVEMENT, RBX::InputObject::INPUT_STATE_CHANGE, RBX::Vector3(getCursorPosition(),0), RBX::Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UserInput::sendMouseEvent(RBX::InputObject::UserInputType mouseEventType, RBX::InputObject::UserInputState mouseEventState, const RBX::Vector3& position, const RBX::Vector3& delta)
|
||||
{
|
||||
shared_ptr<RBX::InputObject> mouseEventObject;
|
||||
if( inputObjectMap.find(mouseEventType) == inputObjectMap.end() )
|
||||
{
|
||||
mouseEventObject = RBX::Creatable<RBX::Instance>::create<RBX::InputObject>(mouseEventType, mouseEventState, position, delta, game->getDataModel().get());
|
||||
inputObjectMap[mouseEventType] = mouseEventObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
mouseEventObject = inputObjectMap[mouseEventType];
|
||||
mouseEventObject->setInputState(mouseEventState);
|
||||
mouseEventObject->setPosition(position);
|
||||
mouseEventObject->setDelta(delta);
|
||||
inputObjectMap[mouseEventType] = mouseEventObject;
|
||||
}
|
||||
sendEvent(mouseEventObject);
|
||||
}
|
||||
|
||||
void UserInput::sendEvent(const shared_ptr<RBX::InputObject>& event)
|
||||
{
|
||||
if (shared_ptr<RBX::DataModel> dataModel = game->getDataModel())
|
||||
{
|
||||
if ( RBX::UserInputService* inputService = RBX::ServiceProvider::find<RBX::UserInputService>(dataModel.get()) )
|
||||
{
|
||||
inputService->fireInputEvent(event, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool UserInput::isFullScreenMode() const
|
||||
{
|
||||
return wnd->isFullscreenMode();
|
||||
}
|
||||
|
||||
G3D::Rect2D UserInput::getWindowRect() const // { return windowRect; }
|
||||
{
|
||||
G3D::Rect2D answer(G3D::Vector2(wnd->width, wnd->height));
|
||||
return answer;
|
||||
}
|
||||
|
||||
G3D::Vector2int16 UserInput::getWindowSize() const
|
||||
{
|
||||
G3D::Rect2D windowRect = getWindowRect();
|
||||
return G3D::Vector2int16((G3D::int16) windowRect.width(), (G3D::int16) windowRect.height());
|
||||
}
|
||||
|
||||
RBX::TextureProxyBaseRef UserInput::getGameCursor(RBX::Adorn* adorn)
|
||||
{
|
||||
return UserInputBase::getGameCursor(adorn);
|
||||
}
|
||||
|
||||
void UserInput::doWrapHybrid(bool cursorMoved, bool leftMouseUp, G3D::Vector2& wrapMouseDelta, G3D::Vector2& wrapMousePosition, G3D::Vector2& posToWrapTo)
|
||||
{
|
||||
}
|
||||
|
||||
bool UserInput::movementKeysDown()
|
||||
{
|
||||
return ( keyDownInternal(RBX::SDLK_w) || keyDownInternal(RBX::SDLK_s) || keyDownInternal(RBX::SDLK_a) || keyDownInternal(RBX::SDLK_d)
|
||||
|| keyDownInternal(RBX::SDLK_UP)|| keyDownInternal(RBX::SDLK_DOWN) || keyDownInternal(RBX::SDLK_LEFT) || keyDownInternal(RBX::SDLK_RIGHT) );
|
||||
}
|
||||
|
||||
// will be called repeatedly. ignore repeated calls.
|
||||
void UserInput::onMouseInside()
|
||||
{
|
||||
G3D::Vector2 pos;
|
||||
wnd->getCursorPos(&pos);
|
||||
wrapMousePosition = pos - getWindowRect().center(); // i.e. - pull towards the origin - remove chatter
|
||||
|
||||
if(isMouseInside)
|
||||
return; // we know. ignore.
|
||||
|
||||
RobloxCritSecLoc lock(diSection, "onMouseEnter");
|
||||
|
||||
isMouseInside = true;
|
||||
|
||||
};
|
||||
|
||||
void UserInput::onMouseLeave()
|
||||
{
|
||||
if (!isMouseInside)
|
||||
return;
|
||||
|
||||
RobloxCritSecLoc lock(diSection, "onMouseLeave");
|
||||
|
||||
isMouseInside = false;
|
||||
};
|
||||
|
||||
void UserInput::centerCursor()
|
||||
{
|
||||
wrapMousePosition = RBX::Vector2::zero();
|
||||
wnd->setCursorPos(getWindowRect().center());
|
||||
}
|
||||
|
||||
G3D::Vector2 UserInput::getCursorPosition()
|
||||
{
|
||||
RobloxCritSecLoc lock(diSection, "getCursorPosition");
|
||||
|
||||
G3D::Vector2 pt;
|
||||
wnd->getCursorPos(&pt);
|
||||
return pt;
|
||||
}
|
||||
|
||||
|
||||
bool UserInput::keyDownInternal(RBX::KeyCode code) const
|
||||
{
|
||||
if (externallyForcedKeyDown && (code == externallyForcedKeyDown))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::unordered_map<RBX::KeyCode,shared_ptr<RBX::InputObject> >::const_iterator iter = keyboardInputObjects.find(code);
|
||||
if ( iter != keyboardInputObjects.end() )
|
||||
{
|
||||
if ( iter->second && iter->second.get() )
|
||||
{
|
||||
return (iter->second->getUserInputState() != RBX::InputObject::INPUT_STATE_END);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool UserInput::keyDown(RBX::KeyCode code) const
|
||||
{
|
||||
return keyDownInternal(code);
|
||||
}
|
||||
|
||||
void UserInput::setKeyState(RBX::KeyCode code, RBX::ModCode modCode, char modifiedKey, bool isDown)
|
||||
{
|
||||
RobloxCritSecLoc lock(diSection, "setKeyState");
|
||||
|
||||
externallyForcedKeyDown = isDown ? code : 0;
|
||||
}
|
||||
|
||||
|
||||
bool preventWrapMouse()
|
||||
{
|
||||
// unlikely to get here
|
||||
return true;
|
||||
}
|
||||
|
||||
void UserInput::doWrapMouse(const G3D::Vector2& delta, G3D::Vector2& wrapMouseDelta)
|
||||
{
|
||||
RBX::UserInputService* userInputService = RBX::ServiceProvider::find<RBX::UserInputService>(game->getDataModel().get());
|
||||
|
||||
if (!userInputService)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (userInputService->getMouseWrapMode())
|
||||
{
|
||||
case RBX::UserInputService::WRAP_NONEANDCENTER:
|
||||
{
|
||||
centerCursor();
|
||||
}
|
||||
case RBX::UserInputService::WRAP_NONE: // intentional fall thru
|
||||
case RBX::UserInputService::WRAP_CENTER:
|
||||
{
|
||||
UserInputUtil::wrapMouseCenter(delta,
|
||||
wrapMouseDelta,
|
||||
this->wrapMousePosition);
|
||||
break;
|
||||
}
|
||||
case RBX::UserInputService::WRAP_HYBRID:
|
||||
{
|
||||
UserInputUtil::wrapMousePos(delta,
|
||||
wrapMouseDelta,
|
||||
wrapMousePosition,
|
||||
getWindowSize(),
|
||||
posToWrapTo,
|
||||
autoMouseMove);
|
||||
break;
|
||||
}
|
||||
case RBX::UserInputService::WRAP_AUTO:
|
||||
{
|
||||
// 1. If movement keys are down - keep the mouse in the window
|
||||
// 2. If the left mouse button is down (we are dragging) - keep in the window
|
||||
if (movementKeysDown() || isMouseCaptured)
|
||||
{
|
||||
UserInputUtil::wrapMouseBorderLock(delta,
|
||||
wrapMouseDelta,
|
||||
this->wrapMousePosition,
|
||||
getWindowSize());
|
||||
}
|
||||
// 4. OK - we're in PlayMode (i.e. character)
|
||||
else if (isFullScreenMode())
|
||||
{
|
||||
UserInputUtil::wrapFullScreen(delta,
|
||||
wrapMouseDelta,
|
||||
this->wrapMousePosition,
|
||||
getWindowSize());
|
||||
}
|
||||
// We no longer want mouse wrap and camera auto-pan at the horizontal window extents.
|
||||
else
|
||||
{
|
||||
UserInputUtil::wrapMouseNone(delta,
|
||||
wrapMouseDelta,
|
||||
this->wrapMousePosition);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
wnd->setCursorPos(getWindowRect().center() + wrapMousePosition);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
#pragma once
|
||||
|
||||
#include "UserInputUtil.h"
|
||||
|
||||
#include "Util/UserInputBase.h"
|
||||
#include "Util/RunStateOwner.h"
|
||||
#include "Util/Rect.h"
|
||||
|
||||
#include "SDLGameController.h"
|
||||
|
||||
#include "GfxBase/TextureProxyBase.h"
|
||||
|
||||
namespace RBX {
|
||||
class Game;
|
||||
}
|
||||
|
||||
class RobloxCriticalSection
|
||||
{
|
||||
private:
|
||||
public:
|
||||
int callers;
|
||||
std::string recentCaller;
|
||||
std::string olderCaller;
|
||||
RobloxCriticalSection()
|
||||
: callers(0)
|
||||
{}
|
||||
};
|
||||
|
||||
class RobloxCritSecLoc
|
||||
{
|
||||
private:
|
||||
RobloxCriticalSection& robloxCriticalSection;
|
||||
|
||||
public:
|
||||
RobloxCritSecLoc( RobloxCriticalSection& cs, const std::string& location)
|
||||
: robloxCriticalSection(cs)
|
||||
{
|
||||
}
|
||||
|
||||
~RobloxCritSecLoc()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#define NKEYSTATES 512
|
||||
|
||||
struct BufferedEvent
|
||||
{
|
||||
RBX::InputObject::UserInputType userInputType;
|
||||
RBX::InputObject::UserInputState userInputState;
|
||||
unsigned int wParam;
|
||||
unsigned int lParam;
|
||||
};
|
||||
|
||||
class UserInputJob;
|
||||
class UserInput
|
||||
: public RBX::UserInputBase
|
||||
{
|
||||
private:
|
||||
shared_ptr<UserInputJob> job;
|
||||
|
||||
mutable RobloxCriticalSection diSection;
|
||||
|
||||
rbx::signals::scoped_connection renderStepConnection;
|
||||
|
||||
// Mouse Stuff
|
||||
bool isMouseCaptured; // poor man's tracker of button state
|
||||
bool leftMouseButtonDown;
|
||||
G3D::Vector2 wrapMousePosition; // in normalized coordinates (center is 0,0. radius is getWrapRadius)
|
||||
bool wrapping;
|
||||
bool rightMouseDown;
|
||||
bool autoMouseMove;
|
||||
|
||||
// Keyboard Stuff
|
||||
int externallyForcedKeyDown; // + this from exteranal source (like a gui button)
|
||||
|
||||
class RobloxView * wnd;
|
||||
|
||||
boost::mutex bufferMutex;
|
||||
std::vector<BufferedEvent> bufferedEvents;
|
||||
|
||||
// InputObject stuff
|
||||
boost::unordered_map<RBX::InputObject::UserInputType,shared_ptr<RBX::InputObject> > inputObjectMap;
|
||||
boost::unordered_map<RBX::KeyCode,shared_ptr<RBX::InputObject> > keyboardInputObjects;
|
||||
|
||||
////////////////////////////////
|
||||
//
|
||||
// Events
|
||||
void sendEvent(const shared_ptr<RBX::InputObject>& event);
|
||||
void sendMouseEvent(RBX::InputObject::UserInputType mouseEventType, RBX::InputObject::UserInputState mouseEventState, const RBX::Vector3& position, const RBX::Vector3& delta);
|
||||
|
||||
////////////////////////////////////
|
||||
//
|
||||
// Keyboard Mouse
|
||||
bool isMouseInside;
|
||||
|
||||
G3D::Vector2 posToWrapTo;
|
||||
G3D::Vector2 accumulatedFractionalMouseDelta;
|
||||
|
||||
|
||||
////////////////////////////////////
|
||||
// Gamepad Stuff
|
||||
shared_ptr<SDLGameController> sdlGameController;
|
||||
|
||||
// window stuff
|
||||
RBX::Vector2int16 getWindowSize() const;
|
||||
G3D::Rect2D getWindowRect() const;
|
||||
bool isFullScreenMode() const;
|
||||
bool movementKeysDown();
|
||||
bool keyDownInternal(RBX::KeyCode code) const;
|
||||
|
||||
void doWrapMouse(const G3D::Vector2& delta, G3D::Vector2& wrapMouseDelta);
|
||||
|
||||
G3D::Vector2 getGameCursorPositionInternal();
|
||||
G3D::Vector2 getGameCursorPositionExpandedInternal(); // prevent hysteresis
|
||||
G3D::Vector2 getWindowsCursorPositionInternal();
|
||||
RBX::Vector2 getCursorPositionInternal();
|
||||
|
||||
void processInput();
|
||||
|
||||
public:
|
||||
shared_ptr<RBX::Game> game;
|
||||
|
||||
////////////////////////////////
|
||||
//
|
||||
// UserInputBase
|
||||
|
||||
/*implement*/ RBX::Vector2 getCursorPosition();
|
||||
|
||||
void doWrapHybrid(bool cursorMoved, bool leftMouseUp, G3D::Vector2& wrapMouseDelta, G3D::Vector2& wrapMousePosition, G3D::Vector2& posToWrapTo);
|
||||
|
||||
/*implement*/ bool keyDown(RBX::KeyCode code) const;
|
||||
/*implement*/ void setKeyState(RBX::KeyCode code, RBX::ModCode modCode, char modifiedKey, bool isDown);
|
||||
|
||||
/*implement*/ void centerCursor();
|
||||
|
||||
/*override*/ RBX::TextureProxyBaseRef getGameCursor(RBX::Adorn* adorn);
|
||||
|
||||
void PostUserInputMessage(RBX::InputObject::UserInputType eventType, RBX::InputObject::UserInputState eventState, unsigned int wParam, unsigned int lParam);
|
||||
// Call this only within a DataModel lock:
|
||||
void ProcessUserInputMessage(RBX::InputObject::UserInputType eventType, RBX::InputObject::UserInputState eventState, unsigned int wParam, unsigned int lParam);
|
||||
|
||||
UserInput(RobloxView *wnd, shared_ptr<RBX::Game> newGame);
|
||||
~UserInput();
|
||||
|
||||
void setGame(shared_ptr<RBX::Game> newGame);
|
||||
|
||||
void onMouseInside();
|
||||
void onMouseLeave();
|
||||
};
|
||||
@@ -0,0 +1,234 @@
|
||||
|
||||
#include "UserInputUtil.h"
|
||||
#include "Util/Rect.h"
|
||||
#include "util/standardout.h"
|
||||
#include "V8DataModel/GameSettings.h"
|
||||
|
||||
using RBX::Vector2;
|
||||
|
||||
const float UserInputUtil::HybridSensitivity = 8.0f;
|
||||
const float UserInputUtil::MouseTug = 15.0f;
|
||||
|
||||
|
||||
// horizontal - will keep doing deltas
|
||||
// vertial - will peg inset 2 pixels
|
||||
//
|
||||
void UserInputUtil::wrapFullScreen(const Vector2& delta,
|
||||
Vector2& wrapMouseDelta,
|
||||
Vector2& wrapMousePosition,
|
||||
const Vector2& windowSize)
|
||||
{
|
||||
float wrapPositionY = wrapMousePosition.y + delta.y;
|
||||
wrapMouseBorderLock(delta, wrapMouseDelta, wrapMousePosition, windowSize);
|
||||
wrapMouseDelta.y = 0.0;
|
||||
wrapMousePosition.y = wrapPositionY;
|
||||
float halfHeight = (windowSize.y * 0.5);
|
||||
|
||||
float wrapPositionX = wrapMousePosition.x;
|
||||
float halfWidth = (windowSize.x * 0.5);
|
||||
wrapMousePosition.x = G3D::clamp(wrapPositionX,-halfWidth,halfWidth);
|
||||
wrapMousePosition.y = G3D::clamp(wrapPositionY,-halfHeight,halfHeight);
|
||||
|
||||
// Setting this to zero prevents the camera from panning automatically near the extents of the screen.
|
||||
wrapMouseDelta = Vector2::zero();
|
||||
}
|
||||
|
||||
void UserInputUtil::wrapMouseHorizontalTransition(const Vector2& delta,
|
||||
Vector2& wrapMouseDelta,
|
||||
Vector2& wrapMousePosition,
|
||||
const Vector2& windowSize)
|
||||
{
|
||||
float wrapPositionY = wrapMousePosition.y + delta.y;
|
||||
wrapMouseBorderTransition(delta, wrapMouseDelta, wrapMousePosition, windowSize);
|
||||
wrapMouseDelta.y = 0.0;
|
||||
wrapMousePosition.y = wrapPositionY;
|
||||
}
|
||||
|
||||
|
||||
void UserInputUtil::wrapMouseBorder(const Vector2& delta,
|
||||
Vector2& wrapMouseDelta,
|
||||
Vector2& wrapMousePosition,
|
||||
const Vector2& windowSize,
|
||||
const int borderWidth,
|
||||
const float creepFactor)
|
||||
|
||||
{
|
||||
Vector2 halfSize = windowSize * 0.5;
|
||||
RBX::Rect inner = RBX::Rect(-halfSize, halfSize).inset(borderWidth); // in Wrap Coordinates
|
||||
Vector2 oldPosition = wrapMousePosition;
|
||||
inner.unionWith(oldPosition); // now union of the border and old position - ratchet
|
||||
|
||||
Vector2 newPositionUnclamped = oldPosition + delta;
|
||||
Vector2 newPositionClamped = inner.clamp(newPositionUnclamped);
|
||||
|
||||
Vector2 positiveDistanceInBorder = newPositionUnclamped - newPositionClamped;
|
||||
|
||||
wrapMousePosition = newPositionClamped + (positiveDistanceInBorder * creepFactor);
|
||||
wrapMouseDelta += positiveDistanceInBorder;
|
||||
}
|
||||
|
||||
void UserInputUtil::wrapMouseBorderLock(const Vector2& delta,
|
||||
Vector2& wrapMouseDelta,
|
||||
Vector2& wrapMousePosition,
|
||||
const Vector2& windowSize)
|
||||
{
|
||||
wrapMouseBorder(
|
||||
delta,
|
||||
wrapMouseDelta,
|
||||
wrapMousePosition,
|
||||
windowSize,
|
||||
6,
|
||||
0.0f);
|
||||
}
|
||||
|
||||
void UserInputUtil::wrapMouseBorderTransition(const Vector2& delta,
|
||||
Vector2& wrapMouseDelta,
|
||||
Vector2& wrapMousePosition,
|
||||
const Vector2& windowSize)
|
||||
{
|
||||
wrapMouseBorder(
|
||||
delta,
|
||||
wrapMouseDelta,
|
||||
wrapMousePosition,
|
||||
windowSize,
|
||||
20,
|
||||
0.05f);
|
||||
}
|
||||
|
||||
void UserInputUtil::wrapMouseNone(const Vector2& delta,
|
||||
Vector2& wrapMouseDelta,
|
||||
Vector2& wrapMousePosition)
|
||||
{
|
||||
wrapMouseDelta = Vector2::zero();
|
||||
wrapMousePosition += delta;
|
||||
}
|
||||
|
||||
void UserInputUtil::wrapMouseCenter(const Vector2& delta,
|
||||
Vector2& wrapMouseDelta,
|
||||
Vector2& wrapMousePosition)
|
||||
{
|
||||
wrapMouseDelta += delta;
|
||||
// don't move the cursor....
|
||||
// wrapMousePosition = G3D::Vector2::zero();
|
||||
|
||||
}
|
||||
|
||||
|
||||
void UserInputUtil::wrapMouseHorizontalCenter(const Vector2& delta,
|
||||
Vector2& wrapMouseDelta,
|
||||
Vector2& wrapMousePosition)
|
||||
{
|
||||
wrapMouseDelta.x += delta.x;
|
||||
// wrapMousePosition = G3D::Vector2::zero();
|
||||
}
|
||||
|
||||
void UserInputUtil::wrapMousePos(const Vector2& delta,
|
||||
Vector2& wrapMouseDelta,
|
||||
Vector2& wrapMousePosition,
|
||||
const Vector2& windowSize,
|
||||
Vector2& posToWrapTo,
|
||||
bool autoMoveMouse)
|
||||
{
|
||||
if(posToWrapTo.length() > 2)
|
||||
{
|
||||
Vector2 windowDelta = posToWrapTo/windowSize;
|
||||
wrapMouseDelta = windowDelta * HybridSensitivity;
|
||||
posToWrapTo -= (wrapMouseDelta * HybridSensitivity * 0.266f); // 0.266 is a tuning constant
|
||||
|
||||
float xDiff = std::abs(wrapMousePosition.x/wrapMousePosition.length()) * 0.3f;
|
||||
float yDiff = std::abs(wrapMousePosition.y/wrapMousePosition.length()) * 0.4f;
|
||||
|
||||
if(autoMoveMouse && wrapMousePosition != Vector2::zero())
|
||||
{
|
||||
if(wrapMousePosition.x < 0)
|
||||
{
|
||||
wrapMousePosition.x += xDiff * wrapMouseDelta.length() * MouseTug;
|
||||
if(wrapMousePosition.x > 0)
|
||||
wrapMousePosition.x = 0;
|
||||
}
|
||||
else if (wrapMousePosition.x > 0)
|
||||
{
|
||||
wrapMousePosition.x -= xDiff * wrapMouseDelta.length()* MouseTug;
|
||||
if(wrapMousePosition.x < 0)
|
||||
wrapMousePosition.x = 0;
|
||||
}
|
||||
|
||||
|
||||
if(wrapMousePosition.y < 0)
|
||||
{
|
||||
wrapMousePosition.y += yDiff * wrapMouseDelta.length() * MouseTug;
|
||||
if(wrapMousePosition.y > 0)
|
||||
wrapMousePosition.y = 0;
|
||||
}
|
||||
else if (wrapMousePosition.y > 0)
|
||||
{
|
||||
wrapMousePosition.y -= yDiff * wrapMouseDelta.length() * MouseTug;
|
||||
if(wrapMousePosition.y < 0)
|
||||
wrapMousePosition.y = 0;
|
||||
}
|
||||
autoMoveMouse = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
wrapMousePosition += delta;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Maps DIK_* to RBX::SDLK_*
|
||||
RBX::KeyCode UserInputUtil::directInputToKeyCode(DWORD diKey)
|
||||
{
|
||||
RBXASSERT(diKey>=0);
|
||||
RBXASSERT(diKey<256);
|
||||
|
||||
static RBX::KeyCode keymap[256];
|
||||
static bool initialized = false;
|
||||
if (!initialized)
|
||||
{
|
||||
for ( int i=0; i<256; ++i )
|
||||
keymap[i] = RBX::SDLK_UNKNOWN;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return keymap[diKey];
|
||||
}
|
||||
|
||||
// Maps RBX::RBX::SDLK_* to DIK_*
|
||||
DWORD UserInputUtil::keyCodeToDirectInput(RBX::KeyCode keyCode)
|
||||
{
|
||||
static DWORD keymap[RBX::SDLK_LAST];
|
||||
static bool initialized = false;
|
||||
if (!initialized)
|
||||
{
|
||||
for ( int i=0; i<RBX::SDLK_LAST; ++i )
|
||||
keymap[i] = 0;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return keymap[keyCode];
|
||||
}
|
||||
|
||||
|
||||
// Maps RBX::RBX::SDLK_* to VK_*
|
||||
DWORD UserInputUtil::keyCodeToVK(RBX::KeyCode keyCode)
|
||||
{
|
||||
static DWORD keymap[RBX::SDLK_LAST];
|
||||
static bool initialized = false;
|
||||
if (!initialized)
|
||||
{
|
||||
for ( int i=0; i<RBX::SDLK_LAST; ++i )
|
||||
keymap[i] = 0;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return keymap[keyCode];
|
||||
}
|
||||
|
||||
|
||||
RBX::ModCode UserInputUtil::createModCode(const DiKeys& diKeys)
|
||||
{
|
||||
unsigned int modCode = 0;
|
||||
return (RBX::ModCode)modCode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include "Util/KeyCode.h"
|
||||
#include "V8DataModel/InputObject.h"
|
||||
#include "rbx/debug.h"
|
||||
#include "G3D/Vector2.h"
|
||||
|
||||
#define DIRECTINPUT_VERSION 0x0800
|
||||
|
||||
#define WM_KEYUP 0
|
||||
#define WM_KEYDOWN 1
|
||||
#define WM_SETFOCUS 10
|
||||
#define WM_KILLFOCUS 11
|
||||
|
||||
#define GET_X_LPARAM(l) ((short)(l & 0x0000FFFF))
|
||||
#define GET_Y_LPARAM(l) ((short)((l & 0xFFFF0000) >> 16))
|
||||
|
||||
#define MAKEXYLPARAM(x, y) (((unsigned short) x) | ((unsigned short) y) << 16)
|
||||
|
||||
|
||||
class UserInputUtil
|
||||
{
|
||||
private:
|
||||
static void wrapMouseBorder(const G3D::Vector2& delta,
|
||||
G3D::Vector2& wrapMouseDelta,
|
||||
G3D::Vector2& wrapMousePosition,
|
||||
const G3D::Vector2& windowSize,
|
||||
const int borderWidth,
|
||||
const float creepFactor);
|
||||
|
||||
public:
|
||||
typedef uint8_t DiKeys[256];
|
||||
|
||||
static const float HybridSensitivity;
|
||||
static const float MouseTug;
|
||||
|
||||
static RBX::ModCode createModCode(const DiKeys& diKeys);
|
||||
static RBX::InputObject::UserInputType msgToEventType(unsigned int uMsg);
|
||||
static RBX::InputObject::UserInputState msgToEventState(unsigned int uMsg);
|
||||
static DWORD keyCodeToDirectInput(RBX::KeyCode keyCode);
|
||||
static RBX::KeyCode directInputToKeyCode(DWORD diKey);
|
||||
static DWORD keyCodeToVK(RBX::KeyCode diKey);
|
||||
|
||||
// static G3D::Vector2 didodToVector2(const DIDEVICEOBJECTDATA& didod);
|
||||
//
|
||||
static void wrapMouseNone(const G3D::Vector2& delta,
|
||||
G3D::Vector2& wrapMouseDelta,
|
||||
G3D::Vector2& wrapMousePosition);
|
||||
|
||||
static void wrapFullScreen(const G3D::Vector2& delta,
|
||||
G3D::Vector2& wrapMouseDelta,
|
||||
G3D::Vector2& wrapMousePosition,
|
||||
const G3D::Vector2& windowSize);
|
||||
|
||||
static void wrapMouseHorizontalTransition(const G3D::Vector2& delta,
|
||||
G3D::Vector2& wrapMouseDelta,
|
||||
G3D::Vector2& wrapMousePosition,
|
||||
const G3D::Vector2& windowSize);
|
||||
|
||||
static void wrapMouseBorderLock(const G3D::Vector2& delta,
|
||||
G3D::Vector2& wrapMouseDelta,
|
||||
G3D::Vector2& wrapMousePosition,
|
||||
const G3D::Vector2& windowSize);
|
||||
|
||||
static void wrapMouseBorderTransition(const G3D::Vector2& delta,
|
||||
G3D::Vector2& wrapMouseDelta,
|
||||
G3D::Vector2& wrapMousePosition,
|
||||
const G3D::Vector2& windowSize);
|
||||
|
||||
static void wrapMouseCenter(const G3D::Vector2& delta,
|
||||
G3D::Vector2& wrapMouseDelta,
|
||||
G3D::Vector2& wrapMousePosition);
|
||||
|
||||
static void wrapMouseHorizontalCenter(const G3D::Vector2& delta,
|
||||
G3D::Vector2& wrapMouseDelta,
|
||||
G3D::Vector2& wrapMousePosition);
|
||||
|
||||
static void wrapMousePos(const G3D::Vector2& delta,
|
||||
G3D::Vector2& wrapMouseDelta,
|
||||
G3D::Vector2& wrapMousePosition,
|
||||
const G3D::Vector2& windowSize,
|
||||
G3D::Vector2& posToWrapTo,
|
||||
bool autoMoveMouse);
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// main.m
|
||||
// SimplePlayerMac
|
||||
//
|
||||
// Created by Tony on 10/26/10.
|
||||
// Copyright 2010 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/param.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "Roblox.h"
|
||||
|
||||
#include "v8datamodel/ContentProvider.h"
|
||||
|
||||
FASTFLAGVARIABLE(GraphicsReportingInitErrorsToGAEnabled,true)
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// Get the Location where the Resources are installed.
|
||||
CFBundleRef mainBundle = CFBundleGetMainBundle();
|
||||
CFURLRef resourceBundle = CFBundleCopyResourcesDirectoryURL(mainBundle);
|
||||
char resourcePath[PATH_MAX];
|
||||
if(CFURLGetFileSystemRepresentation(resourceBundle, TRUE, (UInt8 *)resourcePath, PATH_MAX))
|
||||
{
|
||||
CFRelease(resourceBundle);
|
||||
chdir(resourcePath);
|
||||
Roblox::setArgs(resourcePath, argc > 1 ? argv[1] : "f");
|
||||
}
|
||||
|
||||
// Set up pool to prevent leaks
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
NSUserDefaults* standardUserDefaults = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
if (standardUserDefaults)
|
||||
{
|
||||
// Forced removal of "Special Characters menu item
|
||||
// Has to be done before the app is loaded with the current defaults
|
||||
[standardUserDefaults setBool:YES forKey:@"NSDisabledCharacterPaletteMenuItem"];
|
||||
|
||||
[standardUserDefaults synchronize];
|
||||
}
|
||||
|
||||
[pool release];
|
||||
|
||||
// need to get client settings before creating window
|
||||
NSString* baseUrl = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"RbxBaseUrl"];
|
||||
const char* s = [baseUrl UTF8String];
|
||||
::SetBaseURL(s);
|
||||
|
||||
std::string appSettings;
|
||||
FetchClientSettingsData(CLIENT_APP_SETTINGS_STRING, CLIENT_SETTINGS_API_KEY, &appSettings);
|
||||
LoadClientSettingsFromString(CLIENT_APP_SETTINGS_STRING, appSettings, &RBX::ClientAppSettings::singleton());
|
||||
|
||||
int mainstat = NSApplicationMain(argc, (const char **) argv);
|
||||
return mainstat;
|
||||
}
|
||||
Reference in New Issue
Block a user