/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */ #include "stdafx.h" #include "V8DataModel/UserController.h" #include "V8DataModel/PVInstance.h" #include "V8DataModel/Workspace.h" #include "V8DataModel/DataModel.h" #include "V8DataModel/VehicleSeat.h" #include "V8DataModel/GameBasicSettings.h" #include "V8DataModel/UserInputService.h" #include "Network/Players.h" #include "Humanoid/Humanoid.h" #include "V8tree/Service.h" #include "Util/UserInputBase.h" #include "Util/Math.h" #include "Util/NavKeys.h" #include "Gui/Widget.h" #include "AppDraw/DrawPrimitives.h" #include "Gui/GuiDraw.h" #include "G3D/g3dmath.h" LOGGROUP(UserInputProfile) #define TOUCH_LEFT_RIGHT_STEER_MIN 0.2 namespace RBX { const char* const sControllerService = "ControllerService"; const char* const sController = "Controller"; const char* const sHumanoidController = "HumanoidController"; const char* const sVehicleController = "VehicleController"; REFLECTION_BEGIN(); static Reflection::BoundFuncDesc func_BindButton(&Controller::bindButton, "BindButton", "button", "caption", Security::None); static Reflection::BoundFuncDesc dep_bindButton(&Controller::bindButton, "bindButton", "button", "caption", Security::None, Reflection::Descriptor::Attributes::deprecated(func_BindButton)); static Reflection::BoundFuncDesc controller_unbindButton(&Controller::unbindButton, "UnbindButton", "button", Security::None); static Reflection::BoundFuncDesc controller_getButton(&Controller::getButton, "GetButton", "button", Security::None); static Reflection::BoundFuncDesc dep_getButton(&Controller::getButton, "getButton", "button", Security::None, Reflection::Descriptor::Attributes::deprecated(controller_getButton)); static Reflection::EventDesc event_ButtonChanged(&Controller::buttonChangedSignal, "ButtonChanged", "button"); REFLECTION_END(); namespace Reflection { template<> EnumDesc::EnumDesc() :EnumDescriptor("Button") { addPair(Controller::JUMP, "Jump"); addPair(Controller::DISMOUNT, "Dismount"); } template<> Controller::Button& Variant::convert(void) { return genericConvert(); } }//namespace Reflection template<> bool RBX::StringConverter::convertToValue(const std::string& text, Controller::Button& value) { if(text.find("Jump")){ value = Controller::JUMP; return true; } if(text.find("Dismount")){ value = Controller::DISMOUNT; return true; } return false; } /////////////////////////////////////////////////////////////////////////////////////////////////// const UserInputBase* Controller::getHardwareDevice() const { const ControllerService* controllerService = Instance::fastDynamicCast(getParent()); RBXASSERT(controllerService == ServiceProvider::find(this)); return controllerService ? controllerService->getHardwareDevice() : NULL; } extern const char *const sButtonBindingWidget = "ButtonBindingWidget"; class ButtonBindingWidget // common root of Tool, HopperBin - stuff that can go in the hopper and on the player : public DescribedNonCreatable { private: typedef DescribedNonCreatable Super; GuiDrawImage guiImageDraw; TextureId textureId; Controller::Button button; weak_ptr controller; // Instance /*override*/ bool askSetParent(const Instance* instance) const; /*override*/ bool askAddChild(const Instance* instance) const; protected: // GuiItem /*override*/ Vector2 getSize(Canvas canvas) const; /*override*/ void render2d(Adorn* adorn); /*override*/ bool isEnabled() {return true;} /*override*/ void onClick(const shared_ptr& event); public: static std::string getKeyName(Controller::Button button); ButtonBindingWidget(Controller::Button button, Controller* controller); void setTextureId(const TextureId& value); const TextureId getTextureId() const; virtual bool drawEnabled() const {return true;} virtual bool drawSelected() const {return false;} }; RBX_REGISTER_CLASS(ButtonBindingWidget); ///////////////////////////////////////////////////////////////////////////////////////// ButtonBindingWidget::ButtonBindingWidget(Controller::Button button, Controller* controller) : button(button) { this->controller = shared_from(controller); } void ButtonBindingWidget::onClick(const shared_ptr& event) { shared_ptr sp = controller.lock(); if(sp) { sp->setButton(button, true); //sp->setButton(button, false); // note: find way to toggle. } } void ButtonBindingWidget::setTextureId(const TextureId& value) { if (value != textureId) { textureId = value; //raisePropertyChanged(desc_TextureId); } } const TextureId ButtonBindingWidget::getTextureId() const { return textureId; } bool ButtonBindingWidget::askAddChild(const Instance* instance) const { return true; // Anything is OK as a child } bool ButtonBindingWidget::askSetParent(const Instance* instance) const { return true; } Vector2 ButtonBindingWidget::getSize(Canvas canvas) const { float width = canvas.toPixelSize(Vector2(8, 8)).x; return Vector2(width, width); } std::string ButtonBindingWidget::getKeyName(Controller::Button button) { switch(button) { case Controller::DISMOUNT: return "Backspace"; default: return ""; } } void ButtonBindingWidget::render2d(Adorn* adorn) { if (UserInputService* userInputService = ServiceProvider::find(this)) if(!userInputService->getKeyboardEnabled()) return; // rendering is only for keyboard shortcuts bool viewHack = false; if (Workspace* workspace = ServiceProvider::find(this)) { if (workspace->imageServerViewHack) { viewHack = true; } } Rect draw = viewHack ? adorn->getCanvas().size : getMyRect(adorn->getCanvas()); draw = Rect::fromLowSize(Vector2(0,0),Vector2(200,30)); // compute font size int fontSize = adorn->getCanvas().normalizedFontSize(14); // Translucent background if (!viewHack) { adorn->rect2d(draw.toRect2D(), Color4(0.0f,0.0f,0.0f,0.5f)); } std::string keyName = getKeyName(button); if (isEnabled() && keyName != "") { // Number std::string text = "Press " + keyName + " to " + getTitle(); adorn->drawFont2D( text, draw.center(), (float)fontSize, false, Color3::white(), Color4::clear(), Text::FONT_ARIALBOLD, Text::XALIGN_CENTER, Text::YALIGN_CENTER ); } if (drawSelected()) { adorn->outlineRect2d( draw.toRect2D(), 3, Color3::green()); } // Draw image or command name /*if (guiImageDraw.setImage(adorn, textureId, GuiDrawImage::ALL)) { guiImageDraw.render2d(adorn, (isEnabled() || viewHack), draw.inset(fontSize), widgetState, drawSelected()); } else { Super::render2d(adorn); // widget rendering }*/ } /////////////////////////////////////////////////////////////////////////////////////////////////// Controller::Controller() : IStepped(StepType_HighPriority), dataModel(NULL) { FASTLOG1(FLog::ISteppedLifetime, "Controller created - %p", this); } Controller::~Controller() { FASTLOG1(FLog::ISteppedLifetime, "Controller destroyed - %p", this); } bool Controller::isButtonBound(Button button) const { return boundButtons.find(button) != boundButtons.end(); } std::string Controller::getButtonCaption(Button button) const { BoundButtonSet::const_iterator it = boundButtons.find(button); if(it != boundButtons.end()) { return it->second.caption; } else { throw std::runtime_error("Button has not been bound. Call BindButton first."); } } bool Controller::getButton(Button button) const { BoundButtonSet::const_iterator it = boundButtons.find(button); if(it != boundButtons.end()) { return it->second.pressed; } else { throw std::runtime_error("Button has not been bound. Call BindButton first."); } } bool Controller::getButton(Button button) { return (const_cast(this))->getButton(button); } void Controller::bindButton(Button button, std::string caption) { boundButtons.insert(std::make_pair(button, BoundButton(button, false, caption))); } void Controller::unbindButton(Button button) { BoundButtonSet::iterator it = boundButtons.find(button); if(it != boundButtons.end()) { it->second.guiWidget->setParent(NULL); boundButtons.erase(it); } } void Controller::setButton(Button button, bool value) { BoundButtonSet::iterator it = boundButtons.find(button); if(it != boundButtons.end()) { if(value != it->second.pressed) { it->second.pressed = value; buttonChangedSignal(button); } } // else ignore. } // Into / out of the datamodel void Controller::onAncestorChanged(const AncestorChanged& event) { Super::onAncestorChanged(event); // Note - moved this to first DataModel* newDataModel = DataModel::get(this); if (newDataModel != dataModel) { dataModel = newDataModel; } } /////////////////////////////////////////////////////////////////////////////////////////////////// VehicleController::VehicleController() {} void VehicleController::setVehicleSeat(VehicleSeat* value) { RBXASSERT(vehicleSeat.expired()); vehicleSeat = shared_from(value); } void VehicleController::onSteppedKeyboardInput(shared_ptr seat) { const UserInputBase* hardwareDevice = getHardwareDevice(); if (!hardwareDevice) return; NavKeys nav; if(DataModel* dm = DataModel::get(this)) hardwareDevice->getNavKeys(nav,dm->getSharedSuppressNavKeys()); int forwardBackward = nav.forwardBackwardArrow() + nav.forwardBackwardASDW(); int leftRight = nav.leftRightArrow() + nav.leftRightASDW(); seat->setThrottle(forwardBackward); seat->setSteer(-leftRight); // vehicle - left == -1, right == 1 } void VehicleController::onSteppedTouchInput(shared_ptr seat) { Humanoid* localHumanoid = seat->getLocalHumanoid(); if(!localHumanoid) return; seat->setThrottle(0); seat->setSteer(0); UserInputService* userInputService = ServiceProvider::find(this); // todo: remove getWalkDirection call when everything uses moveDirection Vector3 moveDirection = localHumanoid->getWalkDirection(); Vector2 movementVector = Vector2::zero(); if (moveDirection != Vector3::zero()) { movementVector = userInputService->getInputWalkDirection(); movementVector /= userInputService->getMaxInputWalkValue(); } else { moveDirection = localHumanoid->getRawMovementVector(); movementVector = Vector2(moveDirection.x,moveDirection.z); } if(moveDirection != Vector3::zero()) { if( UserInputService* userInputService = ServiceProvider::find(this) ) { if( movementVector.y > 0) seat->setThrottle(-1); else if( movementVector.y < 0 ) seat->setThrottle(1); float steerTolerance = TOUCH_LEFT_RIGHT_STEER_MIN; if(userInputService->getTouchEnabled()) steerTolerance *= 4; if( movementVector.x < -steerTolerance) seat->setSteer(-1); else if( movementVector.x > steerTolerance) seat->setSteer(1); } } } void VehicleController::onStepped(const Stepped& event) { shared_ptr sharedSeat = vehicleSeat.lock(); if (!sharedSeat) { RBXASSERT(0); return; } UserInputService* userInputService = ServiceProvider::find(this); if(userInputService->getTouchEnabled()) onSteppedTouchInput(sharedSeat); if(userInputService->getKeyboardEnabled()) onSteppedKeyboardInput(sharedSeat); } /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// HumanoidController::HumanoidController() : stepsRotating(0), panSensitivity(0.06f) {} void HumanoidController::rotateCam(int leftRight, Camera* camera, float gameStep) { camera->panRadians(panSensitivity * leftRight * static_cast(gameStep) * 30.0f); stepsRotating++; } bool HumanoidController::preventMovement( Humanoid* humanoid ) { // do not allow movement if player is // - typing // - sitting // - on a platform return humanoid->getTyping() || humanoid->getSit() || humanoid->getPlatformStanding(); } void HumanoidController::updateCamera( const Stepped& event, const NavKeys& nav ) { Workspace* workspace = ServiceProvider::find(this); Camera* camera = workspace->getCamera(); // if we're in cam lock mode, rotate by ASDW // otherwise, rotate by arrows int rotation = RBX::GameBasicSettings::singleton().camLockedInCamLockMode() ? rotation = nav.leftRightASDW() : rotation = nav.leftRightArrow(); if( camera->getCameraType() != Camera::LOCKED_CAMERA && rotation != 0 ) { // if we have a rotation, rotate by it rotateCam( rotation, camera, event.gameStep ); } else { // if we didn't rotate, reset steps rotating stepsRotating = 0; } } ////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////// ControllerService::ControllerService() : hardwareDevice(NULL) { shared_ptr humanoidController = Creatable::create(); humanoidController->setParent(this); } } // namespace