🔥Finish Flamework Migration.

This commit is contained in:
2025-11-02 21:42:44 +01:00
parent 93022aaba4
commit a5f3434817
12 changed files with 315 additions and 238 deletions

View File

@@ -0,0 +1,45 @@
import { BaseComponent, Component } from "@flamework/components";
import { OnStart } from "@flamework/core";
import { CollectionService, Players } from "@rbxts/services";
interface DoorInstance extends Model {
Right_Door: Model & {
Velocity: LinearVelocity;
};
Left_Door: Model & {
Velocity: LinearVelocity;
};
}
@Component({
tag: "Lobby_Door",
})
export class Doors extends BaseComponent<{}, DoorInstance> implements OnStart {
constructor() {
super();
}
onStart(): void {
const doororigin = this.instance.WorldPivot.Position;
while (task.wait(0.5)) {
let actived = false;
Players.GetPlayers().forEach((player) => {
const character = player.Character;
if (character) {
const hrp = character.FindFirstChild("HumanoidRootPart") as Part;
if (hrp) {
const vectorToDoor = doororigin.sub(hrp.Position);
const distanceToDoor = vectorToDoor.Magnitude;
if (distanceToDoor <= 25) actived = true;
}
}
});
if (actived) {
this.instance.Right_Door.Velocity.VectorVelocity = new Vector3(-5, 0, 0);
this.instance.Left_Door.Velocity.VectorVelocity = new Vector3(5, 0, 0);
} else {
this.instance.Right_Door.Velocity.VectorVelocity = new Vector3(5, 0, 0);
this.instance.Left_Door.Velocity.VectorVelocity = new Vector3(-5, 0, 0);
}
}
}
}

View File

@@ -0,0 +1,31 @@
import { BaseComponent, Component } from "@flamework/components";
import { OnStart } from "@flamework/core";
import { ReplicatedStorage } from "@rbxts/services";
interface Attribute {
Type: string;
}
@Component({
tag: "Random_Decoration",
})
class RandomDecoration extends BaseComponent<Attribute, Part> implements OnStart {
onStart(): void {
const decoration = this.getRandomDecoration(this.attributes.Type);
decoration.Parent = this.instance.Parent;
decoration.MoveTo(this.instance.Position);
this.instance.Destroy();
this.destroy();
}
private getRandomDecoration(deco_type: string) {
function getDecoration(decorations: Model[]) {
return decorations[math.random(0, decorations.size() - 1)];
}
const decorations = ReplicatedStorage.FindFirstChild("Decoration")
?.FindFirstChild(`${deco_type}Types`)
?.GetChildren()
.filter((decoration) => decoration.IsA("Model"));
if (!decorations) throw "Unknow Decoration Type";
return getDecoration(decorations).Clone();
}
}

View File

@@ -1,6 +1,6 @@
import { Flamework } from "@flamework/core"; import { Flamework } from "@flamework/core";
// Flamework.addPaths("src/server/components"); Flamework.addPaths("src/server/components");
Flamework.addPaths("src/server/services"); Flamework.addPaths("src/server/services");
// Flamework.addPaths("src/shared/components"); // Flamework.addPaths("src/shared/components");

View File

@@ -1,20 +1,21 @@
import { OnStart, Service } from "@flamework/core"; import { OnStart, Service } from "@flamework/core";
import { Players, TextChatService } from "@rbxts/services"; import { Players, TextChatService } from "@rbxts/services";
import { RagdollHandler, takeNetworkOwner } from "shared/ownership"; import { takeNetworkOwner } from "shared/utils/ownership";
import { RagdollService } from "./gameplay/ragdoll";
/** /**
* Setup Chat Command * Setup Chat Command
*/ */
@Service() @Service()
class CommandsService implements OnStart { class CommandsService implements OnStart {
constructor(private readonly ragdollService: RagdollService) {}
onStart(): void { onStart(): void {
const ragdollCommand = new TextChatCommand(); const ragdollCommand = new TextChatCommand();
ragdollCommand.Parent = TextChatService.WaitForChild("TextChatCommands"); ragdollCommand.Parent = TextChatService.WaitForChild("TextChatCommands");
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);
const handler = RagdollHandler.getHandlerByCharacter(player!.Character!, true); this.ragdollService.ragdoll(player?.Character!);
handler.ragdoll();
}); });
const unragdollCommand = new TextChatCommand(); const unragdollCommand = new TextChatCommand();
@@ -22,8 +23,7 @@ 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);
const handler = RagdollHandler.getHandlerByCharacter(player!.Character!, true); this.ragdollService.ragdoll(player?.Character!);
handler.stopRagdoll();
}); });
const takeownerCommand = new TextChatCommand(); const takeownerCommand = new TextChatCommand();

