From 0ed41383a2029c83e5a6b5377eca50fdc26e89c8 Mon Sep 17 00:00:00 2001 From: Violet Date: Thu, 31 Jul 2025 21:55:31 +0200 Subject: [PATCH 1/3] I changed message and define message to allow support for statuseffects Normally nothing should stop working, I did have to do some weird things due to some differences between status effect and skills, but I didn't want to break stuff so I just made it a bit weird --- src/luaSpecific/defineMessage.lua | 14 +++++- src/source/character.ts | 9 ++++ src/source/client.ts | 61 +++++++++++++---------- src/source/message.ts | 26 ++++++++-- src/source/serdes.ts | 8 +++- src/source/server.ts | 80 +++++++++++++++++++++---------- src/source/skill.ts | 2 +- src/source/statusEffect.ts | 11 +++++ 8 files changed, 151 insertions(+), 60 deletions(-) diff --git a/src/luaSpecific/defineMessage.lua b/src/luaSpecific/defineMessage.lua index 03f2935..fd8c5ee 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,6 +21,17 @@ 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 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..468950e 100644 --- a/src/source/client.ts +++ b/src/source/client.ts @@ -15,6 +15,7 @@ import { Character } from "./character"; import { Flags } from "./flags"; import { INVALID_MESSAGE_STR, + MessageContentType, type MessageOptions, ValidateArgs, } from "./message"; @@ -114,64 +115,72 @@ 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; + let messageClass: UnknownSkill | UnknownStatus | undefined; + if (ContentType === MessageContentType.Skill) { + messageClass = character.GetSkillFromString(Name); + } else if (ContentType === MessageContentType.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); }; 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; + let messageClass: UnknownSkill | UnknownStatus | undefined; + if (ContentType === MessageContentType.Skill) { + messageClass = character.GetSkillFromString(Name); + } else if (ContentType === MessageContentType.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) + 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..5754841 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"; /** @@ -53,14 +53,25 @@ export function ValidateArgs(validators: t.check[], args: unknown[]) { return true; } +/** @internal @hidden */ +export enum MessageContentType { + Skill = "Skill", + StatusEffect = "StatusEffect", +} + export function Message(Options: T) { return ( ctor: Record, methodName: string, _: TypedPropertyDescriptor>>, ) => { - if (!instanceofConstructor(ctor as never, SkillBase as never)) { - logError(`${ctor} is not a valid skill constructor.`); + let contentType: MessageContentType; + if (instanceofConstructor(ctor as never, SkillBase as never)) { + contentType = MessageContentType.Skill; + } else if (instanceofConstructor(ctor as never, StatusEffect as never)) { + contentType = MessageContentType.StatusEffect; + } else { + 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 +108,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 === MessageContentType.Skill ? this.Name : this.GetId(), methodName, ConvertArgs(args), ]); diff --git a/src/source/serdes.ts b/src/source/serdes.ts index bdedf62..8363502 100644 --- a/src/source/serdes.ts +++ b/src/source/serdes.ts @@ -1,4 +1,5 @@ import { createBinarySerializer } from "@rbxts/flamework-binary-serializer"; +import type { MessageContentType } from "exports"; export const skillRequestSerializer = createBinarySerializer< @@ -6,7 +7,12 @@ export const skillRequestSerializer = >(); export const messageSerializer = createBinarySerializer< - [Name: string, MethodName: string, Args: Map] + [ + ContentType: MessageContentType, + Name: string, + MethodName: string, + Args: Map, + ] >(); export type SerializedData = { diff --git a/src/source/server.ts b/src/source/server.ts index 0540eeb..d83bcee 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, @@ -14,6 +15,7 @@ import { RestoreArgs } from "./arg-converter"; import { Character, type CharacterData } from "./character"; import { INVALID_MESSAGE_STR, + MessageContentType, type MessageOptions, ValidateArgs, } from "./message"; @@ -146,68 +148,94 @@ 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 === MessageContentType.Skill) { + messageClass = character.GetSkillFromString(Name); + } else if (ContentType === MessageContentType.StatusEffect) { + messageClass = character.GetStatusEffectFromId(Name); + } + print( + `Message class: ${messageClass}`, + ContentType, + MessageContentType.StatusEffect, + ContentType === MessageContentType.StatusEffect, + character.GetStatusEffectFromId(Name), + Name, + character.GetAllStatusEffects(), + ); + 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; + let messageClass: UnknownSkill | UnknownStatus | undefined; + if (ContentType === MessageContentType.Skill) { + messageClass = character.GetSkillFromString(Name); + } else if (ContentType === MessageContentType.StatusEffect) { + messageClass = character.GetStatusEffectFromId(Name); + } + print( + `Message class: ${messageClass}`, + ContentType, + MessageContentType.StatusEffect, + ContentType === MessageContentType.StatusEffect, + character.GetStatusEffectFromId(Name), + Name, + character.GetAllStatusEffects(), + ); + 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 + >; +} From 0879374b1f9241400ec6892eff395f70c351406b Mon Sep 17 00:00:00 2001 From: Violet Date: Sun, 17 Aug 2025 22:16:11 +0200 Subject: [PATCH 2/3] added fix to check for valid status effects in defineMessage --- src/luaSpecific/defineMessage.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/src/luaSpecific/defineMessage.lua b/src/luaSpecific/defineMessage.lua index fd8c5ee..9336b4f 100644 --- a/src/luaSpecific/defineMessage.lua +++ b/src/luaSpecific/defineMessage.lua @@ -36,6 +36,7 @@ function DefineMessage(fn, options) 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.") From 6b96c7bd76d601f34b7479f8e826d3bf640cb098 Mon Sep 17 00:00:00 2001 From: Violet Date: Wed, 27 Aug 2025 20:13:49 +0200 Subject: [PATCH 3/3] ternary changes + removing the enum + removing the prints --- src/source/client.ts | 21 ++++++++------------- src/source/message.ts | 21 ++++++--------------- src/source/serdes.ts | 3 +-- src/source/server.ts | 33 ++++++--------------------------- 4 files changed, 21 insertions(+), 57 deletions(-) diff --git a/src/source/client.ts b/src/source/client.ts index 468950e..9473a44 100644 --- a/src/source/client.ts +++ b/src/source/client.ts @@ -15,7 +15,6 @@ import { Character } from "./character"; import { Flags } from "./flags"; import { INVALID_MESSAGE_STR, - MessageContentType, type MessageOptions, ValidateArgs, } from "./message"; @@ -120,12 +119,10 @@ class Client { const character = Character.GetLocalCharacter(); if (!character) return; - let messageClass: UnknownSkill | UnknownStatus | undefined; - if (ContentType === MessageContentType.Skill) { - messageClass = character.GetSkillFromString(Name); - } else if (ContentType === MessageContentType.StatusEffect) { - messageClass = character.GetStatusEffectFromId(Name); - } + const messageClass = + ContentType === "Skill" + ? character.GetSkillFromString(Name) + : character.GetStatusEffectFromId(Name); if (!messageClass) return; const args = RestoreArgs(PackedArgs); @@ -155,12 +152,10 @@ class Client { const character = Character.GetLocalCharacter(); if (!character) return; - let messageClass: UnknownSkill | UnknownStatus | undefined; - if (ContentType === MessageContentType.Skill) { - messageClass = character.GetSkillFromString(Name); - } else if (ContentType === MessageContentType.StatusEffect) { - messageClass = character.GetStatusEffectFromId(Name); - } + const messageClass = + ContentType === "Skill" + ? character.GetSkillFromString(Name) + : character.GetStatusEffectFromId(Name); if (!messageClass) return; const args = RestoreArgs(PackedArgs); diff --git a/src/source/message.ts b/src/source/message.ts index 5754841..ae4f743 100644 --- a/src/source/message.ts +++ b/src/source/message.ts @@ -53,26 +53,17 @@ export function ValidateArgs(validators: t.check[], args: unknown[]) { return true; } -/** @internal @hidden */ -export enum MessageContentType { - Skill = "Skill", - StatusEffect = "StatusEffect", -} - export function Message(Options: T) { return ( ctor: Record, methodName: string, _: TypedPropertyDescriptor>>, ) => { - let contentType: MessageContentType; - if (instanceofConstructor(ctor as never, SkillBase as never)) { - contentType = MessageContentType.Skill; - } else if (instanceofConstructor(ctor as never, StatusEffect as never)) { - contentType = MessageContentType.StatusEffect; - } else { - logError(`${ctor} is not a valid skill/ status effect 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}.`); } @@ -116,7 +107,7 @@ export function Message(Options: T) { const serialized = messageSerializer.serialize([ contentType, - contentType === MessageContentType.Skill ? this.Name : this.GetId(), + contentType === "Skill" ? this.Name : this.GetId(), methodName, ConvertArgs(args), ]); diff --git a/src/source/serdes.ts b/src/source/serdes.ts index 8363502..7d99856 100644 --- a/src/source/serdes.ts +++ b/src/source/serdes.ts @@ -1,5 +1,4 @@ import { createBinarySerializer } from "@rbxts/flamework-binary-serializer"; -import type { MessageContentType } from "exports"; export const skillRequestSerializer = createBinarySerializer< @@ -8,7 +7,7 @@ export const skillRequestSerializer = export const messageSerializer = createBinarySerializer< [ - ContentType: MessageContentType, + ContentType: "Skill" | "StatusEffect", Name: string, MethodName: string, Args: Map, diff --git a/src/source/server.ts b/src/source/server.ts index d83bcee..0bd5ed1 100644 --- a/src/source/server.ts +++ b/src/source/server.ts @@ -15,7 +15,6 @@ import { RestoreArgs } from "./arg-converter"; import { Character, type CharacterData } from "./character"; import { INVALID_MESSAGE_STR, - MessageContentType, type MessageOptions, ValidateArgs, } from "./message"; @@ -157,20 +156,11 @@ class Server { if (!character || character.Player !== Player) return; let messageClass: UnknownSkill | UnknownStatus | undefined; - if (ContentType === MessageContentType.Skill) { + if (ContentType === "Skill") { messageClass = character.GetSkillFromString(Name); - } else if (ContentType === MessageContentType.StatusEffect) { + } else if (ContentType === "StatusEffect") { messageClass = character.GetStatusEffectFromId(Name); } - print( - `Message class: ${messageClass}`, - ContentType, - MessageContentType.StatusEffect, - ContentType === MessageContentType.StatusEffect, - character.GetStatusEffectFromId(Name), - Name, - character.GetAllStatusEffects(), - ); if (!messageClass) return; const args = RestoreArgs(PackedArgs); @@ -202,21 +192,10 @@ class Server { const character = Character.GetCharacterFromInstance(playerCharacter); if (!character || character.Player !== Player) return; - let messageClass: UnknownSkill | UnknownStatus | undefined; - if (ContentType === MessageContentType.Skill) { - messageClass = character.GetSkillFromString(Name); - } else if (ContentType === MessageContentType.StatusEffect) { - messageClass = character.GetStatusEffectFromId(Name); - } - print( - `Message class: ${messageClass}`, - ContentType, - MessageContentType.StatusEffect, - ContentType === MessageContentType.StatusEffect, - character.GetStatusEffectFromId(Name), - Name, - character.GetAllStatusEffects(), - ); + const messageClass = + ContentType === "Skill" + ? character.GetSkillFromString(Name) + : character.GetStatusEffectFromId(Name); if (!messageClass) return; const args = RestoreArgs(PackedArgs);