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

View File

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

View File

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

View File

View File

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

View File

@@ -1,50 +1,6 @@
import { RunService } from "@rbxts/services"; import { RunService } from "@rbxts/services";
import Signal from "@rbxts/signal"; 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. * Takes the Network Ownership of a player's character.
* @param character The character of a player. * @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. * Releases the Network Ownership of a player's character.
* @param character The character of a player * @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. * A class which help to ragdoll the player.
* @param character The character of a player.
*/ */
export function makeRagdoll(character: Model) { export class RagdollHandler {
takeNetworkOwner(character); private character: Model;
const humanoid = character.FindFirstChildOfClass("Humanoid")!; private humanoid: Humanoid;
humanoid.ChangeState(Enum.HumanoidStateType.Physics); private ragdollattachment: Attachment[] = [];
character.GetDescendants().forEach((des) => { private ragdollconstraint: BallSocketConstraint[] = [];
if (des.HasTag("Ragdoll") && des.IsA("BallSocketConstraint")) { private motors: Motor6D[] = [];
des.Enabled = true; private root: Part;
} private static ragdollHandlers: Map<Model, RagdollHandler> = new Map();
if (des.IsA("Motor6D")) {
des.Enabled = false;
}
});
takeNetworkOwner(character);
}
export function stopRagdoll(character: Model) { private getRagdollElements() {
releaseNetworkOwnership(character); return [...this.ragdollattachment, ...this.ragdollconstraint];
character.GetDescendants().forEach((des) => { }
if (des.HasTag("Ragdoll") && des.IsA("BallSocketConstraint")) {
des.Enabled = false;
}
if (des.IsA("Motor6D")) {
des.Enabled = true;
}
});
}
/** constructor(character: Model) {
* This function return a signal which fire when the player stop moving. this.character = character;
* @param character The character of the player const humanoid = character.FindFirstAncestorOfClass("Humanoid");
* @param threshold The maximum distance (in studs) the player must travel in 0.3 seconds for the signal to be fired. const root = character.FindFirstChild("HumanoidRootPart");
* @returns The signal. if (!humanoid || !root?.IsA("Part")) throw '"character" isn\'t a player Character. Failed to handle ragdoll.';
*/ this.humanoid = humanoid;
export function waitMotionLess(character: Model, threshold: number) { this.root = root;
const signal = new Signal(); this.prepareRagdoll();
let delta: number = -1; RagdollHandler.ragdollHandlers.set(character, this);
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;
}
export function getRagdollEvent(character: Model) { private prepareRagdoll() {
const humanoid = character.FindFirstChildOfClass("Humanoid"); const children = this.character.GetDescendants();
const root = character.WaitForChild("HumanoidRootPart") as Part; children.forEach((motor) => {
if (!humanoid) throw "nya UwU eat my paw."; if (motor.IsA("Motor6D")) {
const event = new Signal(); this.motors.push(motor);
let fired = false; const ballsocketconstraint = new BallSocketConstraint(),
let beatEvent: RBXScriptConnection; attachment0 = new Attachment(),
humanoid.FreeFalling.Connect((active) => { attachment1 = new Attachment();
if (active) {
beatEvent = RunService.Heartbeat.Connect(() => { attachment0.CFrame = motor.C0;
if (root.AssemblyLinearVelocity.Y < -38 && !fired) { attachment0.Parent = motor.Part0;
event.Fire(); attachment0.AddTag("Ragdoll");
fired = true; 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);
}
} }