Finish ragdoll rewrite, fix some linting error...

This commit is contained in:
2025-10-26 23:37:10 +01:00
parent 02947cc391
commit 015feb07a5
6 changed files with 163 additions and 170 deletions

View File

@@ -1,6 +1,6 @@
import { OnInit, OnStart, Service } from "@flamework/core";
import { OnStart, Service } from "@flamework/core";
import { Players, TextChatService } from "@rbxts/services";
import { makeRagdoll, stopRagdoll, takeNetworkOwner } from "shared/ragdoll";
import { RagdollHandler, takeNetworkOwner } from "shared/ragdoll";
/**
* Setup Chat Command
@@ -13,7 +13,8 @@ class CommandsService implements OnStart {
ragdollCommand.PrimaryAlias = "/ragdoll";
ragdollCommand.Triggered.Connect((TextSource) => {
const player = Players.GetPlayerByUserId(TextSource.UserId);
makeRagdoll(player!.Character!);
const handler = RagdollHandler.getHandlerByCharacter(player!.Character!, true);
handler.ragdoll();
});
const unragdollCommand = new TextChatCommand();
@@ -21,7 +22,8 @@ class CommandsService implements OnStart {
unragdollCommand.PrimaryAlias = "/unragdoll";
unragdollCommand.Triggered.Connect((TextSource) => {
const player = Players.GetPlayerByUserId(TextSource.UserId);
stopRagdoll(player!.Character!);
const handler = RagdollHandler.getHandlerByCharacter(player!.Character!, true);
handler.stopRagdoll();
});
const takeownerCommand = new TextChatCommand();

View File

@@ -1,7 +1,7 @@
import { Players } from "@rbxts/services";
import { GameplayServerEvents } from "./networking";
import { OnStart, Service } from "@flamework/core";
import { getRagdollEvent, makeRagdoll, prepareRagdoll, stopRagdoll, waitMotionLess } from "shared/ragdoll";
import { RagdollHandler } from "shared/ragdoll";
/**
* Service related to Gameplay.
@@ -23,11 +23,11 @@ class GameplayService implements OnStart {
let running_connection: RBXScriptConnection;
player.CharacterAdded.Connect((character) => {
prepareRagdoll(character);
getRagdollEvent(character).Connect(() => {
makeRagdoll(character);
waitMotionLess(character, 0.1).Wait();
stopRagdoll(character);
const ragdollhandler = new RagdollHandler(character);
ragdollhandler.getRagdollFallingEvent().Connect(() => {
ragdollhandler.ragdoll();
ragdollhandler.waitMotionLess().Wait();
ragdollhandler.stopRagdoll();
});
const humanoid = character.FindFirstChildWhichIsA("Humanoid");

View File

@@ -1,11 +1,13 @@
class AbilityBuilder {
constructor() {}
}
// TODO
class PasiveBuilder {
constructor() {}
}
// class AbilityBuilder {
// constructor() {}
// }
class CharacterBuilder {
constructor() {}
}
// class PasiveBuilder {
// constructor() {}
// }
// class CharacterBuilder {
// constructor() {}
// }

View File

View File

@@ -1,6 +1,6 @@
import { Networking } from "@flamework/networking";
const { createEvent, createFunction } = Networking;
const { createEvent } = Networking;
interface GameplayClient {
sprint(enabled: boolean): void;

View File

@@ -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);
}
}