Setup sprint and random decoration

Co-authored-by: Yowoshi246 <Yowoshi246@users.noreply.github.com>
This commit is contained in:
2025-10-15 21:45:59 +02:00
commit da615efafa
19 changed files with 2906 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/node_modules
/out
/include
*.tsbuildinfo

6
.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"printWidth": 120,
"tabWidth": 4,
"trailingComma": "all",
"useTabs": true
}

6
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"recommendations": [
"roblox-ts.vscode-roblox-ts",
"dbaeumer.vscode-eslint"
]
}

15
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"files.eol": "\n",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.formatOnSave": true
},
"eslint.run": "onType",
"eslint.format.enable": true,
"eslint.useFlatConfig": false
}

58
default.project.json Normal file
View File

@@ -0,0 +1,58 @@
{
"name": "roblox-ts-game",
"globIgnorePaths": [
"**/package.json",
"**/tsconfig.json"
],
"tree": {
"$className": "DataModel",
"ServerScriptService": {
"$className": "ServerScriptService",
"TS": {
"$path": "out/server"
}
},
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"rbxts_include": {
"$path": "include",
"node_modules": {
"$className": "Folder",
"@rbxts": {
"$path": "node_modules/@rbxts"
}
}
},
"TS": {
"$path": "out/shared"
}
},
"StarterPlayer": {
"$className": "StarterPlayer",
"StarterPlayerScripts": {
"$className": "StarterPlayerScripts",
"TS": {
"$path": "out/client"
}
}
},
"Workspace": {
"$className": "Workspace",
"$properties": {
"FilteringEnabled": true
}
},
"HttpService": {
"$className": "HttpService",
"$properties": {
"HttpEnabled": true
}
},
"SoundService": {
"$className": "SoundService",
"$properties": {
"RespectFilteringEnabled": true
}
}
}
}

51
eslint.config.mjs Normal file
View File

@@ -0,0 +1,51 @@
import { defineConfig, globalIgnores } from "eslint/config";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
// import robloxTs from "eslint-plugin-roblox-ts";
import prettier from "eslint-plugin-prettier";
import tsParser from "@typescript-eslint/parser";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
});
export default defineConfig([
globalIgnores(["out"]),
{
extends: compat.extends(
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:roblox-ts/recommended-legacy",
"plugin:prettier/recommended",
),
plugins: {
"@typescript-eslint": typescriptEslint,
// "roblox-ts": robloxTs,
prettier,
},
languageOptions: {
parser: tsParser,
ecmaVersion: 2018,
sourceType: "module",
parserOptions: {
jsx: true,
useJSXTextNode: true,
project: "./tsconfig.json",
},
},
rules: {
"prettier/prettier": "warn",
},
},
]);

2443
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
package.json Normal file
View File

@@ -0,0 +1,37 @@
{
"name": "aconit",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "rbxtsc",
"watch": "rbxtsc -w",
"lint": "eslint"
},
"keywords": [],
"author": "",
"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",
"@typescript-eslint/eslint-plugin": "^8.44.0",
"@typescript-eslint/parser": "^8.44.0",
"eslint": "^9.36.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-roblox-ts": "^1.2.1",
"prettier": "^3.6.2",
"rbxts-transformer-services": "^1.1.1",
"roblox-ts": "^3.0.0",
"typescript": "^5.9.2"
},
"dependencies": {
"-": "^0.0.1"
}
}

16
src/client/main.client.ts Normal file
View File

@@ -0,0 +1,16 @@
import { Players, UserInputService } from "@rbxts/services";
import Gameplay_Remotes from "shared/gameplay";
const sprint = Gameplay_Remotes.Client.Get("Sprint");
UserInputService.InputBegan.Connect((input) => {
if (input.KeyCode === Enum.KeyCode.LeftShift) {
sprint.SendToServer(true);
}
});
UserInputService.InputEnded.Connect((input) => {
if (input.KeyCode === Enum.KeyCode.LeftShift) {
sprint.SendToServer(false);
}
});

View File

@@ -0,0 +1,29 @@
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);
makeRagdoll(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!);
});

View File

@@ -0,0 +1,12 @@
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[]);
}
const rocks = ReplicatedStorage.FindFirstChild("Decoration")?.FindFirstChild("RockTypes")?.GetChildren();
if (rocks) {
randomDecorationPlace("Build_Gravel", rocks as Model[]);
}

View File

@@ -0,0 +1,62 @@
import { Players } from "@rbxts/services";
import Gameplay_Remotes from "shared/gameplay";
const sprint = Gameplay_Remotes.Server.Get("Sprint");
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>();
Players.PlayerAdded.Connect((player) => {
let runningConn: 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);
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);
}
});
}
});
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);
// }
}
}
}
});

14
src/server/main.server.ts Normal file
View File

@@ -0,0 +1,14 @@
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);
});
});
});

16
src/shared/build.ts Normal file
View File

@@ -0,0 +1,16 @@
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();
}
});
}

7
src/shared/gameplay.ts Normal file
View File

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

0
src/shared/module.ts Normal file
View File

98
src/shared/ragdoll.ts Normal file
View File

@@ -0,0 +1,98 @@
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();
}
});
}
/**
* 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;
}
});
takeNetworkOwner(character);
}
export function stopRagdoll(character: Model) {
releaseNetworkOwnership(character);
character.GetDescendants().forEach((des) => {
if (des.HasTag("Ragdoll")) {
des.Destroy();
}
if (des.IsA("Motor6D")) {
des.Enabled = true;
}
});
}
/**
* 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;
}

5
src/shared/replicated.ts Normal file
View File

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

27
tsconfig.json Normal file
View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
// required
"allowSyntheticDefaultImports": true,
"downlevelIteration": true,
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
"module": "commonjs",
"moduleResolution": "Node",
"noLib": true,
"resolveJsonModule": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"moduleDetection": "force",
"strict": true,
"target": "ESNext",
"typeRoots": ["node_modules/@rbxts"],
// configurable
"rootDir": "src",
"outDir": "out",
"baseUrl": "src",
"incremental": true,
"tsBuildInfoFile": "out/tsconfig.tsbuildinfo"
}
}