Moved some interfaces out of services to decouple

+ More OOP changes on the server side
This commit is contained in:
loplkc loplkc 2022-01-29 23:04:01 -05:00
parent 47bde9f3ce
commit 79202b9034
11 changed files with 260 additions and 134 deletions

View file

@ -1,5 +1,5 @@
import { Input, Output } from "shared/Remotes";
export function bindToOutput(functionToBind: Callback) {
export function bindToServerMessage(functionToBind: Callback) {
assert(Output?.IsA("RemoteEvent"), 'Remote event "Input" is of incorrect class or nil');
Output.OnClientEvent.Connect(functionToBind);
}

View file

@ -24,7 +24,7 @@ interface effectHandler extends effectMaker, effectRunner {
}
export function makeEffectHandler(effectFolder: Folder) {
const effectHandler: effectHandler = {
return {
meshPartEffect: function(meshPart: MeshPart, material: Enum.Material, effectKeypoints: effectKeypoint[], priority?: number) {
const effectMeshPart = meshPart.Clone();
effectMeshPart.Material = material;
@ -102,8 +102,7 @@ export function makeEffectHandler(effectFolder: Folder) {
EFFECT_FOLDER: effectFolder,
effectsToRun: [],
}
return effectHandler;
} as effectHandler;
}
/*
local function horseEffModule(o,typ,a1,a2,a3,a4,a5,a6,a7)

View file

@ -0,0 +1,96 @@
const Players = game.GetService("Players");
const UserInputService = game.GetService("UserInputService");
const ContextActionService = game.GetService("ContextActionService");
const Workspace = game.GetService("Workspace");
const CAMERA = Workspace.CurrentCamera as Camera;
assert(CAMERA, 'Camera of "' + Players.LocalPlayer.DisplayName + '"does not exist! (HOW???)');
export type inputBindings = {
[input in keyof controlBindings]?: [((argument1?: unknown, argument2?: unknown) => void), unknown, unknown];
};
export interface inputBinder {
assignControlBindings: (controlBindings: controlBindings) => void;
assignInputBindings: (inputBindings: inputBindings) => void;
removeInputBindings: (inputBindings: inputBindings) => void;
}
interface inputHandler extends inputBinder {
controlHandler: (actionName: string, state: Enum.UserInputState, inputObject: InputObject) => void;
controlBindings?: controlBindings
boundInputs: inputBindings
storedInput?: string // + compound inputs
}
const hitParams = new RaycastParams();
//hitParams.FilterDescendantsInstances = {efFolder,Plr.Character}
hitParams.FilterType = Enum.RaycastFilterType.Blacklist;
function getMouseLocation(): [Vector3, Vector3, Instance | undefined] {
//hitParams.FilterDescendantsInstances = {efFolder,Plr.Character}
const mouseLocation = UserInputService.GetMouseLocation();
const unitRay = CAMERA.ViewportPointToRay(mouseLocation.X, mouseLocation.Y);
const cast = Workspace.Raycast(unitRay.Origin, unitRay.Direction.mul(1000), hitParams);
if (cast) {
return [cast.Position, cast.Normal, cast.Instance];
} else {
return [unitRay.Origin.add(unitRay.Direction.mul(1000)), new Vector3(0, 0, 0), undefined];
}
}
function isValidInput(controlBindings: controlBindings, value: string): value is keyof controlBindings {
return value in controlBindings; // uh oh
}
//function(actionName: string, state: Enum.UserInputState, inputObject: InputObject) {
// inputParameters[0](inputParameters[1])
//}
export function makeInputHandler() {
const t: inputHandler = {//return {
assignControlBindings: function(controlBindings: controlBindings) {
this.controlBindings = controlBindings // WOW!!!!!
},
assignInputBindings: function(inputBindings: inputBindings) {
const controlBindings = this.controlBindings;
if (controlBindings) {
for (let input in inputBindings) {
if (isValidInput(controlBindings, input)) {
// + Check for this.boundInputs[input]
const inputParameters = inputBindings[input]
if (inputParameters) {
this.boundInputs[input] = inputParameters;
const controlArray = controlBindings[input];
for (const control of controlArray) {
ContextActionService.BindAction(input, this.controlHandler, false, control)
}
}
}
}
}
},
removeInputBindings: function(inputBindings: inputBindings) {},
controlHandler: function(actionName: string, state: Enum.UserInputState, inputObject: InputObject) {
const controlBindings = this.controlBindings;
if (controlBindings) {
if (isValidInput(controlBindings, actionName)) {
const inputParameters = this.boundInputs[actionName]
if (inputParameters) {
inputParameters[0](inputParameters[1], inputParameters[2])
}
}
}
},
boundInputs: {}
} //as inputHandler;
}
/*
function handleInput(input: InputObject, otherInteraction: boolean) {
let mousePosition: Vector3, mouseNormal: Vector3, mouseInstance: Instance | undefined;
[mousePosition, mouseNormal, mouseInstance] = getMouseLocation(); // eslint-disable-line prefer-const
if (input.UserInputType === Enum.UserInputType.MouseButton1) {
messageServer("move", mousePosition);
}
}
*/
// UserInputService.InputBegan.Connect(handleInput);

View file

@ -1,9 +1,7 @@
// "init": The local script. This script doesn't have to account for any other players.
// "init": The main client-side thread.
const Players = game.GetService("Players");
const UserInputService = game.GetService("UserInputService");
const RunService = game.GetService("RunService");
const Workspace = game.GetService("Workspace");
import { bindToOutput, messageServer } from "./ClientMessenger";
import { bindToServerMessage, messageServer } from "./ClientMessenger";
import { handleGuiInput, drawGui, closeGui } from "./GuiHandler";
import { makeEffectHandler, effectRunner } from "./EffectMaker";
const LOCALPLAYER = Players.LocalPlayer;
@ -12,8 +10,6 @@ assert(
PLAYERGUI && classIs(PLAYERGUI, "PlayerGui"),
'PlayerGui of "' + LOCALPLAYER.DisplayName + '"does not exist! (HOW???)',
);
const CAMERA = Workspace.CurrentCamera as Camera;
assert(CAMERA, 'Camera of "' + LOCALPLAYER.DisplayName + '"does not exist! (HOW???)');
let inMainMenu = true;
function openMainMenu(playerGui: PlayerGui) {
@ -26,29 +22,7 @@ function openMainMenu(playerGui: PlayerGui) {
}
}
const hitParams = new RaycastParams();
//hitParams.FilterDescendantsInstances = {efFolder,Plr.Character}
//hitParams.FilterType = Enum.RaycastFilterType.Blacklist;
function getMouseLocation(): [Vector3, Vector3, Instance | undefined] {
//hitParams.FilterDescendantsInstances = {efFolder,Plr.Character}
const mouseLocation = UserInputService.GetMouseLocation();
const unitRay = CAMERA.ViewportPointToRay(mouseLocation.X, mouseLocation.Y);
const cast = Workspace.Raycast(unitRay.Origin, unitRay.Direction.mul(1000), hitParams);
if (cast) {
return [cast.Position, cast.Normal, cast.Instance];
} else {
return [unitRay.Origin.add(unitRay.Direction.mul(1000)), new Vector3(0, 0, 0), undefined];
}
}
function handleInput(input: InputObject, otherInteraction: boolean) {
let mousePosition: Vector3, mouseNormal: Vector3, mouseInstance: Instance | undefined;
[mousePosition, mouseNormal, mouseInstance] = getMouseLocation(); // eslint-disable-line prefer-const
if (input.UserInputType === Enum.UserInputType.MouseButton1) {
messageServer("move", mousePosition);
}
}
function handleOutput(messageType: unknown, messageContent: unknown) {
function handleServerMessage(messageType: unknown, messageContent: unknown) {
if (messageType === "init") {
openMainMenu(PLAYERGUI);
inMainMenu = true;
@ -57,12 +31,12 @@ function handleOutput(messageType: unknown, messageContent: unknown) {
inMainMenu = false;
}
}
// Action phase
UserInputService.InputBegan.Connect(handleInput);
bindToOutput(handleOutput);
// Bind functions
bindToServerMessage(handleServerMessage);
const effectRunners: effectRunner[] = [];
// Put stuff in the effectRunners table
// + Put stuff in the effectRunners table
RunService.RenderStepped.Connect(function(deltaTime) {
effectRunners.forEach(effectRunner => {
effectRunner.runEffects(deltaTime)

View file

@ -1,13 +1,13 @@
import { Input, Output } from "shared/Remotes";
export function bindToInput(functionToBind: Callback) {
export function bindToClientMessage(functionToBind: Callback) {
assert(Input?.IsA("RemoteEvent"), 'Remote event "Input" is of incorrect class or nil');
Input.OnServerEvent.Connect(functionToBind);
}
export function messageClient(client: Player, messageType: serverMessageType, messageContent?: string) {
export function messageClient(client: Player, messageType: string, messageContent?: string) {
assert(Output?.IsA("RemoteEvent"), 'Remote event "Output" is of incorrect class or nil');
Output.FireClient(client, messageType, messageContent);
}
export function messageAllClients(messageType: serverMessageType, messageContent?: string) {
export function messageAllClients(messageType: string, messageContent?: string) {
assert(Output?.IsA("RemoteEvent"), 'Remote event "Output" is of incorrect class or nil');
Output.FireAllClients(messageType, messageContent);
}

View file

@ -1,24 +1,23 @@
// "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 { makeEntity, moveEntity } from "shared/EntityManager";
import { initPlayer, deinitPlayer, loadInPlayer, teleportPlayer } from "shared/PlayerManager";
import { bindToInput, messageClient, messageAllClients } from "./ServerMessenger";
const playerStorage: (playerStorageEntry | undefined)[] = [];
const entityStorage: entity[] = [];
import { makePlayerStorage, playerStorage, storedPlayer } from "shared/PlayerManager";
import { bindToClientMessage, messageClient, messageAllClients } from "./ServerMessenger";
const playerStorage: playerStorage = makePlayerStorage();
function addPlayer(player: Player) {
playerStorage[player.UserId] = initPlayer(player);
playerStorage.initPlayer(player);
messageClient(player, "init", "idk");
}
function removePlayer(player: Player) {
playerStorage[player.UserId] = deinitPlayer(playerStorage[player.UserId], player);
playerStorage.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 handleInput(player: Player, messageType: unknown, messageContent: unknown) {
function handleClientMessage(player: Player, messageType: unknown, messageContent: unknown) {
const storedPlayer = playerStorage.fetchPlayer(player);
if (messageType === "EnterGame") {
try {
entityStorage[player.UserId] = loadInPlayer(player);
storedPlayer.loadIn();
messageClient(player, "enterGame");
} catch (thrownError) {
if (typeIs(thrownError, "string")) {
@ -30,11 +29,11 @@ function handleInput(player: Player, messageType: unknown, messageContent: unkno
}
} else if (messageType === "move") {
if (typeIs(messageContent, "Vector3")) {
moveEntity(entityStorage[player.UserId], messageContent);
storedPlayer.setPosition(messageContent)
}
}
}
// Action phase
Players.PlayerAdded.Connect(addPlayer);
Players.PlayerRemoving.Connect(removePlayer);
bindToInput(handleInput);
bindToClientMessage(handleClientMessage);

41
src/services.d.ts vendored
View file

@ -1,33 +1,8 @@
type puppetEntry = ["Character", Player] | ["Placeholder", "Placeholder"];
/*
type bodyPart = "root" | "torso" | "head" | "leftArm" | "rightArm" | "leftLeg" | "rightLeg";
type serverMessageType = "init" | "promptError" | "enterGame";
type clientMessageType = "move" | "placeholder";
interface saveDataEntry {
placeholder: string;
}
interface playerStorageEntry {
inMainMenu: boolean;
// + Other data that is unique to players but does not persist between sessions
saveData: saveDataEntry;
entity?: entity;
}
interface puppet {
entry: puppetEntry;
model: Model;
rootPart: Part;
//placeholder: (x: string) => string; // + "Puppet string" functions will (not?) go here
}
interface entity {
baseStats: [number, number, number, number]; // MaxHealth, Attack, Speed, Defense (things used for calculation, only modified by buffs and debuffs)
baseAmounts: [number, number]; // Health, Barrier (things of indescribable importance)
puppet: puppet;
}
interface event {
winEvents?: event[]; // A list of events that need to return true (in sequence) to complete this event
winEntities?: entity[]; // A list of entities that need to die to complete the event
timeout?: number; // A timeout for the event; passes a lose condition if there are other completion requirements that have not been satisfied
}
// + Why can the client see all of these?
type meshType = "Ball";
type effectState = [CFrame, Vector3, Color3, number]; // The number is transparency
@ -36,6 +11,18 @@ type effectEntry = [meshType, EnumItem, effectState[]]; // The enumitem is mater
interface modeLocal {
aura?: [effectEntry, bodyPart?, number?][]; // effect, part it is attached to (default root), how many times it should be called per frame (default 1)
}
*/
// Genuinely require being on both sides - but in the services file? No shot!
type acceptedControls = Enum.KeyCode[] // + Include controller "keys"
interface controlBindings {
clicker1: acceptedControls; // What is used to click on things (enemies in game, UI elements)
diamond1: acceptedControls; // Diamond controls
diamond2: acceptedControls;
diamond3: acceptedControls;
diamond4: acceptedControls;
special1: acceptedControls; // Special controls
}
/*interface hookInEntry {
name: string;

View file

@ -1,18 +1,27 @@
// "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, movePuppet } from "./Puppetmaster";
export function makeEntity(puppetEntry: puppetEntry) {
const newEntity: entity = {
baseStats: [0, 0, 0, 0],
baseAmounts: [0, 0],
puppet: makePuppet(puppetEntry),
};
return newEntity;
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)
export interface entity {
setPosition: (location: Vector3) => void;
}
// This exists because the main server should never see the puppets, but it is a bit weird
export function moveEntity(entity: entity | undefined, location: Vector3) {
if (entity) {
entity.puppet = movePuppet(entity.puppet, location);
class entityHandler implements entity {
constructor(baseStats: stats, baseAmounts: amounts, puppetEntry: puppetEntry) {
this.baseStats = baseStats;
this.baseAmounts = baseAmounts;
this.puppet = makePuppet(puppetEntry);
};
setPosition(location: Vector3) {
this.puppet.movePuppet(location);
}
baseStats: stats;
baseAmounts: amounts; // Health, Barrier (things of indescribable importance)
puppet: puppet;
}
export function makeEntity(puppetEntry: puppetEntry, baseStats = [100, 1, 16, 0] as stats, baseAmounts = [100, 0] as amounts) {
return new entityHandler(baseStats, baseAmounts, puppetEntry);
}

View file

@ -1,6 +1,11 @@
// "The": Handle events.
// WORST CONDITION RigHT NOW
import { makeEntity } from "./EntityManager";
import { makeEntity, entity } from "./EntityManager";
interface event {
winEvents?: event[]; // A list of events that need to return true (in sequence) to complete this event
winEntities?: entity[]; // A list of entities that need to die to complete the event
timeout?: number; // A timeout for the event; passes a lose condition if there are other completion requirements that have not been satisfied
}
export function runEvent(event: event) {
let complete = false;
const startTime = os.clock();

View file

@ -1,28 +1,66 @@
// "PlayerManager": Handle the data of players. This involves receiving them when they arrive, cleaning up after they exit, teleporting them, etc.
import { makeEntity } from "./EntityManager";
import { makeEntity, entity } from "./EntityManager";
export function initPlayer(player: Player) {
const newEntry: playerStorageEntry = {
inMainMenu: true,
saveData: {
placeholder: "placeholder",
},
interface saveDataEntry { // + May need to move this to archiver
placeholder: string;
}
export interface storedPlayer {
teleportToServer: () => void;
setPosition: (location: Vector3) => void;
loadIn: () => void;
}
class storedPlayerHandler implements storedPlayer {
constructor(player: Player) {
this.player = player;
this.inMainMenu = true;
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
};
// + Load player's datastore into server store
return newEntry; // Return the entry to be put into the server store
setPosition(location: Vector3) {
if (this.entity) {
this.entity.setPosition(location);
}
};
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;
entity?: entity;
}
export function deinitPlayer(entry: playerStorageEntry | undefined, player: Player) {
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
export interface playerStorage {
initPlayer: (player: Player) => void;
deinitPlayer: (player: Player) => void;
fetchPlayer: (player: Player) => storedPlayer;
}
export function loadInPlayer(player: Player) {
const entity = makeEntity(["Character", player]);
// + Give the entity the stats it's supposed to have, load from save data maybe?
return entity;
class playerStorageHandler implements playerStorage {
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];
}
playerStorageArray: storedPlayerHandler[] = [];
}
export function teleportPlayer() {
// + 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
export function makePlayerStorage() {
return new playerStorageHandler();
}

View file

@ -5,34 +5,53 @@ const puppetLibraries = {
["Placeholder"]: m1,
};
export function makePuppet(puppetEntry: puppetEntry) {
export type puppetEntry = ["Character", Player] | ["Placeholder", "Placeholder"];
export interface puppet {
movePuppet: (location: Vector3) => void; // + Success value maybe?
}
/* interface puppetHandler extends puppet {
entry: puppetEntry;
} */
/* interface completePuppetHandler extends puppetHandler {
model: Model;
rootPart: Part;
} */
function makePuppetModel(puppetEntry: puppetEntry) {
if (puppetEntry[0] === "Character") {
const model: [Model, Part] = puppetLibraries[puppetEntry[0]].makeModel(puppetEntry[1]);
return {
entry: puppetEntry,
model: model[0],
rootPart: model[1],
};
return puppetLibraries[puppetEntry[0]].makeModel(puppetEntry[1]) as [Model, Part];
} else {
throw 'Invalid puppet type "' + puppetEntry[0] + '"!';
}
}
function verifyPuppetExistence(puppet: puppet) {
if (puppet.rootPart.Parent) {
// Placeholder; puppet integrity will include other body parts
return puppet;
} else {
print("No puppet!");
return makePuppet(puppet.entry);
function verifyPuppetExistence(puppetHandler: puppetHandler)/*: puppetHandler is completePuppetHandler */{ // + Making this do type checking is currently beyond me
if (!puppetHandler.rootPart || !puppetHandler.rootPart.Parent) {
print("Regenerating puppet!");
[puppetHandler.model, puppetHandler.rootPart] = makePuppetModel(puppetHandler.entry);
}
// return true;
}
class puppetHandler implements puppet {
constructor(puppetEntry: puppetEntry) {
this.entry = puppetEntry;
};
export function movePuppet(puppet: puppet, location: Vector3) {
print("executing puppet move");
puppet = verifyPuppetExistence(puppet); //const newPuppet = verifyPuppetExistence(puppet);
//puppet.rootPart = newPuppet.rootPart;
//puppet.model = newPuppet.model;
puppet.rootPart.CFrame = new CFrame(location);
return puppet;
movePuppet(location: Vector3) {
print("executing puppet move");
verifyPuppetExistence(this);
if (!this.rootPart) { // + Remove this once you get better at typescript
throw "Fornite";
}
this.rootPart.CFrame = new CFrame(location);
}
entry: puppetEntry;
model?: Model;
rootPart?: Part;
};
export function makePuppet(puppetEntry: puppetEntry) {
return new puppetHandler(puppetEntry);// return newPuppet as puppet;
}