|
|
|
|
@@ -1,50 +1,6 @@
|
|
|
|
|
import { RunService } from "@rbxts/services";
|
|
|
|
|
import Signal from "@rbxts/signal";
|
|
|
|
|
|
|
|
|
|
class RagdollHandler {
|
|
|
|
|
private character: Model;
|
|
|
|
|
private humanoid: Humanoid;
|
|
|
|
|
constructor(character: Model) {
|
|
|
|
|
this.character = character;
|
|
|
|
|
this.humanoid = character.FindFirstAncestorOfClass("Humanoid")!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prepareRagdoll() {
|
|
|
|
|
const children = this.character.GetDescendants();
|
|
|
|
|
children.forEach((motor) => {
|
|
|
|
|
if (motor.IsA("Motor6D")) {
|
|
|
|
|
const ballsocketconstraint = new BallSocketConstraint(),
|
|
|
|
|
attachment0 = new Attachment(),
|
|
|
|
|
attachment1 = new Attachment();
|
|
|
|
|
|
|
|
|
|
attachment0.CFrame = motor.C0;
|
|
|
|
|
attachment0.Parent = motor.Part0;
|
|
|
|
|
attachment0.AddTag("Ragdoll");
|
|
|
|
|
attachment0.Name = motor.Name;
|
|
|
|
|
|
|
|
|
|
attachment1.CFrame = motor.C1;
|
|
|
|
|
attachment1.Parent = motor.Part1;
|
|
|
|
|
attachment1.AddTag("Ragdoll");
|
|
|
|
|
attachment1.Name = motor.Name;
|
|
|
|
|
|
|
|
|
|
ballsocketconstraint.Attachment0 = attachment0;
|
|
|
|
|
ballsocketconstraint.Attachment1 = attachment1;
|
|
|
|
|
ballsocketconstraint.Parent = motor.Parent;
|
|
|
|
|
ballsocketconstraint.AddTag("Ragdoll");
|
|
|
|
|
ballsocketconstraint.Enabled = false;
|
|
|
|
|
|
|
|
|
|
if (motor.Name === "Left Hip") {
|
|
|
|
|
attachment0.CFrame = attachment0.CFrame.add(new Vector3(0.5, 0, 0));
|
|
|
|
|
attachment1.CFrame = attachment1.CFrame.add(new Vector3(0.5, 0, 0));
|
|
|
|
|
} else if (motor.Name === "Right Hip") {
|
|
|
|
|
attachment0.CFrame = attachment0.CFrame.sub(new Vector3(0.5, 0, 0));
|
|
|
|
|
attachment1.CFrame = attachment1.CFrame.sub(new Vector3(0.5, 0, 0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Takes the Network Ownership of a player's character.
|
|
|
|
|
* @param character The character of a player.
|
|
|
|
|
@@ -58,7 +14,6 @@ export function takeNetworkOwner(character: Model) {
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Releases the Network Ownership of a player's character.
|
|
|
|
|
* @param character The character of a player
|
|
|
|
|
@@ -73,116 +28,150 @@ export function releaseNetworkOwnership(character: Model) {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function prepareRagdoll(character: Model) {
|
|
|
|
|
const children = character.GetDescendants();
|
|
|
|
|
const humanoid = character.FindFirstChildOfClass("Humanoid")!;
|
|
|
|
|
humanoid.RequiresNeck = false;
|
|
|
|
|
children.forEach((motor) => {
|
|
|
|
|
if (motor.IsA("Motor6D")) {
|
|
|
|
|
const ballsocketconstraint = new BallSocketConstraint(),
|
|
|
|
|
attachment0 = new Attachment(),
|
|
|
|
|
attachment1 = new Attachment();
|
|
|
|
|
|
|
|
|
|
attachment0.CFrame = motor.C0;
|
|
|
|
|
attachment0.Parent = motor.Part0;
|
|
|
|
|
attachment0.AddTag("Ragdoll");
|
|
|
|
|
attachment0.Name = motor.Name;
|
|
|
|
|
|
|
|
|
|
attachment1.CFrame = motor.C1;
|
|
|
|
|
attachment1.Parent = motor.Part1;
|
|
|
|
|
attachment1.AddTag("Ragdoll");
|
|
|
|
|
attachment1.Name = motor.Name;
|
|
|
|
|
|
|
|
|
|
ballsocketconstraint.Attachment0 = attachment0;
|
|
|
|
|
ballsocketconstraint.Attachment1 = attachment1;
|
|
|
|
|
ballsocketconstraint.Parent = motor.Parent;
|
|
|
|
|
ballsocketconstraint.AddTag("Ragdoll");
|
|
|
|
|
ballsocketconstraint.Enabled = false;
|
|
|
|
|
|
|
|
|
|
if (motor.Name === "Left Hip") {
|
|
|
|
|
attachment0.CFrame = attachment0.CFrame.add(new Vector3(0.5, 0, 0));
|
|
|
|
|
attachment1.CFrame = attachment1.CFrame.add(new Vector3(0.5, 0, 0));
|
|
|
|
|
} else if (motor.Name === "Right Hip") {
|
|
|
|
|
attachment0.CFrame = attachment0.CFrame.sub(new Vector3(0.5, 0, 0));
|
|
|
|
|
attachment1.CFrame = attachment1.CFrame.sub(new Vector3(0.5, 0, 0));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Makes the player's character Ragdoll by creating attachments for each part of the R6 Model.
|
|
|
|
|
* @param character The character of a player.
|
|
|
|
|
* A class which help to ragdoll the player.
|
|
|
|
|
*/
|
|
|
|
|
export function makeRagdoll(character: Model) {
|
|
|
|
|
takeNetworkOwner(character);
|
|
|
|
|
const humanoid = character.FindFirstChildOfClass("Humanoid")!;
|
|
|
|
|
humanoid.ChangeState(Enum.HumanoidStateType.Physics);
|
|
|
|
|
character.GetDescendants().forEach((des) => {
|
|
|
|
|
if (des.HasTag("Ragdoll") && des.IsA("BallSocketConstraint")) {
|
|
|
|
|
des.Enabled = true;
|
|
|
|
|
}
|
|
|
|
|
if (des.IsA("Motor6D")) {
|
|
|
|
|
des.Enabled = false;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
takeNetworkOwner(character);
|
|
|
|
|
}
|
|
|
|
|
export class RagdollHandler {
|
|
|
|
|
private character: Model;
|
|
|
|
|
private humanoid: Humanoid;
|
|
|
|
|
private ragdollattachment: Attachment[] = [];
|
|
|
|
|
private ragdollconstraint: BallSocketConstraint[] = [];
|
|
|
|
|
private motors: Motor6D[] = [];
|
|
|
|
|
private root: Part;
|
|
|
|
|
private static ragdollHandlers: Map<Model, RagdollHandler> = new Map();
|
|
|
|
|
|
|
|
|
|
export function stopRagdoll(character: Model) {
|
|
|
|
|
releaseNetworkOwnership(character);
|
|
|
|
|
character.GetDescendants().forEach((des) => {
|
|
|
|
|
if (des.HasTag("Ragdoll") && des.IsA("BallSocketConstraint")) {
|
|
|
|
|
des.Enabled = false;
|
|
|
|
|
}
|
|
|
|
|
if (des.IsA("Motor6D")) {
|
|
|
|
|
des.Enabled = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
private getRagdollElements() {
|
|
|
|
|
return [...this.ragdollattachment, ...this.ragdollconstraint];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This function return a signal which fire when the player stop moving.
|
|
|
|
|
* @param character The character of the player
|
|
|
|
|
* @param threshold The maximum distance (in studs) the player must travel in 0.3 seconds for the signal to be fired.
|
|
|
|
|
* @returns The signal.
|
|
|
|
|
*/
|
|
|
|
|
export function waitMotionLess(character: Model, threshold: number) {
|
|
|
|
|
const signal = new Signal();
|
|
|
|
|
let delta: number = -1;
|
|
|
|
|
const root = character.FindFirstChild("HumanoidRootPart");
|
|
|
|
|
if (!root?.IsA("Part")) throw "Bro are you an idiot ?";
|
|
|
|
|
task.spawn(() => {
|
|
|
|
|
while (0 > delta || delta > threshold) {
|
|
|
|
|
const lastPos = root.Position;
|
|
|
|
|
task.wait(0.3);
|
|
|
|
|
delta = root.Position.sub(lastPos).Magnitude;
|
|
|
|
|
}
|
|
|
|
|
signal.Fire();
|
|
|
|
|
});
|
|
|
|
|
return signal;
|
|
|
|
|
}
|
|
|
|
|
constructor(character: Model) {
|
|
|
|
|
this.character = character;
|
|
|
|
|
const humanoid = character.FindFirstAncestorOfClass("Humanoid");
|
|
|
|
|
const root = character.FindFirstChild("HumanoidRootPart");
|
|
|
|
|
if (!humanoid || !root?.IsA("Part")) throw '"character" isn\'t a player Character. Failed to handle ragdoll.';
|
|
|
|
|
this.humanoid = humanoid;
|
|
|
|
|
this.root = root;
|
|
|
|
|
this.prepareRagdoll();
|
|
|
|
|
RagdollHandler.ragdollHandlers.set(character, this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getRagdollEvent(character: Model) {
|
|
|
|
|
const humanoid = character.FindFirstChildOfClass("Humanoid");
|
|
|
|
|
const root = character.WaitForChild("HumanoidRootPart") as Part;
|
|
|
|
|
if (!humanoid) throw "nya UwU eat my paw.";
|
|
|
|
|
const event = new Signal();
|
|
|
|
|
let fired = false;
|
|
|
|
|
let beatEvent: RBXScriptConnection;
|
|
|
|
|
humanoid.FreeFalling.Connect((active) => {
|
|
|
|
|
if (active) {
|
|
|
|
|
beatEvent = RunService.Heartbeat.Connect(() => {
|
|
|
|
|
if (root.AssemblyLinearVelocity.Y < -38 && !fired) {
|
|
|
|
|
event.Fire();
|
|
|
|
|
fired = true;
|
|
|
|
|
private prepareRagdoll() {
|
|
|
|
|
const children = this.character.GetDescendants();
|
|
|
|
|
children.forEach((motor) => {
|
|
|
|
|
if (motor.IsA("Motor6D")) {
|
|
|
|
|
this.motors.push(motor);
|
|
|
|
|
const ballsocketconstraint = new BallSocketConstraint(),
|
|
|
|
|
attachment0 = new Attachment(),
|
|
|
|
|
attachment1 = new Attachment();
|
|
|
|
|
|
|
|
|
|
attachment0.CFrame = motor.C0;
|
|
|
|
|
attachment0.Parent = motor.Part0;
|
|
|
|
|
attachment0.AddTag("Ragdoll");
|
|
|
|
|
attachment0.Name = motor.Name;
|
|
|
|
|
this.ragdollattachment.push(attachment0);
|
|
|
|
|
|
|
|
|
|
attachment1.CFrame = motor.C1;
|
|
|
|
|
attachment1.Parent = motor.Part1;
|
|
|
|
|
attachment1.AddTag("Ragdoll");
|
|
|
|
|
attachment1.Name = motor.Name;
|
|
|
|
|
this.ragdollattachment.push(attachment1);
|
|
|
|
|
|
|
|
|
|
ballsocketconstraint.Attachment0 = attachment0;
|
|
|
|
|
ballsocketconstraint.Attachment1 = attachment1;
|
|
|
|
|
ballsocketconstraint.Parent = motor.Parent;
|
|
|
|
|
ballsocketconstraint.AddTag("Ragdoll");
|
|
|
|
|
ballsocketconstraint.Enabled = false;
|
|
|
|
|
this.ragdollconstraint.push(ballsocketconstraint);
|
|
|
|
|
|
|
|
|
|
if (motor.Name === "Left Hip") {
|
|
|
|
|
attachment0.CFrame = attachment0.CFrame.add(new Vector3(0.5, 0, 0));
|
|
|
|
|
attachment1.CFrame = attachment1.CFrame.add(new Vector3(0.5, 0, 0));
|
|
|
|
|
} else if (motor.Name === "Right Hip") {
|
|
|
|
|
attachment0.CFrame = attachment0.CFrame.sub(new Vector3(0.5, 0, 0));
|
|
|
|
|
attachment1.CFrame = attachment1.CFrame.sub(new Vector3(0.5, 0, 0));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
beatEvent.Disconnect();
|
|
|
|
|
fired = false;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return event;
|
|
|
|
|
/**
|
|
|
|
|
* Ragdoll the charactere linked to this class.
|
|
|
|
|
*/
|
|
|
|
|
ragdoll() {
|
|
|
|
|
takeNetworkOwner(this.character);
|
|
|
|
|
this.humanoid.ChangeState(Enum.HumanoidStateType.Physics);
|
|
|
|
|
this.motors.forEach((motor) => (motor.Enabled = false));
|
|
|
|
|
this.ragdollconstraint.forEach((constraint) => (constraint.Enabled = true));
|
|
|
|
|
takeNetworkOwner(this.character);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stop the Ragdoll.
|
|
|
|
|
*/
|
|
|
|
|
stopRagdoll() {
|
|
|
|
|
this.humanoid.ChangeState(Enum.HumanoidStateType.Running);
|
|
|
|
|
this.motors.forEach((motor) => (motor.Enabled = true));
|
|
|
|
|
this.ragdollconstraint.forEach((constraint) => (constraint.Enabled = false));
|
|
|
|
|
releaseNetworkOwnership(this.character);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This function return a signal which fire when the player stop moving.
|
|
|
|
|
* @param threshold The maximum distance (in studs) the player must travel in 0.3 seconds for the signal to be fired.
|
|
|
|
|
* @returns The signal.
|
|
|
|
|
*/
|
|
|
|
|
waitMotionLess(threshold: number = 0.1) {
|
|
|
|
|
const signal = new Signal();
|
|
|
|
|
let delta: number = -1;
|
|
|
|
|
task.spawn(() => {
|
|
|
|
|
while (0 > delta || delta > threshold) {
|
|
|
|
|
const lastPos = this.root.Position;
|
|
|
|
|
task.wait(0.3);
|
|
|
|
|
delta = this.root.Position.sub(lastPos).Magnitude;
|
|
|
|
|
}
|
|
|
|
|
signal.Fire();
|
|
|
|
|
});
|
|
|
|
|
return signal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create an Event which is fired when the player fall at an defined speed.
|
|
|
|
|
* @param maximumSpeed The limit speed before the event fired.
|
|
|
|
|
* @returns A signal which is fired when the player fall at the defined speed.
|
|
|
|
|
*/
|
|
|
|
|
getRagdollFallingEvent(maximumSpeed: number = 38) {
|
|
|
|
|
const event = new Signal();
|
|
|
|
|
let fired = false;
|
|
|
|
|
let beatEvent: RBXScriptConnection;
|
|
|
|
|
this.humanoid.FreeFalling.Connect((active) => {
|
|
|
|
|
if (active) {
|
|
|
|
|
beatEvent = RunService.Heartbeat.Connect(() => {
|
|
|
|
|
if (this.root.AssemblyLinearVelocity.Y < -maximumSpeed && !fired) {
|
|
|
|
|
event.Fire();
|
|
|
|
|
fired = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
beatEvent.Disconnect();
|
|
|
|
|
fired = false;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return event;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Destroy all parts related to ragdoll.
|
|
|
|
|
*/
|
|
|
|
|
destroy() {
|
|
|
|
|
this.getRagdollElements().forEach((element) => element.Destroy());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static getHandlerByCharacter(character: Model, createifmissing?: false): RagdollHandler | undefined;
|
|
|
|
|
static getHandlerByCharacter(character: Model, createifmissing: true): RagdollHandler;
|
|
|
|
|
static getHandlerByCharacter(character: Model, createifmissing: boolean = false) {
|
|
|
|
|
return createifmissing
|
|
|
|
|
? (this.ragdollHandlers.get(character) ?? new this(character))
|
|
|
|
|
: this.ragdollHandlers.get(character);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|