mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
1842 lines
58 KiB
C++
1842 lines
58 KiB
C++
/* Copyright 2003-2007 ROBLOX Corporation, All Rights Reserved */
|
|
#include "stdafx.h"
|
|
|
|
#include "Humanoid/HumanoidState.h"
|
|
|
|
#include "Humanoid/Humanoid.h"
|
|
|
|
#include "Humanoid/Running.h"
|
|
#include "Humanoid/RunningNoPhysics.h"
|
|
#include "Humanoid/GettingUp.h"
|
|
#include "Humanoid/FallingDown.h"
|
|
#include "Humanoid/Flying.h"
|
|
#include "Humanoid/Ragdoll.h"
|
|
#include "Humanoid/StrafingNoPhysics.h"
|
|
#include "Humanoid/FreeFall.h"
|
|
#include "Humanoid/Jumping.h"
|
|
#include "Humanoid/Seated.h"
|
|
#include "Humanoid/Swimming.h"
|
|
|
|
|
|
#include "V8DataModel/Filters.h"
|
|
#include "V8DataModel/Gyro.h"
|
|
#include "V8DataModel/GameBasicSettings.h"
|
|
#include "V8DataModel/GeometryService.h"
|
|
#include "V8DataModel/PartInstance.h"
|
|
#include "V8DataModel/Workspace.h"
|
|
#include "v8world/Mechanism.h"
|
|
#include "V8World/ContactManager.h"
|
|
#include "V8World/Primitive.h"
|
|
#include "V8World/Assembly.h"
|
|
#include "V8World/World.h"
|
|
#include "V8World/Buoyancy.h"
|
|
#include "V8Kernel/Body.h"
|
|
#include "V8Kernel/Constants.h"
|
|
#include "Network/Players.h"
|
|
#include "GfxBase/Adorn.h" // to do - move to cpp file when it will compile with windows.h in front of it
|
|
#include "AppDraw/Draw.h"
|
|
#include "AppDraw/DrawAdorn.h"
|
|
|
|
#include "V8Kernel/Kernel.h"
|
|
#include "v8kernel/ContactConnector.h"
|
|
#include "v8world/Contact.h"
|
|
|
|
#include "V8DataModel/HackDefines.h"
|
|
#include "Security/FuzzyTokens.h"
|
|
#include "Security/JunkCode.h"
|
|
|
|
#include "VMProtectSDK.h"
|
|
|
|
DYNAMIC_FASTFLAGVARIABLE(HumanoidFloorPVUpdateSignal, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(NoWalkAnimWeld, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(ClampRunSignalMinSpeed, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(EnableClimbingDirection, false)
|
|
DYNAMIC_FASTFLAGVARIABLE(FixJumpGracePeriod, true)
|
|
DYNAMIC_FASTFLAGVARIABLE(EnableHipHeight, false)
|
|
|
|
FASTFLAG(DebugHumanoidRendering)
|
|
|
|
LOGGROUP(UserInputProfile)
|
|
LOGGROUP(HumanoidFloorProcess)
|
|
DYNAMIC_FASTFLAG(CheckForHeadHit)
|
|
DYNAMIC_FASTFLAG(UseR15Character)
|
|
|
|
namespace RBX {
|
|
namespace HUMAN {
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Note - padded on top and left for error checking
|
|
// Note - also used to determine whether to run "tests" - for example Climbing::Off_Floor signals that climbing needs the floor test
|
|
// Running::Touched signals that running uses this event as well
|
|
//
|
|
|
|
const int STATE_TABLE[ NUM_STATE_TYPES+1 ][ NUM_EVENT_TYPES+1 ] =
|
|
{
|
|
xx, NO_HEALTH, NO_NECK, JUMP_CMD, STRAFE_CMD, NO_STRAFE_CMD, SIT_CMD, NO_SIT_CMD, PLATFORM_STAND_CMD, NO_PLATFORM_STAND_CMD, TIPPED, UPRIGHT, FACE_LDR, AWAY_LDR, OFF_FLOOR, OFF_FLOOR_GRACE, ON_FLOOR, TOUCHED, NEARLY_TOUCHED, TOUCHED_HARD, ACTIVATE_PHYSICS, FINISHED, TIMER_UP, NO_TOUCH_ONE_SECOND, HAS_GYRO, HAS_BUOYANCY, NO_BUOYANCY,
|
|
|
|
FALLING_DWN, DEAD, DEAD, xx, xx, xx, SEATED, xx, PLATFORM_STANDING, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, GETTING_UP, xx, xx, SWIMMING, xx,
|
|
RAGDOLL, DEAD, DEAD, GETTING_UP, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, GETTING_UP, xx, xx, xx, xx,
|
|
GETTING_UP, DEAD, DEAD, xx, xx, xx, SEATED, xx, PLATFORM_STANDING, xx, xx, RUNNING, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, SWIMMING, xx,
|
|
JUMPING, DEAD, DEAD, xx, xx, xx, SEATED, xx, PLATFORM_STANDING, xx, xx, xx, xx, xx, FREE_FALL, FREE_FALL, xx, xx, xx, xx, xx, FREE_FALL, FREE_FALL, xx, xx, xx, xx,
|
|
SWIMMING, DEAD, DEAD, JUMPING, xx, xx, SEATED, xx, PLATFORM_STANDING, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, RAGDOLL, xx, xx, xx, xx, xx, xx, GETTING_UP,
|
|
FREE_FALL, DEAD, DEAD, xx, xx, xx, SEATED, xx, PLATFORM_STANDING, xx, xx, xx, CLIMBING, xx, xx, xx, LANDED, xx, xx, xx, xx, xx, xx, xx, xx, SWIMMING, xx,
|
|
FLYING, DEAD, DEAD, xx, xx, xx, SEATED, xx, PLATFORM_STANDING, xx, FALLING_DWN,xx, CLIMBING, xx, xx, xx, RUNNING, xx, xx, xx, xx, xx, xx, xx, xx, SWIMMING, xx,
|
|
LANDED, DEAD, DEAD, xx, xx, xx, SEATED, xx, PLATFORM_STANDING, xx, FALLING_DWN,xx, xx, xx, xx, FREE_FALL, xx, xx, xx, xx, xx, xx, RUNNING, xx, xx, SWIMMING, xx,
|
|
RUNNING, DEAD, DEAD, JUMPING, xx, xx, SEATED, xx, PLATFORM_STANDING, xx, FALLING_DWN,xx, CLIMBING, xx, xx, FREE_FALL, xx, xx, xx, RAGDOLL, xx, xx, xx, RUNNING_NO_PHYS, xx, SWIMMING, xx,
|
|
RUNNING_SLAVE, DEAD, DEAD, JUMPING, xx, xx, SEATED, xx, PLATFORM_STANDING, xx, FALLING_DWN,xx, CLIMBING, xx, xx, FREE_FALL, xx, xx, xx, RAGDOLL, xx, xx, xx, RUNNING_NO_PHYS, xx, SWIMMING, xx, // change here for phys
|
|
RUNNING_NO_PHYS, DEAD, DEAD, JUMPING, STRAFING_NO_PHYS, xx, SEATED, xx, PLATFORM_STANDING, xx, xx, xx, CLIMBING, xx, xx, FREE_FALL, xx, RUNNING, RUNNING, xx, RUNNING, xx, xx, xx, RUNNING, SWIMMING, xx, // change here for phys
|
|
STRAFING_NO_PHYS, DEAD, DEAD, JUMPING, xx, RUNNING_NO_PHYS,SEATED, xx, PLATFORM_STANDING, xx, xx, xx, CLIMBING, xx, xx, FREE_FALL, xx, RUNNING, RUNNING, xx, RUNNING, xx, xx, xx, RUNNING, SWIMMING, xx,
|
|
CLIMBING, DEAD, DEAD, JUMPING, xx, xx, SEATED, xx, PLATFORM_STANDING, xx, FALLING_DWN,xx, xx, RUNNING, CLIMBING, CLIMBING, CLIMBING, xx, xx, xx, xx, xx, xx, xx, xx, SWIMMING, xx,
|
|
SEATED, DEAD, DEAD, JUMPING, xx, xx, xx, RUNNING, PLATFORM_STANDING, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
|
PLATFORM_STANDING, DEAD, DEAD, JUMPING, xx, xx, SEATED, xx, xx, RUNNING, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
|
DEAD, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
|
|
PHYSICS, DEAD, DEAD, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx
|
|
};
|
|
|
|
// This function exists as part of a security check.
|
|
inline StateType getStateFast(StateType oldState, EventType eventType)
|
|
{
|
|
StateType newState = static_cast<StateType>(STATE_TABLE[oldState + 1][eventType + 1]);
|
|
|
|
return newState;
|
|
}
|
|
|
|
StateType getState(StateType oldState, EventType eventType)
|
|
{
|
|
RBXASSERT(STATE_TABLE[oldState+1][0] == oldState);
|
|
RBXASSERT(STATE_TABLE[0][eventType+1] == eventType);
|
|
|
|
return getStateFast(oldState, eventType);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////5////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#if CHARACTER_FORCE_DEBUG
|
|
|
|
void DebugRay::Draw(Adorn* adorn)
|
|
{
|
|
if (adorn)
|
|
{
|
|
adorn->setObjectToWorldMatrix(CoordinateFrame(Vector3(0,0,0)));
|
|
adorn->ray(ray, color);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////5////////////////////////
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
HumanoidState::HumanoidState(Humanoid* humanoid, StateType priorState)
|
|
: humanoid(humanoid)
|
|
, timer(0.0f)
|
|
, noTouchTimer(0.0f)
|
|
, noFloorTimer(0.0f)
|
|
, nearlyTouched(false)
|
|
, finished(false)
|
|
, outOfWater(false)
|
|
, headClear(true)
|
|
, shouldRender(false) // set to true for debugging
|
|
, lastMovementVelocity(0.0f)
|
|
, priorState(priorState)
|
|
, floorTouchInWorld(unitializedFloorTouch())
|
|
, floorTouchNormal(Vector3::unitY())
|
|
, floorHumanoidLocationInWorld(unitializedFloorTouch())
|
|
, maxForce(0.0f, 0.0f, 0.0f)
|
|
, maxTorque(0.0f, 0.0f, 0.0f)
|
|
, maxContactVel(0.0f)
|
|
, lastForce(0.0f, 0.0f, 0.0f)
|
|
, lastTorque(0.0f, 0.0f, 0.0f)
|
|
, lastContactVel(0.0f)
|
|
, torsoHasBuoyancy( false )
|
|
, leftLegHasBuoyancy( false )
|
|
, rightLegHasBuoyancy( false )
|
|
, ladderCheck(0)
|
|
, luaState(xx)
|
|
, floorMaterial(AIR_MATERIAL)
|
|
{
|
|
humanoid->setJump(false);
|
|
humanoid->setStrafe(false);
|
|
setCanThrottleState(false);
|
|
|
|
PartInstance* torso = getHumanoid()->getVisibleTorsoSlow();
|
|
if (torso)
|
|
buoyancyConnections.push_back( torso->onDemandWrite()->buoyancyChangedSignal.connect( boost::bind(&HumanoidState::setTorsoHasBuoyancy, this, _1 ) ) );
|
|
|
|
PartInstance* leftLeg = humanoid->getLeftLegSlow();
|
|
if (leftLeg)
|
|
buoyancyConnections.push_back( leftLeg->onDemandWrite()->buoyancyChangedSignal.connect( boost::bind(&HumanoidState::setLeftLegHasBuoyancy, this, _1 ) ) );
|
|
|
|
PartInstance* rightLeg = humanoid->getRightLegSlow();
|
|
if (rightLeg)
|
|
buoyancyConnections.push_back( rightLeg->onDemandWrite()->buoyancyChangedSignal.connect( boost::bind(&HumanoidState::setRightLegHasBuoyancy, this, _1 ) ) );
|
|
}
|
|
|
|
HumanoidState::~HumanoidState()
|
|
{
|
|
setCanThrottleState(false);
|
|
|
|
std::vector<rbx::signals::connection>::iterator buoyancyConnectionsIterator;
|
|
for( buoyancyConnectionsIterator = buoyancyConnections.begin(); buoyancyConnectionsIterator != buoyancyConnections.end(); ++buoyancyConnectionsIterator )
|
|
{
|
|
(*buoyancyConnectionsIterator).disconnect();
|
|
}
|
|
buoyancyConnections.clear();
|
|
}
|
|
|
|
float HumanoidState::runningKMoveP()
|
|
{
|
|
// units: 1/sec force = kMoveP * mass * velocity
|
|
return 1250.0f;
|
|
}
|
|
|
|
float HumanoidState::runningKMovePForPGS()
|
|
{
|
|
return 150.0f;
|
|
}
|
|
|
|
|
|
float HumanoidState::steepSlopeAngle() const
|
|
{
|
|
if (humanoid)
|
|
return cos(humanoid->getMaxSlopeAngle() * Math::pif() / 180.0f);
|
|
|
|
return 0.5f;
|
|
}
|
|
|
|
void HumanoidState::fireEvents()
|
|
{
|
|
if (priorState != getStateType())
|
|
{
|
|
fireEvent(priorState, false);
|
|
fireEvent(getStateType(), true);
|
|
humanoid->stateChangedSignal(priorState, getStateType());
|
|
}
|
|
|
|
priorState = getStateType();
|
|
}
|
|
|
|
|
|
const Assembly* HumanoidState::getAssemblyConst() const
|
|
{
|
|
if (humanoid) {
|
|
if (const Primitive* torsoPrim = humanoid->getTorsoPrimitiveSlow()) {
|
|
return torsoPrim->getConstAssembly();
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
Assembly* HumanoidState::getAssembly()
|
|
{
|
|
if (humanoid) {
|
|
if (Primitive* torsoPrim = humanoid->getTorsoPrimitiveFast()) {
|
|
return torsoPrim->getAssembly();
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void HumanoidState::stateToAssembly()
|
|
{
|
|
if (Assembly* a = getAssembly()) {
|
|
StateType currentType = getStateType();
|
|
a->setNetworkHumanoidState(static_cast<unsigned char>(currentType));
|
|
}
|
|
}
|
|
|
|
|
|
StateType HumanoidState::stateFromAssembly()
|
|
{
|
|
if (Assembly* a = getAssembly())
|
|
{
|
|
unsigned char assemblyHumanoidState = a->getNetworkHumanoidState();
|
|
return static_cast<StateType>(assemblyHumanoidState);
|
|
}
|
|
else
|
|
{
|
|
return DEAD; // Hack - using the assemblies to transmit state information to prevent broadcast
|
|
}
|
|
}
|
|
|
|
|
|
// ASSERT the humanoid is always in the world
|
|
const Humanoid* HumanoidState::getHumanoidConst() const
|
|
{
|
|
RBXASSERT(humanoid);
|
|
return humanoid;
|
|
}
|
|
|
|
void HumanoidState::setCanThrottleState(bool canThrottle)
|
|
{
|
|
if (humanoid)
|
|
{
|
|
if (humanoid->getHeadSlow()) {humanoid->getHeadSlow()->getPartPrimitive()->setCanThrottle(canThrottle);}
|
|
if (humanoid->getTorsoSlow()) {humanoid->getTorsoSlow()->getPartPrimitive()->setCanThrottle(canThrottle);}
|
|
if (humanoid->getRightArmSlow()) {humanoid->getRightArmSlow()->getPartPrimitive()->setCanThrottle(canThrottle);}
|
|
if (humanoid->getLeftArmSlow()) {humanoid->getLeftArmSlow()->getPartPrimitive()->setCanThrottle(canThrottle);}
|
|
if (humanoid->getRightLegSlow()) {humanoid->getRightLegSlow()->getPartPrimitive()->setCanThrottle(canThrottle);}
|
|
if (humanoid->getLeftLegSlow()) {humanoid->getLeftLegSlow()->getPartPrimitive()->setCanThrottle(canThrottle);}
|
|
}
|
|
}
|
|
|
|
bool HumanoidState::usesEvent(EventType e) const
|
|
{
|
|
StateType myState = getStateType();
|
|
return (getState(myState, e) != xx);
|
|
}
|
|
|
|
|
|
void HumanoidState::onComputeForce() {
|
|
|
|
Humanoid *humanoid = getHumanoid();
|
|
|
|
Body* torsoBody = humanoid->getTorsoBodyFast();
|
|
Body* root = humanoid->getRootBodyFast();
|
|
if (root && torsoBody) {
|
|
const CoordinateFrame& rootCoord = root->getCoordinateFrame();
|
|
lastTorque = rootCoord.vectorToObjectSpace(root->getBranchTorque());
|
|
lastForce = root->getBranchForce();
|
|
}
|
|
|
|
onComputeForceImpl();
|
|
}
|
|
|
|
void HumanoidState::render3dAdorn(Adorn* adorn)
|
|
{
|
|
// if (shouldRender && (humanoid == Humanoid::getLocalHumanoidFromContext(humanoid)))
|
|
// if (shouldRender && Network::Players::backendProcessing(humanoid))
|
|
|
|
if (shouldRender || FFlag::DebugHumanoidRendering) {
|
|
static int frameCount = 0;
|
|
Humanoid *humanoid = getHumanoid();
|
|
|
|
int yBase = 40;
|
|
if (!humanoid->isLocalSimulating())
|
|
yBase += 100;
|
|
|
|
findLadder(adorn);
|
|
if (floorPart.get()) {
|
|
Draw::selectionBox(floorPart->getPart(), adorn, Color3::white());
|
|
DrawAdorn::cylinder(adorn, CoordinateFrame(floorHumanoidLocationInWorld), 1, 0.2, 1.0, Color3::white());
|
|
Vector3 torsoPos = humanoid->getTorsoBodyFast()->getCoordinateFrame().translation;
|
|
torsoPos.y = getDesiredAltitude();
|
|
DrawAdorn::cylinder(adorn, torsoPos, 1, 0.2, 1.0, Color3::blue());
|
|
}
|
|
|
|
if (lastTorque.magnitude() > maxTorque.magnitude() || frameCount == 2047)
|
|
maxTorque = lastTorque;
|
|
if (lastForce.magnitude() > maxForce.magnitude() || frameCount == 2047)
|
|
maxForce = lastForce;
|
|
if (fabs(lastContactVel) > fabs(maxContactVel) || frameCount == 2047) {
|
|
maxContactVel = lastContactVel;
|
|
}
|
|
frameCount = (++frameCount) % 1024;
|
|
|
|
// debug rays
|
|
#if CHARACTER_FORCE_DEBUG
|
|
for(size_t i = 0; i < debugRayList.size(); ++i) {
|
|
debugRayList[i].Draw(adorn);
|
|
}
|
|
debugRayList.clear();
|
|
|
|
#endif
|
|
|
|
if (humanoid->getHeadSlow())
|
|
{
|
|
adorn->drawFont2D(
|
|
getName().toString(),
|
|
Vector2(16, yBase),
|
|
16, // size
|
|
false,
|
|
Color3::white(),
|
|
Color3::black(),
|
|
Text::FONT_LEGACY,
|
|
Text::XALIGN_LEFT,
|
|
Text::YALIGN_BOTTOM );
|
|
|
|
char buf[256];
|
|
snprintf(buf, sizeof(buf), "torque(%8.1f, %8.1f, %8.1f), length:%8.1f", maxTorque.x, maxTorque.y, maxTorque.z, maxTorque.length());
|
|
adorn->drawFont2D(
|
|
std::string(buf),
|
|
Vector2(16, yBase + 20),
|
|
16, // size
|
|
false,
|
|
Color3::white(),
|
|
Color3::black(),
|
|
Text::FONT_LEGACY,
|
|
Text::XALIGN_LEFT,
|
|
Text::YALIGN_BOTTOM );
|
|
|
|
snprintf(buf, sizeof(buf), "force(%8.1f, %8.1f, %8.1f), length:%8.1f", maxForce.x, maxForce.y, maxForce.z, maxForce.length());
|
|
adorn->drawFont2D(
|
|
std::string(buf),
|
|
Vector2(16, yBase + 40),
|
|
16, // size
|
|
false,
|
|
Color3::white(),
|
|
Color3::black(),
|
|
Text::FONT_LEGACY,
|
|
Text::XALIGN_LEFT,
|
|
Text::YALIGN_BOTTOM );
|
|
|
|
snprintf(buf, sizeof(buf), "contact velocity : %f", maxContactVel);
|
|
adorn->drawFont2D(
|
|
std::string(buf),
|
|
Vector2(16, yBase + 60),
|
|
16, // size
|
|
false,
|
|
Color3::white(),
|
|
Color3::black(),
|
|
Text::FONT_LEGACY,
|
|
Text::XALIGN_LEFT,
|
|
Text::YALIGN_BOTTOM );
|
|
}
|
|
}
|
|
}
|
|
|
|
float HumanoidState::getFloorFrictionProperty(Primitive* floorPrim) const
|
|
{
|
|
if (!floorPrim->getPhysicalProperties().getCustomEnabled())
|
|
{
|
|
return MaterialProperties::generatePhysicalMaterialFromPartMaterial(getFloorMaterial()).getFriction();
|
|
}
|
|
return floorPrim->getPhysicalProperties().getFriction();
|
|
}
|
|
|
|
Primitive* HumanoidState::getFloorPrimitive()
|
|
{
|
|
if (floorPart.get())
|
|
{
|
|
Primitive* answer = floorPart->getPartPrimitive();
|
|
if (answer->getWorld())
|
|
{
|
|
if (Assembly* humanoidAssembly = getAssembly())
|
|
{
|
|
if (humanoidAssembly != answer->getAssembly())
|
|
{
|
|
return answer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const Primitive* HumanoidState::getFloorPrimitiveConst() const
|
|
{
|
|
if (floorPart.get())
|
|
{
|
|
Primitive* answer = floorPart->getPartPrimitive();
|
|
if (answer->getWorld())
|
|
{
|
|
if (const Assembly* humanoidAssembly = getAssemblyConst())
|
|
{
|
|
if (humanoidAssembly != answer->getAssembly())
|
|
{
|
|
return answer;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
HumanoidState* HumanoidState::defaultState(Humanoid* humanoid)
|
|
{
|
|
return new Running(humanoid, RUNNING_NO_PHYS); // will fire an event
|
|
}
|
|
|
|
|
|
void HumanoidState::preStepSimulatorSide(float dt)
|
|
{
|
|
preStepFloor();
|
|
|
|
timer -= dt;
|
|
noTouchTimer += dt;
|
|
noFloorTimer += dt;
|
|
|
|
preStepCollide();
|
|
|
|
if (DFFlag::CheckForHeadHit)
|
|
{
|
|
headClear = true;
|
|
PartInstance* foundBody = humanoid->getTorsoSlow();
|
|
PartInstance* foundHead = humanoid->getHeadSlow();
|
|
if (foundBody && foundHead)
|
|
{
|
|
Vector3 headUp = Vector3::unitY() * (foundHead->getPartSizeUi().y / 2.0f);
|
|
|
|
FilterHumanoidParts filterHumanoidParts;
|
|
MergedFilter *mergedFilter = new MergedFilter(&filterHumanoidParts, this);
|
|
RbxRay caster(foundHead->getCoordinateFrame().translation, headUp);
|
|
Vector3 hitLocation;
|
|
std::vector<const Primitive*> dummy;
|
|
Primitive* hitPrim = NULL;
|
|
|
|
hitPrim = humanoid->getWorld()->getContactManager()->getHit(
|
|
caster,
|
|
&dummy,
|
|
mergedFilter,
|
|
hitLocation);
|
|
|
|
if (mergedFilter)
|
|
delete mergedFilter;
|
|
|
|
if (hitPrim != NULL)
|
|
headClear = false;
|
|
}
|
|
}
|
|
|
|
|
|
facingLadder = false;
|
|
if (usesLadder()) {
|
|
if (ladderCheck <= 0) {
|
|
facingLadder = findLadder(NULL);
|
|
ladderCheck = ladderCheckRate();
|
|
}
|
|
else {
|
|
ladderCheck--;
|
|
}
|
|
}
|
|
if (!facingLadder && humanoid->getAutoJump() && enableAutoJump())
|
|
{
|
|
Velocity desiredWalkVelocity = getHumanoid()->calcDesiredWalkVelocity();
|
|
if (!desiredWalkVelocity.linear.isZero())
|
|
doAutoJump();
|
|
}
|
|
}
|
|
|
|
void HumanoidState::preStepCollide()
|
|
{
|
|
setLegsCanCollide(legsShouldCollide());
|
|
setArmsCanCollide(armsShouldCollide());
|
|
setHeadCanCollide(headShouldCollide());
|
|
setTorsoCanCollide(torsoShouldCollide());
|
|
}
|
|
|
|
|
|
void HumanoidState::preStepFloor()
|
|
{
|
|
shared_ptr<PartInstance> oldFloor = floorPart;
|
|
floorPart.reset();
|
|
floorTouchInWorld = unitializedFloorTouch();
|
|
floorTouchNormal = Vector3::unitY();
|
|
floorHumanoidLocationInWorld = unitializedFloorTouch();
|
|
|
|
if (usesFloor())
|
|
findFloor(oldFloor);
|
|
|
|
if (DFFlag::FixJumpGracePeriod && floorPart)
|
|
humanoid->setLastFloorNormal(floorTouchNormal);
|
|
|
|
if (oldFloor == floorPart)
|
|
return;
|
|
|
|
// changed floor part
|
|
Primitive* floorPrimitive = getFloorPrimitive();
|
|
|
|
if (!floorPrimitive)
|
|
return;
|
|
|
|
// wake up the part we are on, because we are not doing collisions
|
|
// but instead floating with a force
|
|
humanoid->getWorld()->ticklePrimitive(floorPrimitive, false);
|
|
}
|
|
|
|
// State is master
|
|
void HumanoidState::simulate(shared_ptr<HumanoidState>& state, float dt)
|
|
{
|
|
RBXASSERT(state.get());
|
|
|
|
if (state->getStateType() != state->stateFromAssembly()) {
|
|
state->stateToAssembly();
|
|
}
|
|
|
|
if (state->getAssembly()) {
|
|
state->getAssembly()->wakeUp(); // no longer prevent sleep, instead constantly wake up
|
|
}
|
|
|
|
state->preStepSimulatorSide(dt);
|
|
|
|
doSimulatorStateTable(state, dt);
|
|
|
|
state->onSimulatorStepImpl(dt); // only for simulating humanoids
|
|
state->onStepImpl();
|
|
RBXASSERT(!state->getAssembly() || state->getStateType() == state->stateFromAssembly());
|
|
|
|
state->humanoid->setActivatePhysics(false, Vector3::zero());
|
|
|
|
if (DFFlag::HumanoidFloorPVUpdateSignal)
|
|
updateHumanoidFloorStatus(state);
|
|
}
|
|
|
|
bool isValidSubscriptionHumanoidState(shared_ptr<HumanoidState>& state)
|
|
{
|
|
// We will be able to include JUMPING once we fix Humanoid Momentum.
|
|
if (state->getStateType() == GETTING_UP || state->getStateType() == LANDED || state->getStateType() == RUNNING ||
|
|
state->getStateType() == RUNNING_SLAVE || state->getStateType() == RUNNING_NO_PHYS || state->getStateType() == STRAFING_NO_PHYS ||
|
|
state->getStateType() == PLATFORM_STANDING)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
bool checkFloorSubscribable(Primitive* floorPrim, Humanoid* humanoid)
|
|
{
|
|
if (floorPrim)
|
|
{
|
|
float humanoidMass = humanoid->getTorsoBodyFast()->getBranchMass();
|
|
float floorMass = floorPrim->getAssembly()->getAssemblyPrimitive()->getBody()->getBranchMass();
|
|
if (PartInstance* floorPart = PartInstance::fromPrimitive(floorPrim))
|
|
{
|
|
if (Humanoid::constHumanoidFromDescendant(floorPart))
|
|
{
|
|
// NO HUMANOID FLOORS PLEASE
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FASTLOG2F(FLog::HumanoidFloorProcess, "Check floor subscription Player Mass: %4.4f, Floor Mass: %4.4f", humanoidMass, floorMass);
|
|
if (humanoidMass * 2 < floorMass)
|
|
{
|
|
FASTLOG(FLog::HumanoidFloorProcess, "Floor Subscribed");
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void HumanoidState::updateHumanoidFloorStatus(shared_ptr<HumanoidState>& state)
|
|
{
|
|
shared_ptr<PartInstance> lastFloor = state->humanoid->getLastFloor();
|
|
Primitive* lastFloorPrim = NULL;
|
|
if (lastFloor)
|
|
{
|
|
lastFloorPrim = lastFloor->getPartPrimitive();
|
|
}
|
|
|
|
Primitive* floorPrim = state->getFloorPrimitive();
|
|
bool floorChanged = hasFloorChanged(state, lastFloorPrim);
|
|
// check Disconnect conditions
|
|
if (floorChanged || !isValidSubscriptionHumanoidState(state))
|
|
{
|
|
// Prevents from using Floor Displacement as Velocity
|
|
if (state->humanoid->onPositionUpdatedByNetworkConnection.connected())
|
|
{
|
|
FASTLOG(FLog::HumanoidFloorProcess, "Disconnecting from last floor");
|
|
state->humanoid->onPositionUpdatedByNetworkConnection.disconnect();
|
|
}
|
|
// If we Sit, we still want to remember last floor
|
|
if (state->getStateType() == SEATED)
|
|
{
|
|
//Do nothing
|
|
}
|
|
else if (lastFloor)
|
|
{
|
|
state->humanoid->setLastFloor(NULL);
|
|
state->humanoid->setRootFloorPart(NULL);
|
|
lastFloorPrim = NULL;
|
|
}
|
|
}
|
|
|
|
// check Floor update
|
|
if ((floorChanged || lastFloorPrim == NULL) && checkFloorSubscribable(floorPrim, state->humanoid))
|
|
{
|
|
Assembly* floorAssembly = NULL;
|
|
Primitive* floorRootPrim = NULL;
|
|
floorAssembly = floorPrim->getAssembly();
|
|
if (floorAssembly)
|
|
{
|
|
floorRootPrim = floorAssembly->getAssemblyPrimitive();
|
|
}
|
|
|
|
if (floorRootPrim)
|
|
{
|
|
state->humanoid->setLastFloor(PartInstance::fromPrimitive(floorRootPrim));
|
|
lastFloorPrim = floorRootPrim;
|
|
}
|
|
}
|
|
|
|
// check Subscription conditions
|
|
// We always subscribe to LastFloor because it is guaranteed to be correct, while getFloorPrimitive is not
|
|
if (isValidSubscriptionHumanoidState(state) && lastFloorPrim)
|
|
{
|
|
Assembly* floorAssembly = NULL;
|
|
Primitive* floorRootPrim = NULL;
|
|
floorAssembly = lastFloorPrim->getAssembly();
|
|
if (floorAssembly)
|
|
{
|
|
floorRootPrim = floorAssembly->getAssemblyPrimitive();
|
|
// Need to see RootFloor assembly for checking FilterPhase
|
|
// It's okay for floorAssembly to be NULL
|
|
PartInstance* floorRootPart = PartInstance::fromPrimitive(RBX::Mechanism::getRootMovingPrimitive(lastFloorPrim));
|
|
state->humanoid->setRootFloorPart(floorRootPart);
|
|
}
|
|
|
|
|
|
if (!state->humanoid->onPositionUpdatedByNetworkConnection.connected())
|
|
{
|
|
state->humanoid->onPositionUpdatedByNetworkConnection = PartInstance::fromPrimitive(floorRootPrim)->onDemandWrite()->onPositionUpdatedByNetworkSignal.connect(boost::bind(&Humanoid::updateNetworkFloorPosition, state->humanoid, _1, _2, _3, _4));
|
|
state->humanoid->setLastFloorPhase(state->humanoid->getCurrentFloorFilterPhase(floorAssembly));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HumanoidState::hasFloorChanged(shared_ptr<HumanoidState>& state, Primitive* lastFloorPrim)
|
|
{
|
|
Primitive* currentFloor = state->getFloorPrimitive();
|
|
// Handles the part if it was deleted.
|
|
if (lastFloorPrim && lastFloorPrim->getWorld() == NULL)
|
|
{
|
|
state->humanoid->setLastFloor(NULL);
|
|
state->humanoid->setRootFloorPart(NULL);
|
|
return true;
|
|
}
|
|
|
|
if (currentFloor && lastFloorPrim && lastFloorPrim->getAssembly())
|
|
{
|
|
// lastFloorPrim should always be the Assembly root.
|
|
// If there is mismatch, we should disconnect, because the floor HAS changed.
|
|
return ((lastFloorPrim != currentFloor->getAssembly()->getAssemblyPrimitive()) ||
|
|
(lastFloorPrim->getAssembly() != currentFloor->getAssembly()));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// Assembly is master for state, state is slave
|
|
void HumanoidState::noSimulate(shared_ptr<HumanoidState>& state) // for non-simulating humanoids, match states
|
|
{
|
|
RBXASSERT(state.get());
|
|
|
|
StateType newType = state->stateFromAssembly();
|
|
|
|
if (state->getAssembly()) {
|
|
state->getAssembly()->wakeUp();
|
|
}
|
|
|
|
state->preStepSlaveSide();
|
|
|
|
doSlaveStateTable(state, newType);
|
|
|
|
state->onStepImpl();
|
|
}
|
|
|
|
|
|
|
|
|
|
void HumanoidState::changeState(shared_ptr<HumanoidState>& state, StateType newType)
|
|
{
|
|
StateType oldType = state->getStateType();
|
|
|
|
if (newType != oldType)
|
|
{
|
|
FASTLOG2(FLog::UserInputProfile, "Changing humanoid state, old: %u, new: %u", oldType, newType);
|
|
Humanoid* humanoid = state->getHumanoid();
|
|
humanoid->setPreviousStateType(oldType);
|
|
|
|
state.reset(); // force delete before create - not sure if reset destroys first before creating second?
|
|
|
|
state.reset(create(newType, oldType, humanoid));
|
|
}
|
|
}
|
|
|
|
|
|
void HumanoidState::doSimulatorStateTable(shared_ptr<HumanoidState>& state, float dt)
|
|
{
|
|
VMProtectBeginMutation(NULL);
|
|
bool isBadNvReg = false;
|
|
bool isBadType = false;
|
|
|
|
const StateType oldType = state->getStateType();
|
|
|
|
if (oldType != state->luaState && state->luaState != xx) {
|
|
changeState(state, state->luaState);
|
|
state->preStepSimulatorSide(dt); // new state - do it again here
|
|
return;
|
|
}
|
|
|
|
Humanoid* humanoid = state->getHumanoid();
|
|
|
|
volatile StateType oldTypeSecurityBackup = oldType; // ebx/esi/edi are nonvolatile, but exploiters change them anyways.
|
|
for (int i = 0; i < NUM_EVENT_TYPES; ++i)
|
|
{
|
|
EventType e = static_cast<EventType>(i);
|
|
volatile EventType eBackup = e;
|
|
|
|
StateType newType = getState(oldType, e); // hack - should be in state table
|
|
volatile StateType newTypeBackup = newType;
|
|
|
|
RBXASSERT(newType != RUNNING_SLAVE); // only should come to slave side, never out of state table
|
|
|
|
if ((newType != xx) && (newType != oldType) && (humanoid->getStateTransitionEnabled(newType)))
|
|
{
|
|
if (state->computeEvent(e)) // only compute when necessary
|
|
{
|
|
changeState(state, newType);
|
|
|
|
state->preStepSimulatorSide(dt); // new state - do it again here
|
|
|
|
isBadType = isBadType || (state->getStateType() != newType); // exploit: wrong object returned
|
|
|
|
break;
|
|
}
|
|
}
|
|
isBadNvReg = isBadNvReg || (e != eBackup || newType != newTypeBackup) // exploit: nv reg changed
|
|
|| (newType != getStateFast(oldType, e)); // exploit: invalid table jump
|
|
}
|
|
VMProtectEnd();
|
|
VMProtectBeginVirtualization(NULL);
|
|
if (oldType != oldTypeSecurityBackup)
|
|
{
|
|
Tokens::sendStatsToken.addFlagFast(HATE_HSCE_EBX);
|
|
Tokens::simpleToken |= HATE_HSCE_EBX;
|
|
}
|
|
if (isBadType)
|
|
{
|
|
RBX::Security::setHackFlagVmp<LINE_RAND1>(RBX::Security::hackFlag5, HATE_HSCE_EBX);
|
|
}
|
|
if (isBadNvReg)
|
|
{
|
|
Tokens::sendStatsToken.addFlagFast(HATE_HSCE_EBX);
|
|
Tokens::simpleToken |= HATE_HSCE_EBX;
|
|
RBX::Security::setHackFlagVmp<LINE_RAND1>(RBX::Security::hackFlag7, HATE_HSCE_EBX);
|
|
}
|
|
VMProtectEnd();
|
|
}
|
|
|
|
void HumanoidState::doSlaveStateTable(shared_ptr<HumanoidState>& state, StateType newType)
|
|
{
|
|
if (state->computeEvent(NO_HEALTH) || state->computeEvent(NO_NECK)) {
|
|
newType = HUMAN::DEAD;
|
|
}
|
|
else if (state->computeEvent(SIT_CMD)) {
|
|
newType = HUMAN::SEATED;
|
|
}
|
|
else if (state->computeEvent(PLATFORM_STAND_CMD)) {
|
|
newType = HUMAN::PLATFORM_STANDING;
|
|
}
|
|
else if (state->getStateType() == RUNNING_NO_PHYS && state->computeTouchedByMySimulation())
|
|
newType = HUMAN::RUNNING_SLAVE; // ouch - force the server side to go to running mode so we get accurate physics
|
|
else if (state->getStateType() == RUNNING && state->computeEvent(TOUCHED_HARD))
|
|
newType = HUMAN::RAGDOLL; // Get into ragdoll fast even before the owner says so
|
|
|
|
if (newType != state->getStateType()) {
|
|
bool keepRunningSlave = ((state->getStateType() == RUNNING_SLAVE) && (newType == RUNNING_NO_PHYS));
|
|
|
|
if (!keepRunningSlave) {
|
|
changeState(state, newType);
|
|
state->preStepSlaveSide();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HumanoidState::computeEvent(EventType eventType)
|
|
{
|
|
// This code is heavily targeted by exploiters, and vmprotect won't obfuscate it.
|
|
// To make it more difficult, junk code will be added randomly each week.
|
|
// VS2012 will write code in file-order. This means changing/remapping enums will
|
|
// only change the jump table.
|
|
bool returnValue;
|
|
switch (eventType)
|
|
{
|
|
case HAS_BUOYANCY: RBX_JUNK; returnValue = computeHasBuoyancy(); break;
|
|
case NO_BUOYANCY: RBX_JUNK; returnValue = !computeHasBuoyancy(); break;
|
|
case NO_HEALTH: RBX_JUNK; returnValue = (humanoid->getHealth() <= 0.0f); break;
|
|
case NO_NECK: RBX_JUNK; returnValue = (!humanoid->getNeck()); break;
|
|
case JUMP_CMD: RBX_JUNK; returnValue = computeJumped(); break;
|
|
case SIT_CMD: RBX_JUNK; returnValue = humanoid->getSit(); break;
|
|
case NO_SIT_CMD: RBX_JUNK; returnValue = !humanoid->getSit(); break;
|
|
case PLATFORM_STAND_CMD: RBX_JUNK; returnValue = humanoid->getPlatformStanding(); break;
|
|
case NO_PLATFORM_STAND_CMD: RBX_JUNK; returnValue = !humanoid->getPlatformStanding(); break;
|
|
case TIPPED: RBX_JUNK; returnValue = computeTipped(); break;
|
|
case UPRIGHT: RBX_JUNK; returnValue = computeUpright(); break;
|
|
case FACE_LDR: RBX_JUNK; returnValue = facingLadder; break;
|
|
case AWAY_LDR: RBX_JUNK; returnValue = !facingLadder; break;
|
|
case STRAFE_CMD: RBX_JUNK; returnValue = false; break;
|
|
case NO_STRAFE_CMD: RBX_JUNK; returnValue = !humanoid->getStrafe(); break;
|
|
case ON_FLOOR: RBX_JUNK; returnValue = (floorPart != NULL) && (!DFFlag::FixJumpGracePeriod || getRelativeMovementVelocity().y <= 0.0f); break;
|
|
case OFF_FLOOR: RBX_JUNK; returnValue = (floorPart == NULL); break;
|
|
case OFF_FLOOR_GRACE: RBX_JUNK; returnValue = (noFloorTimer > fallDelay()); break;
|
|
case TOUCHED: RBX_JUNK; returnValue = computeTouched(); break;
|
|
case NEARLY_TOUCHED: RBX_JUNK; returnValue = computeNearlyTouched(); break;
|
|
case TOUCHED_HARD: RBX_JUNK; returnValue = computeTouchedHard(); break;
|
|
case ACTIVATE_PHYSICS: RBX_JUNK; returnValue = computeActivatePhysics(); break;
|
|
case TIMER_UP: RBX_JUNK; returnValue = (timer <= 0.0f); break;
|
|
case NO_TOUCH_ONE_SECOND:
|
|
{
|
|
RBX_JUNK;
|
|
computeTouched(); // updates the timer;
|
|
computeNearlyTouched();
|
|
returnValue = (noTouchTimer > 1.0f && !computeHasGyro());
|
|
}
|
|
break;
|
|
case FINISHED: RBX_JUNK; returnValue = finished; break;
|
|
case HAS_GYRO: RBX_JUNK; returnValue = computeHasGyro(); break;
|
|
default:
|
|
{
|
|
returnValue = false;
|
|
}
|
|
break;
|
|
}
|
|
RBX_JUNK;
|
|
return returnValue;
|
|
}
|
|
|
|
HumanoidState* HumanoidState::create(StateType newType, StateType oldType, Humanoid* humanoid)
|
|
{
|
|
HumanoidState* answer = createNew(newType, oldType, humanoid);
|
|
|
|
answer->stateToAssembly();
|
|
|
|
return answer;
|
|
}
|
|
|
|
|
|
|
|
|
|
HumanoidState* HumanoidState::createNew(StateType newType, StateType oldType, Humanoid* humanoid)
|
|
{
|
|
RBXASSERT(humanoid);
|
|
|
|
switch (newType)
|
|
{
|
|
case FALLING_DWN: return new FallingDown(humanoid, oldType);
|
|
case GETTING_UP: return new GettingUp(humanoid, oldType);
|
|
case JUMPING: return new Jumping(humanoid, oldType);
|
|
case FREE_FALL: return new Freefall(humanoid, oldType);
|
|
case FLYING: return new Flying(humanoid, oldType);
|
|
case RAGDOLL: return new Ragdoll(humanoid, oldType);
|
|
case LANDED: return new Landed(humanoid, oldType);
|
|
case RUNNING: return new Running(humanoid, oldType);
|
|
case RUNNING_SLAVE: return new RunningSlave(humanoid, oldType);
|
|
case RUNNING_NO_PHYS: return new RunningNoPhysics(humanoid, oldType);
|
|
case STRAFING_NO_PHYS: return new StrafingNoPhysics(humanoid, oldType);
|
|
case CLIMBING: return new Climbing(humanoid, oldType);
|
|
case SEATED: return new Seated(humanoid, oldType);
|
|
case PLATFORM_STANDING: return new PlatformStanding(humanoid, oldType);
|
|
case SWIMMING: return new Swimming(humanoid, oldType);
|
|
case DEAD: return new Dead(humanoid, oldType);
|
|
case PHYSICS: return new Physics(humanoid, oldType);
|
|
default:
|
|
{
|
|
RBXASSERT(0);
|
|
return new Running(humanoid, oldType);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void HumanoidState::fireEvent(StateType stateType, bool entering)
|
|
{
|
|
RBXASSERT(humanoid);
|
|
|
|
switch (stateType)
|
|
{
|
|
case RUNNING:
|
|
case RUNNING_NO_PHYS:
|
|
if (DFFlag::ClampRunSignalMinSpeed)
|
|
{
|
|
float moveSpeed = getRelativeMovementVelocity().xz().length();
|
|
float clampedVelocity = (moveSpeed < minMoveVelocity()) ? 0.0f : moveSpeed;
|
|
|
|
humanoid->runningSignal(clampedVelocity);
|
|
lastMovementVelocity = clampedVelocity;
|
|
} else {
|
|
humanoid->runningSignal(getRelativeMovementVelocity().xz().length());
|
|
}
|
|
break;
|
|
case CLIMBING: humanoid->climbingSignal(0.0f); break;
|
|
case STRAFING_NO_PHYS: humanoid->strafingSignal(entering); break;
|
|
case FALLING_DWN: humanoid->fallingDownSignal(entering); break;
|
|
case PHYSICS:
|
|
case RAGDOLL: humanoid->ragdollSignal(entering); break;
|
|
case GETTING_UP: humanoid->gettingUpSignal(entering); break;
|
|
case JUMPING: humanoid->jumpingSignal(entering); break;
|
|
case FLYING:
|
|
case LANDED:
|
|
case FREE_FALL: humanoid->freeFallingSignal(entering); break;
|
|
case SEATED: humanoid->seatedSignal(entering, shared_from(humanoid->getSeatPart()));
|
|
break;
|
|
case PLATFORM_STANDING: humanoid->platformStandingSignal(entering); break;
|
|
case DEAD: humanoid->diedSignal(); break;
|
|
case SWIMMING: humanoid->swimmingSignal(0.0f); break;
|
|
|
|
default:
|
|
{
|
|
RBXASSERT(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HumanoidState::setLuaState(StateType state)
|
|
{
|
|
luaState = state;
|
|
void (HumanoidState::* thisFunction)(HUMAN::StateType) = &HumanoidState::setLuaState;
|
|
checkRbxCaller<kCallCheckCodeOnly, callCheckSetApiFlag<kChangeStateApiOffset> >(
|
|
reinterpret_cast<void*>((void*&)(thisFunction)));
|
|
}
|
|
|
|
const char *HumanoidState::getStateNameByType(StateType state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case RUNNING_NO_PHYS: return RunningNoPhysics::name().c_str(); break;
|
|
case RUNNING: return Running::name().c_str(); break;
|
|
case CLIMBING: return Climbing::name().c_str(); break;
|
|
case STRAFING_NO_PHYS: return StrafingNoPhysics::name().c_str(); break;
|
|
case FALLING_DWN: return FallingDown::name().c_str(); break;
|
|
case RAGDOLL: return Ragdoll::name().c_str(); break;
|
|
case GETTING_UP: return GettingUp::name().c_str(); break;
|
|
case JUMPING: return Jumping::name().c_str(); break;
|
|
case FLYING: return Flying::name().c_str(); break;
|
|
case LANDED: return Landed::name().c_str(); break;
|
|
case FREE_FALL: return Freefall::name().c_str(); break;
|
|
case SEATED: return Seated::name().c_str(); break;
|
|
case PLATFORM_STANDING: return PlatformStanding::name().c_str(); break;
|
|
case DEAD: return Dead::name().c_str(); break;
|
|
case SWIMMING: return Swimming::name().c_str(); break;
|
|
case PHYSICS: return Physics::name().c_str(); break;
|
|
|
|
default:
|
|
{
|
|
RBXASSERT(0);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
float HumanoidState::computeTilt() const
|
|
{
|
|
if (Body* torsoBody = humanoid->getTorsoBodyFast())
|
|
{
|
|
Vector3 answer = torsoBody->getCoordinateFrame().vectorToObjectSpace(Vector3::unitY());
|
|
return answer.y;
|
|
}
|
|
else
|
|
{
|
|
//RBXASSERT(0);
|
|
return 1.0f;
|
|
}
|
|
}
|
|
|
|
float HumanoidState::computeFloorTilt() const
|
|
{
|
|
if (getFloorPrimitiveConst())
|
|
{
|
|
float tilt = getFloorTouchNormal().dot(Vector3::unitY());
|
|
return tilt;
|
|
}
|
|
else
|
|
{
|
|
if (DFFlag::FixJumpGracePeriod)
|
|
{
|
|
float tilt = humanoid->getLastFloorNormal().dot(Vector3::unitY());
|
|
return tilt;
|
|
} else {
|
|
|
|
return 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HumanoidState::computeTipped() const
|
|
{
|
|
float tiltThreshhold = 0.7f;
|
|
|
|
if (computeTilt() < tiltThreshhold && !getOutOfWater())
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false; // old FallCutoff();
|
|
}
|
|
}
|
|
|
|
|
|
bool HumanoidState::computeJumped() const
|
|
{
|
|
if ((computeFloorTilt() > steepSlopeAngle() || (getStateType() != RUNNING && getStateType() != RUNNING_NO_PHYS)) && humanoid->getJump() && humanoid->getJumpPower() > 0.0f)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool HumanoidState::computeUpright() const
|
|
{
|
|
if (computeTilt() > 0.90f) { //if (yAxis.y > (1.0 + 2.0 * Balancing::fallCutoff()) / 3.0)
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool HumanoidState::computeHasGyro() const
|
|
{
|
|
const ModelInstance* character = Humanoid::getConstCharacterFromHumanoid(humanoid);
|
|
RBXASSERT(character);
|
|
const BodyMover* bodyMover = character->findConstFirstDescendantOfType<BodyMover>();
|
|
return (bodyMover != NULL);
|
|
}
|
|
|
|
bool HumanoidState::computeHasBuoyancy()
|
|
{
|
|
return ( torsoHasBuoyancy );
|
|
}
|
|
|
|
bool HumanoidState::computeTouchedByMySimulation()
|
|
{
|
|
RBXASSERT(humanoid->getWorld());
|
|
if (World* world = humanoid->getWorld())
|
|
{
|
|
if (PartInstance* foundTorso = humanoid->getTorsoSlow()) {
|
|
RBX::SystemAddress myLocalAddress = RBX::Network::Players::findLocalSimulatorAddress(humanoid);
|
|
|
|
if (world->getContactManager()->intersectingMySimulation(foundTorso->getPartPrimitive(), myLocalAddress, 0.0f)) {
|
|
noTouchTimer = 0.0f;
|
|
return true;
|
|
}
|
|
|
|
if (PartInstance* foundHead = humanoid->getHeadSlow()) {
|
|
if (world->getContactManager()->intersectingMySimulation(foundHead->getPartPrimitive(), myLocalAddress, 0.0f)) {
|
|
noTouchTimer = 0.0f;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
RBXASSERT(0); // no torso - why am I here?
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool intersectsOutsideOfCharacter(Primitive* check, const Instance* character) {
|
|
Contact* c = check->getFirstContact();
|
|
|
|
while (c) {
|
|
Primitive* other = c->otherPrimitive(check);
|
|
|
|
if ( !PartInstance::fromPrimitive(other)->isDescendantOf(character)
|
|
&& c->computeIsCollidingUi(0.0f) )
|
|
{
|
|
return true;
|
|
}
|
|
c = check->getNextContact(c);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool HumanoidState::computeTouched() // only torso, head
|
|
{
|
|
RBXASSERT(humanoid->getWorld());
|
|
|
|
Instance* character = humanoid->getParent();
|
|
|
|
|
|
PartInstance* foundTorso = humanoid->getVisibleTorsoSlow();
|
|
|
|
if (foundTorso) {
|
|
if (intersectsOutsideOfCharacter(foundTorso->getPartPrimitive(), character)) {
|
|
noTouchTimer = 0.0f;
|
|
return true;
|
|
}
|
|
|
|
if (PartInstance* foundHead = humanoid->getHeadSlow()) {
|
|
if (intersectsOutsideOfCharacter(foundHead->getPartPrimitive(), character)) {
|
|
noTouchTimer = 0.0f;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
RBXASSERT(0); // NO TORSO - why am I here?
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool HumanoidState::computeNearlyTouched()
|
|
{
|
|
if (humanoid->computeNearlyTouched())
|
|
{
|
|
noTouchTimer = 0.0f;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool HumanoidState::computeTouchedHard()
|
|
{
|
|
return humanoid->getTouchedHard();
|
|
}
|
|
|
|
bool HumanoidState::computeActivatePhysics()
|
|
{
|
|
return humanoid->getActivatePhysics();
|
|
}
|
|
|
|
// Ragdoll Entrance Criteria - make it conservative
|
|
//
|
|
bool HumanoidState::computeHitByHighImpactObject()
|
|
{
|
|
Body* root = getHumanoid()->getRootBodyFast();
|
|
Velocity rootVelocity = root->getVelocity();
|
|
|
|
PartInstance* part = humanoid->getTorsoSlow();
|
|
Primitive* humanPrim = part ? part->getPartPrimitive() : NULL;
|
|
if ( humanPrim )
|
|
{
|
|
for (int i = 0; i < humanPrim->getNumContacts(); ++i )
|
|
{
|
|
Contact* contact = humanPrim->getContact(i);
|
|
RBXASSERT( contact );
|
|
|
|
for( int j = 0; j < contact->numConnectors(); ++j )
|
|
{
|
|
ContactConnector* connector = contact->getConnector(j);
|
|
RBXASSERT( connector );
|
|
|
|
lastContactVel = connector->computeRelativeVelocity();
|
|
if ( lastContactVel <= getHumanoid()->getRagdollCriteria() )
|
|
continue; // Slow contact speed, reject
|
|
|
|
Vector3 position, normal;
|
|
float length;
|
|
connector->getLengthNormalPosition(position, normal, length);
|
|
|
|
Vector3 humanVelocity = humanPrim->getConstBody()->getPvFast().linearVelocityAtPoint(position);
|
|
float humanNormalVel = normal.dot(humanVelocity);
|
|
if ( humanNormalVel < 0.0f )
|
|
humanNormalVel = -humanNormalVel;
|
|
|
|
const Primitive* objectPrim = contact->getConstPrimitive(0) == humanPrim ? contact->getConstPrimitive(1) : contact->getConstPrimitive(0);
|
|
Vector3 objectVelocity = objectPrim->getConstBody()->getPvFast().linearVelocityAtPoint(position);
|
|
float objectNormalVel = normal.dot(objectVelocity);
|
|
if ( objectNormalVel < 0.0f )
|
|
objectNormalVel = -objectNormalVel;
|
|
|
|
if ( humanNormalVel >= objectNormalVel )
|
|
continue; // Fast human and slow object, reject
|
|
return true; // Slow human hit by fast object
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void HumanoidState::findFloor(shared_ptr<PartInstance>& oldFloor)
|
|
{
|
|
if (Primitive* torsoPrim = humanoid->getTorsoPrimitiveSlow())
|
|
{
|
|
const CoordinateFrame& torsoC = torsoPrim->getCoordinateFrame();
|
|
|
|
Vector3 halfSize = 0.4f * torsoPrim->getSize();
|
|
|
|
float maxDistance = 1.0;
|
|
|
|
// checking if I had a floor last check
|
|
float hysteresis = (oldFloor != NULL) ? 1.5f : 1.1f;
|
|
|
|
// Increase the hysteresis based on our vertical velocity
|
|
float verticalVelocity = fabs(torsoPrim->getBody()->getVelocity().linear.y);
|
|
if (verticalVelocity > 100.0f)
|
|
hysteresis += verticalVelocity / 100.0f;
|
|
|
|
if (PartInstance* foundLeftLeg = humanoid->getLeftLegSlow()) {
|
|
maxDistance += hysteresis * foundLeftLeg->getPartPrimitive()->getSize().y;
|
|
}
|
|
else if (PartInstance* foundRightLeg = humanoid->getRightLegSlow()) {
|
|
maxDistance += hysteresis * foundRightLeg->getPartPrimitive()->getSize().y;
|
|
}
|
|
|
|
if (DFFlag::EnableHipHeight)
|
|
{
|
|
maxDistance += (hysteresis * humanoid->getHipHeight());
|
|
}
|
|
|
|
Assembly* humanoidAssembly = torsoPrim->getAssembly();
|
|
|
|
Vector3 floorHitLocationAccumulate;
|
|
int floorHitCount = 0;
|
|
|
|
// check in direction of motion
|
|
float zOffset[] = { 0.0f, 1.0f, -1.0f, 2.0f, -2.0f }; // order the rays to be closest to center first
|
|
for (int z = 0; z <= 4; z++)
|
|
{
|
|
Vector3 offset (0.0f, -halfSize.y, zOffset[z] * halfSize.z);
|
|
bool UpdateFloorPart = (zOffset[z] < 2.0f && zOffset[z] > -2.0f);
|
|
AverageFloorRayCast(floorPart, floorTouchInWorld, floorTouchNormal, floorMaterial, floorHitLocationAccumulate, floorHitCount, UpdateFloorPart,
|
|
offset, maxDistance, humanoidAssembly, torsoC);
|
|
}
|
|
|
|
if (floorPart == NULL)
|
|
{
|
|
// if nothing on the center line, check for shoulders
|
|
for (int x = -1; x <= 1; x+=2) {
|
|
for (int z = -1; z <= 1; z+=2)
|
|
{
|
|
Vector3 offset (x * halfSize.x, -halfSize.y, z * halfSize.z);
|
|
AverageFloorRayCast(floorPart, floorTouchInWorld, floorTouchNormal, floorMaterial, floorHitLocationAccumulate, floorHitCount, true, offset,
|
|
maxDistance, humanoidAssembly, torsoC);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (floorPart != NULL)
|
|
{
|
|
floorHumanoidLocationInWorld = floorHitLocationAccumulate / (float) floorHitCount;
|
|
noFloorTimer = 0.0f;
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Filters body parts during hit tests with floor
|
|
HitTestFilter::Result HumanoidState::filterResult(const Primitive* testMe) const
|
|
{
|
|
RBXASSERT(testMe);
|
|
|
|
HitTestFilter::Result newResult = HitTestFilter::INCLUDE_PRIM;
|
|
|
|
if ( (!testMe->getCanCollide())
|
|
|| (filteringAssembly && (filteringAssembly == testMe->getConstAssembly())))
|
|
{
|
|
newResult = HitTestFilter::IGNORE_PRIM;
|
|
}
|
|
|
|
return newResult;
|
|
}
|
|
|
|
void HumanoidState::AverageFloorRayCast(shared_ptr<PartInstance> &floorPart, Vector3& floorPartHitLocation, Vector3& floorPartHitNormal,
|
|
PartMaterial& floorPartHitMaterial, Vector3& hitLocationAccumulator, int& hitLocationCount, const bool UpdateFloorPart,
|
|
const Vector3& offset, const float maxDistance, Assembly* humanoidAssembly, const CoordinateFrame& torsoC)
|
|
{
|
|
shared_ptr<PartInstance> floor;
|
|
PartMaterial floorHitMaterial;
|
|
Vector3 floorHitLocation;
|
|
Vector3 floorHitNormal;
|
|
Vector3 pointOnTorso = torsoC.pointToWorldSpace(offset);
|
|
|
|
RbxRay tryRay = RbxRay::fromOriginAndDirection(pointOnTorso, -Vector3::unitY());
|
|
if ((floor = tryFloor(tryRay, floorHitLocation, floorHitNormal, maxDistance, humanoidAssembly, floorHitMaterial)))
|
|
{
|
|
hitLocationAccumulator += floorHitLocation;
|
|
hitLocationCount++;
|
|
if (UpdateFloorPart && floorPart == NULL)
|
|
{
|
|
floorPartHitMaterial = floorHitMaterial;
|
|
floorPartHitLocation = floorHitLocation;
|
|
floorPartHitNormal = floorHitNormal;
|
|
floorPart = floor;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
shared_ptr<PartInstance> HumanoidState::tryFloor(const RbxRay& ray, Vector3& hitLocation, Vector3& hitNormal, float maxDistance, Assembly* humanoidAssembly, PartMaterial& recentFloorMaterial)
|
|
{
|
|
RBXASSERT(humanoid->getWorld());
|
|
|
|
RBXASSERT(humanoidAssembly);
|
|
filteringAssembly = humanoidAssembly;
|
|
Primitive* floorPrim = NULL;
|
|
|
|
RBXASSERT(ray.direction().isUnit());
|
|
RbxRay worldRay = RbxRay::fromOriginAndDirection(ray.origin(), ray.direction());
|
|
worldRay.direction() *= maxDistance;
|
|
|
|
floorPrim = humanoid->getWorld()->getContactManager()->getHit(worldRay,
|
|
NULL,
|
|
this,
|
|
hitLocation,
|
|
false,
|
|
true,
|
|
hitNormal,
|
|
recentFloorMaterial);
|
|
|
|
shared_ptr<PartInstance> answer;
|
|
if (floorPrim && (floorPrim->getAssembly() != humanoidAssembly)) {
|
|
answer = shared_from<PartInstance>(PartInstance::fromPrimitive(floorPrim));
|
|
}
|
|
return answer;
|
|
}
|
|
|
|
// This works even when throttling and the velocity is not in synch with the character velocity
|
|
const Velocity HumanoidState::getFloorPointVelocity()
|
|
{
|
|
RBXASSERT(humanoid->getWorld());
|
|
|
|
if (Primitive* p = getFloorPrimitive())
|
|
{
|
|
RBXASSERT(p->getWorld());
|
|
if (p->getWorld())
|
|
{
|
|
RBXASSERT(floorTouchInWorld != unitializedFloorTouch());
|
|
|
|
World* world = humanoid->getWorld();
|
|
|
|
Velocity throttledV;
|
|
if (DFFlag::HumanoidFloorPVUpdateSignal && getHumanoid()->shouldNotApplyFloorVelocity(p))
|
|
{
|
|
throttledV = Velocity(Vector3(0,0,0), Vector3(0,0,0));
|
|
}
|
|
else
|
|
{
|
|
throttledV = p->getPV().velocityAtPoint(floorTouchInWorld); // this velocity could be inaccurate
|
|
float throttle = world->getEnvironmentSpeed();
|
|
|
|
throttledV.linear *= throttle;
|
|
throttledV.rotational *= throttle;
|
|
}
|
|
|
|
return throttledV;
|
|
}
|
|
}
|
|
return Velocity::zero();
|
|
}
|
|
|
|
Vector3 HumanoidState::getRelativeMovementVelocity()
|
|
{
|
|
RBX::Primitive *torso = humanoid->getTorsoPrimitiveSlow();
|
|
if (torso)
|
|
{
|
|
if (DFFlag::NoWalkAnimWeld && torso->getAssembly() && torso->getAssembly()->getAssemblyState() == Sim::ANCHORED)
|
|
return Vector3::zero();
|
|
|
|
Vector3 walkVelocity = torso->getBody()->getVelocity().linear;
|
|
Vector3 floorVelocity;
|
|
floorVelocity = getFloorPointVelocity().linear;
|
|
return (walkVelocity - floorVelocity);
|
|
}
|
|
else
|
|
{
|
|
return Vector3::zero();
|
|
}
|
|
}
|
|
|
|
void HumanoidState::fireMovementSignal(rbx::signal<void(float)>& movementSignal, float movementVelocity)
|
|
{
|
|
if (humanoid->getTorsoPrimitiveSlow() &&
|
|
!movementSignal.empty())
|
|
{
|
|
if (DFFlag::EnableClimbingDirection)
|
|
{
|
|
float clampedVelocity = (std::abs(movementVelocity) < minMoveVelocity()) ? 0.0f : movementVelocity;
|
|
float absVelocity = std::abs(clampedVelocity);
|
|
|
|
//This fixes an issue where changing climbing direction instantly will make your direction not ever hit 0, causing the absolute checks to not see a change.
|
|
bool opposites = ((lastMovementVelocity < 0 && clampedVelocity > 0) || (lastMovementVelocity > 0 && clampedVelocity < 0));
|
|
float absMovementVelocity = std::abs(lastMovementVelocity);
|
|
float low = absMovementVelocity * 0.95f;
|
|
float high = absMovementVelocity * 1.05f;
|
|
|
|
|
|
if ((absVelocity < low) || (absVelocity > high) || opposites)
|
|
{
|
|
movementSignal(clampedVelocity);
|
|
lastMovementVelocity = clampedVelocity;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float clampedVelocity = (movementVelocity < minMoveVelocity()) ? 0.0f : movementVelocity;
|
|
|
|
float low = lastMovementVelocity * 0.95f;
|
|
float high = lastMovementVelocity * 1.05f;
|
|
if ((clampedVelocity < low) || (clampedVelocity > high))
|
|
{
|
|
movementSignal(clampedVelocity);
|
|
lastMovementVelocity = clampedVelocity;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
float HumanoidState::getCharacterHipHeight() const
|
|
{
|
|
float height = 0.0f;
|
|
|
|
if (Primitive* p = humanoid->getTorsoPrimitiveSlow())
|
|
height += 0.5f * p->getSize().y;
|
|
|
|
if (Primitive* p = PartInstance::getPrimitive(humanoid->getLeftLegSlow())) {
|
|
height += p->getSize().y;
|
|
}
|
|
else if ((p = PartInstance::getPrimitive(humanoid->getRightLegSlow()))) {
|
|
height += p->getSize().y;
|
|
}
|
|
|
|
if (DFFlag::EnableHipHeight)
|
|
{
|
|
height += humanoid->getHipHeight();
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
float HumanoidState::getDesiredAltitude() const
|
|
{
|
|
float desiredAltitude = getFloorHumanoidLocationInWorld().y;
|
|
|
|
return desiredAltitude + getCharacterHipHeight();
|
|
}
|
|
|
|
// Some constants
|
|
// Nyquist rate = 6 samples per stud, use slightly more so we're not integer stud aligned
|
|
float sampleSpacing() {return 1.0f / 7.0f;}
|
|
float lowLadderSearch(HumanoidState *pHumanoidState)
|
|
{
|
|
if (DFFlag::EnableHipHeight)
|
|
{
|
|
return -0.3f + pHumanoidState->getCharacterHipHeight();
|
|
} else {
|
|
return 2.7; // tweaked - not, should take leg length into account!
|
|
}
|
|
}
|
|
|
|
float searchDepth(Humanoid *pHumanoid) {
|
|
if (pHumanoid && pHumanoid->getUseR15())
|
|
{
|
|
return 1.2f;
|
|
}
|
|
else
|
|
{
|
|
return 0.7f;
|
|
}
|
|
} // studs from the middle of leg to the max depth to search for a rung or step
|
|
float ladderSearchDistance(Humanoid *pHumanoid) {return searchDepth(pHumanoid) * 1.5f; } // 1.5x search depth
|
|
|
|
|
|
bool HumanoidState::findPrimitiveInLadderZone(Adorn* adorn)
|
|
{
|
|
|
|
Body* torsoBody = humanoid->getTorsoPrimitiveSlow()->getBody();
|
|
RBXASSERT(torsoBody);
|
|
|
|
const CoordinateFrame& cf = torsoBody->getCoordinateFrame();
|
|
GeometryService *geom = ServiceProvider::find<GeometryService>(humanoid);
|
|
RBXASSERT(geom);
|
|
|
|
float bottom = -lowLadderSearch(this);
|
|
float top = 0.0f;
|
|
float radius = 0.5f * ladderSearchDistance(humanoid);
|
|
Vector3 center = cf.translation + (cf.lookVector() * ladderSearchDistance(humanoid) * 0.5f);
|
|
Vector3 min(-radius, bottom, -radius);
|
|
Vector3 max(radius, top, radius);
|
|
|
|
Extents extents(center + min, center + max);
|
|
|
|
foundParts.fastClear();
|
|
geom->getPartsTouchingExtents(extents, NULL, 0, foundParts);
|
|
|
|
Instance* character = getHumanoid()->getParent();
|
|
for (int i = 0; i < foundParts.size(); ++i) {
|
|
PartInstance* foundPart = foundParts[i];
|
|
if (!foundPart->isDescendantOf(character)) {
|
|
if (adorn) {
|
|
Draw::selectionBox(foundPart->getPart(), adorn, Color3::red());
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// only show if nothing touching, otherwise go to ray search
|
|
if (adorn) {
|
|
adorn->setObjectToWorldMatrix(CoordinateFrame());
|
|
adorn->box(
|
|
AABox(extents.min(), extents.max())
|
|
);
|
|
}
|
|
return false;
|
|
|
|
}
|
|
|
|
void HumanoidState::doLadderRaycast(GeometryService *geom, const RbxRay& caster,
|
|
Humanoid* humanoid, Primitive** hitPrimOut, Vector3* hitLocationOut)
|
|
{
|
|
if (humanoid->getWorld()) {
|
|
FilterDescendents filterDescendents(shared_from(humanoid->getParent()));
|
|
FilterHumanoidParts filterHumanoidParts;
|
|
|
|
MergedFilter *mergedFilter = new MergedFilter(&filterHumanoidParts, this);
|
|
|
|
std::vector<const Primitive*> dummy;
|
|
|
|
*hitPrimOut = humanoid->getWorld()->getContactManager()->getHit(
|
|
caster,
|
|
&dummy,
|
|
mergedFilter,
|
|
*hitLocationOut);
|
|
|
|
if (mergedFilter)
|
|
delete mergedFilter;
|
|
} else {
|
|
*hitLocationOut = Vector3::zero();
|
|
}
|
|
}
|
|
|
|
void HumanoidState::doAutoJump()
|
|
{
|
|
if(!humanoid) {
|
|
return;
|
|
}
|
|
|
|
if (!humanoid->getAutoJumpEnabled() )
|
|
return;
|
|
|
|
float rayLength = 1.5;
|
|
// This is how high a ROBLOXian jumps from the mid point of his torso
|
|
float jumpHeight = 7.0;
|
|
|
|
PartInstance* torso = humanoid->getTorsoSlow();
|
|
if(!torso) {
|
|
return;
|
|
}
|
|
|
|
const CoordinateFrame& torsoCFrame = torso->getCoordinateFrame();
|
|
Vector3 torsoLookVector = torsoCFrame.lookVector();
|
|
Vector3 torsoPos = torsoCFrame.translation;
|
|
|
|
GeometryService *geom = ServiceProvider::create<GeometryService>(humanoid);
|
|
if (geom)
|
|
{
|
|
shared_ptr<PartInstance> hitPart;
|
|
shared_ptr<PartInstance> jumpHitPart;
|
|
|
|
RbxRay torsoRay(torsoPos + Vector3(0, -torso->getConstPartPrimitive()->getSize().y/2, 0), torsoLookVector * rayLength);
|
|
RbxRay jumpRay(torsoPos + Vector3(0, jumpHeight - torso->getConstPartPrimitive()->getSize().y, 0), torsoLookVector * rayLength);
|
|
|
|
Vector3 unusedSurfaceNormal;
|
|
PartMaterial unusedSurfaceMaterial;
|
|
|
|
geom->getHitLocationPartFilterDescendents(torso->getParent(), torsoRay, hitPart, unusedSurfaceNormal, unusedSurfaceMaterial, false, false);
|
|
geom->getHitLocationPartFilterDescendents(torso->getParent(), jumpRay, jumpHitPart, unusedSurfaceNormal, unusedSurfaceMaterial, false, false);
|
|
|
|
if(hitPart && !jumpHitPart && hitPart->getCanCollide())
|
|
{
|
|
humanoid->setJump(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HumanoidState::findLadder(Adorn* adorn)
|
|
{
|
|
if (!humanoid->getStateTransitionEnabled(HUMAN::CLIMBING))
|
|
return false;
|
|
|
|
PartInstance* foundBody = humanoid->getTorsoSlow();
|
|
if (!foundBody)
|
|
return false;
|
|
|
|
// First Check - blow out if no primitives other than floor found
|
|
if (!findPrimitiveInLadderZone(adorn))
|
|
return false;
|
|
|
|
const CoordinateFrame& torsoCoord = foundBody->getCoordinateFrame();
|
|
Vector3 torsoLook = torsoCoord.lookVector();
|
|
|
|
GeometryService *geom = ServiceProvider::create<GeometryService>(humanoid);
|
|
|
|
// climbable conditions
|
|
float first_space = 0.0f; // needs to be < 2.45f
|
|
float first_step = 0.0f; // needs to be > 0 and < 2.45f
|
|
float first_step_mag = -1.0f;
|
|
float second_step = -1.0f;
|
|
|
|
bool look_for_space = true;
|
|
bool look_for_step = false;
|
|
|
|
Color3 color = Color3::orange(); // only when rendering
|
|
float heightScale = getCharacterHipHeight() / 3.0f;
|
|
float spacing = sampleSpacing() * heightScale;
|
|
|
|
int topRay = static_cast<int>(lowLadderSearch(this) / spacing);
|
|
topRay += 10;
|
|
|
|
for (int i = 1; i < topRay; i++) // search for 3 studs, starting at toes NOTE - removed bottom search
|
|
{
|
|
float distanceFromBottom = (float)i * spacing;
|
|
Vector3 originOnTorso(0, -lowLadderSearch(this) + distanceFromBottom, 0);
|
|
RbxRay caster(torsoCoord.translation + originOnTorso, torsoLook * ladderSearchDistance(humanoid) );
|
|
|
|
Primitive* hitPrim = NULL;
|
|
Vector3 hitLoc;
|
|
doLadderRaycast(geom, caster, humanoid, &hitPrim, &hitLoc);
|
|
|
|
// make trusses climbable.
|
|
if (hitPrim)
|
|
{
|
|
PartInstance *p = PartInstance::fromPrimitive(hitPrim);
|
|
if (p)
|
|
{
|
|
if (p->getPartType() == RBX::TRUSS_PART) return true;
|
|
}
|
|
}
|
|
|
|
float mag = (hitLoc - caster.origin()).magnitude();
|
|
|
|
if (hitPrim && mag < searchDepth(humanoid)) {
|
|
// this is a step or rung
|
|
color = Color3::gray();
|
|
if (look_for_space)
|
|
{
|
|
// we were looking for a space and now we've found the end of it
|
|
first_space = distanceFromBottom;
|
|
look_for_space = false;
|
|
look_for_step = true;
|
|
color = Color3::green();
|
|
first_step_mag = mag;
|
|
}
|
|
else if (!look_for_step && second_step < 0.0f)
|
|
{
|
|
second_step = distanceFromBottom;
|
|
color = Color3::blue();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
color = Color3::yellow();
|
|
|
|
// this is a space
|
|
if (look_for_step)
|
|
{
|
|
// we were looking for a step and now we've found the end of it
|
|
first_step = distanceFromBottom - first_space;
|
|
look_for_step = false;
|
|
color = Color3::red();
|
|
}
|
|
}
|
|
|
|
// debug ray to show probes
|
|
if (adorn) {
|
|
adorn->setObjectToWorldMatrix(CoordinateFrame(Vector3(0,0,0)));\
|
|
|
|
if (mag < searchDepth(humanoid))
|
|
{
|
|
caster.direction() = torsoLook * mag;
|
|
}
|
|
adorn->ray(caster, color);
|
|
}
|
|
|
|
/*
|
|
if (adorn) {
|
|
adorn->setObjectToWorldMatrix(torsoCoord.translation);
|
|
RbxRay rayInPart = RbxRay::fromOriginAndDirection( caster.origin() - torsoCoord.translation,
|
|
hitLoc - caster.origin() );
|
|
adorn->ray(rayInPart, color);
|
|
}
|
|
*/
|
|
}
|
|
|
|
if (first_space < maxClimbDistance() * heightScale &&
|
|
first_step > 0.0f &&
|
|
first_step < maxClimbDistance() * heightScale &&
|
|
(second_step >= 0.0f || first_space > (maxClimbDistance() * heightScale / 5.0f))) // did we find a second step or was the first step up high?
|
|
{
|
|
if (adorn) {
|
|
DrawAdorn::cylinder(adorn, CoordinateFrame(torsoCoord.translation + Vector3(0,5,0)), 1, 0.2, 1.0, Color3::purple());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void HumanoidState::setLegsCanCollide(bool canCollide)
|
|
{
|
|
// TODO: put somewhere else, only when leg added/removed
|
|
if (PartInstance* leftLeg = getHumanoid()->getLeftLegSlow())
|
|
leftLeg->getPartPrimitive()->setPreventCollide(!canCollide);
|
|
|
|
if (PartInstance* rightLeg = getHumanoid()->getRightLegSlow())
|
|
rightLeg->getPartPrimitive()->setPreventCollide(!canCollide);
|
|
}
|
|
|
|
void HumanoidState::setArmsCanCollide(bool canCollide)
|
|
{
|
|
// TODO: put somewhere else, only when leg added/removed
|
|
if (PartInstance* leftArm = getHumanoid()->getLeftArmSlow())
|
|
leftArm->getPartPrimitive()->setPreventCollide(!canCollide);
|
|
|
|
if (PartInstance* rightArm = getHumanoid()->getRightArmSlow())
|
|
rightArm->getPartPrimitive()->setPreventCollide(!canCollide);
|
|
}
|
|
|
|
void HumanoidState::setHeadCanCollide(bool canCollide)
|
|
{
|
|
if (PartInstance* head = getHumanoid()->getHeadSlow())
|
|
head->getPartPrimitive()->setPreventCollide(!canCollide);
|
|
}
|
|
|
|
void HumanoidState::setTorsoCanCollide(bool canCollide)
|
|
{
|
|
if (DFFlag::UseR15Character && getHumanoid()->getUseR15())
|
|
{
|
|
PartInstance* lowertorso = Instance::fastDynamicCast<PartInstance>(getHumanoid()->getParent()->findFirstChildByName("LowerTorso"));
|
|
PartInstance* uppertorso = getHumanoid()->getVisibleTorsoSlow();
|
|
if (lowertorso)
|
|
lowertorso->getPartPrimitive()->setPreventCollide(!canCollide);
|
|
if (uppertorso)
|
|
uppertorso->getPartPrimitive()->setPreventCollide(!canCollide);
|
|
} else {
|
|
PartInstance* torso = getHumanoid()->getVisibleTorsoSlow();
|
|
if (torso)
|
|
torso->getPartPrimitive()->setPreventCollide(!canCollide);
|
|
}
|
|
}
|
|
|
|
void HumanoidState::setNearlyTouched()
|
|
{
|
|
nearlyTouched = true;
|
|
}
|
|
|
|
#pragma optimize("s", on)
|
|
// prevent inlining of computeEvent
|
|
unsigned int HumanoidState::checkComputeEvent()
|
|
{
|
|
VMProtectBeginMutation(NULL);
|
|
// these all return true:
|
|
torsoHasBuoyancy = true;
|
|
facingLadder = true;
|
|
humanoid->sit = true;
|
|
humanoid->platformStanding = true;
|
|
bool positiveTests = computeEvent(HAS_BUOYANCY) && !computeEvent(NO_BUOYANCY)
|
|
&& computeEvent(OFF_FLOOR) && computeEvent(SIT_CMD)
|
|
&& computeEvent(PLATFORM_STAND_CMD) && computeEvent(FACE_LDR);
|
|
|
|
// these all return false
|
|
torsoHasBuoyancy = false;
|
|
facingLadder = false;
|
|
humanoid->sit = false;
|
|
humanoid->platformStanding = false;
|
|
bool negativeTests = !(computeEvent(HAS_BUOYANCY) || !computeEvent(NO_BUOYANCY)
|
|
|| computeEvent(ON_FLOOR) || computeEvent(SIT_CMD)
|
|
|| computeEvent(PLATFORM_STAND_CMD) || computeEvent(FACE_LDR));
|
|
|
|
volatile unsigned int returnValue = static_cast<unsigned int>(positiveTests) + static_cast<unsigned int>(negativeTests);
|
|
VMProtectEnd();
|
|
return returnValue;
|
|
}
|
|
#pragma optimize("", on)
|
|
|
|
|
|
} // namespace HUMAN
|
|
} // namespace RBX
|