Compare commits
2 commits
8a9d5414f8
...
ee32742ae8
Author | SHA1 | Date | |
---|---|---|---|
ee32742ae8 | |||
88129a87ba |
8 changed files with 351 additions and 153 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
--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()
|
|
@ -1,7 +0,0 @@
|
||||||
import { sceneTemplate } from "shared/SceneManager"
|
|
||||||
export const globalSceneTemplate: sceneTemplate = {
|
|
||||||
sceneComplete() {
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
onCompletion: []
|
|
||||||
} as const
|
|
6
src/game/scene0.ts
Normal file
6
src/game/scene0.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { sceneTemplate } from "shared/SceneManager";
|
||||||
|
export const scene0Template: sceneTemplate = {
|
||||||
|
sceneComplete() {
|
||||||
|
return [false];
|
||||||
|
},
|
||||||
|
} as const;
|
|
@ -1,58 +1,119 @@
|
||||||
// "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 } from "shared/Shared";
|
import { isUnknownTable, makeApplyConsecutiveRequestsToObjectFunction } 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, applyRequestsToScene } from "shared/SceneManager"
|
import { scene, initScene, runScene, sceneRequest, stageRequest, processSceneExternalInput } from "shared/SceneManager";
|
||||||
import { playerStorage, initPlayerStorage, applyRequestsToPlayerStorage, playerManagerRequest} from "shared/PlayerManager";
|
import {
|
||||||
import { globalSceneTemplate } from "game/globalScene"
|
playerStorage,
|
||||||
|
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 globalScene: scene = initScene(globalSceneTemplate)
|
let stage: stage = {};
|
||||||
// Handle the real world
|
stage["scene0"] = initScene(scene0Template);
|
||||||
let playerEvents: playerManagerRequest[] = [];
|
// Handle the real world (TERA I/O)
|
||||||
function messagePlayerManager(message: playerManagerRequest): void {
|
let playerManagerMailbox: playerActivity[] = [];
|
||||||
playerEvents.push(message);
|
function messagePlayerManager(message: playerActivity): void {
|
||||||
|
playerManagerMailbox.push(message);
|
||||||
}
|
}
|
||||||
Players.PlayerAdded.Connect(function(player: Player) {
|
Players.PlayerAdded.Connect(function (player: Player) {
|
||||||
messagePlayerManager(["initPlayer", player])
|
messagePlayerManager(["player_joined", player]);
|
||||||
});
|
});
|
||||||
Players.PlayerRemoving.Connect(function(player: Player) {
|
Players.PlayerRemoving.Connect(function (player: Player) {
|
||||||
messagePlayerManager(["deinitPlayer", player])
|
messagePlayerManager(["player_left", player]);
|
||||||
});
|
});
|
||||||
bindToClientMessage(function(player: Player, ...messageContents: unknown[]) {
|
let playerAttemptedActionCounter: number[] = [];
|
||||||
messagePlayerManager(["playerInput", player, messageContents])
|
bindToClientMessage(function (player: Player, ...messageContents: unknown[]) {
|
||||||
|
const userId = player.UserId;
|
||||||
|
playerAttemptedActionCounter[userId] += 1;
|
||||||
|
if (playerAttemptedActionCounter[userId] <= 2) {
|
||||||
|
messagePlayerManager(["player_tried_action", player, messageContents]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Run everything sequentially to avoid concurrency issues
|
function applySceneRequestToStage(stage: stage, now: number, request: stageRequest): [stage, messageToPlayer[]] {
|
||||||
let busy = false
|
const sceneName = request[0];
|
||||||
RunService.Heartbeat.Connect(function(delta: number) {
|
const thisScene = stage[sceneName];
|
||||||
assert(!busy)
|
if (!thisScene) {
|
||||||
busy = true
|
warn("Scene " + request[0] + " does not exist");
|
||||||
|
return [stage, []];
|
||||||
|
}
|
||||||
|
const [newScene, messagesToPlayer] = processSceneExternalInput(thisScene, now, request[1]);
|
||||||
|
// No way to make a clean copy of a dictionary, so...
|
||||||
|
const newStage = stage;
|
||||||
|
newStage[sceneName] = newScene;
|
||||||
|
return [newStage, messagesToPlayer];
|
||||||
|
}
|
||||||
|
const applySceneRequestsToStage = makeApplyConsecutiveRequestsToObjectFunction<stage, stageRequest, messageToPlayer>(
|
||||||
|
applySceneRequestToStage,
|
||||||
|
);
|
||||||
|
// Run everything sequentially each frame to avoid concurrency issues
|
||||||
|
let busy = false;
|
||||||
|
let frameCounter = 0;
|
||||||
|
RunService.Heartbeat.Connect(function (delta: number) {
|
||||||
|
if (!busy) {
|
||||||
|
busy = true;
|
||||||
|
if (frameCounter < 10) {
|
||||||
|
frameCounter += 1;
|
||||||
|
} else {
|
||||||
|
frameCounter = 0;
|
||||||
|
playerAttemptedActionCounter = [];
|
||||||
|
}
|
||||||
|
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 now = os.clock()
|
const sceneResult = runScene(globalScene, now);
|
||||||
const sceneResult = runScene(globalScene, now)
|
|
||||||
assert(sceneResult[0]);
|
assert(sceneResult[0]);
|
||||||
globalScene = sceneResult[1][0]
|
stage = sceneResult[1][0];
|
||||||
|
|
||||||
let thesePlayerEvents = playerEvents
|
//thesePlayerEvents.unshift(...sceneResult[1][1]);
|
||||||
playerEvents = []
|
|
||||||
thesePlayerEvents.unshift(...sceneResult[1][1])
|
|
||||||
|
|
||||||
let repetitions = 0
|
let repetitions = 0;
|
||||||
while (thesePlayerEvents[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
|
||||||
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]);
|
||||||
const sceneRequestsResult = applyRequestsToScene(globalScene,now,playerRequestsResult[1][1]);
|
assert(sceneRequestsResult[0], sceneRequestsResult[1] as string);
|
||||||
assert(sceneRequestsResult[0], sceneRequestsResult[1] as string)
|
|
||||||
// Mutable section
|
// Mutable section
|
||||||
globalPlayerStorage = playerRequestsResult[1][0];
|
globalPlayerStorage = playerRequestsResult[1][0];
|
||||||
globalScene = sceneRequestsResult[1][0];
|
globalScene = sceneRequestsResult[1][0];
|
||||||
thesePlayerEvents = sceneRequestsResult[1][1]
|
thesePlayerEvents = sceneRequestsResult[1][1];
|
||||||
repetitions += 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
|
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) {
|
||||||
|
@ -63,8 +124,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 {
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
// "EntityManager": Create entity objects and apply transformations to them.
|
// "EntityManager": Create entity objects and apply transformations to them.
|
||||||
// A bold attempt at functional programming in a videogame entity system
|
// A bold (or just foolish) 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 [string, modifiers, entityTransformTemplate, number];
|
type statusEffect = readonly [
|
||||||
type entityTag = typeof entityTags[number]
|
name: string,
|
||||||
|
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";
|
||||||
|
@ -15,20 +22,21 @@ export interface entityId {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export interface entityProperties {
|
export interface entityProperties {
|
||||||
readonly id: entityId,
|
readonly id: entityId;
|
||||||
readonly baseModifiers: modifiers,
|
readonly maxStatuses: statuses;
|
||||||
readonly maxStatuses: statuses,
|
readonly baseModifiers: modifiers;
|
||||||
readonly baseStatusEffects: statusEffect[],// Permanent immunities/status effects
|
readonly baseStatusEffects: statusEffect[]; // Permanent immunities/status effects
|
||||||
}
|
}
|
||||||
export interface entityStatuses {
|
export interface entity {
|
||||||
readonly statuses: statuses; // Health and stuff that change constantly
|
statuses: statuses;
|
||||||
readonly statusEffects: statusEffect[]; // Burn, poison, etc.
|
statusEffects: statusEffect[];
|
||||||
|
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 = (
|
||||||
|
@ -43,12 +51,13 @@ 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 {//Deprecated
|
export interface entityController {
|
||||||
|
//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)
|
||||||
|
@ -101,7 +110,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
|
||||||
|
@ -124,10 +133,15 @@ 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(entityStatuses, modifiedDamage, entityTransformTemplate.affectsHealth, entityTransformTemplate.affectsBarrier);
|
const newStatuses = applyDamageToStatuses(
|
||||||
|
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(
|
||||||
|
@ -150,7 +164,13 @@ 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(entityStatuses, entityTransformTemplate.magnitude, entityTransformTemplate.affectsHealth, entityTransformTemplate.affectsBarrier, entityMaxStatuses);
|
const newStatuses = applyHealToStatuses(
|
||||||
|
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?
|
||||||
|
@ -178,7 +198,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,
|
||||||
|
@ -307,17 +327,19 @@ function getEntityByName(entityList: entity[], entityName: string): success<enti
|
||||||
return [false, "Entity not found"];
|
return [false, "Entity not found"];
|
||||||
}
|
}
|
||||||
function getAbility() {}
|
function getAbility() {}
|
||||||
function applyEntityTransformToEntityStatuses(entityTransform: entityTransformTemplate, statuses: statuses, maxStatuses: statuses) {
|
function applyEntityTransformToEntityStatuses(
|
||||||
|
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,
|
||||||
|
@ -326,13 +348,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(
|
||||||
|
@ -343,11 +365,12 @@ function applyEntityTransformToEntityList(
|
||||||
)
|
)
|
||||||
: entityStatuses; // Also cringe
|
: entityStatuses; // Also cringe
|
||||||
} else {
|
} else {
|
||||||
assert(entityTransformType === "heal")
|
assert(entityTransformType === "heal");
|
||||||
const outgoingTransformTemplate = applyEntityToHeal( // Lots of DRY violations here
|
const outgoingTransformTemplate = applyEntityToHeal(
|
||||||
|
// Lots of DRY violations here
|
||||||
entityPerformingTransformModifiers,
|
entityPerformingTransformModifiers,
|
||||||
entityPerformingTransformStatusEffects,
|
entityPerformingTransformStatusEffects,
|
||||||
entityTransformTemplate
|
entityTransformTemplate,
|
||||||
);
|
);
|
||||||
const newEntityStatuses = outgoingTransformTemplate
|
const newEntityStatuses = outgoingTransformTemplate
|
||||||
? applyHealToEntityStatuses(
|
? applyHealToEntityStatuses(
|
||||||
|
|
|
@ -1,29 +1,85 @@
|
||||||
// "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.
|
||||||
// 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.
|
// 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 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 { sceneManagerRequest } from "./SceneManager"
|
import { stageRequest } from "./SceneManager";
|
||||||
export type playerManagerRequest = ["initPlayer" | "deinitPlayer", Player] | ["playerInput", ...unknown[]] | ["foo", "bar"]
|
export type playerActivity =
|
||||||
|
| ["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 archiver
|
// + May need to move this to a dedicated archiver module/actor for data safety
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
}
|
}
|
||||||
interface storedPlayer {
|
interface storedPlayer {
|
||||||
// currentScene: event //Not sure about this
|
saveData: saveDataEntry;
|
||||||
/*initPlayer: (player: Player) => void;
|
state: [stateName: "inGame", currentSceneName: string] | [stateName: "inMenu"];
|
||||||
deinitPlayer: (player: Player) => void;
|
|
||||||
fetchPlayer: (player: Player) => storedPlayer;*/
|
|
||||||
}
|
}
|
||||||
export type playerStorage = storedPlayer[];
|
export type playerStorage = (storedPlayer | undefined)[];
|
||||||
export function applyRequestsToPlayerStorage(playerStorage: playerStorage, requests: playerManagerRequest[]): success<[playerStorage, sceneManagerRequest[]]> {
|
function getSaveData(userId: number): saveDataEntry {
|
||||||
return [true, [playerStorage, []]]; // This really sucks to look at right now
|
return { placeholder: "foo" };
|
||||||
}
|
}
|
||||||
|
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 {
|
||||||
|
@ -65,7 +121,6 @@ class storedPlayerHandler implements storedPlayer {
|
||||||
//entity?: entityController;
|
//entity?: entityController;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
|
||||||
/*class playerStorageHandler implements playerStorage {
|
/*class playerStorageHandler implements playerStorage {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
initPlayer(player: Player) {
|
initPlayer(player: Player) {
|
||||||
|
|
|
@ -1,31 +1,70 @@
|
||||||
// "The": Handle events.
|
// "The": Handle events.
|
||||||
import { entityId, entityProperties, entityStatuses/*entityManagerRequest /*makeEntity, entityController*/ } from "./EntityManager";
|
import { makeApplyConsecutiveRequestsToObjectFunction } from "./Shared";
|
||||||
import { applyRequestsToPlayerStorage, playerManagerRequest } from "./PlayerManager"
|
import { entity } from "./EntityManager";
|
||||||
export type sceneManagerRequest = [Player, "useAbility", Vector3] | [Player, "foo", "bar"]
|
import { messageToPlayer } from "./PlayerManager";
|
||||||
type endConditionFunction = (containedScenes: scene[], containedEntities: entity[], timeElapsed: number) => boolean
|
export type sceneRequest =
|
||||||
|
| [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)
|
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 onCompletion: readonly playerManagerRequest[] // Requests to get sent out when the scene ends
|
readonly entityTemplates?: [name: string, entityTemplate: placeholder][];
|
||||||
|
// 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 extends sceneTemplate {
|
export interface scene {
|
||||||
/*containedScenes?: {
|
entities: {
|
||||||
|
[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) {
|
||||||
|
@ -36,8 +75,7 @@ export function runScene(scene: scene, now: number): success<[scene, playerManag
|
||||||
return [true, playerSceneLocation[1]]
|
return [true, playerSceneLocation[1]]
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
function applyRequestToScene(scene: scene, now: number, request: sceneManagerRequest): [scene, playerManagerRequest[]] {
|
/*const playerSceneResult = getPlayerSceneName(scene, request[0].UserId)
|
||||||
/*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
|
||||||
}
|
}
|
||||||
|
@ -59,29 +97,23 @@ function applyRequestToScene(scene: scene, now: number, request: sceneManagerReq
|
||||||
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: playerManagerRequest[] = []
|
let outgoingRequests: messageToPlayer[] = [];
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
|
@ -7,6 +7,33 @@ 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)
|
||||||
|
|
Loading…
Reference in a new issue