mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
3535 lines
115 KiB
C++
3535 lines
115 KiB
C++
/* Copyright 2003-2007 ROBLOX Corporation, All Rights Reserved */
|
|
#include "stdafx.h"
|
|
|
|
#include "Humanoid/Humanoid.h"
|
|
#include "Humanoid/HumanoidState.h"
|
|
#include "Humanoid/Jumping.h"
|
|
|
|
#include "V8DataModel/GameBasicSettings.h"
|
|
#include "V8DataModel/BasicPartInstance.h"
|
|
#include "V8DataModel/PartInstance.h"
|
|
#include "V8DataModel/ModelInstance.h"
|
|
#include "V8DataModel/DataModel.h"
|
|
#include "V8DataModel/Workspace.h"
|
|
#include "V8DataModel/ForceField.h"
|
|
#include "V8DataModel/Backpack.h"
|
|
#include "V8DataModel/JointInstance.h"
|
|
#include "V8DataModel/Camera.h"
|
|
#include "V8DataModel/Filters.h"
|
|
#include "V8DataModel/Teams.h"
|
|
#include "V8DataModel/DataModelMesh.h"
|
|
#include "V8DataModel/Decal.h" // TODOFACE
|
|
#include "V8DataModel/Value.h"
|
|
#include "V8DataModel/Animator.h"
|
|
#include "v8datamodel/Attachment.h"
|
|
#include "V8World/Contact.h"
|
|
#include "V8World/World.h"
|
|
#include "V8World/Primitive.h"
|
|
#include "V8World/Assembly.h"
|
|
#include "V8World/Joint.h"
|
|
#include "AppDraw/DrawAdorn.h"
|
|
#include "GfxBase/Adorn.h"
|
|
#include "GfxBase/AdornSurface.h"
|
|
#include "Util/Rect.h"
|
|
#include "Util/Color.h"
|
|
#include "Util/Region2.h"
|
|
#include "Util/SoundChannel.h"
|
|
#include "Util/Analytics.h"
|
|
#include "V8Kernel/Constants.h"
|
|
#include "V8Kernel/Body.h"
|
|
#include "Gui/ProfanityFilter.h"
|
|
#include "rbx/make_shared.h"
|
|
#include "V8DataModel/UserInputService.h"
|
|
#include "V8Kernel/Kernel.h"
|
|
|
|
#include "Network/Players.h"
|
|
|
|
#include "rbx/Profiler.h"
|
|
|
|
#include "Util/PathInterpolatedCFrame.h"
|
|
|
|
LOGGROUP(HumanoidFloorProcess)
|
|
LOGGROUP(UserInputProfile)
|
|
|
|
LOGGROUP(DistanceMetricP1)
|
|
|
|
DYNAMIC_FASTINTVARIABLE(HumanoidFloorTeleportWeightValue, 50);
|
|
DYNAMIC_FASTINTVARIABLE(HumanoidFloorManualFrictionVelocityMultValue, 100);
|
|
FASTFLAGVARIABLE(PhysicsSkipNonRealTimeHumanoidForceCalc, false)
|
|
|
|
DYNAMIC_FASTFLAG(UseR15Character)
|
|
|
|
DYNAMIC_FASTFLAGVARIABLE(HumanoidCookieRecursive, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(ReplicateLuaMoveDirection, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(NamesOccludedAsDefault, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(FixedSitFirstPersonMove, true)
|
|
DYNAMIC_FASTFLAGVARIABLE(HumanoidCheckForNegatives, true)
|
|
|
|
DYNAMIC_FASTFLAGVARIABLE(EnableMotionAnalytics, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(Enable2edHumanoidDistanceLogging, false)
|
|
DYNAMIC_FASTINTVARIABLE(MotionDiscontinuityThreshold, 1) /* in tenths of a second */
|
|
|
|
DYNAMIC_FASTFLAGVARIABLE(RotateFirstPersonInVR, true)
|
|
FASTFLAGVARIABLE(HumanoidRenderBillboard, false)
|
|
FASTFLAGVARIABLE(HumanoidRenderBillboardVR, true)
|
|
|
|
namespace RBX {
|
|
|
|
// TODO: Create template version of normalIdToMatrix3 to avoid the switch statement
|
|
static CoordinateFrame rightShoulderP( normalIdToMatrix3(NORM_X), Vector3(1, 0.5, 0));
|
|
static CoordinateFrame leftShoulderP( normalIdToMatrix3(NORM_X_NEG), Vector3(-1, 0.5, 0));
|
|
static CoordinateFrame rightHipP( normalIdToMatrix3(NORM_X), Vector3(1, -1, 0));
|
|
static CoordinateFrame leftHipP( normalIdToMatrix3(NORM_X_NEG), Vector3(-1, -1, 0));
|
|
static CoordinateFrame neckP( normalIdToMatrix3(NORM_Y), Vector3(0, 1, 0));
|
|
|
|
static CoordinateFrame rightArmP( normalIdToMatrix3(NORM_X), Vector3(-0.5, 0.5, 0));
|
|
static CoordinateFrame leftArmP( normalIdToMatrix3(NORM_X_NEG), Vector3(0.5, 0.5, 0));
|
|
static CoordinateFrame rightLegP( normalIdToMatrix3(NORM_X), Vector3(0.5, 1, 0));
|
|
static CoordinateFrame leftLegP( normalIdToMatrix3(NORM_X_NEG), Vector3(-0.5, 1, 0));
|
|
static CoordinateFrame headP( normalIdToMatrix3(NORM_Y), Vector3(0, -0.5, 0));
|
|
|
|
static CoordinateFrame headTorsoOffset( neckP * headP.inverse()); // used to calculate camera target
|
|
|
|
static float defaultDisplayDistance(100.0f);
|
|
|
|
const char* const sHumanoid = "Humanoid";
|
|
const float epsilonDisplacement = 0.000001f;
|
|
const double epsilonDisplacementSqr = epsilonDisplacement * epsilonDisplacement;
|
|
|
|
REFLECTION_BEGIN();
|
|
// UI
|
|
static const Reflection::RefPropDescriptor<Humanoid, PartInstance> propTorso("Torso", category_Data, &Humanoid::getTorsoDangerous, &Humanoid::setTorso, Reflection::PropertyDescriptor::UI);
|
|
static const Reflection::RefPropDescriptor<Humanoid, PartInstance> propLeftLeg("LeftLeg", category_Data, &Humanoid::getLeftLegDangerous, &Humanoid::setLeftLeg, Reflection::PropertyDescriptor::UI);
|
|
static const Reflection::RefPropDescriptor<Humanoid, PartInstance> propRightLeg("RightLeg", category_Data, &Humanoid::getRightLegDangerous, &Humanoid::setRightLeg, Reflection::PropertyDescriptor::UI);
|
|
|
|
|
|
static const Reflection::PropDescriptor<Humanoid, float> propHealthUi("Health", "Game", &Humanoid::getHealth, &Humanoid::setHealthUi, Reflection::PropertyDescriptor::UI);
|
|
static const Reflection::PropDescriptor<Humanoid, float> propHealth("Health_XML", "Game", &Humanoid::getHealth, &Humanoid::setHealth, Reflection::PropertyDescriptor::STREAMING);
|
|
static const Reflection::PropDescriptor<Humanoid, float> propMaxHealth("MaxHealth", "Game", &Humanoid::getMaxHealth, &Humanoid::setMaxHealth);
|
|
static const Reflection::PropDescriptor<Humanoid, float> propMaxHealthDep("maxHealth", "Game", &Humanoid::getMaxHealth, &Humanoid::setMaxHealth, Reflection::PropertyDescriptor::Attributes::deprecated(propMaxHealth));
|
|
static const Reflection::PropDescriptor<Humanoid, float> propWalkSpeed("WalkSpeed", "Game", &Humanoid::getWalkSpeed, &Humanoid::setWalkSpeed);
|
|
|
|
static const Reflection::PropDescriptor<Humanoid, float> propJumpPower("JumpPower", "Game", &Humanoid::getJumpPower, &Humanoid::setJumpPower);
|
|
static const Reflection::PropDescriptor<Humanoid, float> propMaxSlopeAngle("MaxSlopeAngle", "Game", &Humanoid::getMaxSlopeAngle, &Humanoid::setMaxSlopeAngle);
|
|
static const Reflection::PropDescriptor<Humanoid, float> propHipHeight("HipHeight", "Game", &Humanoid::getHipHeight, &Humanoid::setHipHeight);
|
|
|
|
// SCRIPTING
|
|
static Reflection::BoundFuncDesc<Humanoid, void(Vector3, bool)> moveFunction(&Humanoid::move, "Move", "moveDirection","relativeToCamera", false, Security::None);
|
|
|
|
static const Reflection::RefPropDescriptor<Humanoid, PartInstance> propSeatPart("SeatPart", "Control", &Humanoid::getSeatPart, NULL, Reflection::PropertyDescriptor::SCRIPTING);
|
|
static const Reflection::PropDescriptor<Humanoid, Vector3> propLuaMoveDirection("MoveDirection", "Control", &Humanoid::getLuaMoveDirection, NULL, Reflection::PropertyDescriptor::SCRIPTING);
|
|
static const Reflection::RefPropDescriptor<Humanoid, PartInstance> propWalkToPart("WalkToPart", "Control", &Humanoid::getWalkToPart, &Humanoid::setWalkToPart, Reflection::PropertyDescriptor::SCRIPTING);
|
|
static const Reflection::PropDescriptor<Humanoid, Vector3> propWalkToPoint("WalkToPoint", "Control", &Humanoid::getWalkToPoint, &Humanoid::setWalkToPoint, Reflection::PropertyDescriptor::SCRIPTING);
|
|
static const Reflection::PropDescriptor<Humanoid, Vector3> propTargetPoint("TargetPoint", "Control", &Humanoid::getTargetPoint, &Humanoid::setTargetPointLocal, Reflection::PropertyDescriptor::SCRIPTING);
|
|
static const Reflection::PropDescriptor<Humanoid, bool> propJump("Jump", "Control", &Humanoid::getJump, &Humanoid::setJump, Reflection::PropertyDescriptor::UI);
|
|
static const Reflection::PropDescriptor<Humanoid, bool> propJumpRep("JumpReplicate", "Control", &Humanoid::getJump, &Humanoid::setJump, Reflection::PropertyDescriptor::REPLICATE_ONLY);
|
|
static const Reflection::PropDescriptor<Humanoid, bool> propSit("Sit", "Control", &Humanoid::getSit, &Humanoid::setSit, Reflection::PropertyDescriptor::SCRIPTING);
|
|
static const Reflection::PropDescriptor<Humanoid, bool> propPlatformStanding("PlatformStand", "Control", &Humanoid::getPlatformStanding, &Humanoid::setPlatformStanding, Reflection::PropertyDescriptor::SCRIPTING);
|
|
static const Reflection::PropDescriptor<Humanoid, bool> propAutoRotate("AutoRotate", "Control", &Humanoid::getAutoRotate, &Humanoid::setAutoRotate, Reflection::PropertyDescriptor::SCRIPTING);
|
|
static const Reflection::PropDescriptor<Humanoid, bool> propAutoJumpEnabled("AutoJumpEnabled", "Control", &Humanoid::getAutoJumpEnabled, &Humanoid::setAutoJumpEnabled, Reflection::PropertyDescriptor::SCRIPTING);
|
|
|
|
// REPLICATE_ONLY
|
|
|
|
static const Reflection::PropDescriptor<Humanoid, Vector3> propWalkDirection("WalkDirection", "Control", &Humanoid::getWalkDirection, &Humanoid::setWalkDirection, Reflection::PropertyDescriptor::REPLICATE_ONLY);
|
|
static const Reflection::PropDescriptor<Humanoid, Vector3> propLuaMoveDirectionInternal("MoveDirectionInternal", "Control", &Humanoid::getLuaMoveDirection, &Humanoid::setLuaMoveDirection, Reflection::PropertyDescriptor::REPLICATE_ONLY);
|
|
static const Reflection::PropDescriptor<Humanoid, float> propWalkAngleError("WalkAngleError", "Control", &Humanoid::getWalkAngleError, &Humanoid::setWalkAngleError, Reflection::PropertyDescriptor::REPLICATE_ONLY);
|
|
static const Reflection::PropDescriptor<Humanoid, bool> propStrafe("Strafe", "Control", &Humanoid::getStrafe, &Humanoid::setStrafe, Reflection::PropertyDescriptor::REPLICATE_ONLY);
|
|
|
|
static Reflection::EnumPropDescriptor<Humanoid, Humanoid::HumanoidRigType> propRigType("RigType", category_Data, &Humanoid::getRigType, &Humanoid::setRigType, Reflection::PropertyDescriptor::SCRIPTING);
|
|
static const Reflection::PropDescriptor<Humanoid, float> propCameraMinDistance("CameraMinDistance", category_Data, &Humanoid::getMinDistance, &Humanoid::setMinDistance, Reflection::PropertyDescriptor::REPLICATE_ONLY);
|
|
static const Reflection::PropDescriptor<Humanoid, float> propCameraMaxDistance("CameraMaxDistance", category_Data, &Humanoid::getMaxDistance, &Humanoid::setMaxDistance, Reflection::PropertyDescriptor::REPLICATE_ONLY);
|
|
static const Reflection::PropDescriptor<Humanoid, Vector3> propCameraOffset("CameraOffset", category_Data, &Humanoid::getCamearaOffset, &Humanoid::setCameraOffset, Reflection::PropertyDescriptor::SCRIPTING);
|
|
static Reflection::EnumPropDescriptor<Humanoid, RBX::Camera::CameraMode> prop_CameraMode("CameraMode", category_Data, &Humanoid::getCameraMode, &Humanoid::setCameraMode, Reflection::PropertyDescriptor::REPLICATE_ONLY);
|
|
|
|
static Reflection::BoundFuncDesc<Humanoid, void(float)> func_TakeDamage(&Humanoid::takeDamage, "TakeDamage", "amount", Security::None);
|
|
static Reflection::BoundFuncDesc<Humanoid, void(float)> dep_TakeDamage(&Humanoid::takeDamage, "takeDamage", "amount", Security::None, Reflection::Descriptor::Attributes::deprecated(func_TakeDamage));
|
|
static Reflection::BoundFuncDesc<Humanoid, void(bool)> func_SetClickToWalkEnabled(&Humanoid::setClickToWalkEnabled, "SetClickToWalkEnabled","enabled", Security::RobloxScript);
|
|
static Reflection::BoundFuncDesc<Humanoid, void(Vector3, shared_ptr<Instance>)> func_MoveTo(&Humanoid::moveTo2, "MoveTo", "location", "part", shared_ptr<Instance>(), Security::None);
|
|
static Reflection::EventDesc<Humanoid, void(bool)> event_MoveToFinished(&Humanoid::moveToFinishedSignal, "MoveToFinished", "reached");
|
|
|
|
// These now fire client and server side, to allow for local animation
|
|
static Reflection::EventDesc<Humanoid, void()> event_Died(&Humanoid::diedSignal, "Died");
|
|
static Reflection::EventDesc<Humanoid, void(float)> event_Running(&Humanoid::runningSignal, "Running", "speed");
|
|
static Reflection::EventDesc<Humanoid, void(float)> event_Climbing(&Humanoid::climbingSignal, "Climbing", "speed");
|
|
static Reflection::EventDesc<Humanoid, void(bool)> event_Jumping(&Humanoid::jumpingSignal, "Jumping", "active");
|
|
static Reflection::EventDesc<Humanoid, void(bool)> event_FreeFalling(&Humanoid::freeFallingSignal, "FreeFalling", "active");
|
|
static Reflection::EventDesc<Humanoid, void(bool)> event_GettingUp(&Humanoid::gettingUpSignal, "GettingUp", "active");
|
|
static Reflection::EventDesc<Humanoid, void(bool)> event_Strafing(&Humanoid::strafingSignal, "Strafing", "active");
|
|
static Reflection::EventDesc<Humanoid, void(bool)> event_FallingDown(&Humanoid::fallingDownSignal, "FallingDown", "active");
|
|
static Reflection::EventDesc<Humanoid, void(bool)> event_Ragdoll(&Humanoid::ragdollSignal, "Ragdoll", "active");
|
|
static Reflection::EventDesc<Humanoid, void(bool, shared_ptr<Instance>)> event_Seated(&Humanoid::seatedSignal, "Seated", "active", "currentSeatPart");
|
|
static Reflection::EventDesc<Humanoid, void(bool)> event_PlatformStanding(&Humanoid::platformStandingSignal, "PlatformStanding", "active");
|
|
static Reflection::EventDesc<Humanoid, void(float)> event_Swimming(&Humanoid::swimmingSignal, "Swimming", "speed");
|
|
static Reflection::EventDesc<Humanoid, void(HUMAN::StateType, HUMAN::StateType)> event_StateChanged(&Humanoid::stateChangedSignal, "StateChanged", "old", "new");
|
|
static Reflection::BoundFuncDesc<Humanoid, HUMAN::StateType()> func_GetState(&Humanoid::getCurrentStateType, "GetState", Security::None);
|
|
static Reflection::BoundFuncDesc<Humanoid, void(HUMAN::StateType)> func_ChangeState(&Humanoid::changeState, "ChangeState", "state", RBX::HUMAN::xx, Security::None);
|
|
|
|
static Reflection::EventDesc<Humanoid, void(float)> event_HealthChanged(&Humanoid::healthChangedSignal, "HealthChanged", "health");
|
|
|
|
static Reflection::BoundFuncDesc<Humanoid, bool(Humanoid::Status)> func_AddStatus(&Humanoid::addStatus, "AddStatus", "status", Humanoid::POISON_STATUS, Security::None, Reflection::Descriptor::Attributes::deprecated());
|
|
static Reflection::BoundFuncDesc<Humanoid, bool(Humanoid::Status)> func_RemoveStatus(&Humanoid::removeStatus, "RemoveStatus", "status", Humanoid::POISON_STATUS, Security::None, Reflection::Descriptor::Attributes::deprecated());
|
|
static Reflection::BoundFuncDesc<Humanoid, bool(Humanoid::Status)> func_HasStatus(&Humanoid::hasStatus, "HasStatus", "status", Humanoid::POISON_STATUS, Security::None, Reflection::Descriptor::Attributes::deprecated());
|
|
|
|
static Reflection::BoundFuncDesc<Humanoid, bool(std::string)> func_AddCustomStatus(&Humanoid::addCustomStatus, "AddCustomStatus", "status", Security::None, Reflection::Descriptor::Attributes::deprecated());
|
|
static Reflection::BoundFuncDesc<Humanoid, bool(std::string)> func_RemoveCustomStatus(&Humanoid::removeCustomStatus, "RemoveCustomStatus", "status", Security::None, Reflection::Descriptor::Attributes::deprecated());
|
|
static Reflection::BoundFuncDesc<Humanoid, bool(std::string)> func_HasCustomStatus(&Humanoid::hasCustomStatus, "HasCustomStatus", "status", Security::None, Reflection::Descriptor::Attributes::deprecated());
|
|
|
|
static Reflection::BoundFuncDesc<Humanoid, void(shared_ptr<Instance>)> func_equipTool(&Humanoid::equipToolInstance, "EquipTool", "tool", Security::None);
|
|
static Reflection::BoundFuncDesc<Humanoid, void()> func_unequipTools(&Humanoid::unequipTools, "UnequipTools", Security::None);
|
|
|
|
static Reflection::BoundFuncDesc<Humanoid, shared_ptr<const Reflection::ValueArray>()> func_getStatuses(&Humanoid::getStatuses, "GetStatuses", Security::None, Reflection::Descriptor::Attributes::deprecated());
|
|
|
|
static Reflection::EventDesc<Humanoid, void(Humanoid::Status)> event_StatusAdded(&Humanoid::statusAddedSignal, "StatusAdded", "status", Reflection::Descriptor::Attributes::deprecated());
|
|
static Reflection::EventDesc<Humanoid, void(Humanoid::Status)> event_StatusRemoved(&Humanoid::statusRemovedSignal, "StatusRemoved", "status", Reflection::Descriptor::Attributes::deprecated());
|
|
|
|
static Reflection::EventDesc<Humanoid, void(std::string)> event_CustomStatusAdded(&Humanoid::customStatusAddedSignal, "CustomStatusAdded", "status", Reflection::Descriptor::Attributes::deprecated());
|
|
static Reflection::EventDesc<Humanoid, void(std::string)> event_CustomStatusRemoved(&Humanoid::customStatusRemovedSignal, "CustomStatusRemoved", "status", Reflection::Descriptor::Attributes::deprecated());
|
|
|
|
static Reflection::RemoteEventDesc<Humanoid, void(shared_ptr<Instance>)> desc_serverEquipTool(&Humanoid::serverEquipToolSignal, "ServerEquipTool", "tool", Security::Roblox, Reflection::RemoteEventCommon::REPLICATE_ONLY, Reflection::RemoteEventCommon::CLIENT_SERVER);
|
|
|
|
static Reflection::EnumPropDescriptor<Humanoid, Humanoid::NameOcclusion> prop_NameOcclusion("NameOcclusion", category_Data, &Humanoid::getNameOcclusion, &Humanoid::setNameOcclusion);
|
|
static Reflection::EnumPropDescriptor<Humanoid, Humanoid::HumanoidDisplayDistanceType> prop_DisplayDistanceType("DisplayDistanceType", category_Data, &Humanoid::getDisplayDistanceType, &Humanoid::setDisplayDistanceType);
|
|
static const Reflection::PropDescriptor<Humanoid, float> propNameDisplayDistance("NameDisplayDistance", category_Data, &Humanoid::getNameDisplayDistance, &Humanoid::setNameDisplayDistance);
|
|
static const Reflection::PropDescriptor<Humanoid, float> propHealthDisplayDistance("HealthDisplayDistance", category_Data, &Humanoid::getHealthDisplayDistance, &Humanoid::setHealthDisplayDistance);
|
|
|
|
static Reflection::BoundFuncDesc<Humanoid, bool(HUMAN::StateType)> desc_getStateTransitionEnabled(&Humanoid::getStateTransitionEnabled, "GetStateEnabled","state", Security::None);
|
|
static Reflection::BoundFuncDesc<Humanoid, void(HUMAN::StateType, bool)> desc_setStateTransitionEnabled(&Humanoid::setStateTransitionEnabled, "SetStateEnabled","state", "enabled", Security::None);
|
|
static Reflection::EventDesc<Humanoid, void(HUMAN::StateType, bool)> event_StateEnabledChanged(&Humanoid::stateEnabledChangedSignal, "StateEnabledChanged", "state", "isEnabled");
|
|
|
|
//keep these in sync with the Animator object.
|
|
// this is proxy interface to the Animator that is contained in Humanoid.
|
|
static Reflection::BoundFuncDesc<Humanoid, shared_ptr<Instance>(shared_ptr<Instance>)> desc_LoadAnimation(&Humanoid::loadAnimation, "LoadAnimation","animation", Security::None);
|
|
static Reflection::BoundFuncDesc<Humanoid, shared_ptr<Instance>(shared_ptr<Instance>)> dep_loadAnimation(&Humanoid::loadAnimation, "loadAnimation","animation", Security::None, Reflection::Descriptor::Attributes::deprecated(desc_LoadAnimation));
|
|
static Reflection::BoundFuncDesc<Humanoid, shared_ptr<const Reflection::ValueArray>()> desc_GetPlayingAnimationTracks(&Humanoid::getPlayingAnimationTracks, "GetPlayingAnimationTracks", Security::None);
|
|
|
|
static Reflection::EventDesc<Humanoid, void(shared_ptr<Instance>)> event_AnimationPlayed(&Humanoid::animationPlayedSignal, "AnimationPlayed", "animationTrack");
|
|
REFLECTION_END();
|
|
|
|
|
|
namespace Reflection {
|
|
template<>
|
|
EnumDesc<HUMAN::StateType>::EnumDesc()
|
|
:EnumDescriptor("HumanoidStateType")
|
|
{
|
|
addPair(RBX::HUMAN::FALLING_DWN, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::FALLING_DWN));
|
|
addPair(RBX::HUMAN::RUNNING, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::RUNNING));
|
|
addPair(RBX::HUMAN::RUNNING_NO_PHYS, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::RUNNING_NO_PHYS));
|
|
addPair(RBX::HUMAN::CLIMBING, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::CLIMBING));
|
|
addPair(RBX::HUMAN::STRAFING_NO_PHYS, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::STRAFING_NO_PHYS));
|
|
addPair(RBX::HUMAN::RAGDOLL, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::RAGDOLL));
|
|
addPair(RBX::HUMAN::GETTING_UP, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::GETTING_UP));
|
|
addPair(RBX::HUMAN::JUMPING, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::JUMPING));
|
|
addPair(RBX::HUMAN::LANDED, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::LANDED));
|
|
addPair(RBX::HUMAN::FLYING, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::FLYING));
|
|
addPair(RBX::HUMAN::FREE_FALL, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::FREE_FALL));
|
|
addPair(RBX::HUMAN::SEATED, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::SEATED));
|
|
addPair(RBX::HUMAN::PLATFORM_STANDING, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::PLATFORM_STANDING));
|
|
addPair(RBX::HUMAN::DEAD, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::DEAD));
|
|
addPair(RBX::HUMAN::SWIMMING, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::SWIMMING));
|
|
addPair(RBX::HUMAN::PHYSICS, RBX::HUMAN::HumanoidState::getStateNameByType(RBX::HUMAN::PHYSICS));
|
|
addPair(RBX::HUMAN::xx, "None");
|
|
}
|
|
template<>
|
|
HUMAN::StateType& Variant::convert<HUMAN::StateType>(void)
|
|
{
|
|
return genericConvert<HUMAN::StateType>();
|
|
}
|
|
|
|
}
|
|
|
|
bool Humanoid::isStateInString(const std::string& text, const HUMAN::StateType &compare, HUMAN::StateType& value)
|
|
{
|
|
if(text == RBX::HUMAN::HumanoidState::getStateNameByType(compare)) {
|
|
value = compare;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template<>
|
|
bool RBX::StringConverter<HUMAN::StateType>::convertToValue(const std::string& text, HUMAN::StateType& value)
|
|
{
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::FALLING_DWN, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::RUNNING, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::RUNNING_NO_PHYS, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::CLIMBING, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::STRAFING_NO_PHYS, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::RAGDOLL, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::GETTING_UP, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::JUMPING, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::LANDED, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::FLYING, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::FREE_FALL, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::SEATED, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::PLATFORM_STANDING, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::DEAD, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::SWIMMING, value)) return true;
|
|
if (Humanoid::isStateInString(text, RBX::HUMAN::PHYSICS, value)) return true;
|
|
if(text.find("None")){
|
|
value = RBX::HUMAN::xx;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static const std::string kAppendageNames[] =
|
|
{
|
|
"HumanoidRootPart", "Head", "Right Arm", "Left Arm", "Right Leg", "Left Leg", "Torso"
|
|
};
|
|
|
|
static const std::string kAppendageNamesR15[] =
|
|
{
|
|
"HumanoidRootPart", "Head", "RightArm", "LeftArm", "RightLeg", "LeftLeg", "UpperTorso"
|
|
};
|
|
|
|
static const std::string& getAppendageString(size_t appendage, bool R15)
|
|
{
|
|
if (appendage < sizeof(kAppendageNames) / sizeof(kAppendageNames[0]))
|
|
{
|
|
if (R15)
|
|
return kAppendageNamesR15[appendage];
|
|
else
|
|
return kAppendageNames[appendage];
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(!"Unknown appendage type");
|
|
|
|
static std::string dummy;
|
|
return dummy;
|
|
}
|
|
}
|
|
|
|
Humanoid::Humanoid()
|
|
:world(NULL)
|
|
, torsoArrived(false)
|
|
, localSimulating(false)
|
|
, ownedByLocalPlayer(false)
|
|
, walkAngleError(0.0f)
|
|
, walkSpeed(16.0f)
|
|
, walkSpeedShadow(16.0f)
|
|
, percentWalkSpeed(1.0f)
|
|
, walkSpeedErrors(0)
|
|
, jump(false)
|
|
, autoJump(false)
|
|
, sit(false)
|
|
, jumpPower(HUMAN::Jumping::kJumpVelocityGrid())
|
|
, maxSlopeAngle(89.0)
|
|
, hipHeight(0.0)
|
|
, touchedHard(false)
|
|
, activatePhysics(false)
|
|
, ragdollCriteria(34)
|
|
, numContacts(0)
|
|
, platformStanding(false)
|
|
, strafe(false)
|
|
, health(100.0f)
|
|
, maxHealth(100.0f)
|
|
, nameDisplayDistance(defaultDisplayDistance)
|
|
, healthDisplayDistance(defaultDisplayDistance)
|
|
, hadNeck(false)
|
|
, hadHealth(false)
|
|
, isWalking(false)
|
|
, walkTimer(0)
|
|
, displayText("")
|
|
, filterState(ContentFilter::Waiting)
|
|
, clickToWalkEnabled(true)
|
|
, typing(false)
|
|
, nameOcclusion(Humanoid::NAME_OCCLUSION_NONE)
|
|
, displayDistanceType(Humanoid::HUMANOID_DISPLAY_DISTANCE_TYPE_VIEWER)
|
|
, autorotate(true)
|
|
, luaMoveDirection(Vector3::zero())
|
|
, isWalkingFromStudioTouchEmulation(false)
|
|
, lastFloorNormal(Vector3::unitY())
|
|
, lastFloorPart()
|
|
, rootFloorMechPart()
|
|
, lastFilterPhase(0)
|
|
, autoJumpEnabled(true)
|
|
, previousState(HUMAN::FREE_FALL)
|
|
, rigType(HUMANOID_RIG_TYPE_R6)
|
|
, pos0()
|
|
, pos1()
|
|
, pos2()
|
|
{
|
|
setName("Humanoid");
|
|
|
|
FASTLOG1(FLog::ISteppedLifetime, "Humanoid created - %p", this);
|
|
for (int i = HUMAN::FALLING_DWN; i < HUMAN::NUM_STATE_TYPES; i++)
|
|
{
|
|
stateTransitionEnabled[i] = true;
|
|
}
|
|
|
|
if (DFFlag::NamesOccludedAsDefault)
|
|
{
|
|
nameOcclusion = Humanoid::NAME_OCCLUSION_ALL;
|
|
}
|
|
}
|
|
|
|
Humanoid::~Humanoid()
|
|
{
|
|
RBXASSERT(!currentState.get());
|
|
RBXASSERT(world==NULL);
|
|
FASTLOG1(FLog::ISteppedLifetime, "Humanoid destroyed - %p", this);
|
|
}
|
|
|
|
void collectStatus(shared_ptr<RBX::Instance> instance, Reflection::ValueArray* result)
|
|
{
|
|
//Try to push an enum first
|
|
if(const Reflection::EnumDescriptor::Item* item = Reflection::EnumDesc<Humanoid::Status>::singleton().lookup(instance->getName().c_str())){
|
|
Reflection::Variant value;
|
|
if(item->convertToValue(value)){
|
|
result->push_back(value);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//But if that doesn't work, push a string
|
|
result->push_back(instance->getName());
|
|
}
|
|
|
|
shared_ptr<const Reflection::ValueArray> Humanoid::getStatuses()
|
|
{
|
|
shared_ptr<Reflection::ValueArray> result(rbx::make_shared<Reflection::ValueArray>());
|
|
if(StatusInstance* status = getStatusFast()){
|
|
status->visitChildren(boost::bind(&collectStatus, _1, result.get()));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool Humanoid::hasStatus(Status newStatus)
|
|
{
|
|
return hasCustomStatus(Reflection::EnumDesc<Humanoid::Status>::singleton().convertToString(newStatus));
|
|
}
|
|
|
|
bool Humanoid::hasCustomStatus(std::string name)
|
|
{
|
|
if(StatusInstance* status = getStatusFast()){
|
|
if(status->findFirstChildByName(name)){
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
bool Humanoid::addCustomStatus(std::string name)
|
|
{
|
|
if(StatusInstance* status = getStatusFast()){
|
|
if(status->findFirstChildByName(name)){
|
|
//Already exists
|
|
return false;
|
|
}
|
|
|
|
shared_ptr<BoolValue> boolValue = Creatable<Instance>::create<BoolValue>();
|
|
boolValue->setName(name);
|
|
boolValue->setValue(true); //don't care
|
|
boolValue->setParent(getStatusFast());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Humanoid::addStatus(Status newStatus)
|
|
{
|
|
return addCustomStatus(Reflection::EnumDesc<Humanoid::Status>::singleton().convertToString(newStatus));
|
|
}
|
|
|
|
bool Humanoid::removeCustomStatus(std::string name)
|
|
{
|
|
if(StatusInstance* status = getStatusFast()){
|
|
if(Instance* instance = status->findFirstChildByName(name))
|
|
{
|
|
instance->setParent(NULL);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Humanoid::removeStatus(Status newStatus)
|
|
{
|
|
return removeCustomStatus(Reflection::EnumDesc<Humanoid::Status>::singleton().convertToString(newStatus));
|
|
}
|
|
|
|
void Humanoid::setNameOcclusion(NameOcclusion value)
|
|
{
|
|
if(value != nameOcclusion)
|
|
{
|
|
nameOcclusion = value;
|
|
raisePropertyChanged(prop_NameOcclusion);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setDisplayDistanceType(HumanoidDisplayDistanceType value)
|
|
{
|
|
if (value != displayDistanceType)
|
|
{
|
|
displayDistanceType = value;
|
|
raisePropertyChanged(prop_DisplayDistanceType);
|
|
}
|
|
}
|
|
|
|
void Humanoid::onDescendantAdded(Instance* instance)
|
|
{
|
|
if(instance->getParent() == getStatusFast()){
|
|
Status status;
|
|
if(Reflection::EnumDesc<Humanoid::Status>::singleton().convertToValue(instance->getName().c_str(), status)){
|
|
statusAddedSignal(status);
|
|
}
|
|
customStatusAddedSignal(instance->getName());
|
|
}
|
|
Super::onDescendantAdded(instance);
|
|
}
|
|
void Humanoid::onDescendantRemoving(const shared_ptr<Instance>& instance)
|
|
{
|
|
if(instance->getParent() == getStatusFast()){
|
|
Status status;
|
|
if(Reflection::EnumDesc<Humanoid::Status>::singleton().convertToValue(instance->getName().c_str(), status)){
|
|
statusRemovedSignal(status);
|
|
}
|
|
customStatusRemovedSignal(instance->getName());
|
|
}
|
|
Super::onDescendantRemoving(instance);
|
|
}
|
|
|
|
void Humanoid::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
|
|
{
|
|
if(newProvider)
|
|
{
|
|
humanoidEquipConnection = serverEquipToolSignal.connect(boost::bind(&Humanoid::equipToolInstance,this,_1));
|
|
|
|
if (DFFlag::UseR15Character && RBX::Network::Players::backendProcessing(this))
|
|
{
|
|
DataModel* dataModel = Instance::fastDynamicCast<DataModel>(newProvider);
|
|
if (dataModel)
|
|
{
|
|
RBX::Security::Impersonator impersonate(RBX::Security::Replicator_);
|
|
if (dataModel->getForceR15())
|
|
setRigType(Humanoid::HUMANOID_RIG_TYPE_R15);
|
|
else
|
|
setRigType(Humanoid::HUMANOID_RIG_TYPE_R6);
|
|
}
|
|
}
|
|
}
|
|
else if(oldProvider)
|
|
{
|
|
humanoidEquipConnection.disconnect();
|
|
}
|
|
|
|
Super::onServiceProvider(oldProvider, newProvider);
|
|
|
|
// IStepped hook
|
|
onServiceProviderIStepped(oldProvider, newProvider);
|
|
|
|
ServiceProvider::create< FilteredSelection<PartInstance> >(newProvider); // will need this later on during const access
|
|
|
|
|
|
}
|
|
|
|
|
|
bool Humanoid::getDead() const
|
|
{
|
|
if (this->world != NULL) {
|
|
RBXASSERT(currentState.get());
|
|
return (currentState->getStateType() == RBX::HUMAN::DEAD);
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void Humanoid::setWalkSpeed(float value)
|
|
{
|
|
if (DFFlag::HumanoidCheckForNegatives && value < 0.0f)
|
|
value = 0.0f;
|
|
|
|
if (value != walkSpeed)
|
|
{
|
|
walkSpeed = value;
|
|
walkSpeedShadow = value;
|
|
raisePropertyChanged(propWalkSpeed);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setPercentWalkSpeed(float value)
|
|
{
|
|
value = G3D::clamp(value,0.0f,1.0f);
|
|
|
|
if(value != percentWalkSpeed)
|
|
{
|
|
percentWalkSpeed = value;
|
|
}
|
|
}
|
|
|
|
void Humanoid::setJumpPower(float value)
|
|
{
|
|
value = G3D::clamp(value,0.0f,1000.0f);
|
|
|
|
if(value != jumpPower)
|
|
{
|
|
jumpPower = value;
|
|
raisePropertyChanged(propJumpPower);
|
|
}
|
|
}
|
|
|
|
|
|
void Humanoid::setMaxSlopeAngle(float value)
|
|
{
|
|
value = G3D::clamp(value,0.0f,89.9f);
|
|
|
|
if(value != maxSlopeAngle)
|
|
{
|
|
maxSlopeAngle = value;
|
|
raisePropertyChanged(propMaxSlopeAngle);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setHipHeight(float value)
|
|
{
|
|
if(value != hipHeight)
|
|
{
|
|
hipHeight = value;
|
|
raisePropertyChanged(propHipHeight);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setHealth(float value)
|
|
{
|
|
if (health != value)
|
|
{
|
|
health = value;
|
|
updateHadHealth();
|
|
raisePropertyChanged(propHealth);
|
|
healthChangedSignal(value);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setHealthUi(float value)
|
|
{
|
|
float legalValue = G3D::clamp(value, 0.0f, maxHealth);
|
|
|
|
if (health != legalValue)
|
|
{
|
|
raisePropertyChanged(propHealthUi);
|
|
setHealth(legalValue);
|
|
}
|
|
else if(legalValue != value)
|
|
{
|
|
raisePropertyChanged(propHealthUi);
|
|
}
|
|
}
|
|
|
|
|
|
void Humanoid::setMaxHealth(float value)
|
|
{
|
|
if (DFFlag::HumanoidCheckForNegatives && value < 0.0f)
|
|
value = 0.0f;
|
|
|
|
if (maxHealth != value)
|
|
{
|
|
maxHealth = value;
|
|
raisePropertyChanged(propMaxHealth);
|
|
healthChangedSignal(health);
|
|
}
|
|
}
|
|
|
|
|
|
void Humanoid::takeDamage(float value) // honors explosions, forceFields, etc.
|
|
{
|
|
RBX::PartInstance* tempTorso = getTorsoSlow();
|
|
if (tempTorso) {
|
|
if (!ForceField::partInForceField(tempTorso)) {
|
|
setHealth(getHealth() - value);
|
|
}
|
|
}
|
|
}
|
|
|
|
const Humanoid* Humanoid::getConstLocalHumanoidFromContext(const Instance* context)
|
|
{
|
|
if (const ModelInstance* character = Network::Players::findConstLocalCharacter(context)) {
|
|
return modelIsConstCharacter(character);
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
Humanoid* Humanoid::getLocalHumanoidFromContext(Instance* context)
|
|
{
|
|
if (ModelInstance* character = Network::Players::findLocalCharacter(context)) {
|
|
return modelIsCharacter(character);
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
PartInstance* Humanoid::getLocalHeadFromContext(Instance* context)
|
|
{
|
|
ModelInstance* character = Network::Players::findLocalCharacter(context);
|
|
return getHeadFromCharacter(character);
|
|
}
|
|
|
|
const PartInstance* Humanoid::getConstLocalHeadFromContext(const Instance* context)
|
|
{
|
|
const ModelInstance* character = Network::Players::findConstLocalCharacter(context);
|
|
return getConstHeadFromCharacter(character);
|
|
}
|
|
|
|
|
|
const PartInstance* Humanoid::getConstHeadFromCharacter(const ModelInstance* character)
|
|
{
|
|
return (character)
|
|
? Instance::fastDynamicCast<PartInstance>(character->findConstFirstChildByName("Head"))
|
|
: NULL;
|
|
}
|
|
|
|
PartInstance* Humanoid::getHeadFromCharacter(ModelInstance* character)
|
|
{
|
|
return const_cast<PartInstance*>(getConstHeadFromCharacter(character));
|
|
}
|
|
|
|
|
|
Weld* Humanoid::getGrip(Instance* character)
|
|
{
|
|
if (Humanoid* humanoid = modelIsCharacter(character))
|
|
{
|
|
if (PartInstance* right_arm = humanoid->getRightArmSlow())
|
|
{
|
|
return Instance::fastDynamicCast<Weld>(right_arm->findFirstChildByName("RightGrip"));
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
shared_ptr<JointInstance> newJoint(bool animated)
|
|
{
|
|
if (animated) {
|
|
shared_ptr<Motor> m = Creatable<Instance>::create<Motor6D>();
|
|
m->setMaxVelocity(0.1f);
|
|
return m;
|
|
}
|
|
else {
|
|
shared_ptr<Snap> s = Creatable<Instance>::create<Snap>();
|
|
return s;
|
|
}
|
|
}
|
|
|
|
void Humanoid::setRigType(HumanoidRigType type)
|
|
{
|
|
try {
|
|
RBX::Security::Context::current().requirePermission(RBX::Security::RobloxScript, "setRigType");
|
|
}
|
|
catch (RBX::base_exception& e)
|
|
{
|
|
raisePropertyChanged(propRigType);
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Insufficient permissions to setRigType");
|
|
throw e;
|
|
}
|
|
|
|
if (type != rigType)
|
|
{
|
|
rigType = type;
|
|
raisePropertyChanged(propRigType);
|
|
|
|
if (getParent())
|
|
{
|
|
// update all cache entries
|
|
for (int i = TORSO; i < APPENDAGE_COUNT; i++)
|
|
{
|
|
appendageCache[i] = Instance::fastSharedDynamicCast<PartInstance>(shared_from(getParent()->findFirstChildByName(getAppendageString(i, getUseR15()))));
|
|
updateSiblingPropertyListener(appendageCache[i]);
|
|
}
|
|
updateBaseInstance();
|
|
|
|
// register update to listen to sibling changes
|
|
characterChildAdded = getParent()->onDemandWrite()->childAddedSignal.connect(boost::bind(&Humanoid::onEvent_ChildModified, this, _1));
|
|
characterChildRemoved = getParent()->onDemandWrite()->childRemovedSignal.connect(boost::bind(&Humanoid::onEvent_ChildModified, this, _1));
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
static void createJoint(const std::string& jointName, Attachment& parentAttachment, Attachment& partForJointAttachment)
|
|
{
|
|
shared_ptr<Motor6D> newMotor = Creatable<Instance>::create<Motor6D>();
|
|
PartInstance* partForJoint = Instance::fastDynamicCast<PartInstance>(partForJointAttachment.getParent());
|
|
|
|
newMotor->setName(jointName);
|
|
|
|
newMotor->setPart0(Instance::fastDynamicCast<PartInstance>(parentAttachment.getParent()));
|
|
newMotor->setPart1(partForJoint);
|
|
|
|
newMotor->setC0(parentAttachment.getFrameInPart());
|
|
newMotor->setC1(partForJointAttachment.getFrameInPart());
|
|
|
|
newMotor->setParent(partForJoint);
|
|
}
|
|
|
|
void Humanoid::buildJointsFromAttachments(PartInstance* part, std::vector<PartInstance*>& characterParts)
|
|
{
|
|
if (part == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (shared_ptr<const Instances> children = part->getChildren2())
|
|
{
|
|
// first, loop thru all of the part's children to find attachments
|
|
Instances::const_iterator end =children->end();
|
|
for (Instances::const_iterator iter = children->begin(); iter != end; ++iter)
|
|
{
|
|
if(RBX::Attachment* attachment = Instance::fastDynamicCast<RBX::Attachment>((*iter).get()))
|
|
{
|
|
// only do joint build from "RigAttachments"
|
|
const std::string attachmentName = attachment->getName();
|
|
size_t findPos = attachmentName.find("RigAttachment");
|
|
if (findPos == std::string::npos)
|
|
{
|
|
continue;
|
|
}
|
|
// also don't make double joints (there is the same named
|
|
// rigattachment under two parts)
|
|
const std::string jointName = attachmentName.substr(0, findPos);
|
|
if (part->findConstFirstChildByName(jointName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// try to find other part with same rig attachment name
|
|
for (std::vector<PartInstance*>::const_iterator partIter = characterParts.begin(); partIter != characterParts.end(); ++partIter)
|
|
{
|
|
PartInstance* characterPart = *partIter;
|
|
if (characterPart != part)
|
|
{
|
|
if (Instance* instance = characterPart->findFirstChildByName(attachmentName))
|
|
{
|
|
if (Attachment* matchingAttachment = Instance::fastDynamicCast<Attachment>(instance))
|
|
{
|
|
createJoint(jointName, *attachment, *matchingAttachment);
|
|
buildJointsFromAttachments(characterPart, characterParts);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Server side
|
|
void Humanoid::buildJoints(RBX::DataModel* dm)
|
|
{
|
|
if(!status){
|
|
status = Creatable<Instance>::create<RBX::StatusInstance>();
|
|
Instance::propArchivable.setValue(status.get(), false);
|
|
status->setParent(this);
|
|
}
|
|
|
|
ModelInstance* character = getCharacterFromHumanoid(this);
|
|
RBXASSERT(character);
|
|
if (character)
|
|
{
|
|
if (dm && dm->getForceR15())
|
|
{
|
|
PartInstance* torso = getTorsoFast();
|
|
if (torso)
|
|
{
|
|
std::vector<PartInstance*> characterParts;
|
|
getParts(characterParts);
|
|
|
|
buildJointsFromAttachments(torso, characterParts);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PartInstance* head = getHeadSlow();
|
|
PartInstance* torso = getAppendageSlow(VISIBLE_TORSO);
|
|
PartInstance* rightArm = getRightArmSlow();
|
|
PartInstance* leftArm = getLeftArmSlow();
|
|
PartInstance* rightLeg = getRightLegSlow();
|
|
PartInstance* leftLeg = getLeftLegSlow();
|
|
|
|
RBXASSERT(head && torso && rightArm && leftArm && rightLeg && leftLeg);
|
|
|
|
if (torso)
|
|
{
|
|
PartInstance *pRoot = getTorsoSlow();
|
|
|
|
if (pRoot != NULL)
|
|
{
|
|
shared_ptr<JointInstance> rootJoint = newJoint(true);
|
|
rootJoint->setName("RootJoint");
|
|
rootJoint->setPart0(pRoot);
|
|
rootJoint->setPart1(torso);
|
|
|
|
CoordinateFrame torsoP( normalIdToMatrix3(NORM_Y), Vector3(0, 0, 0));
|
|
rootJoint->setC0(torsoP);
|
|
rootJoint->setC1(torsoP);
|
|
|
|
rootJoint->setParent(pRoot);
|
|
|
|
pRoot->getPartPrimitive()->setMassInertia(0.1f); // Should be small, but not zero. Physics doesn't like 0 weight parts
|
|
}
|
|
|
|
shared_ptr<JointInstance> rightShoulder = newJoint(true);
|
|
shared_ptr<JointInstance> leftShoulder = newJoint(true);
|
|
shared_ptr<JointInstance> rightHip = newJoint(true);
|
|
shared_ptr<JointInstance> leftHip = newJoint(true);
|
|
shared_ptr<JointInstance> neck = newJoint(true);
|
|
|
|
rightShoulder->setName("Right Shoulder");
|
|
leftShoulder->setName("Left Shoulder");
|
|
rightHip->setName("Right Hip");
|
|
leftHip->setName("Left Hip");
|
|
neck->setName("Neck");
|
|
|
|
rightShoulder->setPart0(torso);
|
|
leftShoulder->setPart0(torso);
|
|
rightHip->setPart0(torso);
|
|
leftHip->setPart0(torso);
|
|
neck->setPart0(torso);
|
|
|
|
rightShoulder->setPart1(rightArm);
|
|
leftShoulder->setPart1(leftArm);
|
|
rightHip->setPart1(rightLeg);
|
|
leftHip->setPart1(leftLeg);
|
|
neck->setPart1(head);
|
|
|
|
|
|
rightShoulder->setC0(rightShoulderP);
|
|
leftShoulder->setC0(leftShoulderP);
|
|
rightHip->setC0(rightHipP);
|
|
leftHip->setC0(leftHipP);
|
|
neck->setC0(neckP);
|
|
|
|
rightShoulder->setC1(rightArmP);
|
|
leftShoulder->setC1(leftArmP);
|
|
rightHip->setC1(rightLegP);
|
|
leftHip->setC1(leftLegP);
|
|
neck->setC1(headP);
|
|
|
|
rightShoulder->setParent(torso);
|
|
leftShoulder->setParent(torso);
|
|
rightHip->setParent(torso);
|
|
leftHip->setParent(torso);
|
|
neck->setParent(torso);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Humanoid::askSetParent(const Instance* instance) const {
|
|
// Humanoids must be children of a ModelInstance
|
|
// TODO: If we refactor Humanoind to BE a ModelInstance then this code can be removed
|
|
return Instance::fastDynamicCast<ModelInstance>(instance)!=NULL;
|
|
}
|
|
|
|
const Humanoid* Humanoid::constHumanoidFromBodyPart(const Instance* bodyPart)
|
|
{
|
|
return bodyPart ? modelIsConstCharacter(bodyPart->getParent()) : NULL;
|
|
}
|
|
|
|
const Humanoid* Humanoid::constHumanoidFromDescendant(const Instance* bodyPart)
|
|
{
|
|
if (bodyPart) {
|
|
const Instance *pParent = bodyPart->getParent();
|
|
if (pParent != NULL)
|
|
{
|
|
const Humanoid *pHumanoid = modelIsConstCharacter(pParent);
|
|
if (pHumanoid != NULL)
|
|
return pHumanoid;
|
|
else
|
|
return constHumanoidFromDescendant(pParent);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
Humanoid* Humanoid::humanoidFromBodyPart(Instance* bodyPart)
|
|
{
|
|
// return bodyPart ? modelIsCharacter(bodyPart->getParent()) : NULL;
|
|
return const_cast<Humanoid*>(constHumanoidFromBodyPart(bodyPart));
|
|
}
|
|
|
|
|
|
// abstract so as not to include Humanoid in general code
|
|
// for now - anything with a humanoid directly below it comes up yes
|
|
const Humanoid* Humanoid::modelIsConstCharacter(const Instance* testModel)
|
|
{
|
|
return ModelInstance::findConstFirstModifierOfType<Humanoid>(testModel);
|
|
}
|
|
|
|
Humanoid* Humanoid::modelIsCharacter(Instance* testModel)
|
|
{
|
|
return const_cast<Humanoid*>(modelIsConstCharacter(testModel));
|
|
}
|
|
|
|
|
|
|
|
const ModelInstance* Humanoid::getConstCharacterFromHumanoid(const Humanoid* humanoid)
|
|
{
|
|
if (humanoid) {
|
|
const Instance* parent = humanoid->getParent();
|
|
if (const ModelInstance* m = Instance::fastDynamicCast<ModelInstance>(parent)) {
|
|
return m;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
ModelInstance* Humanoid::getCharacterFromHumanoid(Humanoid* humanoid)
|
|
{
|
|
return const_cast<ModelInstance*>(getConstCharacterFromHumanoid(humanoid));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
//
|
|
// Controller stuff
|
|
|
|
|
|
bool Humanoid::hasWalkToPoint(Vector3& worldPosition) const
|
|
{
|
|
Network::Player* player = Network::Players::getPlayerFromCharacter(getCharacterFromHumanoid(const_cast<Humanoid*>(this))); // this should always be safe, right?
|
|
// only do click-to-walk in certain modes
|
|
if(player && (RBX::GameBasicSettings::singleton().inHybridMode() || RBX::GameBasicSettings::singleton().inMousepanMode()) )
|
|
return false;
|
|
|
|
if (walkToPart.get())
|
|
{
|
|
if (const Workspace* workspace = Workspace::findConstWorkspace(this))
|
|
{
|
|
if (workspace->contextInWorkspace(walkToPart.get()))
|
|
{
|
|
worldPosition = walkToPart->getCoordinateFrame().pointToWorldSpace(walkToPoint);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
worldPosition = walkToPoint;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
HUMAN::StateType Humanoid::getCurrentStateType()
|
|
{
|
|
if (currentState.get())
|
|
{
|
|
return currentState->getStateType();
|
|
}
|
|
return RBX::HUMAN::xx;
|
|
}
|
|
|
|
void Humanoid::setPreviousStateType(RBX::HUMAN::StateType newState)
|
|
{
|
|
if (newState != previousState)
|
|
{
|
|
previousState = newState;
|
|
}
|
|
}
|
|
|
|
// cds: This function is an exploit target because it can give several powers when abused.
|
|
void Humanoid::changeState(HUMAN::StateType state)
|
|
{
|
|
if (currentState.get())
|
|
{
|
|
currentState->setLuaState(state);
|
|
}
|
|
void (Humanoid::* thisFunction)(HUMAN::StateType) = &Humanoid::changeState;
|
|
checkRbxCaller<kCallCheckCodeOnly, callCheckSetApiFlag<kChangeStateApiOffset> >(
|
|
reinterpret_cast<void*>((void*&)(thisFunction)));
|
|
}
|
|
|
|
|
|
void Humanoid::equipToolInstance(shared_ptr<Instance> instance)
|
|
{
|
|
if(instance)
|
|
if(Tool* tool = Instance::fastDynamicCast<Tool>(instance.get()))
|
|
equipTool(tool);
|
|
}
|
|
|
|
void Humanoid::equipTool(RBX::Tool* tool)
|
|
{
|
|
if(tool)
|
|
{
|
|
if(ModelInstance* character = getCharacterFromHumanoid(this))
|
|
{
|
|
unequipTools();
|
|
|
|
if (RBX::Network::Players::backendProcessing(this))
|
|
{
|
|
// if also frontend, we are in solo play
|
|
if (Network::Players::frontendProcessing(this))
|
|
{
|
|
tool->setParent(character);
|
|
return;
|
|
}
|
|
|
|
if(ModelInstance* character = getCharacterFromHumanoid(this))
|
|
{
|
|
Workspace* workspace = ServiceProvider::find<Workspace>(this);
|
|
bool fromWorkspace = (tool->getParent() == workspace || Workspace::contextInWorkspace(tool->getParent()));
|
|
|
|
// server signals the client that this humanoid belongs to
|
|
Network::Player* player = Network::Players::getPlayerFromCharacter(character);
|
|
if(player && !fromWorkspace)
|
|
{
|
|
Reflection::EventArguments args(1);
|
|
args[0] = Instance::fastSharedDynamicCast<Instance>(shared_from(tool));
|
|
const RBX::SystemAddress addr = player->getRemoteAddressAsRbxAddress();
|
|
raiseEventInvocation(desc_serverEquipTool, args, &addr);
|
|
}
|
|
else
|
|
{
|
|
// if this is not a player character or the tool is currenting under workspace, do equip on server
|
|
tool->setParent(character);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// client can perform this only if the tool was not in workspace, otherwise let server do it
|
|
Workspace* workspace = ServiceProvider::find<Workspace>(this);
|
|
if (tool->getParent() != workspace && !Workspace::contextInWorkspace(tool->getParent()))
|
|
tool->setParent(character);
|
|
else
|
|
desc_serverEquipTool.replicateEvent(this, shared_from(tool));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Humanoid::unequipTools()
|
|
{
|
|
if(ModelInstance* character = getCharacterFromHumanoid(this))
|
|
{
|
|
if(character->getChildren())
|
|
{
|
|
if(Network::Player* player = Network::Players::getPlayerFromCharacter(character))
|
|
{
|
|
if(RBX::Backpack* backpack = player->findFirstChildOfType<Backpack>())
|
|
{
|
|
Instances::const_iterator end = character->getChildren()->end();
|
|
for (Instances::const_iterator iter = character->getChildren()->begin(); iter != end; ++iter)
|
|
{
|
|
if(RBX::Tool* tool = Instance::fastDynamicCast<RBX::Tool>((*iter).get()))
|
|
tool->setParent(backpack);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
StandardOut::singleton()->printf(MESSAGE_WARNING, "UnequipTools can only be called on a Humanoid with a corresponding Player object.");
|
|
}
|
|
}
|
|
}
|
|
|
|
Velocity Humanoid::calcDesiredWalkVelocity() const
|
|
{
|
|
Velocity answer;
|
|
Vector3 intendedMovementVector = walkDirection;
|
|
|
|
// if we aren't currently trying to move somewhere via user input, set to override movement
|
|
if (intendedMovementVector == Vector3::zero())
|
|
{
|
|
intendedMovementVector = luaMoveDirection;
|
|
if (luaMoveDirection != Vector3::zero() && !luaMoveDirection.isUnit())
|
|
{
|
|
RBXASSERT(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!allow3dWalkDirection()) {
|
|
intendedMovementVector.y = 0;
|
|
}
|
|
if (!(intendedMovementVector == Vector3::zero())) {
|
|
intendedMovementVector = Math::safeDirection(intendedMovementVector);
|
|
}
|
|
}
|
|
|
|
Network::Player* player = Network::Players::getPlayerFromCharacter(getCharacterFromHumanoid(const_cast<Humanoid*>(this))); // this should always be safe, right?
|
|
|
|
if (isWalking && canClickToWalk())
|
|
{
|
|
Vector3 goal = getDeltaToGoal();
|
|
if (goal.isZero())
|
|
intendedMovementVector = Vector3::zero();
|
|
else
|
|
intendedMovementVector = Math::safeDirection(getDeltaToGoal());
|
|
}
|
|
|
|
// this has been modified due to exploits
|
|
float walkSpeed = getWalkSpeed();
|
|
float percentWalkSpeed = getPercentWalkSpeed();
|
|
answer.linear = walkSpeed * intendedMovementVector * percentWalkSpeed;
|
|
testWalkSpeed(walkSpeed, percentWalkSpeed);
|
|
|
|
if (autorotate)
|
|
{
|
|
if (player && RBX::GameBasicSettings::singleton().getRotationType() == RBX::GameBasicSettings::ROTATION_TYPE_CAMERA_RELATIVE)
|
|
{
|
|
answer.rotational.y = getWalkAngleError() * Humanoid::autoTurnSpeed() * 0.5f; // hacky - multiplying angle error times auto turn speed here go get velocity
|
|
}
|
|
else
|
|
{
|
|
if (!answer.linear.isZero())
|
|
{
|
|
float deltaAngle = Math::radWrap(Math::getHeading(answer.linear) - getTorsoHeading());
|
|
answer.rotational.y = autoTurnSpeed() * deltaAngle;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(FLog::UserInputProfile && answer.linear.squaredLength() > 0.0001)
|
|
FASTLOG3F(FLog::UserInputProfile, "Moving velocity: %f %f %f", answer.linear.x, answer.linear.y, answer.linear.z);
|
|
|
|
return answer;
|
|
}
|
|
|
|
bool Humanoid::canClickToWalk() const
|
|
{
|
|
Humanoid* thisHumanoid = const_cast<Humanoid*>(this);
|
|
|
|
Network::Player* player = Network::Players::getPlayerFromCharacter( getCharacterFromHumanoid(thisHumanoid) );
|
|
if (player)
|
|
return clickToWalkEnabled;
|
|
return true;
|
|
}
|
|
|
|
void Humanoid::stepWalkMode(double gameDt)
|
|
{
|
|
if (isWalking && canClickToWalk())
|
|
{
|
|
if (TaskScheduler::singleton().isCyclicExecutive())
|
|
{
|
|
walkTimer-=gameDt;
|
|
}
|
|
else
|
|
{
|
|
walkTimer-=1.0f;
|
|
}
|
|
if (walkTimer <= (double) 0)
|
|
{
|
|
setWalkMode(false);
|
|
moveToFinishedSignal(false);
|
|
}
|
|
else
|
|
{
|
|
float distanceToGoalSqr = getDeltaToGoal().squaredLength();
|
|
if (distanceToGoalSqr < 1.0f)// done, close enough;
|
|
{
|
|
setWalkMode(false);
|
|
moveToFinishedSignal(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector3 Humanoid::getDeltaToGoal() const
|
|
{
|
|
if (isWalking && canClickToWalk())
|
|
{
|
|
const PartInstance* tempTorso = getTorsoSlow();
|
|
Vector3 worldPosition;
|
|
if (tempTorso && hasWalkToPoint(worldPosition))
|
|
{
|
|
Vector3 answer = worldPosition - tempTorso->getCoordinateFrame().translation + Vector3(0,3.0f,0); // height of legs + half of torso
|
|
answer.y = 0.0f;
|
|
if(answer.magnitude() < 0.3f) // this is to stop minute moving of character; we are close enough
|
|
return Vector3::zero();
|
|
return answer;
|
|
}
|
|
}
|
|
return Vector3::zero();
|
|
}
|
|
|
|
float Humanoid::getYAxisRotationalVelocity() const
|
|
{
|
|
return (currentState.get())
|
|
? currentState->getYAxisRotationalVelocity()
|
|
: 0.0f;
|
|
}
|
|
|
|
void Humanoid::setWalkDirectionInternal(const Vector3& value, bool raiseSignal)
|
|
{
|
|
{
|
|
// value should be normalized
|
|
RBXASSERT(value.x <= 1.0f && value.z <= 1.0f);
|
|
|
|
if (fabs(value.x - walkDirection.x) > 0.000001f || fabs(value.z - walkDirection.z) > 0.000001f)
|
|
{
|
|
FASTLOG3F(FLog::UserInputProfile, "Setting walking direction: %f %f %f", value.x, value.y, value.z);
|
|
if (value == Vector3::zero()) {
|
|
walkDirection = value; // doesn't reset the walk mode
|
|
}
|
|
else {
|
|
if (value.squaredMagnitude() > 1.0f) // don't normalize again
|
|
walkDirection = Math::safeDirection(value);
|
|
else
|
|
walkDirection = value;
|
|
isWalking = false;
|
|
}
|
|
|
|
if (raiseSignal)
|
|
raisePropertyChanged(propWalkDirection);
|
|
}
|
|
}
|
|
|
|
// walkdirection has been set (most likely by user input)
|
|
// cancel move direction now
|
|
if (walkDirection != RBX::Vector3::zero())
|
|
{
|
|
setLuaMoveDirection(Vector3::zero());
|
|
}
|
|
}
|
|
|
|
void Humanoid::setWalkDirection(const Vector3& value)
|
|
{
|
|
setWalkDirectionInternal(value, true);
|
|
}
|
|
|
|
void Humanoid::setLuaMoveDirection(const Vector3& value)
|
|
{
|
|
// might need to modify this value, so copy it
|
|
Vector3 copyValue = value;
|
|
|
|
if (copyValue.isZero())
|
|
{
|
|
copyValue = Vector3::zero();
|
|
}
|
|
else if (copyValue.length() > 1.0f)
|
|
{
|
|
copyValue = copyValue.unit();
|
|
}
|
|
|
|
FASTLOG3F(FLog::UserInputProfile, "Setting lua move direction: %f %f %f", copyValue.x, copyValue.y, copyValue.z);
|
|
|
|
if (luaMoveDirection != copyValue)
|
|
{
|
|
luaMoveDirection = copyValue;
|
|
if (luaMoveDirection != Vector3::zero())
|
|
{
|
|
setWalkMode(false); // stop WalkToPoint from moving my character now
|
|
}
|
|
raisePropertyChanged(propLuaMoveDirection);
|
|
if (DFFlag::ReplicateLuaMoveDirection)
|
|
{
|
|
raisePropertyChanged(propLuaMoveDirectionInternal);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Humanoid::allow3dWalkDirection() const
|
|
{
|
|
return ( (currentState.get()) ? currentState->getStateType() == RBX::HUMAN::SWIMMING : false);
|
|
}
|
|
|
|
void Humanoid::setWalkAngleError(const float &value)
|
|
{
|
|
if (walkAngleError != value)
|
|
{
|
|
walkAngleError = value;
|
|
}
|
|
}
|
|
|
|
void Humanoid::setWalkMode(bool walking)
|
|
{
|
|
if (TaskScheduler::singleton().isCyclicExecutive())
|
|
{
|
|
walking ? walkTimer = 8.0 : walkTimer = 0;
|
|
}
|
|
else
|
|
{
|
|
walking ? walkTimer = 240 : walkTimer = 0;
|
|
}
|
|
isWalking = walking;
|
|
}
|
|
|
|
void Humanoid::setWalkToPoint(const Vector3& value)
|
|
{
|
|
{
|
|
if (walkToPoint != value) {
|
|
walkToPoint = value;
|
|
raisePropertyChanged(propWalkToPoint);
|
|
}
|
|
|
|
setWalkMode(true);
|
|
}
|
|
// make sure we aren't moving according to a unit vector at the same time
|
|
setLuaMoveDirection(Vector3(0,0,0));
|
|
}
|
|
|
|
void Humanoid::setWalkToPart(PartInstance* value)
|
|
{
|
|
if (walkToPart.get() != value) {
|
|
walkToPart = shared_from(value);
|
|
raisePropertyChanged(propWalkToPart);
|
|
}
|
|
|
|
setWalkMode(isWalking);
|
|
}
|
|
|
|
void Humanoid::setSeatPart(PartInstance* value)
|
|
{
|
|
if (seatPart.get() != value)
|
|
{
|
|
seatPart = shared_from(value);
|
|
raisePropertyChanged(propSeatPart);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setStrafe(bool value)
|
|
{
|
|
if (strafe != value) {
|
|
strafe = value;
|
|
raisePropertyChanged(propStrafe);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setJumpInternal(bool value, bool replicate)
|
|
{
|
|
if(UserInputService* userInputService = ServiceProvider::create<UserInputService>(this))
|
|
{
|
|
if(!userInputService->getLocalCharacterJumpEnabled())
|
|
return;
|
|
|
|
if (ownedByLocalPlayer)
|
|
{
|
|
userInputService->jumpRequestEvent();
|
|
}
|
|
}
|
|
|
|
if (jump != value) {
|
|
FASTLOG1(FLog::UserInputProfile, "Humanoid jump set: %u", value);
|
|
jump = value;
|
|
|
|
raisePropertyChanged(propJump);
|
|
|
|
if (replicate)
|
|
raisePropertyChanged(propJumpRep);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setJump(bool value)
|
|
{
|
|
setJumpInternal(value, true);
|
|
}
|
|
|
|
void Humanoid::setAutoJump(bool value)
|
|
{
|
|
if(autoJump != value)
|
|
{
|
|
autoJump = value;
|
|
}
|
|
}
|
|
|
|
void Humanoid::setSit(bool value)
|
|
{
|
|
if (sit!=value) {
|
|
sit = value;
|
|
raisePropertyChanged(propSit);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setAutoRotate(bool value)
|
|
{
|
|
if (autorotate!=value) {
|
|
autorotate = value;
|
|
raisePropertyChanged(propAutoRotate);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setAutoJumpEnabled(bool value)
|
|
{
|
|
if (autoJumpEnabled != value) {
|
|
autoJumpEnabled = value;
|
|
raisePropertyChanged(propAutoJumpEnabled);
|
|
}
|
|
}
|
|
|
|
|
|
void Humanoid::setRagdollCriteria(int value)
|
|
{
|
|
if (ragdollCriteria != value) {
|
|
ragdollCriteria = value;
|
|
}
|
|
}
|
|
|
|
void Humanoid::setPlatformStanding(bool value)
|
|
{
|
|
if (platformStanding!=value) {
|
|
platformStanding = value;
|
|
raisePropertyChanged(propPlatformStanding);
|
|
}
|
|
}
|
|
void Humanoid::setTargetPoint(const Vector3& value)
|
|
{
|
|
float tolerance = 1.0f; // default accuracy, studs.
|
|
if (PartInstance* foundTorso = getTorsoSlow()) {
|
|
float torsoToTarget = Math::taxiCabMagnitude(foundTorso->getCoordinateFrame().translation - value);
|
|
float distance = std::max(2.0f, torsoToTarget);
|
|
tolerance = distance / 100.0f;
|
|
}
|
|
|
|
if (Math::taxiCabMagnitude(value - replicatedTargetPoint) > tolerance) // debounce target point to 1/10th of a stud
|
|
{
|
|
targetPoint = value;
|
|
replicatedTargetPoint = targetPoint;
|
|
raisePropertyChanged(propTargetPoint);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setTargetPointLocal(const Vector3& value)
|
|
{
|
|
float tolerance = 1.0f; // default accuracy, studs.
|
|
if (PartInstance* foundTorso = getTorsoSlow()) {
|
|
float torsoToTarget = Math::taxiCabMagnitude(foundTorso->getCoordinateFrame().translation - value);
|
|
float distance = std::max(2.0f, torsoToTarget);
|
|
tolerance = distance / 100.0f;
|
|
}
|
|
|
|
if (Math::taxiCabMagnitude(value - targetPoint) > tolerance) // debounce target point to 1/10th of a stud
|
|
{
|
|
targetPoint = value;
|
|
}
|
|
}
|
|
|
|
Vector3 yPlaneDirection(Vector3& v)
|
|
{
|
|
v.y = 0.0f;
|
|
return Math::safeDirection(v);
|
|
}
|
|
|
|
void Humanoid::moveTo2(Vector3 worldPosition, shared_ptr<Instance> part)
|
|
{
|
|
PartInstance* p = Instance::fastDynamicCast<PartInstance>(part.get());
|
|
moveTo(worldPosition, p);
|
|
}
|
|
|
|
void Humanoid::moveTo(const Vector3& worldPosition, PartInstance* part)
|
|
{
|
|
// client side test here
|
|
Vector3 local = worldPosition;
|
|
if (part)
|
|
local = part->getCoordinateFrame().pointToObjectSpace(worldPosition);
|
|
|
|
walkToPoint = local + Vector3::unitX(); // will force replication
|
|
walkToPart.reset(); // will force replication
|
|
|
|
setWalkToPart(part);
|
|
setWalkToPoint(local);
|
|
}
|
|
|
|
void Humanoid::move(Vector3 walkVector, bool relativeToCamera)
|
|
{
|
|
if(DataModel* dataModel = RBX::DataModel::get(this))
|
|
{
|
|
if(walkVector == Vector3::zero())
|
|
{
|
|
setLuaMoveDirection(RBX::Vector3(0,0,0));
|
|
rawMovementVector = RBX::Vector3(0,0,0);
|
|
return;
|
|
}
|
|
else if (walkVector.length() > 1.0f)
|
|
{
|
|
walkVector.unitize();
|
|
}
|
|
|
|
// set the percent walk speed for analog style controls (like a thumbstick)
|
|
this->setPercentWalkSpeed(walkVector.length()/1.0f);
|
|
Vector3 movementVector = walkVector.direction();
|
|
|
|
rawMovementVector = movementVector;
|
|
|
|
// get the angle between the standard "forward" direction and our current desired direction
|
|
if (relativeToCamera)
|
|
{
|
|
RBX::Workspace* workspace = dataModel->getWorkspace();
|
|
if(!workspace)
|
|
return;
|
|
|
|
Camera* camera = workspace->getCamera();
|
|
if(!camera)
|
|
return;
|
|
|
|
const RBX::Vector3 northDirection(0,0,-1);
|
|
|
|
if (currentState.get() && currentState->getStateType() == HUMAN::SWIMMING) // we can move in three dimensions
|
|
{
|
|
Vector3 moveDir = camera->getCameraCoordinateFrame().vectorToWorldSpace(movementVector);
|
|
setLuaMoveDirection(moveDir);
|
|
}
|
|
else // we can move in two dimensions
|
|
{
|
|
CoordinateFrame cframe = camera->getCameraCoordinateFrame();
|
|
const Vector3 target = cframe.translation + (cframe.lookVector() * Vector3(1, 0, 1));
|
|
cframe.lookAt(target, Vector3::unitY());
|
|
Vector3 moveDir = cframe.vectorToWorldSpace(movementVector);
|
|
moveDir.y = 0;
|
|
if (moveDir.unitize() == 0.0f)
|
|
{
|
|
moveDir = Vector3::zero();
|
|
}
|
|
setLuaMoveDirection(moveDir);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setLuaMoveDirection(movementVector);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Humanoid::setNameDisplayDistance(float d)
|
|
{
|
|
float legalValue = G3D::max(d, 0.0f);
|
|
|
|
if (nameDisplayDistance != legalValue)
|
|
{
|
|
nameDisplayDistance = legalValue;
|
|
}
|
|
}
|
|
|
|
void Humanoid::setHealthDisplayDistance(float d)
|
|
{
|
|
float legalValue = G3D::max(d, 0.0f);
|
|
|
|
if (healthDisplayDistance != legalValue)
|
|
{
|
|
healthDisplayDistance = legalValue;
|
|
}
|
|
}
|
|
|
|
void Humanoid::setCameraOffset(const Vector3 &value)
|
|
{
|
|
if (value != cameraOffset)
|
|
{
|
|
cameraOffset = value;
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
void Humanoid::setLeftLeg(PartInstance* value)
|
|
{
|
|
// deprecated, but can be called by LUA
|
|
}
|
|
|
|
void Humanoid::setRightLeg(PartInstance* value)
|
|
{
|
|
// deprecated, but can be called by LUA
|
|
}
|
|
|
|
void Humanoid::setTorso(PartInstance* value)
|
|
{
|
|
// deprecated, but can be called by LUA
|
|
}
|
|
void Humanoid::setHeadMesh(DataModelMesh* value)
|
|
{
|
|
PartInstance* foundHead;
|
|
if ((foundHead = getHeadSlow()))
|
|
{
|
|
if (DataModelMesh* oldMesh = foundHead->findFirstChildOfType<DataModelMesh>()) {
|
|
oldMesh->remove();
|
|
}
|
|
value->setParent(foundHead);
|
|
}
|
|
|
|
}
|
|
|
|
// TODOFACE
|
|
void Humanoid::setHeadDecal(Decal* val)
|
|
{
|
|
PartInstance* foundHead;
|
|
if ((foundHead = getHeadSlow()))
|
|
{
|
|
if (Decal* oldDecal = foundHead->findFirstChildOfType<Decal>())
|
|
oldDecal->remove();
|
|
|
|
val->setParent(foundHead);
|
|
}
|
|
|
|
}
|
|
|
|
CoordinateFrame Humanoid::getTopOfHead() const
|
|
{
|
|
//PartInstance *h = getHead();
|
|
//RBXASSERT(h);
|
|
//CoordinateFrame c(h->getCoordinateFrame());
|
|
// HACK. USE EXTENTS to allow for different sized heads.
|
|
return CoordinateFrame(G3D::Vector3(0,.5f, 0));
|
|
}
|
|
|
|
CoordinateFrame Humanoid::getRightArmGrip() const
|
|
{
|
|
CoordinateFrame C0;
|
|
|
|
if (getUseR15())
|
|
{
|
|
Attachment *answer = Tool::findFirstAttachmentByNameRecursive(getParent(), "RightGripAttachment");
|
|
if (answer != NULL)
|
|
{
|
|
return answer->getFrameInPart();
|
|
} else {
|
|
// HACK. USE EXTENTS to allow for different sized arms.
|
|
C0.lookAt(Vector3(0,-1,0), Vector3(0,0,-1));
|
|
C0.translation = Vector3(0, -0.2, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// HACK. USE EXTENTS to allow for different sized arms.
|
|
C0.lookAt(Vector3(0,-1,0), Vector3(0,0,-1));
|
|
C0.translation = Vector3(0, -1, 0);
|
|
}
|
|
|
|
return C0;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Humanoid::setCachePointerByType(AppendageType appendage, PartInstance *part)
|
|
{
|
|
appendageCache[appendage] = shared_from<PartInstance>(part);
|
|
updateBaseInstance();
|
|
}
|
|
|
|
void Humanoid::updateBaseInstance()
|
|
{
|
|
Body* body = getRootBodyFast();
|
|
SimBody* simBody = body ? body->getRootSimBody() : NULL;
|
|
|
|
if (simBody && simBody->getHumanoidConnectorCount() > 0 )
|
|
return;
|
|
|
|
if (appendageCache[TORSO] == NULL)
|
|
{
|
|
baseInstance = appendageCache[VISIBLE_TORSO];
|
|
}
|
|
else
|
|
{
|
|
baseInstance = appendageCache[TORSO];
|
|
}
|
|
}
|
|
|
|
const PartInstance* Humanoid::getConstAppendageSlow(AppendageType appendage) const
|
|
{
|
|
return appendageCache[appendage].get();
|
|
}
|
|
|
|
PartInstance* Humanoid::getAppendageSlow(AppendageType appendage)
|
|
{
|
|
return const_cast<PartInstance*>(getConstAppendageSlow(appendage));
|
|
}
|
|
|
|
PartInstance* Humanoid::getAppendageFast(AppendageType appendage, shared_ptr<PartInstance>& appendagePart)
|
|
{
|
|
PartInstance* answer = appendagePart.get();
|
|
|
|
RBXASSERT_FISHING( !answer
|
|
|| (answer == Instance::fastDynamicCast<PartInstance>(getParent()->findFirstChildByName(getAppendageString(appendage, getUseR15())))) );
|
|
|
|
return answer;
|
|
}
|
|
|
|
PartInstance* Humanoid::getTorsoDangerous() const {return const_cast<PartInstance*>(getTorsoSlow());} // reflection only
|
|
PartInstance* Humanoid::getLeftLegDangerous() const {return const_cast<PartInstance*>(getLeftLegSlow());} // reflection only
|
|
PartInstance* Humanoid::getRightLegDangerous() const {return const_cast<PartInstance*>(getRightLegSlow());} // reflection only
|
|
|
|
PartInstance* Humanoid::getTorsoSlow()
|
|
{
|
|
PartInstance *retval = getAppendageSlow(TORSO);
|
|
|
|
if (!retval)
|
|
retval = getAppendageSlow(VISIBLE_TORSO);
|
|
return retval;
|
|
}
|
|
PartInstance* Humanoid::getVisibleTorsoSlow() {return getAppendageSlow(VISIBLE_TORSO);}
|
|
PartInstance* Humanoid::getHeadSlow() {return getAppendageSlow(HEAD);}
|
|
PartInstance* Humanoid::getRightArmSlow() {return getAppendageSlow(RIGHT_ARM);}
|
|
PartInstance* Humanoid::getLeftArmSlow() {return getAppendageSlow(LEFT_ARM);}
|
|
PartInstance* Humanoid::getRightLegSlow() {return getAppendageSlow(RIGHT_LEG);}
|
|
PartInstance* Humanoid::getLeftLegSlow() {return getAppendageSlow(LEFT_LEG);}
|
|
|
|
const PartInstance* Humanoid::getTorsoSlow() const
|
|
{
|
|
const PartInstance *retval = getConstAppendageSlow(TORSO);
|
|
|
|
if (!retval)
|
|
retval = getConstAppendageSlow(VISIBLE_TORSO);
|
|
return retval;
|
|
}
|
|
const PartInstance* Humanoid::getVisibleTorsoSlow() const {return getConstAppendageSlow(VISIBLE_TORSO);}
|
|
const PartInstance* Humanoid::getHeadSlow() const {return getConstAppendageSlow(HEAD);}
|
|
const PartInstance* Humanoid::getRightArmSlow() const {return getConstAppendageSlow(RIGHT_ARM);}
|
|
const PartInstance* Humanoid::getLeftArmSlow() const {return getConstAppendageSlow(LEFT_ARM);}
|
|
const PartInstance* Humanoid::getRightLegSlow() const {return getConstAppendageSlow(RIGHT_LEG);}
|
|
const PartInstance* Humanoid::getLeftLegSlow() const {return getConstAppendageSlow(LEFT_LEG);}
|
|
|
|
PartInstance* Humanoid::getTorsoFast() {return getAppendageFast(TORSO, appendageCache[TORSO]);}
|
|
PartInstance* Humanoid::getVisibleTorsoFast() {return getAppendageFast(VISIBLE_TORSO, appendageCache[VISIBLE_TORSO]);}
|
|
PartInstance* Humanoid::getHeadFast() {return getAppendageFast(HEAD, appendageCache[HEAD]);}
|
|
PartInstance* Humanoid::getRightArmFast() {return getAppendageFast(RIGHT_ARM, appendageCache[RIGHT_ARM]);}
|
|
PartInstance* Humanoid::getLeftArmFast() {return getAppendageFast(LEFT_ARM, appendageCache[LEFT_ARM]);}
|
|
PartInstance* Humanoid::getRightLegFast() {return getAppendageFast(RIGHT_LEG, appendageCache[RIGHT_LEG]);}
|
|
PartInstance* Humanoid::getLeftLegFast() {return getAppendageFast(LEFT_LEG, appendageCache[LEFT_LEG]);}
|
|
|
|
|
|
const StatusInstance* Humanoid::getStatusSlow() const
|
|
{
|
|
return Instance::fastDynamicCast<StatusInstance>(findConstFirstChildByName("Status"));
|
|
}
|
|
|
|
StatusInstance* Humanoid::getStatusSlow()
|
|
{
|
|
return Instance::fastDynamicCast<StatusInstance>(findFirstChildByName("Status"));
|
|
}
|
|
|
|
StatusInstance* Humanoid::getStatusFast()
|
|
{
|
|
if(!status) {
|
|
status = shared_from(getStatusSlow());
|
|
}
|
|
return status.get();
|
|
}
|
|
|
|
Primitive* Humanoid::getTorsoPrimitiveSlow() {return PartInstance::getPrimitive(getTorsoSlow());}
|
|
Primitive* Humanoid::getHeadPrimitiveSlow() {return PartInstance::getPrimitive(getHeadSlow());}
|
|
|
|
|
|
void Humanoid::setName(const std::string& value)
|
|
{
|
|
if (!ProfanityFilter::ContainsProfanity(value)){
|
|
Instance::setName(value);
|
|
filterState = ContentFilter::Waiting;
|
|
}
|
|
}
|
|
|
|
JointInstance* Humanoid::getRightShoulder()
|
|
{
|
|
PartInstance* temp = getVisibleTorsoSlow();
|
|
|
|
return temp ? rbx_static_cast<JointInstance*>(temp->findFirstChildByName("Right Shoulder"))
|
|
: NULL;
|
|
}
|
|
|
|
Joint* Humanoid::getNeck()
|
|
{
|
|
PartInstance* foundHead = getHeadSlow();
|
|
PartInstance* foundTorso = NULL;
|
|
|
|
foundTorso = getVisibleTorsoSlow();
|
|
|
|
if (foundHead && foundTorso) {
|
|
hadNeck = true; // this humanoid once had a neck
|
|
return Primitive::getJoint(foundHead->getPartPrimitive(), foundTorso->getPartPrimitive());
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
Primitive* Humanoid::getTorsoPrimitiveFast()
|
|
{
|
|
PartInstance* answer = baseInstance.get();
|
|
return answer ? answer->getPartPrimitive() : NULL;
|
|
}
|
|
|
|
Body* Humanoid::getRootBodyFast()
|
|
{
|
|
Body* torsoBody = getTorsoBodyFast();
|
|
return torsoBody ? torsoBody->getRoot() : NULL;
|
|
}
|
|
|
|
|
|
float Humanoid::getTorsoHeading() const
|
|
{
|
|
if (const PartInstance* foundTorso = getTorsoSlow()) {
|
|
return Math::getHeading(foundTorso->getCoordinateFrame().lookVector());
|
|
}
|
|
else {
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
float Humanoid::getTorsoElevation() const
|
|
{
|
|
if (const PartInstance* foundTorso = getTorsoSlow()) {
|
|
return Math::getElevation(foundTorso->getCoordinateFrame().lookVector().direction());
|
|
}
|
|
else {
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
// Engine Step - 1000's per second
|
|
void Humanoid::computeForce(bool throttling)
|
|
{
|
|
if (FFlag::PhysicsSkipNonRealTimeHumanoidForceCalc)
|
|
{
|
|
RBXASSERT(getEngineBody() && getEngineBody()->getRootSimBody());
|
|
if (!getEngineBody()->getRootSimBody()->isRealTimeBody())
|
|
return;
|
|
}
|
|
|
|
if (world && currentState.get()) // put this here for safety
|
|
{
|
|
RBXASSERT(currentState.get());
|
|
|
|
G3D::Vector3 temp;
|
|
// The following RBXASSERT is just a way to get around the system to build this line on DEBUG build
|
|
// The expression will always evaluate to true as we are comparing temp == temp
|
|
// The return value of getTorsoFast()->getCoordinateFrame().translation is not bool & is Vector3.
|
|
LEGACY_ASSERT(getTorsoFast() ? ((temp = getTorsoFast()->getCoordinateFrame().translation) == temp) : true);
|
|
|
|
currentState->onComputeForce();
|
|
|
|
LEGACY_ASSERT(getTorsoFast() ? ((temp - getTorsoFast()->getCoordinateFrame().translation).length() < 1.0f) : true);
|
|
}
|
|
}
|
|
|
|
static void fireHumanoidChanged(Instance* parent)
|
|
{
|
|
if (parent && parent->getChildren())
|
|
{
|
|
const Instances& children = *parent->getChildren();
|
|
for (size_t i = 0; i < children.size(); ++i)
|
|
{
|
|
children[i]->humanoidChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fireHumanoidChangedRec(Instance* parent)
|
|
{
|
|
if (!parent)
|
|
return;
|
|
|
|
parent->humanoidChanged();
|
|
|
|
if (parent->getChildren())
|
|
{
|
|
const Instances& children = *parent->getChildren();
|
|
|
|
for (auto& child: children)
|
|
fireHumanoidChangedRec(child.get());
|
|
}
|
|
}
|
|
|
|
void Humanoid::updateSiblingPropertyListener(shared_ptr<PartInstance> sibling)
|
|
{
|
|
if (sibling != NULL)
|
|
{
|
|
// hook up property change notifications
|
|
siblingMap.erase(sibling);
|
|
if (sibling->getParent() == getParent())
|
|
{
|
|
siblingMap[sibling] = sibling->propertyChangedSignal.connect(boost::bind(&Humanoid::onEvent_SiblingPropertyChanged,this,_1));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Humanoid::onEvent_ChildModified(shared_ptr<Instance> child)
|
|
{
|
|
PartInstance *pChild = Instance::fastDynamicCast<PartInstance>(child.get());
|
|
|
|
if (pChild != NULL)
|
|
{
|
|
// see if this is a humanoid part
|
|
for (int i = TORSO; i < APPENDAGE_COUNT; i++)
|
|
{
|
|
if (pChild->getName() == getAppendageString(i, getUseR15()))
|
|
{
|
|
if (pChild->getParent() == getParent())
|
|
{
|
|
setCachePointerByType((AppendageType) i, pChild);
|
|
}
|
|
else
|
|
{
|
|
setCachePointerByType((AppendageType) i, NULL);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
updateSiblingPropertyListener(shared_from<PartInstance>(pChild));
|
|
}
|
|
}
|
|
|
|
void Humanoid::onEvent_SiblingPropertyChanged(const RBX::Reflection::PropertyDescriptor* desc)
|
|
{
|
|
if (desc == &Instance::desc_Name)
|
|
{
|
|
Instance *pParent = getParent();
|
|
|
|
if (pParent != NULL)
|
|
{
|
|
// update all cache entries
|
|
for (int i = TORSO; i < APPENDAGE_COUNT; i++)
|
|
{
|
|
// remove any old map for this part in case it changes due to the name change
|
|
if (appendageCache[i] != NULL)
|
|
siblingMap.erase(appendageCache[i]);
|
|
appendageCache[i] = Instance::fastSharedDynamicCast<PartInstance>(shared_from(pParent->findFirstChildByName(getAppendageString(i, getUseR15()))));
|
|
updateSiblingPropertyListener(appendageCache[i]);
|
|
}
|
|
updateBaseInstance();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Into / out of the world
|
|
void Humanoid::onAncestorChanged(const AncestorChanged& event)
|
|
{
|
|
Super::onAncestorChanged(event); // Note - moved this to first
|
|
|
|
if (event.child == this)
|
|
{
|
|
if (event.newParent == NULL)
|
|
{
|
|
// clear all cache entries
|
|
for (int i = TORSO; i < APPENDAGE_COUNT; i++)
|
|
{
|
|
updateSiblingPropertyListener(appendageCache[i]);
|
|
appendageCache[i] = shared_ptr<PartInstance>();;
|
|
}
|
|
baseInstance = shared_ptr<PartInstance>();;
|
|
|
|
// remove old sibling change listeners
|
|
characterChildAdded.disconnect();
|
|
characterChildRemoved.disconnect();
|
|
|
|
}
|
|
else
|
|
{
|
|
// update all cache entries
|
|
for (int i = TORSO; i < APPENDAGE_COUNT; i++)
|
|
{
|
|
appendageCache[i] = Instance::fastSharedDynamicCast<PartInstance>(shared_from(getParent()->findFirstChildByName(getAppendageString(i, getUseR15()))));
|
|
updateSiblingPropertyListener(appendageCache[i]);
|
|
}
|
|
updateBaseInstance();
|
|
|
|
// register update to listen to sibling changes
|
|
characterChildAdded = getParent()->onDemandWrite()->childAddedSignal.connect(boost::bind(&Humanoid::onEvent_ChildModified, this, _1));
|
|
characterChildRemoved = getParent()->onDemandWrite()->childRemovedSignal.connect(boost::bind(&Humanoid::onEvent_ChildModified, this, _1));
|
|
}
|
|
}
|
|
|
|
World* newWorld = Workspace::getWorldIfInWorkspace(this);
|
|
|
|
if (world != newWorld)
|
|
{
|
|
if (world != NULL) { // HACK - currentState destructor looks at world
|
|
RBXASSERT(newWorld == NULL);
|
|
|
|
ownedByLocalPlayer = false;
|
|
onCFrameChangedConnection.disconnect();
|
|
|
|
currentState.reset();
|
|
if (!waitingForTorso())
|
|
{
|
|
setPrimitive(0, NULL);
|
|
setPrimitive(1, NULL);
|
|
world->removeJoint(this);
|
|
}
|
|
}
|
|
|
|
world = newWorld;
|
|
|
|
if (world != NULL) {
|
|
CheckTorso();
|
|
currentState.reset(HUMAN::HumanoidState::defaultState(this)); // not balanced with the reset see below
|
|
onLocalHumanoidEnteringWorkspace();
|
|
}
|
|
}
|
|
|
|
if (DFFlag::HumanoidCookieRecursive)
|
|
{
|
|
fireHumanoidChangedRec(event.oldParent);
|
|
fireHumanoidChangedRec(event.newParent);
|
|
}
|
|
else
|
|
{
|
|
fireHumanoidChanged(event.oldParent);
|
|
fireHumanoidChanged(event.newParent);
|
|
}
|
|
}
|
|
|
|
void Humanoid::onLocalHumanoidEnteringWorkspace()
|
|
{
|
|
// called when Humanoid enters workspace
|
|
Workspace* workspace = ServiceProvider::find<Workspace>(this);
|
|
|
|
RBXASSERT(workspace);
|
|
|
|
Humanoid *me = Humanoid::getLocalHumanoidFromContext(this);
|
|
if (this == me)
|
|
{
|
|
// 1. Update camera subject
|
|
Camera::CameraType type = workspace->getCamera()->getCameraType();
|
|
if (type == Camera::CUSTOM_CAMERA ||
|
|
type == Camera::FOLLOW_CAMERA ||
|
|
type == Camera::ATTACH_CAMERA ||
|
|
type == Camera::TRACK_CAMERA)
|
|
{
|
|
if (workspace->getCamera()->getCameraSubject() != this) workspace->getCamera()->setCameraSubject(this);
|
|
}
|
|
|
|
ownedByLocalPlayer = true;
|
|
}
|
|
}
|
|
|
|
|
|
void Humanoid::updateLocalSimulating()
|
|
{
|
|
PartInstance* torso = this->getTorsoSlow();
|
|
Assembly* assembly = torso ? torso->getPartPrimitive()->getAssembly() : NULL;
|
|
bool movingAssembly = assembly ? !assembly->computeIsGrounded() : false;
|
|
|
|
if (!assembly) {
|
|
localSimulating = false;
|
|
}
|
|
else if (movingAssembly) {
|
|
localSimulating = !torso->computeNetworkOwnerIsSomeoneElse();
|
|
}
|
|
}
|
|
|
|
void Humanoid::onCFrameChangedFromReflection()
|
|
{
|
|
if (currentState.get())
|
|
currentState->onCFrameChangedFromReflection();
|
|
}
|
|
|
|
|
|
// All humanoids client/server do this
|
|
void Humanoid::onStepped(const Stepped& event)
|
|
{
|
|
if (!ownedByLocalPlayer && !event.longStep)
|
|
{
|
|
return; // if this isn't a humanoid controlled by a player, only step every other step (aka long step)
|
|
}
|
|
|
|
if (waitingForTorso() && !CheckTorso())
|
|
{
|
|
return; // sign, still waiting for torso
|
|
}
|
|
|
|
RBXPROFILER_SCOPE("Physics", "Humanoid::onStepped");
|
|
|
|
// 1. All Humanoids - update local walk mode variables
|
|
if (event.longStep)
|
|
{
|
|
stepWalkMode(event.gameStep);
|
|
}
|
|
|
|
updateLocalSimulating();
|
|
|
|
// 2. All Humanoids - nothing to do with local - simulate those in spatial region, otherwise "no-simulate" them
|
|
if (localSimulating || ownedByLocalPlayer) {
|
|
RBXASSERT(world);
|
|
RBXASSERT(currentState.get());
|
|
if (Instance::fastDynamicCast<ModelInstance>(getParent()))
|
|
{
|
|
G3D::Vector3 temp;
|
|
// The following RBXASSERT is just a way to get around the system to build this line on DEBUG build
|
|
// The expression will always evaluate to true as we are comapring temp == temp
|
|
// The return value of getTorsoFast()->getCoordinateFrame().translation is not bool & is Vector3.
|
|
RBXASSERT(getTorsoFast() ? ((temp = getTorsoFast()->getCoordinateFrame().translation) == temp) : true);
|
|
|
|
if (!ownedByLocalPlayer && !RBX::TaskScheduler::singleton().isCyclicExecutive())
|
|
{
|
|
if (event.longStep)
|
|
{
|
|
HUMAN::HumanoidState::simulate(currentState, Constants::longUiStepDt()); // may change state
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HUMAN::HumanoidState::simulate(currentState, event.gameStep); // may change state
|
|
}
|
|
|
|
RBXASSERT(getTorsoFast() ? ((temp - getTorsoFast()->getCoordinateFrame().translation).length() < 10.0f) : true);
|
|
}
|
|
|
|
updateHadHealth();
|
|
|
|
}
|
|
else
|
|
{
|
|
if (world)
|
|
{
|
|
RBXASSERT(currentState.get());
|
|
HUMAN::HumanoidState::noSimulate(currentState); // may change currentState
|
|
}
|
|
}
|
|
|
|
if (world)
|
|
{
|
|
RBXASSERT(currentState.get());
|
|
if (currentState.get())
|
|
{
|
|
shared_ptr<HUMAN::HumanoidState> tempState(currentState); // Holding the current state in case of ancestor changed and a reset
|
|
tempState->fireEvents();
|
|
}
|
|
}
|
|
|
|
if(animator)
|
|
{
|
|
animator->onStepped(event);
|
|
}
|
|
}
|
|
|
|
|
|
void Humanoid::renderWaypoint(Adorn* adorn, const Vector3& waypoint)
|
|
{
|
|
if(RBX::GameBasicSettings::singleton().inClassicMode())
|
|
DrawAdorn::cylinder(adorn, CoordinateFrame(waypoint), 1, 0.2, 1.0, Color3::green());
|
|
}
|
|
|
|
void Humanoid::render3dAdorn(Adorn* adorn)
|
|
{
|
|
if (currentState.get())
|
|
{
|
|
currentState->render3dAdorn(adorn);
|
|
}
|
|
}
|
|
|
|
void Humanoid::renderMultiplayer(Adorn* adorn, const RBX::Camera& camera)
|
|
{
|
|
if (displayDistanceType == Humanoid::HUMANOID_DISPLAY_DISTANCE_TYPE_NONE)
|
|
return;
|
|
|
|
bool occludeName = false;
|
|
float localHumanoidDistance = 0.0f;
|
|
float localNameDisplayDistance = defaultDisplayDistance;
|
|
float localHealthDisplayDistance = defaultDisplayDistance;
|
|
Humanoid* localHumanoid = NULL;
|
|
|
|
if ((localHumanoid = Humanoid::getLocalHumanoidFromContext(this)))
|
|
{
|
|
if(localHumanoid->getNameOcclusion() == Humanoid::NAME_OCCLUSION_ALL)
|
|
occludeName = true;
|
|
else if(localHumanoid->getNameOcclusion() == Humanoid::NAME_OCCLUSION_ENEMY)
|
|
if(Teams *teams = ServiceProvider::find<Teams>(this))
|
|
if(teams->getTeamColorForHumanoid(this) != teams->getTeamColorForHumanoid(localHumanoid))
|
|
occludeName = true;
|
|
|
|
if (displayDistanceType == Humanoid::HUMANOID_DISPLAY_DISTANCE_TYPE_SUBJECT)
|
|
{
|
|
localNameDisplayDistance = getNameDisplayDistance();
|
|
localHealthDisplayDistance = getHealthDisplayDistance();
|
|
}
|
|
else
|
|
{
|
|
localNameDisplayDistance = localHumanoid->getNameDisplayDistance();
|
|
localHealthDisplayDistance = localHumanoid->getHealthDisplayDistance();
|
|
}
|
|
}
|
|
|
|
if (PartInstance *foundHead = getHeadSlow())
|
|
{
|
|
Vector3 aboveHead = foundHead->calcRenderingCoordinateFrame().translation;
|
|
|
|
if(DFFlag::EnableMotionAnalytics)
|
|
{
|
|
Vector3 pos3 = aboveHead;
|
|
Primitive * HeadPrimitive = RBX::PartInstance::getPrimitive(foundHead);
|
|
if(HeadPrimitive)
|
|
{
|
|
World *HeadWorld = HeadPrimitive->getWorld();
|
|
|
|
if(HeadWorld)
|
|
{
|
|
double threshold = (double)(DFInt::MotionDiscontinuityThreshold) * 0.1;
|
|
if(((pos0 - pos1).magnitude() > threshold)
|
|
&& ((pos1 - pos2).magnitude() <= threshold)
|
|
&& ((pos2 - pos3).magnitude() > threshold))
|
|
{
|
|
HeadWorld->plusErrorCount(1.0);
|
|
}
|
|
HeadWorld->plusPassCount(1.0);
|
|
pos0 = pos1;
|
|
pos1 = pos2;
|
|
pos2 = pos3;
|
|
}
|
|
}
|
|
}
|
|
aboveHead.y += 1.5f;
|
|
|
|
float distance = (camera.coordinateFrame().translation - aboveHead).magnitude();
|
|
|
|
if (distance > 100.0f || foundHead->getTransparencyXml() > .99f) // if the head is 100% transparent, assume user wants this char to be invisible
|
|
return;
|
|
|
|
|
|
if(occludeName) // if we are occluding, do occlusion check first
|
|
if(ContactManager* contactManager = getContactManager())
|
|
{
|
|
FilterHumanoidNameOcclusion filter(shared_from(this));
|
|
if(!camera.isPartVisibleFast(*foundHead, *contactManager, &filter))
|
|
return;
|
|
}
|
|
|
|
if (localHumanoid)
|
|
{
|
|
if (PartInstance *localHead = localHumanoid->getHeadSlow())
|
|
{
|
|
Vector3 localHeadPos = localHead->calcRenderingCoordinateFrame().translation;
|
|
localHumanoidDistance = (localHeadPos - aboveHead).magnitude();
|
|
}
|
|
}
|
|
|
|
float fontSize = 12.0f;
|
|
if (distance < 20.0f)
|
|
fontSize = 24.0f;
|
|
else if (distance < 50.0f)
|
|
fontSize = 18.0f;
|
|
|
|
G3D::Color3 nameTagColor;
|
|
Teams *teams = ServiceProvider::find<Teams>(this);
|
|
if(teams != NULL)
|
|
nameTagColor = teams->getTeamColorForHumanoid(this);
|
|
else
|
|
nameTagColor = G3D::Color3::white();
|
|
|
|
|
|
if(this->getParent() && displayText != this->getParent()->getName())
|
|
{
|
|
displayText = this->getParent()->getName();
|
|
if (!ProfanityFilter::ContainsProfanity(displayText))
|
|
filterState = ContentFilter::Waiting;
|
|
else
|
|
{
|
|
displayText = "";
|
|
filterState = ContentFilter::Succeeded;
|
|
}
|
|
}
|
|
|
|
if(filterState == ContentFilter::Waiting)
|
|
if(ContentFilter* contentFilter = ServiceProvider::create<ContentFilter>(this))
|
|
filterState = contentFilter->getStringState(displayText);
|
|
|
|
const float width = (float)(fontSize * 4);
|
|
|
|
Vector3 screenLoc = camera.project(aboveHead);
|
|
if(screenLoc.z == std::numeric_limits<float>::infinity())
|
|
return;
|
|
|
|
float nameAlpha = 1.0f;
|
|
float healthAlpha = 1.0f;
|
|
if (localHumanoidDistance > (0.9f * localNameDisplayDistance)) {
|
|
nameAlpha = std::max(((localNameDisplayDistance - localHumanoidDistance) / (0.1f * localNameDisplayDistance)), 0.0f);
|
|
}
|
|
if (localHumanoidDistance > (0.9f * localHealthDisplayDistance)) {
|
|
healthAlpha = std::max(((localHealthDisplayDistance - localHumanoidDistance) / (0.1f * localHealthDisplayDistance)), 0.0f);
|
|
}
|
|
|
|
if (adorn->isVR())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Workspace::showActiveAnimationAsset)
|
|
{
|
|
Instance* child = Instance::findFirstChildByName("Animator");
|
|
if (child)
|
|
{
|
|
if (Animator* animator = child->fastDynamicCast<Animator>())
|
|
{
|
|
std::string activeAnim = animator->getActiveAnimation();
|
|
adorn->drawFont2D(
|
|
activeAnim,
|
|
screenLoc.xy(),
|
|
fontSize,
|
|
false,
|
|
Color4(nameTagColor, nameAlpha),
|
|
Color4(Color3::black(), nameAlpha),
|
|
Text::FONT_ARIALBOLD,
|
|
Text::XALIGN_CENTER,
|
|
Text::YALIGN_BOTTOM );
|
|
}
|
|
}
|
|
}
|
|
else if(filterState == ContentFilter::Succeeded && (localNameDisplayDistance >= localHumanoidDistance))
|
|
{
|
|
static float alpha = 0.5f;
|
|
Color4 strokeColor;
|
|
|
|
static float rFactor = 0.25f;
|
|
static float gFactor = 0.21f;
|
|
static float bFactor = 0.0722f;
|
|
float colorY = rFactor * nameTagColor.r + gFactor * nameTagColor.g + bFactor * nameTagColor.b;
|
|
float nameColorL = (116.0f * std::pow(colorY, (1.0f/3.0f)) - 16.0f) / 100.0f;
|
|
|
|
static float factor = 0.52f;
|
|
if (nameColorL >= factor)
|
|
{
|
|
strokeColor = Color4(49.0f/255.0f, 49.0f/255.0f, 49.0f/255.0f, alpha * nameAlpha);
|
|
} else {
|
|
strokeColor = Color4(227.0f/255.0f, 227.0f/255.0f, 227.0f/255.0f, alpha * nameAlpha);
|
|
}
|
|
|
|
|
|
// Name
|
|
Vector2 pos = screenLoc.xy();
|
|
pos.y -= 2.0f;
|
|
adorn->drawFont2D(
|
|
displayText,
|
|
pos,
|
|
fontSize,
|
|
false,
|
|
Color4(nameTagColor, nameAlpha),
|
|
strokeColor,
|
|
Text::FONT_SOURCESANS,
|
|
Text::XALIGN_CENTER,
|
|
Text::YALIGN_BOTTOM );
|
|
}
|
|
// Health
|
|
if (maxHealth != 0 && localHealthDisplayDistance >= localHumanoidDistance)
|
|
{
|
|
float relativeHealth = G3D::clamp(health/maxHealth, 0, 1);
|
|
|
|
float healthHeight = 2;
|
|
float strokeWidth = 2;
|
|
Vector2 topLeft(screenLoc.x - 0.5f * width, screenLoc.y + 2);
|
|
float middle = topLeft.x + (width) * relativeHealth;
|
|
|
|
Rect healthRect(topLeft, Vector2(middle, topLeft.y + healthHeight));
|
|
|
|
Rect backRect[3] = {
|
|
Rect(topLeft + Vector2(0, -strokeWidth), topLeft + Vector2(width, healthHeight + strokeWidth)),
|
|
Rect(topLeft + Vector2(-strokeWidth, -strokeWidth/2.0f), topLeft + Vector2(0, healthHeight + strokeWidth/2.0f)),
|
|
Rect(topLeft + Vector2(width, -strokeWidth/2.0f), topLeft + Vector2(width + strokeWidth, healthHeight + strokeWidth/2.0f))
|
|
};
|
|
|
|
Rect strokeRect[4] = {
|
|
Rect(topLeft + Vector2(0, -(strokeWidth*1.5f)), topLeft + Vector2(width, -strokeWidth)),
|
|
Rect(topLeft + Vector2(0, healthHeight + strokeWidth), topLeft + Vector2(width, healthHeight + (strokeWidth*1.5f))),
|
|
Rect(topLeft + Vector2(-(strokeWidth*1.5), -(strokeWidth*1.0f)), topLeft + Vector2(0, healthHeight + (strokeWidth*1.0f))),
|
|
Rect(topLeft + Vector2(width, -(strokeWidth*1.0f)), topLeft + Vector2(width + strokeWidth*1.5f, healthHeight + (strokeWidth*1.0f)))
|
|
};
|
|
|
|
// Calc bar color
|
|
Color4 barColor;
|
|
|
|
static Color3 healthColors[] = {
|
|
Color3(255.0f/255.0f, 28.0f/255.0f, 0.0f/255.0f),
|
|
Color3(250.0f/255.0f, 235.0f/255.0f, 0.0f/255.0f),
|
|
Color3(27.0f/255.0f, 252.0f/255.0f, 107.0f/255.0f)
|
|
};
|
|
|
|
if (relativeHealth <= 0.1f)
|
|
{
|
|
barColor = Color4(healthColors[0], healthAlpha);
|
|
}
|
|
else if (relativeHealth >= 0.8f)
|
|
{
|
|
barColor = Color4(healthColors[2], healthAlpha);
|
|
}
|
|
else
|
|
{
|
|
static float samplePoints[] = { 0.1f, 0.5f, 0.8f };
|
|
Vector3 numeratorSum;
|
|
float denominatorSum = 0;
|
|
bool found = false;
|
|
for (int i = 0; i < 3; i ++)
|
|
{
|
|
float distance = fabsf(relativeHealth - samplePoints[i]);
|
|
if (distance <= 0.0f)
|
|
{
|
|
barColor = Color4(healthColors[i], healthAlpha);
|
|
found = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
float wi = 1.0f / (distance * distance);
|
|
numeratorSum = numeratorSum + wi * Vector3(healthColors[i].r, healthColors[i].g, healthColors[i].b);
|
|
denominatorSum = denominatorSum + wi;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
numeratorSum = numeratorSum / denominatorSum;
|
|
barColor = Color4(numeratorSum.x, numeratorSum.y, numeratorSum.z, healthAlpha);
|
|
}
|
|
}
|
|
|
|
adorn->setIgnoreTexture(true);
|
|
Color4 backGrey(101.0f/255.0f, 100.0f/255.0f, 100.0f/255.0f, healthAlpha * 0.5);
|
|
Color4 backGreyOpaque(100.0f/255.0f, 100.0f/255.0f, 100.0f/255.0f, healthAlpha);
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
adorn->rect2d(strokeRect[i].toRect2D(), Vector2::one(), Vector2::one(), barColor, strokeRect[i].toRect2D());
|
|
}
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
Color4 color = backGrey;
|
|
if (i > 0)
|
|
{
|
|
color = backGreyOpaque;
|
|
}
|
|
adorn->rect2d(backRect[i].toRect2D(), Vector2::one(), Vector2::one(), color, backRect[i].toRect2D());
|
|
}
|
|
|
|
adorn->rect2d(healthRect.toRect2D(), Vector2::one(), Vector2::one(), barColor, healthRect.toRect2D());
|
|
adorn->setIgnoreTexture(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Humanoid::renderBillboard(Adorn* adorn, const RBX::Camera& camera)
|
|
{
|
|
if (displayDistanceType == Humanoid::HUMANOID_DISPLAY_DISTANCE_TYPE_NONE)
|
|
return;
|
|
|
|
// Get head
|
|
PartInstance* head = getHeadSlow();
|
|
if (!head)
|
|
return;
|
|
|
|
if (head->getTransparencyXml() > .99f) // if the head is 100% transparent, assume user wants this char to be invisible
|
|
return;
|
|
|
|
// Compute camera distance
|
|
Vector3 aboveHead = head->calcRenderingCoordinateFrame().translation;
|
|
aboveHead.y += 1.5f;
|
|
|
|
Vector3 look = camera.getRenderingCoordinateFrame().translation - aboveHead;
|
|
|
|
float distance = look.magnitude();
|
|
|
|
// Compute occlusion/fade parameters
|
|
Humanoid* localHumanoid = Humanoid::getLocalHumanoidFromContext(this);
|
|
Teams *teams = ServiceProvider::find<Teams>(this);
|
|
|
|
bool occludeName = false;
|
|
float localHumanoidDistance = distance;
|
|
float localNameDisplayDistance = defaultDisplayDistance;
|
|
float localHealthDisplayDistance = defaultDisplayDistance;
|
|
|
|
if (localHumanoid)
|
|
{
|
|
if (localHumanoid->getNameOcclusion() == Humanoid::NAME_OCCLUSION_ALL)
|
|
occludeName = true;
|
|
else if (localHumanoid->getNameOcclusion() == Humanoid::NAME_OCCLUSION_ENEMY)
|
|
occludeName = (teams && teams->getTeamColorForHumanoid(this) != teams->getTeamColorForHumanoid(localHumanoid));
|
|
|
|
if (displayDistanceType == Humanoid::HUMANOID_DISPLAY_DISTANCE_TYPE_SUBJECT)
|
|
{
|
|
localNameDisplayDistance = getNameDisplayDistance();
|
|
localHealthDisplayDistance = getHealthDisplayDistance();
|
|
}
|
|
else
|
|
{
|
|
localNameDisplayDistance = localHumanoid->getNameDisplayDistance();
|
|
localHealthDisplayDistance = localHumanoid->getHealthDisplayDistance();
|
|
}
|
|
|
|
if (PartInstance* localHead = localHumanoid->getHeadSlow())
|
|
{
|
|
Vector3 localHeadPos = localHead->calcRenderingCoordinateFrame().translation;
|
|
|
|
localHumanoidDistance = (localHeadPos - aboveHead).magnitude();
|
|
}
|
|
}
|
|
|
|
// Fast early-out
|
|
if (localHumanoidDistance >= localNameDisplayDistance && localHumanoidDistance >= localHealthDisplayDistance)
|
|
return;
|
|
|
|
// Occlude if necessary
|
|
if (occludeName)
|
|
{
|
|
if (ContactManager* contactManager = getContactManager())
|
|
{
|
|
FilterHumanoidNameOcclusion filter(shared_from(this));
|
|
if (!camera.isPartVisibleFast(*head, *contactManager, &filter))
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Update display text
|
|
if (displayText != getParent()->getName())
|
|
{
|
|
displayText = getParent()->getName();
|
|
|
|
if (ProfanityFilter::ContainsProfanity(displayText))
|
|
displayText = "";
|
|
}
|
|
|
|
// Compute name tag color
|
|
Color3 nameTagColor = teams ? teams->getTeamColorForHumanoid(this) : Color3::white();
|
|
|
|
// Compute name/health fade
|
|
float nameAlpha = 1.0f;
|
|
float healthAlpha = 1.0f;
|
|
|
|
if (localHumanoidDistance > (0.9f * localNameDisplayDistance))
|
|
nameAlpha = std::max(((localNameDisplayDistance - localHumanoidDistance) / (0.1f * localNameDisplayDistance)), 0.0f);
|
|
|
|
if (localHumanoidDistance > (0.9f * localHealthDisplayDistance))
|
|
healthAlpha = std::max(((localHealthDisplayDistance - localHumanoidDistance) / (0.1f * localHealthDisplayDistance)), 0.0f);
|
|
|
|
// Render!
|
|
if (adorn->isVR())
|
|
{
|
|
// Compute font size
|
|
float fontSize = 18.0f;
|
|
|
|
CoordinateFrame cframe(aboveHead);
|
|
|
|
Vector3 heading = Vector3(look.x, 0, look.z);
|
|
|
|
cframe.lookAt(aboveHead - heading);
|
|
|
|
// Keep constant world space size, with the height of text being ~1 stud
|
|
cframe.rotation *= 1 / fontSize;
|
|
|
|
AdornSurface adornView(adorn, Rect2D(), cframe);
|
|
renderBillboardImpl(&adornView, Vector2(0, 0), fontSize, nameTagColor, nameAlpha, healthAlpha);
|
|
}
|
|
else
|
|
{
|
|
// Project to screen
|
|
Vector3 screenLoc = camera.project(aboveHead);
|
|
if (screenLoc.z == std::numeric_limits<float>::infinity())
|
|
return;
|
|
|
|
// Compute font size
|
|
float fontSize = 12.0f;
|
|
if (distance < 20.0f)
|
|
fontSize = 24.0f;
|
|
else if (distance < 50.0f)
|
|
fontSize = 18.0f;
|
|
|
|
renderBillboardImpl(adorn, screenLoc.xy(), fontSize, nameTagColor, nameAlpha, healthAlpha);
|
|
}
|
|
}
|
|
|
|
void Humanoid::renderBillboardImpl(Adorn* adorn, const Vector2& screenLoc, float fontSize, const Color3& nameTagColor, float nameAlpha, float healthAlpha)
|
|
{
|
|
float width = fontSize * 4;
|
|
|
|
if (nameAlpha > 0)
|
|
{
|
|
static float alpha = 0.5f;
|
|
Color4 strokeColor;
|
|
|
|
static float rFactor = 0.25f;
|
|
static float gFactor = 0.21f;
|
|
static float bFactor = 0.0722f;
|
|
float colorY = rFactor * nameTagColor.r + gFactor * nameTagColor.g + bFactor * nameTagColor.b;
|
|
float nameColorL = (116.0f * std::pow(colorY, (1.0f/3.0f)) - 16.0f) / 100.0f;
|
|
|
|
static float factor = 0.52f;
|
|
|
|
if (nameColorL >= factor)
|
|
strokeColor = Color4(49.0f/255.0f, 49.0f/255.0f, 49.0f/255.0f, alpha * nameAlpha);
|
|
else
|
|
strokeColor = Color4(227.0f/255.0f, 227.0f/255.0f, 227.0f/255.0f, alpha * nameAlpha);
|
|
|
|
// Name
|
|
Vector2 pos = screenLoc;
|
|
pos.y -= 2.0f;
|
|
adorn->drawFont2D(displayText, pos, fontSize, false, Color4(nameTagColor, nameAlpha), strokeColor, Text::FONT_SOURCESANS, Text::XALIGN_CENTER, Text::YALIGN_BOTTOM);
|
|
}
|
|
|
|
// Health
|
|
if (maxHealth != 0 && healthAlpha > 0)
|
|
{
|
|
float relativeHealth = G3D::clamp(health/maxHealth, 0, 1);
|
|
|
|
float healthHeight = 2;
|
|
float strokeWidth = 2;
|
|
Vector2 topLeft(screenLoc.x - 0.5f * width, screenLoc.y + 2);
|
|
float middle = topLeft.x + (width) * relativeHealth;
|
|
|
|
Rect healthRect(topLeft, Vector2(middle, topLeft.y + healthHeight));
|
|
|
|
Rect backRect[3] =
|
|
{
|
|
Rect(topLeft + Vector2(0, -strokeWidth), topLeft + Vector2(width, healthHeight + strokeWidth)),
|
|
Rect(topLeft + Vector2(-strokeWidth, -strokeWidth/2.0f), topLeft + Vector2(0, healthHeight + strokeWidth/2.0f)),
|
|
Rect(topLeft + Vector2(width, -strokeWidth/2.0f), topLeft + Vector2(width + strokeWidth, healthHeight + strokeWidth/2.0f))
|
|
};
|
|
|
|
Rect strokeRect[4] =
|
|
{
|
|
Rect(topLeft + Vector2(0, -(strokeWidth*1.5f)), topLeft + Vector2(width, -strokeWidth)),
|
|
Rect(topLeft + Vector2(0, healthHeight + strokeWidth), topLeft + Vector2(width, healthHeight + (strokeWidth*1.5f))),
|
|
Rect(topLeft + Vector2(-(strokeWidth*1.5), -(strokeWidth*1.0f)), topLeft + Vector2(0, healthHeight + (strokeWidth*1.0f))),
|
|
Rect(topLeft + Vector2(width, -(strokeWidth*1.0f)), topLeft + Vector2(width + strokeWidth*1.5f, healthHeight + (strokeWidth*1.0f)))
|
|
};
|
|
|
|
// Calc bar color
|
|
static const Color3 healthColors[] =
|
|
{
|
|
Color3(255.0f/255.0f, 28.0f/255.0f, 0.0f/255.0f),
|
|
Color3(250.0f/255.0f, 235.0f/255.0f, 0.0f/255.0f),
|
|
Color3(27.0f/255.0f, 252.0f/255.0f, 107.0f/255.0f)
|
|
};
|
|
|
|
Color4 barColor;
|
|
|
|
if (relativeHealth <= 0.1f)
|
|
{
|
|
barColor = Color4(healthColors[0], healthAlpha);
|
|
}
|
|
else if (relativeHealth >= 0.8f)
|
|
{
|
|
barColor = Color4(healthColors[2], healthAlpha);
|
|
}
|
|
else
|
|
{
|
|
static float samplePoints[] = { 0.1f, 0.5f, 0.8f };
|
|
Vector3 numeratorSum;
|
|
float denominatorSum = 0;
|
|
bool found = false;
|
|
for (int i = 0; i < 3; i ++)
|
|
{
|
|
float distance = fabsf(relativeHealth - samplePoints[i]);
|
|
if (distance <= 0.0f)
|
|
{
|
|
barColor = Color4(healthColors[i], healthAlpha);
|
|
found = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
float wi = 1.0f / (distance * distance);
|
|
numeratorSum = numeratorSum + wi * Vector3(healthColors[i].r, healthColors[i].g, healthColors[i].b);
|
|
denominatorSum = denominatorSum + wi;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
numeratorSum = numeratorSum / denominatorSum;
|
|
barColor = Color4(numeratorSum.x, numeratorSum.y, numeratorSum.z, healthAlpha);
|
|
}
|
|
}
|
|
|
|
adorn->setIgnoreTexture(true);
|
|
|
|
Color4 backGrey(101.0f/255.0f, 100.0f/255.0f, 100.0f/255.0f, healthAlpha * 0.5);
|
|
Color4 backGreyOpaque(100.0f/255.0f, 100.0f/255.0f, 100.0f/255.0f, healthAlpha);
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
adorn->rect2d(strokeRect[i].toRect2D(), Vector2::one(), Vector2::one(), barColor, strokeRect[i].toRect2D());
|
|
}
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
Color4 color = (i == 0) ? backGrey : backGreyOpaque;
|
|
|
|
adorn->rect2d(backRect[i].toRect2D(), Vector2::one(), Vector2::one(), color, backRect[i].toRect2D());
|
|
}
|
|
|
|
adorn->rect2d(healthRect.toRect2D(), Vector2::one(), Vector2::one(), barColor, healthRect.toRect2D());
|
|
adorn->setIgnoreTexture(false);
|
|
}
|
|
}
|
|
|
|
Vector3 Humanoid::render3dSortedPosition() const
|
|
{
|
|
if (const PartInstance *foundHead = getHeadSlow())
|
|
{
|
|
return foundHead->getTranslationUi();
|
|
}
|
|
return Vector3();
|
|
}
|
|
|
|
void Humanoid::render3dSortedAdorn(Adorn* adorn)
|
|
{
|
|
if (!ownedByLocalPlayer)
|
|
{
|
|
if (FFlag::HumanoidRenderBillboard)
|
|
renderBillboard(adorn, *adorn->getCamera());
|
|
else if (FFlag::HumanoidRenderBillboardVR && adorn->isVR())
|
|
renderBillboard(adorn, *adorn->getCamera());
|
|
else
|
|
renderMultiplayer(adorn, *adorn->getCamera());
|
|
}
|
|
}
|
|
|
|
static void collectAllDescendantCharacterPrims(shared_ptr<RBX::Instance> descendant, std::vector<Primitive*>& primitives)
|
|
{
|
|
PartInstance *part = Instance::fastDynamicCast<PartInstance>(descendant.get());
|
|
if (part)
|
|
{
|
|
primitives.push_back(part->getPartPrimitive());
|
|
}
|
|
}
|
|
|
|
void Humanoid::getPrimitives(std::vector<Primitive*>& primitives) const
|
|
{
|
|
// Get prims of all parts in character model
|
|
const ModelInstance *m = getConstCharacterFromHumanoid(this);
|
|
if (!m) return;
|
|
|
|
m->visitDescendants(boost::bind(&collectAllDescendantCharacterPrims, _1, boost::ref(primitives)));
|
|
}
|
|
|
|
static void collectAllDescendantCharacterParts(shared_ptr<RBX::Instance> descendant, std::vector<PartInstance*>& parts)
|
|
{
|
|
if (PartInstance *part = Instance::fastDynamicCast<PartInstance>(descendant.get()))
|
|
{
|
|
parts.push_back(part);
|
|
}
|
|
}
|
|
|
|
void Humanoid::getParts(std::vector<PartInstance*>& parts) const
|
|
{
|
|
// Get all parts in character model
|
|
const ModelInstance *m = getConstCharacterFromHumanoid(this);
|
|
if (!m) return;
|
|
|
|
m->visitDescendants(boost::bind(&collectAllDescendantCharacterParts, _1, boost::ref(parts)));
|
|
}
|
|
|
|
const CoordinateFrame Humanoid::getRenderLocation()
|
|
{
|
|
PartInstance* foundHead = NULL;
|
|
|
|
if (getDead())
|
|
{
|
|
foundHead = getVisibleTorsoSlow();
|
|
} else {
|
|
foundHead = getTorsoSlow();
|
|
}
|
|
|
|
if (foundHead) {
|
|
CoordinateFrame frame = foundHead->calcRenderingCoordinateFrame();
|
|
frame = frame * headTorsoOffset; // apply offset from root CFrame to approximate head location
|
|
frame = frame * cameraOffset;
|
|
return frame;
|
|
}
|
|
else
|
|
return CoordinateFrame();
|
|
}
|
|
|
|
const Vector3 Humanoid::getRenderSize()
|
|
{
|
|
if(ModelInstance* character = getCharacterFromHumanoid(this))
|
|
return character->calculateModelSize();
|
|
else
|
|
return Vector3(0,0,0);
|
|
}
|
|
|
|
const CoordinateFrame Humanoid::getLocation()
|
|
{
|
|
if (PartInstance* foundHead = getHeadSlow()) {
|
|
return foundHead->getCoordinateFrame();
|
|
}
|
|
else {
|
|
return CoordinateFrame();
|
|
}
|
|
}
|
|
|
|
void Humanoid::getSelectionIgnorePrimitives(std::vector<const Primitive*>& primitives)
|
|
{
|
|
// TODO: This cast blows. We should refactor getPrimitives to use back_insert_iterator or a for_each pattern...
|
|
std::vector<Primitive*>& temp = reinterpret_cast<std::vector<Primitive*>&>(primitives);
|
|
|
|
getPrimitives(temp);
|
|
}
|
|
|
|
void Humanoid::getCameraIgnorePrimitives(std::vector<const Primitive*>& primitives)
|
|
{
|
|
getSelectionIgnorePrimitives(primitives);
|
|
|
|
FilteredSelection<PartInstance>* sel = ServiceProvider::create< FilteredSelection<PartInstance> >(this);
|
|
if (sel) {
|
|
for(std::vector<PartInstance *>::const_iterator iter = sel->begin(); iter != sel->end(); iter++) {
|
|
primitives.push_back((*iter)->getPartPrimitive());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void setLocalTransparencyModifierDecendants(shared_ptr<RBX::Instance> descendant, float transparencyModifier)
|
|
{
|
|
PartInstance *part = Instance::fastDynamicCast<PartInstance>(descendant.get());
|
|
if (part)
|
|
{
|
|
if (Instance::fastDynamicCast<Tool>(part->getParent()) == NULL)
|
|
part->setLocalTransparencyModifier(transparencyModifier); // don't ever make tools transparent.
|
|
}
|
|
else if (Decal* d = Instance::fastDynamicCast<Decal>(descendant.get()))
|
|
{
|
|
d->setTransparency(transparencyModifier);
|
|
}
|
|
}
|
|
|
|
void Humanoid::setLocalTransparencyModifier(float transparencyModifier) const
|
|
{
|
|
// Get prims of all parts in character model
|
|
const ModelInstance *m = getConstCharacterFromHumanoid(this);
|
|
if (!m) return;
|
|
|
|
m->visitDescendants(boost::bind(&setLocalTransparencyModifierDecendants, _1, transparencyModifier));
|
|
}
|
|
|
|
void Humanoid::tellCameraNear(float distance) const
|
|
{
|
|
float localTransparencyModifier = 0.0f;
|
|
if (isDistanceFirstPerson(distance) && !isTransitioning())
|
|
localTransparencyModifier = 1.0f;
|
|
else if (distance < (1.5f * firstPersonCutoff()))
|
|
localTransparencyModifier = std::min(1.0f - (distance - (1.0f * firstPersonCutoff())) / (0.5f * firstPersonCutoff()), 1.0f);
|
|
|
|
setLocalTransparencyModifier(localTransparencyModifier);
|
|
}
|
|
|
|
void Humanoid::tellCameraSubjectDidChange(shared_ptr<Instance> oldSubject, shared_ptr<Instance> newSubject) const
|
|
{
|
|
if( (this == oldSubject.get()) && (this != newSubject.get()) )
|
|
setLocalTransparencyModifier(0.0f); // insures humanoid's character is not invisible on camera subject change
|
|
}
|
|
|
|
void Humanoid::tellCursorOver(float cursorOffset) const
|
|
{
|
|
std::vector<Primitive*> primitives;
|
|
getPrimitives(primitives);
|
|
|
|
float localTransparencyModifier = 0.0f;
|
|
if (isFirstPerson() && !isTransitioning())
|
|
localTransparencyModifier = 1.0f;
|
|
else if (cursorOffset < 0.5f)
|
|
localTransparencyModifier = (0.5f - cursorOffset);
|
|
|
|
for (size_t i = 0; i < primitives.size(); ++i) {
|
|
PartInstance* part = PartInstance::fromPrimitive(primitives[i]);
|
|
if (Instance::fastDynamicCast<Tool>(part->getParent()) == NULL)
|
|
{
|
|
part->setLocalTransparencyModifier(localTransparencyModifier); // don't ever make tools transparent.
|
|
}
|
|
}
|
|
}
|
|
|
|
void Humanoid::setFirstPersonRotationalVelocity(const Vector3& desiredLook, bool firstPersonOn)
|
|
{
|
|
if (!firstPersonOn || !autorotate || (DFFlag::FixedSitFirstPersonMove && sit))
|
|
{
|
|
// FYI: when sitting in not-anchored Seat objects, this check is not necessary since the rotation is reverted back later this heartbeat
|
|
setWalkAngleError(0.0f);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (PartInstance* foundTorso = this->getTorsoSlow())
|
|
{
|
|
Vector3 adjustedLook = desiredLook;
|
|
UserInputService* uis = ServiceProvider::find<UserInputService>(this);
|
|
if (uis && DFFlag::RotateFirstPersonInVR && uis->getVREnabled())
|
|
{
|
|
// Rotate desiredLook by the head yaw rotation
|
|
Matrix3 headRotation = uis->getUserHeadCFrame().rotation;
|
|
adjustedLook = headRotation * desiredLook;
|
|
}
|
|
|
|
Vector3 look = foundTorso->getCoordinateFrame().lookVector();
|
|
float lookAng = Math::getHeading(look);
|
|
float desiredLookAng = Math::getHeading(adjustedLook);
|
|
|
|
float dl = Math::radWrap(desiredLookAng - lookAng);
|
|
|
|
if (currentState.get() &&
|
|
currentState->getStateType() != HUMAN::SWIMMING)
|
|
{
|
|
CoordinateFrame currentPosition = foundTorso->getCoordinateFrame();
|
|
Math::rotateAboutYGlobal(currentPosition, dl);
|
|
if (autorotate) {
|
|
foundTorso->setPhysics(currentPosition);
|
|
}
|
|
}
|
|
|
|
if (fabs(dl) < .1f) dl = 0.0f; // 3 degrees of slosh, so we don't spam small updates over the network.
|
|
|
|
setWalkAngleError(dl); // warning - big hack - setting walk rotational velocity to the angle error!
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace Reflection {
|
|
template<>
|
|
EnumDesc<RBX::Humanoid::NameOcclusion>::EnumDesc()
|
|
:EnumDescriptor("NameOcclusion")
|
|
{
|
|
addPair(RBX::Humanoid::NAME_OCCLUSION_ALL, "OccludeAll");
|
|
addPair(RBX::Humanoid::NAME_OCCLUSION_ENEMY, "EnemyOcclusion");
|
|
addPair(RBX::Humanoid::NAME_OCCLUSION_NONE, "NoOcclusion");
|
|
}
|
|
|
|
template<>
|
|
EnumDesc<RBX::Humanoid::HumanoidDisplayDistanceType>::EnumDesc()
|
|
:EnumDescriptor("HumanoidDisplayDistanceType")
|
|
{
|
|
addPair(RBX::Humanoid::HUMANOID_DISPLAY_DISTANCE_TYPE_VIEWER, "Viewer");
|
|
addPair(RBX::Humanoid::HUMANOID_DISPLAY_DISTANCE_TYPE_SUBJECT, "Subject");
|
|
addPair(RBX::Humanoid::HUMANOID_DISPLAY_DISTANCE_TYPE_NONE, "None");
|
|
}
|
|
|
|
template<>
|
|
EnumDesc<RBX::Humanoid::HumanoidRigType>::EnumDesc()
|
|
:EnumDescriptor("HumanoidRigType")
|
|
{
|
|
addPair(RBX::Humanoid::HUMANOID_RIG_TYPE_R6, "R6");
|
|
addPair(RBX::Humanoid::HUMANOID_RIG_TYPE_R15, "R15");
|
|
}
|
|
|
|
|
|
template<>
|
|
RBX::Humanoid::NameOcclusion& Variant::convert<RBX::Humanoid::NameOcclusion>(void)
|
|
{
|
|
return genericConvert<RBX::Humanoid::NameOcclusion>();
|
|
}
|
|
|
|
template<>
|
|
EnumDesc<RBX::Humanoid::Status>::EnumDesc()
|
|
:EnumDescriptor("Status")
|
|
{
|
|
addPair(RBX::Humanoid::POISON_STATUS, "Poison");
|
|
addPair(RBX::Humanoid::CONFUSION_STATUS, "Confusion");
|
|
}
|
|
|
|
template<>
|
|
RBX::Humanoid::Status& Variant::convert<RBX::Humanoid::Status>(void)
|
|
{
|
|
return genericConvert<RBX::Humanoid::Status>();
|
|
}
|
|
} // namespace Reflection
|
|
|
|
template<>
|
|
bool RBX::StringConverter<RBX::Humanoid::NameOcclusion>::convertToValue(const std::string& text, RBX::Humanoid::NameOcclusion& value)
|
|
{
|
|
if(text.find("OccludeAll")){
|
|
value = RBX::Humanoid::NAME_OCCLUSION_ALL;
|
|
return true;
|
|
}
|
|
if(text.find("EnemyOcclusion")){
|
|
value = RBX::Humanoid::NAME_OCCLUSION_ENEMY;
|
|
return true;
|
|
}
|
|
if(text.find("NoOcclusion")){
|
|
value = RBX::Humanoid::NAME_OCCLUSION_NONE;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template<>
|
|
bool RBX::StringConverter<RBX::Humanoid::Status>::convertToValue(const std::string& text, RBX::Humanoid::Status& value)
|
|
{
|
|
if(text.find("Poison")){
|
|
value = RBX::Humanoid::POISON_STATUS;
|
|
return true;
|
|
}
|
|
if(text.find("Confusion")){
|
|
value = RBX::Humanoid::CONFUSION_STATUS;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Humanoid::setupAnimator()
|
|
{
|
|
if(!animator)
|
|
{
|
|
animator = shared_from<Animator>(findFirstChildOfType<Animator>());
|
|
|
|
if (!animator)
|
|
{
|
|
animator = shared_ptr<Animator>(Creatable<Instance>::create<RBX::Animator>(this));
|
|
if (getParent()->getClassNameStr() == "Model")
|
|
animator->setParent(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
Animator* Humanoid::getAnimator()
|
|
{
|
|
setupAnimator();
|
|
RBXASSERT(animator);
|
|
return animator.get();
|
|
}
|
|
|
|
shared_ptr<const Reflection::ValueArray> Humanoid::getPlayingAnimationTracks()
|
|
{
|
|
return getAnimator()->getPlayingAnimationTracks();
|
|
}
|
|
|
|
shared_ptr<Instance> Humanoid::loadAnimation(shared_ptr<Instance> instance)
|
|
{
|
|
return getAnimator()->loadAnimation(instance);
|
|
}
|
|
|
|
bool Humanoid::getStateTransitionEnabled(HUMAN::StateType state)
|
|
{
|
|
if (state < HUMAN::FALLING_DWN || state >= HUMAN::NUM_STATE_TYPES)
|
|
{
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Invalid state passed to GetStateEnabled.");
|
|
}
|
|
else
|
|
{
|
|
return stateTransitionEnabled[state];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Humanoid::setStateTransitionEnabled(HUMAN::StateType state, bool enabled)
|
|
{
|
|
if (state < HUMAN::FALLING_DWN || state >= HUMAN::NUM_STATE_TYPES)
|
|
{
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Invalid state passed to SetStateEnabled.");
|
|
}
|
|
else
|
|
{
|
|
stateTransitionEnabled[state] = enabled;
|
|
stateEnabledChangedSignal(state, enabled);
|
|
}
|
|
}
|
|
|
|
bool Humanoid::computeNearlyTouched()
|
|
{
|
|
// This will not detect close-by objects accelerated from zero to high speed
|
|
PartInstance* part = getVisibleTorsoSlow();
|
|
Primitive* prim = part ? part->getPartPrimitive() : NULL;
|
|
if ( prim )
|
|
{
|
|
int nearCount = 0;
|
|
for ( int i = 0; i < prim->getNumContacts(); ++i)
|
|
{
|
|
Contact* contact = prim->getContact(i);
|
|
RBXASSERT( contact );
|
|
if ( !contact->isInContact() )
|
|
++nearCount;
|
|
}
|
|
if ( nearCount > numContacts )
|
|
{
|
|
numContacts = nearCount;
|
|
return true;
|
|
}
|
|
numContacts = nearCount;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Humanoid::isLegalForClientToChange( const Reflection::PropertyDescriptor& desc ) const
|
|
{
|
|
if (desc == propJump)
|
|
return true;
|
|
if (desc == propWalkDirection)
|
|
return true;
|
|
if (desc == propWalkToPoint)
|
|
return true;
|
|
if (desc == propWalkToPart)
|
|
return true;
|
|
if (desc == propLuaMoveDirection)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool Humanoid::CheckTorso()
|
|
{
|
|
Primitive* torso = getTorsoPrimitiveSlow();
|
|
if (!torso || !world)
|
|
return false;
|
|
setPrimitive(0, torso);
|
|
setPrimitive(1, world->getGroundPrimitive());
|
|
world->insertJoint(this);
|
|
onCFrameChangedConnection = PartInstance::fromPrimitive(torso)->onDemandWrite()->cframeChangedFromReflectionSignal.connect(boost::bind(&Humanoid::onCFrameChangedFromReflection, this));
|
|
torsoArrived = true;
|
|
return true;
|
|
}
|
|
|
|
int Humanoid::getPlayerId()
|
|
{
|
|
if (Network::Player* player = Network::Players::getPlayerFromCharacter(getParent()))
|
|
{
|
|
return player->getUserID();
|
|
}
|
|
|
|
return INT_MAX;
|
|
}
|
|
|
|
Vector3 findClosestTorsoCornerToTeleportGoal(const Vector3& torsoSize, const Vector3& torsoLocalSpaceGoal)
|
|
{
|
|
// Look through 4 corners of the Humanoid Torso in the XZ Plane and find the furthest corner.
|
|
// If only we had Shape Casts
|
|
Vector3 closestCorner(0,0,0);
|
|
|
|
for (size_t i = 0; i < 4; i++)
|
|
{
|
|
int xSign = 1;
|
|
int zSign = 1;
|
|
if (i > 2)
|
|
xSign = -1;
|
|
if (i % 2)
|
|
zSign = -1;
|
|
|
|
Vector3 currentCorner(xSign*torsoSize.x/2, 0 , zSign*torsoSize.z/2);
|
|
|
|
if (currentCorner.dot(torsoLocalSpaceGoal) > closestCorner.dot(torsoLocalSpaceGoal))
|
|
{
|
|
closestCorner = currentCorner;
|
|
}
|
|
}
|
|
|
|
return closestCorner;
|
|
}
|
|
|
|
bool foundShortestDistanceFromCornerToObject(PartInstance* floorPart, Humanoid* charHumanoid, const Vector3& deltaDisplacementGoal, const Vector3& torsoSpaceClosestCorner, Vector3& allowedDisplacementOut)
|
|
{
|
|
bool foundShorterDelta = false;
|
|
World* characterWorld = charHumanoid->getWorld();
|
|
|
|
if ( characterWorld == NULL )
|
|
return false;
|
|
|
|
RBX::ContactManager* contactManager = characterWorld->getContactManager();
|
|
CoordinateFrame torsoCFrame = charHumanoid->getTorsoPrimitiveFast()->getCoordinateFrame();
|
|
allowedDisplacementOut = deltaDisplacementGoal;
|
|
|
|
for (size_t i = 0; i < 3; i++)
|
|
{
|
|
int xSign = 1;
|
|
int zSign = 1;
|
|
|
|
// Find adjacent corners by multiplying local X and Z coordinates by -1 in alternating order
|
|
if (i == 1)
|
|
zSign = -1;
|
|
if (i == 2)
|
|
xSign = -1;
|
|
|
|
Vector3 closestCornerAdjacents(xSign*torsoSpaceClosestCorner.x, 0 , zSign*torsoSpaceClosestCorner.z);
|
|
Vector3 worldSpaceCurrentCorner = torsoCFrame.pointToWorldSpace(closestCornerAdjacents);
|
|
|
|
|
|
// Raycast Setup
|
|
RbxRay newRay(worldSpaceCurrentCorner, deltaDisplacementGoal);
|
|
std::vector<const Primitive*> dummy;
|
|
Vector3 hitLocationOut;
|
|
FilterSameAssembly filterHumanoids(shared_from(charHumanoid->getTorsoFast()));
|
|
FilterSameAssembly filterFloor(shared_from(floorPart));
|
|
MergedFilter combinedFilter(&filterHumanoids, &filterFloor);
|
|
FilterHumanoidParts humanoidFilter;
|
|
|
|
// Raycast
|
|
Primitive* hitPrim = contactManager->getHit(newRay,
|
|
&dummy,
|
|
&combinedFilter,
|
|
hitLocationOut);
|
|
|
|
if (hitPrim)
|
|
{
|
|
Vector3 foundDisplacementAllowed = hitLocationOut - worldSpaceCurrentCorner;
|
|
|
|
// if foundDisplacementAllowed is shorter than last allowedDisplacementOut, we found an obstacle even earlier in the way.
|
|
if ((foundDisplacementAllowed).squaredMagnitude() < allowedDisplacementOut.squaredMagnitude())
|
|
{
|
|
foundShorterDelta = true;
|
|
allowedDisplacementOut = foundDisplacementAllowed;
|
|
}
|
|
}
|
|
}
|
|
|
|
return foundShorterDelta;
|
|
}
|
|
|
|
void Humanoid::truncateDisplacementIfObstacle(PartInstance* floorPart, Vector3& newCharPosition, const Vector3& previousCharPosition)
|
|
{
|
|
Primitive* torsoPrim = this->getTorsoPrimitiveFast();
|
|
if (torsoPrim)
|
|
{
|
|
// When the Player does not simulate the floor, the floor teleports around based on Network updates
|
|
// This may cause the player to teleport around obstacles, so we predict if there are obstacles in the path and limit the teleportation based
|
|
// on obstacles
|
|
Vector3 deltaDisplacement = newCharPosition - previousCharPosition;
|
|
if (deltaDisplacement.squaredMagnitude() > 0.0f)
|
|
{
|
|
CoordinateFrame torsoCFrame = torsoPrim->getCoordinateFrame();
|
|
PartInstance* torso = PartInstance::fromPrimitive(torsoPrim);
|
|
|
|
// Find appropriate origin of Raycast
|
|
Vector3 closestCorner = findClosestTorsoCornerToTeleportGoal(torso->getPartSizeUi(), torsoCFrame.pointToObjectSpace(newCharPosition));
|
|
|
|
// Find Closest Obstacle To Goal
|
|
Vector3 maxDisplacementAllowed;
|
|
if (foundShortestDistanceFromCornerToObject(floorPart, this, deltaDisplacement, closestCorner, maxDisplacementAllowed))
|
|
{
|
|
newCharPosition = previousCharPosition + deltaDisplacement.unit() * sqrt((maxDisplacementAllowed.dot(maxDisplacementAllowed)));
|
|
FASTLOG3F(FLog::HumanoidFloorProcess, "Obstacle Found, Moving to: %4.4f, %4.4f, %4.4f", newCharPosition.x, newCharPosition.y, newCharPosition.z);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Vector3 Humanoid::getSimulatedFrictionVelocityOffset(PartInstance* floorPart, Vector3& newCharPosition, const Vector3& previousCharPosition, const Vector3& charPosInFloorSpace, const RBX::Velocity& previousFloorVelocity, float& netDt)
|
|
{
|
|
Primitive* floorPrim = floorPart->getPartPrimitive();
|
|
if (floorPrim && (getCurrentFloorFilterPhase() >= Assembly::NoSim_SendIfSim) && floorPrim->getWorld())
|
|
{
|
|
Assembly* floorAssy = floorPrim->getAssembly();
|
|
Primitive* floorRootPrim = NULL;
|
|
if (floorAssy)
|
|
{
|
|
floorRootPrim = floorAssy->getAssemblyPrimitive();
|
|
if (floorRootPrim == NULL)
|
|
{
|
|
return Vector3(0,0,0);
|
|
}
|
|
}
|
|
|
|
|
|
Vector3 newLinearVelocityAtGoal = (floorRootPrim->getBody()->getVelocity().linearVelocityAtOffset(charPosInFloorSpace));
|
|
Vector3 oldLinearVelocityOnFloor = previousFloorVelocity.linearVelocityAtOffset(charPosInFloorSpace);
|
|
Vector3 deltaVelocity(newLinearVelocityAtGoal - oldLinearVelocityOnFloor);
|
|
|
|
float multiplyer = 0.0f;
|
|
if (getWorld()->getUsingPGSSolver())
|
|
multiplyer = RBX::HUMAN::HumanoidState::runningKMovePForPGS() * netDt * (1000.0f/(DFInt::HumanoidFloorManualFrictionVelocityMultValue));
|
|
else
|
|
multiplyer = RBX::HUMAN::HumanoidState::runningKMoveP() * netDt * (1000.0f/(DFInt::HumanoidFloorManualFrictionVelocityMultValue));
|
|
|
|
Vector3 approxXZForce(deltaVelocity.x * multiplyer , 0 , deltaVelocity.z * multiplyer);
|
|
float forceMagnitudeOverMax = (approxXZForce.magnitude() / (RBX::HUMAN::HumanoidState::maxLinearGroundMoveForce()));
|
|
|
|
|
|
// If the approximate force application by this "Network Update" is larger than
|
|
// Max XZ Force allowed, then we have to modify our position update.
|
|
Vector3 deltaDisplacement = newCharPosition - previousCharPosition;
|
|
if (forceMagnitudeOverMax > 1.0f && deltaDisplacement.squaredMagnitude() > 0.0f)
|
|
{
|
|
Vector3 velocityOffset;
|
|
// If acceleration is in Displacement Dir
|
|
Vector3 deltaVelocity = newLinearVelocityAtGoal - oldLinearVelocityOnFloor;
|
|
if ((deltaVelocity).dot(deltaDisplacement) >= 0.0f)
|
|
{
|
|
velocityOffset = -(newCharPosition - previousCharPosition).unit() * (deltaVelocity - deltaVelocity*(1/forceMagnitudeOverMax)).magnitude();
|
|
}
|
|
else
|
|
{
|
|
velocityOffset = (newCharPosition - previousCharPosition).unit() * (deltaVelocity - deltaVelocity*(1/forceMagnitudeOverMax)).magnitude();
|
|
}
|
|
FASTLOG1F(FLog::HumanoidFloorProcess, "forceMagnitudeOverMax: %4.4f", forceMagnitudeOverMax);
|
|
FASTLOG3F(FLog::HumanoidFloorProcess, "Accelerating too fast, Applying Velocity: %4.4f, %4.4f, %4.4f", velocityOffset.x, velocityOffset.y, velocityOffset.z);
|
|
return velocityOffset;
|
|
}
|
|
}
|
|
return Vector3(0,0,0);
|
|
}
|
|
|
|
void Humanoid::updateNetworkFloorPosition(PartInstance* floorPart, CoordinateFrame& previousFloorPosition, RBX::Velocity& lastFloorVelocity, float& netDt)
|
|
{
|
|
Body* rootHumanBody = getRootBodyFast();
|
|
if (floorPart && getWorld() && rootHumanBody && validateNetworkUpdateDistance(floorPart, previousFloorPosition, netDt))
|
|
{
|
|
Primitive* floorPrimitive = floorPart->getPartPrimitive();
|
|
if (floorPrimitive->getWorld() == NULL)
|
|
return;
|
|
|
|
Vector3 charPosInObjectSpace = previousFloorPosition.pointToObjectSpace(rootHumanBody->getCoordinateFrame().translation);
|
|
Vector3 charPosInWorldSpaceGoal = floorPrimitive->getCoordinateFrame().pointToWorldSpace(charPosInObjectSpace);
|
|
Matrix3 deltaRotation = (floorPrimitive->getCoordinateFrame().rotation * previousFloorPosition.rotation.transpose());
|
|
// No Update needed if no rotation or displacement needs to be applied.
|
|
// TODO: Fix this part for optimization. For some reason this causes many false
|
|
// early outs when in BufferZone.
|
|
if ((floorPrimitive->getCoordinateFrame().translation - previousFloorPosition.translation).squaredMagnitude() < epsilonDisplacementSqr
|
|
&& (floorPrimitive->getBody()->getVelocity() - lastFloorVelocity).linear.squaredMagnitude() < epsilonDisplacementSqr)
|
|
{
|
|
Vector3 eulerDeltaRot;
|
|
deltaRotation.toEulerAnglesXYZ(eulerDeltaRot.x, eulerDeltaRot.y, eulerDeltaRot.z);
|
|
|
|
if ((floorPrimitive->getBody()->getVelocity() - lastFloorVelocity).rotational.squaredMagnitude() < epsilonDisplacementSqr && eulerDeltaRot.squaredMagnitude() < epsilonDisplacementSqr)
|
|
{
|
|
FASTLOG1F(FLog::HumanoidFloorProcess, "Floor movement too small: %4.4f", (floorPrimitive->getCoordinateFrame().translation - previousFloorPosition.translation).squaredMagnitude());
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
// Check for Obstacles
|
|
truncateDisplacementIfObstacle(floorPart, charPosInWorldSpaceGoal, rootHumanBody->getCoordinateFrame().translation);
|
|
|
|
|
|
// Grab Y Rotation ONLY
|
|
Vector3 eulAngleRotation(0,0,0);
|
|
deltaRotation.toEulerAnglesYZX(eulAngleRotation.y, eulAngleRotation.z, eulAngleRotation.x);
|
|
Matrix3 deltaRotationAboutY = G3D::Matrix3::fromEulerAnglesYZX(eulAngleRotation.y, 0, 0);
|
|
|
|
// Create new Coordinate Frame
|
|
CoordinateFrame newFrame(rootHumanBody->getCoordinateFrame().rotation * deltaRotationAboutY);
|
|
newFrame = newFrame + charPosInWorldSpaceGoal;
|
|
|
|
Vector3 deltaDisplacement = (floorPrimitive->getCoordinateFrame().translation - previousFloorPosition.translation);
|
|
FASTLOG3F(FLog::HumanoidFloorProcess, "Pre Friction Displacement: %4.4f, %4.4f, %4.4f", deltaDisplacement.x, deltaDisplacement.y, deltaDisplacement.z);
|
|
Vector3 addedVelocity = getSimulatedFrictionVelocityOffset(floorPart, charPosInWorldSpaceGoal, rootHumanBody->getCoordinateFrame().translation, charPosInObjectSpace, lastFloorVelocity, netDt);
|
|
|
|
getRootBodyFast()->setPv(PV(newFrame, rootHumanBody->getVelocity() + addedVelocity), *getTorsoPrimitiveFast());
|
|
Vector3 newCharPosInObjectSpace = floorPrimitive->getCoordinateFrame().pointToObjectSpace(rootHumanBody->getCoordinateFrame().translation);
|
|
FASTLOG3F(FLog::HumanoidFloorProcess, "Platform displaced: %4.4f, %4.4f, %4.4f", deltaDisplacement.x, deltaDisplacement.y, deltaDisplacement.z);
|
|
FASTLOG3F(FLog::HumanoidFloorProcess, "character Position in Object Space: %4.4f, %4.4f, %4.4f", charPosInObjectSpace.x, charPosInObjectSpace.y, charPosInObjectSpace.z);
|
|
FASTLOG3F(FLog::HumanoidFloorProcess, "New characterPosition in Object Space: %4.4f, %4.4f, %4.4f", newCharPosInObjectSpace.x, newCharPosInObjectSpace.y, newCharPosInObjectSpace.z);
|
|
}
|
|
}
|
|
|
|
Vector3 getMaxAllowedDisplacement(const Vector3& floorVelocity, const float& netDt)
|
|
{
|
|
return (floorVelocity) * netDt * (float)(DFInt::HumanoidFloorTeleportWeightValue/10.0f);
|
|
}
|
|
|
|
bool Humanoid::validateNetworkUpdateDistance(PartInstance* floorPart, CoordinateFrame& previousFloorPosition, float& netDt)
|
|
{
|
|
float bufferMoveDistanceSqr(25.0f); // Allows some motion when we think the platform is not moving.
|
|
Vector3 calculatedMaximumNetworkUpdate = getMaxAllowedDisplacement(floorPart->getVelocity().linear, netDt);
|
|
Vector3 floorPositionDelta = floorPart->getCoordinateFrame().translation - previousFloorPosition.translation;
|
|
|
|
if (floorPositionDelta.squaredLength() < (calculatedMaximumNetworkUpdate.squaredLength() + bufferMoveDistanceSqr))
|
|
return true;
|
|
else
|
|
{
|
|
FASTLOG3F(FLog::HumanoidFloorProcess, "Detected Teleport with floorPositionDelta: %4.4f, %4.4f, %4.4f", floorPositionDelta.x, floorPositionDelta.y, floorPositionDelta.z);
|
|
FASTLOG1F(FLog::HumanoidFloorProcess, "Maximum 'Teleport' Allowed: %4.4f", calculatedMaximumNetworkUpdate.magnitude());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Humanoid::updateFloorSimPhaseCharVelocity(Primitive* floorPrim)
|
|
{
|
|
if (floorPrim && primitiveIsLastFloor(floorPrim))
|
|
{
|
|
Assembly* floorAssembly = floorPrim->getAssembly();
|
|
if (floorAssembly)
|
|
{
|
|
int currentPhase = getCurrentFloorFilterPhase();
|
|
// If we suddenly when from not simulating to BufferZone (which happens often) we have to quickly speed the character up by the floor's velocity.
|
|
if (currentPhase != lastFilterPhase)
|
|
{
|
|
Primitive* torsoPrim = getTorsoPrimitiveFast();
|
|
if ( torsoPrim && getWorld() )
|
|
{
|
|
if ((lastFilterPhase >= (int)Assembly::NoSim_SendIfSim && currentPhase < (int)Assembly::NoSim_SendIfSim))
|
|
{
|
|
FASTLOG(FLog::HumanoidFloorProcess, "Updating Character Velocity based on floor Phase switch");
|
|
torsoPrim->setVelocity(torsoPrim->getPV().velocity + getHumanoidRelativeVelocityToPart(floorPrim));
|
|
}
|
|
else if ((lastFilterPhase < (int)Assembly::NoSim_SendIfSim && currentPhase >= (int)Assembly::NoSim_SendIfSim))
|
|
{
|
|
FASTLOG(FLog::HumanoidFloorProcess, "Updating Character Velocity based on floor Phase switch");
|
|
torsoPrim->setVelocity(torsoPrim->getPV().velocity - getHumanoidRelativeVelocityToPart(floorPrim));
|
|
}
|
|
}
|
|
}
|
|
lastFilterPhase = currentPhase;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool Humanoid::shouldNotApplyFloorVelocity(Primitive* floorPrim)
|
|
{
|
|
if (floorPrim && floorPrim->getAssembly() && floorPrim->getWorld() && primitiveIsLastFloor(floorPrim))
|
|
{
|
|
return (getCurrentFloorFilterPhase() >= Assembly::NoSim_SendIfSim && !floorPrim->getAnchoredProperty() );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Humanoid::primitiveIsLastFloor(Primitive* prim)
|
|
{
|
|
shared_ptr<PartInstance> savedFloorPart = getLastFloor();
|
|
|
|
Assembly* savedFloorAssembly = NULL;
|
|
if (savedFloorPart && savedFloorPart->getPartPrimitive())
|
|
{
|
|
savedFloorAssembly = savedFloorPart->getPartPrimitive()->getAssembly();
|
|
}
|
|
|
|
Assembly* checkPrimAssembly = NULL;
|
|
if (prim)
|
|
{
|
|
checkPrimAssembly = prim->getAssembly();
|
|
}
|
|
|
|
if (savedFloorAssembly && checkPrimAssembly && savedFloorAssembly->getAssemblyPrimitive() && checkPrimAssembly->getAssemblyPrimitive())
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const Velocity Humanoid::getHumanoidRelativeVelocityToPart(Primitive* floorPrim)
|
|
{
|
|
RBXASSERT(getWorld());
|
|
|
|
if (floorPrim)
|
|
{
|
|
RBXASSERT(floorPrim->getWorld());
|
|
if (floorPrim->getWorld())
|
|
{
|
|
|
|
World* world = getWorld();
|
|
Velocity throttledV = floorPrim->getPV().velocityAtPoint(getRootBodyFast()->getCoordinateFrame().translation); // this velocity could be inaccurate
|
|
float throttle = world->getEnvironmentSpeed();
|
|
|
|
throttledV.linear *= throttle;
|
|
throttledV.rotational *= throttle;
|
|
|
|
return throttledV;
|
|
}
|
|
}
|
|
return Velocity::zero();
|
|
}
|
|
|
|
int Humanoid::getCurrentFloorFilterPhase()
|
|
{
|
|
if (lastFloorPart.get())
|
|
{
|
|
if (rootFloorMechPart.get())
|
|
{
|
|
Assembly* rootAssembly = rootFloorMechPart->getPartPrimitive()->getAssembly();
|
|
if (rootAssembly)
|
|
{
|
|
return rootAssembly->getFilterPhase();
|
|
}
|
|
}
|
|
// If no root Assembly, fall back to using floorAssembly
|
|
Assembly* floorAssembly = lastFloorPart->getPartPrimitive()->getAssembly();
|
|
return (int)(floorAssembly ? floorAssembly->getFilterPhase() : Assembly::NOT_ASSIGNED);
|
|
}
|
|
return (int)Assembly::NOT_ASSIGNED;
|
|
}
|
|
|
|
int Humanoid::getCurrentFloorFilterPhase(Assembly* floorAssembly)
|
|
{
|
|
if (lastFloorPart.get())
|
|
{
|
|
if (rootFloorMechPart.get())
|
|
{
|
|
Assembly* rootAssembly = rootFloorMechPart->getPartPrimitive()->getAssembly();
|
|
if (rootAssembly)
|
|
{
|
|
return rootAssembly->getFilterPhase();
|
|
}
|
|
}
|
|
// If no root Assembly, fall back to using floorAssembly
|
|
return (int)(floorAssembly ? floorAssembly->getFilterPhase() : Assembly::NOT_ASSIGNED);
|
|
}
|
|
return (int)Assembly::NOT_ASSIGNED;
|
|
}
|
|
|
|
} // namespace
|