From 052a37b36fc9cfbe12b3dfbca5082156a3b1e170 Mon Sep 17 00:00:00 2001 From: loplkc Date: Sun, 8 May 2022 11:29:10 -0400 Subject: [PATCH] Server structure defined + functional shift ! This commit will not compile or build or anything fancy like that + Server structure defined as having two concurrent actors, the global scene and the player manager, which chatter every frame + Entity manager and scene manager expanded - Removed a bunch of shared state by not using classes --- src/game/globalScene.ts | 7 + src/server/ServerMessenger.ts | 2 +- src/server/main.server.ts | 75 +++++++++-- src/services.d.ts | 2 + src/shared/EntityManager.ts | 238 ++++++++++++++++++++++++++++++++-- src/shared/EventManager.ts | 19 --- src/shared/PlayerManager.ts | 49 ++++--- src/shared/SceneManager.ts | 82 ++++++++++++ src/shared/Shared.ts | 16 ++- 9 files changed, 429 insertions(+), 61 deletions(-) create mode 100644 src/game/globalScene.ts delete mode 100644 src/shared/EventManager.ts create mode 100644 src/shared/SceneManager.ts diff --git a/src/game/globalScene.ts b/src/game/globalScene.ts new file mode 100644 index 0000000..51165c4 --- /dev/null +++ b/src/game/globalScene.ts @@ -0,0 +1,7 @@ +import { sceneTemplate } from "shared/SceneManager" +export const globalSceneTemplate: sceneTemplate = { + sceneComplete() { + return false + }, + onCompletion: [] +} as const \ No newline at end of file diff --git a/src/server/ServerMessenger.ts b/src/server/ServerMessenger.ts index 9b1ba5c..1e5ceb0 100644 --- a/src/server/ServerMessenger.ts +++ b/src/server/ServerMessenger.ts @@ -1,5 +1,5 @@ import { Input, Output } from "shared/Shared"; -export function bindToClientMessage(functionToBind: Callback) { +export function bindToClientMessage(functionToBind: (player: Player, ...messageContents: unknown[]) => void) { assert(Input?.IsA("RemoteEvent"), 'Remote event "Input" is of incorrect class or nil'); Input.OnServerEvent.Connect(functionToBind); } diff --git a/src/server/main.server.ts b/src/server/main.server.ts index 12b9385..eb635f1 100644 --- a/src/server/main.server.ts +++ b/src/server/main.server.ts @@ -1,20 +1,70 @@ -// "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"); +// "main": Initializes all state and handles the real world. +const Players = game.GetService("Players"); // This should be the only place on the server where the Players service is mentioned +const RunService = game.GetService("RunService"); import { isUnknownTable } from "shared/Shared"; -import { makePlayerStorage, playerStorage, storedPlayer } from "shared/PlayerManager"; import { bindToClientMessage, messageClient, messageAllClients } from "./ServerMessenger"; -const mainPlayerStorage: playerStorage = makePlayerStorage(); +// Please note: This should not use any of the properties of "scene" or "playerStorage" (it only needs to know that they exist) +import { scene, initScene, runScene, applyRequestsToScene } from "shared/SceneManager" +import { playerStorage, initPlayerStorage, applyRequestsToPlayerStorage, playerManagerRequest} from "shared/PlayerManager"; +import { globalSceneTemplate } from "game/globalScene" +// Initialize all state +let globalPlayerStorage: playerStorage = initPlayerStorage(); +let globalScene: scene = initScene(globalSceneTemplate) +// Handle the real world +let playerEvents: playerManagerRequest[] = []; +function messagePlayerManager(message: playerManagerRequest): void { + playerEvents.push(message); +} +Players.PlayerAdded.Connect(function(player: Player) { + messagePlayerManager(["initPlayer", player]) +}); +Players.PlayerRemoving.Connect(function(player: Player) { + messagePlayerManager(["deinitPlayer", player]) +}); +bindToClientMessage(function(player: Player, ...messageContents: unknown[]) { + messagePlayerManager(["playerInput", player, messageContents]) +}); +// Run everything sequentially to avoid concurrency issues +let busy = false +RunService.Heartbeat.Connect(function(delta: number) { + assert(!busy) + busy = true -function addPlayer(player: Player) { + const now = os.clock() + const sceneResult = runScene(globalScene, now) + assert(sceneResult[0]); + globalScene = sceneResult[1][0] + + let thesePlayerEvents = playerEvents + playerEvents = [] + thesePlayerEvents.unshift(...sceneResult[1][1]) + + let repetitions = 0 + while (thesePlayerEvents[0]) { + const playerRequestsResult = applyRequestsToPlayerStorage(globalPlayerStorage, thesePlayerEvents) + assert(playerRequestsResult[0], playerRequestsResult[1] as string) // + The other actor should probably cleanup instead of just crashing + const sceneRequestsResult = applyRequestsToScene(globalScene,now,playerRequestsResult[1][1]); + assert(sceneRequestsResult[0], sceneRequestsResult[1] as string) + // Mutable section + globalPlayerStorage = playerRequestsResult[1][0]; + globalScene = sceneRequestsResult[1][0]; + thesePlayerEvents = sceneRequestsResult[1][1] + repetitions += 1 + assert(repetitions > 4)// I don't know if this can enter an infinite loop, but it would be very dangerous if it did + } + busy = false +}); +//const playerManager: actor = initPlayerManager(eventManager); +/* function addPlayer(player: Player) { mainPlayerStorage.initPlayer(player); messageClient(player, "init", "idk"); } function removePlayer(player: 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) { +}*/ +// function handleClientMessage(player: Player, messageType: unknown, messageContent: unknown) { + //playerManager.message(["PlayerInput"]) + /* const storedPlayer = mainPlayerStorage.fetchPlayer(player); if (messageType === "EnterGame") { try { @@ -41,8 +91,5 @@ function handleClientMessage(player: Player, messageType: unknown, messageConten storedPlayer.setPosition(messageContent); } } -} -// Action phase -Players.PlayerAdded.Connect(addPlayer); -Players.PlayerRemoving.Connect(removePlayer); -bindToClientMessage(handleClientMessage); + */ +// } diff --git a/src/services.d.ts b/src/services.d.ts index 6cffac0..bd362d8 100644 --- a/src/services.d.ts +++ b/src/services.d.ts @@ -10,6 +10,8 @@ type effectEntry = [meshType, EnumItem, effectState[]]; // The enumitem is mater */ type unknownTable = { [numberKey: number]: unknown; [stringKey: string]: unknown }; +type success = [true, Wrapped] | [false, string] +type placeholder = "foo" /*interface hookInEntry { name: string; guiObject: GuiObject; diff --git a/src/shared/EntityManager.ts b/src/shared/EntityManager.ts index cbea573..b979832 100644 --- a/src/shared/EntityManager.ts +++ b/src/shared/EntityManager.ts @@ -1,18 +1,229 @@ -// "EntityManager": Create entities objects and deck them out with functions to use. +// "EntityManager": Create entity objects and apply transformations to them. // + Functions are here, as to avoid storing unecessary data in the server store. import { makePuppet, puppet, puppetEntry } from "./Puppetmaster"; -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) - +// import { ability } from "./AbilityManager"; +// I spent a lot of time thinking about the names for these types and they still suck +type modifiers = readonly [[maxHealth: number, defense: number], [power: number, speed: number, resistance: number]]; // values used for calculation, only modified by buffs and debuffs (the first group is additive, the second multiplicative) +type statuses = readonly [health: number, barrier: number]; // values used to store an entity's current status (more existential than stats) +type statusEffect = readonly [string, modifiers, (entity: entity) => entity]; + export interface entityController { setPosition: (location: Vector3) => void; useAbility: (abilityName: string, activating: boolean) => void; } export interface entityModifier {} +/* Prototype + entityTransform(setOfAllEntities: entity[], actingEntity: entity, ability destructured to {hitBoxList, modificationList}) + const entitiesEligible = getAffectedEntities(setOfAllEntities, actingEntity) + const entitiesHit = entitiesEligible ? getHitEntities(entitiesEligible, hitBoxList) : entitiesEligible // This would be a generic function that accepts an array or a single hitbox + const entityAlteration = entitiesHit ? applyEntityToAlteration(entityTransform, actingEntity) : entitiesHit + return modifiedEntities = entityAlteration ? alterEntity(entitiesHit, entityAlteration) : entitiesHit // entitiesHit could be an array of arrays containing entities or just an array of entities + + useAbilityPart(abilityPart: abilityPart, setOfAllEntities: entity[], thisEntity: entity) + const transformedEntities = ableToUse ? entityTranform(setOfAllEntities, thisEntity) : ableToUse + const privateMessaged = transformedEntities ? messageClient(UNIMPLEMENTED) : transformedEntities + const globalMessaged = privateMessaged ? messageAllClients(UNIMPLEMENTED) : privateMessaged + return [globalMessaged, transformedEntities, schedulingForNextAbilityPart] // Might need to add some kind of reason here, not just an indiscriminate error -class entityHandler implements entityController { + useAbility(now: number, setOfAllEntities: entity[], thisEntity: entity, ability: ability) + const completeAbility = getAbilityToUse(ability, thisEntity) // Can return either [false, error] or [true, value] + const ableToUse = completeAbility ? canUseAbility(now, completeAbility) : completeAbility // canUseAbility should return the first abilityPart + return resultOfFirstAbilityPart = ableToUse ? useAbilityPart(ability.firstPart, setOfAllEntities, thisEntity) : [ableToUse] +*/ +interface entityStats { + statuses: statuses // Health and stuff that change constantly + baseModifiers: modifiers // Base modifiers that change only when the player switches weapons or something + immunities: { + [statusEffectName: string]: boolean | undefined + } + statusEffects: statusEffect[] // Burn, poison, etc. +} +interface entityId { + readonly name: string + readonly team: "players" | "enemies", + readonly isMinion: boolean +} +export interface entity { + readonly id: entityId + stats: entityStats + puppet: puppet +} + +type entityTransform = (entity: entity) => entity +// interface entityTransformTemplate { +type abuiloga = (entityPerformingId: entityId, entityReceivingId: entityId) => entityTransformTemplate | false +/* + allies: { + minions: { + } + } + opponents: { + + } + affectsMinions: boolean, + excludesSelf: boolean, + affectsSameTeam: boolean, +*///} +type entityTransformType = "heal" | "attack" +interface entityTransformTemplate { + extraFunction: (entityPerformingTransform: entity, entityReceivingTransform: entity) => boolean + thingus: entityTransformType + magnitude: number, // Positive for heal, negative for damage + affectsHealth: boolean + affectsBarrier: boolean + statusEffectsGranted: placeholder[], // Stuff like burn, slow, stun, etc.*/ +} +// type entityTransformTemplate = [entityTransformEligibilityTemplate, entityTransformApplicationTemplate] +/*function isEligibleForTransform(entityPerformingTransform: entity, entityReceivingTransform: entity, eligibilityTemplate: entityTransformEligibilityTemplate): false | [boolean] { // This function sucks + const entityReceivingId = entityReceivingTransform.id + const onSameTeam = entityPerformingTransform.id.team == entityReceivingId.team + if (onSameTeam && !eligibilityTemplate.affectsSameTeam) { + return false + } + if (entityPerformingTransform == entityReceivingTransform && eligibilityTemplate.excludesSelf) { + return false + } + if (entityReceivingId.isMinion && !eligibilityTemplate.affectsMinions) { + return false + } + if (!eligibilityTemplate.extraFunction(entityPerformingTransform, entityReceivingTransform)) { + return false + } + return [onSameTeam] +}*/ +function applyEntityToAttack(entityModifiers: modifiers, entityStatusEffects: statusEffect[], magnitude: number): number { + const attack = applyModifiersToAttack(magnitude, entityModifiers); + // + Apply status effects of performing entity to attack (e.g. weaken) + return attack +} +function applyAttackToEntityStatuses(entityStatuses: statuses, entityModifiers: modifiers, entityStatusEffects: statusEffect[], attack: number, affectsHealth: boolean, affectsBarrier: boolean): statuses { // Not sure if this should return a whole entity + // + Apply status effects of receiving entity to damage (e.g. armor break) + const damage = applyModifiersToAttack(attack, entityModifiers); + const newStatuses = applyDamageToStatuses(entityStatuses, damage, affectsHealth, affectsBarrier); + return entityStatuses +} +function makeEntityTransform(entityPerformingTransformId: entityId, abuiloga: abuiloga): (entityReceivingTransform: entity) => entityStats { + return function(entityReceivingTransform: entity): entityStats { + const entityTransformTemplate = abuiloga(entityPerformingTransformId, entityReceivingTransform.id); + + const newStats = transformEntityStatuses( // All of this stuff should be packed into one object, maybe a new entityTransformTemplate + entityReceivingTransform.stats.statuses, + finalCalculatedMaxHealth, + finalCalculatedMaxBarrier, + entityTransformTemplate.thingus, + finalCalculatedMagnitude, + finalCalculatedAffectsHealth, + finalCalculatedAffectsBarrier) // L + // const newEntity = entityTransformTemplate ? applyEntityTransform(entityPerformingTransform, entityReceivingTransform, onSameTeam, template[1]) : entityReceivingTransform; + return newStats + } +} +/*interface entityTransform extends entityTransformTemplate { + specificEntity?: string, + team: "players" | "enemies", +}*/ +type abilityTemplate = [ + [entityTransformTemplate[], number] // I guess the number is a delay until the next part or something +] +/*type ability = [ + [entityTransform[], number] +]*/ +// scene, player, aimLocation +// const ability = getAbility +// const entities = applyAbilityToScene(scene, ability) + +function applyDamageToHealth(health: number, damage: number): [newHealth: number, excessDamage: number] { // Needs testing + const newHealth = health - damage + if (newHealth < 0) { + return [0, -newHealth] + } else { + return [newHealth, 0] + } +} +const applyDamageToBarrier = applyDamageToHealth +function applyDamageToStatuses(statuses: statuses, damage: number, affectsHealth: boolean, affectsBarrier: boolean): statuses { + if (affectsBarrier) { + const [newBarrier, excessBarrierDamage] = applyDamageToBarrier(statuses[1], damage) + const [newHealth, excessHealthDamage] = applyDamageToHealth(statuses[0], affectsHealth? excessBarrierDamage : 0) + return [newHealth, newBarrier] + } else if (affectsHealth) { + const [newHealth, excessHealthDamage] = applyDamageToHealth(statuses[0], damage) + return [newHealth, statuses[1]] + } else { + return statuses + } +} +function applyHealToHealth(currentHealth: number, heal: number, maxHealth: number): [newHealth: number, excessHeal: number] { + const newHealth = currentHealth + heal + if (newHealth > maxHealth) { + return [maxHealth, newHealth - maxHealth] + } else { + return [newHealth, 0] + } +} +const applyHealToBarrier = applyHealToHealth +function applyHealToStatuses(statuses: statuses, heal: number, maxHealth: number, maxBarrier: number, affectsHealth: boolean, affectsBarrier: boolean): statuses { + if (affectsHealth) { + const [newHealth, excessHealth] = applyHealToHealth(statuses[0], heal, maxHealth) + const [newBarrier, excessBarrier] = applyHealToBarrier(statuses[1], affectsBarrier ? excessHealth : 0, maxBarrier) + return [newHealth, newBarrier] + } else if (affectsBarrier) { // Using a branch isn't optimal, but as of writing I can't think of a better solution + const [newBarrier, excessBarrier] = applyHealToBarrier(statuses[1], heal, maxBarrier) + return [statuses[0], newBarrier] + } else { + return statuses + } +} +function transformEntityStatuses(entityStatuses: statuses, maxHealth: number, maxBarrier: number, transformType: string, magnitude: number, affectsHealth: boolean, affectsBarrier: boolean): statuses { + if (transformType == "heal") { + const newStatuses = applyHealToStatuses(entityStatuses, magnitude, maxHealth, maxBarrier, affectsHealth, affectsBarrier) // More chaining method calls... + return newStatuses + } else if (transformType == "attack") { + const newStatuses = applyDamageToStatuses(entityStatuses, magnitude, affectsHealth, affectsBarrier) + return newStatuses + } else { + throw "Unimplemented transformType " + transformType + } +}// Damage should come before status effects are applied +function applyPowerToAttack(attack: number, power: number) { + return attack*power +} +function applyModifiersToAttack(attack: number, modifiers: modifiers): number { // Get arguments they use (a bit sketchy) + return applyPowerToAttack(attack, modifiers[1][0]) +} +function applyResistanceToDamage(damage: number, resistance: number): number { + return damage*(1 - resistance) +} +function applyDefenseToDamage(damage: number, defense: number): number { + return damage - defense +} +function applyModifiersToDamage(damage: number, modifiers: modifiers): number { + return applyResistanceToDamage(applyDefenseToDamage(damage, modifiers[0][1]), modifiers[1][2]) +} +function modifiersArrayFunction(applyModifiersToNumber: (number: number, modifiers: modifiers) => number): (number: number, modifiers: modifiers[]) => number { + return function(number: number, modifiersArray: modifiers[]) { + let newNumber = number + modifiersArray.forEach(function(modifiers: modifiers) { + newNumber = applyModifiersToNumber(newNumber, modifiers) + }) + return newNumber + } +} +const applyModifiersArrayToAttack = modifiersArrayFunction(applyModifiersToAttack); +const applyModifiersArrayToDamage = modifiersArrayFunction(applyModifiersToDamage); +function getEntityByName(entityList: entity[], entityName: string): success { + entityList.forEach(function(entity: entity) { + if (entity.id.name == entityName) { // Chained method calls are cringe + return [true, entity] + } + }) + return [false, "Entity not found"] +} +function getAbility() {} +function useAbility(entityList: entity[], entityUsing: entity, aim: Vector3): [entity[], placeholder[]] { +} +/*class entityHandler implements entityController { constructor(baseStats: stats, baseAmounts: amounts, puppetEntry: puppetEntry) { this.baseStats = baseStats; this.baseAmounts = baseAmounts; @@ -57,8 +268,8 @@ class entityHandler implements entityController { } = {}; baseStats: stats; baseAmounts: amounts; -} - +}*/ +/* export function makeEntity( puppetEntry: puppetEntry, baseStats = [100, 1, 16, 0] as stats, @@ -66,3 +277,14 @@ export function makeEntity( ) { return new entityHandler(baseStats, baseAmounts, puppetEntry); } + +class entityManager extends actorClass { + constructor() { + super() + } + entities: entity[] = []; +} +export function initEntityManager() { + return new entityManager(); +} +*/ \ No newline at end of file diff --git a/src/shared/EventManager.ts b/src/shared/EventManager.ts deleted file mode 100644 index 416ec6c..0000000 --- a/src/shared/EventManager.ts +++ /dev/null @@ -1,19 +0,0 @@ -// "The": Handle events. -// WORST CONDITION RigHT NOW -// Consider that this is the second module coupled to the EntityManager -import { makeEntity, entityController } from "./EntityManager"; -interface event { - winEvents?: event[]; // A list of events that need to return true (in sequence) to complete this event - winEntities?: entityController[]; // 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(); - const endTime = 2; - while (!complete) { - if (event.timeout === 2) { - complete = true; - } - } -} diff --git a/src/shared/PlayerManager.ts b/src/shared/PlayerManager.ts index f389073..62a126b 100644 --- a/src/shared/PlayerManager.ts +++ b/src/shared/PlayerManager.ts @@ -1,17 +1,39 @@ // "PlayerManager": Handle the data of players. This involves receiving them when they arrive, cleaning up after they exit, teleporting them, etc. -import { makeEntity, entityController } from "./EntityManager"; +// The player would never even touch the SceneManager if they entered a server, tweaked settings in the menu, and joined a friend in another server. +// The handling of players must be sequential- it does not make sense to try to handle the same player joining and leaving in parallel +// This is also where persisted data is stored while the player is playing. + +// import { makeEntity, entityController } from "./EntityManager"; +//import { actorClass } from "shared/Shared" +import { sceneManagerRequest } from "./SceneManager" +export type playerManagerRequest = ["initPlayer" | "deinitPlayer", Player] | ["playerInput", ...unknown[]] | ["foo", "bar"] interface saveDataEntry { // + May need to move this to archiver placeholder: string; } +interface storedPlayer { + // currentScene: event //Not sure about this + /*initPlayer: (player: Player) => void; + deinitPlayer: (player: Player) => void; + fetchPlayer: (player: Player) => storedPlayer;*/ +} +export type playerStorage = storedPlayer[]; +export function applyRequestsToPlayerStorage(playerStorage: playerStorage, requests: playerManagerRequest[]): success<[playerStorage, sceneManagerRequest[]]> { + return [true, [playerStorage, []]]; // This really sucks to look at right now +} +export function initPlayerStorage() { + return [{},{}]; +} +/* Deprecated export interface storedPlayer { teleportToServer: () => void; - setPosition: (location: Vector3) => void; - ability: (ability: string, state: boolean) => void; - loadIn: () => void; -} + //setPosition: (location: Vector3) => void; + //ability: (ability: string, state: boolean) => void; + //loadIn: () => void; +}*/ +/* Deprecated (may have useful information) class storedPlayerHandler implements storedPlayer { constructor(player: Player) { this.player = player; @@ -40,16 +62,11 @@ class storedPlayerHandler implements storedPlayer { inMainMenu: boolean; // + Other data that is unique to players but does not persist between sessions saveData: saveDataEntry; // This gets synced with the actual datastore - entity?: entityController; -} + //entity?: entityController; +}*/ -export interface playerStorage { - initPlayer: (player: Player) => void; - deinitPlayer: (player: Player) => void; - fetchPlayer: (player: Player) => storedPlayer; -} -class playerStorageHandler implements playerStorage { +/*class playerStorageHandler implements playerStorage { constructor() {} initPlayer(player: Player) { this.playerStorageArray[player.UserId] = new storedPlayerHandler(player); @@ -66,8 +83,4 @@ class playerStorageHandler implements playerStorage { return this.playerStorageArray[player.UserId]; } playerStorageArray: storedPlayerHandler[] = []; -} - -export function makePlayerStorage() { - return new playerStorageHandler(); -} +}*/ diff --git a/src/shared/SceneManager.ts b/src/shared/SceneManager.ts new file mode 100644 index 0000000..b02c063 --- /dev/null +++ b/src/shared/SceneManager.ts @@ -0,0 +1,82 @@ +// "The": Handle events. +import { entity, getEntityByName/*entityManagerRequest /*makeEntity, entityController*/ } from "./EntityManager"; +import { applyRequestsToPlayerStorage, playerManagerRequest } from "./PlayerManager" +export type sceneManagerRequest = [Player, "useAbility", Vector3] | [Player, "foo", "bar"] +type endConditionFunction = (containedScenes: scene[], containedEntities: entity[], timeElapsed: number) => boolean +export interface sceneTemplate { + readonly sceneComplete: endConditionFunction // Checks conditions that need to pass for the scene to end (e.g. entityX.Alive == false || timeSpent > 1000) + readonly onCompletion: readonly playerManagerRequest[] // Requests to get sent out when the scene ends +} +export interface scene extends sceneTemplate { + containedScenes?: { + [sceneName: string]: scene | undefined + }; // Scenes within this scene that are isolated from each other and can be run in parallel + // Not necessarily "A list of events that need to return true (in sequence) to complete this event", but such events would go there + containedEntities: entity[]; // A list of entities that need to die to complete the event + players: { + [userId: number]: [inThisScene: true] | [inThisScene: false, subScene: string] | undefined + } + //timeout?: number; // A timeout for the event; passes a lose condition if there are other completion requirements that have not been satisfied +} +export function runScene(scene: scene, now: number): success<[scene, playerManagerRequest[]]> { + return [true, [scene, []]]; +} +function getPlayerSceneName(scene: scene, userId: number): success { + let playerSceneLocation = scene.players[userId]; + if (!playerSceneLocation) { + return [false, "Player does not exist"]; // Some kind of error needs to go here + } else if (playerSceneLocation[0]) { + return [true, false]; + } else { + return [true, playerSceneLocation[1]] + } +} +function applyRequestToScene(scene: scene, now: number, request: sceneManagerRequest): [scene, playerManagerRequest[]] { + const playerSceneResult = getPlayerSceneName(scene, request[0].UserId) + if (!playerSceneResult[0]) { + return [scene, []]; // Some kind of error needs to go here + } + const playerSceneName = playerSceneResult[1] + if (!playerSceneName) { + const playerEntity = getEntityByName + if (request[1] == "useAbility") { + return useAbility(scene, ) + } else { + throw("Invalid request to SceneManager") + } + } else { // Case needs testing once it becomes relevant (this code is a mess) + const containedScenes = scene.containedScenes + assert(containedScenes) + let playerScene = containedScenes[playerSceneName] + assert(playerScene) + const sceneRequestResult = applyRequestToScene(playerScene, now, request) // There should be no stack overflow unless you nest too many scenes + containedScenes[playerSceneName] = sceneRequestResult[0] // This is questionably object-oriented + scene.containedScenes = containedScenes + return [scene, sceneRequestResult[1]] + } +} +export function applyRequestsToScene(scene: scene, now: number, requests: sceneManagerRequest[]): success<[scene, playerManagerRequest[]]> { + try { + let newScene: scene = scene + let outgoingRequests: playerManagerRequest[] = [] + requests.forEach(function(request: sceneManagerRequest) { + const sceneRequestResult = applyRequestToScene(newScene, now, request); + newScene = sceneRequestResult[0] + outgoingRequests = [...outgoingRequests, ...sceneRequestResult[1]] + }) + return [true, [newScene, outgoingRequests]]; + } + catch(error) { + return [false, error] + } +} +export function initScene(sceneTemplate: sceneTemplate): scene { + // Make the stuff described in the scene... + const newScene: scene = { + containedEntities: [], + players: [], + sceneComplete: sceneTemplate.sceneComplete, + onCompletion: sceneTemplate.onCompletion, + } + return newScene; +}; diff --git a/src/shared/Shared.ts b/src/shared/Shared.ts index b334761..97a25a5 100644 --- a/src/shared/Shared.ts +++ b/src/shared/Shared.ts @@ -1,4 +1,4 @@ -// "Remotes" +// "Shared" const ReplicatedStorage = game.GetService("ReplicatedStorage"); export const Input = ReplicatedStorage.WaitForChild("Input", 1); export const Output = ReplicatedStorage.WaitForChild("Output", 1); @@ -6,3 +6,17 @@ export const Output = ReplicatedStorage.WaitForChild("Output", 1); export function isUnknownTable(thing: unknown): thing is unknownTable { return typeIs(thing, "table"); } + +/*export class actorClass implements actor { + message(message: MessageType) { + this.mailbox.push(message) + if (!this.busy) { + this.busy = true + } + } + readMessage() { + return this.mailbox.shift() + } + mailbox: MessageType[] = []; + busy = false; +}*/ \ No newline at end of file