Migrate to flamework + strating rewrite ragdoll.

This commit is contained in:
2025-10-26 22:05:57 +01:00
parent da615efafa
commit 02947cc391
20 changed files with 1068 additions and 400 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
/out /out
/include /include
*.tsbuildinfo *.tsbuildinfo
flamework.build

View File

@@ -11,5 +11,10 @@
}, },
"eslint.run": "onType", "eslint.run": "onType",
"eslint.format.enable": true, "eslint.format.enable": true,
"eslint.useFlatConfig": false "eslint.useFlatConfig": false,
"files.autoSave": "onWindowChange",
"editor.formatOnSave": true,
"files.associations": {
"*.luau": "lua"
}
} }

View File

@@ -20,6 +20,9 @@
"$className": "Folder", "$className": "Folder",
"@rbxts": { "@rbxts": {
"$path": "node_modules/@rbxts" "$path": "node_modules/@rbxts"
},
"@flamework": {
"$path": "node_modules/@flamework"
} }
} }
}, },

View File

@@ -46,6 +46,9 @@ export default defineConfig([
rules: { rules: {
"prettier/prettier": "warn", "prettier/prettier": "warn",
"@typescript-eslint/no-unused-vars": ["error", {
"varsIgnorePattern": "^[A-Z].*(Service|Controller|Component)$"
}]
}, },
}, },
]); ]);

990
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,14 +9,13 @@
"lint": "eslint" "lint": "eslint"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "Aconit team",
"license": "ISC", "license": "ISC",
"type": "commonjs", "type": "commonjs",
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.37.0", "@eslint/js": "^9.37.0",
"@rbxts/compiler-types": "^3.0.0-types.0", "@rbxts/compiler-types": "^3.0.0-types.0",
"@rbxts/net": "^3.0.10",
"@rbxts/services": "^1.5.5", "@rbxts/services": "^1.5.5",
"@rbxts/signal": "^1.1.1", "@rbxts/signal": "^1.1.1",
"@rbxts/types": "^1.0.882", "@rbxts/types": "^1.0.882",
@@ -27,11 +26,15 @@
"eslint-plugin-prettier": "^5.5.4", "eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-roblox-ts": "^1.2.1", "eslint-plugin-roblox-ts": "^1.2.1",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"rbxts-transformer-flamework": "^1.3.2",
"rbxts-transformer-instances": "^1.0.1",
"rbxts-transformer-services": "^1.1.1", "rbxts-transformer-services": "^1.1.1",
"roblox-ts": "^3.0.0", "roblox-ts": "^3.0.0",
"typescript": "^5.9.2" "typescript": "~5.5.3"
}, },
"dependencies": { "dependencies": {
"-": "^0.0.1" "@flamework/components": "^1.3.2",
"@flamework/core": "^1.3.2",
"@flamework/networking": "^1.3.2"
} }
} }

View File

@@ -1,16 +1,20 @@
import { Players, UserInputService } from "@rbxts/services"; import { UserInputService } from "@rbxts/services";
import Gameplay_Remotes from "shared/gameplay"; import { GameplayClientEvents } from "./networking";
import { Controller, OnStart } from "@flamework/core";
const sprint = Gameplay_Remotes.Client.Get("Sprint"); @Controller()
class GameplayController implements OnStart {
UserInputService.InputBegan.Connect((input) => { onStart(): void {
UserInputService.InputBegan.Connect((input) => {
if (input.KeyCode === Enum.KeyCode.LeftShift) { if (input.KeyCode === Enum.KeyCode.LeftShift) {
sprint.SendToServer(true); GameplayClientEvents.sprint(true);
} }
}); });
UserInputService.InputEnded.Connect((input) => { UserInputService.InputEnded.Connect((input) => {
if (input.KeyCode === Enum.KeyCode.LeftShift) { if (input.KeyCode === Enum.KeyCode.LeftShift) {
sprint.SendToServer(false); GameplayClientEvents.sprint(false);
} }
}); });
}
}

3
src/client/networking.ts Normal file
View File

@@ -0,0 +1,3 @@
import { GameplayEvents } from "shared/networking";
export const GameplayClientEvents = GameplayEvents.createClient({});

View File

