Compare commits

..

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

2 changed files with 117 additions and 165 deletions

View file

@ -1,54 +1,18 @@
// "EntityManager": Create entity objects and apply transformations to them. // "EntityManager": Create entity objects and apply transformations to them.
// A bold attempt at functional programming in a videogame entity system // + 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";
const entityTags = ["minion"] as const // I spent a lot of time thinking about the names for these types and they still suck
type modifiers = readonly [power: number, speed: number, defense: number, resistance: number]; // values used for calculation, only modified by buffs and debuffs type modifiers = readonly [power: number, speed: number, defense: number, resistance: number]; // values used for calculation, only modified by buffs and debuffs (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 statuses = readonly [health: number, barrier: number]; // values used to store an entity's current status (more existential than stats)
type statusEffect = readonly [string, modifiers, entityTransformTemplate, number]; type statusEffect = readonly [string, modifiers, entityTransformTemplate, number];
type entityTag = typeof entityTags[number]
export interface entityId {
readonly name: string;
readonly team: "players" | "enemies";
readonly tags: {
[tagName in entityTag]: boolean;
};
}
export interface entityProperties {
readonly id: entityId,
readonly baseModifiers: modifiers,
readonly maxStatuses: statuses,
readonly baseStatusEffects: statusEffect[],// Permanent immunities/status effects
}
export interface entityStatuses {
readonly statuses: statuses; // Health and stuff that change constantly
readonly statusEffects: statusEffect[]; // Burn, poison, etc.
}
type entityTransformType = "support" | "attack"; export interface entityController {
type healthTransformValue = [magnitude: number, affectsHealth: boolean, affectsBarrier: boolean]
interface entityTransformTemplate {
healthTransformValue: healthTransformValue,
statusEffectsGranted?: placeholder[]; // TBA (Stuff like burn, slow, stun, etc.)
}
type entityTransformDeterminer = (
entityPerformingId: entityId,
entityReceivingId: entityId,
entityPerformingPuppet: puppet,
entityReceivingPuppet: puppet,
) => [entityTransformType, entityTransformTemplate] | false;
interface ability {
placeholder: entityTransformDeterminer; // TBA (Most abilities will not be a single instant of damage/heal)
}
// Immunities are just status effects that take the place of a damaging status effect (so ones of that type can't be added) but don't do anything
// Status effects have a strength value that determines whether they can override another status effect of the same type (holy inferno can replace burn but burn can't replace holy inferno)
export interface entityController {//Deprecated
setPosition: (location: Vector3) => void; setPosition: (location: Vector3) => void;
useAbility: (abilityName: string, activating: boolean) => void; useAbility: (abilityName: string, activating: boolean) => void;
} }
export interface entityModifier {}// Deprecated export interface entityModifier {}
/* Prototype /* Prototype
entityTransform(setOfAllEntities: entity[], actingEntity: entity, ability destructured to {hitBoxList, modificationList}) entityTransform(setOfAllEntities: entity[], actingEntity: entity, ability destructured to {hitBoxList, modificationList})
const entitiesEligible = getAffectedEntities(setOfAllEntities, actingEntity) const entitiesEligible = getAffectedEntities(setOfAllEntities, actingEntity)
@ -67,6 +31,25 @@ export interface entityModifier {}// Deprecated
const ableToUse = completeAbility ? canUseAbility(now, completeAbility) : completeAbility // canUseAbility should return the first abilityPart const ableToUse = completeAbility ? canUseAbility(now, completeAbility) : completeAbility // canUseAbility should return the first abilityPart
return resultOfFirstAbilityPart = ableToUse ? useAbilityPart(ability.firstPart, setOfAllEntities, thisEntity) : [ableToUse] return resultOfFirstAbilityPart = ableToUse ? useAbilityPart(ability.firstPart, setOfAllEntities, thisEntity) : [ableToUse]
*/ */
interface entityStats {
statuses: statuses; // Health and stuff that change constantly
/* 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;
baseModifiers: modifiers; // Base modifiers that change only when the player switches weapons or something
maxStatuses: statuses;
puppet: puppet;
}
// type entityTransform = (entity: entity) => entity; // type entityTransform = (entity: entity) => entity;
// interface entityTransformTemplate { // interface entityTransformTemplate {
@ -82,6 +65,23 @@ export interface entityModifier {}// Deprecated
excludesSelf: boolean, excludesSelf: boolean,
affectsSameTeam: boolean, affectsSameTeam: boolean,
*/ //} */ //}
type entityTransformType = "heal" | "attack";
interface entityTransformTemplate {
thingus: entityTransformType;
magnitude: number;
affectsHealth: boolean;
affectsBarrier: boolean;
statusEffectsGranted?: placeholder[]; // TBA (Stuff like burn, slow, stun, etc.)
}
type entityTransformDeterminer = (
entityPerformingId: entityId,
entityReceivingId: entityId,
entityPerformingPuppet: puppet,
entityReceivingPuppet: puppet,
) => entityTransformTemplate | false;
interface ability {
placeholder: entityTransformDeterminer; // TBA (Most abilities will not be a single instant of damage/heal)
}
// type entityTransformTemplate = [entityTransformEligibilityTemplate, entityTransformApplicationTemplate] // type entityTransformTemplate = [entityTransformEligibilityTemplate, entityTransformApplicationTemplate]
/*function isEligibleForTransform(entityPerformingTransform: entity, entityReceivingTransform: entity, eligibilityTemplate: entityTransformEligibilityTemplate): false | [boolean] { // This function sucks /*function isEligibleForTransform(entityPerformingTransform: entity, entityReceivingTransform: entity, eligibilityTemplate: entityTransformEligibilityTemplate): false | [boolean] { // This function sucks
const entityReceivingId = entityReceivingTransform.id const entityReceivingId = entityReceivingTransform.id
@ -100,57 +100,27 @@ export interface entityModifier {}// Deprecated
} }
return [onSameTeam] return [onSameTeam]
}*/ }*/
function applyEntityToAttack(
/*function applyAttackToEntityStats() {//else if (transformType === "heal") { entityModifiers: modifiers,
// Apply receiver's status effects to incoming heal entityStatusEffects: statusEffect[],
const newEntityStatuses = applyHealToStatuses( magnitude: number,
entityStats.statuses, // DRY alert ): number {
magnitude, const attack = applyModifiersToAttack(magnitude, entityModifiers);
entityTransformTemplate.affectsHealth, // + Apply status effects of performing entity to attack (e.g. weaken)
entityTransformTemplate.affectsBarrier, return attack;
maxStatuses, }
);
// + Add or remove status effects (don't compare against immunities)
return {
statuses: newEntityStatuses,
statusEffects: entityStats.statusEffects, // Placeholder
};
}*/
function applyAttackToEntityStatuses( function applyAttackToEntityStatuses(
entityStatuses: statuses, entityStatuses: statuses,
entityTransformTemplate: entityTransformTemplate,
entityModifiers: modifiers, entityModifiers: modifiers,
entityStatusEffects: statusEffect[], entityStatusEffects: statusEffect[],
attack: number,
affectsHealth: boolean,
affectsBarrier: boolean,
): statuses { ): statuses {
// Not sure if this should return a whole entity
// + Apply status effects of receiving entity to damage (e.g. armor break) // + Apply status effects of receiving entity to damage (e.g. armor break)
const modifiedDamage = applyModifiersToDamage(entityTransformTemplate.magnitude, entityModifiers); const damage = applyModifiersToAttack(attack, entityModifiers);
const newStatuses = applyDamageToStatuses(entityStatuses, modifiedDamage, entityTransformTemplate.affectsHealth, entityTransformTemplate.affectsBarrier); const newStatuses = applyDamageToStatuses(entityStatuses, damage, affectsHealth, affectsBarrier);
return entityStatuses;
}
/*function applyHealToEntityStats() {//if (transformType === "attack") {
// Apply receiver's status effects to incoming damage
const incomingDamage = applyModifiersToDamage(magnitude, baseModifiers);
const newEntityStatuses = applyDamageToStatuses(
entityStats.statuses,
incomingDamage,
entityTransformTemplate.affectsHealth,
entityTransformTemplate.affectsBarrier,
);
// + Add or remove status effects (compare against immunities)
return {
statuses: newEntityStatuses,
statusEffects: entityStats.statusEffects, // Placeholder
};
} */
function applyHealToEntityStatuses(
entityStatuses: statuses,
entityTransformTemplate: entityTransformTemplate,
entityModifiers: modifiers,
entityMaxStatuses: statuses,
entityStatusEffects: statusEffect[],
): statuses {
// + Apply status effects of receiving entity to damage (e.g. heal up)
const newStatuses = applyHealToStatuses(entityStatuses, entityTransformTemplate.magnitude, entityTransformTemplate.affectsHealth, entityTransformTemplate.affectsBarrier, entityMaxStatuses);
return entityStatuses; return entityStatuses;
} }
// ? Are you meant to pipe determineEntityTransform into the resulting function? // ? Are you meant to pipe determineEntityTransform into the resulting function?
@ -161,33 +131,53 @@ function applyHealToEntityStatuses(
return false; // Placeholder return false; // Placeholder
} }
*/ */
function applyEntityToAttack( function transformEntityStats(
entityModifiers: modifiers, entityPerformingTransform: entity,
entityStatusEffects: statusEffect[],
entityTransformTemplate: entityTransformTemplate,
): entityTransformTemplate {
const outgoingAttack = applyModifiersToAttack(entityTransformTemplate.magnitude, entityModifiers);
// + Apply user's status effects to outgoing attack
// (Old; same thing) + Apply status effects of performing entity to attack (e.g. weaken)
entityTransformTemplate.magnitude = outgoingAttack; // Mutating is cringe, but the other parts of the transform need to be defined first
return entityTransformTemplate;
}
function applyEntityToHeal(
entityModifiers: modifiers,
entityStatusEffects: statusEffect[],
entityTransformTemplate: entityTransformTemplate,
): entityTransformTemplate {
// + Apply user's status effects to outgoing heal
return entityTransformTemplate // There could be a heal modifier later
}
/*function transformEntityStats(
entityStats: entityStats, entityStats: entityStats,
entityTransformTemplate: entityTransformTemplate, entityTransformTemplate: entityTransformTemplate,
baseModifiers: modifiers, baseModifiers: modifiers,
maxStatuses: statuses, maxStatuses: statuses,
): entityStats { ): entityStats {
const magnitude = entityTransformTemplate.magnitude*/ const transformType = entityTransformTemplate.thingus;
//} if (transformType === "attack") {
const outgoingAttack = applyModifiersToAttack(
entityTransformTemplate.magnitude,
entityPerformingTransform.baseModifiers,
);
// Apply user's status effects to outgoing attack
// Apply receiver's status effects to incoming damage
const incomingDamage = applyModifiersToDamage(outgoingAttack, baseModifiers);
const newEntityStatuses = applyDamageToStatuses(
entityStats.statuses,
incomingDamage,
entityTransformTemplate.affectsHealth,
entityTransformTemplate.affectsBarrier,
);
// Add or remove status effects
return {
statuses: newEntityStatuses,
statusEffects: entityStats.statusEffects, // Placeholder
};
} else if (transformType === "heal") {
const outgoingHeal = entityTransformTemplate.magnitude; // There could be a heal modifier later
// Apply user's status effects to outgoing heal
// Apply receiver's status effects to incoming heal
const newEntityStatuses = applyHealToStatuses(
entityStats.statuses, // DRY alert
outgoingHeal,
entityTransformTemplate.affectsHealth,
entityTransformTemplate.affectsBarrier,
maxStatuses,
);
// Add or remove status effects
return {
statuses: newEntityStatuses,
statusEffects: entityStats.statusEffects, // Placeholder
};
} else {
throw "Unknown entity transform type " + transformType; // Should be never
}
}
/*interface entityTransform extends entityTransformTemplate { /*interface entityTransform extends entityTransformTemplate {
specificEntity?: string, specificEntity?: string,
team: "players" | "enemies", team: "players" | "enemies",
@ -307,61 +297,28 @@ function getEntityByName(entityList: entity[], entityName: string): success<enti
return [false, "Entity not found"]; return [false, "Entity not found"];
} }
function getAbility() {} function getAbility() {}
function applyEntityTransformToEntityStatuses(entityTransform: entityTransformTemplate, statuses: statuses, maxStatuses: statuses) {
}
function applyEntityTransformToEntityList( function applyEntityTransformToEntityList(
entityList: entity[], entityList: entity[],
entityTransformDeterminer: entityTransformDeterminer, entityTransformDeterminer: entityTransformDeterminer,
entityPerformingTransform: entity, entityPerformingTransform: entity,
aim: Vector3, aim: Vector3,
): [entity[], placeholder[]?] { ): [entity[], placeholder[]?] {
const entityPerformingTransformModifiers = entityPerformingTransform.baseModifiers
const entityPerformingTransformStatusEffects = entityPerformingTransform.stats.statusEffects // Not good
const newEntityList = entityList.map(function (entityReceivingTransform: entity): entity { const newEntityList = entityList.map(function (entityReceivingTransform: entity): entity {
const entityTransform = entityTransformDeterminer( const entityTransformTemplate = entityTransformDeterminer(
entityPerformingTransform.id, entityPerformingTransform.id,
entityReceivingTransform.id, entityReceivingTransform.id,
entityPerformingTransform.puppet, entityPerformingTransform.puppet,
entityReceivingTransform.puppet, entityReceivingTransform.puppet,
); );
if (entityTransform) { const newEntityStats = entityTransformTemplate
const entityStatuses = entityPerformingTransform.stats.statuses ? transformEntityStats(
const [entityTransformType, entityTransformTemplate] = entityTransform entityPerformingTransform,
if (entityTransformType === "attack") { entityReceivingTransform.stats,
const outgoingTransformTemplate = applyEntityToAttack(
entityPerformingTransformModifiers,
entityPerformingTransformStatusEffects,
entityTransformTemplate
);
const newEntityStatuses = outgoingTransformTemplate
? applyAttackToEntityStatuses(
entityStatuses,
entityTransformTemplate,
entityReceivingTransform.baseModifiers,
entityReceivingTransform.stats.statusEffects, // This is also cringe
)
: entityStatuses; // Also cringe
} else {
assert(entityTransformType === "heal")
const outgoingTransformTemplate = applyEntityToHeal( // Lots of DRY violations here
entityPerformingTransformModifiers,
entityPerformingTransformStatusEffects,
entityTransformTemplate
);
const newEntityStatuses = outgoingTransformTemplate
? applyHealToEntityStatuses(
entityStatuses,
entityTransformTemplate, entityTransformTemplate,
entityReceivingTransform.baseModifiers, entityReceivingTransform.baseModifiers,
entityReceivingTransform.maxStatuses, entityReceivingTransform.maxStatuses,
entityReceivingTransform.stats.statusEffects, // So much cringe
) )
: entityStatuses; : entityReceivingTransform.stats;
}
} else {
return entityReceivingTransform;
}
}); });
return [newEntityList]; return [newEntityList];
} }

View file

@ -1,5 +1,5 @@
// "The": Handle events. // "The": Handle events.
import { entityId, entityProperties, entityStatuses/*entityManagerRequest /*makeEntity, entityController*/ } from "./EntityManager"; import { entity, getEntityByName/*entityManagerRequest /*makeEntity, entityController*/ } from "./EntityManager";
import { applyRequestsToPlayerStorage, playerManagerRequest } from "./PlayerManager" import { applyRequestsToPlayerStorage, playerManagerRequest } from "./PlayerManager"
export type sceneManagerRequest = [Player, "useAbility", Vector3] | [Player, "foo", "bar"] export type sceneManagerRequest = [Player, "useAbility", Vector3] | [Player, "foo", "bar"]
type endConditionFunction = (containedScenes: scene[], containedEntities: entity[], timeElapsed: number) => boolean type endConditionFunction = (containedScenes: scene[], containedEntities: entity[], timeElapsed: number) => boolean
@ -8,16 +8,11 @@ export interface sceneTemplate {
readonly onCompletion: readonly playerManagerRequest[] // Requests to get sent out when the scene ends readonly onCompletion: readonly playerManagerRequest[] // Requests to get sent out when the scene ends
} }
export interface scene extends sceneTemplate { export interface scene extends sceneTemplate {
/*containedScenes?: { containedScenes?: {
[sceneName: string]: scene | undefined [sceneName: string]: scene | undefined
}; // Scenes within this scene that are isolated from each other and can be run in parallel }; // Scenes within this scene that are isolated from each other and can be run in parallel
// Not necessarily "A list of events that need to return true (in sequence) to complete this event", but such events would go there // Not necessarily "A list of events that need to return true (in sequence) to complete this event", but such events would go there
*///Scene nesting is currently cancelled containedEntities: entity[]; // A list of entities that need to die to complete the event
containedEntities: string[]; // "A list of entities that need to die to complete the event" (not really but kinda)
containedEntityProperties: {
[entityName: string]: entityProperties
}
containedEntityStatuses
players: { players: {
[userId: number]: [inThisScene: true] | [inThisScene: false, subScene: string] | undefined [userId: number]: [inThisScene: true] | [inThisScene: false, subScene: string] | undefined
} }
@ -26,7 +21,7 @@ export interface scene extends sceneTemplate {
export function runScene(scene: scene, now: number): success<[scene, playerManagerRequest[]]> { export function runScene(scene: scene, now: number): success<[scene, playerManagerRequest[]]> {
return [true, [scene, []]]; return [true, [scene, []]];
} }
/*function getPlayerSceneName(scene: scene, userId: number): success<string | false> { function getPlayerSceneName(scene: scene, userId: number): success<string | false> {
let playerSceneLocation = scene.players[userId]; let playerSceneLocation = scene.players[userId];
if (!playerSceneLocation) { if (!playerSceneLocation) {
return [false, "Player does not exist"]; // Some kind of error needs to go here return [false, "Player does not exist"]; // Some kind of error needs to go here
@ -35,9 +30,9 @@ export function runScene(scene: scene, now: number): success<[scene, playerManag
} else { } else {
return [true, playerSceneLocation[1]] return [true, playerSceneLocation[1]]
} }
}*/ }
function applyRequestToScene(scene: scene, now: number, request: sceneManagerRequest): [scene, playerManagerRequest[]] { function applyRequestToScene(scene: scene, now: number, request: sceneManagerRequest): [scene, playerManagerRequest[]] {
/*const playerSceneResult = getPlayerSceneName(scene, request[0].UserId) const playerSceneResult = getPlayerSceneName(scene, request[0].UserId)
if (!playerSceneResult[0]) { if (!playerSceneResult[0]) {
return [scene, []]; // Some kind of error needs to go here return [scene, []]; // Some kind of error needs to go here
} }
@ -58,7 +53,7 @@ function applyRequestToScene(scene: scene, now: number, request: sceneManagerReq
containedScenes[playerSceneName] = sceneRequestResult[0] // This is questionably object-oriented containedScenes[playerSceneName] = sceneRequestResult[0] // This is questionably object-oriented
scene.containedScenes = containedScenes scene.containedScenes = containedScenes
return [scene, sceneRequestResult[1]] return [scene, sceneRequestResult[1]]
}*/ }
} }
export function applyRequestsToScene(scene: scene, now: number, requests: sceneManagerRequest[]): success<[scene, playerManagerRequest[]]> { export function applyRequestsToScene(scene: scene, now: number, requests: sceneManagerRequest[]): success<[scene, playerManagerRequest[]]> {
try { try {