Compare commits
No commits in common. "052a37b36fc9cfbe12b3dfbca5082156a3b1e170" and "991fce6103903ae5d9806ef6086a32aea7e48b01" have entirely different histories.
052a37b36f
...
991fce6103
11 changed files with 107 additions and 462 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 actionList = [
|
const actionAssignmentsReference: string[] = [
|
||||||
"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,11 +24,21 @@ const actionList = [
|
||||||
"diamond4",
|
"diamond4",
|
||||||
"special1", // Special controls
|
"special1", // Special controls
|
||||||
"special2",
|
"special2",
|
||||||
] as const;
|
];
|
||||||
type action = typeof actionList[number]
|
export interface actionAssignments {
|
||||||
export type actionAssignments = {
|
// Based on the reference array
|
||||||
[actionName in action]?: validInput;
|
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
|
||||||
|
}
|
||||||
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] {
|
||||||
|
@ -72,9 +82,9 @@ class actionHandler implements actionBinder {
|
||||||
}
|
}
|
||||||
assignInputsToActions(actionAssignments: unknownTable) {
|
assignInputsToActions(actionAssignments: unknownTable) {
|
||||||
const newActionAssignments: actionAssignments = {};
|
const newActionAssignments: actionAssignments = {};
|
||||||
actionList.forEach((action) => {
|
actionAssignmentsReference.forEach((action) => {
|
||||||
const input: unknown = actionAssignments[action];
|
const input: unknown = actionAssignments[action];
|
||||||
if (isValidInput(input)) {
|
if (isValidAction(action) && isValidInput(input)) {
|
||||||
newActionAssignments[action] = input;
|
newActionAssignments[action] = input;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
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: (player: Player, ...messageContents: unknown[]) => void) {
|
export function bindToClientMessage(functionToBind: Callback) {
|
||||||
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,70 +1,20 @@
|
||||||
// "main": Initializes all state and handles the real world.
|
// "main": This is the core of reality. It serves as the highest-level abstraction.
|
||||||
const Players = game.GetService("Players"); // This should be the only place on the server where the Players service is mentioned
|
// + Prevent this from coupling with the entity manager, if possible
|
||||||
const RunService = game.GetService("RunService");
|
const Players = game.GetService("Players");
|
||||||
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";
|
||||||
// Please note: This should not use any of the properties of "scene" or "playerStorage" (it only needs to know that they exist)
|
const mainPlayerStorage: playerStorage = makePlayerStorage();
|
||||||
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
|
|
||||||
|
|
||||||
const now = os.clock()
|
function addPlayer(player: Player) {
|
||||||
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);
|
||||||
}*/
|
}
|
||||||
// function handleClientMessage(player: Player, messageType: unknown, messageContent: unknown) {
|
// 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
|
||||||
//playerManager.message(["PlayerInput"])
|
function handleClientMessage(player: Player, messageType: unknown, messageContent: unknown) {
|
||||||
/*
|
|
||||||
const storedPlayer = mainPlayerStorage.fetchPlayer(player);
|
const storedPlayer = mainPlayerStorage.fetchPlayer(player);
|
||||||
if (messageType === "EnterGame") {
|
if (messageType === "EnterGame") {
|
||||||
try {
|
try {
|
||||||
|
@ -91,5 +41,8 @@ function removePlayer(player: Player) {
|
||||||
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,8 +10,6 @@ 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,5 +1,4 @@
|
||||||
import { entityModifier } from "./EntityManager"
|
|
||||||
export interface ability {
|
export interface ability {
|
||||||
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?
|
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?
|
||||||
// ... 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,229 +1,16 @@
|
||||||
// "EntityManager": Create entity objects and apply transformations to them.
|
// "EntityManager": Create entities objects and deck them out with functions to use.
|
||||||
// + 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";
|
||||||
// I spent a lot of time thinking about the names for these types and they still suck
|
type stats = [maxHealth: number, attack: number, speed: number, defense: number]; // values used for calculation, only modified by buffs and debuffs
|
||||||
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 amounts = [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, (entity: entity) => entity];
|
|
||||||
|
|
||||||
export interface entityController {
|
|
||||||
setPosition: (location: Vector3) => void;
|
|
||||||
useAbility: (abilityName: string, activating: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface entityModifier {}
|
|
||||||
/* Prototype
|
|
||||||
entityTransform(setOfAllEntities: entity[], actingEntity: entity, ability destructured to {hitBoxList, modificationList})
|
|
||||||
const entitiesEligible = getAffectedEntities(setOfAllEntities, actingEntity)
|
|
||||||
const entitiesHit = entitiesEligible ? getHitEntities(entitiesEligible, hitBoxList) : entitiesEligible // This would be a generic function that accepts an array or a single hitbox
|
|
||||||
const entityAlteration = entitiesHit ? applyEntityToAlteration(entityTransform, actingEntity) : entitiesHit
|
|
||||||
return modifiedEntities = entityAlteration ? alterEntity(entitiesHit, entityAlteration) : entitiesHit // entitiesHit could be an array of arrays containing entities or just an array of entities
|
|
||||||
|
|
||||||
useAbilityPart(abilityPart: abilityPart, setOfAllEntities: entity[], thisEntity: entity)
|
|
||||||
const transformedEntities = ableToUse ? entityTranform(setOfAllEntities, thisEntity) : ableToUse
|
|
||||||
const privateMessaged = transformedEntities ? messageClient(UNIMPLEMENTED) : transformedEntities
|
|
||||||
const globalMessaged = privateMessaged ? messageAllClients(UNIMPLEMENTED) : privateMessaged
|
|
||||||
return [globalMessaged, transformedEntities, schedulingForNextAbilityPart] // Might need to add some kind of reason here, not just an indiscriminate error
|
|
||||||
|
|
||||||
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 {
|
export interface entity {
|
||||||
readonly id: entityId
|
setPosition: (location: Vector3) => void;
|
||||||
stats: entityStats
|
ability: (ability: string, state: boolean) => void;
|
||||||
puppet: puppet
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type entityTransform = (entity: entity) => entity
|
class entityHandler implements 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;
|
||||||
|
@ -235,41 +22,48 @@ function useAbility(entityList: entity[], entityUsing: entity, aim: Vector3): [e
|
||||||
canUseAbility(abilityName: string): boolean {
|
canUseAbility(abilityName: string): boolean {
|
||||||
// ! Not concurrency safe
|
// ! Not concurrency safe
|
||||||
// + buncha status checks
|
// + buncha status checks
|
||||||
const cooldown = this.cooldowns[abilityName];
|
for (let i = 1; i <= 2; i++) {
|
||||||
if (cooldown) {
|
const cooldown = this.cooldowns[abilityName];
|
||||||
const now = os.clock();
|
if (cooldown !== undefined) {
|
||||||
if (now - cooldown[0] < cooldown[1]) {
|
const now = os.clock();
|
||||||
return false
|
if (now - cooldown[0] >= cooldown[1]) {
|
||||||
|
this.cooldowns[abilityName] = undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
useAbility(abilityName: string, activating: boolean) {
|
ability(abilityName: string, activated: boolean) {
|
||||||
const abilities = this.abilities;
|
const abilities = this.abilities;
|
||||||
if (activating) {
|
if (abilities) {
|
||||||
if (this.canUseAbility(abilityName)) {
|
if (activated) {
|
||||||
const ability = abilities[abilityName];
|
if (this.canUseAbility(abilityName)) {
|
||||||
if (ability != undefined) {
|
const ability = abilities[abilityName];
|
||||||
const abilityResult = ability.use();
|
if (ability !== undefined) {
|
||||||
|
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 | undefined;
|
[key: string]: ability;
|
||||||
} = {};
|
};
|
||||||
|
|
||||||
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,
|
||||||
|
@ -277,14 +71,3 @@ 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();
|
|
||||||
}
|
|
||||||
*/
|
|
18
src/shared/EventManager.ts
Normal file
18
src/shared/EventManager.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// "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,39 +1,17 @@
|
||||||
// "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.
|
import { makeEntity, entity } from "./EntityManager";
|
||||||
// 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;
|
||||||
|
@ -51,7 +29,7 @@ class storedPlayerHandler implements storedPlayer {
|
||||||
}
|
}
|
||||||
ability(ability: string, state: boolean) {
|
ability(ability: string, state: boolean) {
|
||||||
if (this.entity) {
|
if (this.entity) {
|
||||||
this.entity.useAbility(ability, state);
|
this.entity.ability(ability, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadIn() {
|
loadIn() {
|
||||||
|
@ -62,11 +40,16 @@ 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?: entityController;
|
entity?: entity;
|
||||||
}*/
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
@ -83,4 +66,8 @@ class storedPlayerHandler implements storedPlayer {
|
||||||
return this.playerStorageArray[player.UserId];
|
return this.playerStorageArray[player.UserId];
|
||||||
}
|
}
|
||||||
playerStorageArray: storedPlayerHandler[] = [];
|
playerStorageArray: storedPlayerHandler[] = [];
|
||||||
}*/
|
}
|
||||||
|
|
||||||
|
export function makePlayerStorage() {
|
||||||
|
return new playerStorageHandler();
|
||||||
|
}
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
// "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 @@
|
||||||
// "Shared"
|
// "Remotes"
|
||||||
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,17 +6,3 @@ 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