@@ -1,29 +1,35 @@
import { OnInit, 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 { makeRagdoll, stopRagdoll, takeNetworkOwner } from "shared/ragdoll";
const ragdollCommand = new Instance("TextChatCommand"); /**
ragdollCommand.Parent = TextChatService.WaitForChild("TextChatCommands"); * Setup Chat Command
ragdollCommand.PrimaryAlias = "/ragdoll"; */
ragdollCommand.Triggered.Connect((TextSource) => { @Service()
class CommandsService implements OnStart {
onStart(): void {
const ragdollCommand = new TextChatCommand();
ragdollCommand.Parent = TextChatService.WaitForChild("TextChatCommands");
ragdollCommand.PrimaryAlias = "/ragdoll";
ragdollCommand.Triggered.Connect((TextSource) => {
const player = Players.GetPlayerByUserId(TextSource.UserId); const player = Players.GetPlayerByUserId(TextSource.UserId);
makeRagdoll(player!.Character!); makeRagdoll(player!.Character!);
}); });
const unragdollCommand = new Instance("TextChatCommand"); const unragdollCommand = new TextChatCommand();
unragdollCommand.Parent = TextChatService.WaitForChild("TextChatCommands"); unragdollCommand.Parent = TextChatService.WaitForChild("TextChatCommands");
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!); stopRagdoll(player!.Character!);
}); });
const takeownerCommand = new Instance("TextChatCommand"); const takeownerCommand = new TextChatCommand();
takeownerCommand.Parent = TextChatService.WaitForChild("TextChatCommands"); takeownerCommand.Parent = TextChatService.WaitForChild("TextChatCommands");
takeownerCommand.PrimaryAlias = "/takeowner"; takeownerCommand.PrimaryAlias = "/takeowner";
takeownerCommand.Triggered.Connect((TextSource) => { takeownerCommand.Triggered.Connect((TextSource) => {
const player = Players.GetPlayerByUserId(TextSource.UserId); const player = Players.GetPlayerByUserId(TextSource.UserId);
takeNetworkOwner(player!.Character!); takeNetworkOwner(player!.Character!);
}); });
}
}

View File

@@ -1,12 +1,21 @@
import { OnStart, Service } from "@flamework/core";
import { ReplicatedStorage } from "@rbxts/services"; import { ReplicatedStorage } from "@rbxts/services";
import { randomDecorationPlace } from "shared/build"; import { randomDecorationPlace } from "shared/build";
const flowers = ReplicatedStorage.FindFirstChild("Decoration")?.FindFirstChild("FlowerTypes")?.GetChildren(); /** Place Decorator on map. */
if (flowers) { @Service()
class DecoratorService implements OnStart {
onStart(): void {
//#region Castle
const flowers = ReplicatedStorage.FindFirstChild("Decoration")?.FindFirstChild("FlowerTypes")?.GetChildren();
if (flowers) {
randomDecorationPlace("Build_Flower", flowers as Model[]); randomDecorationPlace("Build_Flower", flowers as Model[]);
} }
const rocks = ReplicatedStorage.FindFirstChild("Decoration")?.FindFirstChild("RockTypes")?.GetChildren(); const rocks = ReplicatedStorage.FindFirstChild("Decoration")?.FindFirstChild("RockTypes")?.GetChildren();
if (rocks) { if (rocks) {
randomDecorationPlace("Build_Gravel", rocks as Model[]); randomDecorationPlace("Build_Gravel", rocks as Model[]);
}
//#endregion
}
} }

View File

