Compare commits
2 commits
991fce6103
...
052a37b36f
Author | SHA1 | Date | |
---|---|---|---|
052a37b36f | |||
436cbde19d |
11 changed files with 463 additions and 108 deletions
|
@ -16,7 +16,7 @@ type validInput = Enum.KeyCode; // + Include controller "keys"
|
|||
function isValidInput(value: unknown): value is validInput {
|
||||
return enumTypeIs<Enum.KeyCode>(value, Enum.KeyCode);
|
||||
}
|
||||
const actionAssignmentsReference: string[] = [
|
||||
const actionList = [
|
||||
"clicker1", // What is used to click on things (enemies in game, UI elements)
|
||||
"diamond1", // Diamond controls
|
||||
"diamond2",
|
||||
|
@ -24,21 +24,11 @@ const actionAssignmentsReference: string[] = [
|
|||
"diamond4",
|
||||
"special1", // Special controls
|
||||
"special2",
|
||||
];
|
||||
export interface actionAssignments {
|
||||
// Based on the reference array
|
||||
clicker1?: validInput; // What is used to click on things (enemies in game, UI elements)
|
||||
diamond1?: validInput; // Diamond controls
|
||||
diamond2?: validInput;
|
||||
diamond3?: validInput;
|
||||
diamond4?: validInput;
|
||||
special1?: validInput; // Special controls
|
||||
special2?: validInput;
|
||||
}
|
||||
type action = keyof actionAssignments;
|
||||
function isValidAction(value: string): value is keyof actionAssignments {
|
||||
return value in actionAssignmentsReference; // uh oh
|
||||
}
|
||||
] as const;
|
||||
type action = typeof actionList[number]
|
||||
export type actionAssignments = {
|
||||
[actionName in action]?: validInput;
|
||||
};
|
||||
type actionBinding = [action, (actionName: string, state: Enum.UserInputState, inputObject: InputObject) => void];
|
||||
|
||||
function getMouseLocation(filterDescendantsInstances: Instance[]): [Vector3, Vector3, Instance | undefined] {
|
||||
|
@ -82,9 +72,9 @@ class actionHandler implements actionBinder {
|
|||
}
|
||||
assignInputsToActions(actionAssignments: unknownTable) {
|
||||
const newActionAssignments: actionAssignments = {};
|
||||
actionAssignmentsReference.forEach((action) => {
|
||||
actionList.forEach((action) => {
|
||||
const input: unknown = actionAssignments[action];
|
||||
if (isValidAction(action) && isValidInput(input)) {
|
||||
if (isValidInput(input)) {
|
||||
newActionAssignments[action] = input;
|
||||
}
|
||||
});
|
||||
|
|
7
src/game/globalScene.ts
Normal file
7
src/game/globalScene.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { sceneTemplate } from "shared/SceneManager"
|
||||
export const globalSceneTemplate: sceneTemplate = {
|
||||
sceneComplete() {
|
||||
return false
|
||||
},
|
||||
onCompletion: []
|
||||
} as const
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<playerManagerRequest> = 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);
|
||||
*/
|
||||
// }
|
||||
|
|
2
src/services.d.ts
vendored
2
src/services.d.ts
vendored
|
@ -10,6 +10,8 @@ type effectEntry = [meshType, EnumItem, effectState[]]; // The enumitem is mater
|
|||
|
||||
*/
|
||||
type unknownTable = { [numberKey: number]: unknown; [stringKey: string]: unknown };
|
||||
type success<Wrapped> = [true, Wrapped] | [false, string]
|
||||
type placeholder = "foo"
|
||||
/*interface hookInEntry {
|
||||
name: string;
|
||||
guiObject: GuiObject;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { entityModifier } from "./EntityManager"
|
||||
export interface ability {
|
||||
use: () => void; // ? Does it pass back information to the entity manager that it does something with? Is an ability really just a transform of entities?
|
||||
use: (entities?: entityModifier[]) => void; // ? Does it pass back information to the entity manager that it does something with? Is an ability really just a transform of entities?
|
||||
// ... this transform would need the positions and stats of the entities involved as well as potential obstacles
|
||||
}
|
||||
|
|
|
@ -1,16 +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 entity {
|
||||
export interface entityController {
|
||||
setPosition: (location: Vector3) => void;
|
||||
ability: (ability: string, state: boolean) => void;
|
||||
useAbility: (abilityName: string, activating: boolean) => void;
|
||||
}
|
||||
|
||||
class entityHandler implements entity {
|
||||
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
|
||||
|
||||
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<entity> {
|
||||
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;
|
||||
|
@ -22,48 +235,41 @@ class entityHandler implements entity {
|
|||
canUseAbility(abilityName: string): boolean {
|
||||
// ! Not concurrency safe
|
||||
// + buncha status checks
|
||||
for (let i = 1; i <= 2; i++) {
|
||||
const cooldown = this.cooldowns[abilityName];
|
||||
if (cooldown !== undefined) {
|
||||
const now = os.clock();
|
||||
if (now - cooldown[0] >= cooldown[1]) {
|
||||
this.cooldowns[abilityName] = undefined;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
const cooldown = this.cooldowns[abilityName];
|
||||
if (cooldown) {
|
||||
const now = os.clock();
|
||||
if (now - cooldown[0] < cooldown[1]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
ability(abilityName: string, activated: boolean) {
|
||||
useAbility(abilityName: string, activating: boolean) {
|
||||
const abilities = this.abilities;
|
||||
if (abilities) {
|
||||
if (activated) {
|
||||
if (this.canUseAbility(abilityName)) {
|
||||
const ability = abilities[abilityName];
|
||||
if (ability !== undefined) {
|
||||
ability.use();
|
||||
}
|
||||
if (activating) {
|
||||
if (this.canUseAbility(abilityName)) {
|
||||
const ability = abilities[abilityName];
|
||||
if (ability != undefined) {
|
||||
const abilityResult = ability.use();
|
||||
}
|
||||
} else {
|
||||
// + Ability cancellation - perhaps the useAbility inside the entity returns a function to store that the ability watches that ceases the ability when executed
|
||||
}
|
||||
} else {
|
||||
// + Ability cancellation - perhaps the useAbility inside the entity returns a function to store that the ability watches that ceases the ability when executed
|
||||
}
|
||||
// Blah blah blah
|
||||
}
|
||||
|
||||
baseStats: stats;
|
||||
baseAmounts: amounts;
|
||||
puppet: puppet;
|
||||
abilities?: {
|
||||
[key: string]: ability;
|
||||
};
|
||||
|
||||
abilities: {
|
||||
[key: string]: ability | undefined;
|
||||
} = {};
|
||||
cooldowns: {
|
||||
[key: string]: [start: number, length: number] | undefined;
|
||||
} = {};
|
||||
}
|
||||
|
||||
baseStats: stats;
|
||||
baseAmounts: amounts;
|
||||
}*/
|
||||
/*
|
||||
export function makeEntity(
|
||||
puppetEntry: puppetEntry,
|
||||
baseStats = [100, 1, 16, 0] as stats,
|
||||
|
@ -71,3 +277,14 @@ export function makeEntity(
|
|||
) {
|
||||
return new entityHandler(baseStats, baseAmounts, puppetEntry);
|
||||
}
|
||||
|
||||
class entityManager extends actorClass<entityManagerRequest> {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
entities: entity[] = [];
|
||||
}
|
||||
export function initEntityManager() {
|
||||
return new entityManager();
|
||||
}
|
||||
*/
|
|
@ -1,18 +0,0 @@
|
|||
// "The": Handle events.
|
||||
// WORST CONDITION RigHT NOW
|
||||
import { makeEntity, entity } from "./EntityManager";
|
||||
interface event {
|
||||
winEvents?: event[]; // A list of events that need to return true (in sequence) to complete this event
|
||||
winEntities?: entity[]; // A list of entities that need to die to complete the event
|
||||
timeout?: number; // A timeout for the event; passes a lose condition if there are other completion requirements that have not been satisfied
|
||||
}
|
||||
export function runEvent(event: event) {
|
||||
let complete = false;
|
||||
const startTime = os.clock();
|
||||
const endTime = 2;
|
||||
while (!complete) {
|
||||
if (event.timeout === 2) {
|
||||
complete = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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, entity } 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;
|
||||
|
@ -29,7 +51,7 @@ class storedPlayerHandler implements storedPlayer {
|
|||
}
|
||||
ability(ability: string, state: boolean) {
|
||||
if (this.entity) {
|
||||
this.entity.ability(ability, state);
|
||||
this.entity.useAbility(ability, state);
|
||||
}
|
||||
}
|
||||
loadIn() {
|
||||
|
@ -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?: entity;
|
||||
}
|
||||
//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();
|
||||
}
|
||||
}*/
|
||||
|
|
82
src/shared/SceneManager.ts
Normal file
82
src/shared/SceneManager.ts
Normal file
|
@ -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<string | false> {
|
||||
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;
|
||||
};
|
|
@ -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<MessageType> implements actor<MessageType> {
|
||||
message(message: MessageType) {
|
||||
this.mailbox.push(message)
|
||||
if (!this.busy) {
|
||||
this.busy = true
|
||||
}
|
||||
}
|
||||
readMessage() {
|
||||
return this.mailbox.shift()
|
||||
}
|
||||
mailbox: MessageType[] = [];
|
||||
busy = false;
|
||||
}*/
|
Loading…
Reference in a new issue