/* Copyright 2003-2007 ROBLOX Corporation, All Rights Reserved */ #include "stdafx.h" #include "V8DataModel/Tool.h" #include "V8DataModel/Backpack.h" #include "V8DataModel/PartInstance.h" #include "V8DataModel/JointInstance.h" #include "V8DataModel/Workspace.h" #include "V8DataModel/TimerService.h" #include "V8DataModel/Accoutrement.h" #include "V8DataModel/Attachment.h" #include "Humanoid/Humanoid.h" #include "V8DataModel/ToolMouseCommand.h" #include "V8DataModel/Mouse.h" #include "V8DataModel/UserInputService.h" #include "V8DataModel/GuiService.h" #include "Network/Players.h" #include "Network/Player.h" #include "Tool/DragUtilities.h" #include "Script/Script.h" #include "FastLog.h" LOGGROUP(UserInputProfile) FASTFLAG(StudioDE6194FixEnabled) namespace RBX { using namespace RBX::Network; const char* const sTool = "Tool"; REFLECTION_BEGIN(); static const Reflection::PropDescriptor prop_Grip("Grip", category_Appearance, &Tool::getGrip, &Tool::setGrip, Reflection::PropertyDescriptor::STANDARD); static const Reflection::PropDescriptor prop_GripPos("GripPos", category_Appearance, &Tool::getGripPos, &Tool::setGripPos, Reflection::PropertyDescriptor::UI); static const Reflection::PropDescriptor prop_GripForward("GripForward", category_Appearance, &Tool::getGripForward, &Tool::setGripForward, Reflection::PropertyDescriptor::UI); static const Reflection::PropDescriptor prop_GripUp("GripUp", category_Appearance, &Tool::getGripUp, &Tool::setGripUp, Reflection::PropertyDescriptor::UI); static const Reflection::PropDescriptor prop_GripRight("GripRight", category_Appearance, &Tool::getGripRight, &Tool::setGripRight, Reflection::PropertyDescriptor::UI); static const Reflection::PropDescriptor prop_ToolTip("ToolTip", category_Appearance, &Tool::getToolTip, &Tool::setToolTip); static const Reflection::PropDescriptor prop_manualActivation("ManualActivationOnly", category_Behavior, &Tool::getManualActivationOnly, &Tool::setManualActivationOnly); static const Reflection::PropDescriptor prop_CanDrop("CanBeDropped", category_Behavior, &Tool::isDroppable, &Tool::setDroppable); static const Reflection::PropDescriptor prop_requiresHandle("RequiresHandle", category_Behavior, &Tool::getRequiresHandle, &Tool::setRequiresHandle); Reflection::BoundProp Tool::prop_Enabled("Enabled", "State", &Tool::enabled); // Backend events static Reflection::EventDesc), Tool::special_equipped_signal> event_Equipped( &Tool::equippedSignal, "Equipped", "mouse"); static Reflection::EventDesc event_Unequipped(&Tool::unequippedSignal, "Unequipped"); static Reflection::RemoteEventDesc event_Activated(&Tool::activatedSignal, "Activated", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER); static Reflection::RemoteEventDesc event_Deactivated(&Tool::deactivatedSignal, "Deactivated", Security::None, Reflection::RemoteEventCommon::SCRIPTING, Reflection::RemoteEventCommon::CLIENT_SERVER); static Reflection::BoundFuncDesc func_Activate(&Tool::luaActivate, "Activate", Security::None); REFLECTION_END(); Tool::special_equipped_signal::special_equipped_signal() : Super(), currentlyEquipped(false), lastArg() {} void Tool::special_equipped_signal::operator()(shared_ptr instance) { // This operator is used by EventDesc.fireEvent(). In order to track // the equipped/unequipped state correctly, use the equipped() or // unequipped() methods instead. throw std::runtime_error("Don't use Event.fireEvent for equipped signal!"); } void Tool::special_equipped_signal::equipped(shared_ptr instance) { currentlyEquipped = true; lastArg = instance; Super::operator()(instance); } void Tool::special_equipped_signal::unequipped() { currentlyEquipped = false; lastArg.reset(); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // FRONTEND AND BACKEND Tool::Tool() : DescribedCreatable() , backendToolState(NOTHING) , frontendActivationState(0) , enabled(true) , workspaceForToolMouseCommand(NULL) , handleTouched(FLog::TouchedSignal != 0) , droppable(true) , toolTip("") , requiresHandle(true) , manualActivationOnly(false) , ownWeld(false) { Instance::setName(sTool); } Tool::~Tool() { RBXASSERT(!handleTouched.connected()); RBXASSERT(!characterChildAdded.connected()); RBXASSERT(!characterChildRemoved.connected()); RBXASSERT(!torsoChildAdded.connected()); RBXASSERT(!torsoChildRemoved.connected()); } void Tool::render3dSelect(Adorn* adorn, SelectState selectState) { for (size_t i = 0; i < this->numChildren(); ++i) { if (IAdornable* iRenderable = dynamic_cast(this->getChild(i))) { iRenderable->render3dSelect(adorn, selectState); } } } bool Tool::characterCanUnequipTool(ModelInstance *character) { if (character == NULL) return true; Humanoid* humanoid = Humanoid::modelIsCharacter(character); if (humanoid) { Tool *tool = Instance::fastDynamicCast(character->findFirstChildOfType()); if (tool) return tool->canUnequip(); } return true; } bool Tool::isSelectable3d() { return !FFlag::StudioDE6194FixEnabled || countDescendantsOfType() > 0; } PartInstance* Tool::getHandle() { return const_cast(getHandleConst()); } const PartInstance* Tool::getHandleConst() const { return Instance::fastDynamicCast(findConstFirstChildByName("Handle")); } const CoordinateFrame Tool::getLocation() { const PartInstance* handle = getHandleConst(); return handle ? handle->getCoordinateFrame() : CoordinateFrame(); } // Must be called externally - changes tool parent void Tool::dropAll(Network::Player* player) { if (ModelInstance* character = player->getCharacter()) { if (Workspace* workspaceLocal = Workspace::findWorkspace(character)) { while (Tool* tool = character->findFirstChildOfType()) { if (!tool->isDroppable()) tool->setParent(player->getPlayerBackpack()); else tool->setParent(workspaceLocal); // ok - should drop! check it out } } } } void Tool::moveAllToolsToBackpack(Network::Player* player) { if (player) { if (ModelInstance* m = player->getCharacter()) { while (Tool* tool = m->findFirstChildOfType()) { tool->setParent(player->getPlayerBackpack()); } } } } shared_ptr Tool::createMouse() { shared_ptr mouse = Creatable::create(); mouse->setWorkspace(NULL); return mouse; } /* watch_touch ACTIVATED EQUIPPED (Weld) HAS_TORSO x IN_CHARACTER x IN_WORKSPACE x -> needs touch or parent already set to proceed HAS_HANDLE x NOTHING */ void Tool::setBackendToolState(int value) { if (value != backendToolState) { if ((backendToolState < EQUIPPED) && (value >= EQUIPPED)) { shared_ptr mouse = onEquipping(); if (!mouse.get()) { mouse = createMouse(); } currentMouse = mouse; equippedSignal.equipped(mouse); } if ((backendToolState >= EQUIPPED) && (value < EQUIPPED)) { equippedSignal.unequipped(); unequippedSignal(); if (currentMouse) { currentMouse->setIcon(RBX::TextureId()); } onUnequipped(); } backendToolState = static_cast(value); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // BACKEND void Tool::connectTouchEvent() { PartInstance* handle = getHandle(); if (handle) { handleTouched = handle->onDemandWrite()->touchedSignal.connect(boost::bind(&Tool::onEvent_HandleTouched, shared_from(this), _1)); FASTLOG3(FLog::TouchedSignal, "Connecting Tool to touched signal, instance: %p, part: %p, part signal: %p", this, handle, &(handle->onDemandRead()->touchedSignal)); } else { FASTLOG2(FLog::TouchedSignal, "Disconnecting handle Touched since we don't have part. Tool: %p, connection: %p", this, &handleTouched); // RBXASSERT(0); // only call if >= the HAS_HANDLE state handleTouched.disconnect(); // just to be safe } } void Tool::rebuildBackendState() { ToolState desiredState = computeDesiredState(); const ServiceProvider* serviceProvider = ServiceProvider::findServiceProvider(this); RBXASSERT(serviceProvider); setDesiredState(desiredState, serviceProvider); } /* Backend: watch_touch EQUIPPED (Weld) HAS_TORSO x IN_CHARACTER x IN_WORKSPACE x -> needs touch or parent already set to proceed HAS_HANDLE x -> unless not in workspace NOTHING */ Tool::ToolState Tool::computeDesiredState() { PartInstance* handle = getHandle(); if (!handle && getRequiresHandle()) { return NOTHING; } if (!Workspace::contextInWorkspace(this)) { return NOTHING; // if tool is not in workspace, no need to listen to touch events } return computeDesiredState(getParent()); } Tool::ToolState Tool::computeDesiredState(Instance* testParent) { Humanoid* humanoid = Humanoid::modelIsCharacter(testParent); if (!humanoid) { return IN_WORKSPACE; } if (!humanoid->getTorsoSlow()) { return IN_CHARACTER; } if (humanoid->getUseR15()) { if (Instance::fastDynamicCast(humanoid->getParent()->findFirstChildByName("RightHand")) == NULL) return HAS_TORSO; } else { if (!(humanoid->getRightArmSlow() && humanoid->getRightShoulder())) { return HAS_TORSO; } } return EQUIPPED; } void Tool::setDesiredState(ToolState desiredState, const ServiceProvider* serviceProvider) { #ifdef RBXASSERTENABLED int numToolsInCharacter; #endif RBXASSERT((numToolsInCharacter = getNumToolsInCharacter()) || 1); // short cut if (desiredState == EQUIPPED && backendToolState == NOTHING) fromNothingToEquipped(Network::Players::backendProcessing(serviceProvider)); else if (desiredState == NOTHING && backendToolState == EQUIPPED) fromEquippedToNothing(); else { ToolState currentState = backendToolState; // go the long way if (desiredState > currentState) { do { switch (currentState) { case EQUIPPED: break; case HAS_TORSO: upTo_Equipped(); break; case IN_CHARACTER: upTo_HasTorso(); break; case IN_WORKSPACE: upTo_InCharacter(); break; case HAS_HANDLE: upTo_InWorkspace(); break; case NOTHING: upTo_HasHandle(); break; } currentState = static_cast(currentState + 1); } while (desiredState > currentState); } else if (desiredState < currentState) { do { switch (currentState) { case EQUIPPED: downFrom_Equipped(Network::Players::backendProcessing(serviceProvider)); break; case HAS_TORSO: downFrom_HasTorso(); break; case IN_CHARACTER: downFrom_InCharacter(); break; case IN_WORKSPACE: downFrom_InWorkspace(); break; case HAS_HANDLE: downFrom_HasHandle(); break; case NOTHING: RBXASSERT(0); break; } currentState = static_cast(currentState - 1); } while (desiredState < currentState); } setBackendToolState(currentState); } RBXASSERT(numToolsInCharacter == getNumToolsInCharacter()); } void Tool::fromNothingToEquipped(bool isBackend) { // No need to call upTo_HasHandle, because we don't need handle touch events when the tool is equipped RBXASSERT(!handleTouched.connected()); setBackendToolState(EQUIPPED); if (isBackend) { upTo_InWorkspace(); upTo_InCharacter(); upTo_HasTorso(); } upTo_Equipped(); } void Tool::fromEquippedToNothing() { RBXASSERT(!handleTouched.connected()); downFrom_Equipped(false); // specify false here to indicate we don't need handle touch events downFrom_HasTorso(); downFrom_InCharacter(); downFrom_InWorkspace(); // No need to call downFrom_HasHandle, because handle touch events should already be disconnected setBackendToolState(NOTHING); } void Tool::upTo_HasHandle() { connectTouchEvent(); } void Tool::downFrom_HasHandle() { FASTLOG2(FLog::TouchedSignal, "Disconnecting handle in downFrom_HasHandle. Tool: %p, connection: %p", this, &handleTouched); handleTouched.disconnect(); } void Tool::upTo_InWorkspace() { RBXASSERT(ServiceProvider::findServiceProvider(this)); } void Tool::downFrom_InWorkspace() { } void Tool::upTo_InCharacter() { RBXASSERT(Humanoid::modelIsCharacter(getParent())); if (getParent()) { characterChildAdded = getParent()->onDemandWrite()->childAddedSignal.connect(boost::bind(&Tool::onEvent_AddedBackend, shared_from(this), _1)); characterChildRemoved = getParent()->onDemandWrite()->childRemovedSignal.connect(boost::bind(&Tool::onEvent_RemovedBackend, shared_from(this), _1)); } else { characterChildAdded.disconnect(); characterChildRemoved.disconnect(); } } void Tool::downFrom_InCharacter() { characterChildAdded.disconnect(); characterChildRemoved.disconnect(); } void Tool::upTo_HasTorso() { Humanoid* humanoid = Humanoid::modelIsCharacter(getParent()); RBXASSERT(humanoid); PartInstance* torso = humanoid->getTorsoSlow(); RBXASSERT(torso); torsoChildAdded = torso->onDemandWrite()->childAddedSignal.connect(boost::bind(&Tool::onEvent_AddedBackend, shared_from(this), _1)); torsoChildRemoved = torso->onDemandWrite()->childRemovedSignal.connect(boost::bind(&Tool::onEvent_RemovedBackend, shared_from(this), _1)); } void Tool::downFrom_HasTorso() { torsoChildAdded.disconnect(); torsoChildRemoved.disconnect(); } void Tool::upTo_Equipped() { FASTLOG2(FLog::TouchedSignal, "Disconnecting handle in upTo_Equipped. Tool: %p, connection: %p", this, &handleTouched); // Disconnect listening for touches handleTouched.disconnect(); // Build the weld Humanoid* humanoid = Humanoid::modelIsCharacter(getParent()); if (!humanoid) return; PartInstance* arm; if (humanoid->getUseR15()) { arm = Instance::fastDynamicCast(humanoid->getParent()->findFirstChildByName("RightHand")); } else { arm = humanoid->getRightArmSlow(); } if (!arm) return; PartInstance* handle = getHandle(); if (ownWeld) { if (handle) { // This will be blocked by filter enabled on the client, but server will also run this code and set correct value handle->setCanCollide(false); buildWeld(arm, handle, humanoid->getRightArmGrip(), grip, "RightGrip"); } } else if (Network::Players::backendProcessing(this)) { if (handle) { // search for weld weld = Instance::fastSharedDynamicCast(arm->findFirstChildByName2("RightGrip", false)); if (!weld) armChildAdded = arm->onDemandWrite()->childAddedSignal.connect(boost::bind(&Tool::onEvent_AddedToArmBackend, shared_from(this), _1, handle)); handle->setCanCollide(false); } } } void Tool::downFrom_Equipped(bool connectTouchEvent) { ModelInstance* oldCharacter = NULL; if (weld) { armChildAdded.disconnect(); if (PartInstance* oldArm = weld->getPart0()) { oldCharacter = Instance::fastDynamicCast(oldArm->getParent()); } weld->setParent(NULL); weld.reset(); } if (PartInstance* handle = getHandle()) { handle->setCanCollide(true); if (oldCharacter && Humanoid::modelIsCharacter(oldCharacter)) { CoordinateFrame cf = oldCharacter->getLocation(); Vector3 pos = cf.translation; pos.y += 4; pos += cf.lookVector() * 8; handle->moveToPointNoUnjoinNoJoin(pos); // important - otherwise may cause recursive unjoining handle->setRotationalVelocity(Vector3(1.0f, 1.0f, 1.0f)); } } if (connectTouchEvent) { // Dropping - must start listening again this->connectTouchEvent(); } } ////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////// // // Backend stuff - events // Used for both the tool and the torso // // Character: RightArm, Tool // Torso: Shoulder? // void Tool::onEvent_AddedBackend(shared_ptr child) { if (child.get() != this) // tool parent changed is handled in ancestor stuff, because this might not be hooked up { RBXASSERT(ServiceProvider::findServiceProvider(this)); rebuildBackendState(); } } void Tool::onEvent_RemovedBackend(shared_ptr child) { // there's a chance this is being called when a character is being removed - trap here if (ServiceProvider::findServiceProvider(this)) { if (child.get() != this) // tool parent changed is handled in ancestor stuff, because this might not be hooked up { if (child.get() == weld.get()) { RBXASSERT(backendToolState >= EQUIPPED); setDesiredState(HAS_TORSO, ServiceProvider::findServiceProvider(this)); // force down, nuke the weld } rebuildBackendState(); } } } void Tool::onChildAdded(Instance* child) { if (ServiceProvider::findServiceProvider(this) && Network::Players::backendProcessing(this)) { rebuildBackendState(); } } void Tool::onChildRemoved(Instance* child) { Super::onChildRemoved(child); if (ServiceProvider::findServiceProvider(this) && Network::Players::backendProcessing(this)) { rebuildBackendState(); } } void Tool::onEvent_AddedToArmBackend(shared_ptr child, Instance *handle) { shared_ptr w = Instance::fastSharedDynamicCast(child); if (w && w->getName() == "RightGrip" && w->getPart1() == handle) { RBXASSERT(!weld); weld = w; armChildAdded.disconnect(); } } // This is a backend event void Tool::onEvent_HandleTouched(shared_ptr other) { // this is probably going off because you are trying to JOIN a game in distributed physics mode with no server found RBXASSERT(Network::Players::backendProcessing(this)); if ((backendToolState == IN_WORKSPACE) && other) { Instance* touchingCharacter = other->getParent(); if (computeDesiredState(touchingCharacter) == EQUIPPED) { if (characterCanUnequipTool(Instance::fastDynamicCast(touchingCharacter))) { Player *p = Players::getPlayerFromCharacter(touchingCharacter); if (p && canBePickedUpByPlayer(p)) { moveAllToolsToBackpack(p); setParent(touchingCharacter); RBXASSERT(backendToolState == EQUIPPED); // Move any other tool to the backpack, should the player // have more than one equipped at the same time. boost::weak_ptr player_weak_ptr = weak_from(p); setTimerCallback(player_weak_ptr); } } } } } static void disableHopperBin(Instance* instance) { HopperBin* hopperBin = instance->findFirstChildOfType(); if (hopperBin) hopperBin->disable(); } // Sets a timer callback so we can move any other // tool to the player's backpack should there // be multiple tools equipped at one time. // // This happens when the player touches a tool // that's on the ground at the same time that he // equips one from his backpack. // // This is not the perfect solution, nor it's pretty, but // it's less invasive than rewriting a bunch of things in // order to fix this bug. void Tool::setTimerCallback(weak_ptr player) { TimerService* callback = ServiceProvider::create(this); if (callback) { boost::function func = boost::bind(&Tool::moveOtherToolsToBackpack, this, player); callback->delay(func, 0.2); } } // Moves tool pointed by "child" to the backpack, if it's a tool other // than the one we'd like to keep (toolToKeep). // // See Tool::setTimerCallback for more details on where this is used. static void moveToBackpack(shared_ptr child, Tool* toolToKeep, Backpack* backpack) { Tool* tool = Instance::fastDynamicCast(child.get()); // We only care about the other tools if (!tool || tool == toolToKeep) return; // Move it to the backpack tool->setParent(backpack); } // Moves all other instances of Tool to the player's backpack. // // See Tool::setTimerCallback comments for more details on where this is used. void Tool::moveOtherToolsToBackpack(weak_ptr player_weak_ptr) { boost::shared_ptr player = player_weak_ptr.lock(); if (backendToolState < EQUIPPED || !player) return; Backpack* backpack = player->getPlayerBackpack(); if (!backpack) return; ModelInstance* character = player->getCharacter(); if (!character) return; character->visitChildren(boost::bind(&moveToBackpack, _1, this, backpack)); disableHopperBin(backpack); } // Debugging int Tool::getNumToolsInCharacter() { int answer = 0; if (Humanoid::modelIsCharacter(this->getParent())) { Instance* character = this->getParent(); for (size_t i = 0; i < character->numChildren(); ++i) { Instance* child = character->getChild(i); if (Instance::fastDynamicCast(child)) { answer++; } } } return answer; } void Tool::onAncestorChanged(const AncestorChanged& event) { #ifdef RBXASSERTENABLED int numToolsInCharacter; #endif RBXASSERT((numToolsInCharacter = getNumToolsInCharacter()) || 1); const ServiceProvider* oldProvider = ServiceProvider::findServiceProvider(event.oldParent); const ServiceProvider* newProvider = ServiceProvider::findServiceProvider(event.newParent); if (oldProvider) { ownWeld = false; if (Network::Players::backendProcessing(oldProvider)) { setDesiredState(NOTHING, oldProvider); } else { setBackendToolState(NOTHING); } } Super::onAncestorChanged(event); if (newProvider) { bool frontendProcessing = Network::Players::frontendProcessing(newProvider); bool backendProcessing = Network::Players::backendProcessing(newProvider); RBXASSERT(ServiceProvider::findServiceProvider(this) == newProvider); if (backendProcessing) { Player* player = NULL; if (frontendProcessing) // solo play ownWeld = true; else { // if this tool is being parented to NPC, backend will take care of everything if (Humanoid::modelIsCharacter(getParent())) { player = Network::Players::getPlayerFromCharacter(getParent()); if(!player) { ownWeld = true; } } } // backend should own weld for all tools not being equipped from back backpack if (player && event.oldParent != player->getPlayerBackpack()) ownWeld = true; rebuildBackendState(); } else // frontend { Player* player = Players::findLocalPlayer(this); if (player && (getParent() == player->getCharacter()) /*&& getHandle()*/) { // frontend should not create weld if tool came from outside the players backpack if (event.oldParent == player->getPlayerBackpack()) ownWeld = true; rebuildBackendState(); } } } // Checking to make sure that we don't have setParent byproducts here RBXASSERT(numToolsInCharacter == getNumToolsInCharacter()); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Frontend only stuff // void Tool::onUnequipped() { if (workspaceForToolMouseCommand) { workspaceForToolMouseCommand->setDefaultMouseCommand(); workspaceForToolMouseCommand = NULL; } } shared_ptr Tool::onEquipping() { if (getParent()){ if(Network::Players::frontendProcessing(this)){ Player* player = Players::findLocalPlayer(this); if(player && player->getCharacter() == getParent()){ workspaceForToolMouseCommand = ServiceProvider::find(this); shared_ptr toolMouseCommand = Creatable::create(workspaceForToolMouseCommand, this); workspaceForToolMouseCommand->setMouseCommand(toolMouseCommand); currentToolMouseCommand = toolMouseCommand; return toolMouseCommand->getMouse(); } } } shared_ptr nothing; return nothing; } void Tool::setMousePositionForInputType() { if (RBX::UserInputService* inputService = RBX::ServiceProvider::find(this)) { if (inputService->getGamepadEnabled() ) { if (currentMouse) { currentMouse->update(inputService->getCurrentMousePosition()); } if (currentToolMouseCommand) { currentToolMouseCommand->onMouseDown(inputService->getCurrentMousePosition()); } } // if we are a touch device, make sure mouse points to center of all touches else if (inputService->getTouchEnabled()) { UserInputService::InputObjectVector currentTouches = inputService->getCurrentTouches(); RBX::StandardOut::singleton()->printf(MESSAGE_INFO, "current num of touches is %lu",currentTouches.size()); if (currentTouches.size() > 0) { Vector2 avgTouchPosition = Vector2::zero(); int numOfTouches = 0; for(UserInputService::InputObjectVector::iterator iter = currentTouches.begin(); iter != currentTouches.end(); ++iter) { avgTouchPosition += (*iter)->get2DPosition(); numOfTouches++; } avgTouchPosition = avgTouchPosition/numOfTouches; shared_ptr fakeMouseActivatePosition = RBX::Creatable::create(InputObject::TYPE_MOUSEMOVEMENT, InputObject::INPUT_STATE_CHANGE, Vector3(avgTouchPosition.x,avgTouchPosition.y,0), Vector3::zero(), RBX::DataModel::get(this)); if (currentMouse) { currentMouse->update(fakeMouseActivatePosition); } if (currentToolMouseCommand) { currentToolMouseCommand->onMouseDown(fakeMouseActivatePosition); } } } } } void Tool::setManualActivationOnly(bool value) { if (value != manualActivationOnly) { manualActivationOnly = value; raisePropertyChanged(prop_manualActivation); } } void Tool::luaActivate() { if (backendToolState == EQUIPPED) { setMousePositionForInputType(); activate(true); } else { RBX::StandardOut::singleton()->printf(MESSAGE_WARNING, "Tool:Activate() called from script when tool is not equipped. Tool will not be activated."); } } void Tool::activate(bool manuallyActivated) { if (!manuallyActivated && getManualActivationOnly()) { return; } FASTLOG(FLog::UserInputProfile, "Tool::activate"); RBXASSERT(Network::Players::frontendProcessing(this)); event_Activated.fireAndReplicateEvent(this); } void Tool::deactivate() { RBXASSERT(Network::Players::frontendProcessing(this)); event_Deactivated.fireAndReplicateEvent(this); } // Local Event - must be from outside void Tool::onLocalClicked() { RBXASSERT(Network::Players::frontendProcessing(this)); if (Player* player = Players::findLocalPlayer(this)) { if (characterCanUnequipTool(player->getCharacter())) { if (this->getParent() == player->getPlayerBackpack()) { RBXASSERT(player); moveAllToolsToBackpack(player); setParent(player->getCharacter()); } else { setParent(player->getPlayerBackpack()); } } } // Confirm I'm not starting with a bad parent/child relationship - i.e., >1 tool in a character RBXASSERT(this->getNumToolsInCharacter() < 2); } void Tool::onLocalOtherClicked() { RBXASSERT(Network::Players::frontendProcessing(this)); if (Player* player = Players::findLocalPlayer(this)) { if (characterCanUnequipTool(player->getCharacter())) { if (this->getParent() != player->getPlayerBackpack()) setParent(player->getPlayerBackpack()); } } } ////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////// void cleanUpZeroColumn(Matrix3& r) { bool zeroColumn = false; for (int i = 0; i < 3; ++i) { Vector3 c = r.column(i); // Hack - prevent matrices with all 0 rows if (c.squaredMagnitude() < 1e-6f) { zeroColumn = true; break; } } if (zeroColumn) { r.setColumn(0, Vector3(0,-1,0)); //right r.setColumn(1, Vector3(1,0,0)); // up r.setColumn(2, Vector3(0,0,-1)); // front - corresponds to 0,0,1 from properties } } void Tool::setGrip(const CoordinateFrame& value) { if (value != grip) { grip = value; cleanUpZeroColumn(grip.rotation); // MEGA HACK!!! Math::orthonormalizeIfNecessary(grip.rotation); raisePropertyChanged(prop_Grip); if (weld != NULL) { // all this does not occur client side RBXASSERT(Network::Players::backendProcessing(this)); weld->setC1(grip); } } } // Auxiliary UI props const Vector3 Tool::getGripPos() const { return grip.translation; } const Vector3 Tool::getGripForward() const { return grip.lookVector(); } const Vector3 Tool::getGripUp() const { return grip.upVector(); } const Vector3 Tool::getGripRight() const { return grip.rightVector(); } void Tool::setGripPos(const Vector3& v) { setGrip(CoordinateFrame(grip.rotation, v)); } void Tool::setGripForward(const Vector3& v) { CoordinateFrame c(grip); //Vector3 oldX = c.rightVector(); Vector3 oldY = c.upVector(); Vector3 z = -Math::safeDirection(v); Vector3 x = oldY.cross(z); x.unitize(); Vector3 y = z.cross(x); y.unitize(); c.rotation.setColumn(0, x); c.rotation.setColumn(1, y); c.rotation.setColumn(2, z); setGrip(c); } void Tool::setGripUp(const Vector3& v) { CoordinateFrame c(grip); Vector3 oldX = c.rightVector(); //Vector3 oldZ = -c.lookVector(); Vector3 y = Math::safeDirection(v); Vector3 z = oldX.cross(y); z.unitize(); Vector3 x = y.cross(z); x.unitize(); c.rotation.setColumn(0, x); c.rotation.setColumn(1, y); c.rotation.setColumn(2, z); setGrip(c); } void Tool::setGripRight(const Vector3& v) { CoordinateFrame c(grip); Vector3 oldY = c.upVector(); //Vector3 oldZ = -c.lookVector(); Vector3 x = Math::safeDirection(v); Vector3 z = x.cross(oldY); z.unitize(); Vector3 y = z.cross(x); y.unitize(); c.rotation.setColumn(0, x); c.rotation.setColumn(1, y); c.rotation.setColumn(2, z); setGrip(c); } void Tool::setToolTip(std::string value) { if(value.size() > ContentFilter::MAX_CONTENT_FILTER_SIZE) value = value.substr(0, ContentFilter::MAX_CONTENT_FILTER_SIZE); if(!ProfanityFilter::ContainsProfanity(value) || getRobloxLocked()) { if(toolTip != value) { toolTip = value; raisePropertyChanged(prop_ToolTip); } } } void Tool::setRequiresHandle(bool value) { if (requiresHandle != value) { requiresHandle = value; raisePropertyChanged(prop_requiresHandle); } } Attachment *Tool::findFirstAttachmentByName(const Instance *part, const std::string& findName) { if (!part || !part->getChildren()) return NULL; const Instances& children = *part->getChildren(); for (size_t i = 0; i < children.size(); ++i) { Attachment *attach = Instance::fastDynamicCast(children[i].get()); if (attach == NULL) continue; if (attach->getName() == findName) { return attach; } } return NULL; } Attachment *Tool::findFirstAttachmentByNameRecursive(const Instance *part, const std::string& findName) { if (!part) return NULL; // breadth-first search Attachment *child = findFirstAttachmentByName(part, findName); if (child != NULL) return child; if (!part->getChildren()) return NULL; const Instances& children = *part->getChildren(); for (size_t i = 0; i < children.size(); ++i) { child = findFirstAttachmentByNameRecursive(children[i].get(), findName); if (child != NULL) return child; } return NULL; } } // namespace