Compare commits

..

No commits in common. "ee32742ae8d938b38a81c38583ee13c09ad10c3c" and "8a9d5414f83c5d202b36c8ac0d1266eb359a1ee3" have entirely different histories.

8 changed files with 152 additions and 350 deletions

View file

@ -1,4 +1,3 @@
--An attempt to profile the speed of TweenService:GetValue
local TweenService = game:GetService("TweenService") local TweenService = game:GetService("TweenService")
local clock = os.clock local clock = os.clock
local first = clock() local first = clock()

7
src/game/globalScene.ts Normal file
View file

@ -0,0 +1,7 @@
import { sceneTemplate } from "shared/SceneManager"
export const globalSceneTemplate: sceneTemplate = {
sceneComplete() {
return false
},
onCompletion: []
} as const

View file

@ -1,6 +0,0 @@
import { sceneTemplate } from "shared/SceneManager";
export const scene0Template: sceneTemplate = {
sceneComplete() {
return [false];
},
} as const;

View file

@ -1,119 +1,58 @@
// "main": Initializes all state and handles the real world. // "main": Initializes all state and handles the real world.
const VERBOSE = true;
if (!VERBOSE) {
function print() {}
function warn() {}
}
const Players = game.GetService("Players"); // This should be the only place on the server where the Players service is mentioned 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"); const RunService = game.GetService("RunService");
import { isUnknownTable, makeApplyConsecutiveRequestsToObjectFunction } from "shared/Shared"; import { isUnknownTable } from "shared/Shared";
import { bindToClientMessage, messageClient, messageAllClients } from "./ServerMessenger"; import { bindToClientMessage, messageClient, messageAllClients } from "./ServerMessenger";
// Please note: This should not use any of the properties of "scene" or "playerStorage" (it only needs to know that they exist) // 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, sceneRequest, stageRequest, processSceneExternalInput } from "shared/SceneManager"; import { scene, initScene, runScene, applyRequestsToScene } from "shared/SceneManager"
import { import { playerStorage, initPlayerStorage, applyRequestsToPlayerStorage, playerManagerRequest} from "shared/PlayerManager";
playerStorage, import { globalSceneTemplate } from "game/globalScene"
initPlayerStorage,
handleConsecutivePlayerActivities,
playerActivity,
messageToPlayer,
} from "shared/PlayerManager";
import { scene0Template } from "game/scene0";
interface stage {
[sceneName: string]: scene | undefined;
}
// Initialize all state // Initialize all state
let globalPlayerStorage: playerStorage = initPlayerStorage(); let globalPlayerStorage: playerStorage = initPlayerStorage();
let stage: stage = {}; let globalScene: scene = initScene(globalSceneTemplate)
stage["scene0"] = initScene(scene0Template); // Handle the real world
// Handle the real world (TERA I/O) let playerEvents: playerManagerRequest[] = [];
let playerManagerMailbox: playerActivity[] = []; function messagePlayerManager(message: playerManagerRequest): void {
function messagePlayerManager(message: playerActivity): void { playerEvents.push(message);
playerManagerMailbox.push(message);
} }
Players.PlayerAdded.Connect(function (player: Player) { Players.PlayerAdded.Connect(function(player: Player) {
messagePlayerManager(["player_joined", player]); messagePlayerManager(["initPlayer", player])
}); });
Players.PlayerRemoving.Connect(function (player: Player) { Players.PlayerRemoving.Connect(function(player: Player) {
messagePlayerManager(["player_left", player]); messagePlayerManager(["deinitPlayer", player])
}); });
let playerAttemptedActionCounter: number[] = []; bindToClientMessage(function(player: Player, ...messageContents: unknown[]) {
bindToClientMessage(function (player: Player, ...messageContents: unknown[]) { messagePlayerManager(["playerInput", player, messageContents])
const userId = player.UserId;
playerAttemptedActionCounter[userId] += 1;
if (playerAttemptedActionCounter[userId] <= 2) {
messagePlayerManager(["player_tried_action", player, messageContents]);
}
}); });
function applySceneRequestToStage(stage: stage, now: number, request: stageRequest): [stage, messageToPlayer[]] { // Run everything sequentially to avoid concurrency issues
const sceneName = request[0]; let busy = false
const thisScene = stage[sceneName]; RunService.Heartbeat.Connect(function(delta: number) {
if (!thisScene) { assert(!busy)
warn("Scene " + request[0] + " does not exist"); busy = true
return [stage, []];
} const now = os.clock()
const [newScene, messagesToPlayer] = processSceneExternalInput(thisScene, now, request[1]); const sceneResult = runScene(globalScene, now)
// No way to make a clean copy of a dictionary, so... assert(sceneResult[0]);
const newStage = stage; globalScene = sceneResult[1][0]
newStage[sceneName] = newScene;
return [newStage, messagesToPlayer]; let thesePlayerEvents = playerEvents
} playerEvents = []
const applySceneRequestsToStage = makeApplyConsecutiveRequestsToObjectFunction<stage, stageRequest, messageToPlayer>( thesePlayerEvents.unshift(...sceneResult[1][1])
applySceneRequestToStage,
); let repetitions = 0
// Run everything sequentially each frame to avoid concurrency issues while (thesePlayerEvents[0]) {
let busy = false; const playerRequestsResult = applyRequestsToPlayerStorage(globalPlayerStorage, thesePlayerEvents)
let frameCounter = 0; assert(playerRequestsResult[0], playerRequestsResult[1] as string) // + The other actor should probably cleanup instead of just crashing
RunService.Heartbeat.Connect(function (delta: number) { const sceneRequestsResult = applyRequestsToScene(globalScene,now,playerRequestsResult[1][1]);
if (!busy) { assert(sceneRequestsResult[0], sceneRequestsResult[1] as string)
busy = true; // Mutable section
if (frameCounter < 10) { globalPlayerStorage = playerRequestsResult[1][0];
frameCounter += 1; globalScene = sceneRequestsResult[1][0];
} else { thesePlayerEvents = sceneRequestsResult[1][1]
frameCounter = 0; repetitions += 1
playerAttemptedActionCounter = []; assert(repetitions > 4)// I don't know if this can enter an infinite loop, but it would be very dangerous if it did
}
const now = os.clock();
const playerManagerMail = playerManagerMailbox;
playerManagerMailbox = [];
if (playerManagerMail.size() > 5) {
warn("playerManagerMail received in a single frame exceeds 5");
}
let sceneManagerRequests;
let messagesToPlayers;
// Players act first
[globalPlayerStorage, sceneManagerRequests] = handleConsecutivePlayerActivities(
globalPlayerStorage,
playerManagerMail,
);
[stage, messagesToPlayers] = applySceneRequestsToStage(stage, now, sceneManagerRequests);
// Scene acts second
// + run scene
// Resolve aftermath
// + coagulate player requests from both of those and give them back to the player manager (wow, guy exploded, colors everywhere, you got a new sword)
// ? Is there any reason why the player manager would respond? probably not, add that later if it's necessary
const sceneResult = runScene(globalScene, now);
assert(sceneResult[0]);
stage = sceneResult[1][0];
//thesePlayerEvents.unshift(...sceneResult[1][1]);
let repetitions = 0;
while (thesePlayerEvents[0]) {
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;
} else {
warn("Loop");
} }
busy = false
}); });
//const playerManager: actor<playerManagerRequest> = initPlayerManager(eventManager); //const playerManager: actor<playerManagerRequest> = initPlayerManager(eventManager);
/* function addPlayer(player: Player) { /* function addPlayer(player: Player) {
@ -124,8 +63,8 @@ function removePlayer(player: Player) {
mainPlayerStorage.deinitPlayer(player); mainPlayerStorage.deinitPlayer(player);
}*/ }*/
// function handleClientMessage(player: Player, messageType: unknown, messageContent: unknown) { // function handleClientMessage(player: Player, messageType: unknown, messageContent: unknown) {
//playerManager.message(["PlayerInput"]) //playerManager.message(["PlayerInput"])
/* /*
const storedPlayer = mainPlayerStorage.fetchPlayer(player); const storedPlayer = mainPlayerStorage.fetchPlayer(player);
if (messageType === "EnterGame") { if (messageType === "EnterGame") {
try { try {

View file

@ -1,19 +1,12 @@
// "EntityManager": Create entity objects and apply transformations to them. // "EntityManager": Create entity objects and apply transformations to them.
// A bold (or just foolish) attempt at functional programming in a videogame entity system // A bold attempt at functional programming in a videogame entity system
import { makePuppet, puppet, puppetEntry } from "./Puppetmaster"; import { makePuppet, puppet, puppetEntry } from "./Puppetmaster";
// import { ability } from "./AbilityManager"; // import { ability } from "./AbilityManager";
const entityTags = ["minion"] as const; const entityTags = ["minion"] as const
type modifiers = readonly [power: number, speed: number, defense: number, resistance: number]; // values used for calculation, only modified by buffs and debuffs type modifiers = readonly [power: number, speed: number, defense: number, resistance: number]; // values used for calculation, only modified by buffs and debuffs
type statuses = readonly [health: number, barrier: number]; // values used to store an entity's current status (more existential than stats) type statuses = readonly [health: number, barrier: number]; // values used to store an entity's current status (more existential than stats)
type statusEffect = readonly [ type statusEffect = readonly [string, modifiers, entityTransformTemplate, number];
name: string, type entityTag = typeof entityTags[number]
type: string,
strength: number,
modifiers?: modifiers,
effect?: entityTransformTemplate,
]; // Should also be able to cause a visual effect somehow - maybe rather than having an entityTransformTemplate, it should broadcast a message back to the top
// ...that can affect other entities or communicate with the client
type entityTag = typeof entityTags[number];
export interface entityId { export interface entityId {
readonly name: string; readonly name: string;
readonly team: "players" | "enemies"; readonly team: "players" | "enemies";
@ -22,21 +15,20 @@ export interface entityId {
}; };
} }
export interface entityProperties { export interface entityProperties {
readonly id: entityId; readonly id: entityId,
readonly maxStatuses: statuses; readonly baseModifiers: modifiers,
readonly baseModifiers: modifiers; readonly maxStatuses: statuses,
readonly baseStatusEffects: statusEffect[]; // Permanent immunities/status effects readonly baseStatusEffects: statusEffect[],// Permanent immunities/status effects
} }
export interface entity { export interface entityStatuses {
statuses: statuses; readonly statuses: statuses; // Health and stuff that change constantly
statusEffects: statusEffect[]; readonly statusEffects: statusEffect[]; // Burn, poison, etc.
readonly properties: entityProperties;
} }
type entityTransformType = "support" | "attack"; type entityTransformType = "support" | "attack";
type healthTransformValue = [magnitude: number, affectsHealth: boolean, affectsBarrier: boolean]; type healthTransformValue = [magnitude: number, affectsHealth: boolean, affectsBarrier: boolean]
interface entityTransformTemplate { interface entityTransformTemplate {
healthTransformValue: healthTransformValue; healthTransformValue: healthTransformValue,
statusEffectsGranted?: placeholder[]; // TBA (Stuff like burn, slow, stun, etc.) statusEffectsGranted?: placeholder[]; // TBA (Stuff like burn, slow, stun, etc.)
} }
type entityTransformDeterminer = ( type entityTransformDeterminer = (
@ -51,13 +43,12 @@ interface ability {
// Immunities are just status effects that take the place of a damaging status effect (so ones of that type can't be added) but don't do anything // Immunities are just status effects that take the place of a damaging status effect (so ones of that type can't be added) but don't do anything
// Status effects have a strength value that determines whether they can override another status effect of the same type (holy inferno can replace burn but burn can't replace holy inferno) // Status effects have a strength value that determines whether they can override another status effect of the same type (holy inferno can replace burn but burn can't replace holy inferno)
export interface entityController { export interface entityController {//Deprecated
//Deprecated
setPosition: (location: Vector3) => void; setPosition: (location: Vector3) => void;
useAbility: (abilityName: string, activating: boolean) => void; useAbility: (abilityName: string, activating: boolean) => void;
} }
export interface entityModifier {} // Deprecated export interface entityModifier {}// Deprecated
/* Prototype /* Prototype
entityTransform(setOfAllEntities: entity[], actingEntity: entity, ability destructured to {hitBoxList, modificationList}) entityTransform(setOfAllEntities: entity[], actingEntity: entity, ability destructured to {hitBoxList, modificationList})
const entitiesEligible = getAffectedEntities(setOfAllEntities, actingEntity) const entitiesEligible = getAffectedEntities(setOfAllEntities, actingEntity)
@ -110,7 +101,7 @@ export interface entityModifier {} // Deprecated
return [onSameTeam] return [onSameTeam]
}*/ }*/
/*function applyAttackToEntityStats() {//else if (transformType === "heal") { /*function applyAttackToEntityStats() {//else if (transformType === "heal") {
// Apply receiver's status effects to incoming heal // Apply receiver's status effects to incoming heal
const newEntityStatuses = applyHealToStatuses( const newEntityStatuses = applyHealToStatuses(
entityStats.statuses, // DRY alert entityStats.statuses, // DRY alert
@ -133,15 +124,10 @@ function applyAttackToEntityStatuses(
): statuses { ): statuses {
// + Apply status effects of receiving entity to damage (e.g. armor break) // + Apply status effects of receiving entity to damage (e.g. armor break)
const modifiedDamage = applyModifiersToDamage(entityTransformTemplate.magnitude, entityModifiers); const modifiedDamage = applyModifiersToDamage(entityTransformTemplate.magnitude, entityModifiers);
const newStatuses = applyDamageToStatuses( const newStatuses = applyDamageToStatuses(entityStatuses, modifiedDamage, entityTransformTemplate.affectsHealth, entityTransformTemplate.affectsBarrier);
entityStatuses,
modifiedDamage,
entityTransformTemplate.affectsHealth,
entityTransformTemplate.affectsBarrier,
);
return entityStatuses; return entityStatuses;
} }
/*function applyHealToEntityStats() {//if (transformType === "attack") { /*function applyHealToEntityStats() {//if (transformType === "attack") {
// Apply receiver's status effects to incoming damage // Apply receiver's status effects to incoming damage
const incomingDamage = applyModifiersToDamage(magnitude, baseModifiers); const incomingDamage = applyModifiersToDamage(magnitude, baseModifiers);
const newEntityStatuses = applyDamageToStatuses( const newEntityStatuses = applyDamageToStatuses(
@ -164,13 +150,7 @@ function applyHealToEntityStatuses(
entityStatusEffects: statusEffect[], entityStatusEffects: statusEffect[],
): statuses { ): statuses {
// + Apply status effects of receiving entity to damage (e.g. heal up) // + Apply status effects of receiving entity to damage (e.g. heal up)
const newStatuses = applyHealToStatuses( const newStatuses = applyHealToStatuses(entityStatuses, entityTransformTemplate.magnitude, entityTransformTemplate.affectsHealth, entityTransformTemplate.affectsBarrier, entityMaxStatuses);
entityStatuses,
entityTransformTemplate.magnitude,
entityTransformTemplate.affectsHealth,
entityTransformTemplate.affectsBarrier,
entityMaxStatuses,
);
return entityStatuses; return entityStatuses;
} }
// ? Are you meant to pipe determineEntityTransform into the resulting function? // ? Are you meant to pipe determineEntityTransform into the resulting function?
@ -198,7 +178,7 @@ function applyEntityToHeal(
entityTransformTemplate: entityTransformTemplate, entityTransformTemplate: entityTransformTemplate,
): entityTransformTemplate { ): entityTransformTemplate {
// + Apply user's status effects to outgoing heal // + Apply user's status effects to outgoing heal
return entityTransformTemplate; // There could be a heal modifier later return entityTransformTemplate // There could be a heal modifier later
} }
/*function transformEntityStats( /*function transformEntityStats(
entityStats: entityStats, entityStats: entityStats,
@ -327,19 +307,17 @@ function getEntityByName(entityList: entity[], entityName: string): success<enti
return [false, "Entity not found"]; return [false, "Entity not found"];
} }
function getAbility() {} function getAbility() {}
function applyEntityTransformToEntityStatuses( function applyEntityTransformToEntityStatuses(entityTransform: entityTransformTemplate, statuses: statuses, maxStatuses: statuses) {
entityTransform: entityTransformTemplate,
statuses: statuses, }
maxStatuses: statuses,
) {}
function applyEntityTransformToEntityList( function applyEntityTransformToEntityList(
entityList: entity[], entityList: entity[],
entityTransformDeterminer: entityTransformDeterminer, entityTransformDeterminer: entityTransformDeterminer,
entityPerformingTransform: entity, entityPerformingTransform: entity,
aim: Vector3, aim: Vector3,
): [entity[], placeholder[]?] { ): [entity[], placeholder[]?] {
const entityPerformingTransformModifiers = entityPerformingTransform.baseModifiers; const entityPerformingTransformModifiers = entityPerformingTransform.baseModifiers
const entityPerformingTransformStatusEffects = entityPerformingTransform.stats.statusEffects; // Not good const entityPerformingTransformStatusEffects = entityPerformingTransform.stats.statusEffects // Not good
const newEntityList = entityList.map(function (entityReceivingTransform: entity): entity { const newEntityList = entityList.map(function (entityReceivingTransform: entity): entity {
const entityTransform = entityTransformDeterminer( const entityTransform = entityTransformDeterminer(
entityPerformingTransform.id, entityPerformingTransform.id,
@ -348,13 +326,13 @@ function applyEntityTransformToEntityList(
entityReceivingTransform.puppet, entityReceivingTransform.puppet,
); );
if (entityTransform) { if (entityTransform) {
const entityStatuses = entityPerformingTransform.stats.statuses; const entityStatuses = entityPerformingTransform.stats.statuses
const [entityTransformType, entityTransformTemplate] = entityTransform; const [entityTransformType, entityTransformTemplate] = entityTransform
if (entityTransformType === "attack") { if (entityTransformType === "attack") {
const outgoingTransformTemplate = applyEntityToAttack( const outgoingTransformTemplate = applyEntityToAttack(
entityPerformingTransformModifiers, entityPerformingTransformModifiers,
entityPerformingTransformStatusEffects, entityPerformingTransformStatusEffects,
entityTransformTemplate, entityTransformTemplate
); );
const newEntityStatuses = outgoingTransformTemplate const newEntityStatuses = outgoingTransformTemplate
? applyAttackToEntityStatuses( ? applyAttackToEntityStatuses(
@ -365,12 +343,11 @@ function applyEntityTransformToEntityList(
) )
: entityStatuses; // Also cringe : entityStatuses; // Also cringe
} else { } else {
assert(entityTransformType === "heal"); assert(entityTransformType === "heal")
const outgoingTransformTemplate = applyEntityToHeal( const outgoingTransformTemplate = applyEntityToHeal( // Lots of DRY violations here
// Lots of DRY violations here
entityPerformingTransformModifiers, entityPerformingTransformModifiers,
entityPerformingTransformStatusEffects, entityPerformingTransformStatusEffects,
entityTransformTemplate, entityTransformTemplate
); );
const newEntityStatuses = outgoingTransformTemplate const newEntityStatuses = outgoingTransformTemplate
? applyHealToEntityStatuses( ? applyHealToEntityStatuses(

View file

@ -1,85 +1,29 @@
// "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.
// This is also where persisted data is stored while the player is playing. Communication is required between here and the scene, rather than direct access, to protect the player's data. // 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 { makeEntity, entityController } from "./EntityManager";
//import { actorClass } from "shared/Shared" //import { actorClass } from "shared/Shared"
import { stageRequest } from "./SceneManager"; import { sceneManagerRequest } from "./SceneManager"
export type playerActivity = export type playerManagerRequest = ["initPlayer" | "deinitPlayer", Player] | ["playerInput", ...unknown[]] | ["foo", "bar"]
| ["player_joined" | "player_left", Player]
| ["player_tried_action", Player, unknown[]]
| ["placeholder", "foo"];
export type messageToPlayer = ["placeholder", "foo"];
interface saveDataEntry { interface saveDataEntry {
// + May need to move this to a dedicated archiver module/actor for data safety // + May need to move this to archiver
placeholder: string; placeholder: string;
} }
interface storedPlayer { interface storedPlayer {
saveData: saveDataEntry; // currentScene: event //Not sure about this
state: [stateName: "inGame", currentSceneName: string] | [stateName: "inMenu"]; /*initPlayer: (player: Player) => void;
deinitPlayer: (player: Player) => void;
fetchPlayer: (player: Player) => storedPlayer;*/
} }
export type playerStorage = (storedPlayer | undefined)[]; export type playerStorage = storedPlayer[];
function getSaveData(userId: number): saveDataEntry { export function applyRequestsToPlayerStorage(playerStorage: playerStorage, requests: playerManagerRequest[]): success<[playerStorage, sceneManagerRequest[]]> {
return { placeholder: "foo" }; return [true, [playerStorage, []]]; // This really sucks to look at right now
} }
function initStoredPlayer(userId: number): storedPlayer {
return {
saveData: getSaveData(userId),
state: ["inMenu"],
};
}
function handlePlayerActivity(playerStorage: playerStorage, activity: playerActivity): [playerStorage, stageRequest[]] {
if (activity[0] === "player_joined") {
const userId = activity[1].UserId;
const newPlayerStorage = [...playerStorage]; // Hopefully not slow
newPlayerStorage[userId] = initStoredPlayer(userId);
// + check if player has auto-load-in turned off
const sceneMessages: stageRequest[] = [];
sceneMessages.push(["scene0", ["load_in_player", userId]]);
return [newPlayerStorage, sceneMessages];
} else if (activity[0] === "player_left") {
const userId = activity[1].UserId;
const newPlayerStorage = [...playerStorage];
// + save data to datastore
newPlayerStorage[userId] = undefined;
const sceneMessages: stageRequest[] = [];
sceneMessages.push(["scene0", ["remove_player", userId]]);
return [newPlayerStorage, sceneMessages];
} else {
// (unimplemented)
return [playerStorage, []];
}
}
export function handleConsecutivePlayerActivities(
playerStorage: playerStorage,
activities: playerActivity[],
): [playerStorage, stageRequest[]] {
const activityToHandle = activities.shift(); // A bit destructive (shift isn't implemented yet), but it works and is honestly cleaner than the alternative
if (activityToHandle) {
const [newPlayerStorage, requests] = handlePlayerActivity(playerStorage, activityToHandle);
const [newerPlayerStorage, newRequests] = handleConsecutivePlayerActivities(playerStorage, activities);
return [newerPlayerStorage, [...requests, ...newRequests]];
} else {
return [playerStorage, []];
}
}
/*export function handleConsecutivePlayerActivities( // Keeping this here as a relic of my bizarre thought process
playerStorage: playerStorage,
activities: playerActivity[],
): [playerStorage, stageRequest[]] {
if (activities.size() > 0) {
const activityToHandle = activities.pop(); // A bit destructive (shift isn't implemented yet), but it works and is honestly cleaner than the alternative
assert(activityToHandle !== undefined);
const [newPlayerStorage, requests] = handleConsecutivePlayerActivities(playerStorage, activities);
const [newerPlayerStorage, newRequests] = handlePlayerActivity(newPlayerStorage, activityToHandle);
return [newerPlayerStorage, [...requests, ...newRequests]];
} else {
return [[...playerStorage], []];
}
}*/
export function sendToPlayer() {}
export function initPlayerStorage() { export function initPlayerStorage() {
return []; return [{},{}];
} }
/* Deprecated /* Deprecated
export interface storedPlayer { export interface storedPlayer {
@ -121,6 +65,7 @@ class storedPlayerHandler implements storedPlayer {
//entity?: entityController; //entity?: entityController;
}*/ }*/
/*class playerStorageHandler implements playerStorage { /*class playerStorageHandler implements playerStorage {
constructor() {} constructor() {}
initPlayer(player: Player) { initPlayer(player: Player) {

View file

@ -1,70 +1,31 @@
// "The": Handle events. // "The": Handle events.
import { makeApplyConsecutiveRequestsToObjectFunction } from "./Shared"; import { entityId, entityProperties, entityStatuses/*entityManagerRequest /*makeEntity, entityController*/ } from "./EntityManager";
import { entity } from "./EntityManager"; import { applyRequestsToPlayerStorage, playerManagerRequest } from "./PlayerManager"
import { messageToPlayer } from "./PlayerManager"; export type sceneManagerRequest = [Player, "useAbility", Vector3] | [Player, "foo", "bar"]
export type sceneRequest = type endConditionFunction = (containedScenes: scene[], containedEntities: entity[], timeElapsed: number) => boolean
| [request: "load_in_player", playerUserId: number] // More stats would go here
| [request: "remove_player", playerUserId: number]
| [request: "use_ability", playerUserId: number, mousePosition: Vector3]
| [request: "placeholder", foo: "bar"];
export type stageRequest = [scene: string, sceneRequest: sceneRequest];
type sceneTransformation = ["attack", entity, entity] | ["heal"];
type endConditionFunction = (containedEntities: entity[], timeElapsed: number) => [false] | [true, messageToPlayer[]]; // This also needs some way to contact other scenes
export interface sceneTemplate { export interface sceneTemplate {
readonly sceneComplete: endConditionFunction; // Checks conditions that need to pass for the scene to end (e.g. entityX.Alive == false || timeSpent > 1000) and also tells what to do about it depending on the result (victory or loss) readonly sceneComplete: endConditionFunction // Checks conditions that need to pass for the scene to end (e.g. entityX.Alive == false || timeSpent > 1000)
readonly entityTemplates?: [name: string, entityTemplate: placeholder][]; readonly onCompletion: readonly playerManagerRequest[] // Requests to get sent out when the scene ends
// Should also be a function that can react to players entering, maybe the endConditionFunction could do that? Seems a bit messy though
} }
export interface scene { export interface scene extends sceneTemplate {
entities: { /*containedScenes?: {
[entityName: string]: entity | undefined;
};
entityList: string[];
readonly sceneComplete: endConditionFunction;
}
export function initScene(sceneTemplate: sceneTemplate): scene {
// Make the stuff described in the scene...
const newScene: scene = {
entities: {},
entityList: [],
sceneComplete: sceneTemplate.sceneComplete,
};
return newScene;
}
export function procesSceneInternalEvents(scene: scene, now: number): [sceneTransformation[], messageToPlayer[]] {}
export function processSceneExternalInput(scene: scene, now: number, input: sceneRequest): [scene, messageToPlayer[]] {}
/*const processStageExternalInputs = makeApplyConsecutiveRequestsToObjectFunction<
scene,
sceneManagerRequest,
messageToPlayer
>(processSceneExternalInput);*/
export function runScene(scene: scene, now: number): [scene, messageToPlayer[]] {
return [scene, []];
}
/*export interface sceneBackstage {
readonly entityProperties: {
[glumph: string]: entityProperties;
}
}*/
// export interface scene extends sceneTemplate {
/*containedScenes?: {
[sceneName: string]: scene | undefined [sceneName: string]: scene | undefined
}; // Scenes within this scene that are isolated from each other and can be run in parallel }; // 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 // Not necessarily "A list of events that need to return true (in sequence) to complete this event", but such events would go there
*/ //Scene nesting is currently cancelled *///Scene nesting is currently cancelled
// containedEntities: string[]; // "A list of entities that need to die to complete the event" (not really but kinda) containedEntities: string[]; // "A list of entities that need to die to complete the event" (not really but kinda)
// containedEntityProperties: { containedEntityProperties: {
// [entityName: string]: entityProperties [entityName: string]: entityProperties
// } }
// containedEntityStatuses containedEntityStatuses
// players: { players: {
// [userId: number]: [inThisScene: true] | [inThisScene: false, subScene: string] | undefined [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 //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<string | false> { /*function getPlayerSceneName(scene: scene, userId: number): success<string | false> {
let playerSceneLocation = scene.players[userId]; let playerSceneLocation = scene.players[userId];
if (!playerSceneLocation) { if (!playerSceneLocation) {
@ -75,7 +36,8 @@ export function runScene(scene: scene, now: number): [scene, messageToPlayer[]]
return [true, playerSceneLocation[1]] return [true, playerSceneLocation[1]]
} }
}*/ }*/
/*const playerSceneResult = getPlayerSceneName(scene, request[0].UserId) function applyRequestToScene(scene: scene, now: number, request: sceneManagerRequest): [scene, playerManagerRequest[]] {
/*const playerSceneResult = getPlayerSceneName(scene, request[0].UserId)
if (!playerSceneResult[0]) { if (!playerSceneResult[0]) {
return [scene, []]; // Some kind of error needs to go here return [scene, []]; // Some kind of error needs to go here
} }
@ -97,23 +59,29 @@ export function runScene(scene: scene, now: number): [scene, messageToPlayer[]]
scene.containedScenes = containedScenes scene.containedScenes = containedScenes
return [scene, sceneRequestResult[1]] return [scene, sceneRequestResult[1]]
}*/ }*/
//} }
export function applyRequestsToScene(scene: scene, now: number, requests: sceneManagerRequest[]): success<[scene, playerManagerRequest[]]> {
/*export function applyRequestsToScene(
scene: scene,
now: number,
requests: sceneManagerRequest[],
): success<[scene, messageToPlayer[]]> {
try { try {
let newScene: scene = scene; let newScene: scene = scene
let outgoingRequests: messageToPlayer[] = []; let outgoingRequests: playerManagerRequest[] = []
requests.forEach(function (request: sceneManagerRequest) { requests.forEach(function(request: sceneManagerRequest) {
const sceneRequestResult = applyRequestToScene(newScene, now, request); const sceneRequestResult = applyRequestToScene(newScene, now, request);
newScene = sceneRequestResult[0]; newScene = sceneRequestResult[0]
outgoingRequests = [...outgoingRequests, ...sceneRequestResult[1]]; outgoingRequests = [...outgoingRequests, ...sceneRequestResult[1]]
}); })
return [true, [newScene, outgoingRequests]]; return [true, [newScene, outgoingRequests]];
} catch (error) {
return [false, error];
} }
}*/ 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;
};

