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
/include
*.tsbuildinfo
flamework.build

View File

@@ -11,5 +11,10 @@
},
"eslint.run": "onType",
"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",
"@rbxts": {
"$path": "node_modules/@rbxts"
},
"@flamework": {
"$path": "node_modules/@flamework"
}
}
},

View File

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

992
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,14 +9,13 @@
"lint": "eslint"
},
"keywords": [],
"author": "",
"author": "Aconit team",
"license": "ISC",
"type": "commonjs",
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.37.0",
"@rbxts/compiler-types": "^3.0.0-types.0",
"@rbxts/net": "^3.0.10",
"@rbxts/services": "^1.5.5",
"@rbxts/signal": "^1.1.1",
"@rbxts/types": "^1.0.882",
@@ -27,11 +26,15 @@
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-roblox-ts": "^1.2.1",
"prettier": "^3.6.2",
"rbxts-transformer-flamework": "^1.3.2",
"rbxts-transformer-instances": "^1.0.1",
"rbxts-transformer-services": "^1.1.1",
"roblox-ts": "^3.0.0",
"typescript": "^5.9.2"
"typescript": "~5.5.3"
},
"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 Gameplay_Remotes from "shared/gameplay";
import { UserInputService } from "@rbxts/services";
import { GameplayClientEvents } from "./networking";
import { Controller, OnStart } from "@flamework/core";
const sprint = Gameplay_Remotes.Client.Get("Sprint");
@Controller()
class GameplayController implements OnStart {
onStart(): void {
UserInputService.InputBegan.Connect((input) => {
if (input.KeyCode === Enum.KeyCode.LeftShift) {
GameplayClientEvents.sprint(true);
}
});
UserInputService.InputBegan.Connect((input) => {
if (input.KeyCode === Enum.KeyCode.LeftShift) {
sprint.SendToServer(true);
UserInputService.InputEnded.Connect((input) => {
if (input.KeyCode === Enum.KeyCode.LeftShift) {
GameplayClientEvents.sprint(false);
}
});
}
});
UserInputService.InputEnded.Connect((input) => {
if (input.KeyCode === Enum.KeyCode.LeftShift) {
sprint.SendToServer(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 { makeRagdoll, stopRagdoll, takeNetworkOwner } from "shared/ragdoll";
const ragdollCommand = new Instance("TextChatCommand");
ragdollCommand.Parent = TextChatService.WaitForChild("TextChatCommands");
ragdollCommand.PrimaryAlias = "/ragdoll";
ragdollCommand.Triggered.Connect((TextSource) => {
const player = Players.GetPlayerByUserId(TextSource.UserId);
/**
* Setup Chat Command
*/
@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);
makeRagdoll(player!.Character!);
});
makeRagdoll(player!.Character!);
});
const unragdollCommand = new TextChatCommand();
unragdollCommand.Parent = TextChatService.WaitForChild("TextChatCommands");
unragdollCommand.PrimaryAlias = "/unragdoll";
unragdollCommand.Triggered.Connect((TextSource) => {
const player = Players.GetPlayerByUserId(TextSource.UserId);
stopRagdoll(player!.Character!);
});
const unragdollCommand = new Instance("TextChatCommand");
unragdollCommand.Parent = TextChatService.WaitForChild("TextChatCommands");
unragdollCommand.PrimaryAlias = "/unragdoll";
unragdollCommand.Triggered.Connect((TextSource) => {
const player = Players.GetPlayerByUserId(TextSource.UserId);
stopRagdoll(player!.Character!);
});
const takeownerCommand = new Instance("TextChatCommand");
takeownerCommand.Parent = TextChatService.WaitForChild("TextChatCommands");
takeownerCommand.PrimaryAlias = "/takeowner";
takeownerCommand.Triggered.Connect((TextSource) => {
const player = Players.GetPlayerByUserId(TextSource.UserId);
takeNetworkOwner(player!.Character!);
});
const takeownerCommand = new TextChatCommand();
takeownerCommand.Parent = TextChatService.WaitForChild("TextChatCommands");
takeownerCommand.PrimaryAlias = "/takeowner";
takeownerCommand.Triggered.Connect((TextSource) => {
const player = Players.GetPlayerByUserId(TextSource.UserId);
takeNetworkOwner(player!.Character!);
});
}
}

View File

@@ -1,12 +1,21 @@
import { OnStart, Service } from "@flamework/core";
import { ReplicatedStorage } from "@rbxts/services";
import { randomDecorationPlace } from "shared/build";
const flowers = ReplicatedStorage.FindFirstChild("Decoration")?.FindFirstChild("FlowerTypes")?.GetChildren();
if (flowers) {
randomDecorationPlace("Build_Flower", flowers as Model[]);
}
/** Place Decorator on map. */
@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[]);
}
const rocks = ReplicatedStorage.FindFirstChild("Decoration")?.FindFirstChild("RockTypes")?.GetChildren();
if (rocks) {
randomDecorationPlace("Build_Gravel", rocks as Model[]);
const rocks = ReplicatedStorage.FindFirstChild("Decoration")?.FindFirstChild("RockTypes")?.GetChildren();
if (rocks) {
randomDecorationPlace("Build_Gravel", rocks as Model[]);
}
//#endregion
}
}

View File

