Files
watrbx-game-engine/App/v8datamodel/VirtualUser.cpp
T
2025-09-18 17:55:52 -04:00

326 lines
12 KiB
C++

/* Copyright 2003-2007 ROBLOX Corporation, All Rights Reserved */
#include "stdafx.h"
#include "V8DataModel/VirtualUser.h"
#include "V8DataModel/DataModel.h"
#include "V8DataModel/UserController.h"
#include "V8DataModel/Workspace.h"
#include "V8DataModel/Camera.h"
#include "util/UserInputBase.h"
#include "G3D/Quat.h"
namespace RBX {
REFLECTION_BEGIN();
static Reflection::BoundFuncDesc<VirtualUser, void(RBX::Vector2, RBX::CoordinateFrame)> func_ClickButton1(&VirtualUser::clickButton1, "ClickButton1", "position", "camera", RBX::CoordinateFrame(), Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, void(RBX::Vector2, RBX::CoordinateFrame)> func_Button1Down(&VirtualUser::button1Down, "Button1Down", "position", "camera", RBX::CoordinateFrame(), Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, void(RBX::Vector2, RBX::CoordinateFrame)> func_Button1Up(&VirtualUser::button1Up, "Button1Up", "position", "camera", RBX::CoordinateFrame(), Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, void(RBX::Vector2, RBX::CoordinateFrame)> func_ClickButton2(&VirtualUser::clickButton2, "ClickButton2", "position", "camera", RBX::CoordinateFrame(), Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, void(RBX::Vector2, RBX::CoordinateFrame)> func_Button2Down(&VirtualUser::button2Down, "Button2Down", "position", "camera", RBX::CoordinateFrame(), Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, void(RBX::Vector2, RBX::CoordinateFrame)> func_Button2Up(&VirtualUser::button2Up, "Button2Up", "position", "camera", RBX::CoordinateFrame(), Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, void(RBX::Vector2, RBX::CoordinateFrame)> func_MoveMouse(&VirtualUser::moveMouse, "MoveMouse", "position", "camera", RBX::CoordinateFrame(), Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, void()> func_StartRecording(&VirtualUser::startRecording, "StartRecording", Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, std::string()> func_StopRecording(&VirtualUser::stopRecording, "StopRecording", Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, void()> func_CaptureController(&VirtualUser::captureInputDevice, "CaptureController", Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, void(std::string)> func_PressKey(&VirtualUser::pressKey, "TypeKey", "key", Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, void(std::string)> func_SetKeyDown(&VirtualUser::setKeyDown, "SetKeyDown", "key", Security::TestLocalUser);
static Reflection::BoundFuncDesc<VirtualUser, void(std::string)> func_SetKeyUp(&VirtualUser::setKeyUp, "SetKeyUp", "key", Security::TestLocalUser);
REFLECTION_END();
const char* const sVirtualUser = "VirtualUser";
DataModel* VirtualUser::getDataModel()
{
DataModel* dataModel = DataModel::get(this);
if (!dataModel)
throw std::runtime_error("The service is not installed");
return dataModel;
}
class VirtualHardwareDevice : public UserInputBase
{
bool keyDownStates[512]; // TODO: Use boost bitfield?
public:
// For debugging convenience, we use an 800x600 virtual screen size
static const int screenWidth = 800;
static const int screenHeight = 600;
Vector2 position;
VirtualHardwareDevice()
{
memset(keyDownStates, 0, sizeof(keyDownStates));
}
virtual void centerCursor()
{
position = Vector2(screenWidth/2, screenHeight/2);
}
virtual bool keyDown(KeyCode code) const
{
return keyDownStates[code];
}
virtual void setKeyState(RBX::KeyCode code, RBX::ModCode modCode, char modifiedKey, bool isDown)
{
if (code<0)
return;
if ((size_t)code>=sizeof(keyDownStates))
return;
keyDownStates[code] = isDown;
}
virtual Vector2 getCursorPosition()
{
return position;
}
void renderGameCursor(Adorn* adorn)
{
// Can't reliably render the cursor
}
};
VirtualUser::VirtualUser()
{
}
void VirtualUser::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
{
if (virtualHardwareDevice)
{
ControllerService* service = ServiceProvider::find<ControllerService>(oldProvider);
if (service && service->getHardwareDevice()==virtualHardwareDevice.get())
service->setHardwareDevice(NULL);
virtualHardwareDevice.reset();
}
Super::onServiceProvider(oldProvider, newProvider);
}
void VirtualUser::captureInputDevice()
{
if (virtualHardwareDevice)
return;
// Replace the hardware controller with ours
ControllerService* service = ServiceProvider::create<ControllerService>(this);
if (!service)
throw std::runtime_error("Can't access controller");
virtualHardwareDevice.reset(new VirtualHardwareDevice());
service->setHardwareDevice(virtualHardwareDevice.get());
}
void VirtualUser::sendMouseEvent(InputObject::UserInputType eventType, InputObject::UserInputState eventState, RBX::Vector2 position, RBX::CoordinateFrame camera)
{
captureInputDevice();
// Convert from normalized coordinates to virtual screen coordinates
position = position.clamp(-1, 1);
position.x = VirtualHardwareDevice::screenWidth/2 * (1 + position.x);
position.y = VirtualHardwareDevice::screenHeight/2 * (1 - position.y);
virtualHardwareDevice->position = position;
Vector2int16 mousePosition((short)position.x, (short)position.y);
if (RBX::DataModel* dataModel = RBX::DataModel::get(this))
{
dataModel->getWorkspace()->getCamera()->setViewport(Vector2int16(VirtualHardwareDevice::screenWidth, VirtualHardwareDevice::screenHeight));
}
const shared_ptr<InputObject>& downEvent = Creatable<Instance>::create<InputObject>(eventType, eventState, Vector3(mousePosition,0), Vector3::zero(), RBX::DataModel::get(this));
Camera* c = getDataModel()->getWorkspace()->getCamera();
if (c && camera != CoordinateFrame())
{
//RBX::CoordinateFrame oldCamera = c->getCameraCoordinateFrame();
c->setCameraCoordinateFrame(camera);
getDataModel()->processInputObject(downEvent);
//c->setCameraCoordinateFrame(oldCamera);
}
else
getDataModel()->processInputObject(downEvent);
}
void VirtualUser::moveMouse(RBX::Vector2 position, RBX::CoordinateFrame camera)
{
sendMouseEvent(InputObject::TYPE_MOUSEMOVEMENT, InputObject::INPUT_STATE_CHANGE, position, camera);
}
void VirtualUser::clickButton1(RBX::Vector2 position, RBX::CoordinateFrame camera)
{
button1Down(position, camera);
button1Up(position, camera);
}
void VirtualUser::button1Down(RBX::Vector2 position, RBX::CoordinateFrame camera)
{
sendMouseEvent(InputObject::TYPE_MOUSEBUTTON1, InputObject::INPUT_STATE_BEGIN, position, camera);
}
void VirtualUser::button1Up(RBX::Vector2 position, RBX::CoordinateFrame camera)
{
sendMouseEvent(InputObject::TYPE_MOUSEBUTTON1, InputObject::INPUT_STATE_END, position, camera);
}
void VirtualUser::clickButton2(RBX::Vector2 position, RBX::CoordinateFrame camera)
{
button2Down(position, camera);
button2Up(position, camera);
}
void VirtualUser::button2Down(RBX::Vector2 position, RBX::CoordinateFrame camera)
{
sendMouseEvent(InputObject::TYPE_MOUSEBUTTON2, InputObject::INPUT_STATE_BEGIN, position, camera);
}
void VirtualUser::button2Up(RBX::Vector2 position, RBX::CoordinateFrame camera)
{
sendMouseEvent(InputObject::TYPE_MOUSEBUTTON2, InputObject::INPUT_STATE_END, position, camera);
}
KeyCode VirtualUser::convert(const std::string& key)
{
// Hex numbers are converted:
if (key.substr(0, 2)=="0x")
return (KeyCode) strtol(key.c_str(), NULL, 0);
// 3-digit strings are interpreted as decimal numbers
if (key.length()==3)
return (KeyCode) strtol(key.c_str(), NULL, 0);
if (key.length()!=1)
throw RBX::runtime_error("Unsupported key %s", key.c_str());
KeyCode keyCode = (KeyCode) key[0];
return keyCode;
}
void VirtualUser::setKeyDown(std::string key)
{
captureInputDevice();
KeyCode keyCode = convert(key);
if (virtualHardwareDevice->keyDown(keyCode))
return;
virtualHardwareDevice->setKeyState(keyCode, RBX::KMOD_NONE, 0, true);
const shared_ptr<InputObject>& downEvent = Creatable<Instance>::create<InputObject>(InputObject::TYPE_KEYBOARD, InputObject::INPUT_STATE_BEGIN, keyCode, (ModCode)0, (char)0, RBX::DataModel::get(this));
getDataModel()->processInputObject(downEvent);
}
void VirtualUser::setKeyUp(std::string key)
{
captureInputDevice();
KeyCode keyCode = convert(key);
if (!virtualHardwareDevice->keyDown(keyCode))
return;
virtualHardwareDevice->setKeyState(keyCode, RBX::KMOD_NONE, 0, false);
const shared_ptr<InputObject>& upEvent = Creatable<Instance>::create<InputObject>(InputObject::TYPE_KEYBOARD, InputObject::INPUT_STATE_END, keyCode, (ModCode)0,(char)0, RBX::DataModel::get(this));
getDataModel()->processInputObject(upEvent);
}
void VirtualUser::pressKey(std::string key)
{
setKeyDown(key);
setKeyUp(key);
}
void VirtualUser::startRecording()
{
if (recordingConnection.connected())
throw std::runtime_error("Already recording");
recording.clear();
recording << "-- Begin Recording\n";
recording << "local virtualUser = game:GetService('VirtualUser')\n";
recording << "virtualUser:CaptureController()\n";
lastEventTime = Time::now<Time::Fast>();
recordingConnection = getDataModel()->InputObjectProcessed.connect(boost::bind(&VirtualUser::onInputObject, this, _1));
}
static RBX::Vector2 toNormalized(Vector2int16 windowSize, const Vector2int16 mousePosition)
{
Vector2 position;
position.x = (2 * (float)mousePosition.x/(float)windowSize.x - 1);
position.y = - (2 * (float)mousePosition.y/(float)windowSize.y - 1);
return position.clamp(-1, 1);
}
void VirtualUser::writeWait()
{
Time now = Time::now<Time::Fast>();
recording << "wait(" << (now - lastEventTime).seconds() << ")\n";
lastEventTime = now;
}
void VirtualUser::writeKey(const char* func, const shared_ptr<InputObject>& event)
{
writeWait();
recording << "virtualUser:" << func << "('" << RBX::format("0x%02x", event->getKeyCode()) << "')";
if (event->isTextCharacterKey())
recording << " -- " << event->modifiedKey;
recording << '\n';
}
void VirtualUser::writeMouse(const char* func, const shared_ptr<InputObject>& event)
{
writeWait();
Vector2 m = toNormalized(event->getWindowSize(), event->get2DPosition());
CoordinateFrame camera;
if (Camera* c = getDataModel()->getWorkspace()->getCamera())
camera = c->getCameraCoordinateFrame();\
G3D::Quat q = camera.rotation;
recording << "virtualUser:" << func << "(";
recording << "Vector2.new(" << m.x << ", " << m.y << "), ";
recording << "CFrame.new("
<< camera.translation.x << ", " << camera.translation.y << ", " << camera.translation.z << ", "
<< q.x << ", " << q.y << ", " << q.z << ", " << q.w
<< ")";
recording << ")\n";
}
void VirtualUser::onInputObject( const shared_ptr<InputObject>& event )
{
switch (event->getUserInputType())
{
case InputObject::TYPE_KEYBOARD:
RBXASSERT(event->isKeyDownEvent() || event->isKeyUpEvent());
if (event->isKeyDownEvent())
writeKey("SetKeyDown", event);
else if (event->isKeyUpEvent())
writeKey("SetKeUp", event);
break;
case InputObject::TYPE_MOUSEBUTTON1:
RBXASSERT(event->isLeftMouseDownEvent() || event->isLeftMouseUpEvent());
if (event->isLeftMouseDownEvent())
writeMouse("Button1Down", event);
else if (event->isLeftMouseUpEvent())
writeMouse("Button1Up", event);
break;
default:
break;
}
}
std::string VirtualUser::stopRecording()
{
if (!recordingConnection.connected())
throw std::runtime_error("Not recording");
recordingConnection.disconnect();
recording << "-- End Recording\n";
return recording.str();
}
} // namespace RBX