View file

@ -7,33 +7,6 @@ export function isUnknownTable(thing: unknown): thing is unknownTable {
return typeIs(thing, "table"); return typeIs(thing, "table");
} }
export function makeApplyConsecutiveRequestsToObjectFunction<mainObjectType, inputRequestType, outputRequestType>(
applyRequestToObject: (
mainObject: mainObjectType,
now: number,
inputRequest: inputRequestType,
) => [mainObjectType, outputRequestType[]],
) {
function applyConsecutiveRequestsToObject(
mainObject: mainObjectType,
now: number,
inputRequests: inputRequestType[],
): [mainObjectType, outputRequestType[]] {
const inputRequestToHandle = inputRequests.shift(); // A bit destructive (shift isn't implemented yet), but it works and is honestly cleaner than the alternative
if (inputRequestToHandle !== undefined) {
const [newMainObject, outputRequests] = applyRequestToObject(mainObject, now, inputRequestToHandle);
const [newerMainObject, newOutputRequests] = applyConsecutiveRequestsToObject(
newMainObject,
now,
inputRequests,
);
return [newerMainObject, [...outputRequests, ...newOutputRequests]];
} else {
return [mainObject, []];
}
}
return applyConsecutiveRequestsToObject;
}
/*export class actorClass<MessageType> implements actor<MessageType> { /*export class actorClass<MessageType> implements actor<MessageType> {
message(message: MessageType) { message(message: MessageType) {
this.mailbox.push(message) this.mailbox.push(message)