Added groundwork for action system
+ "UnknownTable" universal type ~ Moved data shared by scripts on the client and server to "Shared.ts" ~ ESLint on this machine reformatted a ton of things
This commit is contained in:
parent
1564575a7f
commit
846b86f74e
11 changed files with 164 additions and 86 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { Input, Output } from "shared/Remotes";
|
import { Input, Output } from "shared/Shared";
|
||||||
export function bindToServerMessage(functionToBind: Callback) {
|
export function bindToServerMessage(functionToBind: Callback) {
|
||||||
assert(Output?.IsA("RemoteEvent"), 'Remote event "Input" is of incorrect class or nil');
|
assert(Output?.IsA("RemoteEvent"), 'Remote event "Input" is of incorrect class or nil');
|
||||||
Output.OnClientEvent.Connect(functionToBind);
|
Output.OnClientEvent.Connect(functionToBind);
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
const TweenService = game.GetService("TweenService");
|
const TweenService = game.GetService("TweenService");
|
||||||
export type effectKeypoint = [time: number, color: Color3, size: Vector3, cframe: CFrame, transparency: number, easingStyle?: Enum.EasingStyle, easingDirection?: Enum.EasingDirection];
|
export type effectKeypoint = [
|
||||||
type effect = [number, effectKeypoint[], MeshPart, number] // Time since last keypoint, effect keypoints, effect meshPart, effect priority
|
time: number,
|
||||||
|
color: Color3,
|
||||||
|
size: Vector3,
|
||||||
|
cframe: CFrame,
|
||||||
|
transparency: number,
|
||||||
|
easingStyle?: Enum.EasingStyle,
|
||||||
|
easingDirection?: Enum.EasingDirection,
|
||||||
|
];
|
||||||
|
type effect = [number, effectKeypoint[], MeshPart, number]; // Time since last keypoint, effect keypoints, effect meshPart, effect priority
|
||||||
|
|
||||||
export interface effectMaker {
|
export interface effectMaker {
|
||||||
meshPartEffect: (
|
meshPartEffect: (
|
||||||
|
@ -8,7 +16,7 @@ export interface effectMaker {
|
||||||
material: Enum.Material,
|
material: Enum.Material,
|
||||||
effectKeypoints: effectKeypoint[],
|
effectKeypoints: effectKeypoint[],
|
||||||
priority?: number,
|
priority?: number,
|
||||||
) => void;
|
) => void;
|
||||||
particleEffect: () => void;
|
particleEffect: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +31,7 @@ interface effectHandler extends effectMaker, effectRunner {
|
||||||
*/
|
*/
|
||||||
class effectHandler implements effectMaker, effectRunner {
|
class effectHandler implements effectMaker, effectRunner {
|
||||||
constructor(effectFolder: Folder) {
|
constructor(effectFolder: Folder) {
|
||||||
this.EFFECT_FOLDER = effectFolder
|
this.EFFECT_FOLDER = effectFolder;
|
||||||
}
|
}
|
||||||
meshPartEffect(meshPart: MeshPart, material: Enum.Material, effectKeypoints: effectKeypoint[], priority?: number) {
|
meshPartEffect(meshPart: MeshPart, material: Enum.Material, effectKeypoints: effectKeypoint[], priority?: number) {
|
||||||
const effectMeshPart = meshPart.Clone();
|
const effectMeshPart = meshPart.Clone();
|
||||||
|
@ -32,32 +40,37 @@ class effectHandler implements effectMaker, effectRunner {
|
||||||
effectMeshPart.Size = effectKeypoints[0][2];
|
effectMeshPart.Size = effectKeypoints[0][2];
|
||||||
effectMeshPart.CFrame = effectKeypoints[0][3];
|
effectMeshPart.CFrame = effectKeypoints[0][3];
|
||||||
effectMeshPart.Transparency = effectKeypoints[0][4];
|
effectMeshPart.Transparency = effectKeypoints[0][4];
|
||||||
let effectDuration = 0
|
let effectDuration = 0;
|
||||||
effectKeypoints.forEach(effectKeypoint => {
|
effectKeypoints.forEach((effectKeypoint) => {
|
||||||
effectDuration += effectKeypoint[0]
|
effectDuration += effectKeypoint[0];
|
||||||
});
|
});
|
||||||
effectMeshPart.CastShadow = false;
|
effectMeshPart.CastShadow = false;
|
||||||
effectMeshPart.CanCollide = false;
|
effectMeshPart.CanCollide = false;
|
||||||
effectMeshPart.Anchored = true;
|
effectMeshPart.Anchored = true;
|
||||||
effectMeshPart.Parent = this.EFFECT_FOLDER;
|
effectMeshPart.Parent = this.EFFECT_FOLDER;
|
||||||
// Insert the effect before the effect that will end after it
|
// Insert the effect before the effect that will end after it
|
||||||
const effectsToRun = this.effectsToRun
|
const effectsToRun = this.effectsToRun;
|
||||||
for (let index = 0; index < effectsToRun.size(); index += 1) {
|
for (let index = 0; index < effectsToRun.size(); index += 1) {
|
||||||
const effectInArray = effectsToRun[index];
|
const effectInArray = effectsToRun[index];
|
||||||
let effectInArrayDuration = -effectInArray[0]
|
let effectInArrayDuration = -effectInArray[0];
|
||||||
effectInArray[1].forEach(effectKeypoint => {
|
effectInArray[1].forEach((effectKeypoint) => {
|
||||||
effectInArrayDuration += effectKeypoint[0]
|
effectInArrayDuration += effectKeypoint[0];
|
||||||
});
|
});
|
||||||
if (effectInArrayDuration > effectDuration) {
|
if (effectInArrayDuration > effectDuration) {
|
||||||
effectsToRun.insert(index, [0, effectKeypoints, effectMeshPart, priority || 0] as effect);
|
effectsToRun.insert(index, [0, effectKeypoints, effectMeshPart, priority !== undefined || 0] as effect);
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
effectsToRun.insert(effectsToRun.size(), [0, effectKeypoints, effectMeshPart, priority || 0] as effect);
|
effectsToRun.insert(effectsToRun.size(), [
|
||||||
|
0,
|
||||||
|
effectKeypoints,
|
||||||
|
effectMeshPart,
|
||||||
|
priority !== undefined || 0,
|
||||||
|
] as effect);
|
||||||
}
|
}
|
||||||
particleEffect() {}
|
particleEffect() {}
|
||||||
runEffects(timeSinceLastFrame: number) {
|
runEffects(timeSinceLastFrame: number) {
|
||||||
let effectsToRun = this.effectsToRun;
|
const effectsToRun = this.effectsToRun;
|
||||||
print(tostring(effectsToRun.size()) + " effects to run.");
|
print(tostring(effectsToRun.size()) + " effects to run.");
|
||||||
for (const effect of effectsToRun) {
|
for (const effect of effectsToRun) {
|
||||||
// Update the effect time
|
// Update the effect time
|
||||||
|
@ -72,7 +85,7 @@ class effectHandler implements effectMaker, effectRunner {
|
||||||
timeOfNextKeypoint = nextKeypoint[0];
|
timeOfNextKeypoint = nextKeypoint[0];
|
||||||
} else {
|
} else {
|
||||||
effect[2].Destroy();
|
effect[2].Destroy();
|
||||||
effectsToRun.remove(0);
|
effectsToRun.remove(0);
|
||||||
continue; // Move on if this effect is done
|
continue; // Move on if this effect is done
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +115,7 @@ class effectHandler implements effectMaker, effectRunner {
|
||||||
|
|
||||||
EFFECT_FOLDER: Folder;
|
EFFECT_FOLDER: Folder;
|
||||||
effectsToRun: effect[] = [];
|
effectsToRun: effect[] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeEffectRunner(effectFolder: Folder): effectRunner {
|
export function makeEffectRunner(effectFolder: Folder): effectRunner {
|
||||||
return new effectHandler(effectFolder);
|
return new effectHandler(effectFolder);
|
||||||
|
|
|
@ -7,14 +7,14 @@ assert(CAMERA, 'Camera of "' + Players.LocalPlayer.DisplayName + '"does not exis
|
||||||
|
|
||||||
function enumTypeIs<EnumAsType>(value: unknown, EnumAsObject: Enum): value is EnumAsType {
|
function enumTypeIs<EnumAsType>(value: unknown, EnumAsObject: Enum): value is EnumAsType {
|
||||||
if (typeIs(value, "EnumItem")) {
|
if (typeIs(value, "EnumItem")) {
|
||||||
return value.Name in EnumAsObject
|
return value.Name in EnumAsObject;
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
type validInput = Enum.KeyCode // + Include controller "keys"
|
type validInput = Enum.KeyCode; // + Include controller "keys"
|
||||||
function isValidInput(value: unknown): value is validInput {
|
function isValidInput(value: unknown): value is validInput {
|
||||||
return enumTypeIs<Enum.KeyCode>(value, Enum.KeyCode)
|
return enumTypeIs<Enum.KeyCode>(value, Enum.KeyCode);
|
||||||
}
|
}
|
||||||
const actionAssignmentsReference: string[] = [
|
const actionAssignmentsReference: string[] = [
|
||||||
"clicker1", // What is used to click on things (enemies in game, UI elements)
|
"clicker1", // What is used to click on things (enemies in game, UI elements)
|
||||||
|
@ -24,8 +24,9 @@ const actionAssignmentsReference: string[] = [
|
||||||
"diamond4",
|
"diamond4",
|
||||||
"special1", // Special controls
|
"special1", // Special controls
|
||||||
"special2",
|
"special2",
|
||||||
]
|
];
|
||||||
export interface actionAssignments { // Based on the reference array
|
export interface actionAssignments {
|
||||||
|
// Based on the reference array
|
||||||
clicker1?: validInput; // What is used to click on things (enemies in game, UI elements)
|
clicker1?: validInput; // What is used to click on things (enemies in game, UI elements)
|
||||||
diamond1?: validInput; // Diamond controls
|
diamond1?: validInput; // Diamond controls
|
||||||
diamond2?: validInput;
|
diamond2?: validInput;
|
||||||
|
@ -34,20 +35,17 @@ export interface actionAssignments { // Based on the reference array
|
||||||
special1?: validInput; // Special controls
|
special1?: validInput; // Special controls
|
||||||
special2?: validInput;
|
special2?: validInput;
|
||||||
}
|
}
|
||||||
type action = keyof actionAssignments
|
type action = keyof actionAssignments;
|
||||||
function isValidAction(value: string): value is keyof actionAssignments {
|
function isValidAction(value: string): value is keyof actionAssignments {
|
||||||
return value in actionAssignmentsReference; // uh oh
|
return value in actionAssignmentsReference; // uh oh
|
||||||
}
|
}
|
||||||
type actionBinding = [action, ((actionName?: string, state?: Enum.UserInputState, inputObject?: InputObject) => void)];
|
type actionBinding = [action, (actionName: string, state: Enum.UserInputState, inputObject: InputObject) => void];
|
||||||
type unknownTable = {[numberKey: number]: unknown, [stringKey: string]: unknown}
|
|
||||||
export function isUnknownTable(thing: unknown): thing is unknownTable {
|
|
||||||
return typeIs(thing, "table")
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMouseLocation(filterDescendantsInstances: any[]): [Vector3, Vector3, Instance | undefined] { // May be unnecessary
|
function getMouseLocation(filterDescendantsInstances: Instance[]): [Vector3, Vector3, Instance | undefined] {
|
||||||
|
// May be unnecessary
|
||||||
const hitParams = new RaycastParams();
|
const hitParams = new RaycastParams();
|
||||||
hitParams.FilterType = Enum.RaycastFilterType.Blacklist;
|
hitParams.FilterType = Enum.RaycastFilterType.Blacklist;
|
||||||
hitParams.FilterDescendantsInstances = filterDescendantsInstances
|
hitParams.FilterDescendantsInstances = filterDescendantsInstances;
|
||||||
const mouseLocation = UserInputService.GetMouseLocation();
|
const mouseLocation = UserInputService.GetMouseLocation();
|
||||||
const unitRay = CAMERA.ViewportPointToRay(mouseLocation.X, mouseLocation.Y);
|
const unitRay = CAMERA.ViewportPointToRay(mouseLocation.X, mouseLocation.Y);
|
||||||
const cast = Workspace.Raycast(unitRay.Origin, unitRay.Direction.mul(1000), hitParams);
|
const cast = Workspace.Raycast(unitRay.Origin, unitRay.Direction.mul(1000), hitParams);
|
||||||
|
@ -58,10 +56,17 @@ function getMouseLocation(filterDescendantsInstances: any[]): [Vector3, Vector3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function translateInputState(state: any) {
|
export function translateInputState(state: unknown) {
|
||||||
if (enumTypeIs<Enum.UserInputState>(state, Enum.UserInputState)) {
|
// if (enumTypeIs<Enum.UserInputState>(state, Enum.UserInputState)) {
|
||||||
// + Translate to simple boolean
|
if (state === Enum.UserInputState.Begin) {
|
||||||
|
return true;
|
||||||
|
} else if (state === Enum.UserInputState.End) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
warn("Non-standard UserInputState");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface actionBinder {
|
export interface actionBinder {
|
||||||
|
@ -70,25 +75,26 @@ export interface actionBinder {
|
||||||
unbindFunctionsFromActions: (actions: action[]) => void;
|
unbindFunctionsFromActions: (actions: action[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class actionHandler implements actionBinder { // + Needs a semaphore if concurrency issues arise
|
class actionHandler implements actionBinder {
|
||||||
|
// + Needs a semaphore if concurrency issues arise
|
||||||
constructor() {
|
constructor() {
|
||||||
// Fortnite
|
// Fortnite
|
||||||
}
|
}
|
||||||
assignInputsToActions(actionAssignments: unknownTable) {
|
assignInputsToActions(actionAssignments: unknownTable) {
|
||||||
let newActionAssignments: actionAssignments = {}
|
const newActionAssignments: actionAssignments = {};
|
||||||
actionAssignmentsReference.forEach(action => {
|
actionAssignmentsReference.forEach((action) => {
|
||||||
const input: unknown = actionAssignments[action]
|
const input: unknown = actionAssignments[action];
|
||||||
if (isValidAction(action) && isValidInput(input)) {
|
if (isValidAction(action) && isValidInput(input)) {
|
||||||
newActionAssignments[action] = input
|
newActionAssignments[action] = input;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
bindFunctionsToActions(actionBindings: actionBinding[]) {
|
bindFunctionsToActions(actionBindings: actionBinding[]) {
|
||||||
const actionAssignments = this.actionAssignments;
|
const actionAssignments = this.actionAssignments;
|
||||||
const boundActions = this.boundActions;
|
const boundActions = this.boundActions;
|
||||||
if (actionAssignments) {
|
if (actionAssignments) {
|
||||||
actionBindings.forEach(actionBinding => {
|
actionBindings.forEach((actionBinding) => {
|
||||||
const action = actionBinding[0]
|
const action = actionBinding[0];
|
||||||
const input = actionAssignments[action];
|
const input = actionAssignments[action];
|
||||||
if (!boundActions[action] && input) {
|
if (!boundActions[action] && input) {
|
||||||
boundActions[action] = true;
|
boundActions[action] = true;
|
||||||
|
@ -96,24 +102,24 @@ class actionHandler implements actionBinder { // + Needs a semaphore if concurre
|
||||||
} else {
|
} else {
|
||||||
// ???
|
// ???
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unbindFunctionsFromActions(actions: action[]) {
|
unbindFunctionsFromActions(actions: action[]) {
|
||||||
const boundActions = this.boundActions
|
const boundActions = this.boundActions;
|
||||||
actions.forEach(action => {
|
actions.forEach((action) => {
|
||||||
if (boundActions[action]) {
|
if (boundActions[action]) {
|
||||||
boundActions[action] = undefined;
|
boundActions[action] = undefined;
|
||||||
ContextActionService.UnbindAction(action);
|
ContextActionService.UnbindAction(action);
|
||||||
} else {
|
} else {
|
||||||
// ???
|
// ???
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
actionAssignments?: actionAssignments;
|
actionAssignments?: actionAssignments;
|
||||||
boundActions: {[action: string]: boolean | undefined} = {};
|
boundActions: { [action: string]: boolean | undefined } = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeActionBinder(): actionBinder {
|
export function makeActionBinder(): actionBinder {
|
||||||
return new actionHandler();
|
return new actionHandler();
|
||||||
|
@ -128,4 +134,4 @@ function handleInput(input: InputObject, otherInteraction: boolean) {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// UserInputService.InputBegan.Connect(handleInput);
|
// UserInputService.InputBegan.Connect(handleInput);
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
// "init": The main client-side thread.
|
// "init": The main client-side thread.
|
||||||
const Players = game.GetService("Players");
|
const Players = game.GetService("Players");
|
||||||
const RunService = game.GetService("RunService");
|
const RunService = game.GetService("RunService");
|
||||||
|
import { isUnknownTable } from "shared/Shared";
|
||||||
import { bindToServerMessage, messageServer } from "./ClientMessenger";
|
import { bindToServerMessage, messageServer } from "./ClientMessenger";
|
||||||
import { handleGuiInput, drawGui, closeGui } from "./GuiHandler";
|
import { handleGuiInput, drawGui, closeGui } from "./GuiHandler";
|
||||||
import { makeEffectRunner, effectRunner } from "./EffectMaker";
|
import { makeEffectRunner, effectRunner } from "./EffectMaker";
|
||||||
import { makeActionBinder, actionBinder, isUnknownTable } from "./InputHandler";
|
import { makeActionBinder, actionBinder, translateInputState } from "./InputHandler";
|
||||||
const LOCALPLAYER = Players.LocalPlayer;
|
const LOCALPLAYER = Players.LocalPlayer;
|
||||||
const PLAYERGUI = LOCALPLAYER.WaitForChild("PlayerGui", 1) as PlayerGui;
|
const PLAYERGUI = LOCALPLAYER.WaitForChild("PlayerGui", 1) as PlayerGui;
|
||||||
assert(
|
assert(
|
||||||
|
@ -19,34 +20,46 @@ function openMainMenu(playerGui: PlayerGui) {
|
||||||
for (const mainMenuButton of mainMenuButtons) {
|
for (const mainMenuButton of mainMenuButtons) {
|
||||||
mainMenuButton[0].Activated.Connect(function () {
|
mainMenuButton[0].Activated.Connect(function () {
|
||||||
handleGuiInput(messageServer, mainMenuButton[1][0], mainMenuButton[1][1]);
|
handleGuiInput(messageServer, mainMenuButton[1][0], mainMenuButton[1][1]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handlePlayerAction(action: string, state: Enum.UserInputState, inputObject: InputObject) {
|
||||||
|
messageServer("PlayerAction", [action, translateInputState(state)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGuiAction() {}
|
||||||
|
|
||||||
function handleServerMessage(messageType: unknown, messageContent: unknown) {
|
function handleServerMessage(messageType: unknown, messageContent: unknown) {
|
||||||
if (messageType === "init") {
|
if (messageType === "init") {
|
||||||
openMainMenu(PLAYERGUI);
|
openMainMenu(PLAYERGUI);
|
||||||
inMainMenu = true;
|
inMainMenu = true;
|
||||||
|
mainActionBinder.bindFunctionsToActions([["clicker1", handleGuiAction]]);
|
||||||
} else if (messageType === "enterGame") {
|
} else if (messageType === "enterGame") {
|
||||||
closeGui(PLAYERGUI, "MainMenu");
|
closeGui(PLAYERGUI, "MainMenu");
|
||||||
inMainMenu = false;
|
inMainMenu = false;
|
||||||
|
mainActionBinder.unbindFunctionsFromActions(["clicker1"]);
|
||||||
|
mainActionBinder.bindFunctionsToActions([
|
||||||
|
["clicker1", handlePlayerAction],
|
||||||
|
["special1", handlePlayerAction],
|
||||||
|
["special2", handlePlayerAction],
|
||||||
|
]);
|
||||||
} else if (messageType === "bindActions") {
|
} else if (messageType === "bindActions") {
|
||||||
if (isUnknownTable(messageContent)) {
|
if (isUnknownTable(messageContent)) {
|
||||||
mainActionBinder.assignInputsToActions(messageContent)
|
mainActionBinder.assignInputsToActions(messageContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Bind functions
|
// Bind functions
|
||||||
|
|
||||||
|
|
||||||
const effectRunners: effectRunner[] = [];
|
const effectRunners: effectRunner[] = [];
|
||||||
// + Put stuff in the effectRunners table
|
// + Put stuff in the effectRunners table
|
||||||
RunService.RenderStepped.Connect(function(deltaTime) {
|
RunService.RenderStepped.Connect(function (deltaTime) {
|
||||||
effectRunners.forEach(effectRunner => {
|
effectRunners.forEach((effectRunner) => {
|
||||||
effectRunner.runEffects(deltaTime)
|
effectRunner.runEffects(deltaTime);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
const mainActionBinder: actionBinder = makeActionBinder()
|
const mainActionBinder: actionBinder = makeActionBinder();
|
||||||
|
|
||||||
bindToServerMessage(handleServerMessage);
|
bindToServerMessage(handleServerMessage);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Input, Output } from "shared/Remotes";
|
import { Input, Output } from "shared/Shared";
|
||||||
export function bindToClientMessage(functionToBind: Callback) {
|
export function bindToClientMessage(functionToBind: Callback) {
|
||||||
assert(Input?.IsA("RemoteEvent"), 'Remote event "Input" is of incorrect class or nil');
|
assert(Input?.IsA("RemoteEvent"), 'Remote event "Input" is of incorrect class or nil');
|
||||||
Input.OnServerEvent.Connect(functionToBind);
|
Input.OnServerEvent.Connect(functionToBind);
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
// "main": This is the core of reality. It serves as the highest-level abstraction.
|
// "main": This is the core of reality. It serves as the highest-level abstraction.
|
||||||
// + Prevent this from coupling with the entity manager, if possible
|
// + Prevent this from coupling with the entity manager, if possible
|
||||||
const Players = game.GetService("Players");
|
const Players = game.GetService("Players");
|
||||||
import { makePlayerStorage, playerStorage, storedPlayer } from "shared/PlayerManager";
|
import { isUnknownTable } from "shared/Shared";
|
||||||
|
import { makePlayerStorage, playerStorage, storedPlayer } from "shared/PlayerManager";
|
||||||
import { bindToClientMessage, messageClient, messageAllClients } from "./ServerMessenger";
|
import { bindToClientMessage, messageClient, messageAllClients } from "./ServerMessenger";
|
||||||
const playerStorage: playerStorage = makePlayerStorage();
|
const mainPlayerStorage: playerStorage = makePlayerStorage();
|
||||||
|
|
||||||
function addPlayer(player: Player) {
|
function addPlayer(player: Player) {
|
||||||
playerStorage.initPlayer(player);
|
mainPlayerStorage.initPlayer(player);
|
||||||
messageClient(player, "init", "idk");
|
messageClient(player, "init", "idk");
|
||||||
}
|
}
|
||||||
function removePlayer(player: Player) {
|
function removePlayer(player: Player) {
|
||||||
playerStorage.deinitPlayer(player);
|
mainPlayerStorage.deinitPlayer(player);
|
||||||
}
|
}
|
||||||
// Handling input is a complicated process that requires passing a large variety of data to a large variety of places, so it's staying here for now
|
// Handling input is a complicated process that requires passing a large variety of data to a large variety of places, so it's staying here for now
|
||||||
function handleClientMessage(player: Player, messageType: unknown, messageContent: unknown) {
|
function handleClientMessage(player: Player, messageType: unknown, messageContent: unknown) {
|
||||||
const storedPlayer = playerStorage.fetchPlayer(player);
|
const storedPlayer = mainPlayerStorage.fetchPlayer(player);
|
||||||
if (messageType === "EnterGame") {
|
if (messageType === "EnterGame") {
|
||||||
try {
|
try {
|
||||||
storedPlayer.loadIn();
|
storedPlayer.loadIn();
|
||||||
|
@ -27,9 +28,17 @@ function handleClientMessage(player: Player, messageType: unknown, messageConten
|
||||||
messageClient(player, "promptError", errorMessage);
|
messageClient(player, "promptError", errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (messageType === "PlayerAction") {
|
||||||
|
if (isUnknownTable(messageContent)) {
|
||||||
|
const action = messageContent[0];
|
||||||
|
const state = messageContent[1];
|
||||||
|
if (typeIs(action, "string") && typeIs(state, "boolean")) {
|
||||||
|
storedPlayer.ability(action, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (messageType === "move") {
|
} else if (messageType === "move") {
|
||||||
if (typeIs(messageContent, "Vector3")) {
|
if (typeIs(messageContent, "Vector3")) {
|
||||||
storedPlayer.setPosition(messageContent)
|
storedPlayer.setPosition(messageContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
4
src/services.d.ts
vendored
4
src/services.d.ts
vendored
|
@ -9,9 +9,7 @@ type effectState = [CFrame, Vector3, Color3, number]; // The number is transpare
|
||||||
type effectEntry = [meshType, EnumItem, effectState[]]; // The enumitem is material
|
type effectEntry = [meshType, EnumItem, effectState[]]; // The enumitem is material
|
||||||
|
|
||||||
*/
|
*/
|
||||||
// Genuinely require being on both sides - but in the services file? No shot!
|
type unknownTable = { [numberKey: number]: unknown; [stringKey: string]: unknown };
|
||||||
|
|
||||||
|
|
||||||
/*interface hookInEntry {
|
/*interface hookInEntry {
|
||||||
name: string;
|
name: string;
|
||||||
guiObject: GuiObject;
|
guiObject: GuiObject;
|
||||||
|
|
4
src/shared/AbilityManager.ts
Normal file
4
src/shared/AbilityManager.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface ability {
|
||||||
|
use: () => void; // ? Does it pass back information to the entity manager that it does something with? Is an ability really just a transform of entities?
|
||||||
|
// ... this transform would need the positions and stats of the entities involved as well as potential obstacles
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
// "EntityManager": Create entities objects and deck them out with functions to use.
|
// "EntityManager": Create entities objects and deck them out with functions to use.
|
||||||
// + Functions are here, as to avoid storing unecessary data in the server store.
|
// + Functions are here, as to avoid storing unecessary data in the server store.
|
||||||
import { makePuppet, puppet, puppetEntry } from "./Puppetmaster";
|
import { makePuppet, puppet, puppetEntry } from "./Puppetmaster";
|
||||||
type stats = [maxHealth: number, attack: number, speed: number, defense: number] // values used for calculation, only modified by buffs and debuffs
|
import { ability } from "./AbilityManager";
|
||||||
type amounts = [health: number, barrier: number] // values used to store an entity's current status (more existential than stats)
|
type stats = [maxHealth: number, attack: number, speed: number, defense: number]; // values used for calculation, only modified by buffs and debuffs
|
||||||
|
type amounts = [health: number, barrier: number]; // values used to store an entity's current status (more existential than stats)
|
||||||
|
|
||||||
export interface entity {
|
export interface entity {
|
||||||
setPosition: (location: Vector3) => void;
|
setPosition: (location: Vector3) => void;
|
||||||
|
ability: (ability: string, state: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class entityHandler implements entity {
|
class entityHandler implements entity {
|
||||||
|
@ -13,15 +15,37 @@ class entityHandler implements entity {
|
||||||
this.baseStats = baseStats;
|
this.baseStats = baseStats;
|
||||||
this.baseAmounts = baseAmounts;
|
this.baseAmounts = baseAmounts;
|
||||||
this.puppet = makePuppet(puppetEntry);
|
this.puppet = makePuppet(puppetEntry);
|
||||||
};
|
}
|
||||||
setPosition(location: Vector3) {
|
setPosition(location: Vector3) {
|
||||||
this.puppet.movePuppet(location);
|
this.puppet.movePuppet(location);
|
||||||
}
|
}
|
||||||
|
ability(abilityName: string, activated: boolean) {
|
||||||
|
const abilities = this.abilities;
|
||||||
|
if (abilities) {
|
||||||
|
if (activated) {
|
||||||
|
const ability = abilities[abilityName];
|
||||||
|
if (ability !== undefined) {
|
||||||
|
ability.use();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// + Ability cancellation - perhaps the useAbility inside the entity returns a function to store that the ability watches that ceases the ability when executed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Blah blah blah
|
||||||
|
}
|
||||||
|
|
||||||
baseStats: stats;
|
baseStats: stats;
|
||||||
baseAmounts: amounts; // Health, Barrier (things of indescribable importance)
|
baseAmounts: amounts;
|
||||||
puppet: puppet;
|
puppet: puppet;
|
||||||
|
abilities?: {
|
||||||
|
[key: string]: ability;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeEntity(puppetEntry: puppetEntry, baseStats = [100, 1, 16, 0] as stats, baseAmounts = [100, 0] as amounts) {
|
export function makeEntity(
|
||||||
|
puppetEntry: puppetEntry,
|
||||||
|
baseStats = [100, 1, 16, 0] as stats,
|
||||||
|
baseAmounts = [100, 0] as amounts,
|
||||||
|
) {
|
||||||
return new entityHandler(baseStats, baseAmounts, puppetEntry);
|
return new entityHandler(baseStats, baseAmounts, puppetEntry);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
// "PlayerManager": Handle the data of players. This involves receiving them when they arrive, cleaning up after they exit, teleporting them, etc.
|
// "PlayerManager": Handle the data of players. This involves receiving them when they arrive, cleaning up after they exit, teleporting them, etc.
|
||||||
import { makeEntity, entity } from "./EntityManager";
|
import { makeEntity, entity } from "./EntityManager";
|
||||||
|
|
||||||
interface saveDataEntry { // + May need to move this to archiver
|
interface saveDataEntry {
|
||||||
|
// + May need to move this to archiver
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
}
|
}
|
||||||
export interface storedPlayer {
|
export interface storedPlayer {
|
||||||
teleportToServer: () => void;
|
teleportToServer: () => void;
|
||||||
setPosition: (location: Vector3) => void;
|
setPosition: (location: Vector3) => void;
|
||||||
|
ability: (ability: string, state: boolean) => void;
|
||||||
loadIn: () => void;
|
loadIn: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,25 +16,30 @@ class storedPlayerHandler implements storedPlayer {
|
||||||
constructor(player: Player) {
|
constructor(player: Player) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.inMainMenu = true;
|
this.inMainMenu = true;
|
||||||
this.saveData = {placeholder: "fortnite"};
|
this.saveData = { placeholder: "fortnite" };
|
||||||
}
|
}
|
||||||
teleportToServer() {
|
teleportToServer() {
|
||||||
// + Do checking related to where the player is allowed to go
|
// + Do checking related to where the player is allowed to go
|
||||||
// + Teleport player to other server, sending a message to have them load in automatically
|
// + Teleport player to other server, sending a message to have them load in automatically
|
||||||
};
|
}
|
||||||
setPosition(location: Vector3) {
|
setPosition(location: Vector3) {
|
||||||
if (this.entity) {
|
if (this.entity) {
|
||||||
this.entity.setPosition(location);
|
this.entity.setPosition(location);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
ability(ability: string, state: boolean) {
|
||||||
|
if (this.entity) {
|
||||||
|
this.entity.ability(ability, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
loadIn() {
|
loadIn() {
|
||||||
this.entity = makeEntity(["Character", this.player]);
|
this.entity = makeEntity(["Character", this.player]);
|
||||||
// + Give the entity the stats it's supposed to have, load from save data maybe?
|
// + Give the entity the stats it's supposed to have, load from save data maybe?
|
||||||
};
|
}
|
||||||
player: Player;
|
player: Player;
|
||||||
inMainMenu: boolean;
|
inMainMenu: boolean;
|
||||||
// + Other data that is unique to players but does not persist between sessions
|
// + Other data that is unique to players but does not persist between sessions
|
||||||
saveData: saveDataEntry;
|
saveData: saveDataEntry; // This gets synced with the actual datastore
|
||||||
entity?: entity;
|
entity?: entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,18 +50,18 @@ export interface playerStorage {
|
||||||
}
|
}
|
||||||
|
|
||||||
class playerStorageHandler implements playerStorage {
|
class playerStorageHandler implements playerStorage {
|
||||||
constructor() {};
|
constructor() {}
|
||||||
initPlayer(player: Player) {
|
initPlayer(player: Player) {
|
||||||
this.playerStorageArray[player.UserId] = new storedPlayerHandler(player);
|
this.playerStorageArray[player.UserId] = new storedPlayerHandler(player);
|
||||||
// + Load player's datastore into server store
|
// + Load player's datastore into server store
|
||||||
};
|
}
|
||||||
deinitPlayer(player: Player) {
|
deinitPlayer(player: Player) {
|
||||||
const entry = this.playerStorageArray[player.UserId];
|
const entry = this.playerStorageArray[player.UserId];
|
||||||
assert(entry, "Trying to remove entry of player " + player.DisplayName + ", but entry does not exist!");
|
assert(entry, "Trying to remove entry of player " + player.DisplayName + ", but entry does not exist!");
|
||||||
// ? Tell the entity to unload, if it still exists (the entity will tell the other clients to remove the player)
|
// ? Tell the entity to unload, if it still exists (the entity will tell the other clients to remove the player)
|
||||||
// + Unload player's server store to datastores
|
// + Unload player's server store to datastores
|
||||||
return undefined; // A nil entry to replace the entry to be wiped and maybe a success value in a wrapper
|
return undefined; // A nil entry to replace the entry to be wiped and maybe a success value in a wrapper
|
||||||
};
|
}
|
||||||
fetchPlayer(player: Player) {
|
fetchPlayer(player: Player) {
|
||||||
return this.playerStorageArray[player.UserId];
|
return this.playerStorageArray[player.UserId];
|
||||||
}
|
}
|
||||||
|
@ -63,4 +70,4 @@ class playerStorageHandler implements playerStorage {
|
||||||
|
|
||||||
export function makePlayerStorage() {
|
export function makePlayerStorage() {
|
||||||
return new playerStorageHandler();
|
return new playerStorageHandler();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,3 +2,7 @@
|
||||||
const ReplicatedStorage = game.GetService("ReplicatedStorage");
|
const ReplicatedStorage = game.GetService("ReplicatedStorage");
|
||||||
export const Input = ReplicatedStorage.WaitForChild("Input", 1);
|
export const Input = ReplicatedStorage.WaitForChild("Input", 1);
|
||||||
export const Output = ReplicatedStorage.WaitForChild("Output", 1);
|
export const Output = ReplicatedStorage.WaitForChild("Output", 1);
|
||||||
|
|
||||||
|
export function isUnknownTable(thing: unknown): thing is unknownTable {
|
||||||
|
return typeIs(thing, "table");
|
||||||
|
}
|
Loading…
Reference in a new issue