View File

@@ -1,21 +0,0 @@
import { OnStart, Service } from "@flamework/core";
import { ReplicatedStorage } from "@rbxts/services";
import { randomDecorationPlace } from "shared/build";
/** 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[]);
}
//#endregion
}
}

View File

@@ -1,20 +1,181 @@
import { OnStart, Service } from "@flamework/core"; import { Modding, OnStart, Service } from "@flamework/core";
import { RagdollHandler } from "shared/ownership"; import { RunService } from "@rbxts/services";
import { OnPlayerJoined } from "../modding/playerjoinservice"; import Signal from "@rbxts/signal";
import { OnPlayerJoined } from "shared/modding/player_events";
import { releaseNetworkOwnership, takeNetworkOwner } from "shared/utils/ownership";
import { SprintService } from "./sprint";
export interface OnRagdoll {
onPlayerRagdoll: (character: Model) => void;
}
/** /**
* Service related to Ragdoll. * Service related to Ragdoll.
*/ */
@Service() @Service()
class RagdollService implements OnPlayerJoined { export class RagdollService implements OnPlayerJoined, OnStart {
private listeners = new Set<OnRagdoll>();
constructor(private readonly sprintService: SprintService) {}
//#region Flamework events.
onStart(): void {
Modding.onListenerAdded<OnRagdoll>((object) => this.listeners.add(object));
Modding.onListenerRemoved<OnRagdoll>((object) => this.listeners.delete(object));
}
onPlayerJoined(player: Player): void { onPlayerJoined(player: Player): void {
player.CharacterAdded.Connect((character) => { player.CharacterAdded.Connect((character) => {
const ragdollhandler = new RagdollHandler(character); this.prepareRagdoll(character);
ragdollhandler.getRagdollFallingEvent().Connect(() => { this.getRagdollFallingEvent(character).Connect(() => {
ragdollhandler.ragdoll(); this.ragdoll(character);
ragdollhandler.waitMotionLess().Wait(); this.sprintService.setRunningInterdiction(player, true);
ragdollhandler.stopRagdoll(); this.waitMotionLess(character).Once(() => {
this.stopRagdoll(character);
this.sprintService.setRunningInterdiction(player, false);
});
}); });
}); });
} }
//#endregion
//#region Private functions.
private getRagdollPhysic(character: Model) {
const motors = new Set<Motor6D>();
const constraints = new Set<BallSocketConstraint>();
character.GetDescendants().forEach((descendant) => {
if (descendant.IsA("Motor6D")) {
motors.add(descendant);
}
if (descendant.IsA("BallSocketConstraint") && descendant.HasTag("Ragdoll")) {
constraints.add(descendant);
}
});
return $tuple(motors, constraints);
}
private prepareRagdoll(character: Model) {
const children = 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));
}
}
});
}
private fireOnRagdoll(character: Model) {
for (const listener of this.listeners) {
task.spawn(() => listener.onPlayerRagdoll(character));
}
}
//#endregion
//#region Main functions.
/**
* Ragdoll a character.
* @param character The player character to ragdoll.
*/
ragdoll(character: Model) {
this.fireOnRagdoll(character);
takeNetworkOwner(character);
const humanoid = character.WaitForChild("Humanoid", 10) as Humanoid;
const [motors, constraint] = this.getRagdollPhysic(character);
humanoid.ChangeState(Enum.HumanoidStateType.Physics);
motors.forEach((motor) => (motor.Enabled = false));
constraint.forEach((constraint) => (constraint.Enabled = true));
takeNetworkOwner(character);
}
/**
* Stop a Ragdoll.
* @param character The player character to unragdoll.
*/
stopRagdoll(character: Model) {
const humanoid = character.WaitForChild("Humanoid", 10) as Humanoid;
const [motors, constraint] = this.getRagdollPhysic(character);
humanoid.ChangeState(Enum.HumanoidStateType.Running);
motors.forEach((motor) => (motor.Enabled = true));
constraint.forEach((constraint) => (constraint.Enabled = false));
releaseNetworkOwnership(character);
}
//#endregion
//#region Events.
/**
* Create an Event which is fired when the player fall at an defined speed.
* @param character The player character to check.
* @param maximumSpeed The limit speed before the event fired.
* @returns A signal which is fired when the player fall at the defined speed.
*/
getRagdollFallingEvent(character: Model, maximumSpeed: number = 38) {
const humanoid = character.WaitForChild("Humanoid", 10) as Humanoid;
const root = character.FindFirstChild("HumanoidRootPart") as Part;
const event = new Signal();
let fired = false;
let beatEvent: RBXScriptConnection;
humanoid.FreeFalling.Connect((active) => {
if (active) {
beatEvent = RunService.Heartbeat.Connect(() => {
if (root.AssemblyLinearVelocity.Y < -maximumSpeed && !fired) {
event.Fire();
fired = true;
}
});
} else {
beatEvent.Disconnect();
fired = false;
}
});
return event;
}
/**
* This function return a signal which fire when the player stop moving.
* @param character The player character to check.
* @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(character: Model, threshold: number = 0.1) {
const root = character.FindFirstChild("HumanoidRootPart") as Part;
const signal = new Signal();
let delta: number = -1;
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;
}
//#endregion
} }

View File

@@ -1,22 +1,23 @@
import { OnStart, Service } from "@flamework/core"; import { OnStart, Service } from "@flamework/core";
import { Players } from "@rbxts/services"; import { Players } from "@rbxts/services";
import { GameplayServerEvents } from "server/networking"; import { GameplayServerEvents } from "server/networking";
import { OnPlayerJoined } from "../modding/playerjoinservice"; import { OnPlayerJoined } from "shared/modding/player_events";
/** /**
* Service related to Sprinting. * Service related to Sprinting.
*/ */
@Service() @Service()
class SprintService implements OnStart, OnPlayerJoined { export class SprintService implements OnStart, OnPlayerJoined {
// Sprint config. // Sprint config.
private default_stamina = 100; private default_stamina = 100;
private base_sprint_speed = 16; private base_sprint_speed = 16;
private sprint_speed = 1.5; private sprint_speed = 1.5;
private animation_id = "rbxassetid://88297455683117"; private animation_id = "rbxassetid://88297455683117";
// Cache // Data
private is_running = new Set<number>(); private is_running = new Set<number>();
private is_sprinting = new Set<number>(); private is_sprinting = new Set<number>();
private cant_run = new Set<number>();
private stamina = new Map<number, NumberValue>(); private stamina = new Map<number, NumberValue>();
private timeout = new Map<number, number>(); private timeout = new Map<number, number>();
private runanim = new Animation(); private runanim = new Animation();
@@ -60,7 +61,7 @@ class SprintService implements OnStart, OnPlayerJoined {
// Change the sprint state with networking // Change the sprint state with networking
GameplayServerEvents.sprint.connect((player, enabled) => { GameplayServerEvents.sprint.connect((player, enabled) => {
if (this.timeout.has(player.UserId)) return; if (this.timeout.has(player.UserId) && !this.cant_run.has(player.UserId)) return;
const character = player.Character; const character = player.Character;
enabled ? this.is_running.add(player.UserId) : this.is_running.delete(player.UserId); enabled ? this.is_running.add(player.UserId) : this.is_running.delete(player.UserId);
if (character) { if (character) {
@@ -75,6 +76,7 @@ class SprintService implements OnStart, OnPlayerJoined {
} }
}); });
} }
onPlayerJoined(player: Player): void { onPlayerJoined(player: Player): void {
let running_connection: RBXScriptConnection; let running_connection: RBXScriptConnection;
@@ -119,4 +121,30 @@ class SprintService implements OnStart, OnPlayerJoined {
// Clean up the connection when the character died. // Clean up the connection when the character died.
player.CharacterRemoving.Once(() => running_connection.Disconnect()); player.CharacterRemoving.Once(() => running_connection.Disconnect());
} }
setRunning(player: Player, enabled: boolean): void {
const character = player.Character;
if (!character) throw character;
if (enabled) {
this.is_running.add(player.UserId);
} else {
this.is_running.delete(player.UserId);
this.is_sprinting.delete(player.UserId);
}
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;
}
}
}
}
setRunningInterdiction(player: Player, enabled: boolean) {
this.setRunning(player, !enabled);
enabled ? this.cant_run.add(player.UserId) : this.cant_run.delete(player.UserId);
}
} }

