diff --git a/src/luaSpecific/defineMessage.lua b/src/luaSpecific/defineMessage.lua index 03f2935..9336b4f 100644 --- a/src/luaSpecific/defineMessage.lua +++ b/src/luaSpecific/defineMessage.lua @@ -1,8 +1,9 @@ local luaSpecific = script.Parent local source = luaSpecific.Parent.source -local skillLib = require(source.skill) local holdable = require(source.holdableSkill) +local skillLib = require(source.skill) +local statusLib = require(source.statusEffect) local message = require(source.message) local utility = require(source.utility) @@ -20,10 +21,22 @@ function DefineMessage(fn, options) end end end + for _, ctor in statusLib.GetRegisteredStatusEffects() do + if foundCtor then + break + end + for name, method in ctor do + if method == fn then + foundCtor, methodName = ctor, name + break + end + end + end local isInvalid = skillLib.SkillBase[methodName] ~= nil or skillLib.Skill[methodName] ~= nil or holdable.HoldableSkill[methodName] ~= nil + or statusLib.StatusEffect[methodName] ~= nil if not foundCtor or isInvalid or not rawget(foundCtor, methodName) then utility.logError("Provided function is not a valid skill method.") diff --git a/src/source/character.ts b/src/source/character.ts index 90338ac..7eac197 100644 --- a/src/source/character.ts +++ b/src/source/character.ts @@ -453,6 +453,15 @@ export class Character { return mapToArray(this.statusEffects).filter((T) => T.GetState().IsActive); } + /** + * @internal + * @hidden + * Retrieves a status effect from the status effects map based on a given id. + */ + public GetStatusEffectFromId(Id: string) { + return this.statusEffects.get(Id); + } + /** * Retrieves all status effects of a given type. */ diff --git a/src/source/client.ts b/src/source/client.ts index e0db66d..9473a44 100644 --- a/src/source/client.ts +++ b/src/source/client.ts @@ -114,64 +114,68 @@ class Client { }); const eventHandler = (serialized: SerializedData) => { - const [Name, MethodName, PackedArgs] = messageSerializer.deserialize( - serialized.buffer, - serialized.blobs, - ); + const [ContentType, Name, MethodName, PackedArgs] = + messageSerializer.deserialize(serialized.buffer, serialized.blobs); const character = Character.GetLocalCharacter(); if (!character) return; - const skill = character.GetSkillFromString(Name); - if (!skill) return; + const messageClass = + ContentType === "Skill" + ? character.GetSkillFromString(Name) + : character.GetStatusEffectFromId(Name); + if (!messageClass) return; const args = RestoreArgs(PackedArgs); - const config = Reflect.getMetadata(skill, `Config_${MethodName}`) as - | MessageOptions - | undefined; - if (config?.OnlyWhenActive && !skill.GetState().IsActive) return; + const config = Reflect.getMetadata( + messageClass, + `Config_${MethodName}`, + ) as MessageOptions | undefined; + if (config?.OnlyWhenActive && !messageClass.GetState().IsActive) return; if (config?.Validators) { if (!ValidateArgs(config.Validators, args)) return; } - const method = skill[MethodName as never] as ( - self: UnknownSkill, + const method = messageClass[MethodName as never] as ( + self: UnknownSkill | UnknownStatus, ...args: unknown[] ) => unknown; - method(skill, ...args); + method(messageClass, ...args); }; ClientEvents.messageToClient.connect(eventHandler); ClientEvents.messageToClient_urel.connect(eventHandler); ClientFunctions.messageToClient.setCallback((serialized) => { - const [Name, MethodName, PackedArgs] = messageSerializer.deserialize( - serialized.buffer, - serialized.blobs, - ); + const [ContentType, Name, MethodName, PackedArgs] = + messageSerializer.deserialize(serialized.buffer, serialized.blobs); const character = Character.GetLocalCharacter(); if (!character) return; - const skill = character.GetSkillFromString(Name); - if (!skill) return; + const messageClass = + ContentType === "Skill" + ? character.GetSkillFromString(Name) + : character.GetStatusEffectFromId(Name); + if (!messageClass) return; const args = RestoreArgs(PackedArgs); - const config = Reflect.getMetadata(skill, `Config_${MethodName}`) as - | MessageOptions - | undefined; - if (config?.OnlyWhenActive && !skill.GetState().IsActive) + const config = Reflect.getMetadata( + messageClass, + `Config_${MethodName}`, + ) as MessageOptions | undefined; + if (config?.OnlyWhenActive && !messageClass.GetState().IsActive) return INVALID_MESSAGE_STR; if (config?.Validators) { if (!ValidateArgs(config.Validators, args)) return INVALID_MESSAGE_STR; } - const method = skill[MethodName as never] as ( - self: UnknownSkill, + const method = messageClass[MethodName as never] as ( + self: UnknownSkill | UnknownStatus, ...args: unknown[] ) => Promise | unknown; - const returnedValue = method(skill, ...args); + const returnedValue = method(messageClass, ...args); if (Promise.is(returnedValue)) { const [success, value] = returnedValue.await(); return success ? value : INVALID_MESSAGE_STR; diff --git a/src/source/message.ts b/src/source/message.ts index f75c510..ae4f743 100644 --- a/src/source/message.ts +++ b/src/source/message.ts @@ -10,12 +10,12 @@ import { } from "./networking"; import { messageSerializer } from "./serdes"; import { SkillBase } from "./skill"; +import { StatusEffect } from "./statusEffect"; import { instanceofConstructor, isClientContext, isServerContext, logError, - logWarning, } from "./utility"; /** @@ -59,9 +59,11 @@ export function Message(Options: T) { methodName: string, _: TypedPropertyDescriptor>>, ) => { - if (!instanceofConstructor(ctor as never, SkillBase as never)) { - logError(`${ctor} is not a valid skill constructor.`); - } + const contentType = instanceofConstructor(ctor as never, SkillBase as never) + ? "Skill" + : instanceofConstructor(ctor as never, StatusEffect as never) + ? "StatusEffect" + : logError(`${ctor} is not a valid skill/ status effect constructor.`); if (!rawget(ctor, methodName)) { logError(`${ctor} does not have a method named ${methodName}.`); } @@ -97,10 +99,15 @@ export function Message(Options: T) { if (current === Options.Destination) return; - ctor[methodName] = function (this: SkillBase, ...args: unknown[]) { + ctor[methodName] = function ( + this: SkillBase | StatusEffect, + ...args: unknown[] + ) { if (!this.Player) return; + const serialized = messageSerializer.serialize([ - this.Name, + contentType, + contentType === "Skill" ? this.Name : this.GetId(), methodName, ConvertArgs(args), ]); diff --git a/src/source/serdes.ts b/src/source/serdes.ts index bdedf62..7d99856 100644 --- a/src/source/serdes.ts +++ b/src/source/serdes.ts @@ -6,7 +6,12 @@ export const skillRequestSerializer = >(); export const messageSerializer = createBinarySerializer< - [Name: string, MethodName: string, Args: Map] + [ + ContentType: "Skill" | "StatusEffect", + Name: string, + MethodName: string, + Args: Map, + ] >(); export type SerializedData = { diff --git a/src/source/server.ts b/src/source/server.ts index 0540eeb..0bd5ed1 100644 --- a/src/source/server.ts +++ b/src/source/server.ts @@ -3,6 +3,7 @@ import { atom } from "@rbxts/charm"; import CharmSync from "@rbxts/charm-sync"; import { Players } from "@rbxts/services"; import { t } from "@rbxts/t"; +import type { UnknownStatus } from "exports"; import { isClientContext, logError, @@ -146,68 +147,74 @@ class Server { }); const eventHandler = (Player: Player, serialized: SerializedData) => { - const [Name, MethodName, PackedArgs] = messageSerializer.deserialize( - serialized.buffer, - serialized.blobs, - ); + const [ContentType, Name, MethodName, PackedArgs] = + messageSerializer.deserialize(serialized.buffer, serialized.blobs); const playerCharacter = Player.Character; if (!playerCharacter) return; const character = Character.GetCharacterFromInstance(playerCharacter); if (!character || character.Player !== Player) return; - const skill = character.GetSkillFromString(Name); - if (!skill) return; + let messageClass: UnknownSkill | UnknownStatus | undefined; + if (ContentType === "Skill") { + messageClass = character.GetSkillFromString(Name); + } else if (ContentType === "StatusEffect") { + messageClass = character.GetStatusEffectFromId(Name); + } + if (!messageClass) return; const args = RestoreArgs(PackedArgs); - const config = Reflect.getMetadata(skill, `Config_${MethodName}`) as - | MessageOptions - | undefined; - if (config?.OnlyWhenActive && !skill.GetState().IsActive) return; + const config = Reflect.getMetadata( + messageClass, + `Config_${MethodName}`, + ) as MessageOptions | undefined; + if (config?.OnlyWhenActive && !messageClass.GetState().IsActive) return; if (config?.Validators) { if (!ValidateArgs(config.Validators, args)) return; } - const method = skill[MethodName as never] as ( - self: UnknownSkill, + const method = messageClass[MethodName as never] as ( + self: UnknownSkill | UnknownStatus, ...args: unknown[] ) => unknown; - method(skill, ...args); + method(messageClass, ...args); }; ServerEvents.messageToServer.connect(eventHandler); ServerEvents.messageToServer_urel.connect(eventHandler); ServerFunctions.messageToServer.setCallback((Player, serialized) => { - const [Name, MethodName, PackedArgs] = messageSerializer.deserialize( - serialized.buffer, - serialized.blobs, - ); + const [ContentType, Name, MethodName, PackedArgs] = + messageSerializer.deserialize(serialized.buffer, serialized.blobs); const playerCharacter = Player.Character; if (!playerCharacter) return; const character = Character.GetCharacterFromInstance(playerCharacter); if (!character || character.Player !== Player) return; - const skill = character.GetSkillFromString(Name); - if (!skill) return; + const messageClass = + ContentType === "Skill" + ? character.GetSkillFromString(Name) + : character.GetStatusEffectFromId(Name); + if (!messageClass) return; const args = RestoreArgs(PackedArgs); - const config = Reflect.getMetadata(skill, `Config_${MethodName}`) as - | MessageOptions - | undefined; - if (config?.OnlyWhenActive && !skill.GetState().IsActive) + const config = Reflect.getMetadata( + messageClass, + `Config_${MethodName}`, + ) as MessageOptions | undefined; + if (config?.OnlyWhenActive && !messageClass.GetState().IsActive) return INVALID_MESSAGE_STR; if (config?.Validators) { if (!ValidateArgs(config.Validators, args)) return INVALID_MESSAGE_STR; } - const method = skill[MethodName as never] as ( - self: UnknownSkill, + const method = messageClass[MethodName as never] as ( + self: UnknownSkill | UnknownStatus, ...args: unknown[] ) => Promise | unknown; - const returnedValue = method(skill, ...args); + const returnedValue = method(messageClass, ...args); if (Promise.is(returnedValue)) { const [_, value] = returnedValue.await(); return value; diff --git a/src/source/skill.ts b/src/source/skill.ts index e35102d..1957552 100644 --- a/src/source/skill.ts +++ b/src/source/skill.ts @@ -151,7 +151,7 @@ export abstract class SkillBase< }; private destroyed = false; private metadata?: Metadata; - protected readonly Name = tostring(getmetatable(this)); + public readonly Name = tostring(getmetatable(this)); /** * A table of arguments provided after the Character in .new(). */ diff --git a/src/source/statusEffect.ts b/src/source/statusEffect.ts index 2bbab12..8133a68 100644 --- a/src/source/statusEffect.ts +++ b/src/source/statusEffect.ts @@ -613,3 +613,14 @@ export function StatusEffectDecorator( export function GetRegisteredStatusEffectConstructor(Name: string) { return registeredStatuses.get(Name); } + +/** + * @internal + * @hidden + */ +export function GetRegisteredStatusEffects() { + return table.freeze(table.clone(registeredStatuses)) as ReadonlyMap< + string, + StatusEffectConstructor + >; +}