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) {
|
||||
assert(Output?.IsA("RemoteEvent"), 'Remote event "Input" is of incorrect class or nil');
|
||||
Output.OnClientEvent.Connect(functionToBind);
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
const TweenService = game.GetService("TweenService");
|
||||
export type effectKeypoint = [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 type effectKeypoint = [
|
||||
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 {
|
||||
meshPartEffect: (
|
||||
|
@ -8,7 +16,7 @@ export interface effectMaker {
|
|||
material: Enum.Material,
|
||||
effectKeypoints: effectKeypoint[],
|
||||
priority?: number,
|
||||
) => void;
|
||||
) => void;
|
||||
particleEffect: () => void;
|
||||
}
|
||||
|
||||
|
@ -23,7 +31,7 @@ interface effectHandler extends effectMaker, effectRunner {
|
|||
*/
|
||||
class effectHandler implements effectMaker, effectRunner {
|
||||
constructor(effectFolder: Folder) {
|
||||
this.EFFECT_FOLDER = effectFolder
|
||||
this.EFFECT_FOLDER = effectFolder;
|
||||
}
|
||||
meshPartEffect(meshPart: MeshPart, material: Enum.Material, effectKeypoints: effectKeypoint[], priority?: number) {
|
||||
const effectMeshPart = meshPart.Clone();
|
||||
|
@ -32,32 +40,37 @@ class effectHandler implements effectMaker, effectRunner {
|
|||
effectMeshPart.Size = effectKeypoints[0][2];
|
||||
effectMeshPart.CFrame = effectKeypoints[0][3];
|
||||
effectMeshPart.Transparency = effectKeypoints[0][4];
|
||||
let effectDuration = 0
|
||||
effectKeypoints.forEach(effectKeypoint => {
|
||||
effectDuration += effectKeypoint[0]
|
||||
let effectDuration = 0;
|
||||
effectKeypoints.forEach((effectKeypoint) => {
|
||||
effectDuration += effectKeypoint[0];
|
||||
});
|
||||
effectMeshPart.CastShadow = false;
|
||||
effectMeshPart.CanCollide = false;
|
||||
effectMeshPart.Anchored = true;
|
||||
effectMeshPart.Parent = this.EFFECT_FOLDER;
|
||||
// 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) {
|
||||
const effectInArray = effectsToRun[index];
|
||||
let effectInArrayDuration = -effectInArray[0]
|
||||
effectInArray[1].forEach(effectKeypoint => {
|
||||
effectInArrayDuration += effectKeypoint[0]
|
||||
let effectInArrayDuration = -effectInArray[0];
|
||||
effectInArray[1].forEach((effectKeypoint) => {
|
||||
effectInArrayDuration += effectKeypoint[0];
|
||||
});
|
||||
if (effectInArrayDuration > effectDuration) {
|
||||
effectsToRun.insert(index, [0, effectKeypoints, effectMeshPart, priority || 0] as effect);
|
||||
break
|
||||
effectsToRun.insert(index, [0, effectKeypoints, effectMeshPart, priority !== undefined || 0] as effect);
|
||||
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() {}
|
||||
runEffects(timeSinceLastFrame: number) {
|
||||
let effectsToRun = this.effectsToRun;
|
||||
const effectsToRun = this.effectsToRun;
|
||||
print(tostring(effectsToRun.size()) + " effects to run.");
|
||||
for (const effect of effectsToRun) {
|
||||
// Update the effect time
|
||||
|
|
|
@ -7,14 +7,14 @@ assert(CAMERA, 'Camera of "' + Players.LocalPlayer.DisplayName + '"does not exis
|
|||
|
||||
function enumTypeIs<EnumAsType>(value: unknown, EnumAsObject: Enum): value is EnumAsType {
|
||||
if (typeIs(value, "EnumItem")) {
|
||||
return value.Name in EnumAsObject
|
||||
return value.Name in EnumAsObject;
|
||||
} 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 {
|
||||
return enumTypeIs<Enum.KeyCode>(value, Enum.KeyCode)
|
||||
return enumTypeIs<Enum.KeyCode>(value, Enum.KeyCode);
|
||||
}
|
||||
const actionAssignmentsReference: string[] = [
|
||||
"clicker1", // What is used to click on things (enemies in game, UI elements)
|
||||
|
@ -24,8 +24,9 @@ const actionAssignmentsReference: string[] = [
|
|||
"diamond4",
|
||||
"special1", // Special controls
|
||||
"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)
|
||||
diamond1?: validInput; // Diamond controls
|
||||
diamond2?: validInput;
|
||||
|
@ -34,20 +35,17 @@ export interface actionAssignments { // Based on the reference array
|
|||
special1?: validInput; // Special controls
|
||||
special2?: validInput;
|
||||
}
|
||||
type action = keyof actionAssignments
|
||||
type action = keyof actionAssignments;
|
||||
function isValidAction(value: string): value is keyof actionAssignments {
|
||||
return value in actionAssignmentsReference; // uh oh
|
||||
}
|
||||
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")
|
||||
}
|
||||
type actionBinding = [action, (actionName: string, state: Enum.UserInputState, inputObject: InputObject) => void];
|
||||
|
||||
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();
|
||||
hitParams.FilterType = Enum.RaycastFilterType.Blacklist;
|
||||
hitParams.FilterDescendantsInstances = filterDescendantsInstances
|
||||
hitParams.FilterDescendantsInstances = filterDescendantsInstances;
|
||||
const mouseLocation = UserInputService.GetMouseLocation();
|
||||
const unitRay = CAMERA.ViewportPointToRay(mouseLocation.X, mouseLocation.Y);
|
||||
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) {
|
||||
if (enumTypeIs<Enum.UserInputState>(state, Enum.UserInputState)) {
|
||||
// + Translate to simple boolean
|
||||
export function translateInputState(state: unknown) {
|
||||
// if (enumTypeIs<Enum.UserInputState>(state, Enum.UserInputState)) {
|
||||
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 {
|
||||
|
@ -70,25 +75,26 @@ export interface actionBinder {
|
|||
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() {
|
||||
// Fortnite
|
||||
}
|
||||
assignInputsToActions(actionAssignments: unknownTable) {
|
||||
let newActionAssignments: actionAssignments = {}
|
||||
actionAssignmentsReference.forEach(action => {
|
||||
const input: unknown = actionAssignments[action]
|
||||
const newActionAssignments: actionAssignments = {};
|
||||
actionAssignmentsReference.forEach((action) => {
|
||||
const input: unknown = actionAssignments[action];
|
||||
if (isValidAction(action) && isValidInput(input)) {
|
||||
newActionAssignments[action] = input
|
||||
newActionAssignments[action] = input;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
bindFunctionsToActions(actionBindings: actionBinding[]) {
|
||||
const actionAssignments = this.actionAssignments;
|
||||
const boundActions = this.boundActions;
|
||||
if (actionAssignments) {
|
||||
actionBindings.forEach(actionBinding => {
|
||||
const action = actionBinding[0]
|
||||
actionBindings.forEach((actionBinding) => {
|
||||
const action = actionBinding[0];
|
||||
const input = actionAssignments[action];
|
||||
if (!boundActions[action] && input) {
|
||||
boundActions[action] = true;
|
||||
|
@ -100,19 +106,19 @@ class actionHandler implements actionBinder { // + Needs a semaphore if concurre
|
|||
}
|
||||
}
|
||||
unbindFunctionsFromActions(actions: action[]) {
|
||||
const boundActions = this.boundActions
|
||||
actions.forEach(action => {
|
||||
const boundActions = this.boundActions;
|
||||
actions.forEach((action) => {
|
||||
if (boundActions[action]) {
|
||||
boundActions[action] = undefined;
|
||||
ContextActionService.UnbindAction(action);
|
||||
} else {
|
||||
// ???
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
actionAssignments?: actionAssignments;
|
||||
boundActions: {[action: string]: boolean | undefined} = {};
|
||||
boundActions: { [action: string]: boolean | undefined } = {};
|
||||
}
|
||||
|
||||
export function makeActionBinder(): actionBinder {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
// "init": The main client-side thread.
|
||||
const Players = game.GetService("Players");
|
||||
const RunService = game.GetService("RunService");
|
||||
import { isUnknownTable } from "shared/Shared";
|
||||
import { bindToServerMessage, messageServer } from "./ClientMessenger";
|
||||
import { handleGuiInput, drawGui, closeGui } from "./GuiHandler";
|
||||
import { makeEffectRunner, effectRunner } from "./EffectMaker";
|
||||
import { makeActionBinder, actionBinder, isUnknownTable } from "./InputHandler";
|
||||
import { makeActionBinder, actionBinder, translateInputState } from "./InputHandler";
|
||||
const LOCALPLAYER = Players.LocalPlayer;
|
||||
const PLAYERGUI = LOCALPLAYER.WaitForChild("PlayerGui", 1) as PlayerGui;
|
||||
assert(
|
||||
|
@ -23,30 +24,42 @@ function openMainMenu(playerGui: PlayerGui) {
|
|||
}
|
||||
}
|
||||
|
||||
function handlePlayerAction(action: string, state: Enum.UserInputState, inputObject: InputObject) {
|
||||
messageServer("PlayerAction", [action, translateInputState(state)]);
|
||||
}
|
||||
|
||||
function handleGuiAction() {}
|
||||
|
||||
function handleServerMessage(messageType: unknown, messageContent: unknown) {
|
||||
if (messageType === "init") {
|
||||
openMainMenu(PLAYERGUI);
|
||||
inMainMenu = true;
|
||||
mainActionBinder.bindFunctionsToActions([["clicker1", handleGuiAction]]);
|
||||
} else if (messageType === "enterGame") {
|
||||
closeGui(PLAYERGUI, "MainMenu");
|
||||
inMainMenu = false;
|
||||
mainActionBinder.unbindFunctionsFromActions(["clicker1"]);
|
||||
mainActionBinder.bindFunctionsToActions([
|
||||
["clicker1", handlePlayerAction],
|
||||
["special1", handlePlayerAction],
|
||||
["special2", handlePlayerAction],
|
||||
]);
|
||||
} else if (messageType === "bindActions") {
|
||||
if (isUnknownTable(messageContent)) {
|
||||
mainActionBinder.assignInputsToActions(messageContent)
|
||||
mainActionBinder.assignInputsToActions(messageContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Bind functions
|
||||
|
||||
|
||||
const effectRunners: effectRunner[] = [];
|
||||
// + Put stuff in the effectRunners table
|
||||
RunService.RenderStepped.Connect(function(deltaTime) {
|
||||
effectRunners.forEach(effectRunner => {
|
||||
effectRunner.runEffects(deltaTime)
|
||||
RunService.RenderStepped.Connect(function (deltaTime) {
|
||||
effectRunners.forEach((effectRunner) => {
|
||||
effectRunner.runEffects(deltaTime);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
const mainActionBinder: actionBinder = makeActionBinder()
|
||||
const mainActionBinder: actionBinder = makeActionBinder();
|
||||
|
||||
bindToServerMessage(handleServerMessage);
|
|
@ -1,4 +1,4 @@
|
|||
import { Input, Output } from "shared/Remotes";
|
||||
import { Input, Output } from "shared/Shared";
|
||||
export function bindToClientMessage(functionToBind: Callback) {
|
||||
assert(Input?.IsA("RemoteEvent"), 'Remote event "Input" is of incorrect class or nil');
|
||||
Input.OnServerEvent.Connect(functionToBind);
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
// "main": This is the core of reality. It serves as the highest-level abstraction.
|
||||
// + Prevent this from coupling with the entity manager, if possible
|
||||
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";
|
||||
const playerStorage: playerStorage = makePlayerStorage();
|
||||
const mainPlayerStorage: playerStorage = makePlayerStorage();
|
||||
|
||||
function addPlayer(player: Player) {
|
||||
playerStorage.initPlayer(player);
|
||||
mainPlayerStorage.initPlayer(player);
|
||||
messageClient(player, "init", "idk");
|
||||
}
|
||||
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
|
||||
function handleClientMessage(player: Player, messageType: unknown, messageContent: unknown) {
|
||||
const storedPlayer = playerStorage.fetchPlayer(player);
|
||||
const storedPlayer = mainPlayerStorage.fetchPlayer(player);
|
||||
if (messageType === "EnterGame") {
|
||||
try {
|
||||
storedPlayer.loadIn();
|
||||
|
@ -27,9 +28,17 @@ function handleClientMessage(player: Player, messageType: unknown, messageConten
|
|||
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") {
|
||||
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
|
||||
|
||||
*/
|
||||
// Genuinely require being on both sides - but in the services file? No shot!
|
||||
|
||||
|
||||
type unknownTable = { [numberKey: number]: unknown; [stringKey: string]: unknown };
|
||||
/*interface hookInEntry {
|
||||
name: string;
|
||||
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.
|
||||
// + Functions are here, as to avoid storing unecessary data in the server store.
|
||||
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
|
||||
type amounts = [health: number, barrier: number] // values used to store an entity's current status (more existential than stats)
|
||||
import { ability } from "./AbilityManager";
|
||||
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 {
|
||||
setPosition: (location: Vector3) => void;
|
||||
ability: (ability: string, state: boolean) => void;
|
||||
}
|
||||
|
||||
class entityHandler implements entity {
|
||||
|
@ -13,15 +15,37 @@ class entityHandler implements entity {
|
|||
this.baseStats = baseStats;
|
||||
this.baseAmounts = baseAmounts;
|
||||
this.puppet = makePuppet(puppetEntry);
|
||||
};
|
||||
}
|
||||
setPosition(location: Vector3) {
|
||||
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;
|
||||
baseAmounts: amounts; // Health, Barrier (things of indescribable importance)
|
||||
baseAmounts: amounts;
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
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;
|
||||
}
|
||||
export interface storedPlayer {
|
||||
teleportToServer: () => void;
|
||||
setPosition: (location: Vector3) => void;
|
||||
ability: (ability: string, state: boolean) => void;
|
||||
loadIn: () => void;
|
||||
}
|
||||
|
||||
|
@ -14,25 +16,30 @@ class storedPlayerHandler implements storedPlayer {
|
|||
constructor(player: Player) {
|
||||
this.player = player;
|
||||
this.inMainMenu = true;
|
||||
this.saveData = {placeholder: "fortnite"};
|
||||
this.saveData = { placeholder: "fortnite" };
|
||||
}
|
||||
teleportToServer() {
|
||||
// + 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
|
||||
};
|
||||
}
|
||||
setPosition(location: Vector3) {
|
||||
if (this.entity) {
|
||||
this.entity.setPosition(location);
|
||||
}
|
||||
};
|
||||
}
|
||||
ability(ability: string, state: boolean) {
|
||||
if (this.entity) {
|
||||
this.entity.ability(ability, state);
|
||||
}
|
||||
}
|
||||
loadIn() {
|
||||
this.entity = makeEntity(["Character", this.player]);
|
||||
// + Give the entity the stats it's supposed to have, load from save data maybe?
|
||||
};
|
||||
}
|
||||
player: Player;
|
||||
inMainMenu: boolean;
|
||||
// + 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;
|
||||
}
|
||||
|
||||
|
@ -43,18 +50,18 @@ export interface playerStorage {
|
|||
}
|
||||
|
||||
class playerStorageHandler implements playerStorage {
|
||||
constructor() {};
|
||||
constructor() {}
|
||||
initPlayer(player: Player) {
|
||||
this.playerStorageArray[player.UserId] = new storedPlayerHandler(player);
|
||||
// + Load player's datastore into server store
|
||||
};
|
||||
}
|
||||
deinitPlayer(player: Player) {
|
||||
const entry = this.playerStorageArray[player.UserId];
|
||||
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)
|
||||
// + 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
|
||||
};
|
||||
}
|
||||
fetchPlayer(player: Player) {
|
||||
return this.playerStorageArray[player.UserId];
|
||||
}
|
||||
|
|
|
@ -2,3 +2,7 @@
|
|||
const ReplicatedStorage = game.GetService("ReplicatedStorage");
|
||||
export const Input = ReplicatedStorage.WaitForChild("Input", 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