mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
1051 lines
29 KiB
C++
1051 lines
29 KiB
C++
#include "stdafx.h"
|
|
#include "UserInput.h"
|
|
|
|
#include "resource.h"
|
|
#include "Util/Rect.h"
|
|
#include "Util/Standardout.h"
|
|
#include "Util/NavKeys.h"
|
|
#include "v8datamodel/Game.h"
|
|
#include "v8datamodel/Datamodel.h"
|
|
#include "v8datamodel/DataModelJob.h"
|
|
#include "v8datamodel/GameBasicSettings.h"
|
|
#include "v8datamodel/ContentProvider.h"
|
|
#include "v8datamodel/Workspace.h"
|
|
#include "v8datamodel/UserInputService.h"
|
|
#include "View.h"
|
|
#include "Humanoid/Humanoid.h"
|
|
#include "RbxG3D/RbxTime.h"
|
|
#include "G3D/System.h"
|
|
#include "G3D/Vector2.h"
|
|
#include "RenderSettingsItem.h"
|
|
#include "FastLog.h"
|
|
|
|
#define DXINPUT_TRACE __noop
|
|
|
|
LOGGROUP(UserInputProfile)
|
|
DYNAMIC_FASTFLAGVARIABLE(MouseDeltaWhenNotMouseLocked, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(UserInputViewportSizeFixWindows, true)
|
|
|
|
FASTFLAG(UserAllCamerasInLua)
|
|
|
|
#define RBX_DI_BUFFER_SIZE 2048
|
|
|
|
namespace RBX {
|
|
|
|
class RobloxCritSecLoc
|
|
{
|
|
RobloxCriticalSection& robloxCriticalSection;
|
|
CCritSecLock lock;
|
|
|
|
public:
|
|
RobloxCritSecLoc( RobloxCriticalSection& cs, const std::string& location)
|
|
: robloxCriticalSection(cs)
|
|
, lock(cs.diSection)
|
|
{
|
|
// should compile away the string manipulations
|
|
#ifdef __RBX_NOT_RELEASE
|
|
// Show THESE ASSERTS TO David/Erik - they imply multiple entry points
|
|
// to User Input Object - trying to track down
|
|
RBXASSERT(robloxCriticalSection.callers == 0);
|
|
robloxCriticalSection.callers++;
|
|
robloxCriticalSection.olderCaller = robloxCriticalSection.recentCaller;
|
|
robloxCriticalSection.recentCaller = location;
|
|
#endif
|
|
}
|
|
|
|
~RobloxCritSecLoc()
|
|
{
|
|
// should compile away the string manipulations
|
|
#ifdef __RBX_NOT_RELEASE
|
|
RBXASSERT(robloxCriticalSection.callers == 1);
|
|
robloxCriticalSection.callers--;
|
|
robloxCriticalSection.recentCaller = "";
|
|
robloxCriticalSection.olderCaller = "";
|
|
#endif
|
|
}
|
|
};
|
|
|
|
UserInput::UserInput(HWND wnd, shared_ptr<RBX::Game> game, View* view)
|
|
: externallyForcedKeyDown(0)
|
|
, isMouseCaptured(false)
|
|
, isMouseAcquired(false)
|
|
, isKeyboardAcquired(false)
|
|
, desireKeyboardAcquired(false)
|
|
, isMouseInside(false)
|
|
, wnd(wnd)
|
|
, wrapping(false)
|
|
, posToWrapTo(0, 0)
|
|
, leftMouseButtonDown(false)
|
|
, rightMouseDown(false)
|
|
, autoMouseMove(true)
|
|
, mouseButtonSwap(false)
|
|
, parentView(view)
|
|
{
|
|
#ifdef _DEBUG
|
|
hArrow = LoadCursor(NULL, IDC_CROSS);
|
|
hInvisibleCursor = LoadCursor(GetModuleHandle(NULL), IDC_CROSS);
|
|
#else
|
|
hArrow = LoadCursor(NULL, IDC_ARROW);
|
|
hInvisibleCursor = LoadCursor(GetModuleHandle(NULL),
|
|
MAKEINTRESOURCE(IDR_INVISIBLECURSOR));
|
|
#endif
|
|
|
|
setGame(game);
|
|
|
|
memset(&diKeys, 0, sizeof(diKeys));
|
|
|
|
HRESULT hr;
|
|
if( FAILED( hr = DirectInput8Create(GetModuleHandle(NULL),
|
|
DIRECTINPUT_VERSION,
|
|
IID_IDirectInput8,
|
|
(VOID**)&diPtr,
|
|
NULL ) ) )
|
|
{
|
|
MessageBoxA(NULL, "watrbx could not initialize input. Please make sure you have a mouse and keyboard plugged in.", "watrbx", MB_OK);
|
|
return;
|
|
}
|
|
|
|
// check to see if user has swapped their primary and secondary mouse buttons (left hand mouse)
|
|
CRegKey mouseKey;
|
|
if (mouseKey.Open(HKEY_CURRENT_USER, "Control Panel\\Mouse", KEY_READ) == ERROR_SUCCESS) // ERROR_SUCCESS? Really, Microsoft?
|
|
{
|
|
TCHAR szBuffer[256];
|
|
ULONG cchBuffer = 256;
|
|
if(mouseKey.QueryStringValue(_T("SwapMouseButtons"), szBuffer, &cchBuffer) == ERROR_SUCCESS)
|
|
mouseButtonSwap = ( szBuffer[0] == '1' );
|
|
}
|
|
|
|
createMouse();
|
|
createKeyboard();
|
|
createAccelerators();
|
|
|
|
layout = GetKeyboardLayout(0);
|
|
}
|
|
|
|
void UserInput::setGame(shared_ptr<RBX::Game> game)
|
|
{
|
|
this->game = game;
|
|
|
|
runService = shared_from(ServiceProvider::create<RunService>(game->getDataModel().get()));
|
|
|
|
shared_ptr<DataModel> dataModel = game->getDataModel();
|
|
|
|
sdlGameController = shared_ptr<SDLGameController>(new SDLGameController(dataModel));
|
|
|
|
steppedConnection = runService->earlyRenderSignal.connect(boost::bind(&UserInput::processInput, this));
|
|
}
|
|
UserInput::~UserInput()
|
|
{
|
|
sdlGameController.reset();
|
|
|
|
RBXASSERT(diMousePtr);
|
|
RBXASSERT(diKeyboardPtr);
|
|
RBXASSERT(diPtr);
|
|
|
|
if (diMousePtr)
|
|
{
|
|
diMousePtr->Unacquire();
|
|
diMousePtr = 0;
|
|
}
|
|
|
|
if (diKeyboardPtr)
|
|
{
|
|
diKeyboardPtr->Unacquire();
|
|
diKeyboardPtr = 0;
|
|
}
|
|
|
|
if (diPtr)
|
|
diPtr = 0; // is this the same as release() -> Yes!
|
|
}
|
|
|
|
void getDiProp(DIPROPDWORD& dipdw)
|
|
{
|
|
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
|
|
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
|
|
dipdw.diph.dwObj = 0;
|
|
dipdw.diph.dwHow = DIPH_DEVICE;
|
|
dipdw.dwData = RBX_DI_BUFFER_SIZE; // Arbitrary buffer size
|
|
}
|
|
|
|
void UserInput::createMouse()
|
|
{
|
|
HRESULT hr;
|
|
if (FAILED(hr = diPtr->CreateDevice( GUID_SysMouse, &diMousePtr, NULL)))
|
|
{
|
|
MessageBoxA(NULL, "Could not find a mouse! watrbx requires a mouse.", "watrbx", MB_OK);
|
|
return;
|
|
}
|
|
|
|
if (FAILED(hr = diMousePtr->SetDataFormat(&c_dfDIMouse2)))
|
|
{
|
|
MessageBoxA(NULL, "Could not find a mouse (data format failed)! watrbx requires a mouse.", "watrbx", MB_OK);
|
|
return;
|
|
}
|
|
|
|
DIPROPDWORD dipdw;
|
|
getDiProp(dipdw);
|
|
if (FAILED(hr = diMousePtr->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)))
|
|
{
|
|
MessageBoxA(NULL, "Could not find a mouse (set property failed)! watrbx requires a mouse.", "watrbx", MB_OK);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void UserInput::createKeyboard()
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (FAILED(hr = diPtr->CreateDevice(GUID_SysKeyboard, &diKeyboardPtr, NULL)))
|
|
{
|
|
MessageBoxA(NULL, "Could not find a keyboard! watrbx requires a keyboard.", "watrbx", MB_OK);
|
|
return;
|
|
}
|
|
|
|
if (FAILED(hr = diKeyboardPtr->SetDataFormat(&c_dfDIKeyboard)))
|
|
{
|
|
MessageBoxA(NULL, "Could not find a keyboard (data format failed)! watrbx requires a keyboard.", "watrbx", MB_OK);
|
|
return;
|
|
}
|
|
|
|
DIPROPDWORD dipdw;
|
|
getDiProp(dipdw);
|
|
if (FAILED(hr = diKeyboardPtr->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)))
|
|
{
|
|
MessageBoxA(NULL, "Could not find a keyboard (set property failed)! watrbx requires a keyboard.", "watrbx", MB_OK);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void UserInput::createAccelerators()
|
|
{
|
|
HACCEL hAccel = LoadAccelerators(GetModuleHandle(NULL), MAKEINTRESOURCE(IDR_GAME_ACCELERATOR));
|
|
if (NULL != hAccel) {
|
|
int numAccelerators = CopyAcceleratorTable(hAccel, NULL, 0);
|
|
accelerators.resize(numAccelerators);
|
|
if (numAccelerators != 0) {
|
|
CopyAcceleratorTable(hAccel, &accelerators[0], numAccelerators);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UserInput::setKeyboardDesiredInternal(bool set)
|
|
{
|
|
desireKeyboardAcquired = set;
|
|
updateKeyboard();
|
|
}
|
|
|
|
void UserInput::setKeyboardDesired(bool set)
|
|
{
|
|
RobloxCritSecLoc lock(diSection, "setKeyboardDesired");
|
|
setKeyboardDesiredInternal(set);
|
|
}
|
|
|
|
void UserInput::updateMouse()
|
|
{
|
|
if (!diMousePtr || diMousePtr.p == NULL)
|
|
{
|
|
isMouseAcquired = false;
|
|
return;
|
|
}
|
|
|
|
if (isMouseAcquired == isMouseInside)
|
|
return;
|
|
|
|
if (isMouseInside)
|
|
acquireMouseInternal();
|
|
else
|
|
unAcquireMouse();
|
|
}
|
|
|
|
void UserInput::acquireMouseInternal()
|
|
{
|
|
Vector2 cp = getCursorPositionInternal();
|
|
acquireMouseInternalBase(cp);
|
|
}
|
|
|
|
void UserInput::acquireMouseInternalBase(const Vector2& pos)
|
|
{
|
|
RBXASSERT(!isMouseAcquired);
|
|
|
|
if (diMousePtr)
|
|
{
|
|
diMousePtr->Unacquire(); // for good measure -
|
|
|
|
HRESULT hr = diMousePtr->SetCooperativeLevel(wnd,
|
|
DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
|
|
if (hr != DI_OK)
|
|
{
|
|
DXINPUT_TRACE("diMousePtr->SetCooperativeLevel() Failed %d\n", hr);
|
|
}
|
|
else
|
|
{
|
|
DXINPUT_TRACE("diMousePtr->SetCooperativeLevel() Succeeded %d\n", hr);
|
|
hr = diMousePtr->Acquire();
|
|
if (hr != DI_OK)
|
|
{
|
|
DXINPUT_TRACE("diMousePtr->Acquire() Failed %d\n", hr);
|
|
}
|
|
else
|
|
{
|
|
wrapMousePosition = Math::expandVector2(pos - getWindowRect().center(), -10); // i.e. - pull towards the origin - remove chatter
|
|
DXINPUT_TRACE("diMousePtr->Acquire() Succeeded %d\n", hr);
|
|
isMouseAcquired = true;
|
|
isMouseCaptured = false; // reset here
|
|
SetClassLong(wnd, GCL_HCURSOR, (long)hInvisibleCursor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UserInput::unAcquireMouse()
|
|
{
|
|
RBXASSERT(isMouseAcquired);
|
|
|
|
HRESULT hr = diMousePtr->Unacquire();
|
|
if (hr != DI_OK)
|
|
DXINPUT_TRACE("diMousePtr->UnAcquire() Failed %d\n", hr);
|
|
else
|
|
DXINPUT_TRACE("diMousePtr->UnAcquire() Succeeded \n");
|
|
|
|
// Move the Windows cursor into position
|
|
Vector2 pos = getGameCursorPositionExpandedInternal();
|
|
POINT p;
|
|
p.x = static_cast<int>(pos.x);
|
|
p.y = static_cast<int>(pos.y);
|
|
ClientToScreen(wnd, &p);
|
|
|
|
SetCursorPos(p.x, p.y);
|
|
|
|
SetClassLong(wnd, GCL_HCURSOR, (long)hArrow);
|
|
isMouseAcquired = false;
|
|
isMouseCaptured = false; // reset here
|
|
}
|
|
|
|
void UserInput::updateKeyboard()
|
|
{
|
|
if (diKeyboardPtr.p == NULL)
|
|
return;
|
|
|
|
if (isKeyboardAcquired == desireKeyboardAcquired)
|
|
return;
|
|
|
|
if (desireKeyboardAcquired) {
|
|
DXINPUT_TRACE("updateKeyboard believes keyboard should be acquired\n");
|
|
acquireKeyboard();
|
|
} else {
|
|
DXINPUT_TRACE("updateKeyboard believes keyboard should be unacquired\n");
|
|
unAcquireKeyboard();
|
|
}
|
|
}
|
|
|
|
void UserInput::acquireKeyboard()
|
|
{
|
|
RBXASSERT(!isKeyboardAcquired);
|
|
|
|
diKeyboardPtr->Unacquire(); // for good measure
|
|
|
|
HRESULT hr = diKeyboardPtr->SetCooperativeLevel(wnd,
|
|
DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
|
|
if (hr != DI_OK)
|
|
{
|
|
DXINPUT_TRACE("diKeyboardPtr->SetCooperativeLevel failed %d\n", hr);
|
|
}
|
|
else
|
|
{
|
|
DXINPUT_TRACE("diKeyboardPtr->SetCooperativeLevel succeeded %d\n", hr);
|
|
if (FAILED(hr = diKeyboardPtr->Acquire()))
|
|
{
|
|
DXINPUT_TRACE("Keyboard Acquisition failed %d\n", hr);
|
|
}
|
|
else
|
|
{
|
|
isKeyboardAcquired = true;
|
|
DXINPUT_TRACE("Keyboard Acquisition succeeded %d\n", hr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UserInput::unAcquireKeyboard()
|
|
{
|
|
diKeyboardPtr->Unacquire();
|
|
DXINPUT_TRACE("Keyboard Acquisition given up intentionally\n");
|
|
isKeyboardAcquired = false;
|
|
}
|
|
|
|
|
|
void UserInput::postUserInputMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
if (uMsg == WM_MOUSEMOVE)
|
|
if (shared_ptr<DataModel> dataModel = game->getDataModel())
|
|
dataModel->mouseStats.osMouseMove.sample();
|
|
|
|
processUserInputMessage(uMsg, wParam, lParam);
|
|
}
|
|
|
|
void UserInput::processUserInputMessage(UINT uMsg, WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
RobloxCritSecLoc lock(diSection, "ProcessUserInputMessage");
|
|
|
|
if (uMsg == WM_MOUSEMOVE)
|
|
{
|
|
onMouseInside();
|
|
|
|
if(!isMouseAcquired)
|
|
{
|
|
G3D::Vector3 pos((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam),0);
|
|
sendMouseEvent(UserInputUtil::msgToEventType(uMsg),UserInputUtil::msgToEventState(uMsg), pos, Vector3(0,0,0));
|
|
}
|
|
updateMouse(); // correct potential failure to acquire. (this happens on mouse over when app is not foreground)
|
|
|
|
if (uMsg == WM_LBUTTONDOWN)
|
|
isMouseCaptured = true;
|
|
if (uMsg == WM_LBUTTONUP)
|
|
isMouseCaptured = false;
|
|
|
|
return;
|
|
}
|
|
|
|
if (uMsg == WM_MOUSELEAVE)
|
|
{
|
|
onMouseLeave();
|
|
return;
|
|
}
|
|
|
|
if (uMsg == WM_MOUSEWHEEL)
|
|
{
|
|
if(!isMouseAcquired)
|
|
{
|
|
int zDelta = (int) wParam; // wheel rotation
|
|
G3D::Vector3 pos((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam),zDelta);
|
|
sendMouseEvent(InputObject::TYPE_MOUSEWHEEL,InputObject::INPUT_STATE_CHANGE,pos, Vector3(0,0,0));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (uMsg == WM_SETFOCUS)
|
|
{
|
|
setKeyboardDesiredInternal(true);
|
|
|
|
DXINPUT_TRACE(std::string("UserInput::WM_SETFOCUS\n").c_str());
|
|
shared_ptr<InputObject> event = Creatable<Instance>::create<InputObject>(InputObject::TYPE_FOCUS, InputObject::INPUT_STATE_BEGIN, game->getDataModel().get());
|
|
sendEvent(event);
|
|
return;
|
|
}
|
|
|
|
if (uMsg == WM_KILLFOCUS)
|
|
{
|
|
DXINPUT_TRACE(std::string("UserInput::WM_KILLFOCUS\n").c_str());
|
|
shared_ptr<InputObject> event = Creatable<Instance>::create<InputObject>(InputObject::TYPE_FOCUS, InputObject::INPUT_STATE_END, game->getDataModel().get());
|
|
sendEvent(event);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Event Handling
|
|
//
|
|
|
|
bool UserInput::readBufferedData(LPDIDEVICEOBJECTDATA didod,
|
|
DWORD& dwElements,
|
|
IDirectInputDevice8* device)
|
|
{
|
|
HRESULT hr = device->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), didod, &dwElements, 0 );
|
|
|
|
if (hr == DI_OK)
|
|
{
|
|
return true;
|
|
}
|
|
else if (hr == DI_BUFFEROVERFLOW)
|
|
{
|
|
std::string devString = (device == diKeyboardPtr) ? "Keyboard " : "Mouse ";
|
|
std::string error = devString + "DI_BUFFEROVERFLOW!\n";
|
|
StandardOut::singleton()->print(MESSAGE_ERROR, error);
|
|
DXINPUT_TRACE(("UserInput::readBufferedData: " + error).c_str());
|
|
return true;
|
|
}
|
|
|
|
std::string devString = (device == diKeyboardPtr) ? "Keyboard " : "Mouse ";
|
|
DXINPUT_TRACE(("UserInput::readBufferedData: " + devString + "Acquisition was lost\n").c_str());
|
|
return false;
|
|
}
|
|
|
|
void UserInput::readBufferedKeyboardData()
|
|
{
|
|
RBXASSERT(isKeyboardAcquired);
|
|
|
|
DIDEVICEOBJECTDATA didod[RBX_DI_BUFFER_SIZE]; // Receives buffered data
|
|
DWORD dwElements = RBX_DI_BUFFER_SIZE;
|
|
|
|
if (!readBufferedData(didod, dwElements, diKeyboardPtr))
|
|
{
|
|
isKeyboardAcquired = false;
|
|
return;
|
|
}
|
|
|
|
FASTLOG1(FLog::UserInputProfile, "Read %u keyboard events", dwElements);
|
|
|
|
|
|
// Study each of the buffer elements and process them.
|
|
for (DWORD i = 0; i < dwElements; ++i)
|
|
{
|
|
DWORD diKey = didod[ i ].dwOfs;
|
|
const bool down = (didod[i].dwData & 0x80) != 0;
|
|
|
|
ModCode modCode = UserInputUtil::createModCode(diKeys);
|
|
KeyCode keyCode = UserInputUtil::directInputToKeyCode(diKey);
|
|
|
|
if (down)
|
|
{
|
|
// suppress is to prevent keyboard shortcuts (brute-force attempt
|
|
// to prevent back-doors)
|
|
bool suppress = false;
|
|
DWORD vKey = MapVirtualKeyEx(diKey, 1, layout);
|
|
if (vKey == 0)
|
|
vKey = UserInputUtil::keyCodeToVK(keyCode);
|
|
|
|
// We have a whitelist of approved keys. Any key not in this list
|
|
// will not be processed as an accelerator. Note that Lua GUI
|
|
// controls will still receive keyboard input.
|
|
switch (vKey) {
|
|
case VK_F4:
|
|
if (modCode & (RBX::KMOD_LALT | RBX::KMOD_RALT)) {
|
|
PostMessage(wnd, WM_CLOSE, 0, 0);
|
|
continue;
|
|
}
|
|
break;
|
|
case VK_F1:
|
|
case VK_F8:
|
|
case VK_F11:
|
|
case VK_SNAPSHOT:
|
|
break;
|
|
default:
|
|
suppress = true;
|
|
break;
|
|
}
|
|
|
|
// Find if key corresponds to accelerator, and if so post
|
|
// associated WM_COMMAND message
|
|
if (!suppress) {
|
|
int iCharCode = MapVirtualKeyEx(vKey, 2, layout);
|
|
char charCode = 0xFF & iCharCode;
|
|
DXINPUT_TRACE("diKey=0x%x, vKey=0x%x, iCharCode=0x%x", diKey, vKey, iCharCode);
|
|
if (iCharCode!=0)
|
|
DXINPUT_TRACE(", charCode='%c'\n", charCode);
|
|
else
|
|
DXINPUT_TRACE("\n");
|
|
|
|
DWORD modKeys = modCode & (RBX::KMOD_LCTRL | RBX::KMOD_RCTRL) ? FCONTROL : 0;
|
|
modKeys |= modCode & (RBX::KMOD_LALT | RBX::KMOD_RALT) ? FALT : 0;
|
|
modKeys |= modCode & (RBX::KMOD_LSHIFT | RBX::KMOD_RSHIFT) ? FSHIFT : 0;
|
|
|
|
for (size_t i = 0; i<accelerators.size(); ++i)
|
|
{
|
|
if (accelerators[i].fVirt & FVIRTKEY)
|
|
{
|
|
if (accelerators[i].key != vKey)
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (accelerators[i].key != charCode)
|
|
continue;
|
|
}
|
|
if (modKeys != (accelerators[i].fVirt & (FALT | FCONTROL | FSHIFT)))
|
|
continue;
|
|
|
|
PostMessage(wnd, WM_COMMAND, 0x10000 | accelerators[i].cmd, 0);
|
|
continue;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
GetKeyboardState(keyboardState);
|
|
|
|
DWORD virtKey = MapVirtualKeyEx(diKey, 1, layout);
|
|
unsigned short asciiKey;
|
|
int result = ToAsciiEx(virtKey, diKey, keyboardState, &asciiKey,
|
|
0, layout);
|
|
|
|
char key = UserInputService::getModifiedKey(keyCode, modCode);
|
|
|
|
if (result == 1)
|
|
key = asciiKey & 0xff;
|
|
|
|
if( RBX::UserInputService* userInputService = RBX::ServiceProvider::find<RBX::UserInputService>(game->getDataModel().get()) )
|
|
{
|
|
userInputService->setKeyState(keyCode, modCode, key, down);
|
|
}
|
|
|
|
InputObject::UserInputState eventState = down ? InputObject::INPUT_STATE_BEGIN : InputObject::INPUT_STATE_END;
|
|
|
|
shared_ptr<RBX::InputObject> keyInput = RBX::Creatable<RBX::Instance>::create<RBX::InputObject>(InputObject::TYPE_KEYBOARD, eventState, keyCode, modCode, key, game->getDataModel().get());
|
|
sendEvent(keyInput);
|
|
}
|
|
}
|
|
|
|
void UserInput::processMouseButtonEvent(DIDEVICEOBJECTDATA mouseData, MouseButtonType mouseButton, bool &leftMouseUp)
|
|
{
|
|
if(mouseButtonSwap) // we need to send events to each button reversed
|
|
{
|
|
if(mouseButton == MBUTTON_LEFT)
|
|
mouseButton = MBUTTON_RIGHT;
|
|
else
|
|
mouseButton = MBUTTON_LEFT;
|
|
}
|
|
|
|
switch(mouseButton)
|
|
{
|
|
case MBUTTON_LEFT:
|
|
DXINPUT_TRACE("wnd.PostMessage(WM_CALL_SETFOCUS)\n");
|
|
::PostMessage(wnd, WM_CALL_SETFOCUS, 0, 0);
|
|
sendMouseEvent( RBX::InputObject::TYPE_MOUSEBUTTON1,
|
|
(int)mouseData.dwData==0x80
|
|
? RBX::InputObject::INPUT_STATE_BEGIN
|
|
: RBX::InputObject::INPUT_STATE_END,
|
|
Vector3(getCursorPositionInternal(),0), Vector3(0,0,0));
|
|
|
|
leftMouseUp = ((int)mouseData.dwData != 0x80);
|
|
autoMouseMove = leftMouseUp;
|
|
leftMouseButtonDown = ((int)mouseData.dwData == 0x80);
|
|
break;
|
|
case MBUTTON_RIGHT:
|
|
DXINPUT_TRACE("wnd.PostMessage(WM_CALL_SETFOCUS)\n");
|
|
::PostMessage(wnd, WM_CALL_SETFOCUS, 0, 0);
|
|
sendMouseEvent( RBX::InputObject::TYPE_MOUSEBUTTON2,
|
|
(int)mouseData.dwData==0x80
|
|
? RBX::InputObject::INPUT_STATE_BEGIN
|
|
: RBX::InputObject::INPUT_STATE_END,
|
|
Vector3(getCursorPositionInternal(),0), Vector3(0,0,0) );
|
|
|
|
rightMouseDown = ((int)mouseData.dwData == 0x80);
|
|
break;
|
|
case MBUTTON_MIDDLE:
|
|
DXINPUT_TRACE("wnd.PostMessage(WM_CALL_SETFOCUS)\n");
|
|
::PostMessage(wnd, WM_CALL_SETFOCUS, 0, 0);
|
|
sendMouseEvent( RBX::InputObject::TYPE_MOUSEBUTTON3,
|
|
(int)mouseData.dwData==0x80
|
|
? RBX::InputObject::INPUT_STATE_BEGIN
|
|
: RBX::InputObject::INPUT_STATE_END,
|
|
Vector3(getCursorPositionInternal(),0), Vector3(0,0,0) );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void UserInput::readBufferedMouseData()
|
|
{
|
|
RBXASSERT(isMouseAcquired);
|
|
|
|
DIDEVICEOBJECTDATA didod[RBX_DI_BUFFER_SIZE]; // Receives buffered data
|
|
DWORD dwElements = RBX_DI_BUFFER_SIZE;
|
|
|
|
if (!readBufferedData(didod, dwElements, diMousePtr))
|
|
{
|
|
isMouseAcquired = false;
|
|
return;
|
|
}
|
|
|
|
FASTLOG1(FLog::UserInputProfile, "Read %u mouse events", dwElements);
|
|
|
|
bool cursorMoved = false;
|
|
bool leftMouseUp = false;
|
|
G3D::Vector2 wrapMouseDelta;
|
|
G3D::Vector2 mouseDelta;
|
|
|
|
// Study each of the buffer elements and process them.
|
|
for(DWORD i = 0; i < dwElements; ++i )
|
|
{
|
|
switch( didod[ i ].dwOfs )
|
|
{
|
|
case DIMOFS_BUTTON0:
|
|
FASTLOG(FLog::UserInputProfile, "Processing mouse button0");
|
|
processMouseButtonEvent(didod[i], MBUTTON_LEFT, leftMouseUp);
|
|
break;
|
|
case DIMOFS_BUTTON1:
|
|
processMouseButtonEvent(didod[i], MBUTTON_RIGHT, leftMouseUp);
|
|
break;
|
|
case DIMOFS_BUTTON2:
|
|
processMouseButtonEvent(didod[i], MBUTTON_MIDDLE, leftMouseUp);
|
|
break;
|
|
case DIMOFS_X:
|
|
case DIMOFS_Y:
|
|
{
|
|
mouseDelta = UserInputUtil::didodToVector2(didod[i]);
|
|
previousCursorPosFraction += mouseDelta * RBX::GameBasicSettings::singleton().getMouseSensitivity();
|
|
mouseDelta.x = (int) previousCursorPosFraction.x;
|
|
mouseDelta.y = (int) previousCursorPosFraction.y;
|
|
previousCursorPosFraction -= mouseDelta;
|
|
cursorMoved = true;
|
|
doWrapMouse(mouseDelta, wrapMouseDelta);
|
|
}
|
|
break;
|
|
case DIMOFS_Z:
|
|
float delta = float((int)didod[ i ].dwData) / float(WHEEL_DELTA);
|
|
|
|
if (DFFlag::MouseDeltaWhenNotMouseLocked)
|
|
{
|
|
sendMouseEvent( InputObject::TYPE_MOUSEWHEEL,
|
|
InputObject::INPUT_STATE_CHANGE,
|
|
Vector3(getGameCursorPositionInternal(), delta), Vector3(mouseDelta.x, mouseDelta.y, 0) );
|
|
}
|
|
else
|
|
{
|
|
sendMouseEvent( InputObject::TYPE_MOUSEWHEEL,
|
|
InputObject::INPUT_STATE_CHANGE,
|
|
Vector3(getGameCursorPositionInternal(), delta), Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0) );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/////////// DONE PROCESSING ////////////////
|
|
postProcessUserInput(cursorMoved, leftMouseUp, wrapMouseDelta, mouseDelta);
|
|
}
|
|
|
|
void UserInput::postProcessUserInput(bool cursorMoved, bool leftMouseUp, RBX::Vector2 wrapMouseDelta, RBX::Vector2 mouseDelta)
|
|
{
|
|
UserInputService* userInputService = ServiceProvider::find<UserInputService>(game->getDataModel().get());
|
|
|
|
// kind of a hack to allows us to pan when mouse isn't moving
|
|
if (userInputService->getMouseWrapMode() == UserInputService::WRAP_HYBRID && !game->getDataModel()->getMouseOverGui())
|
|
{
|
|
doWrapHybrid(cursorMoved,
|
|
leftMouseUp,
|
|
wrapMouseDelta,
|
|
wrapMousePosition,
|
|
posToWrapTo);
|
|
}
|
|
else if (GameBasicSettings::singleton().inMousepanMode())
|
|
{
|
|
if (leftMouseButtonDown && GameBasicSettings::singleton().getCanMousePan())
|
|
userInputService->setMouseWrapMode(UserInputService::WRAP_CENTER);
|
|
else if (!rightMouseDown)
|
|
userInputService->setMouseWrapMode(UserInputService::WRAP_AUTO);
|
|
}
|
|
|
|
if (wrapMouseDelta != Vector2::zero())
|
|
{
|
|
if (RBX::Workspace* workspace = game->getDataModel()->getWorkspace())
|
|
{
|
|
if (Camera* camera = workspace->getCamera())
|
|
{
|
|
if (!(FFlag::UserAllCamerasInLua && camera->hasClientPlayer()))
|
|
{
|
|
if ( camera->getCameraType() != Camera::CUSTOM_CAMERA )
|
|
{
|
|
workspace->onWrapMouse(wrapMouseDelta);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sendMouseEvent(InputObject::TYPE_MOUSEDELTA, InputObject::INPUT_STATE_CHANGE, Vector3(getCursorPositionInternal(),0), Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0) );
|
|
sendMouseEvent(InputObject::TYPE_MOUSEMOVEMENT, InputObject::INPUT_STATE_CHANGE, Vector3(getCursorPositionInternal(),0), Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0) );
|
|
|
|
}
|
|
else if (cursorMoved)
|
|
{
|
|
if (DFFlag::MouseDeltaWhenNotMouseLocked)
|
|
{
|
|
sendMouseEvent(InputObject::TYPE_MOUSEMOVEMENT, InputObject::INPUT_STATE_CHANGE, Vector3(getCursorPositionInternal(),0), Vector3(mouseDelta.x,mouseDelta.y,0) );
|
|
}
|
|
else
|
|
{
|
|
sendMouseEvent(InputObject::TYPE_MOUSEMOVEMENT, InputObject::INPUT_STATE_CHANGE, Vector3(getCursorPositionInternal(),0), Vector3(wrapMouseDelta.x,wrapMouseDelta.y,0) );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void UserInput::sendMouseEvent(InputObject::UserInputType mouseEventType,
|
|
InputObject::UserInputState mouseEventState,
|
|
Vector3& position,
|
|
Vector3& delta)
|
|
{
|
|
FASTLOG1(FLog::UserInputProfile, "Processing mouse event from the queue, event: %u", mouseEventType);
|
|
|
|
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(shared_ptr<InputObject> event)
|
|
{
|
|
if( RBX::UserInputService* userInputService = RBX::ServiceProvider::find<RBX::UserInputService>(game->getDataModel().get()) )
|
|
userInputService->fireInputEvent(event, NULL);
|
|
}
|
|
|
|
bool UserInput::isFullScreenMode() const
|
|
{
|
|
return parentView->IsFullscreen();
|
|
}
|
|
|
|
G3D::Rect2D UserInput::getWindowRect() const
|
|
{
|
|
if (!DFFlag::UserInputViewportSizeFixWindows) {
|
|
RECT rect;
|
|
GetClientRect(wnd, &rect);
|
|
return Rect2D::xyxy(rect.left, rect.top, rect.right, rect.bottom);
|
|
}
|
|
else
|
|
{
|
|
RBX::Camera* cam = game->getDataModel().get()->getWorkspace()->getCamera();
|
|
return Rect2D::xywh(0.0f, 0.0f, cam->getViewportWidth(), cam->getViewportHeight());
|
|
}
|
|
}
|
|
|
|
G3D::Vector2int16 UserInput::getWindowSize() const
|
|
{
|
|
G3D::Rect2D windowRect = getWindowRect();
|
|
return G3D::Vector2int16((G3D::int16) windowRect.width(), (G3D::int16) windowRect.height());
|
|
}
|
|
|
|
TextureProxyBaseRef UserInput::getGameCursor(Adorn* adorn)
|
|
{
|
|
if (isMouseAcquired)
|
|
return __super::getGameCursor(adorn);
|
|
else
|
|
return TextureProxyBaseRef();
|
|
}
|
|
|
|
void UserInput::doWrapHybrid(bool cursorMoved, bool leftMouseUp, G3D::Vector2& wrapMouseDelta, G3D::Vector2& wrapMousePosition, G3D::Vector2& posToWrapTo)
|
|
{
|
|
}
|
|
|
|
bool UserInput::movementKeysDown()
|
|
{
|
|
return ( keyDownInternal(SDLK_w) || keyDownInternal(SDLK_s) || keyDownInternal(SDLK_a) || keyDownInternal(SDLK_d)
|
|
|| keyDownInternal(SDLK_UP)|| keyDownInternal(SDLK_DOWN) || keyDownInternal(SDLK_LEFT) || keyDownInternal(SDLK_RIGHT) );
|
|
}
|
|
|
|
G3D::Vector2 UserInput::getGameCursorPositionInternal()
|
|
{
|
|
RBXASSERT(isMouseAcquired);
|
|
return getWindowRect().center() + wrapMousePosition;
|
|
}
|
|
|
|
G3D::Vector2 UserInput::getGameCursorPositionExpandedInternal()
|
|
{
|
|
RBXASSERT(isMouseAcquired);
|
|
return getWindowRect().center() + Math::expandVector2(wrapMousePosition, 10);
|
|
}
|
|
|
|
// will be called repeatedly. ignore repeated calls.
|
|
void UserInput::onMouseInside()
|
|
{
|
|
if(isMouseInside)
|
|
return; // we know. ignore.
|
|
|
|
TRACKMOUSEEVENT tme;
|
|
tme.cbSize = sizeof(TRACKMOUSEEVENT);
|
|
tme.dwFlags = TME_LEAVE;
|
|
tme.hwndTrack = wnd;
|
|
|
|
if(UserInputService* userInputService = ServiceProvider::find<UserInputService>(game->getDataModel().get()))
|
|
{
|
|
if (userInputService->getMouseWrapMode() == RBX::UserInputService::WRAP_NONEANDCENTER)
|
|
{
|
|
centerCursor();
|
|
}
|
|
}
|
|
|
|
if (!TrackMouseEvent(&tme))
|
|
return;
|
|
|
|
isMouseInside = true;
|
|
|
|
if (!isMouseCaptured)
|
|
acquireMouseInternal();
|
|
else
|
|
RBXASSERT(isMouseAcquired); // isMouseCaptured should imply isMouseAquired.
|
|
}
|
|
|
|
void UserInput::onMouseLeave()
|
|
{
|
|
if (!isMouseInside)
|
|
return;
|
|
|
|
isMouseInside = false;
|
|
|
|
if(!isMouseCaptured)
|
|
{
|
|
// acquire could have failed... we could be already in
|
|
// an unacquired state.
|
|
if(isMouseAcquired)
|
|
unAcquireMouse();
|
|
}
|
|
}
|
|
|
|
G3D::Vector2 UserInput::getWindowsCursorPositionInternal()
|
|
{
|
|
RBXASSERT(!isMouseAcquired);
|
|
|
|
POINT p;
|
|
GetCursorPos(&p);
|
|
ScreenToClient(wnd, &p);
|
|
return G3D::Vector2(static_cast<float>(p.x), static_cast<float>(p.y));
|
|
}
|
|
|
|
G3D::Vector2 UserInput::getCursorPosition()
|
|
{
|
|
RobloxCritSecLoc lock(diSection, "getCursorPosition");
|
|
return getCursorPositionInternal();
|
|
}
|
|
|
|
G3D::Vector2 UserInput::getCursorPositionInternal()
|
|
{
|
|
return isMouseAcquired ? getGameCursorPositionInternal() : getWindowsCursorPositionInternal();
|
|
}
|
|
|
|
bool UserInput::keyDownInternal(KeyCode code) const
|
|
{
|
|
if (externallyForcedKeyDown && (code == externallyForcedKeyDown))
|
|
return true;
|
|
|
|
if (diKeys[UserInputUtil::keyCodeToDirectInput(code)] & 0x80)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UserInput::keyDown(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;
|
|
}
|
|
|
|
void UserInput::reacquireKeyboard()
|
|
{
|
|
DXINPUT_TRACE("UserInput::reaquireKeyboard begin\n");
|
|
unAcquireKeyboard();
|
|
setKeyboardDesiredInternal(true);
|
|
acquireKeyboard();
|
|
DXINPUT_TRACE("UserInput::reaquireKeyboard end\n");
|
|
}
|
|
|
|
void UserInput::processInput()
|
|
{
|
|
RobloxCritSecLoc lock(diSection, "processInput");
|
|
updateKeyboard();
|
|
|
|
if (isMouseAcquired)
|
|
readBufferedMouseData();
|
|
|
|
if (diKeyboardPtr!=NULL)
|
|
diKeyboardPtr->GetDeviceState(256, diKeys);
|
|
|
|
if (isKeyboardAcquired)
|
|
readBufferedKeyboardData();
|
|
}
|
|
|
|
void UserInput::doWrapMouse(const G3D::Vector2& delta, G3D::Vector2& wrapMouseDelta)
|
|
{
|
|
RBXASSERT(isMouseAcquired);
|
|
|
|
UserInputService* userInputService = ServiceProvider::find<UserInputService>(game->getDataModel().get());
|
|
|
|
switch (userInputService->getMouseWrapMode())
|
|
{
|
|
case UserInputService::WRAP_NONEANDCENTER: // intentional fall thru
|
|
centerCursor();
|
|
case RBX::UserInputService::WRAP_NONE: // intentional fall thru
|
|
case UserInputService::WRAP_CENTER:
|
|
UserInputUtil::wrapMouseCenter(delta,
|
|
wrapMouseDelta,
|
|
this->wrapMousePosition);
|
|
break;
|
|
case UserInputService::WRAP_HYBRID:
|
|
UserInputUtil::wrapMousePos(delta,
|
|
wrapMouseDelta,
|
|
wrapMousePosition,
|
|
getWindowSize(),
|
|
posToWrapTo,
|
|
autoMouseMove);
|
|
break;
|
|
case UserInputService::WRAP_AUTO:
|
|
if (!GameBasicSettings::singleton().inMousepanMode() && (movementKeysDown() || isMouseCaptured)) // 1. If movement keys are down - keep the mouse in the window
|
|
{
|
|
UserInputUtil::wrapMouseBorderLock(delta, // 2. If the left mouse button is down (we are dragging) - keep in the window
|
|
wrapMouseDelta,
|
|
this->wrapMousePosition,
|
|
getWindowSize());
|
|
}
|
|
else if (isFullScreenMode()) // 3. OK - we're in PlayMode (i.e. character)
|
|
{
|
|
UserInputUtil::wrapFullScreen(delta,
|
|
wrapMouseDelta,
|
|
this->wrapMousePosition,
|
|
getWindowSize());
|
|
}
|
|
else
|
|
{
|
|
// We no longer want mouse wrap and camera auto-pan at the horizontal window extents.
|
|
UserInputUtil::wrapMouseNone(delta, wrapMouseDelta,
|
|
this->wrapMousePosition);
|
|
}
|
|
break;
|
|
}
|
|
|
|
Vector2 pos = getGameCursorPositionInternal();
|
|
|
|
POINT p;
|
|
p.x = static_cast<int>(pos.x);
|
|
p.y = static_cast<int>(pos.y);
|
|
|
|
ClientToScreen(wnd, &p);
|
|
SetCursorPos(p.x, p.y);
|
|
}
|
|
|
|
void UserInput::doDiagnostics()
|
|
{
|
|
// Diagnostics
|
|
static int errorCount = 0;
|
|
static bool haveFailed = false;
|
|
if (isMouseAcquired)
|
|
{
|
|
if (haveFailed)
|
|
{
|
|
if (errorCount>0)
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_WARNING, "UserInput::attemptOwnMouse Failed to acquire mouse exclusively %d time(s)", errorCount);
|
|
errorCount = 0;
|
|
}
|
|
haveFailed = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static G3D::RealTime nextMessage = G3D::System::time();
|
|
++errorCount;
|
|
haveFailed = true;
|
|
if (nextMessage <= G3D::System::time())
|
|
{
|
|
StandardOut::singleton()->printf(MESSAGE_WARNING, "UserInput::attemptOwnMouse Failed to acquire mouse exclusively %d time(s)", errorCount);
|
|
errorCount = 0;
|
|
nextMessage = G3D::System::time() + 10.0f; // wait 10 seconds so as not to slow things down with logging
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace RBX
|