diff --git a/package.json b/package.json index 7470e73..d401ded 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@rbxts/plasma": "^0.4.1-ts.1", "@rbxts/rewire": "^0.3.0", "@rbxts/services": "^1.5.1", - "@rbxts/testez": "^0.4.2-ts.0" + "@rbxts/testez": "^0.4.2-ts.0", + "@rbxts/variant": "^1.0.2" } } diff --git a/readme.md b/readme.md index e9b1402..8a9867c 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,7 @@ An in-dev game that I plan to make a shooter game out of. — reidlab # Hacks -* I get a strange error about private identifiers in [`./src/ReplicatedStorage/ecs/state.ts:7`](./src/ReplicatedStorage/ecs/state.ts) +* I get a strange error about private identifiers in [`./src/ReplicatedStorage/ecs/state.ts`](./src/ReplicatedStorage/ecs/state.ts) * I decided to omit the "TS" folder from [`./default.project.json:40`](./default.project.json) due to the script override not working in Health.server.ts in StarterCharacterScripts. # Todo diff --git a/src/ReplicatedStorage/ecs/index.ts b/src/ReplicatedStorage/ecs/index.ts index 7cc7dcf..1854ea9 100644 --- a/src/ReplicatedStorage/ecs/index.ts +++ b/src/ReplicatedStorage/ecs/index.ts @@ -6,7 +6,7 @@ import { Host } from "ReplicatedStorage/hosts" import { tags } from "./boundTags" import { Model } from "./components" import { start as startReplication, stop as stopReplication } from "./replication" -import { State } from "./state" +import { clientState, serverState } from "./state" import { start as startSystems, stop as stopSystems } from "./systems" import { start as startTags, stop as stopTags } from "./tags" @@ -22,10 +22,9 @@ let connections: } | undefined -export function start(host: Host): [World, State] { +export function start(host: Host, state: S): [World, S] { if (connections) throw "ECS already running." - const state: State = new State() const world = new World() const debug = new Debugger(Plasma) debug.authorize = authorize @@ -46,11 +45,15 @@ export function start(host: Host): [World, State] { }) if (host === Host.All || host === Host.Server) { + const _ServerState = state as serverState + startTags(world, tags) } if (host === Host.All || host === Host.Client) { - startReplication(world, state) + const ClientState = state as clientState + + startReplication(world, ClientState) const serverDebugger = ReplicatedStorage.FindFirstChild("MatterDebugger") if (serverDebugger && serverDebugger.IsA("ScreenGui")) { @@ -65,7 +68,7 @@ export function start(host: Host): [World, State] { UserInputService.InputBegan.Connect((input) => { if (input.KeyCode === Enum.KeyCode.F4 && authorize(Players.LocalPlayer)) { debug.toggle() - state.debugEnabled = debug.enabled + ClientState.debugEnabled = debug.enabled } }) } diff --git a/src/ReplicatedStorage/ecs/replication.ts b/src/ReplicatedStorage/ecs/replication.ts index fbe91c6..e299a8d 100644 --- a/src/ReplicatedStorage/ecs/replication.ts +++ b/src/ReplicatedStorage/ecs/replication.ts @@ -1,7 +1,7 @@ import { AnyEntity, World } from "@rbxts/matter" import { waitForEvent } from "ReplicatedStorage/remotes" import * as Components from "./components" -import { State } from "./state" +import { clientState } from "./state" type ComponentNames = keyof typeof Components type ComponentConstructors = (typeof Components)[ComponentNames] @@ -16,13 +16,13 @@ let connection: RBXScriptConnection | undefined * Starts the replication receiver. * * @param world - The world to replicate components in - * @param state - The global state for the ECS + * @param ClientState - The client state for the ECS */ -export function start(world: World, state: State): void { +export function start(world: World, ClientState: clientState): void { if (connection) return function debugPrint(message: string, args: () => (string | number)[]): void { - if (state.debugEnabled) { + if (ClientState.debugEnabled) { print("ECS Replication>", string.format(message, ...args())) } } diff --git a/src/ReplicatedStorage/ecs/state.ts b/src/ReplicatedStorage/ecs/state.ts index 29a462a..949c421 100644 --- a/src/ReplicatedStorage/ecs/state.ts +++ b/src/ReplicatedStorage/ecs/state.ts @@ -1,8 +1,21 @@ +/* eslint-disable roblox-ts/no-private-identifier */ + +import { InputKind } from "ReplicatedStorage/inputKind" + /** - * The global ECS state. + * The client ECS state. */ -export class State { +export class clientState { [index: string]: unknown, - // eslint-disable-next-line roblox-ts/no-private-identifier debugEnabled = false + isJumping = false + isRunning = false + lastProcessedCommand?: InputKind +} + +/** + * The server ECS state. + */ +export class serverState { + [index: string]: unknown } \ No newline at end of file diff --git a/src/ReplicatedStorage/ecs/systems/client/inputMapper.ts b/src/ReplicatedStorage/ecs/systems/client/inputMapper.ts new file mode 100644 index 0000000..e72099b --- /dev/null +++ b/src/ReplicatedStorage/ecs/systems/client/inputMapper.ts @@ -0,0 +1,44 @@ +import { useDeltaTime, useEvent, useThrottle, World } from "@rbxts/matter" +import { UserInputService } from "@rbxts/services" +import { clientState } from "ReplicatedStorage/ecs/state" +import { InputKind } from "ReplicatedStorage/inputKind" + +let holdDuration = 0 +function inputMapper(_: World, client: clientState): void { + for (const [, input, gpe] of useEvent(UserInputService, "InputBegan")) { + if (gpe) return undefined + if (input.KeyCode !== Enum.KeyCode.Unknown) { + client.lastProcessedCommand = InputKind.KeyDown(input.KeyCode) + } else if (input.UserInputType === Enum.UserInputType.MouseButton1) { + if (useThrottle(0.5)) { + client.lastProcessedCommand = InputKind.PointerClick + return undefined + } + client.lastProcessedCommand = InputKind.DoubleClick + } + return undefined + } + + for (const [, input, gpe] of useEvent(UserInputService, "InputEnded")) { + if (gpe) return undefined + + if (input.KeyCode !== Enum.KeyCode.Unknown) { + client.lastProcessedCommand = InputKind.KeyUp(input.KeyCode) + } else if (input.UserInputType === Enum.UserInputType.MouseButton1) { + client.lastProcessedCommand = InputKind.HoldRelease + } + return undefined + } + + if (UserInputService.IsMouseButtonPressed(Enum.UserInputType.MouseButton1)) { + holdDuration += useDeltaTime() + client.lastProcessedCommand = InputKind.Hold(holdDuration) + return + } + + holdDuration = 0 + client.lastProcessedCommand = undefined + return +} + +export = inputMapper \ No newline at end of file diff --git a/src/ReplicatedStorage/inputKind.ts b/src/ReplicatedStorage/inputKind.ts new file mode 100644 index 0000000..96adb48 --- /dev/null +++ b/src/ReplicatedStorage/inputKind.ts @@ -0,0 +1,16 @@ +import variantModule, { TypeNames, VariantOf } from "@rbxts/variant" + +export const InputKind = variantModule({ + // Sub-messages + KeyDown: (key: Enum.KeyCode) => ({ key }), + KeyUp: (key: Enum.KeyCode) => ({ key }), + Hold: (duration: number) => ({ duration }), + + // Messages + HoldRelease: {}, + DoubleClick: {}, + PointerMove: {}, + PointerClick: {} +}) + +export type InputKind = undefined> = VariantOf \ No newline at end of file diff --git a/src/ServerScriptService/main.server.ts b/src/ServerScriptService/main.server.ts index eb3c851..e1e4051 100644 --- a/src/ServerScriptService/main.server.ts +++ b/src/ServerScriptService/main.server.ts @@ -1,13 +1,16 @@ import { start } from "ReplicatedStorage/ecs" +import { serverState } from "ReplicatedStorage/ecs/state" import { Host } from "ReplicatedStorage/hosts" import { setEnvironment } from "ReplicatedStorage/idAttribute" import { getEvent } from "ReplicatedStorage/remotes" const HOST = Host.Server +const ServerState = new serverState() + // We only do this here at the moment to create a dummy event for replication. // In the future this will be created by the replication system. getEvent("EcsReplication") setEnvironment(HOST) -start(HOST) \ No newline at end of file +start(HOST, ServerState) \ No newline at end of file diff --git a/src/StarterPlayer/StarterPlayerScripts/main.client.ts b/src/StarterPlayer/StarterPlayerScripts/main.client.ts index d2c9573..686184e 100644 --- a/src/StarterPlayer/StarterPlayerScripts/main.client.ts +++ b/src/StarterPlayer/StarterPlayerScripts/main.client.ts @@ -1,8 +1,11 @@ import { start } from "ReplicatedStorage/ecs" +import { clientState } from "ReplicatedStorage/ecs/state" import { Host } from "ReplicatedStorage/hosts" import { setEnvironment } from "ReplicatedStorage/idAttribute" const HOST = Host.Client +const ClientState = new clientState() + setEnvironment(HOST) -start(HOST) \ No newline at end of file +start(HOST, ClientState) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 6b7101d..28840e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -121,6 +121,11 @@ resolved "https://registry.yarnpkg.com/@rbxts/validate-tree/-/validate-tree-2.0.2.tgz#2a1807eaa391a482822207177ff5dcd4b8f811b2" integrity sha512-OA0E9ZjEeOpPa3XeV5/oC51aro2QFcL5dItufbagYZW8TOsX8C+FDXLx+A/ulMwcF42WcFNcQybxzcWSS/QSrA== +"@rbxts/variant@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@rbxts/variant/-/variant-1.0.2.tgz#91863201d3eb8f7759d7c5719f4af54ae969b8d4" + integrity sha512-cLfjIheMFmII0YMlLbChjD+jj3jcmLhcli2xXdGXCE1WIt6txOYqehaLZcUxyK6D6tFL727txhaseKu+3Hiazg== + "@types/json-schema@^7.0.9": version "7.0.12" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"