@@ -1,62 +1,74 @@
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");
RunAnim.AnimationId = "rbxassetid://88297455683117";
const base_sprint_speed = 16;
const sprint_speed = 1.5;
const runanim = new Map<number, AnimationTrack>();
const isrunning = new Map<number, boolean>();
onStart(): void {
// Handle Animation.
const RunAnim = new Animation();
RunAnim.AnimationId = "rbxassetid://88297455683117";
Players.PlayerAdded.Connect((player) => {
let runningConn: RBXScriptConnection;
Players.PlayerAdded.Connect((player) => {
let running_connection: RBXScriptConnection;
player.CharacterAdded.Connect((character) => {
const humanoid = character.FindFirstChildWhichIsA("Humanoid");
if (humanoid) {
const animator = humanoid.FindFirstChildOfClass("Animator");
const anim = animator!.LoadAnimation(RunAnim);
anim.Priority = Enum.AnimationPriority.Action;
runanim.set(player.UserId, anim);
player.CharacterAdded.Connect((character) => {
prepareRagdoll(character);
getRagdollEvent(character).Connect(() => {
makeRagdoll(character);
waitMotionLess(character, 0.1).Wait();
stopRagdoll(character);
});
runningConn = humanoid.Running.Connect((speed) => {
if (isrunning.get(player.UserId)) {
if (speed > 2) {
if (!anim.IsPlaying) anim.Play(0.2);
} else {
if (anim.IsPlaying) anim?.Stop(0.2);
}
} else if (anim?.IsPlaying) {
anim?.Stop(1);
const humanoid = character.FindFirstChildWhichIsA("Humanoid");
if (humanoid) {
// Setup the animation.
const animator = humanoid.WaitForChild("Animator") as Animator;
const animation_track = animator.LoadAnimation(RunAnim);
animation_track.Priority = Enum.AnimationPriority.Action;
// Change the animation when the running speed change.
running_connection = humanoid.Running.Connect((speed) => {
if (this.is_running.get(player.UserId)) {
if (speed > 2) {
if (!animation_track.IsPlaying) animation_track.Play(0.2);
} else {
if (animation_track.IsPlaying) animation_track?.Stop(0.2);
}
} else if (animation_track?.IsPlaying) {
animation_track?.Stop(1);
}
});
}
});
}
});
// Clean up the connection when the character died.
player.CharacterRemoving.Once(() => running_connection.Disconnect());
});
player.CharacterRemoving.Once(() => runningConn.Disconnect());
});
sprint.Connect((player, enabled) => {
const character = player.Character;
isrunning.set(player.UserId, enabled);
if (character) {
const humanoid = character.FindFirstChildOfClass("Humanoid");
if (humanoid) {
if (enabled) {
humanoid.WalkSpeed = base_sprint_speed * sprint_speed;
// if (humanoid.GetStateEnabled(Enum.HumanoidStateType.Running)) {
// const anim = runanim.get(player.UserId);
// anim?.Play(0.2);
// }
} else {
humanoid.WalkSpeed = base_sprint_speed;
// if (humanoid.GetStateEnabled(Enum.HumanoidStateType.Running)) {
// const anim = runanim.get(player.UserId);
// anim?.Stop(1);
// }
// Change the sprint state with networking
GameplayServerEvents.sprint.connect((player, enabled) => {
const character = player.Character;
this.is_running.set(player.UserId, enabled);
if (character) {
const humanoid = character.FindFirstChildOfClass("Humanoid");
if (humanoid) {
if (enabled) {
humanoid.WalkSpeed = this.base_sprint_speed * this.sprint_speed;
} else {
humanoid.WalkSpeed = this.base_sprint_speed;
}
}
}
}
});
}
});
}

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";
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.
@@ -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.
* @param character The character of a player.
*/
export function makeRagdoll(character: Model) {
takeNetworkOwner(character);
const children = character.GetDescendants();
const humanoid = character.FindFirstChildOfClass("Humanoid")!;
humanoid.RequiresNeck = false;
humanoid.ChangeState(Enum.HumanoidStateType.Physics);
children.forEach((motor) => {
if (motor.IsA("Motor6D")) {
const ballsocketconstraint = new Instance("BallSocketConstraint"),
attachment0 = new Instance("Attachment"),
attachment1 = new Instance("Attachment");
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;
character.GetDescendants().forEach((des) => {
if (des.HasTag("Ragdoll") && des.IsA("BallSocketConstraint")) {
des.Enabled = true;
}
if (des.IsA("Motor6D")) {
des.Enabled = false;
}
});
takeNetworkOwner(character);
@@ -66,8 +132,8 @@ export function makeRagdoll(character: Model) {
export function stopRagdoll(character: Model) {
releaseNetworkOwnership(character);
character.GetDescendants().forEach((des) => {
if (des.HasTag("Ragdoll")) {
des.Destroy();
if (des.HasTag("Ragdoll") && des.IsA("BallSocketConstraint")) {
des.Enabled = false;
}
if (des.IsA("Motor6D")) {
des.Enabled = true;
@@ -96,3 +162,27 @@ export function waitMotionLess(character: Model, threshold: number) {
});
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",
"strict": true,
"target": "ESNext",
"typeRoots": ["node_modules/@rbxts"],
"typeRoots": [
"node_modules/@rbxts",
"node_modules/@flamework"
],
// configurable
"rootDir": "src",
"outDir": "out",
"baseUrl": "src",
"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"
]
}