View File

@@ -1,9 +1,6 @@
import { Modding, OnStart, Service } from "@flamework/core"; import { Modding, OnStart, Service } from "@flamework/core";
import { Players } from "@rbxts/services"; import { Players } from "@rbxts/services";
import { OnPlayerJoined } from "shared/modding/player_events";
export interface OnPlayerJoined {
onPlayerJoined(player: Player): void;
}
@Service() @Service()
class PlayerJoinService implements OnStart { class PlayerJoinService implements OnStart {

View File

@@ -1,16 +0,0 @@
import { CollectionService } from "@rbxts/services";
export function randomDecorationPlace(tag: string, decorations: Model[]) {
function getDecoration() {
return decorations[math.random(0, decorations.size() - 1)];
}
CollectionService.GetTagged(tag).forEach((part) => {
if (part.IsA("Part")) {
const deco = getDecoration().Clone();
deco.Parent = part.Parent;
deco.MoveTo(part.Position);
part.Destroy();
}
});
}

View File

@@ -0,0 +1,3 @@
export interface OnPlayerJoined {
onPlayerJoined(player: Player): void;
}

View File

@@ -1,178 +0,0 @@
import { RunService } from "@rbxts/services";
import Signal from "@rbxts/signal";
/**
* Takes the Network Ownership of a player's character.
* @param character The character of a player.
*/
export function takeNetworkOwner(character: Model) {
const part = character.GetDescendants();
part.forEach((part) => {
if (part.IsA("Part")) {
part.SetNetworkOwner(undefined);
}
});
}
/**
* Releases the Network Ownership of a player's character.
* @param character The character of a player
*/
export function releaseNetworkOwnership(character: Model) {
const part = character.GetDescendants();
part.forEach((part) => {
if (part.IsA("Part")) {
part.SetNetworkOwnershipAuto();
}
});
}
/**
* A class which help to ragdoll the player.
*/
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();
private getRagdollElements() {
return [...this.ragdollattachment, ...this.ragdollconstraint];
}
constructor(character: Model) {
this.character = character;
const humanoid = character.WaitForChild("Humanoid");
const root = character.FindFirstChild("HumanoidRootPart");
if (!humanoid?.IsA("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);
}
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));
}
}
});
}
/**
* 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);
}
}

View File

@@ -0,0 +1,27 @@
/**
* Takes the Network Ownership of a player's character.
* @param character The character of a player.
*/
export function takeNetworkOwner(character: Model) {
const part = character.GetDescendants();
part.forEach((part) => {
if (part.IsA("Part")) {
part.SetNetworkOwner(undefined);
}
});
}
/**
* Releases the Network Ownership of a player's character.
* @param character The character of a player
*/
export function releaseNetworkOwnership(character: Model) {
const part = character.GetDescendants();
part.forEach((part) => {
if (part.IsA("Part")) {
part.SetNetworkOwnershipAuto();
}
});
}