@@ -1,62 +1,74 @@
import { Players } from "@rbxts/services"; import { Players } from "@rbxts/services";
import Gameplay_Remotes from "shared/gameplay"; import { GameplayServerEvents } from "./networking";
import { OnStart, Service } from "@flamework/core";
import { getRagdollEvent, makeRagdoll, prepareRagdoll, stopRagdoll, waitMotionLess } from "shared/ragdoll";
const sprint = Gameplay_Remotes.Server.Get("Sprint"); /**
* Service related to Gameplay.
* - Contains Script related to Sprinting, Ragdoll, etc.
*/
@Service()
class GameplayService implements OnStart {
// Sprint config.
private base_sprint_speed = 16;
private sprint_speed = 1.5;
private is_running = new Map<number, boolean>();
const RunAnim = new Instance("Animation"); onStart(): void {
RunAnim.AnimationId = "rbxassetid://88297455683117"; // Handle Animation.
const base_sprint_speed = 16; const RunAnim = new Animation();
const sprint_speed = 1.5; RunAnim.AnimationId = "rbxassetid://88297455683117";
const runanim = new Map<number, AnimationTrack>();
const isrunning = new Map<number, boolean>();
Players.PlayerAdded.Connect((player) => { Players.PlayerAdded.Connect((player) => {
let runningConn: RBXScriptConnection; let running_connection: RBXScriptConnection;
player.CharacterAdded.Connect((character) => { player.CharacterAdded.Connect((character) => {
prepareRagdoll(character);
getRagdollEvent(character).Connect(() => {
makeRagdoll(character);
waitMotionLess(character, 0.1).Wait();
stopRagdoll(character);
});
const humanoid = character.FindFirstChildWhichIsA("Humanoid"); const humanoid = character.FindFirstChildWhichIsA("Humanoid");
if (humanoid) { if (humanoid) {
const animator = humanoid.FindFirstChildOfClass("Animator"); // Setup the animation.
const anim = animator!.LoadAnimation(RunAnim); const animator = humanoid.WaitForChild("Animator") as Animator;
anim.Priority = Enum.AnimationPriority.Action; const animation_track = animator.LoadAnimation(RunAnim);
runanim.set(player.UserId, anim); animation_track.Priority = Enum.AnimationPriority.Action;
runningConn = humanoid.Running.Connect((speed) => { // Change the animation when the running speed change.
if (isrunning.get(player.UserId)) { running_connection = humanoid.Running.Connect((speed) => {
if (this.is_running.get(player.UserId)) {
if (speed > 2) { if (speed > 2) {
if (!anim.IsPlaying) anim.Play(0.2); if (!animation_track.IsPlaying) animation_track.Play(0.2);
} else { } else {
if (anim.IsPlaying) anim?.Stop(0.2); if (animation_track.IsPlaying) animation_track?.Stop(0.2);
} }
} else if (anim?.IsPlaying) { } else if (animation_track?.IsPlaying) {
anim?.Stop(1); animation_track?.Stop(1);
} }
}); });
} }
}); });
// Clean up the connection when the character died.
player.CharacterRemoving.Once(() => running_connection.Disconnect());
});
player.CharacterRemoving.Once(() => runningConn.Disconnect()); // Change the sprint state with networking
}); GameplayServerEvents.sprint.connect((player, enabled) => {
sprint.Connect((player, enabled) => {
const character = player.Character; const character = player.Character;
isrunning.set(player.UserId, enabled); this.is_running.set(player.UserId, enabled);
if (character) { if (character) {
const humanoid = character.FindFirstChildOfClass("Humanoid"); const humanoid = character.FindFirstChildOfClass("Humanoid");
if (humanoid) { if (humanoid) {
if (enabled) { if (enabled) {
humanoid.WalkSpeed = base_sprint_speed * sprint_speed; humanoid.WalkSpeed = this.base_sprint_speed * this.sprint_speed;
// if (humanoid.GetStateEnabled(Enum.HumanoidStateType.Running)) {
// const anim = runanim.get(player.UserId);
// anim?.Play(0.2);
// }
} else { } else {
humanoid.WalkSpeed = base_sprint_speed; humanoid.WalkSpeed = this.base_sprint_speed;
// if (humanoid.GetStateEnabled(Enum.HumanoidStateType.Running)) {
// const anim = runanim.get(player.UserId);
// anim?.Stop(1);
// }
} }
} }
} }
}); });
}
}

View File

@@ -1,14 +0,0 @@
import { Players } from "@rbxts/services";
import { makeRagdoll, stopRagdoll, waitMotionLess } from "shared/ragdoll";
Players.PlayerAdded.Connect((player) => {
player.CharacterAdded.Connect((character) => {
const humanoid = character.FindFirstChildOfClass("Humanoid");
if (!humanoid) return;
humanoid.FreeFalling.Connect(() => {
makeRagdoll(character);
waitMotionLess(character, 0.1).Wait();
stopRagdoll(character);
});
});
});

3
src/server/networking.ts Normal file
View File

@@ -0,0 +1,3 @@
import { GameplayEvents } from "shared/networking";
export const GameplayServerEvents = GameplayEvents.createServer({});

View File

@@ -0,0 +1,11 @@
class AbilityBuilder {
constructor() {}
}
class PasiveBuilder {
constructor() {}
}
class CharacterBuilder {
constructor() {}
}

View File

@@ -1,7 +0,0 @@
import Net from "@rbxts/net";
const Gameplay_Remotes = Net.Definitions.Create({
Sprint: Net.Definitions.ClientToServerEvent<[enabled: boolean]>(),
});
export default Gameplay_Remotes;

11
src/shared/networking.ts Normal file
View File

