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 {
|
function isValidInput(value: unknown): value is validInput {
|
||||||
return enumTypeIs<Enum.KeyCode>(value, Enum.KeyCode);
|
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)
|
"clicker1", // What is used to click on things (enemies in game, UI elements)
|
||||||
"diamond1", // Diamond controls
|
"diamond1", // Diamond controls
|
||||||
"diamond2",
|
"diamond2",
|
||||||
|
@ -24,21 +24,11 @@ const actionAssignmentsReference: string[] = [
|
||||||
"diamond4",
|
"diamond4",
|
||||||
"special1", // Special controls
|
"special1", // Special controls
|
||||||
"special2",
|
"special2",
|
||||||
];
|
] as const;
|
||||||
export interface actionAssignments {
|
type action = typeof actionList[number]
|
||||||
// Based on the reference array
|
export type actionAssignments = {
|
||||||
clicker1?: validInput; // What is used to click on things (enemies in game, UI elements)
|
[actionName in action]?: validInput;
|
||||||
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
|
|
||||||
}
|
|
||||||
type actionBinding = [action, (actionName: string, state: Enum.UserInputState, inputObject: InputObject) => void];
|
type actionBinding = [action, (actionName: string, state: Enum.UserInputState, inputObject: InputObject) => void];
|
||||||
|
|
||||||
function getMouseLocation(filterDescendantsInstances: Instance[]): [Vector3, Vector3, Instance | undefined] {
|
function getMouseLocation(filterDescendantsInstances: Instance[]): [Vector3, Vector3, Instance | undefined] {
|
||||||
|
@ -82,9 +72,9 @@ class actionHandler implements actionBinder {
|
||||||
}
|
}
|
||||||
assignInputsToActions(actionAssignments: unknownTable) {
|
assignInputsToActions(actionAssignments: unknownTable) {
|
||||||
const newActionAssignments: actionAssignments = {};
|
const newActionAssignments: actionAssignments = {};
|
||||||
actionAssignmentsReference.forEach((action) => {
|
actionList.forEach((action) => {
|
||||||
const input: unknown = actionAssignments[action];
|
const input: unknown = actionAssignments[action];
|
||||||
if (isValidAction(action) && isValidInput(input)) {
|
if (isValidInput(input)) {
|
||||||
newActionAssignments[action] = 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";
|
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');
|
assert(Input?.IsA("RemoteEvent"), 'Remote event "Input" is of incorrect class or nil');
|
||||||
Input.OnServerEvent.Connect(functionToBind);
|
Input.OnServerEvent.Connect(functionToBind);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,70 @@
|
||||||
// "main": This is the core of reality. It serves as the highest-level abstraction.
|
// "main": Initializes all state and handles the real world.
|
||||||
// + Prevent this from coupling with the entity manager, if possible
|
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");
|
const RunService = game.GetService("RunService");
|
||||||
import { isUnknownTable } from "shared/Shared";
|
import { isUnknownTable } from "shared/Shared";
|
||||||
import { makePlayerStorage, playerStorage, storedPlayer } from "shared/PlayerManager";
|
|
||||||
import { bindToClientMessage, messageClient, messageAllClients } from "./ServerMessenger";
|
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);
|
mainPlayerStorage.initPlayer(player);
|
||||||
messageClient(player, "init", "idk");
|
messageClient(player, "init", "idk");
|
||||||
}
|
}
|
||||||
function removePlayer(player: Player) {
|
function removePlayer(player: Player) {
|
||||||
mainPlayerStorage.deinitPlayer(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);
|
const storedPlayer = mainPlayerStorage.fetchPlayer(player);
|
||||||
if (messageType === "EnterGame") {
|
if (messageType === "EnterGame") {
|
||||||
try {
|
try {
|
||||||
|
@ -41,8 +91,5 @@ function handleClientMessage(player: Player, messageType: unknown, messageConten
|
||||||
storedPlayer.setPosition(messageContent);
|
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 unknownTable = { [numberKey: number]: unknown; [stringKey: string]: unknown };
|
||||||
|
type success<Wrapped> = [true, Wrapped] | [false, string]
|
||||||
|
type placeholder = "foo"
|
||||||
/*interface hookInEntry {
|
/*interface hookInEntry {
|
||||||
name: string;
|
name: string;
|
||||||
guiObject: GuiObject;
|
guiObject: GuiObject;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
import { entityModifier } from "./EntityManager"
|
||||||
export interface ability {
|
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
|
// ... 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.
|
// + Functions are here, as to avoid storing unecessary data in the server store.
|
||||||
import { makePuppet, puppet, puppetEntry } from "./Puppetmaster";
|
import { makePuppet, puppet, puppetEntry } from "./Puppetmaster";
|
||||||
import { ability } from "./AbilityManager";
|
// import { ability } from "./AbilityManager";
|
||||||
type stats = [maxHealth: number, attack: number, speed: number, defense: number]; // values used for calculation, only modified by buffs and debuffs
|
// I spent a lot of time thinking about the names for these types and they still suck
|
||||||
type amounts = [health: number, barrier: number]; // values used to store an entity's current status (more existential than stats)
|
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)
|
||||||
export interface entity {
|
type statusEffect = readonly [string, modifiers, (entity: entity) => entity];
|
||||||
|
|
||||||
|
export interface entityController {
|
||||||
setPosition: (location: Vector3) => void;
|
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) {
|
constructor(baseStats: stats, baseAmounts: amounts, puppetEntry: puppetEntry) {
|
||||||
this.baseStats = baseStats;
|
this.baseStats = baseStats;
|
||||||
this.baseAmounts = baseAmounts;
|
this.baseAmounts = baseAmounts;
|
||||||
|
@ -22,48 +235,41 @@ class entityHandler implements entity {
|
||||||
canUseAbility(abilityName: string): boolean {
|
canUseAbility(abilityName: string): boolean {
|
||||||
// ! Not concurrency safe
|
// ! Not concurrency safe
|
||||||
// + buncha status checks
|
// + buncha status checks
|
||||||
for (let i = 1; i <= 2; i++) {
|
const cooldown = this.cooldowns[abilityName];
|
||||||
const cooldown = this.cooldowns[abilityName];
|
if (cooldown) {
|
||||||
if (cooldown !== undefined) {
|
const now = os.clock();
|
||||||
const now = os.clock();
|
if (now - cooldown[0] < cooldown[1]) {
|
||||||
if (now - cooldown[0] >= cooldown[1]) {
|
return false
|
||||||
this.cooldowns[abilityName] = undefined;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
ability(abilityName: string, activated: boolean) {
|
useAbility(abilityName: string, activating: boolean) {
|
||||||
const abilities = this.abilities;
|
const abilities = this.abilities;
|
||||||
if (abilities) {
|
if (activating) {
|
||||||
if (activated) {
|
if (this.canUseAbility(abilityName)) {
|
||||||
if (this.canUseAbility(abilityName)) {
|
const ability = abilities[abilityName];
|
||||||
const ability = abilities[abilityName];
|
if (ability != undefined) {
|
||||||
if (ability !== undefined) {
|
const abilityResult = ability.use();
|
||||||
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
|
// Blah blah blah
|
||||||
}
|
}
|
||||||
|
|
||||||
baseStats: stats;
|
|
||||||
baseAmounts: amounts;
|
|
||||||
puppet: puppet;
|
puppet: puppet;
|
||||||
abilities?: {
|
abilities: {
|
||||||
[key: string]: ability;
|
[key: string]: ability | undefined;
|
||||||
};
|
} = {};
|
||||||
|
|
||||||
cooldowns: {
|
cooldowns: {
|
||||||
[key: string]: [start: number, length: number] | undefined;
|
[key: string]: [start: number, length: number] | undefined;
|
||||||
} = {};
|
} = {};
|
||||||
}
|
baseStats: stats;
|
||||||
|
baseAmounts: amounts;
|
||||||
|
}*/
|
||||||
|
/*
|
||||||
export function makeEntity(
|
export function makeEntity(
|
||||||
puppetEntry: puppetEntry,
|
puppetEntry: puppetEntry,
|
||||||
baseStats = [100, 1, 16, 0] as stats,
|
baseStats = [100, 1, 16, 0] as stats,
|
||||||
|
@ -71,3 +277,14 @@ export function makeEntity(
|
||||||
) {
|
) {
|
||||||
return new entityHandler(baseStats, baseAmounts, puppetEntry);
|
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.
|
// "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 {
|
interface saveDataEntry {
|
||||||
// + May need to move this to archiver
|
// + May need to move this to archiver
|
||||||
placeholder: string;
|
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 {
|
export interface storedPlayer {
|
||||||
teleportToServer: () => void;
|
teleportToServer: () => void;
|
||||||
setPosition: (location: Vector3) => void;
|
//setPosition: (location: Vector3) => void;
|
||||||
ability: (ability: string, state: boolean) => void;
|
//ability: (ability: string, state: boolean) => void;
|
||||||
loadIn: () => void;
|
//loadIn: () => void;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
/* Deprecated (may have useful information)
|
||||||
class storedPlayerHandler implements storedPlayer {
|
class storedPlayerHandler implements storedPlayer {
|
||||||
constructor(player: Player) {
|
constructor(player: Player) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
|
@ -29,7 +51,7 @@ class storedPlayerHandler implements storedPlayer {
|
||||||
}
|
}
|
||||||
ability(ability: string, state: boolean) {
|
ability(ability: string, state: boolean) {
|
||||||
if (this.entity) {
|
if (this.entity) {
|
||||||
this.entity.ability(ability, state);
|
this.entity.useAbility(ability, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadIn() {
|
loadIn() {
|
||||||
|
@ -40,16 +62,11 @@ class storedPlayerHandler implements storedPlayer {
|
||||||
inMainMenu: boolean;
|
inMainMenu: boolean;
|
||||||
// + Other data that is unique to players but does not persist between sessions
|
// + Other data that is unique to players but does not persist between sessions
|
||||||
saveData: saveDataEntry; // This gets synced with the actual datastore
|
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() {}
|
constructor() {}
|
||||||
initPlayer(player: Player) {
|
initPlayer(player: Player) {
|
||||||
this.playerStorageArray[player.UserId] = new storedPlayerHandler(player);
|
this.playerStorageArray[player.UserId] = new storedPlayerHandler(player);
|
||||||
|
@ -66,8 +83,4 @@ class playerStorageHandler implements playerStorage {
|
||||||
return this.playerStorageArray[player.UserId];
|
return this.playerStorageArray[player.UserId];
|
||||||
}
|
}
|
||||||
playerStorageArray: storedPlayerHandler[] = [];
|
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");
|
const ReplicatedStorage = game.GetService("ReplicatedStorage");
|
||||||
export const Input = ReplicatedStorage.WaitForChild("Input", 1);
|
export const Input = ReplicatedStorage.WaitForChild("Input", 1);
|
||||||
export const Output = ReplicatedStorage.WaitForChild("Output", 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 {
|
export function isUnknownTable(thing: unknown): thing is unknownTable {
|
||||||
return typeIs(thing, "table");
|
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