@@ -0,0 +1,11 @@
import { Networking } from "@flamework/networking";
const { createEvent, createFunction } = Networking;
interface GameplayClient {
sprint(enabled: boolean): void;
}
interface GameplayServer {}
export const GameplayEvents = createEvent<GameplayClient, GameplayServer>();

View File

@@ -1,5 +1,50 @@
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.
@@ -28,36 +73,57 @@ 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. * Makes the player's character Ragdoll by creating attachments for each part of the R6 Model.
* @param character The character of a player. * @param character The character of a player.
*/ */
export function makeRagdoll(character: Model) { export function makeRagdoll(character: Model) {
takeNetworkOwner(character); takeNetworkOwner(character);
const children = character.GetDescendants();
const humanoid = character.FindFirstChildOfClass("Humanoid")!; const humanoid = character.FindFirstChildOfClass("Humanoid")!;
humanoid.RequiresNeck = false;
humanoid.ChangeState(Enum.HumanoidStateType.Physics); humanoid.ChangeState(Enum.HumanoidStateType.Physics);
children.forEach((motor) => { character.GetDescendants().forEach((des) => {
if (motor.IsA("Motor6D")) { if (des.HasTag("Ragdoll") && des.IsA("BallSocketConstraint")) {
const ballsocketconstraint = new Instance("BallSocketConstraint"), des.Enabled = true;
attachment0 = new Instance("Attachment"), }
attachment1 = new Instance("Attachment"); if (des.IsA("Motor6D")) {
des.Enabled = false;
attachment0.CFrame = motor.C0;
attachment0.Parent = motor.Part0;
attachment0.AddTag("Ragdoll");
attachment1.CFrame = motor.C1;
attachment1.Parent = motor.Part1;
attachment1.AddTag("Ragdoll");
ballsocketconstraint.Attachment0 = attachment0;
ballsocketconstraint.Attachment1 = attachment1;
ballsocketconstraint.Parent = motor.Parent;
ballsocketconstraint.AddTag("Ragdoll");
motor.Enabled = false;
} }
}); });
takeNetworkOwner(character); takeNetworkOwner(character);
@@ -66,8 +132,8 @@ export function makeRagdoll(character: Model) {
export function stopRagdoll(character: Model) { export function stopRagdoll(character: Model) {
releaseNetworkOwnership(character); releaseNetworkOwnership(character);
character.GetDescendants().forEach((des) => { character.GetDescendants().forEach((des) => {
if (des.HasTag("Ragdoll")) { if (des.HasTag("Ragdoll") && des.IsA("BallSocketConstraint")) {
des.Destroy(); des.Enabled = false;
} }
if (des.IsA("Motor6D")) { if (des.IsA("Motor6D")) {
des.Enabled = true; des.Enabled = true;
@@ -96,3 +162,27 @@ export function waitMotionLess(character: Model, threshold: number) {
}); });
return signal; return signal;
} }
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;
}
});
} else {
beatEvent.Disconnect();
fired = false;
}
});
return event;
}

View File

@@ -1,5 +0,0 @@
import Net from "@rbxts/net";
const Remotes = Net.Definitions.Create({
SendAttack: Net.Definitions.ClientToServerEvent<[attackId: number]>(),
});
export default Remotes;

View File

@@ -0,0 +1,6 @@
export type RestOrArray<Type> = Type[] | [Type[]];
export function normalizeArray<ItemType>(arr: RestOrArray<ItemType>): ItemType[] {
if (typeIs(arr[0], "table")) return [...arr[0]];
return arr as ItemType[];
}

View File

@@ -15,13 +15,31 @@
"moduleDetection": "force", "moduleDetection": "force",
"strict": true, "strict": true,
"target": "ESNext", "target": "ESNext",
"typeRoots": ["node_modules/@rbxts"], "typeRoots": [
"node_modules/@rbxts",
"node_modules/@flamework"
],
// configurable // configurable
"rootDir": "src", "rootDir": "src",
"outDir": "out", "outDir": "out",
"baseUrl": "src", "baseUrl": "src",
"incremental": true, "incremental": true,
"tsBuildInfoFile": "out/tsconfig.tsbuildinfo" "tsBuildInfoFile": "out/tsconfig.tsbuildinfo",
"plugins": [
{
"transform": "rbxts-transformer-flamework",
"obfuscation": true
},
{
"transform": "rbxts-transformer-instances"
},
{
"transform": "rbxts-transformer-services"
} }
]
},
"include": [
"src/**/*",
"node_modules/rbxts-transformer-instances"
]
} }