From 21dca4f0211f6ee8885b303e50910655c29dfb43 Mon Sep 17 00:00:00 2001 From: RetroReul Date: Fri, 13 Mar 2026 22:19:49 +0200 Subject: [PATCH 01/60] some methods --- .../AudioMethods/SpeakerExistsMethod.cs | 23 ++++++ .../Methods/DoorMethods/PryGateMethod.cs | 45 ++++++++++++ .../ElevatorMethods/ElevatorInfoMethod.cs | 50 +++++++++++++ .../ElevatorMethods/SetElevatorTextMethod.cs | 27 +++++++ .../Methods/HealthMethods/RegenerateMethod.cs | 27 +++++++ .../Methods/ItemMethods/ForceEquipMethod.cs | 32 ++++++++ .../MapMethods/CreateRagdolllMethod.cs | 73 +++++++++++++++++++ .../Methods/PlayerMethods/JumpMethod.cs | 31 ++++++++ .../PlayerMethods/SetSpectatabilityMethod.cs | 30 ++++++++ .../PlayerMethods/ShowHitMarkerMethod.cs | 28 +++++++ .../Methods/PlayerMethods/StaminaMethod.cs | 63 ++++++++++++++++ .../ScriptMethods/ScriptExistsMethod.cs | 26 +++++++ .../Methods/TextMethods/ContainsTextMethod.cs | 27 +++++++ 13 files changed, 482 insertions(+) create mode 100644 Code/MethodSystem/Methods/AudioMethods/SpeakerExistsMethod.cs create mode 100644 Code/MethodSystem/Methods/DoorMethods/PryGateMethod.cs create mode 100644 Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs create mode 100644 Code/MethodSystem/Methods/ElevatorMethods/SetElevatorTextMethod.cs create mode 100644 Code/MethodSystem/Methods/HealthMethods/RegenerateMethod.cs create mode 100644 Code/MethodSystem/Methods/ItemMethods/ForceEquipMethod.cs create mode 100644 Code/MethodSystem/Methods/MapMethods/CreateRagdolllMethod.cs create mode 100644 Code/MethodSystem/Methods/PlayerMethods/JumpMethod.cs create mode 100644 Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs create mode 100644 Code/MethodSystem/Methods/PlayerMethods/ShowHitMarkerMethod.cs create mode 100644 Code/MethodSystem/Methods/PlayerMethods/StaminaMethod.cs create mode 100644 Code/MethodSystem/Methods/ScriptMethods/ScriptExistsMethod.cs create mode 100644 Code/MethodSystem/Methods/TextMethods/ContainsTextMethod.cs diff --git a/Code/MethodSystem/Methods/AudioMethods/SpeakerExistsMethod.cs b/Code/MethodSystem/Methods/AudioMethods/SpeakerExistsMethod.cs new file mode 100644 index 00000000..c877e3f1 --- /dev/null +++ b/Code/MethodSystem/Methods/AudioMethods/SpeakerExistsMethod.cs @@ -0,0 +1,23 @@ +using JetBrains.Annotations; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; +using SER.Code.ValueSystem; + +namespace SER.Code.MethodSystem.Methods.AudioMethods; + +[UsedImplicitly] +public class SpeakerExistsMethod : ReturningMethod +{ + public override string Description => "Returns true or false indicating if a speaker with the provided name exists."; + + public override Argument[] ExpectedArguments { get; } = + [ + new TextArgument("speaker name") + ]; + + public override void Execute() + { + ReturnValue = AudioPlayer.TryGet(Args.GetText("speaker name"), out _); + } +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/DoorMethods/PryGateMethod.cs b/Code/MethodSystem/Methods/DoorMethods/PryGateMethod.cs new file mode 100644 index 00000000..ee28f7c6 --- /dev/null +++ b/Code/MethodSystem/Methods/DoorMethods/PryGateMethod.cs @@ -0,0 +1,45 @@ +using JetBrains.Annotations; +using LabApi.Features.Wrappers; +using MapGeneration.Distributors; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; + +namespace SER.Code.MethodSystem.Methods.DoorMethods; + +[UsedImplicitly] +public class PryGateMethod : SynchronousMethod +{ + public override string Description => "Pries a gate."; + + public override Argument[] ExpectedArguments => + [ + new DoorArgument("gate"), + new BoolArgument("should play effects") + { + DefaultValue = new(false, "does not play button effects"), + Description = "Whether to play gate button effects when pried." + } + ]; + + public override void Execute() + { + var door = Args.GetDoor("gate"); + var playEffects = Args.GetBool("should play effects"); + + if (door is not Gate gate) return; + + if (gate.IsOpened || gate.ExactState != 0f || gate.Base.IsBeingPried) return; + + if (playEffects) + { + gate.PlayPermissionDeniedAnimation(); + gate.PlayLockBypassDeniedSound(); + } + + // Spawn pickups in case a player goes through the gate. This should not duplicate pickups if the gate gets opened properly later. + SpawnablesDistributorBase.ServerSpawnForAllDoor(gate.Base); + + gate.Pry(); + } +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs b/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs new file mode 100644 index 00000000..857f7f1b --- /dev/null +++ b/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs @@ -0,0 +1,50 @@ +using JetBrains.Annotations; +using LabApi.Features.Wrappers; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.Exceptions; +using SER.Code.MethodSystem.BaseMethods.Synchronous; +using SER.Code.MethodSystem.MethodDescriptors; +using SER.Code.ValueSystem; + +namespace SER.Code.MethodSystem.Methods.ElevatorMethods; + +[UsedImplicitly] +public class ElevatorInfoMethod : ReturningMethod, IReferenceResolvingMethod +{ + public override string? Description => IReferenceResolvingMethod.Desc.Get(this); + public override Argument[] ExpectedArguments => + [ + new ReferenceArgument("elevator"), + new OptionsArgument("info", options: + [ + new("name"), + new("group"), + new("isReady"), + new("isGoingUp"), + new("currentSequence"), + new("allDoorsLockedReason"), + new("anyDoorLockedReason"), + new("isAdminLocked") + ]) + ]; + public override void Execute() + { + var elevator = Args.GetReference("elevator"); + ReturnValue = Args.GetOption("info") switch + { + "name" => new StaticTextValue(elevator.Base.name), + "group" => new StaticTextValue(elevator.Group.ToString()), + "isready" => new BoolValue(elevator.IsReady), + "isgoingup" => new BoolValue(elevator.GoingUp), + "currentsequence" => new StaticTextValue(elevator.CurrentSequence.ToString()), + "alldoorslockedreason" => new StaticTextValue(elevator.AllDoorsLockedReason.ToString()), + "anydoorlockedreason" => new StaticTextValue(elevator.AnyDoorLockedReason.ToString()), + "isadminlocked" => new BoolValue(elevator.DynamicAdminLock), + _ => throw new ScriptRuntimeError(this, "out of range") + }; + } + + public override TypeOfValue Returns => new TypesOfValue([typeof(TextValue), typeof(BoolValue)]); + public Type ResolvesReference => typeof(Elevator); +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ElevatorMethods/SetElevatorTextMethod.cs b/Code/MethodSystem/Methods/ElevatorMethods/SetElevatorTextMethod.cs new file mode 100644 index 00000000..5089bf36 --- /dev/null +++ b/Code/MethodSystem/Methods/ElevatorMethods/SetElevatorTextMethod.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using LabApi.Features.Wrappers; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; + +namespace SER.Code.MethodSystem.Methods.ElevatorMethods; + +[UsedImplicitly] +public class SetElevatorTextMethod : SynchronousMethod +{ + public override string Description => "Changes the text on the elevator panels between LCZ and HCZ."; + + public override Argument[] ExpectedArguments => + [ + new TextArgument("new text") + { + DefaultValue = new(string.Empty, "Resets the text to it's original value."), + Description = "An empty value will reset the text." + } + ]; + + public override void Execute() + { + Decontamination.ElevatorsText = Args.GetText("text"); + } +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/HealthMethods/RegenerateMethod.cs b/Code/MethodSystem/Methods/HealthMethods/RegenerateMethod.cs new file mode 100644 index 00000000..8979ae29 --- /dev/null +++ b/Code/MethodSystem/Methods/HealthMethods/RegenerateMethod.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; + +namespace SER.Code.MethodSystem.Methods.HealthMethods; + +[UsedImplicitly] +public class RegenerateMethod : SynchronousMethod +{ + public override string Description => "Adds health regeneration to players."; + + public override Argument[] ExpectedArguments => + [ + new PlayersArgument("players"), + new FloatArgument("regeneration rate"), + new FloatArgument("regeneration duration") + ]; + + public override void Execute() + { + var players = Args.GetPlayers("players"); + var regenerationRate = Args.GetFloat("regeneration rate"); + var regenerationDuration = Args.GetFloat("regeneration duration"); + players.ForEach(plr => plr.AddRegeneration(regenerationRate, regenerationDuration)); + } +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ItemMethods/ForceEquipMethod.cs b/Code/MethodSystem/Methods/ItemMethods/ForceEquipMethod.cs new file mode 100644 index 00000000..bc95f6be --- /dev/null +++ b/Code/MethodSystem/Methods/ItemMethods/ForceEquipMethod.cs @@ -0,0 +1,32 @@ +using JetBrains.Annotations; +using LabApi.Features.Wrappers; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; + +namespace SER.Code.MethodSystem.Methods.ItemMethods; + +[UsedImplicitly] +public class ForceEquipMethod : SynchronousMethod +{ + public override string Description => "Forces players to equip a provided item."; + + public override Argument[] ExpectedArguments => + [ + new PlayersArgument("players"), + new EnumArgument("item type") + { DefaultValue = new(ItemType.None, "Un-equip held item.") } + ]; + + public override void Execute() + { + var players = Args.GetPlayers("players"); + var itemType = Args.GetEnum("item type"); + + players.ForEach(plr => + { + var item = itemType != ItemType.None ? Item.Get(plr.Inventory.UserInventory.Items.FirstOrDefault(x => x.Value.ItemTypeId == itemType).Value) : null; + plr.CurrentItem = item; + }); + } +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/MapMethods/CreateRagdolllMethod.cs b/Code/MethodSystem/Methods/MapMethods/CreateRagdolllMethod.cs new file mode 100644 index 00000000..3bf97398 --- /dev/null +++ b/Code/MethodSystem/Methods/MapMethods/CreateRagdolllMethod.cs @@ -0,0 +1,73 @@ +using JetBrains.Annotations; +using LabApi.Features.Wrappers; +using PlayerRoles; +using PlayerStatsSystem; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.Exceptions; +using SER.Code.MethodSystem.BaseMethods.Synchronous; +using SER.Code.MethodSystem.MethodDescriptors; +using SER.Code.ValueSystem; +using UnityEngine; + +namespace SER.Code.MethodSystem.Methods.MapMethods; + +[UsedImplicitly] +public class SpawnRagdollMethod : SynchronousMethod, ICanError +{ + public override string Description => "Spawns a ragdoll."; + + public override Argument[] ExpectedArguments => + [ + new EnumArgument("role"), + new TextArgument("name"), + new ReferenceArgument("position"), + new ReferenceArgument("scale") + { DefaultValue = new(null, "default size") }, + new ReferenceArgument("rotation") + { DefaultValue = new(Quaternion.identity, "default rotation") }, + new AnyValueArgument("damage handler") + { + DefaultValue = new(new CustomReasonDamageHandler(""), "damage reason will be blank"), + Description = $"Accepts a {nameof(TextValue)} or a {nameof(DamageHandlerBase)} reference." + }, + ]; + + public override void Execute() + { + var role = Args.GetEnum("role"); + var name = Args.GetText("name"); + var position = Args.GetReference("position"); + var scale = Args.GetReference("scale"); + var rotation = Args.GetReference("rotation"); + var value = Args.GetAnyValue("damage handler"); + + DamageHandlerBase? damageHandler = null; + + switch (value) + { + case ReferenceValue referenceValue: + { + if (referenceValue.Value is DamageHandlerBase handler) + damageHandler = handler; + else + throw new ScriptRuntimeError(this, ErrorReasons[1]); + break; + } + case TextValue textValue: + damageHandler = new CustomReasonDamageHandler(textValue.StringRep); + break; + } + + if (damageHandler is null) + throw new ScriptRuntimeError(this, ErrorReasons[0]); + + Ragdoll.SpawnRagdoll(role, position, rotation, damageHandler, name, scale); + } + + public string[] ErrorReasons => + [ + $"Damage handler value must be a {nameof(DamageHandlerBase)} reference or a {nameof(TextValue)}.", + $"The provided reference value was not a {nameof(DamageHandlerBase)} reference." + ]; +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PlayerMethods/JumpMethod.cs b/Code/MethodSystem/Methods/PlayerMethods/JumpMethod.cs new file mode 100644 index 00000000..d348632d --- /dev/null +++ b/Code/MethodSystem/Methods/PlayerMethods/JumpMethod.cs @@ -0,0 +1,31 @@ +using JetBrains.Annotations; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; +using SER.Code.MethodSystem.MethodDescriptors; + +namespace SER.Code.MethodSystem.Methods.PlayerMethods; + +[UsedImplicitly] +public class JumpMethod : SynchronousMethod, IAdditionalDescription +{ + public override string Description => + "Makes players jump (with modifiable jump strength)."; + + public override Argument[] ExpectedArguments { get; } = + [ + new PlayersArgument("players"), + new FloatArgument("jump strength") + { DefaultValue = new(4.9f, "default jump strength") } + ]; + + public override void Execute() + { + var players = Args.GetPlayers("players"); + var jumpStrength = Args.GetFloat("jump strength"); + + players.ForEach(plr => plr.Jump(jumpStrength)); + } + + public string AdditionalDescription => "This also works for players in the air. Allowing for mid-air jumps."; +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs b/Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs new file mode 100644 index 00000000..3ca512b9 --- /dev/null +++ b/Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs @@ -0,0 +1,30 @@ +using JetBrains.Annotations; +using PlayerRoles.Spectating; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; + +namespace SER.Code.MethodSystem.Methods.PlayerMethods; + +[UsedImplicitly] +public class SetSpectatabilityMethod : SynchronousMethod +{ + public override string Description => "Sets spectatability for players."; + + public override Argument[] ExpectedArguments { get; } = + [ + new PlayersArgument("players"), + new BoolArgument("is spectatable") + ]; + + public override void Execute() + { + var players = Args.GetPlayers("players"); + var isSpectatable = Args.GetBool("is spectatable"); + + players.ForEach(player => + { + SpectatableVisibilityManager.SetHidden(player.ReferenceHub, isSpectatable); + }); + } +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PlayerMethods/ShowHitMarkerMethod.cs b/Code/MethodSystem/Methods/PlayerMethods/ShowHitMarkerMethod.cs new file mode 100644 index 00000000..7f070a3b --- /dev/null +++ b/Code/MethodSystem/Methods/PlayerMethods/ShowHitMarkerMethod.cs @@ -0,0 +1,28 @@ +using JetBrains.Annotations; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; + +namespace SER.Code.MethodSystem.Methods.PlayerMethods; + +[UsedImplicitly] +public class ShowHitMarkerMethod : SynchronousMethod +{ + public override string Description => + "Shows a hit marker to players."; + + public override Argument[] ExpectedArguments { get; } = + [ + new PlayersArgument("players"), + new FloatArgument("hitmarker size") + { DefaultValue = new(1f, "default size") } + ]; + + public override void Execute() + { + var players = Args.GetPlayers("players"); + var size = Args.GetFloat("hitmarker size"); + + players.ForEach(plr => plr.SendHitMarker(size)); + } +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PlayerMethods/StaminaMethod.cs b/Code/MethodSystem/Methods/PlayerMethods/StaminaMethod.cs new file mode 100644 index 00000000..ed63fbf4 --- /dev/null +++ b/Code/MethodSystem/Methods/PlayerMethods/StaminaMethod.cs @@ -0,0 +1,63 @@ +using JetBrains.Annotations; +using PlayerRoles.FirstPersonControl; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; + +namespace SER.Code.MethodSystem.Methods.PlayerMethods; + +[UsedImplicitly] +public class StaminaMethod : SynchronousMethod +{ + public override string Description => "Control a player's stamina."; + + public override Argument[] ExpectedArguments => + [ + new OptionsArgument("options", options: + [ + new("add"), + new("remove"), + new("set"), + ]), + new PlayersArgument("players"), + new FloatArgument("stamina value", 0f, 1f) + { + Description = "Stamina is valued from 0 to 1. 0 meaning an empty stamina bar and 1 meaning a full stamina bar." + }, + new BoolArgument("delay stamina regen") + { + Description = "Stops stamina regeneration for a short duration after applying new stamina value, just like at the end of a sprint.", + DefaultValue = new(true, "will delay stamina regeneration for a second when new stamina value is applied.") + } + ]; + + public override void Execute() + { + var players = Args.GetPlayers("players"); + var staminaValue = Args.GetFloat("stamina value"); + + players.ForEach(plr => + { + if (plr?.RoleBase is not IFpcRole currentRole) return; + var newStamina = 0f; + switch (Args.GetOption("options")) + { + case "add": + newStamina = plr.StaminaRemaining + staminaValue; + if (newStamina > 1) newStamina = 1; + break; + case "remove": + newStamina = plr.StaminaRemaining - staminaValue; + if (newStamina < 0) newStamina = 0; + break; + case "set": + newStamina = staminaValue; + break; + } + + plr.StaminaRemaining = newStamina; + if (Args.GetBool("delay stamina regen")) + currentRole.FpcModule.StateProcessor._regenStopwatch.Restart(); + }); + } +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ScriptMethods/ScriptExistsMethod.cs b/Code/MethodSystem/Methods/ScriptMethods/ScriptExistsMethod.cs new file mode 100644 index 00000000..8d40ebbf --- /dev/null +++ b/Code/MethodSystem/Methods/ScriptMethods/ScriptExistsMethod.cs @@ -0,0 +1,26 @@ +using JetBrains.Annotations; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; +using SER.Code.ValueSystem; + +namespace SER.Code.MethodSystem.Methods.ScriptMethods; + +[UsedImplicitly] +public class ScriptExistsMethod : ReturningMethod +{ + public override string Description => "Returns true or false indicating if a script with the provided name exists."; + + public override Argument[] ExpectedArguments => + [ + new TextArgument("script name") + ]; + + public override void Execute() + { + var scriptName = Args.GetText("script name"); + ReturnValue = new BoolValue(FileSystem.FileSystem.RegisteredScriptPaths.Any(p => Path.GetFileNameWithoutExtension(p) == scriptName)); + } + + public override TypeOfValue Returns => new SingleTypeOfValue(typeof(BoolValue)); +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/TextMethods/ContainsTextMethod.cs b/Code/MethodSystem/Methods/TextMethods/ContainsTextMethod.cs new file mode 100644 index 00000000..04334167 --- /dev/null +++ b/Code/MethodSystem/Methods/TextMethods/ContainsTextMethod.cs @@ -0,0 +1,27 @@ +using JetBrains.Annotations; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; +using SER.Code.ValueSystem; + +namespace SER.Code.MethodSystem.Methods.TextMethods; + +[UsedImplicitly] +public class ContainsTextMethod : ReturningMethod +{ + public override string Description => "Returns true or false indicating if the provided text contains a provided value."; + + public override Argument[] ExpectedArguments => + [ + new TextArgument("text"), + new TextArgument("text to check for"), + ]; + public override void Execute() + { + var stringToCheck = Args.GetText("text"); + var substringToCheck = Args.GetText("text to check for"); + ReturnValue = new BoolValue(stringToCheck.Contains(substringToCheck)); + } + + public override TypeOfValue Returns => new SingleTypeOfValue(typeof(BoolValue)); +} \ No newline at end of file From 86d610deae6155a2ed2c0b793127a65650ab8857 Mon Sep 17 00:00:00 2001 From: RetroReul Date: Fri, 13 Mar 2026 23:31:49 +0200 Subject: [PATCH 02/60] fixed some oopsies --- Code/Exceptions/RetroReulFuckedUpException.cs | 12 ++++++++++++ .../Methods/ElevatorMethods/ElevatorInfoMethod.cs | 9 ++++++--- .../Methods/ElevatorMethods/SetElevatorTextMethod.cs | 8 +++++--- .../Methods/PlayerMethods/StaminaMethod.cs | 6 +++--- 4 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 Code/Exceptions/RetroReulFuckedUpException.cs diff --git a/Code/Exceptions/RetroReulFuckedUpException.cs b/Code/Exceptions/RetroReulFuckedUpException.cs new file mode 100644 index 00000000..14b9ad8f --- /dev/null +++ b/Code/Exceptions/RetroReulFuckedUpException.cs @@ -0,0 +1,12 @@ +namespace SER.Code.Exceptions; + +public class RetroReulFuckedUpException : DeveloperFuckedUpException +{ + public RetroReulFuckedUpException() : base("retroreul") + { + } + + public RetroReulFuckedUpException(string msg) : base("retroreul", msg) + { + } +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs b/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs index 857f7f1b..ad0110f4 100644 --- a/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs +++ b/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs @@ -12,7 +12,7 @@ namespace SER.Code.MethodSystem.Methods.ElevatorMethods; [UsedImplicitly] public class ElevatorInfoMethod : ReturningMethod, IReferenceResolvingMethod { - public override string? Description => IReferenceResolvingMethod.Desc.Get(this); + public override string Description => IReferenceResolvingMethod.Desc.Get(this); public override Argument[] ExpectedArguments => [ new ReferenceArgument("elevator"), @@ -41,10 +41,13 @@ public override void Execute() "alldoorslockedreason" => new StaticTextValue(elevator.AllDoorsLockedReason.ToString()), "anydoorlockedreason" => new StaticTextValue(elevator.AnyDoorLockedReason.ToString()), "isadminlocked" => new BoolValue(elevator.DynamicAdminLock), - _ => throw new ScriptRuntimeError(this, "out of range") + _ => throw new RetroReulFuckedUpException() }; } - public override TypeOfValue Returns => new TypesOfValue([typeof(TextValue), typeof(BoolValue)]); + public override TypeOfValue Returns => new TypesOfValue([ + typeof(TextValue), + typeof(BoolValue) + ]); public Type ResolvesReference => typeof(Elevator); } \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ElevatorMethods/SetElevatorTextMethod.cs b/Code/MethodSystem/Methods/ElevatorMethods/SetElevatorTextMethod.cs index 5089bf36..33793e84 100644 --- a/Code/MethodSystem/Methods/ElevatorMethods/SetElevatorTextMethod.cs +++ b/Code/MethodSystem/Methods/ElevatorMethods/SetElevatorTextMethod.cs @@ -3,20 +3,20 @@ using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.MethodSystem.BaseMethods.Synchronous; +using SER.Code.MethodSystem.MethodDescriptors; namespace SER.Code.MethodSystem.Methods.ElevatorMethods; [UsedImplicitly] -public class SetElevatorTextMethod : SynchronousMethod +public class SetElevatorTextMethod : SynchronousMethod, IAdditionalDescription { public override string Description => "Changes the text on the elevator panels between LCZ and HCZ."; public override Argument[] ExpectedArguments => [ - new TextArgument("new text") + new TextArgument("text") { DefaultValue = new(string.Empty, "Resets the text to it's original value."), - Description = "An empty value will reset the text." } ]; @@ -24,4 +24,6 @@ public override void Execute() { Decontamination.ElevatorsText = Args.GetText("text"); } + + public string AdditionalDescription => "An empty text value will reset the elevator panel text to it's original value."; } \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PlayerMethods/StaminaMethod.cs b/Code/MethodSystem/Methods/PlayerMethods/StaminaMethod.cs index ed63fbf4..9e97f965 100644 --- a/Code/MethodSystem/Methods/PlayerMethods/StaminaMethod.cs +++ b/Code/MethodSystem/Methods/PlayerMethods/StaminaMethod.cs @@ -9,7 +9,7 @@ namespace SER.Code.MethodSystem.Methods.PlayerMethods; [UsedImplicitly] public class StaminaMethod : SynchronousMethod { - public override string Description => "Control a player's stamina."; + public override string Description => "Control the stamina of players."; public override Argument[] ExpectedArguments => [ @@ -44,11 +44,11 @@ public override void Execute() { case "add": newStamina = plr.StaminaRemaining + staminaValue; - if (newStamina > 1) newStamina = 1; + if (newStamina > 1f) newStamina = 1f; break; case "remove": newStamina = plr.StaminaRemaining - staminaValue; - if (newStamina < 0) newStamina = 0; + if (newStamina < 0f) newStamina = 0f; break; case "set": newStamina = staminaValue; From bff908c221579a81f3cd73ac859e73489459e92d Mon Sep 17 00:00:00 2001 From: RetroReul Date: Fri, 13 Mar 2026 23:49:16 +0200 Subject: [PATCH 03/60] effect category + new methods (includes a way for non-exiled users to take advantage of effects via events) --- .../EffectMethods/ClearEffectMethod.cs | 38 ++++++++++++++ .../Methods/EffectMethods/EffectInfoMethod.cs | 50 +++++++++++++++++++ .../GiveEffectMethod.cs | 2 +- .../Methods/EffectMethods/HasEffectMethod.cs | 39 +++++++++++++++ 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs create mode 100644 Code/MethodSystem/Methods/EffectMethods/EffectInfoMethod.cs rename Code/MethodSystem/Methods/{PlayerMethods => EffectMethods}/GiveEffectMethod.cs (96%) create mode 100644 Code/MethodSystem/Methods/EffectMethods/HasEffectMethod.cs diff --git a/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs b/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs new file mode 100644 index 00000000..f274fe91 --- /dev/null +++ b/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs @@ -0,0 +1,38 @@ +using Exiled.API.Enums; +using Exiled.API.Features; +using JetBrains.Annotations; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.Helpers; +using SER.Code.MethodSystem.BaseMethods.Synchronous; +using SER.Code.MethodSystem.MethodDescriptors; +using SER.Code.MethodSystem.Structures; + +namespace SER.Code.MethodSystem.Methods.EffectMethods; + +[UsedImplicitly] +public class ClearEffectMethod : SynchronousMethod, IDependOnFramework, IAdditionalDescription +{ + public override string Description => "Removes the provided status effect from players."; + + public override Argument[] ExpectedArguments => + [ + new PlayersArgument("players"), + new EnumArgument("effect type") + { DefaultValue = new (EffectType.None, "Removes all status effects") }, + ]; + + public override void Execute() + { + var players = Args.GetPlayers("players"); + var effectType = Args.GetEnum("effect type"); + + if (effectType == EffectType.None) + players.ForEach(plr => Player.Get(plr).DisableAllEffects()); + else + players.ForEach(plr => Player.Get(plr).DisableEffect(effectType)); + } + + public FrameworkBridge.Type DependsOn => FrameworkBridge.Type.Exiled; + public string AdditionalDescription => $"Using the effect type {EffectType.None} will remove all status effects."; +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/EffectMethods/EffectInfoMethod.cs b/Code/MethodSystem/Methods/EffectMethods/EffectInfoMethod.cs new file mode 100644 index 00000000..7c70bebb --- /dev/null +++ b/Code/MethodSystem/Methods/EffectMethods/EffectInfoMethod.cs @@ -0,0 +1,50 @@ +using CustomPlayerEffects; +using JetBrains.Annotations; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.Exceptions; +using SER.Code.MethodSystem.BaseMethods.Synchronous; +using SER.Code.MethodSystem.MethodDescriptors; +using SER.Code.ValueSystem; + +namespace SER.Code.MethodSystem.Methods.EffectMethods; + +[UsedImplicitly] +public class EffectInfoMethod : LiteralValueReturningMethod, IReferenceResolvingMethod +{ + public override string Description => IReferenceResolvingMethod.Desc.Get(this); + + public override Argument[] ExpectedArguments => + [ + new ReferenceArgument("effect"), + new OptionsArgument("info", options: + [ + new("name"), + new("duration"), + new("intensity"), + new("classification"), + new("timeLeft") + ]) + ]; + public override void Execute() + { + var effect = Args.GetReference("effect"); + ReturnValue = Args.GetOption("info") switch + { + "name" => new StaticTextValue(effect.name), + "duration" => new DurationValue(TimeSpan.FromSeconds(effect.Duration)), + "intensity" => new NumberValue(effect.Intensity), + "classification" => new StaticTextValue(effect.Classification.ToString()), + "timeleft" => new DurationValue(TimeSpan.FromSeconds(effect.TimeLeft)), + _ => throw new RetroReulFuckedUpException() + }; + } + + public override TypeOfValue LiteralReturnTypes => new TypesOfValue([ + typeof(TextValue), + typeof(NumberValue), + typeof(DurationValue) + ]); + + public Type ResolvesReference => typeof(StatusEffectBase); +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PlayerMethods/GiveEffectMethod.cs b/Code/MethodSystem/Methods/EffectMethods/GiveEffectMethod.cs similarity index 96% rename from Code/MethodSystem/Methods/PlayerMethods/GiveEffectMethod.cs rename to Code/MethodSystem/Methods/EffectMethods/GiveEffectMethod.cs index a386d914..8f282371 100644 --- a/Code/MethodSystem/Methods/PlayerMethods/GiveEffectMethod.cs +++ b/Code/MethodSystem/Methods/EffectMethods/GiveEffectMethod.cs @@ -7,7 +7,7 @@ using SER.Code.MethodSystem.BaseMethods.Synchronous; using SER.Code.MethodSystem.Structures; -namespace SER.Code.MethodSystem.Methods.PlayerMethods; +namespace SER.Code.MethodSystem.Methods.EffectMethods; [UsedImplicitly] public class GiveEffectMethod : SynchronousMethod, IDependOnFramework diff --git a/Code/MethodSystem/Methods/EffectMethods/HasEffectMethod.cs b/Code/MethodSystem/Methods/EffectMethods/HasEffectMethod.cs new file mode 100644 index 00000000..fcbd2844 --- /dev/null +++ b/Code/MethodSystem/Methods/EffectMethods/HasEffectMethod.cs @@ -0,0 +1,39 @@ +using Exiled.API.Enums; +using Exiled.API.Features; +using JetBrains.Annotations; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.Helpers; +using SER.Code.MethodSystem.BaseMethods.Synchronous; +using SER.Code.MethodSystem.Structures; +using SER.Code.ValueSystem; + +namespace SER.Code.MethodSystem.Methods.EffectMethods; + +[UsedImplicitly] +public class HasEffectMethod : LiteralValueReturningMethod, IDependOnFramework +{ + public override TypeOfValue LiteralReturnTypes => new SingleTypeOfValue(typeof(BoolValue)); + + public override string Description => "Returns true or false indicating if the player has the provided effect."; + + public override Argument[] ExpectedArguments => + [ + new PlayerArgument("player"), + new EnumArgument("effect type") + ]; + + public override void Execute() + { + var player = Args.GetPlayer("player"); + var effectType = Args.GetEnum("effect type"); + + if (!Player.Get(player).TryGetEffect(effectType, out var effect)) + ReturnValue = new BoolValue(false); + + // this feels kinda stupid, but you never know... + ReturnValue = new BoolValue(effect.IsEnabled); + } + + public FrameworkBridge.Type DependsOn => FrameworkBridge.Type.Exiled; +} \ No newline at end of file From b9d824a3765ae0179769ba0e0ab368ebc7dd53a1 Mon Sep 17 00:00:00 2001 From: RetroReul Date: Sat, 14 Mar 2026 00:12:31 +0200 Subject: [PATCH 04/60] new player properties --- .../ExpressionTokens/PlayerExpressionToken.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Code/TokenSystem/Tokens/ExpressionTokens/PlayerExpressionToken.cs b/Code/TokenSystem/Tokens/ExpressionTokens/PlayerExpressionToken.cs index dc00afe6..938967bf 100644 --- a/Code/TokenSystem/Tokens/ExpressionTokens/PlayerExpressionToken.cs +++ b/Code/TokenSystem/Tokens/ExpressionTokens/PlayerExpressionToken.cs @@ -1,6 +1,8 @@ using LabApi.Features.Wrappers; using PlayerRoles; +using PlayerRoles.FirstPersonControl; using PlayerRoles.PlayableScps.Scp079; +using Respawning.NamingRules; using SER.Code.ArgumentSystem.Arguments; using SER.Code.Extensions; using SER.Code.Helpers.ResultSystem; @@ -67,6 +69,16 @@ public enum PlayerProperty RelativeZ, IsNpc, IsDummy, + IsSpeaking, + IsSpectatable, + IsJumping, + IsGrounded, + Stamina, + MovementState, + RoleColor, + LifeId, + UnitId, + Unit, } public abstract class Info @@ -177,7 +189,17 @@ public class Info(Func handler, string? description) : Info [PlayerProperty.RelativeY] = new Info(plr => (decimal)plr.RelativeRoomPosition().y, "Returns the player's y relative to the current room or 0 if in no room"), [PlayerProperty.RelativeZ] = new Info(plr => (decimal)plr.RelativeRoomPosition().z, "Returns the player's z relative to the current room or 0 if in no room"), [PlayerProperty.IsNpc] = new Info(plr => plr.IsNpc, "True if it's a player without any client connected to it"), - [PlayerProperty.IsDummy] = new Info(plr => plr.IsDummy, null) + [PlayerProperty.IsDummy] = new Info(plr => plr.IsDummy, null), + [PlayerProperty.IsSpeaking] = new Info(plr => plr.IsSpeaking, null), + [PlayerProperty.IsSpectatable] = new Info(plr => plr.IsSpectatable, null), + [PlayerProperty.IsJumping] = new Info(plr => plr.RoleBase is IFpcRole currentRole && currentRole.FpcModule.Motor.JumpController.IsJumping, null), + [PlayerProperty.IsGrounded] = new Info(plr => plr.ReferenceHub.IsGrounded(), null), + [PlayerProperty.Stamina] = new Info(plr => (decimal)plr.StaminaRemaining, "Returns the player's remaining stamina."), + [PlayerProperty.MovementState] = new Info(plr => plr.RoleBase is IFpcRole currentRole ? currentRole.FpcModule.CurrentMovementState.ToString().ToStaticTextValue() : new("None"), "Returns the player's movement state or 'None' if the player is not a first-person role."), + [PlayerProperty.RoleColor] = new Info(plr => plr.RoleBase.RoleColor.ToHex().ToStaticTextValue(), "Returns the hex value of the player's role color."), + [PlayerProperty.LifeId] = new Info(plr => plr.LifeId, null), + [PlayerProperty.UnitId] = new Info(plr => (decimal)plr.UnitId, null), + [PlayerProperty.Unit] = new Info(plr => NamingRulesManager.ClientFetchReceived(plr.Team, plr.UnitId).ToStaticTextValue(), "Returns the player's unit (e.g FOXTROT-03) if player is NTF or Facility Guard, otherwise returns an empty text value."), }; protected override IParseResult InternalParse(BaseToken[] tokens) From ab0eef16671e853690f7264599a0cb4ecce9785d Mon Sep 17 00:00:00 2001 From: RetroReul Date: Sat, 14 Mar 2026 02:13:44 +0200 Subject: [PATCH 05/60] minor changes --- .../Methods/EffectMethods/ClearEffectMethod.cs | 13 ++++++------- .../Methods/ElevatorMethods/ElevatorInfoMethod.cs | 4 ++-- .../Methods/ItemMethods/ForceEquipMethod.cs | 4 +++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs b/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs index f274fe91..81512a5c 100644 --- a/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs +++ b/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs @@ -11,7 +11,7 @@ namespace SER.Code.MethodSystem.Methods.EffectMethods; [UsedImplicitly] -public class ClearEffectMethod : SynchronousMethod, IDependOnFramework, IAdditionalDescription +public class ClearEffectMethod : SynchronousMethod, IDependOnFramework { public override string Description => "Removes the provided status effect from players."; @@ -19,20 +19,19 @@ public class ClearEffectMethod : SynchronousMethod, IDependOnFramework, IAdditio [ new PlayersArgument("players"), new EnumArgument("effect type") - { DefaultValue = new (EffectType.None, "Removes all status effects") }, + { DefaultValue = new (null, "Removes all status effects") }, ]; public override void Execute() { var players = Args.GetPlayers("players"); - var effectType = Args.GetEnum("effect type"); + var effectType = Args.GetNullableEnum("effect type"); - if (effectType == EffectType.None) - players.ForEach(plr => Player.Get(plr).DisableAllEffects()); + if (effectType.HasValue) + players.ForEach(plr => Player.Get(plr).DisableEffect(effectType.Value)); else - players.ForEach(plr => Player.Get(plr).DisableEffect(effectType)); + players.ForEach(plr => Player.Get(plr).DisableAllEffects()); } public FrameworkBridge.Type DependsOn => FrameworkBridge.Type.Exiled; - public string AdditionalDescription => $"Using the effect type {EffectType.None} will remove all status effects."; } \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs b/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs index ad0110f4..2056786a 100644 --- a/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs +++ b/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs @@ -10,7 +10,7 @@ namespace SER.Code.MethodSystem.Methods.ElevatorMethods; [UsedImplicitly] -public class ElevatorInfoMethod : ReturningMethod, IReferenceResolvingMethod +public class ElevatorInfoMethod : LiteralValueReturningMethod, IReferenceResolvingMethod { public override string Description => IReferenceResolvingMethod.Desc.Get(this); public override Argument[] ExpectedArguments => @@ -45,7 +45,7 @@ public override void Execute() }; } - public override TypeOfValue Returns => new TypesOfValue([ + public override TypeOfValue LiteralReturnTypes => new TypesOfValue([ typeof(TextValue), typeof(BoolValue) ]); diff --git a/Code/MethodSystem/Methods/ItemMethods/ForceEquipMethod.cs b/Code/MethodSystem/Methods/ItemMethods/ForceEquipMethod.cs index bc95f6be..bbf537f4 100644 --- a/Code/MethodSystem/Methods/ItemMethods/ForceEquipMethod.cs +++ b/Code/MethodSystem/Methods/ItemMethods/ForceEquipMethod.cs @@ -25,7 +25,9 @@ public override void Execute() players.ForEach(plr => { - var item = itemType != ItemType.None ? Item.Get(plr.Inventory.UserInventory.Items.FirstOrDefault(x => x.Value.ItemTypeId == itemType).Value) : null; + var item = itemType != ItemType.None + ? Item.Get(plr.Inventory.UserInventory.Items.FirstOrDefault(x => x.Value.ItemTypeId == itemType).Value) + : null; plr.CurrentItem = item; }); } From 09019724493a73278213ba3fcf54443870155d74 Mon Sep 17 00:00:00 2001 From: RetroReul Date: Sat, 14 Mar 2026 02:18:07 +0200 Subject: [PATCH 06/60] fixed really big oopsies --- ...gdolllMethod.cs => CreateRagdollMethod.cs} | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) rename Code/MethodSystem/Methods/MapMethods/{CreateRagdolllMethod.cs => CreateRagdollMethod.cs} (54%) diff --git a/Code/MethodSystem/Methods/MapMethods/CreateRagdolllMethod.cs b/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs similarity index 54% rename from Code/MethodSystem/Methods/MapMethods/CreateRagdolllMethod.cs rename to Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs index 3bf97398..b2171dc9 100644 --- a/Code/MethodSystem/Methods/MapMethods/CreateRagdolllMethod.cs +++ b/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs @@ -1,6 +1,7 @@ using JetBrains.Annotations; using LabApi.Features.Wrappers; using PlayerRoles; +using PlayerRoles.Ragdolls; using PlayerStatsSystem; using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; @@ -21,14 +22,26 @@ public class SpawnRagdollMethod : SynchronousMethod, ICanError [ new EnumArgument("role"), new TextArgument("name"), - new ReferenceArgument("position"), - new ReferenceArgument("scale") - { DefaultValue = new(null, "default size") }, - new ReferenceArgument("rotation") - { DefaultValue = new(Quaternion.identity, "default rotation") }, + new FloatArgument("x position"), + new FloatArgument("y position"), + new FloatArgument("z position"), + new FloatArgument("x size") + { DefaultValue = new(null, "default role x scale") }, + new FloatArgument("y size") + { DefaultValue = new(null, "default role y scale") }, + new FloatArgument("z size") + { DefaultValue = new(null, "default role z scale") }, + new FloatArgument("x rotation") + { DefaultValue = new(0f, null) }, + new FloatArgument("y rotation") + { DefaultValue = new(0f, null) }, + new FloatArgument("z rotation") + { DefaultValue = new(0f, null) }, + new FloatArgument("w rotation") + { DefaultValue = new(1f, null) }, new AnyValueArgument("damage handler") { - DefaultValue = new(new CustomReasonDamageHandler(""), "damage reason will be blank"), + DefaultValue = new(new CustomReasonDamageHandler(""), "Damage reason will be blank"), Description = $"Accepts a {nameof(TextValue)} or a {nameof(DamageHandlerBase)} reference." }, ]; @@ -37,11 +50,28 @@ public override void Execute() { var role = Args.GetEnum("role"); var name = Args.GetText("name"); - var position = Args.GetReference("position"); - var scale = Args.GetReference("scale"); - var rotation = Args.GetReference("rotation"); + + var xPosition = Args.GetFloat("x position"); + var yPosition = Args.GetFloat("y position"); + var zPosition = Args.GetFloat("z position"); + + var defaultSize = RagdollManager.GetDefaultScale(role); + + var xSize = Args.GetNullableFloat("x size") ?? defaultSize.x; + var ySize = Args.GetNullableFloat("y size") ?? defaultSize.y; + var zSize = Args.GetNullableFloat("z size") ?? defaultSize.z; + + var xRotation = Args.GetFloat("x rotation"); + var yRotation = Args.GetFloat("y rotation"); + var zRotation = Args.GetFloat("z rotation"); + var wRotation = Args.GetFloat("w rotation"); + var value = Args.GetAnyValue("damage handler"); + var position = new Vector3(xPosition, yPosition, zPosition); + var rotation = new Quaternion(xRotation, yRotation, zRotation, wRotation); + var size = new Vector3(xSize, ySize, zSize); + DamageHandlerBase? damageHandler = null; switch (value) @@ -62,7 +92,7 @@ public override void Execute() if (damageHandler is null) throw new ScriptRuntimeError(this, ErrorReasons[0]); - Ragdoll.SpawnRagdoll(role, position, rotation, damageHandler, name, scale); + Ragdoll.SpawnRagdoll(role, position, rotation, damageHandler, name, size); } public string[] ErrorReasons => From 94791896252574d90bbfb7c371b98c880d070405 Mon Sep 17 00:00:00 2001 From: RetroReul Date: Sat, 14 Mar 2026 02:20:32 +0200 Subject: [PATCH 07/60] minor change to CreateRagdollMethod.cs --- .../MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs b/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs index b2171dc9..ba9798a0 100644 --- a/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs +++ b/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs @@ -72,7 +72,7 @@ public override void Execute() var rotation = new Quaternion(xRotation, yRotation, zRotation, wRotation); var size = new Vector3(xSize, ySize, zSize); - DamageHandlerBase? damageHandler = null; + DamageHandlerBase? damageHandler; switch (value) { @@ -87,11 +87,10 @@ public override void Execute() case TextValue textValue: damageHandler = new CustomReasonDamageHandler(textValue.StringRep); break; + default: + throw new ScriptRuntimeError(this, ErrorReasons[0]); } - if (damageHandler is null) - throw new ScriptRuntimeError(this, ErrorReasons[0]); - Ragdoll.SpawnRagdoll(role, position, rotation, damageHandler, name, size); } From 316740273cfd2c4b0ed5b3595400d806c00fd0a5 Mon Sep 17 00:00:00 2001 From: RetroReul Date: Sat, 14 Mar 2026 03:26:05 +0200 Subject: [PATCH 08/60] extra info options for DoorInfoMethod.cs and ServerInfoMethod.cs --- Code/MethodSystem/Methods/DoorMethods/DoorInfoMethod.cs | 4 ++++ Code/MethodSystem/Methods/ServerMethods/ServerInfoMethod.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/Code/MethodSystem/Methods/DoorMethods/DoorInfoMethod.cs b/Code/MethodSystem/Methods/DoorMethods/DoorInfoMethod.cs index 519bfb2c..a313be31 100644 --- a/Code/MethodSystem/Methods/DoorMethods/DoorInfoMethod.cs +++ b/Code/MethodSystem/Methods/DoorMethods/DoorInfoMethod.cs @@ -33,6 +33,8 @@ public class DoorInfoMethod : LiteralValueReturningMethod, IReferenceResolvingMe "isClosed", "isLocked", "isUnlocked", + "isGate", + "isCheckpoint", Option.Enum("name"), "unityName", "remainingHealth", @@ -54,6 +56,8 @@ public override void Execute() "isclosed" => new BoolValue(!door.IsOpened), "islocked" => new BoolValue(door.IsLocked), "isunlocked" => new BoolValue(!door.IsLocked), + "isgate" => new BoolValue(door is Gate), + "ischeckpoint" => new BoolValue(door is CheckpointDoor), "remaininghealth" => new NumberValue(door is BreakableDoor bDoor ? (decimal)bDoor.Health : -1), "maxhealth" => new NumberValue(door is BreakableDoor bDoor ? (decimal)bDoor.MaxHealth : -1), "permissions" => new StaticTextValue(door.Permissions.ToString()), diff --git a/Code/MethodSystem/Methods/ServerMethods/ServerInfoMethod.cs b/Code/MethodSystem/Methods/ServerMethods/ServerInfoMethod.cs index 9c396d02..9e7dda62 100644 --- a/Code/MethodSystem/Methods/ServerMethods/ServerInfoMethod.cs +++ b/Code/MethodSystem/Methods/ServerMethods/ServerInfoMethod.cs @@ -19,8 +19,10 @@ public class ServerInfoMethod : ReturningMethod "ip", "port", "name", + "playerCount", "maxPlayers", "tps", + "maxTps", "isVerified") ]; @@ -37,8 +39,10 @@ public override void Execute() "ip" => new StaticTextValue(Server.IpAddress), "port" => new NumberValue(Server.Port), "name" => new StaticTextValue(Server.ServerListName), + "playercount" => new NumberValue(Server.PlayerCount), "maxplayers" => new NumberValue(Server.MaxPlayers), "tps" => new NumberValue((decimal)Server.Tps), + "maxtps" => new NumberValue(Server.MaxTps), "isverified" => new BoolValue(CustomNetworkManager.IsVerified), _ => throw new TosoksFuckedUpException("out of order") }; From 3b10022c1e3b96502985ca887f419c614df97747 Mon Sep 17 00:00:00 2001 From: RetroReul Date: Sat, 14 Mar 2026 04:13:07 +0200 Subject: [PATCH 09/60] forgot to rename from SpawnRagdoll to CreateRagdoll --- Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs b/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs index ba9798a0..bbc88fb7 100644 --- a/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs +++ b/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs @@ -14,7 +14,7 @@ namespace SER.Code.MethodSystem.Methods.MapMethods; [UsedImplicitly] -public class SpawnRagdollMethod : SynchronousMethod, ICanError +public class CreateRagdollMethod : SynchronousMethod, ICanError { public override string Description => "Spawns a ragdoll."; From e5da27423d72e9df35e9a17581ba91e1592321e0 Mon Sep 17 00:00:00 2001 From: RetroReul Date: Mon, 16 Mar 2026 19:06:46 +0200 Subject: [PATCH 10/60] SetSpectatabilityMethod.cs made it into base SER :D --- .../PlayerMethods/SetSpectatabilityMethod.cs | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs diff --git a/Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs b/Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs deleted file mode 100644 index 3ca512b9..00000000 --- a/Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs +++ /dev/null @@ -1,30 +0,0 @@ -using JetBrains.Annotations; -using PlayerRoles.Spectating; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.MethodSystem.BaseMethods.Synchronous; - -namespace SER.Code.MethodSystem.Methods.PlayerMethods; - -[UsedImplicitly] -public class SetSpectatabilityMethod : SynchronousMethod -{ - public override string Description => "Sets spectatability for players."; - - public override Argument[] ExpectedArguments { get; } = - [ - new PlayersArgument("players"), - new BoolArgument("is spectatable") - ]; - - public override void Execute() - { - var players = Args.GetPlayers("players"); - var isSpectatable = Args.GetBool("is spectatable"); - - players.ForEach(player => - { - SpectatableVisibilityManager.SetHidden(player.ReferenceHub, isSpectatable); - }); - } -} \ No newline at end of file From a34409d5009f2b8dafde8393ae93c3c7b28f3d9b Mon Sep 17 00:00:00 2001 From: RetroReul Date: Mon, 16 Mar 2026 19:12:40 +0200 Subject: [PATCH 11/60] changes to effect methods to keep up with recent commits --- .../Methods/EffectMethods/ClearEffectMethod.cs | 6 ++++-- .../Methods/EffectMethods/GiveEffectMethod.cs | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs b/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs index 81512a5c..f738cf19 100644 --- a/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs +++ b/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs @@ -28,9 +28,11 @@ public override void Execute() var effectType = Args.GetNullableEnum("effect type"); if (effectType.HasValue) - players.ForEach(plr => Player.Get(plr).DisableEffect(effectType.Value)); + foreach (var plr in players) + Player.Get(plr).DisableEffect(effectType.Value); else - players.ForEach(plr => Player.Get(plr).DisableAllEffects()); + foreach (var plr in players) + Player.Get(plr).DisableAllEffects(); } public FrameworkBridge.Type DependsOn => FrameworkBridge.Type.Exiled; diff --git a/Code/MethodSystem/Methods/EffectMethods/GiveEffectMethod.cs b/Code/MethodSystem/Methods/EffectMethods/GiveEffectMethod.cs index 8f282371..3381a761 100644 --- a/Code/MethodSystem/Methods/EffectMethods/GiveEffectMethod.cs +++ b/Code/MethodSystem/Methods/EffectMethods/GiveEffectMethod.cs @@ -13,7 +13,7 @@ namespace SER.Code.MethodSystem.Methods.EffectMethods; public class GiveEffectMethod : SynchronousMethod, IDependOnFramework { public FrameworkBridge.Type DependsOn => FrameworkBridge.Type.Exiled; - + public override string Description => "Adds a provided effect to a player."; public override Argument[] ExpectedArguments => @@ -40,9 +40,11 @@ public override void Execute() var effectType = Args.GetEnum("effect type"); var duration = (float)Args.GetDuration("duration").TotalSeconds; var intensity = (byte)Args.GetInt("intensity"); - - players.ForEach(plr - => Player.Get(plr).EnableEffect(effectType, intensity, duration) - ); + var addDurationIfActive = Args.GetBool("add duration if active"); + + foreach (var plr in players) + { + Player.Get(plr).EnableEffect(effectType, intensity, duration, addDurationIfActive); + } } } \ No newline at end of file From 5e35f3b33171744a5a3cea6ce20bac164f8fdce6 Mon Sep 17 00:00:00 2001 From: RetroReul Date: Tue, 31 Mar 2026 23:44:41 +0300 Subject: [PATCH 12/60] new hitmarker stuff and info method removals for 0.16 --- .../EffectMethods/ClearEffectMethod.cs | 1 - .../Methods/EffectMethods/EffectInfoMethod.cs | 50 ----------------- .../ElevatorMethods/ElevatorInfoMethod.cs | 53 ------------------- .../PlayerMethods/ShowHitMarkerMethod.cs | 10 +++- SER.csproj | 4 +- 5 files changed, 10 insertions(+), 108 deletions(-) delete mode 100644 Code/MethodSystem/Methods/EffectMethods/EffectInfoMethod.cs delete mode 100644 Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs diff --git a/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs b/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs index f738cf19..4b67f139 100644 --- a/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs +++ b/Code/MethodSystem/Methods/EffectMethods/ClearEffectMethod.cs @@ -5,7 +5,6 @@ using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.Helpers; using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; using SER.Code.MethodSystem.Structures; namespace SER.Code.MethodSystem.Methods.EffectMethods; diff --git a/Code/MethodSystem/Methods/EffectMethods/EffectInfoMethod.cs b/Code/MethodSystem/Methods/EffectMethods/EffectInfoMethod.cs deleted file mode 100644 index 7c70bebb..00000000 --- a/Code/MethodSystem/Methods/EffectMethods/EffectInfoMethod.cs +++ /dev/null @@ -1,50 +0,0 @@ -using CustomPlayerEffects; -using JetBrains.Annotations; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.Exceptions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.EffectMethods; - -[UsedImplicitly] -public class EffectInfoMethod : LiteralValueReturningMethod, IReferenceResolvingMethod -{ - public override string Description => IReferenceResolvingMethod.Desc.Get(this); - - public override Argument[] ExpectedArguments => - [ - new ReferenceArgument("effect"), - new OptionsArgument("info", options: - [ - new("name"), - new("duration"), - new("intensity"), - new("classification"), - new("timeLeft") - ]) - ]; - public override void Execute() - { - var effect = Args.GetReference("effect"); - ReturnValue = Args.GetOption("info") switch - { - "name" => new StaticTextValue(effect.name), - "duration" => new DurationValue(TimeSpan.FromSeconds(effect.Duration)), - "intensity" => new NumberValue(effect.Intensity), - "classification" => new StaticTextValue(effect.Classification.ToString()), - "timeleft" => new DurationValue(TimeSpan.FromSeconds(effect.TimeLeft)), - _ => throw new RetroReulFuckedUpException() - }; - } - - public override TypeOfValue LiteralReturnTypes => new TypesOfValue([ - typeof(TextValue), - typeof(NumberValue), - typeof(DurationValue) - ]); - - public Type ResolvesReference => typeof(StatusEffectBase); -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs b/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs deleted file mode 100644 index 2056786a..00000000 --- a/Code/MethodSystem/Methods/ElevatorMethods/ElevatorInfoMethod.cs +++ /dev/null @@ -1,53 +0,0 @@ -using JetBrains.Annotations; -using LabApi.Features.Wrappers; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.Exceptions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.ElevatorMethods; - -[UsedImplicitly] -public class ElevatorInfoMethod : LiteralValueReturningMethod, IReferenceResolvingMethod -{ - public override string Description => IReferenceResolvingMethod.Desc.Get(this); - public override Argument[] ExpectedArguments => - [ - new ReferenceArgument("elevator"), - new OptionsArgument("info", options: - [ - new("name"), - new("group"), - new("isReady"), - new("isGoingUp"), - new("currentSequence"), - new("allDoorsLockedReason"), - new("anyDoorLockedReason"), - new("isAdminLocked") - ]) - ]; - public override void Execute() - { - var elevator = Args.GetReference("elevator"); - ReturnValue = Args.GetOption("info") switch - { - "name" => new StaticTextValue(elevator.Base.name), - "group" => new StaticTextValue(elevator.Group.ToString()), - "isready" => new BoolValue(elevator.IsReady), - "isgoingup" => new BoolValue(elevator.GoingUp), - "currentsequence" => new StaticTextValue(elevator.CurrentSequence.ToString()), - "alldoorslockedreason" => new StaticTextValue(elevator.AllDoorsLockedReason.ToString()), - "anydoorlockedreason" => new StaticTextValue(elevator.AnyDoorLockedReason.ToString()), - "isadminlocked" => new BoolValue(elevator.DynamicAdminLock), - _ => throw new RetroReulFuckedUpException() - }; - } - - public override TypeOfValue LiteralReturnTypes => new TypesOfValue([ - typeof(TextValue), - typeof(BoolValue) - ]); - public Type ResolvesReference => typeof(Elevator); -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PlayerMethods/ShowHitMarkerMethod.cs b/Code/MethodSystem/Methods/PlayerMethods/ShowHitMarkerMethod.cs index 7f070a3b..4a4a2108 100644 --- a/Code/MethodSystem/Methods/PlayerMethods/ShowHitMarkerMethod.cs +++ b/Code/MethodSystem/Methods/PlayerMethods/ShowHitMarkerMethod.cs @@ -15,14 +15,20 @@ public class ShowHitMarkerMethod : SynchronousMethod [ new PlayersArgument("players"), new FloatArgument("hitmarker size") - { DefaultValue = new(1f, "default size") } + { DefaultValue = new(1f, "Default Size") }, + new BoolArgument("should play audio") + { DefaultValue = new(true, "Hitmarker will make a noise.")}, + new EnumArgument("hitmarker type") + { DefaultValue = new(HitmarkerType.Regular, "Regular Hitmarker")} ]; public override void Execute() { var players = Args.GetPlayers("players"); var size = Args.GetFloat("hitmarker size"); + var playAudio = Args.GetBool("should play audio"); + var hitmarkerType = Args.GetEnum("hitmarker type"); - players.ForEach(plr => plr.SendHitMarker(size)); + players.ForEach(plr => Hitmarker.SendHitmarkerDirectly(plr.ReferenceHub, size, playAudio, hitmarkerType)); } } \ No newline at end of file diff --git a/SER.csproj b/SER.csproj index f2f730f1..0290f230 100644 --- a/SER.csproj +++ b/SER.csproj @@ -107,10 +107,10 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 49c15cdbf7e8e0b30d580999d20ee28fb6781122 Mon Sep 17 00:00:00 2001 From: RetroReul Date: Wed, 1 Apr 2026 01:25:46 +0300 Subject: [PATCH 13/60] fixed damage handler default value error --- Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs b/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs index bbc88fb7..4232d6f5 100644 --- a/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs +++ b/Code/MethodSystem/Methods/MapMethods/CreateRagdollMethod.cs @@ -41,7 +41,7 @@ public class CreateRagdollMethod : SynchronousMethod, ICanError { DefaultValue = new(1f, null) }, new AnyValueArgument("damage handler") { - DefaultValue = new(new CustomReasonDamageHandler(""), "Damage reason will be blank"), + DefaultValue = new(new ReferenceValue(new(" ")), "Damage reason will be blank"), Description = $"Accepts a {nameof(TextValue)} or a {nameof(DamageHandlerBase)} reference." }, ]; From d37b8cba5179b0af22fcea86913b2074df25c663 Mon Sep 17 00:00:00 2001 From: Tosoks67 Date: Wed, 11 Mar 2026 19:05:12 +0100 Subject: [PATCH 14/60] typo fixes --- Code/FlagSystem/Flags/CustomCommandFlag.cs | 34 +++++++++---------- .../ResetGlobalCommandCooldownMethod.cs | 2 +- .../ResetPlayerCommandCooldownMethod.cs | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Code/FlagSystem/Flags/CustomCommandFlag.cs b/Code/FlagSystem/Flags/CustomCommandFlag.cs index 394c2323..ab680115 100644 --- a/Code/FlagSystem/Flags/CustomCommandFlag.cs +++ b/Code/FlagSystem/Flags/CustomCommandFlag.cs @@ -263,24 +263,24 @@ public bool Execute(ArraySegment arguments, ICommandSender sender, out s } public required string Command { get; init; } - public string[] Aliases { get; set; } = []; + public string[] Aliases => []; public string Description { get; set; } = ""; public string[] Usage { get; set; } = []; public ConsoleType ConsoleTypes = ConsoleType.Server; public string[] NeededRanks = []; - public string? InvalidRankMessage = null; + public string? InvalidRankMessage; public string[] NeededPermissions = []; - public string? NoPermissionMessage = null; + public string? NoPermissionMessage; public TimeSpan PlayerCooldown = TimeSpan.Zero; - public Dictionary NextEligableDateForPlayer { get; } = []; - public string? OnCooldownMessage = null; + public Dictionary NextEligibleDateForPlayer { get; } = []; + public string? OnCooldownMessage; public TimeSpan GlobalCooldown = TimeSpan.Zero; - public DateTime? NextEligableDateForGlobal = null; - public string? OnGlobalCooldownMessage = null; + public DateTime? NextEligibleDateForGlobal; + public string? OnGlobalCooldownMessage; public string GetHelp(ArraySegment arguments) { @@ -302,23 +302,23 @@ public static Result RunAttachedScript(CustomCommand cmd, ScriptExecutor sender, if (cmd.GlobalCooldown > TimeSpan.Zero) { - if (cmd.NextEligableDateForGlobal is { } nextEligableDate - && nextEligableDate > DateTime.UtcNow) + if (cmd.NextEligibleDateForGlobal is { } nextEligibleDate + && nextEligibleDate > DateTime.UtcNow) { var timeRemaining = Math.Round( - (nextEligableDate - DateTime.UtcNow).TotalSeconds, + (nextEligibleDate - DateTime.UtcNow).TotalSeconds, MidpointRounding.AwayFromZero ); if (cmd.OnGlobalCooldownMessage is not null) { - return cmd.OnGlobalCooldownMessage.Replace("%time%", timeRemaining.ToString()); + return cmd.OnGlobalCooldownMessage.Replace("%time%", timeRemaining.ToString("F1")); } return $"This command is on cooldown! You will be able to use this command in {timeRemaining} seconds."; } - cmd.NextEligableDateForGlobal = DateTime.UtcNow + cmd.GlobalCooldown; + cmd.NextEligibleDateForGlobal = DateTime.UtcNow + cmd.GlobalCooldown; } if (!ScriptCommands.TryGetValue(cmd, out var flag)) @@ -414,28 +414,28 @@ public static Result RunAttachedScript(CustomCommand cmd, ScriptExecutor sender, return null; } - if (cmd.NextEligableDateForPlayer.TryGetValue(plr, out var nextEligableDate) && nextEligableDate > DateTime.UtcNow) + if (cmd.NextEligibleDateForPlayer.TryGetValue(plr, out var nextEligibleDate) && nextEligibleDate > DateTime.UtcNow) { var timeRemaining = Math.Round( - (nextEligableDate - DateTime.UtcNow).TotalSeconds, + (nextEligibleDate - DateTime.UtcNow).TotalSeconds, MidpointRounding.AwayFromZero ); if (cmd.OnCooldownMessage is not null) { - return cmd.OnCooldownMessage.Replace("%time%", timeRemaining.ToString()); + return cmd.OnCooldownMessage.Replace("%time%", timeRemaining.ToString("F1")); } return $"You are on cooldown! You will be able to use this command in {timeRemaining} seconds."; } - cmd.NextEligableDateForPlayer[plr] = DateTime.UtcNow + cmd.PlayerCooldown; + cmd.NextEligibleDateForPlayer[plr] = DateTime.UtcNow + cmd.PlayerCooldown; return null; } private Result AddArguments(string[] args) { - bool onlyOptionals = false; + var onlyOptionals = false; foreach (var arg in args) { var markedOptional = arg.Last() == '?'; diff --git a/Code/MethodSystem/Methods/CommandMethods/ResetGlobalCommandCooldownMethod.cs b/Code/MethodSystem/Methods/CommandMethods/ResetGlobalCommandCooldownMethod.cs index 607b5f05..8b0c391c 100644 --- a/Code/MethodSystem/Methods/CommandMethods/ResetGlobalCommandCooldownMethod.cs +++ b/Code/MethodSystem/Methods/CommandMethods/ResetGlobalCommandCooldownMethod.cs @@ -18,6 +18,6 @@ public class ResetGlobalCommandCooldownMethod : SynchronousMethod public override void Execute() { - Args.GetReference("command").NextEligableDateForGlobal = null; + Args.GetReference("command").NextEligibleDateForGlobal = null; } } \ No newline at end of file diff --git a/Code/MethodSystem/Methods/CommandMethods/ResetPlayerCommandCooldownMethod.cs b/Code/MethodSystem/Methods/CommandMethods/ResetPlayerCommandCooldownMethod.cs index 06d0cfdf..07f9014d 100644 --- a/Code/MethodSystem/Methods/CommandMethods/ResetPlayerCommandCooldownMethod.cs +++ b/Code/MethodSystem/Methods/CommandMethods/ResetPlayerCommandCooldownMethod.cs @@ -21,7 +21,7 @@ public override void Execute() { Args .GetReference("command") - .NextEligableDateForPlayer + .NextEligibleDateForPlayer .Remove(Args.GetPlayer("player")); } } \ No newline at end of file From 00678ec13aeba83fbc66d30691928b61f0d3300e Mon Sep 17 00:00:00 2001 From: Tosoks67 Date: Wed, 11 Mar 2026 19:05:26 +0100 Subject: [PATCH 15/60] new IsFloor property to SymbolToken --- Code/TokenSystem/Tokens/SymbolToken.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Code/TokenSystem/Tokens/SymbolToken.cs b/Code/TokenSystem/Tokens/SymbolToken.cs index 17072423..fccbcc65 100644 --- a/Code/TokenSystem/Tokens/SymbolToken.cs +++ b/Code/TokenSystem/Tokens/SymbolToken.cs @@ -5,6 +5,7 @@ namespace SER.Code.TokenSystem.Tokens; public class SymbolToken : BaseToken { public bool IsJoker => RawRep == "*"; + public bool IsFloor => RawRep == "_"; protected override IParseResult InternalParse(Script scr) { From 49b3abc83adc16b1c07f7217da420e60289116f5 Mon Sep 17 00:00:00 2001 From: Tosoks67 Date: Wed, 11 Mar 2026 19:25:06 +0100 Subject: [PATCH 16/60] type and stackTrace optional variables for on_error and also removal of ExceptionInfo as it's no longer needed --- .../Contexts/Control/OnErrorStatement.cs | 89 ++++++++++++++++--- .../ScriptMethods/ExceptionInfoMethod.cs | 40 --------- 2 files changed, 75 insertions(+), 54 deletions(-) delete mode 100644 Code/MethodSystem/Methods/ScriptMethods/ExceptionInfoMethod.cs diff --git a/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs b/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs index 1e665dae..dbe768e9 100644 --- a/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs +++ b/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs @@ -8,6 +8,7 @@ using SER.Code.TokenSystem.Tokens.VariableTokens; using SER.Code.ValueSystem; using SER.Code.VariableSystem.Bases; +using SER.Code.VariableSystem.Variables; namespace SER.Code.ContextSystem.Contexts.Control; @@ -15,8 +16,7 @@ namespace SER.Code.ContextSystem.Contexts.Control; public class OnErrorStatement : StatementContext, IStatementExtender, IKeywordContext, IAcceptOptionalVariableDefinitionsContext { public string KeywordName => "on_error"; - public string Description => - $"Catches an exception thrown inside of a {typeof(AttemptStatement).FriendlyTypeName(true)}"; + public string Description => "Catches an exception thrown inside of an 'attempt' statement."; public string[] Arguments => []; public string Example => @@ -25,11 +25,22 @@ public class OnErrorStatement : StatementContext, IStatementExtender, IKeywordCo attempt Print {CollectionFetch &collection 2} # ERROR: there's nothing at index 2 + + Print "Hello, world!" + # ^ won't get executed because 'attempt' skips the remaining code + # inside of it if an error was made on_error - with $message + with $message $type $stackTrace # this will print the error message Print "Error: {$message}" + + # In 90% of situations $type will be ScriptRuntimeError + Print "Type of error: {$type}" + + # This just shows where in the internal code (not the script) the error was made + # (basically to allow devs to know where in the code they may have fucked up) + Print "Stack trace: {$stackTrace}" end """; @@ -46,7 +57,9 @@ public Exception? Exception field = value; } } - private LiteralVariableToken? _variableToken; + private VariableToken? _messageVariableToken; + private VariableToken? _typeVariableToken; + private VariableToken? _stackTraceVariableToken; public override TryAddTokenRes TryAddToken(BaseToken token) { @@ -60,31 +73,79 @@ public override Result VerifyCurrentState() public Result SetOptionalVariables(params VariableToken[] variableTokens) { - if (variableTokens.Length > 1) - return $"Too many arguments provided for {FriendlyName}, only 1 is allowed."; + if (variableTokens.Length > 3) + return $"Too many arguments provided for {FriendlyName}, only up to 3 are allowed."; + + var errorMsg = "Provided variable '{0}' cannot be used for an " + FriendlyName + + $", as it's not a {typeof(LiteralVariable).FriendlyTypeName()}"; - if (variableTokens.First() is not LiteralVariableToken token) + var messageToken = variableTokens.FirstOrDefault(); + switch (messageToken) { - return $"{FriendlyName} only accepts a literal variable."; + case null: + return true; + case LiteralVariableToken: + _messageVariableToken = messageToken; + break; + default: + return string.Format(errorMsg, messageToken.RawRepr); + } + + var typeToken = variableTokens.Skip(1).FirstOrDefault(); + switch (typeToken) + { + case null: + return true; + case LiteralVariableToken: + _typeVariableToken = typeToken; + break; + default: + return string.Format(errorMsg, typeToken.RawRepr); + } + + var stackTraceToken = variableTokens.Skip(2).FirstOrDefault(); + switch (stackTraceToken) + { + case null: + break; // it's gonna return true either way + case LiteralVariableToken: + _stackTraceVariableToken = stackTraceToken; + break; + default: + return string.Format(errorMsg, stackTraceToken.RawRepr); } - _variableToken = token; return true; } protected override IEnumerator Execute() { - Variable? variable = null; - if (_variableToken is not null) + Variable? messageVariable = null; + Variable? typeVariable = null; + Variable? stackTraceVariable = null; + + if (_messageVariableToken is not null) + { + messageVariable = Variable.Create(_messageVariableToken.Name, Value.Parse(Exception!.Message, Script)); + Script.AddLocalVariable(messageVariable); + } + if (_typeVariableToken is not null) + { + typeVariable = Variable.Create(_typeVariableToken.Name, Value.Parse(Exception!.GetType().AccurateName, Script)); + Script.AddLocalVariable(typeVariable); + } + if (_stackTraceVariableToken is not null) { - variable = Variable.Create(_variableToken.Name, Value.Parse(Exception!.Message, Script)); - Script.AddLocalVariable(variable); + stackTraceVariable = Variable.Create(_stackTraceVariableToken.Name, Value.Parse(Exception!.StackTrace, Script)); + Script.AddLocalVariable(stackTraceVariable); } var coro = RunChildren(); while (coro.MoveNext()) yield return coro.Current; - if (variable is not null) Script.RemoveLocalVariable(variable); + if (messageVariable is not null) Script.RemoveLocalVariable(messageVariable); + if (typeVariable is not null) Script.RemoveLocalVariable(typeVariable); + if (stackTraceVariable is not null) Script.RemoveLocalVariable(stackTraceVariable); } } \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ScriptMethods/ExceptionInfoMethod.cs b/Code/MethodSystem/Methods/ScriptMethods/ExceptionInfoMethod.cs deleted file mode 100644 index 101f1b8a..00000000 --- a/Code/MethodSystem/Methods/ScriptMethods/ExceptionInfoMethod.cs +++ /dev/null @@ -1,40 +0,0 @@ -using JetBrains.Annotations; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.Exceptions; -using SER.Code.Extensions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.ScriptMethods; - -[UsedImplicitly] -public class ExceptionInfoMethod : ReturningMethod, IReferenceResolvingMethod -{ - public override string Description => IReferenceResolvingMethod.Desc.Get(this); - - public Type ResolvesReference => typeof(Exception); - - public override Argument[] ExpectedArguments { get; } = - [ - new ReferenceArgument("exception reference"), - new OptionsArgument("info to get", - "type", - "message", - "stackTrace") - ]; - - public override void Execute() - { - var exception = Args.GetReference("exception reference"); - - ReturnValue = new StaticTextValue(Args.GetOption("info to get") switch - { - "type" => exception.GetType().AccurateName, - "message" => exception.Message, - "stacktrace" => exception.StackTrace, - _ => throw new TosoksFuckedUpException("out of order") - }); - } -} \ No newline at end of file From cbb9b06bc4923c058864fa896b3427aee3a15117 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Fri, 13 Mar 2026 16:26:54 +0100 Subject: [PATCH 17/60] make SER friendlier for external programs --- Code/EventSystem/EventHandler.cs | 2 +- Code/FlagSystem/Flags/Flag.cs | 5 ++-- .../Commands/HelpSystem/DocsProvider.cs | 25 ++++++++++--------- .../Plugin/Commands/HelpSystem/HelpCommand.cs | 2 +- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Code/EventSystem/EventHandler.cs b/Code/EventSystem/EventHandler.cs index 4dd69365..d3f32718 100644 --- a/Code/EventSystem/EventHandler.cs +++ b/Code/EventSystem/EventHandler.cs @@ -23,7 +23,7 @@ public static class EventHandler private static readonly HashSet DisabledEvents = []; public static List AvailableEvents = []; - internal static void Initialize() + public static void Initialize() { AvailableEvents = typeof(PluginLoader).Assembly.GetTypes() .Where(t => t.FullName?.Equals($"LabApi.Events.Handlers.{t.Name}") is true) diff --git a/Code/FlagSystem/Flags/Flag.cs b/Code/FlagSystem/Flags/Flag.cs index a463deca..1985dd30 100644 --- a/Code/FlagSystem/Flags/Flag.cs +++ b/Code/FlagSystem/Flags/Flag.cs @@ -42,9 +42,10 @@ public virtual void OnParsingComplete() public static Dictionary FlagInfos = []; - internal static void RegisterFlags() + public static void RegisterFlags(Assembly? ass = null) { - FlagInfos = GetRegisteredFlags(Assembly.GetExecutingAssembly()); + ass ??= Assembly.GetExecutingAssembly(); + FlagInfos = GetRegisteredFlags(ass); } // ReSharper disable once UnusedMember.Global diff --git a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs index f5a316cc..2c714610 100644 --- a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs +++ b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs @@ -36,6 +36,7 @@ public static class DocsProvider public static bool GetGeneralOutput(string arg, out string response) { + arg = arg.ToLower(); if (Enum.TryParse(arg, true, out HelpOption option)) { if (!GeneralOptions.TryGetValue(option, out var func)) @@ -125,7 +126,7 @@ public static string GetOptionsList() """; } - private static string GetKeywordInfo(string name, string description, string[] arguments, bool isStatement, Type type) + public static string GetKeywordInfo(string name, string description, string[] arguments, bool isStatement, Type type) { var usageInfo = Activator.CreateInstance(type) is IStatementExtender extender ? $""" @@ -177,7 +178,7 @@ This statement can ONLY be used after a statement supporting the "{extender.Exte """; } - private static string GetKeywordHelpPage() + public static string GetKeywordHelpPage() { return """ @@ -198,7 +199,7 @@ These statements control how the methods inside their body are executed. .JoinStrings("\n"); } - private static string GetFlagHelpPage() + public static string GetFlagHelpPage() { var flags = Flag.FlagInfos.Keys .Select(f => $"> {f}") @@ -220,7 +221,7 @@ Flags should be used at the top of the script. """; } - private static string GetFlagInfo(string flagName) + public static string GetFlagInfo(string flagName) { var flag = Flag.FlagInfos[flagName].CreateInstance(); @@ -268,7 +269,7 @@ private static string GetFlagInfo(string flagName) """; } - private static string GetEventInfo(EventInfo ev) + public static string GetEventInfo(EventInfo ev) { var variables = EventSystem.EventHandler.GetMimicVariables(ev); var msg = variables.Count > 0 @@ -286,7 +287,7 @@ private static string GetEventInfo(EventInfo ev) """; } - private static string GetReferenceResolvingMethodsHelpPage() + public static string GetReferenceResolvingMethodsHelpPage() { var referenceResolvingMethods = MethodIndex.GetMethods() .Where(m => m is IReferenceResolvingMethod) @@ -308,7 +309,7 @@ This help option is just here to make it easier to find said methods. """; } - private static string GetEventsHelpPage() + public static string GetEventsHelpPage() { var sb = new StringBuilder(); @@ -337,7 +338,7 @@ Some events have additional information attached to them in a form of variables. """; } - private static string GetEnum(Type enumType) + public static string GetEnum(Type enumType) { return $""" @@ -353,7 +354,7 @@ private static string GetEnum(Type enumType) """; } - private static string GetEnumHelpPage() + public static string GetEnumHelpPage() { return $""" @@ -386,7 +387,7 @@ private static Dictionary> MethodsByCategory() return methodsByCategory; } - private static string GetMethodList() + public static string GetMethodList() { const string retsSuffix = " [rets]"; @@ -418,7 +419,7 @@ private static string GetMethodList() return sb.ToString(); } - private static string GetVariableList() + public static string GetVariableList() { var allVars = VariableIndex.GlobalVariables .Where(var => var is PredefinedPlayerVariable) @@ -441,7 +442,7 @@ private static string GetVariableList() return sb.ToString(); } - private static string GetMethodHelp(Method method) + public static string GetMethodHelp(Method method) { var sb = new StringBuilder($"=== {method.Name} ===\n"); diff --git a/Code/Plugin/Commands/HelpSystem/HelpCommand.cs b/Code/Plugin/Commands/HelpSystem/HelpCommand.cs index 7fd2d595..fc8d2cc2 100644 --- a/Code/Plugin/Commands/HelpSystem/HelpCommand.cs +++ b/Code/Plugin/Commands/HelpSystem/HelpCommand.cs @@ -14,7 +14,7 @@ public bool Execute(ArraySegment arguments, ICommandSender _, out string { if (arguments.Count > 0) { - return DocsProvider.GetGeneralOutput(arguments.First().ToLower(), out response); + return DocsProvider.GetGeneralOutput(arguments.First(), out response); } response = DocsProvider.GetOptionsList(); From 9d66c6666eac2939cb73ed2726a94322bdb947f2 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sat, 14 Mar 2026 08:24:07 +0100 Subject: [PATCH 18/60] add literal color value --- .../ArgumentSystem/Arguments/ColorArgument.cs | 46 ++++------------ Code/TokenSystem/Tokenizer.cs | 2 + .../Tokens/ValueTokens/ColorToken.cs | 54 +++++++++++++++++++ Code/ValueSystem/ColorValue.cs | 8 +++ 4 files changed, 74 insertions(+), 36 deletions(-) create mode 100644 Code/TokenSystem/Tokens/ValueTokens/ColorToken.cs create mode 100644 Code/ValueSystem/ColorValue.cs diff --git a/Code/ArgumentSystem/Arguments/ColorArgument.cs b/Code/ArgumentSystem/Arguments/ColorArgument.cs index 92cc9d65..48d05a0b 100644 --- a/Code/ArgumentSystem/Arguments/ColorArgument.cs +++ b/Code/ArgumentSystem/Arguments/ColorArgument.cs @@ -1,8 +1,10 @@ -using System.Globalization; -using JetBrains.Annotations; +using JetBrains.Annotations; using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.Extensions; using SER.Code.Helpers.ResultSystem; using SER.Code.TokenSystem.Tokens; +using SER.Code.TokenSystem.Tokens.ValueTokens; +using SER.Code.ValueSystem; using UnityEngine; namespace SER.Code.ArgumentSystem.Arguments; @@ -14,44 +16,16 @@ public class ColorArgument(string name) : Argument(name) [UsedImplicitly] public DynamicTryGet GetConvertSolution(BaseToken token) { - if (TryParseColor(token.GetBestTextRepresentation(Script)).WasSuccessful(out var value)) + if (token is ColorToken colorToken) { - return value; + return colorToken.Value.Value; } - return new(() => TryParseColor(token.GetBestTextRepresentation(Script))); - } - - public static TryGet TryParseColor(string value) - { - var initValue = value; - if (value.StartsWith("#")) + if (token.CanReturn(out var get)) { - value = value[1..]; - } - - switch (value.Length) - { - // RRGGBB - case 6 when uint.TryParse(value, NumberStyles.HexNumber, null, out uint hexVal): - { - float r = ((hexVal & 0xFF0000) >> 16) / 255f; - float g = ((hexVal & 0x00FF00) >> 8) / 255f; - float b = (hexVal & 0x0000FF) / 255f; - return new Color(r, g, b, 1f); - } - - // RRGGBBAA - case 8 when uint.TryParse(value, NumberStyles.HexNumber, null, out uint hexVal): - { - float r = ((hexVal & 0xFF000000) >> 24) / 255f; - float g = ((hexVal & 0x00FF0000) >> 16) / 255f; - float b = ((hexVal & 0x0000FF00) >> 8) / 255f; - float a = (hexVal & 0x000000FF) / 255f; - return new Color(r, g, b, a); - } - default: - return $"Invalid color format. Expected RRGGBB (6) or RRGGBBAA (8), got '{initValue}' ({value.Length})."; + return new(get().OnSuccess(cv => cv.Value)); } + + return $"{token} is not a valid color."; } } \ No newline at end of file diff --git a/Code/TokenSystem/Tokenizer.cs b/Code/TokenSystem/Tokenizer.cs index 67264699..fdd695e6 100644 --- a/Code/TokenSystem/Tokenizer.cs +++ b/Code/TokenSystem/Tokenizer.cs @@ -6,6 +6,7 @@ using SER.Code.TokenSystem.Structures; using SER.Code.TokenSystem.Tokens; using SER.Code.TokenSystem.Tokens.ExpressionTokens; +using SER.Code.TokenSystem.Tokens.ValueTokens; using SER.Code.TokenSystem.Tokens.VariableTokens; namespace SER.Code.TokenSystem; @@ -19,6 +20,7 @@ public static class Tokenizer typeof(MethodToken), typeof(FlagToken), typeof(FlagArgumentToken), + typeof(ColorToken), typeof(CommentToken), typeof(SymbolToken), typeof(NumberToken), diff --git a/Code/TokenSystem/Tokens/ValueTokens/ColorToken.cs b/Code/TokenSystem/Tokens/ValueTokens/ColorToken.cs new file mode 100644 index 00000000..e89a258f --- /dev/null +++ b/Code/TokenSystem/Tokens/ValueTokens/ColorToken.cs @@ -0,0 +1,54 @@ +using System.Globalization; +using SER.Code.Helpers.ResultSystem; +using SER.Code.ScriptSystem; +using SER.Code.ValueSystem; +using UnityEngine; + +namespace SER.Code.TokenSystem.Tokens.ValueTokens; + +public class ColorToken : LiteralValueToken +{ + protected override IParseResult InternalParse(Script scr) + { + if (TryParseColor(RawRep).WasSuccessful(out var color)) + { + Value = new ColorValue(color); + return new Success(); + } + + return new Ignore(); + } + + public static TryGet TryParseColor(string value) + { + var initValue = value; + if (value.StartsWith("#")) + { + value = value[1..]; + } + + switch (value.Length) + { + // RRGGBB + case 6 when uint.TryParse(value, NumberStyles.HexNumber, null, out uint hexVal): + { + float r = ((hexVal & 0xFF0000) >> 16) / 255f; + float g = ((hexVal & 0x00FF00) >> 8) / 255f; + float b = (hexVal & 0x0000FF) / 255f; + return new Color(r, g, b, 1f); + } + + // RRGGBBAA + case 8 when uint.TryParse(value, NumberStyles.HexNumber, null, out uint hexVal): + { + float r = ((hexVal & 0xFF000000) >> 24) / 255f; + float g = ((hexVal & 0x00FF0000) >> 16) / 255f; + float b = ((hexVal & 0x0000FF00) >> 8) / 255f; + float a = (hexVal & 0x000000FF) / 255f; + return new Color(r, g, b, a); + } + default: + return $"Invalid color format. Expected RRGGBB (6) or RRGGBBAA (8), got '{initValue}' ({value.Length})."; + } + } +} \ No newline at end of file diff --git a/Code/ValueSystem/ColorValue.cs b/Code/ValueSystem/ColorValue.cs new file mode 100644 index 00000000..fe06d7c1 --- /dev/null +++ b/Code/ValueSystem/ColorValue.cs @@ -0,0 +1,8 @@ +using UnityEngine; + +namespace SER.Code.ValueSystem; + +public class ColorValue(Color color) : LiteralValue(color) +{ + public override string StringRep => Value.ToHex(); +} \ No newline at end of file From 583fdf2e071e1ad3937b59ab7d9ff1ed1d6bc88a Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sat, 14 Mar 2026 08:24:39 +0100 Subject: [PATCH 19/60] move value tokens to seperate folder --- Code/ArgumentSystem/Arguments/FloatArgument.cs | 1 + Code/ArgumentSystem/Arguments/IntArgument.cs | 1 + Code/ArgumentSystem/Arguments/TextArgument.cs | 1 + Code/ContextSystem/Contexts/Control/Loops/RepeatLoop.cs | 1 + .../VariableDefinition/LiteralVariableDefinitionContext.cs | 2 +- Code/FlagSystem/Flags/CustomCommandFlag.cs | 2 +- Code/MethodSystem/Methods/NumberMethods/TryParseNumberMethod.cs | 1 + Code/TokenSystem/Tokens/BaseToken.cs | 1 + Code/TokenSystem/Tokens/{ => ValueTokens}/BoolToken.cs | 2 +- Code/TokenSystem/Tokens/{ => ValueTokens}/DurationToken.cs | 2 +- Code/TokenSystem/Tokens/{ => ValueTokens}/LiteralValueToken.cs | 2 +- Code/TokenSystem/Tokens/{ => ValueTokens}/NumberToken.cs | 2 +- Code/TokenSystem/Tokens/{ => ValueTokens}/TextToken.cs | 2 +- 13 files changed, 13 insertions(+), 7 deletions(-) rename Code/TokenSystem/Tokens/{ => ValueTokens}/BoolToken.cs (87%) rename Code/TokenSystem/Tokens/{ => ValueTokens}/DurationToken.cs (96%) rename Code/TokenSystem/Tokens/{ => ValueTokens}/LiteralValueToken.cs (93%) rename Code/TokenSystem/Tokens/{ => ValueTokens}/NumberToken.cs (91%) rename Code/TokenSystem/Tokens/{ => ValueTokens}/TextToken.cs (93%) diff --git a/Code/ArgumentSystem/Arguments/FloatArgument.cs b/Code/ArgumentSystem/Arguments/FloatArgument.cs index f08a3b84..670afd82 100644 --- a/Code/ArgumentSystem/Arguments/FloatArgument.cs +++ b/Code/ArgumentSystem/Arguments/FloatArgument.cs @@ -3,6 +3,7 @@ using SER.Code.Exceptions; using SER.Code.Helpers.ResultSystem; using SER.Code.TokenSystem.Tokens; +using SER.Code.TokenSystem.Tokens.ValueTokens; using SER.Code.ValueSystem; using Random = UnityEngine.Random; diff --git a/Code/ArgumentSystem/Arguments/IntArgument.cs b/Code/ArgumentSystem/Arguments/IntArgument.cs index 61f782ff..67c1570c 100644 --- a/Code/ArgumentSystem/Arguments/IntArgument.cs +++ b/Code/ArgumentSystem/Arguments/IntArgument.cs @@ -3,6 +3,7 @@ using SER.Code.Exceptions; using SER.Code.Helpers.ResultSystem; using SER.Code.TokenSystem.Tokens; +using SER.Code.TokenSystem.Tokens.ValueTokens; using SER.Code.ValueSystem; namespace SER.Code.ArgumentSystem.Arguments; diff --git a/Code/ArgumentSystem/Arguments/TextArgument.cs b/Code/ArgumentSystem/Arguments/TextArgument.cs index c757e8c3..214d48cc 100644 --- a/Code/ArgumentSystem/Arguments/TextArgument.cs +++ b/Code/ArgumentSystem/Arguments/TextArgument.cs @@ -4,6 +4,7 @@ using SER.Code.Helpers.ResultSystem; using SER.Code.TokenSystem.Tokens; using SER.Code.TokenSystem.Tokens.Interfaces; +using SER.Code.TokenSystem.Tokens.ValueTokens; using SER.Code.ValueSystem; namespace SER.Code.ArgumentSystem.Arguments; diff --git a/Code/ContextSystem/Contexts/Control/Loops/RepeatLoop.cs b/Code/ContextSystem/Contexts/Control/Loops/RepeatLoop.cs index 15ad9827..ce22e1b4 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/RepeatLoop.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/RepeatLoop.cs @@ -6,6 +6,7 @@ using SER.Code.Helpers.ResultSystem; using SER.Code.TokenSystem.Tokens; using SER.Code.TokenSystem.Tokens.Interfaces; +using SER.Code.TokenSystem.Tokens.ValueTokens; using SER.Code.ValueSystem; namespace SER.Code.ContextSystem.Contexts.Control.Loops; diff --git a/Code/ContextSystem/Contexts/VariableDefinition/LiteralVariableDefinitionContext.cs b/Code/ContextSystem/Contexts/VariableDefinition/LiteralVariableDefinitionContext.cs index 5e8c1bd4..6261f6e1 100644 --- a/Code/ContextSystem/Contexts/VariableDefinition/LiteralVariableDefinitionContext.cs +++ b/Code/ContextSystem/Contexts/VariableDefinition/LiteralVariableDefinitionContext.cs @@ -1,4 +1,4 @@ -using SER.Code.TokenSystem.Tokens; +using SER.Code.TokenSystem.Tokens.ValueTokens; using SER.Code.TokenSystem.Tokens.VariableTokens; using SER.Code.ValueSystem; using SER.Code.VariableSystem.Variables; diff --git a/Code/FlagSystem/Flags/CustomCommandFlag.cs b/Code/FlagSystem/Flags/CustomCommandFlag.cs index ab680115..3053bc3f 100644 --- a/Code/FlagSystem/Flags/CustomCommandFlag.cs +++ b/Code/FlagSystem/Flags/CustomCommandFlag.cs @@ -10,7 +10,7 @@ using SER.Code.ScriptSystem; using SER.Code.ScriptSystem.Structures; using SER.Code.TokenSystem; -using SER.Code.TokenSystem.Tokens; +using SER.Code.TokenSystem.Tokens.ValueTokens; using SER.Code.ValueSystem; using SER.Code.VariableSystem.Bases; using SER.Code.VariableSystem.Variables; diff --git a/Code/MethodSystem/Methods/NumberMethods/TryParseNumberMethod.cs b/Code/MethodSystem/Methods/NumberMethods/TryParseNumberMethod.cs index 82bee92b..92c0ae3f 100644 --- a/Code/MethodSystem/Methods/NumberMethods/TryParseNumberMethod.cs +++ b/Code/MethodSystem/Methods/NumberMethods/TryParseNumberMethod.cs @@ -4,6 +4,7 @@ using SER.Code.MethodSystem.BaseMethods.Synchronous; using SER.Code.MethodSystem.Structures; using SER.Code.TokenSystem.Tokens; +using SER.Code.TokenSystem.Tokens.ValueTokens; using SER.Code.ValueSystem; namespace SER.Code.MethodSystem.Methods.NumberMethods; diff --git a/Code/TokenSystem/Tokens/BaseToken.cs b/Code/TokenSystem/Tokens/BaseToken.cs index 2a933ff8..bbfefaf8 100644 --- a/Code/TokenSystem/Tokens/BaseToken.cs +++ b/Code/TokenSystem/Tokens/BaseToken.cs @@ -3,6 +3,7 @@ using SER.Code.ScriptSystem; using SER.Code.TokenSystem.Slices; using SER.Code.TokenSystem.Tokens.Interfaces; +using SER.Code.TokenSystem.Tokens.ValueTokens; using SER.Code.TokenSystem.Tokens.VariableTokens; using SER.Code.ValueSystem; using SER.Code.VariableSystem.Variables; diff --git a/Code/TokenSystem/Tokens/BoolToken.cs b/Code/TokenSystem/Tokens/ValueTokens/BoolToken.cs similarity index 87% rename from Code/TokenSystem/Tokens/BoolToken.cs rename to Code/TokenSystem/Tokens/ValueTokens/BoolToken.cs index 8a81e4cf..ee02422a 100644 --- a/Code/TokenSystem/Tokens/BoolToken.cs +++ b/Code/TokenSystem/Tokens/ValueTokens/BoolToken.cs @@ -1,7 +1,7 @@ using SER.Code.ScriptSystem; using SER.Code.ValueSystem; -namespace SER.Code.TokenSystem.Tokens; +namespace SER.Code.TokenSystem.Tokens.ValueTokens; public class BoolToken : LiteralValueToken { diff --git a/Code/TokenSystem/Tokens/DurationToken.cs b/Code/TokenSystem/Tokens/ValueTokens/DurationToken.cs similarity index 96% rename from Code/TokenSystem/Tokens/DurationToken.cs rename to Code/TokenSystem/Tokens/ValueTokens/DurationToken.cs index 62753b9f..283b923a 100644 --- a/Code/TokenSystem/Tokens/DurationToken.cs +++ b/Code/TokenSystem/Tokens/ValueTokens/DurationToken.cs @@ -2,7 +2,7 @@ using SER.Code.ScriptSystem; using SER.Code.ValueSystem; -namespace SER.Code.TokenSystem.Tokens; +namespace SER.Code.TokenSystem.Tokens.ValueTokens; public class DurationToken : LiteralValueToken { diff --git a/Code/TokenSystem/Tokens/LiteralValueToken.cs b/Code/TokenSystem/Tokens/ValueTokens/LiteralValueToken.cs similarity index 93% rename from Code/TokenSystem/Tokens/LiteralValueToken.cs rename to Code/TokenSystem/Tokens/ValueTokens/LiteralValueToken.cs index 9df9af7f..55ebdf47 100644 --- a/Code/TokenSystem/Tokens/LiteralValueToken.cs +++ b/Code/TokenSystem/Tokens/ValueTokens/LiteralValueToken.cs @@ -4,7 +4,7 @@ using SER.Code.TokenSystem.Tokens.Interfaces; using SER.Code.ValueSystem; -namespace SER.Code.TokenSystem.Tokens; +namespace SER.Code.TokenSystem.Tokens.ValueTokens; public abstract class LiteralValueToken : BaseToken, IValueToken where T : LiteralValue diff --git a/Code/TokenSystem/Tokens/NumberToken.cs b/Code/TokenSystem/Tokens/ValueTokens/NumberToken.cs similarity index 91% rename from Code/TokenSystem/Tokens/NumberToken.cs rename to Code/TokenSystem/Tokens/ValueTokens/NumberToken.cs index 42bd619d..02744e15 100644 --- a/Code/TokenSystem/Tokens/NumberToken.cs +++ b/Code/TokenSystem/Tokens/ValueTokens/NumberToken.cs @@ -1,7 +1,7 @@ using SER.Code.ScriptSystem; using SER.Code.ValueSystem; -namespace SER.Code.TokenSystem.Tokens; +namespace SER.Code.TokenSystem.Tokens.ValueTokens; public class NumberToken : LiteralValueToken { diff --git a/Code/TokenSystem/Tokens/TextToken.cs b/Code/TokenSystem/Tokens/ValueTokens/TextToken.cs similarity index 93% rename from Code/TokenSystem/Tokens/TextToken.cs rename to Code/TokenSystem/Tokens/ValueTokens/TextToken.cs index 72494990..bc34bbd3 100644 --- a/Code/TokenSystem/Tokens/TextToken.cs +++ b/Code/TokenSystem/Tokens/ValueTokens/TextToken.cs @@ -4,7 +4,7 @@ using SER.Code.TokenSystem.Structures; using SER.Code.ValueSystem; -namespace SER.Code.TokenSystem.Tokens; +namespace SER.Code.TokenSystem.Tokens.ValueTokens; public class TextToken : LiteralValueToken { From 556d87a65e18abed8b7757124eb0767c66ee9f2c Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sat, 14 Mar 2026 08:24:46 +0100 Subject: [PATCH 20/60] Update Script.cs --- Code/ScriptSystem/Script.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Code/ScriptSystem/Script.cs b/Code/ScriptSystem/Script.cs index 6531dbe8..79af41a1 100644 --- a/Code/ScriptSystem/Script.cs +++ b/Code/ScriptSystem/Script.cs @@ -1,7 +1,6 @@ using System.Collections.ObjectModel; using JetBrains.Annotations; using LabApi.Features.Wrappers; -using MEC; using SER.Code.ContextSystem; using SER.Code.ContextSystem.BaseContexts; using SER.Code.ContextSystem.Contexts; From 6e58e02507dcdf5c601f40b5b2a6a77d3f294ff5 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sat, 14 Mar 2026 08:25:15 +0100 Subject: [PATCH 21/60] update flag handler to error when duplicate flag --- Code/FlagSystem/ScriptFlagHandler.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Code/FlagSystem/ScriptFlagHandler.cs b/Code/FlagSystem/ScriptFlagHandler.cs index 1524f93b..2a984eb7 100644 --- a/Code/FlagSystem/ScriptFlagHandler.cs +++ b/Code/FlagSystem/ScriptFlagHandler.cs @@ -102,7 +102,7 @@ private static Result HandleFlagArgument(string argName, string[] arguments) private static TryGet HandleFlag(string name, string[] arguments, ScriptName scriptName) { _currentFlag?.OnParsingComplete(); - Result rs = $"Flag '{name}' failed when parsing."; + var rs = $"Flag '{name}' failed when parsing.".AsError(); if (Flag.TryGet(name, scriptName).HasErrored(out var getErr, out var flag)) { @@ -113,6 +113,11 @@ private static TryGet HandleFlag(string name, string[] arguments, ScriptNa { return rs + error; } + + if (ScriptsFlags.TryGetValue(scriptName, out var scriptFlags) && scriptFlags.Any(f => f.Name == flag.Name)) + { + return rs + $"A '{flag.Name}' flag has been already registered once - one script cannot have two of the same flags."; + } ScriptsFlags.AddOrInitListWithKey(scriptName, flag); return _currentFlag = flag; From dfa216f6f1ccb657cb711ef8685c74f7746efe56 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sat, 14 Mar 2026 08:25:19 +0100 Subject: [PATCH 22/60] Create SetSpectatabilityMethod.cs --- .../PlayerMethods/SetSpectatabilityMethod.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs diff --git a/Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs b/Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs new file mode 100644 index 00000000..ce1d7525 --- /dev/null +++ b/Code/MethodSystem/Methods/PlayerMethods/SetSpectatabilityMethod.cs @@ -0,0 +1,29 @@ +using JetBrains.Annotations; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; + +namespace SER.Code.MethodSystem.Methods.PlayerMethods; + +[UsedImplicitly] +public class SetSpectatabilityMethod : SynchronousMethod +{ + public override string Description => "Allows or disallows a player to be spectated."; + + public override Argument[] ExpectedArguments { get; } = + [ + new PlayersArgument("players"), + new BoolArgument("new state") + ]; + + public override void Execute() + { + var players = Args.GetPlayers("players"); + var newState = Args.GetBool("new state"); + + foreach (var player in players) + { + player.IsSpectatable = newState; + } + } +} \ No newline at end of file From de7cc19c0f3feec1b9e25dfcf0506c9670d85c4c Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:49:08 +0100 Subject: [PATCH 23/60] fix functions not returning properly --- Code/ContextSystem/BaseContexts/LoopContext.cs | 2 +- .../BaseContexts/StatementContext.cs | 7 ++++--- Code/ContextSystem/Contexts/FuncStatement.cs | 17 ++--------------- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/Code/ContextSystem/BaseContexts/LoopContext.cs b/Code/ContextSystem/BaseContexts/LoopContext.cs index ba63657c..c04fa942 100644 --- a/Code/ContextSystem/BaseContexts/LoopContext.cs +++ b/Code/ContextSystem/BaseContexts/LoopContext.cs @@ -44,7 +44,7 @@ protected override void OnReceivedControlMessageFromChild(ParentContextControlMe } } - protected override IEnumerator RunChildren() + protected IEnumerator RunChildren() { foreach (var coro in Children .TakeWhile(_ => !ReceivedBreak) diff --git a/Code/ContextSystem/BaseContexts/StatementContext.cs b/Code/ContextSystem/BaseContexts/StatementContext.cs index 59a51d89..545e4361 100644 --- a/Code/ContextSystem/BaseContexts/StatementContext.cs +++ b/Code/ContextSystem/BaseContexts/StatementContext.cs @@ -19,13 +19,14 @@ protected virtual void OnReceivedControlMessageFromChild(ParentContextControlMes ParentContext?.SendControlMessage(msg); } - protected virtual IEnumerator RunChildren() + protected IEnumerator RunChildren(Func? endCond = null) { - foreach (var coro in Children - .Select(child => child.ExecuteBaseContext())) + foreach (var coro in Children.Select(c => c.ExecuteBaseContext())) { + if (endCond?.Invoke() is true) yield break; while (coro.MoveNext()) { + if (endCond?.Invoke() is true) yield break; yield return coro.Current; } } diff --git a/Code/ContextSystem/Contexts/FuncStatement.cs b/Code/ContextSystem/Contexts/FuncStatement.cs index cb07c6fe..94e55a6e 100644 --- a/Code/ContextSystem/Contexts/FuncStatement.cs +++ b/Code/ContextSystem/Contexts/FuncStatement.cs @@ -136,22 +136,9 @@ protected override void OnReceivedControlMessageFromChild(ParentContextControlMe protected override IEnumerator Execute() { - foreach (var coro in Children - .Select(child => child.ExecuteBaseContext()) - ) - { - while (coro.MoveNext()) - { - if (_end) - { - goto Exit; - } - - yield return coro.Current; - } - } + var coro = RunChildren(() => _end); + while (coro.MoveNext()) yield return coro.Current; - Exit: _localVariables.ForEach(v => Script.RemoveLocalVariable(v)); } } \ No newline at end of file From 5d09cfb73c123b77679589c909ba4a0b2d3c82f4 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:49:35 +0100 Subject: [PATCH 24/60] publicize for external usage --- Code/Helpers/FrameworkBridge.cs | 14 +++++++------- Code/MethodSystem/MethodIndex.cs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Code/Helpers/FrameworkBridge.cs b/Code/Helpers/FrameworkBridge.cs index ba2bc1ef..c559f056 100644 --- a/Code/Helpers/FrameworkBridge.cs +++ b/Code/Helpers/FrameworkBridge.cs @@ -8,9 +8,8 @@ namespace SER.Code.Helpers; public class FrameworkBridge { - protected record struct Framework(string Name, Type Type); - - private readonly List _found = []; + public record struct Framework(string Name, Type Type); + public static readonly List Found = []; private readonly List _handles = []; public enum Type @@ -30,6 +29,7 @@ public enum Type public void Load() { + Found.Clear(); foreach (var framework in _frameworks) { _handles.Add(Timing.RunCoroutine(Await(framework))); @@ -39,10 +39,10 @@ public void Load() { Timing.KillCoroutines(_handles.ToArray()); _handles.Clear(); - Logger.Info(_found.Count == 0 + Logger.Info(Found.Count == 0 ? "No supported framework was found, no additional methods were added." - : $"SER has added methods for {_found.Count} supported framework(s): " + - $"{_found.Select(f => f.Type.ToString()).JoinStrings(", ")}" + : $"SER has added methods for {Found.Count} supported framework(s): " + + $"{Found.Select(f => f.Type.ToString()).JoinStrings(", ")}" ); }); } @@ -61,7 +61,7 @@ private IEnumerator Await(Framework framework) } Logger.Debug($"SER found supported framework '{framework.Type}'"); - _found.Add(framework); + Found.Add(framework); MethodIndex.LoadMethodsOfFramework(framework.Type); } diff --git a/Code/MethodSystem/MethodIndex.cs b/Code/MethodSystem/MethodIndex.cs index e68094e7..3249201d 100644 --- a/Code/MethodSystem/MethodIndex.cs +++ b/Code/MethodSystem/MethodIndex.cs @@ -11,7 +11,7 @@ namespace SER.Code.MethodSystem; public static class MethodIndex { public static readonly Dictionary NameToMethodIndex = []; - private static readonly Dictionary> FrameworkDependentMethods = []; + public static readonly Dictionary> FrameworkDependentMethods = []; /// /// Initializes the method index. From 37bd1ef0bbcf3334b849259e83e0aada8a40f275 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:49:53 +0100 Subject: [PATCH 25/60] add help options for methods using frameworks --- .../Commands/HelpSystem/DocsProvider.cs | 66 +++++++++++++++---- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs index 2c714610..a830ac0d 100644 --- a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs +++ b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs @@ -6,6 +6,7 @@ using SER.Code.Exceptions; using SER.Code.Extensions; using SER.Code.FlagSystem.Flags; +using SER.Code.Helpers; using SER.Code.MethodSystem; using SER.Code.MethodSystem.BaseMethods; using SER.Code.MethodSystem.BaseMethods.Interfaces; @@ -86,6 +87,15 @@ public static bool GetGeneralOutput(string arg, out string response) response = GetMethodHelp(method); return true; } + + var outsideMethodKvp = MethodIndex.FrameworkDependentMethods + .Select(kvp => kvp.Value.Select(m => (m, kvp.Key))) + .Flatten() + .FirstOrDefault(kvp => kvp.m.Name.ToLower() == arg); + if (outsideMethodKvp is { m: {} outsideMethod, Key: var framework}) + { + response = GetMethodHelp(outsideMethod, framework); + } var correctFlagName = Flag.FlagInfos.Keys .FirstOrDefault(k => k.ToLower() == arg); @@ -394,29 +404,52 @@ public static string GetMethodList() var sb = new StringBuilder($"Hi! There are {MethodIndex.GetMethods().Length} methods available for your use!\n"); sb.AppendLine($"If a method has {retsSuffix.TrimStart()}, it means that this method returns a value."); sb.AppendLine("If you want to get specific information about a given method, just do 'serhelp '!"); - + foreach (var kvp in MethodsByCategory().OrderBy(kvp => kvp.Key[0])) { - var descDistance = kvp.Value - .Select(m => m.Name.Length + (m is ReturningMethod ? retsSuffix.Length : 0)) - .Max() + 1; + var descDistance = DescDistance(kvp.Value); sb.AppendLine(); sb.AppendLine($"--- {kvp.Key} methods ---"); foreach (var method in kvp.Value) { - var name = method.Name; - if (method is ReturningMethod) - { - name += retsSuffix; - } - - var descPadding = new string(' ', descDistance - name.Length); - sb.AppendLine($"> {name}{descPadding}~ {method.Description}"); + sb.AppendLine(GetFormatted(method, descDistance)); + } + } + + foreach (var (framework, methods) in MethodIndex.FrameworkDependentMethods + .Where(kvp => FrameworkBridge.Found.All(fb => fb.Type != kvp.Key))) + { + var descDistance = DescDistance(methods); + + sb.AppendLine(); + sb.AppendLine($"--- (not accessible) {framework} framework methods ---"); + foreach (var method in methods) + { + sb.AppendLine(GetFormatted(method, descDistance)); } } return sb.ToString(); + + string GetFormatted(Method method, int descDistance) + { + var name = method.Name; + if (method is ReturningMethod) + { + name += retsSuffix; + } + + var descPadding = new string(' ', descDistance - name.Length); + return $"> {name}{descPadding}~ {method.Description}"; + } + + int DescDistance(IEnumerable methods) + { + return methods + .Select(m => m.Name.Length + (m is ReturningMethod ? retsSuffix.Length : 0)) + .Max() + 1; + } } public static string GetVariableList() @@ -442,12 +475,19 @@ public static string GetVariableList() return sb.ToString(); } - public static string GetMethodHelp(Method method) + public static string GetMethodHelp(Method method, FrameworkBridge.Type? notLoadedFramework = null) { var sb = new StringBuilder($"=== {method.Name} ===\n"); sb.AppendLine($"> {method.Description}"); + if (notLoadedFramework is {} framework) + { + sb.AppendLine(); + sb.AppendLine($"This method requires the '{framework}' framework in order to be used."); + return sb.ToString(); + } + if (method is IAdditionalDescription addDesc) { sb.AppendLine(); From 71ae836bfac305b594a44e1143bf4885d072f327 Mon Sep 17 00:00:00 2001 From: Tosoks67 Date: Sun, 15 Mar 2026 12:52:52 +0100 Subject: [PATCH 26/60] color argumenet fix --- Code/ArgumentSystem/Arguments/ColorArgument.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Code/ArgumentSystem/Arguments/ColorArgument.cs b/Code/ArgumentSystem/Arguments/ColorArgument.cs index 48d05a0b..facc3513 100644 --- a/Code/ArgumentSystem/Arguments/ColorArgument.cs +++ b/Code/ArgumentSystem/Arguments/ColorArgument.cs @@ -1,6 +1,5 @@ using JetBrains.Annotations; using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.Extensions; using SER.Code.Helpers.ResultSystem; using SER.Code.TokenSystem.Tokens; using SER.Code.TokenSystem.Tokens.ValueTokens; @@ -20,12 +19,7 @@ public DynamicTryGet GetConvertSolution(BaseToken token) { return colorToken.Value.Value; } - - if (token.CanReturn(out var get)) - { - return new(get().OnSuccess(cv => cv.Value)); - } - - return $"{token} is not a valid color."; + + return new(() => token.TryGetLiteralValue().OnSuccess(val => val.Value)); } } \ No newline at end of file From 839074a3318fc651d98c88e7b96a2b5e3c6adf0a Mon Sep 17 00:00:00 2001 From: Tosoks67 Date: Sun, 15 Mar 2026 12:53:07 +0100 Subject: [PATCH 27/60] remove unneccessary usings --- Code/ContextSystem/Contexts/FuncStatement.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Code/ContextSystem/Contexts/FuncStatement.cs b/Code/ContextSystem/Contexts/FuncStatement.cs index 94e55a6e..43c2838e 100644 --- a/Code/ContextSystem/Contexts/FuncStatement.cs +++ b/Code/ContextSystem/Contexts/FuncStatement.cs @@ -1,6 +1,5 @@ using JetBrains.Annotations; using SER.Code.ContextSystem.BaseContexts; -using SER.Code.ContextSystem.Extensions; using SER.Code.ContextSystem.Interfaces; using SER.Code.ContextSystem.Structures; using SER.Code.Exceptions; From 709a898db41b614c1694cac6fb9a89336f8c4217 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sun, 15 Mar 2026 14:02:21 +0100 Subject: [PATCH 28/60] add logs for method activation --- Code/MethodSystem/MethodIndex.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Code/MethodSystem/MethodIndex.cs b/Code/MethodSystem/MethodIndex.cs index 3249201d..12f262d0 100644 --- a/Code/MethodSystem/MethodIndex.cs +++ b/Code/MethodSystem/MethodIndex.cs @@ -42,7 +42,11 @@ public static void AddAllDefinedMethodsInAssembly(Assembly? assembly = null) assembly ??= Assembly.GetCallingAssembly(); var definedMethods = assembly.GetTypes() .Where(t => t.IsClass && !t.IsAbstract && typeof(Method).IsAssignableFrom(t)) - .Select(t => Activator.CreateInstance(t) as Method) + .Select(t => + { + Log.Debug($"trying to activate {t.AccurateName}"); + return Activator.CreateInstance(t) as Method; + }) .Where(t => { if (t is not IDependOnFramework framework) From 6865eec0e48949fd0b708841909e2f4fd70ed4a9 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sun, 15 Mar 2026 14:03:00 +0100 Subject: [PATCH 29/60] fix Toy methods using exiled admin toys --- .../Methods/AdminToysMethods/SetToyRotationMethod.cs | 3 ++- .../MethodSystem/Methods/AdminToysMethods/SetToyScaleMethod.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Code/MethodSystem/Methods/AdminToysMethods/SetToyRotationMethod.cs b/Code/MethodSystem/Methods/AdminToysMethods/SetToyRotationMethod.cs index 4e223e3e..d4b5157f 100644 --- a/Code/MethodSystem/Methods/AdminToysMethods/SetToyRotationMethod.cs +++ b/Code/MethodSystem/Methods/AdminToysMethods/SetToyRotationMethod.cs @@ -1,5 +1,6 @@ -using Exiled.API.Features.Toys; + using JetBrains.Annotations; +using LabApi.Features.Wrappers; using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.MethodSystem.BaseMethods.Synchronous; diff --git a/Code/MethodSystem/Methods/AdminToysMethods/SetToyScaleMethod.cs b/Code/MethodSystem/Methods/AdminToysMethods/SetToyScaleMethod.cs index 8faff626..d68c3952 100644 --- a/Code/MethodSystem/Methods/AdminToysMethods/SetToyScaleMethod.cs +++ b/Code/MethodSystem/Methods/AdminToysMethods/SetToyScaleMethod.cs @@ -1,5 +1,6 @@ -using Exiled.API.Features.Toys; + using JetBrains.Annotations; +using LabApi.Features.Wrappers; using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.MethodSystem.BaseMethods.Synchronous; From 31973e5ea1c97efabd8b9d236a5d91ea102694eb Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sun, 15 Mar 2026 14:03:26 +0100 Subject: [PATCH 30/60] fix GetAmmoLimit using static argument with exiled --- Code/MethodSystem/Methods/PlayerMethods/GetAmmoLimitMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/MethodSystem/Methods/PlayerMethods/GetAmmoLimitMethod.cs b/Code/MethodSystem/Methods/PlayerMethods/GetAmmoLimitMethod.cs index 0d0e69c6..230559d6 100644 --- a/Code/MethodSystem/Methods/PlayerMethods/GetAmmoLimitMethod.cs +++ b/Code/MethodSystem/Methods/PlayerMethods/GetAmmoLimitMethod.cs @@ -17,7 +17,7 @@ public class GetAmmoLimitMethod : ReturningMethod, IDependOnFramewo public override string Description => "Gets the player's limit on a certain ammunition type"; - public override Argument[] ExpectedArguments { get; } = + public override Argument[] ExpectedArguments => [ new PlayerArgument("player to get the limit from"), new EnumArgument("ammo type") From ec32213fb1acc8c1e9e5cb176b9c7c32f99a1354 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sun, 15 Mar 2026 14:04:13 +0100 Subject: [PATCH 31/60] fix SetAmmoLimit when no exiled --- .../Methods/PlayerMethods/SetAmmoLimitMethod.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Code/MethodSystem/Methods/PlayerMethods/SetAmmoLimitMethod.cs b/Code/MethodSystem/Methods/PlayerMethods/SetAmmoLimitMethod.cs index e3b28bb4..4b234502 100644 --- a/Code/MethodSystem/Methods/PlayerMethods/SetAmmoLimitMethod.cs +++ b/Code/MethodSystem/Methods/PlayerMethods/SetAmmoLimitMethod.cs @@ -31,6 +31,10 @@ public override void Execute() var ammoType = Args.GetEnum("ammo type"); var limit = (ushort) Args.GetInt("limit"); var labApiPlayers = Args.GetPlayers("players to set the limit to"); - labApiPlayers.ForEach(plr => Player.Get(plr).SetAmmoLimit(ammoType, limit)); + + foreach (var plr in labApiPlayers) + { + Player.Get(plr).SetAmmoLimit(ammoType, limit); + } } } \ No newline at end of file From 2ad02768b58f2d054f78a75017a13dfe79d2dcc6 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sun, 15 Mar 2026 14:06:50 +0100 Subject: [PATCH 32/60] 0.15 -> 0.15.1 --- Code/Plugin/MainPlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Plugin/MainPlugin.cs b/Code/Plugin/MainPlugin.cs index 63cf7d0e..b275c15c 100644 --- a/Code/Plugin/MainPlugin.cs +++ b/Code/Plugin/MainPlugin.cs @@ -23,7 +23,7 @@ public class MainPlugin : LabApi.Loader.Features.Plugins.Plugin public override string Description => "The scripting language for SCP:SL."; public override string Author => "Elektryk_Andrzej"; public override Version RequiredApiVersion => LabApiProperties.CurrentVersion; - public override Version Version => new(0, 15, 0); + public override Version Version => new(0, 15, 1); public static string GitHubLink => "https://github.com/ScriptedEvents/ScriptedEventsReloaded"; public static string DocsLink => "https://scriptedeventsreloaded.gitbook.io/docs/tutorial"; From 20e300847ec5ab2b357b731f730744671f029217 Mon Sep 17 00:00:00 2001 From: Tosoks67 Date: Sun, 15 Mar 2026 15:00:12 +0100 Subject: [PATCH 33/60] reading text in databases fix --- Code/FileSystem/Structures/Database.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/FileSystem/Structures/Database.cs b/Code/FileSystem/Structures/Database.cs index d3bf036d..a9ab2f91 100644 --- a/Code/FileSystem/Structures/Database.cs +++ b/Code/FileSystem/Structures/Database.cs @@ -82,7 +82,7 @@ public Result TrySet(string key, Value value, bool save = true) return $"Value '{value}' cannot be stored in databases"; } - _db[key] = new(value.GetType(), saveVal); + _db[key] = new(value is DynamicTextValue ? typeof(StaticTextValue) : value.GetType(), saveVal); if (save) Save(); return true; } From 4c62db3f707f9577fea7e0e3bd25119de29a4410 Mon Sep 17 00:00:00 2001 From: Tosoks67 Date: Sun, 15 Mar 2026 16:37:05 +0100 Subject: [PATCH 34/60] DatabaseValue ctor change because JSON went brrrr --- Code/FileSystem/Structures/Database.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Code/FileSystem/Structures/Database.cs b/Code/FileSystem/Structures/Database.cs index a9ab2f91..2cadce81 100644 --- a/Code/FileSystem/Structures/Database.cs +++ b/Code/FileSystem/Structures/Database.cs @@ -9,9 +9,9 @@ namespace SER.Code.FileSystem.Structures; public class Database { - public readonly struct DatabaseValue(Type originalType, object value) + public readonly struct DatabaseValue(string type, object value) { - public string Type { get; } = originalType.AccurateName; + public string Type { get; } = type; public object Value { get; } = value; } @@ -82,11 +82,20 @@ public Result TrySet(string key, Value value, bool save = true) return $"Value '{value}' cannot be stored in databases"; } - _db[key] = new(value is DynamicTextValue ? typeof(StaticTextValue) : value.GetType(), saveVal); + _db[key] = new( + value is DynamicTextValue + ? typeof(StaticTextValue).AccurateName + : value.GetType().AccurateName, saveVal + ); if (save) Save(); return true; } + public void RemoveKey(string key, bool save = true) + { + _db.Remove(key); + } + public TryGet HasKey(string key) { if (!_db.TryGetValue(key, out var val)) From 9dd0550180803e7b2dea3cd5042a9420d12bcd12 Mon Sep 17 00:00:00 2001 From: Tosoks67 Date: Sun, 15 Mar 2026 16:44:18 +0100 Subject: [PATCH 35/60] new RemoveDBKey method --- Code/FileSystem/Structures/Database.cs | 1 + .../DatabaseMethods/RemoveDBKeyMethod.cs | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 Code/MethodSystem/Methods/DatabaseMethods/RemoveDBKeyMethod.cs diff --git a/Code/FileSystem/Structures/Database.cs b/Code/FileSystem/Structures/Database.cs index 2cadce81..7151e242 100644 --- a/Code/FileSystem/Structures/Database.cs +++ b/Code/FileSystem/Structures/Database.cs @@ -94,6 +94,7 @@ value is DynamicTextValue public void RemoveKey(string key, bool save = true) { _db.Remove(key); + if (save) Save(); } public TryGet HasKey(string key) diff --git a/Code/MethodSystem/Methods/DatabaseMethods/RemoveDBKeyMethod.cs b/Code/MethodSystem/Methods/DatabaseMethods/RemoveDBKeyMethod.cs new file mode 100644 index 00000000..85a5c04c --- /dev/null +++ b/Code/MethodSystem/Methods/DatabaseMethods/RemoveDBKeyMethod.cs @@ -0,0 +1,23 @@ +using JetBrains.Annotations; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; + +namespace SER.Code.MethodSystem.Methods.DatabaseMethods; + +[UsedImplicitly] +public class RemoveDBKeyMethod : SynchronousMethod +{ + public override string Description => "Removes a key from a database."; + + public override Argument[] ExpectedArguments { get; } = + [ + new DatabaseArgument("database"), + new TextArgument("key") + ]; + + public override void Execute() + { + Args.GetDatabase("database").RemoveKey(Args.GetText("key")); + } +} From c0d8ea760b7b3be61f99c2646221654ffd7ef1eb Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sat, 28 Mar 2026 07:28:26 +0100 Subject: [PATCH 36/60] begin rework --- .../BaseContexts/AdditionalContext.cs | 24 ++ Code/ContextSystem/BaseContexts/Context.cs | 19 +- .../BaseContexts/RunnableContext.cs | 26 ++ .../BaseContexts/StandardContext.cs | 2 +- .../BaseContexts/StatementContext.cs | 2 +- .../BaseContexts/YieldingContext.cs | 2 +- Code/ContextSystem/Contexter.cs | 15 +- .../Contexts/Control/ReturnKeyword.cs | 2 +- .../Contexts/NoOperationContext.cs | 26 -- .../Contexts/ValueExpressionContext.cs | 226 ++++++++++++++++++ .../VariableDefinitionContext.cs | 2 +- .../Extensions/BaseContextExtensions.cs | 2 +- Code/Exceptions/ScriptRuntimeError.cs | 2 +- Code/ScriptSystem/Script.cs | 2 +- .../Structures/IContextableToken.cs | 2 +- Code/TokenSystem/Tokens/CommentToken.cs | 10 +- .../ExpressionTokens/ExpressionToken.cs | 13 +- .../LiteralVariableExpressionToken.cs | 30 --- .../ExpressionTokens/MethodExpressionToken.cs | 59 ----- .../ReferenceVariableExpressionToken.cs | 99 -------- Code/TokenSystem/Tokens/FlagArgumentToken.cs | 10 +- Code/TokenSystem/Tokens/FlagToken.cs | 10 +- .../Tokens/Interfaces/IValueToken.cs | 2 +- Code/TokenSystem/Tokens/KeywordToken.cs | 6 +- Code/TokenSystem/Tokens/MethodToken.cs | 2 +- Code/TokenSystem/Tokens/RunFunctionToken.cs | 2 +- .../VariableTokens/CollectionVariableToken.cs | 2 +- .../VariableTokens/LiteralVariableToken.cs | 2 +- .../VariableTokens/PlayerVariableToken.cs | 2 +- .../VariableTokens/ReferenceVariableToken.cs | 2 +- .../Tokens/VariableTokens/VariableToken.cs | 2 +- Code/ValueSystem/BoolValue.cs | 2 + Code/ValueSystem/CollectionValue.cs | 2 + Code/ValueSystem/ColorValue.cs | 2 + Code/ValueSystem/DurationValue.cs | 2 + Code/ValueSystem/NumberValue.cs | 2 + Code/ValueSystem/PlayerValue.cs | 2 + Code/ValueSystem/ReferenceValue.cs | 2 + Code/ValueSystem/TextValue.cs | 2 + Code/ValueSystem/Value.cs | 19 ++ 40 files changed, 354 insertions(+), 288 deletions(-) create mode 100644 Code/ContextSystem/BaseContexts/AdditionalContext.cs create mode 100644 Code/ContextSystem/BaseContexts/RunnableContext.cs delete mode 100644 Code/ContextSystem/Contexts/NoOperationContext.cs create mode 100644 Code/ContextSystem/Contexts/ValueExpressionContext.cs delete mode 100644 Code/TokenSystem/Tokens/ExpressionTokens/LiteralVariableExpressionToken.cs delete mode 100644 Code/TokenSystem/Tokens/ExpressionTokens/MethodExpressionToken.cs delete mode 100644 Code/TokenSystem/Tokens/ExpressionTokens/ReferenceVariableExpressionToken.cs diff --git a/Code/ContextSystem/BaseContexts/AdditionalContext.cs b/Code/ContextSystem/BaseContexts/AdditionalContext.cs new file mode 100644 index 00000000..17aa467b --- /dev/null +++ b/Code/ContextSystem/BaseContexts/AdditionalContext.cs @@ -0,0 +1,24 @@ +using SER.Code.Helpers; +using SER.Code.ScriptSystem; + +namespace SER.Code.ContextSystem.BaseContexts; + +public abstract class AdditionalContext : Context +{ + private Safe _parentContext; + public RunnableContext ParentContext + { + get => _parentContext.Value; + set => _parentContext = value; + } + + public static AdditionalContext Create(Type contextType, Script scr, RunnableContext parentContext) + { + var context = (AdditionalContext)Activator.CreateInstance(contextType); + context.Script = scr; + context.ParentContext = parentContext; + return context; + } + + public override string ToString() => $"{FriendlyName} attached to {ParentContext}"; +} \ No newline at end of file diff --git a/Code/ContextSystem/BaseContexts/Context.cs b/Code/ContextSystem/BaseContexts/Context.cs index 4c7ee27d..04f0d2ce 100644 --- a/Code/ContextSystem/BaseContexts/Context.cs +++ b/Code/ContextSystem/BaseContexts/Context.cs @@ -1,4 +1,4 @@ -using SER.Code.ContextSystem.Structures; +using SER.Code.ContextSystem.Structures; using SER.Code.Helpers.ResultSystem; using SER.Code.ScriptSystem; using SER.Code.TokenSystem.Tokens; @@ -9,29 +9,18 @@ public abstract class Context { public required Script Script { get; set; } = null!; - public required uint? LineNum { get; set; } - - public StatementContext? ParentContext { get; set; } = null; - protected abstract string FriendlyName { get; } public abstract TryAddTokenRes TryAddToken(BaseToken token); public abstract Result VerifyCurrentState(); - public static Context Create(Type contextType, (Script scr, uint? lineNum) info) + public static Context Create(Type contextType, Script scr) { var context = (Context)Activator.CreateInstance(contextType); - context.Script = info.scr; - context.LineNum = info.lineNum; + context.Script = scr; return context; } - public override string ToString() - { - if (LineNum.HasValue) - return $"{FriendlyName} at line {LineNum}"; - - return FriendlyName; - } + public abstract override string ToString(); } \ No newline at end of file diff --git a/Code/ContextSystem/BaseContexts/RunnableContext.cs b/Code/ContextSystem/BaseContexts/RunnableContext.cs new file mode 100644 index 00000000..3e64a627 --- /dev/null +++ b/Code/ContextSystem/BaseContexts/RunnableContext.cs @@ -0,0 +1,26 @@ +using SER.Code.ScriptSystem; + +namespace SER.Code.ContextSystem.BaseContexts; + +public abstract class RunnableContext : Context +{ + public required uint? LineNum { get; set; } + + public StatementContext? ParentContext { get; set; } = null; + + public static RunnableContext Create(Type contextType, Script scr, uint? lineNum ) + { + var context = (RunnableContext)Activator.CreateInstance(contextType); + context.Script = scr; + context.LineNum = lineNum; + return context; + } + + public override string ToString() + { + if (LineNum.HasValue) + return $"{FriendlyName} at line {LineNum}"; + + return FriendlyName; + } +} \ No newline at end of file diff --git a/Code/ContextSystem/BaseContexts/StandardContext.cs b/Code/ContextSystem/BaseContexts/StandardContext.cs index b6a7609f..e963fe36 100644 --- a/Code/ContextSystem/BaseContexts/StandardContext.cs +++ b/Code/ContextSystem/BaseContexts/StandardContext.cs @@ -1,6 +1,6 @@ namespace SER.Code.ContextSystem.BaseContexts; -public abstract class StandardContext : Context +public abstract class StandardContext : RunnableContext { public void Run() { diff --git a/Code/ContextSystem/BaseContexts/StatementContext.cs b/Code/ContextSystem/BaseContexts/StatementContext.cs index 545e4361..c7b19913 100644 --- a/Code/ContextSystem/BaseContexts/StatementContext.cs +++ b/Code/ContextSystem/BaseContexts/StatementContext.cs @@ -6,7 +6,7 @@ namespace SER.Code.ContextSystem.BaseContexts; public abstract class StatementContext : YieldingContext { - public readonly List Children = []; + public readonly List Children = []; public void SendControlMessage(ParentContextControlMessage msg) { diff --git a/Code/ContextSystem/BaseContexts/YieldingContext.cs b/Code/ContextSystem/BaseContexts/YieldingContext.cs index 90155fcb..a939b08e 100644 --- a/Code/ContextSystem/BaseContexts/YieldingContext.cs +++ b/Code/ContextSystem/BaseContexts/YieldingContext.cs @@ -3,7 +3,7 @@ namespace SER.Code.ContextSystem.BaseContexts; -public abstract class YieldingContext : Context +public abstract class YieldingContext : RunnableContext { public IEnumerator Run() { diff --git a/Code/ContextSystem/Contexter.cs b/Code/ContextSystem/Contexter.cs index 8bc90b77..0b83bf4a 100644 --- a/Code/ContextSystem/Contexter.cs +++ b/Code/ContextSystem/Contexter.cs @@ -15,10 +15,10 @@ namespace SER.Code.ContextSystem; /// public static class Contexter { - public static TryGet ContextLines(Line[] lines, Script scr) + public static TryGet ContextLines(Line[] lines, Script scr) { Stack statementStack = []; - List contexts = []; + List contexts = []; List errors = []; foreach (var line in lines) @@ -47,10 +47,10 @@ public static TryGet ContextLines(Line[] lines, Script scr) } private static Result TryAddResult( - Context context, + RunnableContext context, uint lineNum, Stack statementStack, - List contexts + List contexts ) { Result rs = $"Invalid context {context}"; @@ -129,12 +129,12 @@ List contexts return true; } - public static TryGet ContextLine(BaseToken[] tokens, uint? lineNum, Script scr) + public static TryGet ContextLine(BaseToken[] tokens, uint? lineNum, Script scr) { Result rs = $"Line {(lineNum.HasValue ? $"{lineNum.Value} " : "")}is invalid"; var firstToken = tokens.FirstOrDefault(); - if (firstToken is null) return null as Context; + if (firstToken is null) return null as RunnableContext; if (firstToken is not IContextableToken contextable) { @@ -142,6 +142,7 @@ List contexts } var context = contextable.GetContext(scr); + if (context is null) return context; foreach (var token in tokens.Skip(1)) { @@ -154,7 +155,7 @@ List contexts return context; } - private static Result HandleCurrentContext(BaseToken token, Context context, out bool endLineContexting) + private static Result HandleCurrentContext(BaseToken token, RunnableContext context, out bool endLineContexting) { Result rs = $"Cannot add '{token.RawRep}' to {context}"; Log.Debug($"Handling token {token} in context {context}"); diff --git a/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs b/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs index a0b9f5ec..0f3311a4 100644 --- a/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs +++ b/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs @@ -15,7 +15,7 @@ namespace SER.Code.ContextSystem.Contexts.Control; public class ReturnKeyword : StandardContext, IKeywordContext { private IValueToken? _returnValueToken; - private (Context main, IMayReturnValueContext returner)? _returnContext = null; + private (RunnableContext main, IMayReturnValueContext returner)? _returnContext = null; public string KeywordName => "return"; public string Description => "Returns value when in a function."; diff --git a/Code/ContextSystem/Contexts/NoOperationContext.cs b/Code/ContextSystem/Contexts/NoOperationContext.cs deleted file mode 100644 index 8e1ba2c2..00000000 --- a/Code/ContextSystem/Contexts/NoOperationContext.cs +++ /dev/null @@ -1,26 +0,0 @@ -using SER.Code.ContextSystem.BaseContexts; -using SER.Code.ContextSystem.Interfaces; -using SER.Code.ContextSystem.Structures; -using SER.Code.Helpers.ResultSystem; -using SER.Code.TokenSystem.Tokens; - -namespace SER.Code.ContextSystem.Contexts; - -public class NoOperationContext : StandardContext, INotRunningContext -{ - protected override string FriendlyName => "no operation"; - - public override TryAddTokenRes TryAddToken(BaseToken token) - { - return TryAddTokenRes.Continue(); - } - - public override Result VerifyCurrentState() - { - return true; - } - - protected override void Execute() - { - } -} \ No newline at end of file diff --git a/Code/ContextSystem/Contexts/ValueExpressionContext.cs b/Code/ContextSystem/Contexts/ValueExpressionContext.cs new file mode 100644 index 00000000..c3706e7e --- /dev/null +++ b/Code/ContextSystem/Contexts/ValueExpressionContext.cs @@ -0,0 +1,226 @@ +using SER.Code.ContextSystem.BaseContexts; +using SER.Code.ContextSystem.Structures; +using SER.Code.Exceptions; +using SER.Code.Extensions; +using SER.Code.Helpers; +using SER.Code.Helpers.ResultSystem; +using SER.Code.MethodSystem.BaseMethods.Interfaces; +using SER.Code.MethodSystem.BaseMethods.Yielding; +using SER.Code.ScriptSystem; +using SER.Code.TokenSystem.Tokens; +using SER.Code.TokenSystem.Tokens.Interfaces; +using SER.Code.ValueSystem; + +namespace SER.Code.ContextSystem.Contexts; + +/// +/// Used to unify method calls, math expressions and property access into a single context that returns a value. +/// +public class ValueExpressionContext : AdditionalContext +{ + private readonly BaseToken _initial; + + public abstract class Handler + { + public abstract TryGet GetReturnValue(); + public abstract TryAddTokenRes TryAddToken(BaseToken token); + public abstract Result VerifyCurrentState(); + public abstract IEnumerator Run(); + } + + private readonly string? _error; + private Handler? _handler; + + /// + /// Used to unify method calls, math expressions and property access into a single context that returns a value. + /// + public ValueExpressionContext(BaseToken initial, bool allowsYielding, RunnableContext parent) + { + _initial = initial; + try + { + if (initial is MethodToken methodToken) + { + _handler = new MethodHandler(methodToken, allowsYielding, parent); + } + else if (initial is not IValueToken) + { + _error = $"{initial} is not a valid way to get a value."; + } + } + catch (Exception e) + { + _error = e.Message; + } + } + + protected override string FriendlyName => "value expression"; + + public override TryAddTokenRes TryAddToken(BaseToken token) + { + if (_error is not null) return TryAddTokenRes.Error(_error); + + _handler ??= token switch + { + SymbolToken => new GenericValueExpressionResolver(_initial, Script), + // we assume that an unknown token means property access, but thats kinda sloppy + _ => new ValuePropertyHandler(_initial, (IValueToken)_initial) + }; + + return _handler.TryAddToken(token); + } + + public override Result VerifyCurrentState() + { + if (_error is not null) return _error; + if (_handler is null) return $"{_initial} is not a valid way to get a value."; + return _handler.VerifyCurrentState(); + } + + public IEnumerator Run() + { + var coro = _handler!.Run(); + while (coro.MoveNext()) yield return coro.Current; + } +} + +public class MethodHandler : ValueExpressionContext.Handler +{ + private readonly RunnableContext _parent; + private readonly MethodContext _context; + + public MethodHandler(MethodToken token, bool allowsYielding, RunnableContext parent) + { + _parent = parent; + _context = (MethodContext)token.GetContext(token.Script); + var method = token.Method; + + if (method is not IReturningMethod) + throw new Exception($"Method '{method.Name}' does not return a value.'"); + + if (method is YieldingMethod && !allowsYielding) + throw new Exception( + $"Method '{method.Name}' is yielding, but you cannot use yielding methods in this context. " + + $"Consider making a variable and using that variable instead."); + } + + public override TryGet GetReturnValue() => _context.ReturnedValue + ?? throw new ScriptRuntimeError(_parent, _context.MissingValueHint); + + public override TryAddTokenRes TryAddToken(BaseToken token) + { + return _context.TryAddToken(token); + } + + public override Result VerifyCurrentState() + { + return _context.VerifyCurrentState(); + } + + public override IEnumerator Run() + { + var coro = _context.Run(); + while (coro.MoveNext()) yield return coro.Current; + } +} + +/// +/// Keep in mind that this class will also be used for simple value getting, as parameters are not required! +/// +public class ValuePropertyHandler( + BaseToken baseToken, + IValueToken valueToken) : ValueExpressionContext.Handler +{ + private readonly Queue _propertyNames = []; + private string _valueRepresentation = baseToken.RawRep; + private TypeOfValue _lastValueType = valueToken.PossibleValues; + + public override TryGet GetReturnValue() + { + if (valueToken.Value().HasErrored(out var error, out var value)) + { + return $"Failed to get value from '{_valueRepresentation}'".AsError() + + error.AsError(); + } + + Value current = value; + while (_propertyNames.Count > 0) + { + var prop = _propertyNames.Dequeue(); + if (!current.Properties.TryGetValue(prop, out var propInfo)) + { + return $"{value} does not have property '{prop}'."; + } + + current = propInfo.Func(current); + } + + return current; + } + + public override TryAddTokenRes TryAddToken(BaseToken token) + { + // type verification + if (_lastValueType.AreKnown(out var types)) + { + foreach (var type in types) + { + if (Value.GetPropertiesOfValue(type).TryGetValue(token.RawRep, out var property)) + { + _valueRepresentation += $" {token.RawRep}"; + _lastValueType = property.ReturnType; + break; + } + } + + return TryAddTokenRes.Error($"'{token.RawRep}' is not a valid property of '{_valueRepresentation}' value."); + } + + _propertyNames.Enqueue(token.RawRep); + return TryAddTokenRes.Continue(); + } + + public override Result VerifyCurrentState() => true; + + public override IEnumerator Run() + { + yield break; + } +} + +/// +/// Used for math expressions. +/// +public class GenericValueExpressionResolver(BaseToken initial, Script scr) + : ValueExpressionContext.Handler +{ + private readonly List _tokens = [initial]; + private Safe _expression; + + public override TryGet GetReturnValue() + { + return _expression.Value.Evaluate().OnSuccess(obj => Value.Parse(obj, scr)); + } + + public override TryAddTokenRes TryAddToken(BaseToken token) + { + _tokens.Add(token); + return TryAddTokenRes.Continue(); + } + + public override Result VerifyCurrentState() + { + if (NumericExpressionReslover.CompileExpression(_tokens.ToArray()).HasErrored(out var error, out var compiledExpression)) + { + return error; + } + + _expression = compiledExpression; + return true; + } + + public override IEnumerator Run() + { + yield break; + } +} \ No newline at end of file diff --git a/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs b/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs index 09caeab0..4f346733 100644 --- a/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs +++ b/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs @@ -33,7 +33,7 @@ protected virtual (TryAddTokenRes result, Func parser) AdditionalParsing protected Func?>? AdditionalTokenParser = null; private bool _equalSignSet = false; - private (Context main, IMayReturnValueContext returner)? _returnContext = null; + private (RunnableContext main, IMayReturnValueContext returner)? _returnContext = null; private Func? _parser = null; protected override string FriendlyName => $"'{varToken.RawRep}' variable definition"; diff --git a/Code/ContextSystem/Extensions/BaseContextExtensions.cs b/Code/ContextSystem/Extensions/BaseContextExtensions.cs index 28ec2dd9..d42bbb51 100644 --- a/Code/ContextSystem/Extensions/BaseContextExtensions.cs +++ b/Code/ContextSystem/Extensions/BaseContextExtensions.cs @@ -7,7 +7,7 @@ namespace SER.Code.ContextSystem.Extensions; public static class BaseContextExtensions { - public static IEnumerator ExecuteBaseContext(this Context context) + public static IEnumerator ExecuteBaseContext(this RunnableContext context) { Log.Debug($"Executing context {context.FriendlyTypeName()}"); switch (context) diff --git a/Code/Exceptions/ScriptRuntimeError.cs b/Code/Exceptions/ScriptRuntimeError.cs index d46f1142..b446287f 100644 --- a/Code/Exceptions/ScriptRuntimeError.cs +++ b/Code/Exceptions/ScriptRuntimeError.cs @@ -9,7 +9,7 @@ protected ScriptRuntimeError(string error) : base(error) { } - public ScriptRuntimeError(Context context, string error) : base($"{context} has errored: {error}") + public ScriptRuntimeError(RunnableContext context, string error) : base($"{context} has errored: {error}") { } diff --git a/Code/ScriptSystem/Script.cs b/Code/ScriptSystem/Script.cs index 79af41a1..1abbabad 100644 --- a/Code/ScriptSystem/Script.cs +++ b/Code/ScriptSystem/Script.cs @@ -26,7 +26,7 @@ namespace SER.Code.ScriptSystem; public class Script { private Line[] _lines = []; - private Context[] _contexts = []; + private RunnableContext[] _contexts = []; private bool? _isEventAllowed; public required ScriptName Name { get; init; } diff --git a/Code/TokenSystem/Structures/IContextableToken.cs b/Code/TokenSystem/Structures/IContextableToken.cs index 08018ac6..014f0fa0 100644 --- a/Code/TokenSystem/Structures/IContextableToken.cs +++ b/Code/TokenSystem/Structures/IContextableToken.cs @@ -5,5 +5,5 @@ namespace SER.Code.TokenSystem.Structures; public interface IContextableToken { - public Context GetContext(Script scr); + public RunnableContext? GetContext(Script scr); } \ No newline at end of file diff --git a/Code/TokenSystem/Tokens/CommentToken.cs b/Code/TokenSystem/Tokens/CommentToken.cs index e85c97ad..00ba8162 100644 --- a/Code/TokenSystem/Tokens/CommentToken.cs +++ b/Code/TokenSystem/Tokens/CommentToken.cs @@ -1,5 +1,4 @@ using SER.Code.ContextSystem.BaseContexts; -using SER.Code.ContextSystem.Contexts; using SER.Code.ScriptSystem; using SER.Code.TokenSystem.Structures; @@ -14,12 +13,5 @@ protected override IParseResult InternalParse(Script scr) : new Ignore(); } - public Context GetContext(Script scr) - { - return new NoOperationContext - { - Script = Script, - LineNum = LineNum, - }; - } + public RunnableContext? GetContext(Script scr) => null; } \ No newline at end of file diff --git a/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs b/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs index 825f9410..d7e352a0 100644 --- a/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs +++ b/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs @@ -1,4 +1,6 @@ -using SER.Code.Extensions; +using SER.Code.ContextSystem.BaseContexts; +using SER.Code.ContextSystem.Contexts; +using SER.Code.Extensions; using SER.Code.Helpers.ResultSystem; using SER.Code.ScriptSystem; using SER.Code.TokenSystem.Slices; @@ -8,7 +10,7 @@ namespace SER.Code.TokenSystem.Tokens.ExpressionTokens; -public abstract class ExpressionToken : BaseToken, IValueToken +public class ExpressionToken : BaseToken, IValueToken { protected override IParseResult InternalParse(Script scr) { @@ -29,7 +31,10 @@ protected override IParseResult InternalParse(Script scr) return new Error($"Expression '{collection.Value}' is empty."); } - return InternalParse(tokens); + var ctx = new ValueExpressionContext(tokens[0], false, ); + + + return } public static TryGet TryParse(CollectionSlice slice, Script script) @@ -47,8 +52,6 @@ public static TryGet TryParse(CollectionSlice slice, Script scr return expToken; } - protected abstract IParseResult InternalParse(BaseToken[] tokens); - public abstract TryGet Value(); public abstract TypeOfValue PossibleValues { get; } public bool IsConstant => false; diff --git a/Code/TokenSystem/Tokens/ExpressionTokens/LiteralVariableExpressionToken.cs b/Code/TokenSystem/Tokens/ExpressionTokens/LiteralVariableExpressionToken.cs deleted file mode 100644 index 1c498dc0..00000000 --- a/Code/TokenSystem/Tokens/ExpressionTokens/LiteralVariableExpressionToken.cs +++ /dev/null @@ -1,30 +0,0 @@ -using SER.Code.Helpers; -using SER.Code.Helpers.ResultSystem; -using SER.Code.TokenSystem.Tokens.VariableTokens; -using SER.Code.ValueSystem; - -namespace SER.Code.TokenSystem.Tokens.ExpressionTokens; - -/// -/// Used for variable formatting inside strings, as expression tokens are captured and formatted, but not variables -/// Maybe we should add variable parsing for strings? -/// -public class LiteralVariableExpressionToken : ExpressionToken -{ - private Safe _varToken; - - protected override IParseResult InternalParse(BaseToken[] tokens) - { - if (tokens.Length != 1 || tokens.First() is not LiteralVariableToken literalVariableToken) - { - return new Ignore(); - } - - _varToken = literalVariableToken; - return new Success(); - } - - public override TryGet Value() => _varToken.Value.Value(); - - public override TypeOfValue PossibleValues => new TypeOfValue(); -} \ No newline at end of file diff --git a/Code/TokenSystem/Tokens/ExpressionTokens/MethodExpressionToken.cs b/Code/TokenSystem/Tokens/ExpressionTokens/MethodExpressionToken.cs deleted file mode 100644 index 7d589cd6..00000000 --- a/Code/TokenSystem/Tokens/ExpressionTokens/MethodExpressionToken.cs +++ /dev/null @@ -1,59 +0,0 @@ -using SER.Code.ContextSystem; -using SER.Code.Exceptions; -using SER.Code.Helpers; -using SER.Code.Helpers.ResultSystem; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.BaseMethods.Yielding; -using SER.Code.ValueSystem; - -namespace SER.Code.TokenSystem.Tokens.ExpressionTokens; - -public class MethodExpressionToken : ExpressionToken -{ - private Safe _method; - - //public override TypeOfValue PossibleValues => new UnknownTypeOfValue(); - public override TypeOfValue PossibleValues => _method.Value.Returns; - - protected override IParseResult InternalParse(BaseToken[] tokens) - { - if (tokens.FirstOrDefault() is not { } token) - { - return new Ignore(); - } - - if (token is not MethodToken methodToken) - { - if (char.IsUpper(token.RawRep.First())) - { - return new Error($"'{token.RawRep}' is not a valid method."); - } - - return new Ignore(); - } - - if (methodToken.Method is YieldingMethod) - { - return new Error("Yielding methods are not allowed in expressions."); - } - - if (methodToken.Method is not ReturningMethod method) - { - return new Error($"Method '{methodToken.Method.Name}' does not return a value."); - } - - if (Contexter.ContextLine(tokens, null, Script).HasErrored(out var contextError)) - { - return new Error(contextError); - } - - _method = method; - return new Success(); - } - - public override TryGet Value() - { - _method.Value.Execute(); - return _method.Value.ReturnValue ?? throw new AndrzejFuckedUpException(); - } -} \ No newline at end of file diff --git a/Code/TokenSystem/Tokens/ExpressionTokens/ReferenceVariableExpressionToken.cs b/Code/TokenSystem/Tokens/ExpressionTokens/ReferenceVariableExpressionToken.cs deleted file mode 100644 index 83000369..00000000 --- a/Code/TokenSystem/Tokens/ExpressionTokens/ReferenceVariableExpressionToken.cs +++ /dev/null @@ -1,99 +0,0 @@ -using LabApi.Features.Wrappers; -using SER.Code.Extensions; -using SER.Code.Helpers.ResultSystem; -using SER.Code.TokenSystem.Tokens.VariableTokens; -using SER.Code.ValueSystem; - -namespace SER.Code.TokenSystem.Tokens.ExpressionTokens; - -public class ReferenceVariableExpressionToken : ExpressionToken -{ - private ReferenceVariableToken _refVarToken = null!; - private string _property = null!; - - protected override IParseResult InternalParse(BaseToken[] tokens) - { - if (tokens.First() is not ReferenceVariableToken refVarToken) - { - return new Ignore(); - } - - _refVarToken = refVarToken; - - if (tokens.Length > 2) - { - return new Error( - "When accessing properties of a reference variable, only 2 arguments are allowed: " + - $"the variable and the property to access. You provided {tokens.Length}." - ); - } - - _property = tokens.Last().GetBestTextRepresentation(null); - return new Success(); - } - - public override TryGet Value() - { - if (_refVarToken.TryGetVariable().HasErrored(out var err, out var variable)) - { - return err; - } - - return GetProperty(variable, _property); - } - - public override TypeOfValue PossibleValues => new UnknownTypeOfValue(); - - public abstract class Info - { - public abstract Func Handler { get; } - public abstract SingleTypeOfValue ReturnType { get; } - public abstract string? Description { get; } - } - - public class Info(Func handler, string? description) : Info - where TOut : Value - { - public override Func Handler => input => handler((TIn)input); - public override SingleTypeOfValue ReturnType => new(typeof(TOut)); - public override string? Description => description; - } - - public static TypesOfValue GetTypesOfValue(Type type) - { - return new TypesOfValue( - PropertyInfoMap[typeof(Item)] - .Select(i => i.Value.ReturnType) - .ToArray() - ); - } - - public static TryGet GetProperty(object obj, string propertyName) - { - var objType = obj.GetType(); - while (objType != null) - { - if (PropertyInfoMap.TryGetValue(objType, out var props) && props.TryGetValue(propertyName, out var info)) - { - return info.Handler(obj); - } - - objType = objType.BaseType; - } - - return $"Type {obj.GetType().AccurateName} has no property '{propertyName}' or this type is not supported."; - } - - // this will be improved upon later - public static readonly Dictionary> PropertyInfoMap = new() - { - [typeof(Item)] = new() - { - ["type"] = new Info(item => item.Type.ToString().ToStaticTextValue(), null), - ["category"] = new Info(item => item.Category.ToString().ToStaticTextValue(), null), - ["owner"] = new Info(item => new PlayerValue(item.CurrentOwner), null), - ["isEquipped"] = new Info(item => new BoolValue(item.IsEquipped), null) - } - }; -} - diff --git a/Code/TokenSystem/Tokens/FlagArgumentToken.cs b/Code/TokenSystem/Tokens/FlagArgumentToken.cs index eab11d1e..6c7abc24 100644 --- a/Code/TokenSystem/Tokens/FlagArgumentToken.cs +++ b/Code/TokenSystem/Tokens/FlagArgumentToken.cs @@ -1,5 +1,4 @@ using SER.Code.ContextSystem.BaseContexts; -using SER.Code.ContextSystem.Contexts; using SER.Code.ScriptSystem; using SER.Code.TokenSystem.Structures; @@ -14,12 +13,5 @@ protected override IParseResult InternalParse(Script scr) : new Ignore(); } - public Context GetContext(Script scr) - { - return new NoOperationContext - { - Script = Script, - LineNum = LineNum, - }; - } + public RunnableContext? GetContext(Script scr) => null; } \ No newline at end of file diff --git a/Code/TokenSystem/Tokens/FlagToken.cs b/Code/TokenSystem/Tokens/FlagToken.cs index aa719ea2..2edef75d 100644 --- a/Code/TokenSystem/Tokens/FlagToken.cs +++ b/Code/TokenSystem/Tokens/FlagToken.cs @@ -1,5 +1,4 @@ using SER.Code.ContextSystem.BaseContexts; -using SER.Code.ContextSystem.Contexts; using SER.Code.ScriptSystem; using SER.Code.TokenSystem.Structures; @@ -14,12 +13,5 @@ protected override IParseResult InternalParse(Script scr) : new Ignore(); } - public Context GetContext(Script scr) - { - return new NoOperationContext - { - Script = Script, - LineNum = LineNum, - }; - } + public RunnableContext? GetContext(Script scr) => null; } \ No newline at end of file diff --git a/Code/TokenSystem/Tokens/Interfaces/IValueToken.cs b/Code/TokenSystem/Tokens/Interfaces/IValueToken.cs index b39458eb..4607c6f2 100644 --- a/Code/TokenSystem/Tokens/Interfaces/IValueToken.cs +++ b/Code/TokenSystem/Tokens/Interfaces/IValueToken.cs @@ -11,7 +11,7 @@ public interface IValueToken public TryGet Value(); /// - /// A signature of all possible return values. Null if return signature unknown. + /// A signature of all possible return values. /// public TypeOfValue PossibleValues { get; } diff --git a/Code/TokenSystem/Tokens/KeywordToken.cs b/Code/TokenSystem/Tokens/KeywordToken.cs index 423cb800..e300f5e7 100644 --- a/Code/TokenSystem/Tokens/KeywordToken.cs +++ b/Code/TokenSystem/Tokens/KeywordToken.cs @@ -16,7 +16,7 @@ public class KeywordToken : BaseToken, IContextableToken t.IsClass && !t.IsAbstract && typeof(IKeywordContext).IsAssignableFrom(t) && - typeof(Context).IsAssignableFrom(t) + typeof(RunnableContext).IsAssignableFrom(t) ) .ToArray(); @@ -38,8 +38,8 @@ protected override IParseResult InternalParse(Script scr) : new Ignore(); } - public Context GetContext(Script scr) + public RunnableContext GetContext(Script scr) { - return Context.Create(_keywordType!, (scr, LineNum)); + return RunnableContext.Create(_keywordType!, scr, LineNum); } } \ No newline at end of file diff --git a/Code/TokenSystem/Tokens/MethodToken.cs b/Code/TokenSystem/Tokens/MethodToken.cs index 3ac374a4..978295f3 100644 --- a/Code/TokenSystem/Tokens/MethodToken.cs +++ b/Code/TokenSystem/Tokens/MethodToken.cs @@ -32,7 +32,7 @@ protected override IParseResult InternalParse(Script scr) return new Success(); } - public Context GetContext(Script scr) + public RunnableContext GetContext(Script scr) { return new MethodContext(this) { diff --git a/Code/TokenSystem/Tokens/RunFunctionToken.cs b/Code/TokenSystem/Tokens/RunFunctionToken.cs index 94253d7e..e66d9682 100644 --- a/Code/TokenSystem/Tokens/RunFunctionToken.cs +++ b/Code/TokenSystem/Tokens/RunFunctionToken.cs @@ -7,7 +7,7 @@ namespace SER.Code.TokenSystem.Tokens; public class RunFunctionToken : BaseToken, IContextableToken { - public Context GetContext(Script scr) + public RunnableContext? GetContext(Script scr) { return new RunKeyword { diff --git a/Code/TokenSystem/Tokens/VariableTokens/CollectionVariableToken.cs b/Code/TokenSystem/Tokens/VariableTokens/CollectionVariableToken.cs index 747fc5b8..383f2e68 100644 --- a/Code/TokenSystem/Tokens/VariableTokens/CollectionVariableToken.cs +++ b/Code/TokenSystem/Tokens/VariableTokens/CollectionVariableToken.cs @@ -10,7 +10,7 @@ public class CollectionVariableToken : VariableToken "&collection"; - public override Context GetContext(Script scr) + public override RunnableContext? GetContext(Script scr) { return new CollectionVariableDefinitionContext(this) { diff --git a/Code/TokenSystem/Tokens/VariableTokens/LiteralVariableToken.cs b/Code/TokenSystem/Tokens/VariableTokens/LiteralVariableToken.cs index 1eb3b55b..74122c88 100644 --- a/Code/TokenSystem/Tokens/VariableTokens/LiteralVariableToken.cs +++ b/Code/TokenSystem/Tokens/VariableTokens/LiteralVariableToken.cs @@ -8,7 +8,7 @@ namespace SER.Code.TokenSystem.Tokens.VariableTokens; public class LiteralVariableToken : VariableToken { - public override Context GetContext(Script scr) + public override RunnableContext? GetContext(Script scr) { return new LiteralVariableDefinitionContext(this) { diff --git a/Code/TokenSystem/Tokens/VariableTokens/PlayerVariableToken.cs b/Code/TokenSystem/Tokens/VariableTokens/PlayerVariableToken.cs index 5674b1f8..54da8e60 100644 --- a/Code/TokenSystem/Tokens/VariableTokens/PlayerVariableToken.cs +++ b/Code/TokenSystem/Tokens/VariableTokens/PlayerVariableToken.cs @@ -10,7 +10,7 @@ public class PlayerVariableToken : VariableToken { public static string Example => "@players"; - public override Context GetContext(Script scr) + public override RunnableContext? GetContext(Script scr) { return new PlayerVariableDefinitionContext(this) { diff --git a/Code/TokenSystem/Tokens/VariableTokens/ReferenceVariableToken.cs b/Code/TokenSystem/Tokens/VariableTokens/ReferenceVariableToken.cs index fb11d042..cbe5e48e 100644 --- a/Code/TokenSystem/Tokens/VariableTokens/ReferenceVariableToken.cs +++ b/Code/TokenSystem/Tokens/VariableTokens/ReferenceVariableToken.cs @@ -8,7 +8,7 @@ namespace SER.Code.TokenSystem.Tokens.VariableTokens; public class ReferenceVariableToken : VariableToken { - public override Context GetContext(Script scr) + public override RunnableContext? GetContext(Script scr) { return new ReferenceVariableDefinitionContext(this) { diff --git a/Code/TokenSystem/Tokens/VariableTokens/VariableToken.cs b/Code/TokenSystem/Tokens/VariableTokens/VariableToken.cs index 018dd1ed..81963747 100644 --- a/Code/TokenSystem/Tokens/VariableTokens/VariableToken.cs +++ b/Code/TokenSystem/Tokens/VariableTokens/VariableToken.cs @@ -16,7 +16,7 @@ public abstract class VariableToken : BaseToken, IContextableToken public abstract Type ValueType { get; } - public abstract Context GetContext(Script scr); + public abstract RunnableContext? GetContext(Script scr); public static readonly (char prefix, Type varTypeToken)[] VariablePrefixes = [ diff --git a/Code/ValueSystem/BoolValue.cs b/Code/ValueSystem/BoolValue.cs index 84a47f41..2937852a 100644 --- a/Code/ValueSystem/BoolValue.cs +++ b/Code/ValueSystem/BoolValue.cs @@ -18,4 +18,6 @@ public static implicit operator bool(BoolValue value) [UsedImplicitly] public new static string FriendlyName = "boolean (true/false) value"; + + public override Dictionary Properties => []; } \ No newline at end of file diff --git a/Code/ValueSystem/CollectionValue.cs b/Code/ValueSystem/CollectionValue.cs index 412ce330..05e2f4ce 100644 --- a/Code/ValueSystem/CollectionValue.cs +++ b/Code/ValueSystem/CollectionValue.cs @@ -70,6 +70,8 @@ public override bool EqualCondition(Value other) ? throw new TosoksFuckedUpException(error) : val; + public override Dictionary Properties => []; + public TryGet GetAt(int index) { if (index < 1) return $"Provided index {index}, but index cannot be less than 1"; diff --git a/Code/ValueSystem/ColorValue.cs b/Code/ValueSystem/ColorValue.cs index fe06d7c1..f98998e8 100644 --- a/Code/ValueSystem/ColorValue.cs +++ b/Code/ValueSystem/ColorValue.cs @@ -5,4 +5,6 @@ namespace SER.Code.ValueSystem; public class ColorValue(Color color) : LiteralValue(color) { public override string StringRep => Value.ToHex(); + + public override Dictionary Properties => []; } \ No newline at end of file diff --git a/Code/ValueSystem/DurationValue.cs b/Code/ValueSystem/DurationValue.cs index faed7262..285aeb64 100644 --- a/Code/ValueSystem/DurationValue.cs +++ b/Code/ValueSystem/DurationValue.cs @@ -51,4 +51,6 @@ public override string StringRep [UsedImplicitly] public new static string FriendlyName = "duration value"; + + public override Dictionary Properties => []; } \ No newline at end of file diff --git a/Code/ValueSystem/NumberValue.cs b/Code/ValueSystem/NumberValue.cs index 7bee2521..85c9046d 100644 --- a/Code/ValueSystem/NumberValue.cs +++ b/Code/ValueSystem/NumberValue.cs @@ -18,4 +18,6 @@ public static implicit operator decimal(NumberValue value) [UsedImplicitly] public new static string FriendlyName = "number value"; + + public override Dictionary Properties => []; } \ No newline at end of file diff --git a/Code/ValueSystem/PlayerValue.cs b/Code/ValueSystem/PlayerValue.cs index 42fa9871..b569530b 100644 --- a/Code/ValueSystem/PlayerValue.cs +++ b/Code/ValueSystem/PlayerValue.cs @@ -30,4 +30,6 @@ public PlayerValue(IEnumerable players) [UsedImplicitly] public new static string FriendlyName = "player value"; + + public override Dictionary Properties => []; } \ No newline at end of file diff --git a/Code/ValueSystem/ReferenceValue.cs b/Code/ValueSystem/ReferenceValue.cs index 68f075fc..b79a5b3c 100644 --- a/Code/ValueSystem/ReferenceValue.cs +++ b/Code/ValueSystem/ReferenceValue.cs @@ -24,6 +24,8 @@ public override string ToString() { return $"<{Value.GetType().AccurateName} reference | {Value.GetHashCode()}>"; } + + public override Dictionary Properties => []; } public class ReferenceValue(T? value) : ReferenceValue(value) diff --git a/Code/ValueSystem/TextValue.cs b/Code/ValueSystem/TextValue.cs index 27708684..31fc62ed 100644 --- a/Code/ValueSystem/TextValue.cs +++ b/Code/ValueSystem/TextValue.cs @@ -66,6 +66,8 @@ public static string ParseValue(string text, Script script) => ExpressionRegex.R return value.StringRep; }); + + public override Dictionary Properties => []; } public class DynamicTextValue(string text, Script script) : TextValue(text, script) diff --git a/Code/ValueSystem/Value.cs b/Code/ValueSystem/Value.cs index 7c2a810d..6c26a055 100644 --- a/Code/ValueSystem/Value.cs +++ b/Code/ValueSystem/Value.cs @@ -43,6 +43,25 @@ public static Value Parse(object obj, Script? script) _ => new ReferenceValue(obj), }; } + + public abstract class PropInfo + { + public abstract Func Func { get; } + public abstract SingleTypeOfValue ReturnType { get; } + public abstract string? Description { get; } + } + + public class PropInfo(Func handler, string? description) : PropInfo + where T : Value + { + public override Func Func => handler; + public override SingleTypeOfValue ReturnType => new(typeof(T)); + public override string? Description => description; + } + + public abstract Dictionary Properties { get; } + + public static Dictionary GetPropertiesOfValue(Type t) => t.CreateInstance().Properties; public string FriendlyName => GetFriendlyName(GetType()); From 6650f014117f11ccf9835b5d0399c6a12fa36910 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:34:58 +0200 Subject: [PATCH 37/60] improve `end` keyword error --- Code/ContextSystem/Contexter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/ContextSystem/Contexter.cs b/Code/ContextSystem/Contexter.cs index 0b83bf4a..0cf0cde1 100644 --- a/Code/ContextSystem/Contexter.cs +++ b/Code/ContextSystem/Contexter.cs @@ -61,7 +61,7 @@ List contexts case EndKeyword: { if (statementStack.Count == 0) - return rs + "There is no statement to close with the 'end' keyword!"; + return rs + "There is no valid statement to close with the 'end' keyword! Check if the statement you are trying to close hasn't thrown an error when compiling.".AsError(); statementStack.Pop(); return true; From f8f59c3021a4f1385e171784ec296729d5baadeb Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sun, 29 Mar 2026 13:16:30 +0200 Subject: [PATCH 38/60] update --- Code/ContextSystem/BaseContexts/Context.cs | 2 +- .../ContextSystem/BaseContexts/LoopContext.cs | 2 +- .../Contexts/Control/AttemptStatement.cs | 2 +- .../Contexts/Control/ElifStatement.cs | 2 +- .../Contexts/Control/ElseStatement.cs | 2 +- .../Contexts/Control/EndKeyword.cs | 2 +- .../Contexts/Control/IfStatement.cs | 2 +- .../Contexts/Control/Loops/BreakKeyword.cs | 2 +- .../Contexts/Control/Loops/ContinueKeyword.cs | 2 +- .../Contexts/Control/OnErrorStatement.cs | 2 +- .../Contexts/Control/ReturnKeyword.cs | 2 +- .../Contexts/Control/StopKeyword.cs | 2 +- Code/ContextSystem/Contexts/FuncStatement.cs | 2 +- Code/ContextSystem/Contexts/GlobalKeyword.cs | 2 +- Code/ContextSystem/Contexts/MethodContext.cs | 2 +- Code/ContextSystem/Contexts/RunKeyword.cs | 2 +- .../Contexts/ValueExpressionContext.cs | 107 +++++++++++++----- .../VariableDefinitionContext.cs | 2 +- Code/ContextSystem/Contexts/WithKeyword.cs | 2 +- .../Methods/ItemMethods/ItemInfoMethod.cs | 5 +- .../FilterPlayersMethod.cs | 1 - .../PlayerVariableMethods/ShowMethod.cs | 1 - .../Commands/HelpSystem/DocsProvider.cs | 1 - .../Commands/HelpSystem/HelpInfoStorage.cs | 1 - Code/TokenSystem/Tokenizer.cs | 5 +- .../ExpressionTokens/ExpressionToken.cs | 35 +++++- Code/TokenSystem/Tokens/SymbolToken.cs | 9 +- 27 files changed, 138 insertions(+), 63 deletions(-) diff --git a/Code/ContextSystem/BaseContexts/Context.cs b/Code/ContextSystem/BaseContexts/Context.cs index 04f0d2ce..c832f451 100644 --- a/Code/ContextSystem/BaseContexts/Context.cs +++ b/Code/ContextSystem/BaseContexts/Context.cs @@ -9,7 +9,7 @@ public abstract class Context { public required Script Script { get; set; } = null!; - protected abstract string FriendlyName { get; } + public abstract string FriendlyName { get; } public abstract TryAddTokenRes TryAddToken(BaseToken token); diff --git a/Code/ContextSystem/BaseContexts/LoopContext.cs b/Code/ContextSystem/BaseContexts/LoopContext.cs index c04fa942..f06bbd9a 100644 --- a/Code/ContextSystem/BaseContexts/LoopContext.cs +++ b/Code/ContextSystem/BaseContexts/LoopContext.cs @@ -26,7 +26,7 @@ public abstract class LoopContext : StatementContext, IExtendableStatement, IKey protected bool ReceivedContinue; protected bool ReceivedBreak; - protected sealed override string FriendlyName => $"'{KeywordName}' loop statement"; + public sealed override string FriendlyName => $"'{KeywordName}' loop statement"; protected override void OnReceivedControlMessageFromChild(ParentContextControlMessage msg) { diff --git a/Code/ContextSystem/Contexts/Control/AttemptStatement.cs b/Code/ContextSystem/Contexts/Control/AttemptStatement.cs index bfa7a0ce..5519e924 100644 --- a/Code/ContextSystem/Contexts/Control/AttemptStatement.cs +++ b/Code/ContextSystem/Contexts/Control/AttemptStatement.cs @@ -28,7 +28,7 @@ public class AttemptStatement : StatementContext, IExtendableStatement, IKeyword public IExtendableStatement.Signal AllowedSignals => IExtendableStatement.Signal.ThrewException; public Dictionary RegisteredSignals { get; } = []; - protected override string FriendlyName => "'attempt' statement"; + public override string FriendlyName => "'attempt' statement"; private Exception? _exception; diff --git a/Code/ContextSystem/Contexts/Control/ElifStatement.cs b/Code/ContextSystem/Contexts/Control/ElifStatement.cs index bf0595c0..ce0ac67b 100644 --- a/Code/ContextSystem/Contexts/Control/ElifStatement.cs +++ b/Code/ContextSystem/Contexts/Control/ElifStatement.cs @@ -31,7 +31,7 @@ public class ElifStatement : StatementContext, IStatementExtender, IExtendableSt private NumericExpressionReslover.CompiledExpression _expression; - protected override string FriendlyName => "'elif' statement"; + public override string FriendlyName => "'elif' statement"; public override TryAddTokenRes TryAddToken(BaseToken token) { diff --git a/Code/ContextSystem/Contexts/Control/ElseStatement.cs b/Code/ContextSystem/Contexts/Control/ElseStatement.cs index c80b2d45..dc2c740b 100644 --- a/Code/ContextSystem/Contexts/Control/ElseStatement.cs +++ b/Code/ContextSystem/Contexts/Control/ElseStatement.cs @@ -21,7 +21,7 @@ public class ElseStatement : StatementContext, IStatementExtender, IKeywordConte public IExtendableStatement.Signal Extends => IExtendableStatement.Signal.DidntExecute; - protected override string FriendlyName => "'else' statement"; + public override string FriendlyName => "'else' statement"; public override TryAddTokenRes TryAddToken(BaseToken token) { diff --git a/Code/ContextSystem/Contexts/Control/EndKeyword.cs b/Code/ContextSystem/Contexts/Control/EndKeyword.cs index 792e70c7..f76120e7 100644 --- a/Code/ContextSystem/Contexts/Control/EndKeyword.cs +++ b/Code/ContextSystem/Contexts/Control/EndKeyword.cs @@ -11,7 +11,7 @@ namespace SER.Code.ContextSystem.Contexts.Control; public class EndKeyword : StandardContext, IKeywordContext { public string KeywordName => "end"; - protected override string FriendlyName => "'end' keyword"; + public override string FriendlyName => "'end' keyword"; public string Description => "Ends the current statement's body."; public string[] Arguments => []; public string? Example => null; diff --git a/Code/ContextSystem/Contexts/Control/IfStatement.cs b/Code/ContextSystem/Contexts/Control/IfStatement.cs index 8703f14d..8d21f19f 100644 --- a/Code/ContextSystem/Contexts/Control/IfStatement.cs +++ b/Code/ContextSystem/Contexts/Control/IfStatement.cs @@ -27,7 +27,7 @@ public class IfStatement : StatementContext, IExtendableStatement, IKeywordConte private NumericExpressionReslover.CompiledExpression _expression; - protected override string FriendlyName => "'if' statement"; + public override string FriendlyName => "'if' statement"; public override TryAddTokenRes TryAddToken(BaseToken token) { diff --git a/Code/ContextSystem/Contexts/Control/Loops/BreakKeyword.cs b/Code/ContextSystem/Contexts/Control/Loops/BreakKeyword.cs index d0fab4c8..87cdf7ef 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/BreakKeyword.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/BreakKeyword.cs @@ -33,7 +33,7 @@ Wait 1s end """; - protected override string FriendlyName => "'break' keyword"; + public override string FriendlyName => "'break' keyword"; public override TryAddTokenRes TryAddToken(BaseToken token) { diff --git a/Code/ContextSystem/Contexts/Control/Loops/ContinueKeyword.cs b/Code/ContextSystem/Contexts/Control/Loops/ContinueKeyword.cs index f5c92eec..4e0b2d1c 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/ContinueKeyword.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/ContinueKeyword.cs @@ -19,7 +19,7 @@ public class ContinueKeyword : StandardContext, IKeywordContext public string? Example => null; - protected override string FriendlyName => "'continue' keyword"; + public override string FriendlyName => "'continue' keyword"; public override TryAddTokenRes TryAddToken(BaseToken token) { diff --git a/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs b/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs index dbe768e9..0c32c5a5 100644 --- a/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs +++ b/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs @@ -45,7 +45,7 @@ public class OnErrorStatement : StatementContext, IStatementExtender, IKeywordCo """; public IExtendableStatement.Signal Extends => IExtendableStatement.Signal.ThrewException; - protected override string FriendlyName => "'on_error' statement"; + public override string FriendlyName => "'on_error' statement"; public Exception? Exception { diff --git a/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs b/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs index 0f3311a4..1c9cf5cc 100644 --- a/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs +++ b/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs @@ -22,7 +22,7 @@ public class ReturnKeyword : StandardContext, IKeywordContext public string[] Arguments => ["[return value]"]; public string? Example => null; - protected override string FriendlyName => "'return' keyword"; + public override string FriendlyName => "'return' keyword"; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Code/ContextSystem/Contexts/Control/StopKeyword.cs b/Code/ContextSystem/Contexts/Control/StopKeyword.cs index 809c4e41..9e727adc 100644 --- a/Code/ContextSystem/Contexts/Control/StopKeyword.cs +++ b/Code/ContextSystem/Contexts/Control/StopKeyword.cs @@ -19,7 +19,7 @@ public class StopKeyword : StandardContext, IKeywordContext public string? Example => null; - protected override string FriendlyName => "'stop' keyword"; + public override string FriendlyName => "'stop' keyword"; //////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Code/ContextSystem/Contexts/FuncStatement.cs b/Code/ContextSystem/Contexts/FuncStatement.cs index 43c2838e..0a4d8204 100644 --- a/Code/ContextSystem/Contexts/FuncStatement.cs +++ b/Code/ContextSystem/Contexts/FuncStatement.cs @@ -51,7 +51,7 @@ public TypeOfValue? Returns public string MissingValueHint => "Maybe you forgot to use the 'return' keyword?"; public string UndefinedReturnsHint => "Maybe you forgot to define the return type in the function name?"; - protected override string FriendlyName => + public override string FriendlyName => FunctionName is not null ? $"'{FunctionName}' function definition statement" : "function definition statement"; diff --git a/Code/ContextSystem/Contexts/GlobalKeyword.cs b/Code/ContextSystem/Contexts/GlobalKeyword.cs index ada2d5dc..425742e7 100644 --- a/Code/ContextSystem/Contexts/GlobalKeyword.cs +++ b/Code/ContextSystem/Contexts/GlobalKeyword.cs @@ -14,7 +14,7 @@ namespace SER.Code.ContextSystem.Contexts; [UsedImplicitly] public class GlobalKeyword : YieldingContext, IKeywordContext { - protected override string FriendlyName => + public override string FriendlyName => $"global{(_variableToken is null ? "" : $" '{_variableToken.RawRep}'")} variable definition"; public string KeywordName => "global"; diff --git a/Code/ContextSystem/Contexts/MethodContext.cs b/Code/ContextSystem/Contexts/MethodContext.cs index 5d4f65d3..9de54099 100644 --- a/Code/ContextSystem/Contexts/MethodContext.cs +++ b/Code/ContextSystem/Contexts/MethodContext.cs @@ -32,7 +32,7 @@ public class MethodContext(MethodToken methodToken) : YieldingContext, IMayRetur public string MissingValueHint => "This method did not return a value. This may be a SER bug."; public string UndefinedReturnsHint => "This method does not define a return type. This may be a SER bug."; - protected override string FriendlyName => $"'{Method.Name}' method call"; + public override string FriendlyName => $"'{Method.Name}' method call"; public override TryAddTokenRes TryAddToken(BaseToken token) { diff --git a/Code/ContextSystem/Contexts/RunKeyword.cs b/Code/ContextSystem/Contexts/RunKeyword.cs index be4c3580..db886ef3 100644 --- a/Code/ContextSystem/Contexts/RunKeyword.cs +++ b/Code/ContextSystem/Contexts/RunKeyword.cs @@ -22,7 +22,7 @@ public class RunKeyword : YieldingContext, IMayReturnValueContext public string MissingValueHint => _functionDefinitionContext?.MissingValueHint ?? "Function is not defined."; public string UndefinedReturnsHint => _functionDefinitionContext?.UndefinedReturnsHint ?? "Function is not defined."; - protected override string FriendlyName => + public override string FriendlyName => _functionDefinitionContext is not null ? $"'{_functionDefinitionContext.FunctionName}' function call" : "function call"; diff --git a/Code/ContextSystem/Contexts/ValueExpressionContext.cs b/Code/ContextSystem/Contexts/ValueExpressionContext.cs index c3706e7e..31a61657 100644 --- a/Code/ContextSystem/Contexts/ValueExpressionContext.cs +++ b/Code/ContextSystem/Contexts/ValueExpressionContext.cs @@ -18,35 +18,41 @@ namespace SER.Code.ContextSystem.Contexts; /// public class ValueExpressionContext : AdditionalContext { - private readonly BaseToken _initial; - public abstract class Handler { public abstract TryGet GetReturnValue(); public abstract TryAddTokenRes TryAddToken(BaseToken token); public abstract Result VerifyCurrentState(); public abstract IEnumerator Run(); + public abstract string FriendlyName { get; } + public abstract TypeOfValue PossibleValues { get; } } + private readonly BaseToken _initial; + private readonly IValueToken? _initialValueToken; private readonly string? _error; private Handler? _handler; /// /// Used to unify method calls, math expressions and property access into a single context that returns a value. /// - public ValueExpressionContext(BaseToken initial, bool allowsYielding, RunnableContext parent) + public ValueExpressionContext(BaseToken initial, bool allowsYielding) { _initial = initial; try { if (initial is MethodToken methodToken) { - _handler = new MethodHandler(methodToken, allowsYielding, parent); + _handler = new MethodHandler(methodToken, allowsYielding, initial.Script); } - else if (initial is not IValueToken) + else if (initial is not IValueToken valToken) { _error = $"{initial} is not a valid way to get a value."; } + else + { + _initialValueToken = valToken; + } } catch (Exception e) { @@ -54,45 +60,70 @@ public ValueExpressionContext(BaseToken initial, bool allowsYielding, RunnableCo } } - protected override string FriendlyName => "value expression"; + public override string FriendlyName => _handler?.FriendlyName ?? "value expression"; + + public TypeOfValue PossibleValues => _handler?.PossibleValues ?? new UnknownTypeOfValue(); public override TryAddTokenRes TryAddToken(BaseToken token) { - if (_error is not null) return TryAddTokenRes.Error(_error); + if (_error is not null) + { + return TryAddTokenRes.Error(_error); + } - _handler ??= token switch + if (_handler is not null) { - SymbolToken => new GenericValueExpressionResolver(_initial, Script), - // we assume that an unknown token means property access, but thats kinda sloppy - _ => new ValuePropertyHandler(_initial, (IValueToken)_initial) + goto try_add_token; + } + + if (token is not SymbolToken symbol) + { + return TryAddTokenRes.Error($"{token} is not a valid way to get a value with {_initial}"); + } + + _handler = symbol switch + { + { IsArrow: false } => new NumericExpressionHandler(_initial, Script), + { IsArrow: true } => new ValuePropertyHandler(_initial, (IValueToken)_initial) }; - + + try_add_token: return _handler.TryAddToken(token); } public override Result VerifyCurrentState() { if (_error is not null) return _error; - if (_handler is null) return $"{_initial} is not a valid way to get a value."; + if (_handler is null) return true; return _handler.VerifyCurrentState(); } + /// + /// If the context is not yielding, disregard the return value. + /// public IEnumerator Run() { var coro = _handler!.Run(); while (coro.MoveNext()) yield return coro.Current; } + + public TryGet GetValue() => + _handler?.GetReturnValue() + ?? _initialValueToken?.Value() + ?? throw new AndrzejFuckedUpException(); } public class MethodHandler : ValueExpressionContext.Handler { - private readonly RunnableContext _parent; private readonly MethodContext _context; - public MethodHandler(MethodToken token, bool allowsYielding, RunnableContext parent) + public MethodHandler(MethodToken token, bool allowsYielding, Script scr) { - _parent = parent; - _context = (MethodContext)token.GetContext(token.Script); + _context = new MethodContext(token) + { + Script = scr, + LineNum = null + }; var method = token.Method; if (method is not IReturningMethod) @@ -104,8 +135,17 @@ public MethodHandler(MethodToken token, bool allowsYielding, RunnableContext par $"Consider making a variable and using that variable instead."); } - public override TryGet GetReturnValue() => _context.ReturnedValue - ?? throw new ScriptRuntimeError(_parent, _context.MissingValueHint); + public override string FriendlyName => _context.FriendlyName; + + public override TypeOfValue PossibleValues => + _context.Returns + ?? throw new AndrzejFuckedUpException("Method has no return type."); + + public override TryGet GetReturnValue() + { + if (_context.ReturnedValue is { } value) return value; + return _context.MissingValueHint; + } public override TryAddTokenRes TryAddToken(BaseToken token) { @@ -132,14 +172,17 @@ public class ValuePropertyHandler( IValueToken valueToken) : ValueExpressionContext.Handler { private readonly Queue _propertyNames = []; - private string _valueRepresentation = baseToken.RawRep; + private string _exprRepr = baseToken.RawRep; private TypeOfValue _lastValueType = valueToken.PossibleValues; + public override string FriendlyName => "property access"; + public override TypeOfValue PossibleValues => _lastValueType; + public override TryGet GetReturnValue() { if (valueToken.Value().HasErrored(out var error, out var value)) { - return $"Failed to get value from '{_valueRepresentation}'".AsError() + return $"Failed to get value from '{_exprRepr}'".AsError() + error.AsError(); } @@ -160,6 +203,11 @@ public override TryGet GetReturnValue() public override TryAddTokenRes TryAddToken(BaseToken token) { + if (token is SymbolToken { IsArrow: true }) + { + return TryAddTokenRes.Continue(); + } + // type verification if (_lastValueType.AreKnown(out var types)) { @@ -167,20 +215,23 @@ public override TryAddTokenRes TryAddToken(BaseToken token) { if (Value.GetPropertiesOfValue(type).TryGetValue(token.RawRep, out var property)) { - _valueRepresentation += $" {token.RawRep}"; + _exprRepr += $" {token.RawRep}"; _lastValueType = property.ReturnType; break; } } - return TryAddTokenRes.Error($"'{token.RawRep}' is not a valid property of '{_valueRepresentation}' value."); + return TryAddTokenRes.Error($"'{token.RawRep}' is not a valid property of '{_exprRepr}' value."); } _propertyNames.Enqueue(token.RawRep); return TryAddTokenRes.Continue(); } - public override Result VerifyCurrentState() => true; + public override Result VerifyCurrentState() => Result.Assert( + _propertyNames.Count > 0, + $"The '{SymbolToken.Arrow}' operator was used, but no property to be accessed was specified." + ); public override IEnumerator Run() { @@ -191,12 +242,16 @@ public override IEnumerator Run() /// /// Used for math expressions. /// -public class GenericValueExpressionResolver(BaseToken initial, Script scr) +public class NumericExpressionHandler(BaseToken initial, Script scr) : ValueExpressionContext.Handler { private readonly List _tokens = [initial]; private Safe _expression; - + + public override string FriendlyName => $"numeric expression"; + + public override TypeOfValue PossibleValues => new UnknownTypeOfValue(); + public override TryGet GetReturnValue() { return _expression.Value.Evaluate().OnSuccess(obj => Value.Parse(obj, scr)); diff --git a/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs b/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs index 4f346733..ba5f3174 100644 --- a/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs +++ b/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs @@ -36,7 +36,7 @@ protected virtual (TryAddTokenRes result, Func parser) AdditionalParsing private (RunnableContext main, IMayReturnValueContext returner)? _returnContext = null; private Func? _parser = null; - protected override string FriendlyName => $"'{varToken.RawRep}' variable definition"; + public override string FriendlyName => $"'{varToken.RawRep}' variable definition"; public override TryAddTokenRes TryAddToken(BaseToken token) { diff --git a/Code/ContextSystem/Contexts/WithKeyword.cs b/Code/ContextSystem/Contexts/WithKeyword.cs index 1f3a3354..1b3c7bd5 100644 --- a/Code/ContextSystem/Contexts/WithKeyword.cs +++ b/Code/ContextSystem/Contexts/WithKeyword.cs @@ -56,7 +56,7 @@ public Result AcceptStatement(StatementContext context) return true; } - protected override string FriendlyName => "'with' keyword"; + public override string FriendlyName => "'with' keyword"; public override TryAddTokenRes TryAddToken(BaseToken token) { diff --git a/Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs b/Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs index beea969a..d8d90f2e 100644 --- a/Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs +++ b/Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs @@ -1,11 +1,10 @@ -using JetBrains.Annotations; +/*using JetBrains.Annotations; using LabApi.Features.Wrappers; using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.ArgumentSystem.Structures; using SER.Code.MethodSystem.BaseMethods.Synchronous; using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.TokenSystem.Tokens.ExpressionTokens; using SER.Code.ValueSystem; namespace SER.Code.MethodSystem.Methods.ItemMethods; @@ -39,4 +38,4 @@ public override void Execute() [Args.GetOption("property")] .Handler(item); } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs b/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs index abf2dee5..54f2b340 100644 --- a/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs +++ b/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs @@ -2,7 +2,6 @@ using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.TokenSystem.Tokens.ExpressionTokens; using SER.Code.ValueSystem; namespace SER.Code.MethodSystem.Methods.PlayerVariableMethods; diff --git a/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs b/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs index 8f5e8457..2f3efd07 100644 --- a/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs +++ b/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs @@ -5,7 +5,6 @@ using SER.Code.Extensions; using SER.Code.MethodSystem.BaseMethods.Synchronous; using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.TokenSystem.Tokens.ExpressionTokens; using SER.Code.ValueSystem; namespace SER.Code.MethodSystem.Methods.PlayerVariableMethods; diff --git a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs index a830ac0d..aa4b837f 100644 --- a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs +++ b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs @@ -14,7 +14,6 @@ using SER.Code.MethodSystem.MethodDescriptors; using SER.Code.Plugin.Commands.Interfaces; using SER.Code.TokenSystem.Tokens; -using SER.Code.TokenSystem.Tokens.ExpressionTokens; using SER.Code.ValueSystem; using SER.Code.VariableSystem; using SER.Code.VariableSystem.Variables; diff --git a/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs b/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs index 7e5b2424..3c56e1fe 100644 --- a/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs +++ b/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs @@ -2,7 +2,6 @@ using LabApi.Features.Enums; using MapGeneration; using SER.Code.FlagSystem.Flags; -using SER.Code.TokenSystem.Tokens.ExpressionTokens; namespace SER.Code.Plugin.Commands.HelpSystem; diff --git a/Code/TokenSystem/Tokenizer.cs b/Code/TokenSystem/Tokenizer.cs index fdd695e6..c8afcece 100644 --- a/Code/TokenSystem/Tokenizer.cs +++ b/Code/TokenSystem/Tokenizer.cs @@ -34,10 +34,7 @@ public static class Tokenizer public static readonly Type[] OrderedImportanceTokensFromCollectionSlices = [ - typeof(ReferenceVariableExpressionToken), - typeof(PlayerExpressionToken), - typeof(MethodExpressionToken), - typeof(LiteralVariableExpressionToken), + typeof(ExpressionToken), typeof(ParenthesesToken), typeof(TextToken) ]; diff --git a/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs b/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs index d7e352a0..aec6c210 100644 --- a/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs +++ b/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs @@ -1,5 +1,5 @@ -using SER.Code.ContextSystem.BaseContexts; -using SER.Code.ContextSystem.Contexts; +using SER.Code.ContextSystem.Contexts; +using SER.Code.Exceptions; using SER.Code.Extensions; using SER.Code.Helpers.ResultSystem; using SER.Code.ScriptSystem; @@ -12,6 +12,8 @@ namespace SER.Code.TokenSystem.Tokens.ExpressionTokens; public class ExpressionToken : BaseToken, IValueToken { + private ValueExpressionContext? _context; + protected override IParseResult InternalParse(Script scr) { if (Slice is not CollectionSlice { Type: CollectionBrackets.Curly } collection) @@ -31,10 +33,31 @@ protected override IParseResult InternalParse(Script scr) return new Error($"Expression '{collection.Value}' is empty."); } - var ctx = new ValueExpressionContext(tokens[0], false, ); + _context = new ValueExpressionContext(tokens[0], false) + { + Script = scr + }; + + foreach (var token in tokens.Skip(1)) + { + var res = _context.TryAddToken(token); + if (res.ShouldContinueExecution) + { + continue; + } + + if (res.HasErrored) + { + return new Error(res.ErrorMessage); + } + } + if (_context.VerifyCurrentState().HasErrored(out var error2)) + { + return new Error(error2); + } - return + return new Success(); } public static TryGet TryParse(CollectionSlice slice, Script script) @@ -52,7 +75,7 @@ public static TryGet TryParse(CollectionSlice slice, Script scr return expToken; } - public abstract TryGet Value(); - public abstract TypeOfValue PossibleValues { get; } + public TryGet Value() => _context?.GetValue() ?? throw new AndrzejFuckedUpException(); + public TypeOfValue PossibleValues => _context?.PossibleValues ?? throw new AndrzejFuckedUpException(); public bool IsConstant => false; } \ No newline at end of file diff --git a/Code/TokenSystem/Tokens/SymbolToken.cs b/Code/TokenSystem/Tokens/SymbolToken.cs index fccbcc65..b295365d 100644 --- a/Code/TokenSystem/Tokens/SymbolToken.cs +++ b/Code/TokenSystem/Tokens/SymbolToken.cs @@ -4,8 +4,13 @@ namespace SER.Code.TokenSystem.Tokens; public class SymbolToken : BaseToken { - public bool IsJoker => RawRep == "*"; - public bool IsFloor => RawRep == "_"; + public const string Joker = "*"; + public const string Floor = "_"; + public const string Arrow = "->"; + + public bool IsJoker => RawRep == Joker; + public bool IsFloor => RawRep == Floor; + public bool IsArrow => RawRep == Arrow; protected override IParseResult InternalParse(Script scr) { From 4dd93eff4d780011b62af2e78a1739ac6babb0f1 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sun, 29 Mar 2026 15:35:35 +0200 Subject: [PATCH 39/60] fix TextArgument parsing of constant values --- Code/ArgumentSystem/Arguments/TextArgument.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Code/ArgumentSystem/Arguments/TextArgument.cs b/Code/ArgumentSystem/Arguments/TextArgument.cs index 214d48cc..b3f190cd 100644 --- a/Code/ArgumentSystem/Arguments/TextArgument.cs +++ b/Code/ArgumentSystem/Arguments/TextArgument.cs @@ -1,11 +1,13 @@ using JetBrains.Annotations; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.Extensions; +using SER.Code.Helpers; using SER.Code.Helpers.ResultSystem; using SER.Code.TokenSystem.Tokens; using SER.Code.TokenSystem.Tokens.Interfaces; using SER.Code.TokenSystem.Tokens.ValueTokens; using SER.Code.ValueSystem; +using ZstdSharp.Unsafe; namespace SER.Code.ArgumentSystem.Arguments; @@ -32,10 +34,10 @@ public DynamicTryGet GetConvertSolution(BaseToken token) return DynamicTryGet.Error("Value cannot represent text."); } - + if (valToken.IsConstant) { - return SpaceCheck(get().OnSuccess(v => v.StringRep)); + return get().OnSuccess(v => SpaceCheck(v.StringRep)); } return new(() => get().OnSuccess(v => SpaceCheck(v.StringRep))); @@ -44,7 +46,7 @@ TryGet SpaceCheck(string value) { if (!allowsSpaces && value.Any(char.IsWhiteSpace)) { - return $"Value contains spaces, which are not allowed".AsError(); + return "Value contains spaces, which are not allowed".AsError(); } return value.AsSuccess(); From a5da184f3c0a81200ccebd61f47134aa673580e3 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Sun, 29 Mar 2026 15:36:03 +0200 Subject: [PATCH 40/60] update implementations of value properties --- .../Contexts/Control/Loops/OverLoop.cs | 6 +- .../Contexts/ValueExpressionContext.cs | 88 +++++++++- .../LiteralVariableDefinitionContext.cs | 21 +-- .../PlayerVariableDefinitionContext.cs | 19 +- .../VariableDefinitionContext.cs | 109 +++--------- Code/ContextSystem/Contexts/WithKeyword.cs | 6 +- Code/Examples/ChaosCoinScript.cs | 23 +-- Code/Examples/DiscordServerInfoScript.cs | 2 +- Code/Examples/GnomingTimeScript.cs | 4 +- Code/Examples/HotPotatoScript.cs | 4 +- Code/Examples/PinataLootScript.cs | 2 +- Code/Examples/RaveScript.cs | 2 +- .../Methods/ItemMethods/ItemInfoMethod.cs | 25 ++- .../PlayerMethods/SetCustomInfoMethod.cs | 2 +- .../FilterPlayersMethod.cs | 7 +- .../PlayerVariableMethods/ShowMethod.cs | 11 +- .../Commands/HelpSystem/DocsProvider.cs | 4 +- .../Commands/HelpSystem/HelpInfoStorage.cs | 3 +- .../ExpressionTokens/ExpressionToken.cs | 8 +- .../Tokens/ValueTokens/NumberToken.cs | 7 +- Code/ValueSystem/BoolValue.cs | 3 + Code/ValueSystem/CollectionValue.cs | 3 + Code/ValueSystem/ColorValue.cs | 8 + Code/ValueSystem/DurationValue.cs | 3 + Code/ValueSystem/NumberValue.cs | 3 + Code/ValueSystem/PlayerValue.cs | 165 +++++++++++++++++- Code/ValueSystem/ReferenceValue.cs | 8 + Code/ValueSystem/TextValue.cs | 8 + Code/ValueSystem/Value.cs | 23 ++- 29 files changed, 394 insertions(+), 183 deletions(-) diff --git a/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs b/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs index d169b772..4aa1ed35 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs @@ -41,11 +41,11 @@ over @all over @all with @plr - Print "found player {@plr name}" + Print "found player {@plr -> name}" end # this also works for collections: - &inventory = {@sender inventory} + &inventory = @sender -> inventory over &inventory with *item @@ -61,7 +61,7 @@ with @plr over @all with @plr $index - Print "found player #{$index}: {@plr name}" + Print "found player #{$index}: {@plr -> name}" end """; diff --git a/Code/ContextSystem/Contexts/ValueExpressionContext.cs b/Code/ContextSystem/Contexts/ValueExpressionContext.cs index 31a61657..f2aa0d63 100644 --- a/Code/ContextSystem/Contexts/ValueExpressionContext.cs +++ b/Code/ContextSystem/Contexts/ValueExpressionContext.cs @@ -45,6 +45,10 @@ public ValueExpressionContext(BaseToken initial, bool allowsYielding) { _handler = new MethodHandler(methodToken, allowsYielding, initial.Script); } + else if (initial is KeywordToken { RawRep: "run"} ) + { + _handler = new FunctionCallHandler(initial.Script); + } else if (initial is not IValueToken valToken) { _error = $"{initial} is not a valid way to get a value."; @@ -103,7 +107,8 @@ public override Result VerifyCurrentState() /// public IEnumerator Run() { - var coro = _handler!.Run(); + if (_handler is null) yield break; + var coro = _handler.Run(); while (coro.MoveNext()) yield return coro.Current; } @@ -192,10 +197,15 @@ public override TryGet GetReturnValue() var prop = _propertyNames.Dequeue(); if (!current.Properties.TryGetValue(prop, out var propInfo)) { - return $"{value} does not have property '{prop}'."; + return $"{current} does not have property '{prop}'."; + } + + if (propInfo.GetValue(current).HasErrored(out var fetchError, out var fetchedValue)) + { + return fetchError; } - current = propInfo.Func(current); + current = fetchedValue; } return current; @@ -205,6 +215,7 @@ public override TryAddTokenRes TryAddToken(BaseToken token) { if (token is SymbolToken { IsArrow: true }) { + _exprRepr += $" {token.RawRep}"; return TryAddTokenRes.Continue(); } @@ -217,13 +228,14 @@ public override TryAddTokenRes TryAddToken(BaseToken token) { _exprRepr += $" {token.RawRep}"; _lastValueType = property.ReturnType; - break; + goto found; } } return TryAddTokenRes.Error($"'{token.RawRep}' is not a valid property of '{_exprRepr}' value."); } + found: _propertyNames.Enqueue(token.RawRep); return TryAddTokenRes.Continue(); } @@ -278,4 +290,72 @@ public override IEnumerator Run() { yield break; } +} + +public class FunctionCallHandler(Script scr) : ValueExpressionContext.Handler +{ + private FuncStatement? _func; + private readonly List _providedValues = []; + + public override TryGet GetReturnValue() + { + if (_func!.ReturnedValue is { } value) return value; + return _func.MissingValueHint; + } + + public override TryAddTokenRes TryAddToken(BaseToken token) + { + if (_func is null) + { + if (scr.DefinedFunctions.TryGetValue(token.RawRep, out var func)) + { + _func = func; + } + else + { + return TryAddTokenRes.Error($"Function '{token.RawRep}' is not defined."); + } + } + + if (token is IValueToken valToken) + { + _providedValues.Add(valToken); + return TryAddTokenRes.Continue(); + } + + return TryAddTokenRes.Error($"Unexpected token '{token.RawRep}'"); + } + + public override Result VerifyCurrentState() + { + return Result.Assert( + _func is not null, + "Function to run was not provided." + ); + } + + public override IEnumerator Run() + { + List varsToProvide = []; + foreach (var valToken in _providedValues) + { + if (valToken.Value().HasErrored(out var error, out var variable)) + { + throw new ScriptRuntimeError(_func!, + $"Cannot run {_func!.FriendlyName}: {error}" + ); + } + + varsToProvide.Add(variable); + } + + var coro = _func!.RunProperly(varsToProvide.ToArray()); + while (coro.MoveNext()) yield return coro.Current; + } + + public override string FriendlyName => _func!.FriendlyName; + + public override TypeOfValue PossibleValues => + _func?.Returns + ?? throw new AndrzejFuckedUpException("Function has no return type."); } \ No newline at end of file diff --git a/Code/ContextSystem/Contexts/VariableDefinition/LiteralVariableDefinitionContext.cs b/Code/ContextSystem/Contexts/VariableDefinition/LiteralVariableDefinitionContext.cs index 6261f6e1..6b4ccf75 100644 --- a/Code/ContextSystem/Contexts/VariableDefinition/LiteralVariableDefinitionContext.cs +++ b/Code/ContextSystem/Contexts/VariableDefinition/LiteralVariableDefinitionContext.cs @@ -1,25 +1,10 @@ -using SER.Code.TokenSystem.Tokens.ValueTokens; -using SER.Code.TokenSystem.Tokens.VariableTokens; +using SER.Code.TokenSystem.Tokens.VariableTokens; using SER.Code.ValueSystem; using SER.Code.VariableSystem.Variables; namespace SER.Code.ContextSystem.Contexts.VariableDefinition; -public class LiteralVariableDefinitionContext : - VariableDefinitionContext, LiteralValue, LiteralVariable> -{ - public LiteralVariableDefinitionContext(VariableToken varToken) : base(varToken) - { - AdditionalTokenParser = token => - { - if (token is TextToken textToken) - { - return () => textToken.Value; - } - - return null; - }; - } -} +public class LiteralVariableDefinitionContext(VariableToken varToken) : + VariableDefinitionContext, LiteralValue, LiteralVariable>(varToken); diff --git a/Code/ContextSystem/Contexts/VariableDefinition/PlayerVariableDefinitionContext.cs b/Code/ContextSystem/Contexts/VariableDefinition/PlayerVariableDefinitionContext.cs index c386d9be..88e85655 100644 --- a/Code/ContextSystem/Contexts/VariableDefinition/PlayerVariableDefinitionContext.cs +++ b/Code/ContextSystem/Contexts/VariableDefinition/PlayerVariableDefinitionContext.cs @@ -8,21 +8,4 @@ namespace SER.Code.ContextSystem.Contexts.VariableDefinition; public class PlayerVariableDefinitionContext(VariableToken varToken) : - VariableDefinitionContext, PlayerValue, PlayerVariable>(varToken) -{ - protected override (TryAddTokenRes result, Func parser) AdditionalParsing(BaseToken token) - { - if (token is ParenthesesToken { RawContent: "" }) - { - Log.ScriptWarn( - token.Script, - $"Using () to create an empty player variable will be removed in future versions of SER. " + - $"Please use the @empty variable to create an empty variable instead." - ); - - return (TryAddTokenRes.End(), () => new([])); - } - - return base.AdditionalParsing(token); - } -} \ No newline at end of file + VariableDefinitionContext, PlayerValue, PlayerVariable>(varToken); \ No newline at end of file diff --git a/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs b/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs index ba5f3174..c336b73b 100644 --- a/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs +++ b/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs @@ -1,16 +1,13 @@ using SER.Code.ContextSystem.BaseContexts; -using SER.Code.ContextSystem.Extensions; -using SER.Code.ContextSystem.Interfaces; +using SER.Code.ContextSystem.Contexts; using SER.Code.ContextSystem.Structures; using SER.Code.Exceptions; using SER.Code.Extensions; using SER.Code.Helpers.ResultSystem; -using SER.Code.TokenSystem.Structures; using SER.Code.TokenSystem.Tokens; using SER.Code.TokenSystem.Tokens.VariableTokens; using SER.Code.ValueSystem; using SER.Code.VariableSystem.Bases; -using Log = SER.Code.Helpers.Log; namespace SER.Code.ContextSystem.Contexts.VariableDefinition; @@ -25,16 +22,8 @@ public abstract class VariableDefinitionContext(TV where TValue : Value where TVariable : Variable { - protected virtual (TryAddTokenRes result, Func parser) AdditionalParsing(BaseToken token) - { - return (TryAddTokenRes.Error($"Value '{token.RawRep}' ({token.GetType().AccurateName}) cannot be assigned to {typeof(TVarToken).AccurateName} variable"), null!); - } - - protected Func?>? AdditionalTokenParser = null; - private bool _equalSignSet = false; - private (RunnableContext main, IMayReturnValueContext returner)? _returnContext = null; - private Func? _parser = null; + private ValueExpressionContext? _expression = null; public override string FriendlyName => $"'{varToken.RawRep}' variable definition"; @@ -52,98 +41,44 @@ public override TryAddTokenRes TryAddToken(BaseToken token) return TryAddTokenRes.Continue(); } - if (_returnContext != null) - { - return _returnContext.Value.main.TryAddToken(token); - } - - var parser = AdditionalTokenParser?.Invoke(token); - if (parser != null) - { - _parser = parser; - return TryAddTokenRes.End(); - } - - if (token.CanReturn(out var get)) + if (_expression == null) { - Log.D("set parser using value capable"); - _parser = () => + _expression = new ValueExpressionContext(token, true) { - if (get().HasErrored(out var error, out var value)) - { - throw new ScriptRuntimeError(this, error); - } - - return value; + Script = Script, + ParentContext = this }; - return TryAddTokenRes.End(); - } - - if (token is IContextableToken contextable && - contextable.GetContext(Script) is { } mainContext and IMayReturnValueContext returnValueContext) - { - _returnContext = (mainContext, returnValueContext); return TryAddTokenRes.Continue(); } - - Log.D("set parser using additional"); - var (result, receivedParser) = AdditionalParsing(token); - _parser = receivedParser; - return result; + + return _expression.TryAddToken(token); } public override Result VerifyCurrentState() { - if (_returnContext is { - main: var main, - returner: { Returns: null } returner - }) - { - return $"{main} does not return a value. {returner.UndefinedReturnsHint}"; - } - - return Result.Assert( - _returnContext is not null || - _parser is not null, - $"Value for variable '{varToken.RawRep}' was not provided." - ); + if (!_equalSignSet) return $"Value for variable '{varToken.RawRep}' was not provided (missing equals sign)."; + if (_expression is null) return $"Value for variable '{varToken.RawRep}' was not provided."; + return _expression.VerifyCurrentState(); } protected override IEnumerator Execute() { - if (_returnContext.HasValue) - { - var (main, returner) = _returnContext.Value; - - var coro = main.ExecuteBaseContext(); - while (coro.MoveNext()) - { - yield return coro.Current; - } - - Log.D("checking for returned value"); - if (returner.ReturnedValue is not { } value) - { - throw new ScriptRuntimeError(this, $"{main} has not returned a value! {returner.MissingValueHint}"); - } - - if (value.TryCast().HasErrored(out var error, out var tValue)) - { - throw new ScriptRuntimeError(this, - $"Value returned by {main} cannot be assigned to the '{varToken.RawRep}' variable: {error}" - ); - } + if (_expression is null) throw new AndrzejFuckedUpException(); - DefinedVariable = Variable.Create(varToken.Name, tValue); - } - else if (_parser is not null) + var coro = _expression.Run(); + while (coro.MoveNext()) { - DefinedVariable = Variable.Create(varToken.Name, Value.Parse(_parser(), Script)); + yield return coro.Current; } - else + + if (_expression.GetValue().SuccessTryCast().HasErrored(out var error, out var tValue)) { - throw new AndrzejFuckedUpException(); + throw new ScriptRuntimeError(this, + $"Value returned by '{FriendlyName}' cannot be assigned to the '{varToken.RawRep}' variable: {error}" + ); } + + DefinedVariable = Variable.Create(varToken.Name, tValue); if (CreateLocalVariable) Script.AddLocalVariable(DefinedVariable); diff --git a/Code/ContextSystem/Contexts/WithKeyword.cs b/Code/ContextSystem/Contexts/WithKeyword.cs index 1b3c7bd5..2c1e3736 100644 --- a/Code/ContextSystem/Contexts/WithKeyword.cs +++ b/Code/ContextSystem/Contexts/WithKeyword.cs @@ -27,20 +27,20 @@ public class WithKeyword : StandardContext, IKeywordContext, INotRunningContext, over @all with @plr - Print {@plr name} + Print {@plr -> name} end # WRONG - "with" keyword does not add indentation over @all with @plr - Print {@plr name} + Print {@plr -> name} end # WRONG - with keyword is not a statement that can be closed # this causes an error # over @all # with @plr - # Print {@plr name} + # Print {@plr -> name} # end # end """; diff --git a/Code/Examples/ChaosCoinScript.cs b/Code/Examples/ChaosCoinScript.cs index 9e81f293..7c1c4a41 100644 --- a/Code/Examples/ChaosCoinScript.cs +++ b/Code/Examples/ChaosCoinScript.cs @@ -30,7 +30,7 @@ IsAllowed false # 50% chance to lose the coin if {Chance 50%} Hint @evPlayer 3s "{$hintInfo}Your coin has turned into dust..." - AdvDestroyItem {@evPlayer heldItemRef} + AdvDestroyItem {@evPlayer -> heldItemRef} end # select a random effect of the coin, from 9 available @@ -55,7 +55,7 @@ Broadcast @evPlayer 5s "{$baseText}You now have {$newHP} HP!" # rave if $effect is 3 - *room = {@evPlayer roomRef} + *room = @evPlayer -> roomRef # explode player if he isnt in a room if not {ValidRef *room} @@ -93,7 +93,7 @@ SetPlayerData @evPlayer "coin locked" false # bomb if $effect is 4 SetPlayerData @evPlayer "coin locked" true - $initRole = {@evPlayer role} + $initRole = @evPlayer -> role Countdown @evPlayer 15s "{$baseText}You have %seconds% seconds left to live!" @@ -103,7 +103,7 @@ Wait 1s # every second we are checking if the role of the player changed # if so, we remove the countdown and unlock the coin - if {@evPlayer role} != $initRole + if {@evPlayer -> role} isnt $initRole ClearCountdown @evPlayer SetPlayerData @evPlayer "coin locked" false stop @@ -117,7 +117,7 @@ SetPlayerData @evPlayer "coin locked" false # bypass if $effect is 5 - $initRole = {@evPlayer role} + $initRole = @evPlayer -> role SetPlayerData @evPlayer "coin locked" true Countdown @evPlayer 15s "{$baseText}You can now open any keycard locked thing! (for %seconds% seconds)" @@ -126,7 +126,7 @@ Bypass @evPlayer true repeat 15 Wait 1s - if {@evPlayer role} isnt $initRole + if {@evPlayer -> role} isnt $initRole ClearCountdown @evPlayer break end @@ -139,9 +139,10 @@ SetPlayerData @evPlayer "coin locked" false # role downgrade if $effect is 6 - if {@evPlayer role} isnt "ClassD" + $role = @evPlayer -> role + if $role isnt "ClassD" SetRole @evPlayer ClassD None - elif {@evPlayer role} isnt "Scp0492" + elif $role isnt "Scp0492" SetRole @evPlayer Scp0492 else SetRole @evPlayer Spectator @@ -182,9 +183,9 @@ Explode @evPlayer # gets a random player that is not @evPlayer @swapPlayer = LimitPlayers {RemovePlayers * @evPlayer} 1 - $swapX = {@swapPlayer positionX} - $swapY = {@swapPlayer positionY} - $swapZ = {@swapPlayer positionZ} + $swapX = @swapPlayer -> positionX + $swapY = @swapPlayer -> positionY + $swapZ = @swapPlayer -> positionZ # we can teleport @swapPlayer directly to @evPlayer TPPlayer @swapPlayer @evPlayer diff --git a/Code/Examples/DiscordServerInfoScript.cs b/Code/Examples/DiscordServerInfoScript.cs index fa5f7d28..7ab47d4e 100644 --- a/Code/Examples/DiscordServerInfoScript.cs +++ b/Code/Examples/DiscordServerInfoScript.cs @@ -23,7 +23,7 @@ over @all with @plr #
creates a new line - $text = JoinText $text "
- {@plr name}" + $text = JoinText $text "
- {@plr -> name}" end *embed = DiscordEmbed "📡 {ServerInfo name} status 📡" $text diff --git a/Code/Examples/GnomingTimeScript.cs b/Code/Examples/GnomingTimeScript.cs index 36ce4351..395eb4c5 100644 --- a/Code/Examples/GnomingTimeScript.cs +++ b/Code/Examples/GnomingTimeScript.cs @@ -30,13 +30,13 @@ public class GnomingTimeScript : Example @plr = @evAttacker # lower size by .1 when killing someone - SetSize @plr ({@plr sizeX} - .1) ({@plr sizeY} - .1) ({@plr sizeZ} - .1) + SetSize @plr {{@plr -> sizeX} - .1} {{@plr -> sizeY} - .1} {{@plr -> sizeZ} - .1} Hint @plr 5s "KILLED PLAYER - IT'S GNOMING TIME!" Wait 15s # return them to normal - SetSize @plr ({@plr sizeX} + .1) ({@plr sizeY} + .1) ({@plr sizeZ} + .1) + SetSize @plr ({@plr -> sizeX} + .1) ({@plr -> sizeY} + .1) ({@plr -> sizeZ} + .1) Hint @plr 5s "Gnoming potential depleted :(" """; } \ No newline at end of file diff --git a/Code/Examples/HotPotatoScript.cs b/Code/Examples/HotPotatoScript.cs index 4cfedf9a..d48cc838 100644 --- a/Code/Examples/HotPotatoScript.cs +++ b/Code/Examples/HotPotatoScript.cs @@ -28,7 +28,7 @@ GiveItem @potatoCarrier GunA7 Wait 3s # Check if they still have the item (GunA7) in their inventory - over {@potatoCarrier inventory} + over {@potatoCarrier -> inventory} with *item if {ItemInfo *item type} isnt "GunA7" @@ -37,7 +37,7 @@ Wait 3s AdvDestroyItem *item Explode @potatoCarrier - Broadcast @all 5s "{@potatoCarrier name} failed the Hot Potato!" + Broadcast @all 5s "{@potatoCarrier -> name} failed the Hot Potato!" stop end diff --git a/Code/Examples/PinataLootScript.cs b/Code/Examples/PinataLootScript.cs index 8ad76e62..9257b94a 100644 --- a/Code/Examples/PinataLootScript.cs +++ b/Code/Examples/PinataLootScript.cs @@ -14,7 +14,7 @@ public class PinataLootScript : Example end # Only trigger if the victim is SCP-173 - if {@evPlayer role} isnt "Scp173" + if {@evPlayer -> role} isnt "Scp173" stop end diff --git a/Code/Examples/RaveScript.cs b/Code/Examples/RaveScript.cs index 67aff4fd..165b0a50 100644 --- a/Code/Examples/RaveScript.cs +++ b/Code/Examples/RaveScript.cs @@ -17,7 +17,7 @@ public class RaveScript : Example # the values for the rave $duration = .5s - *room = {@sender roomRef} + *room = @sender -> roomRef # verify that the player is in a room if not {ValidRef *room} diff --git a/Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs b/Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs index d8d90f2e..89fecbf8 100644 --- a/Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs +++ b/Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs @@ -1,8 +1,10 @@ -/*using JetBrains.Annotations; +using JetBrains.Annotations; using LabApi.Features.Wrappers; using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.ArgumentSystem.Structures; +using SER.Code.Exceptions; +using SER.Code.Extensions; using SER.Code.MethodSystem.BaseMethods.Synchronous; using SER.Code.MethodSystem.MethodDescriptors; using SER.Code.ValueSystem; @@ -16,7 +18,11 @@ public class ItemInfoMethod : ReturningMethod, IReferenceResolvingMethod public Type ResolvesReference => typeof(Item); - public override TypeOfValue Returns => ReferenceVariableExpressionToken.GetTypesOfValue(ResolvesReference); + public override TypeOfValue Returns => new TypesOfValue( + typeof(TextValue), + typeof(BoolValue), + typeof(PlayerValue) + ); public override Argument[] ExpectedArguments { get; } = [ @@ -32,10 +38,13 @@ public class ItemInfoMethod : ReturningMethod, IReferenceResolvingMethod public override void Execute() { var item = Args.GetReference("reference"); - ReturnValue = ReferenceVariableExpressionToken - .PropertyInfoMap - [typeof(Item)] - [Args.GetOption("property")] - .Handler(item); + ReturnValue = Args.GetOption("property") switch + { + "type" => item.Type.ToString().ToStaticTextValue(), + "category" => item.Category.ToString().ToStaticTextValue(), + "owner" => new PlayerValue(item.CurrentOwner), + "isEquipped" => new BoolValue(item.IsEquipped), + _ => throw new AndrzejFuckedUpException() + }; } -}*/ \ No newline at end of file +} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PlayerMethods/SetCustomInfoMethod.cs b/Code/MethodSystem/Methods/PlayerMethods/SetCustomInfoMethod.cs index c986c536..120a6e73 100644 --- a/Code/MethodSystem/Methods/PlayerMethods/SetCustomInfoMethod.cs +++ b/Code/MethodSystem/Methods/PlayerMethods/SetCustomInfoMethod.cs @@ -9,7 +9,7 @@ namespace SER.Code.MethodSystem.Methods.PlayerMethods; public class SetCustomInfoMethod : SynchronousMethod { public override string Description => - "Sets custom info (overhead text) for specific players, visible to specific target players."; + "Sets custom info (overhead text) for specific players."; public override Argument[] ExpectedArguments { get; } = [ diff --git a/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs b/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs index 54f2b340..4ab14288 100644 --- a/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs +++ b/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs @@ -1,4 +1,5 @@ using JetBrains.Annotations; +using LabApi.Features.Wrappers; using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.MethodSystem.BaseMethods.Synchronous; @@ -14,16 +15,16 @@ public class FilterPlayersMethod : ReturningMethod public override Argument[] ExpectedArguments { get; } = [ new PlayersArgument("players to filter"), - new EnumArgument("player property"), + new EnumArgument("player property"), new AnyValueArgument("desired value") ]; public override void Execute() { var playersToFilter = Args.GetPlayers("players to filter"); - var playerProperty = Args.GetEnum("player property"); + var playerProperty = Args.GetEnum("player property"); var desiredValue = Args.GetAnyValue("desired value"); - var handler = PlayerExpressionToken.PropertyInfoMap[playerProperty].Handler; + var handler = ((Value.PropInfo)PlayerValue.PropertyInfoMap[playerProperty]).Func; ReturnValue = new(playersToFilter.Where(p => handler(p) == desiredValue)); } diff --git a/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs b/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs index 2f3efd07..695184de 100644 --- a/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs +++ b/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs @@ -1,4 +1,5 @@ using JetBrains.Annotations; +using LabApi.Features.Wrappers; using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.Exceptions; @@ -22,9 +23,9 @@ public class ShowMethod : ReturningMethod, ICanError public override Argument[] ExpectedArguments { get; } = [ new PlayersArgument("players"), - new EnumArgument("property") + new EnumArgument("property") { - DefaultValue = new(PlayerExpressionToken.PlayerProperty.Name, "name"), + DefaultValue = new(PlayerValue.PlayerProperty.Name, "name"), Description = "The property which will be displayed." } ]; @@ -32,10 +33,10 @@ public class ShowMethod : ReturningMethod, ICanError public override void Execute() { var players = Args.GetPlayers("players"); - var property = Args.GetEnum("property"); + var property = Args.GetEnum("property"); - if (!PlayerExpressionToken.PropertyInfoMap.TryGetValue(property, out var propInfo) || - propInfo is not { ReturnType: var type, Handler: var handler } || + if (!PlayerValue.PropertyInfoMap.TryGetValue(property, out var propInfo) || + propInfo is not Value.PropInfo { ReturnType: var type, Func: var handler } || !typeof(LiteralValue).IsAssignableFrom(type.Type)) { throw new ScriptRuntimeError(this, ErrorReasons[0]); diff --git a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs index aa4b837f..a87ddf97 100644 --- a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs +++ b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs @@ -596,7 +596,7 @@ public static string GetMethodHelp(Method method, FrameworkBridge.Type? notLoade public static string GetPlayerInfoAccessorsHelpPage() { StringBuilder sb = new(); - var properties = PlayerExpressionToken.PropertyInfoMap; + var properties = PlayerValue.PropertyInfoMap; foreach (var (property, info) in properties.Select(kvp => (kvp.Key, kvp.Value))) { sb.Append($"{property.ToString().LowerFirst()} -> {info.ReturnType}"); @@ -609,7 +609,7 @@ public static string GetPlayerInfoAccessorsHelpPage() This syntax works as follows: {@plr property} > @plr: is a player variable with exactly 1 player stored in it - > property: is a property of the player we want to get information about (its a {{nameof(PlayerExpressionToken.PlayerProperty)}} enum) + > property: is a property of the player we want to get information about (its a {{nameof(PlayerValue.PlayerProperty)}} enum) Here is a list of all available properties and what they return: {{sb}} diff --git a/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs b/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs index 3c56e1fe..d55ff138 100644 --- a/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs +++ b/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs @@ -2,6 +2,7 @@ using LabApi.Features.Enums; using MapGeneration; using SER.Code.FlagSystem.Flags; +using SER.Code.ValueSystem; namespace SER.Code.Plugin.Commands.HelpSystem; @@ -15,6 +16,6 @@ public static class HelpInfoStorage typeof(ItemType), typeof(ElevatorGroup), typeof(CustomCommandFlag.ConsoleType), - typeof(PlayerExpressionToken.PlayerProperty) + typeof(PlayerValue.PlayerProperty) ]; } \ No newline at end of file diff --git a/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs b/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs index aec6c210..39dbdca5 100644 --- a/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs +++ b/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs @@ -75,7 +75,11 @@ public static TryGet TryParse(CollectionSlice slice, Script scr return expToken; } - public TryGet Value() => _context?.GetValue() ?? throw new AndrzejFuckedUpException(); - public TypeOfValue PossibleValues => _context?.PossibleValues ?? throw new AndrzejFuckedUpException(); + public TryGet Value() + { + _context!.Run().MoveNext(); + return _context.GetValue(); + } + public TypeOfValue PossibleValues => _context!.PossibleValues; public bool IsConstant => false; } \ No newline at end of file diff --git a/Code/TokenSystem/Tokens/ValueTokens/NumberToken.cs b/Code/TokenSystem/Tokens/ValueTokens/NumberToken.cs index 02744e15..c78fe607 100644 --- a/Code/TokenSystem/Tokens/ValueTokens/NumberToken.cs +++ b/Code/TokenSystem/Tokens/ValueTokens/NumberToken.cs @@ -1,4 +1,5 @@ -using SER.Code.ScriptSystem; +using System.Globalization; +using SER.Code.ScriptSystem; using SER.Code.ValueSystem; namespace SER.Code.TokenSystem.Tokens.ValueTokens; @@ -7,13 +8,13 @@ public class NumberToken : LiteralValueToken { protected override IParseResult InternalParse(Script scr) { - if (decimal.TryParse(RawRep, out var value)) + if (decimal.TryParse(RawRep, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { Value = value; return new Success(); } - if (RawRep.EndsWith("%") && decimal.TryParse(RawRep.TrimEnd('%'), out value)) + if (RawRep.EndsWith("%") && decimal.TryParse(RawRep.TrimEnd('%'), NumberStyles.Any, CultureInfo.InvariantCulture, out value)) { Value = value / 100; return new Success(); diff --git a/Code/ValueSystem/BoolValue.cs b/Code/ValueSystem/BoolValue.cs index 2937852a..e371a9b5 100644 --- a/Code/ValueSystem/BoolValue.cs +++ b/Code/ValueSystem/BoolValue.cs @@ -4,6 +4,9 @@ namespace SER.Code.ValueSystem; public class BoolValue(bool value) : LiteralValue(value) { + [UsedImplicitly] + public BoolValue() : this(false) {} + public static implicit operator BoolValue(bool value) { return new(value); diff --git a/Code/ValueSystem/CollectionValue.cs b/Code/ValueSystem/CollectionValue.cs index 05e2f4ce..462f3cdd 100644 --- a/Code/ValueSystem/CollectionValue.cs +++ b/Code/ValueSystem/CollectionValue.cs @@ -8,6 +8,9 @@ namespace SER.Code.ValueSystem; public class CollectionValue(IEnumerable value) : Value { + [UsedImplicitly] + public CollectionValue() : this(Array.Empty()) {} + public Value[] CastedValues { get diff --git a/Code/ValueSystem/ColorValue.cs b/Code/ValueSystem/ColorValue.cs index f98998e8..9e4be990 100644 --- a/Code/ValueSystem/ColorValue.cs +++ b/Code/ValueSystem/ColorValue.cs @@ -1,10 +1,18 @@ +using JetBrains.Annotations; using UnityEngine; namespace SER.Code.ValueSystem; +[UsedImplicitly] public class ColorValue(Color color) : LiteralValue(color) { + [UsedImplicitly] + public ColorValue() : this(Color.white) {} + public override string StringRep => Value.ToHex(); + [UsedImplicitly] + public new static string FriendlyName = "color value"; + public override Dictionary Properties => []; } \ No newline at end of file diff --git a/Code/ValueSystem/DurationValue.cs b/Code/ValueSystem/DurationValue.cs index 285aeb64..73f239a6 100644 --- a/Code/ValueSystem/DurationValue.cs +++ b/Code/ValueSystem/DurationValue.cs @@ -5,6 +5,9 @@ namespace SER.Code.ValueSystem; public class DurationValue(TimeSpan value) : LiteralValue(value) { + [UsedImplicitly] + public DurationValue() : this(TimeSpan.Zero) {} + public static implicit operator DurationValue(TimeSpan value) { return new(value); diff --git a/Code/ValueSystem/NumberValue.cs b/Code/ValueSystem/NumberValue.cs index 85c9046d..37f5520e 100644 --- a/Code/ValueSystem/NumberValue.cs +++ b/Code/ValueSystem/NumberValue.cs @@ -4,6 +4,9 @@ namespace SER.Code.ValueSystem; public class NumberValue(decimal value) : LiteralValue(value) { + [UsedImplicitly] + public NumberValue() : this(0m) {} + public static implicit operator NumberValue(decimal value) { return new(value); diff --git a/Code/ValueSystem/PlayerValue.cs b/Code/ValueSystem/PlayerValue.cs index b569530b..a3f6e9eb 100644 --- a/Code/ValueSystem/PlayerValue.cs +++ b/Code/ValueSystem/PlayerValue.cs @@ -1,5 +1,7 @@ using JetBrains.Annotations; using LabApi.Features.Wrappers; +using PlayerRoles; +using PlayerRoles.PlayableScps.Scp079; using SER.Code.Exceptions; using SER.Code.Extensions; @@ -19,6 +21,12 @@ public PlayerValue(IEnumerable players) Players = players.ToArray(); } + [UsedImplicitly] + public PlayerValue() + { + Players = []; + } + public Player[] Players { get; } public override bool EqualCondition(Value other) => other is PlayerValue otherP && Players.SequenceEqual(otherP.Players); @@ -30,6 +38,159 @@ public PlayerValue(IEnumerable players) [UsedImplicitly] public new static string FriendlyName = "player value"; - - public override Dictionary Properties => []; + + public override Dictionary Properties { get; } = + PropertyInfoMap.ToDictionary(pair => pair.Key.ToString().LowerFirst(), pair => pair.Value); + + public enum PlayerProperty + { + None = 0, + Name, + DisplayName, + Role, + RoleRef, + Team, + Inventory, + ItemCount, + HeldItemRef, + IsAlive, + UserId, + PlayerId, + CustomInfo, + RoomRef, + Health, + MaxHealth, + ArtificialHealth, + MaxArtificialHealth, + HumeShield, + MaxHumeShield, + HumeShieldRegenRate, + GroupName, + PositionX, + PositionY, + PositionZ, + IsDisarmed, + IsMuted, + IsIntercomMuted, + IsGlobalModerator, + IsNorthwoodStaff, + IsBypassEnabled, + IsGodModeEnabled, + IsNoclipEnabled, + Gravity, + RoleChangeReason, + RoleSpawnFlags, + AuxiliaryPower, + Emotion, + Experience, + MaxAuxiliaryPower, + SizeX, + SizeY, + SizeZ, + AccessTier, + RelativeX, + RelativeY, + RelativeZ, + IsNpc, + IsDummy, + } + + public class Info(Func handler, string? description) + : PropInfo(handler, description) where T : Value; + + public static readonly Dictionary PropertyInfoMap = new() + { + [PlayerProperty.Name] = new Info(plr => plr.Nickname, null), + [PlayerProperty.DisplayName] = new Info(plr => plr.DisplayName, null), + [PlayerProperty.Role] = new Info(plr => plr.Role.ToString(), $"Player role type ({nameof(RoleTypeId)} enum value)"), + [PlayerProperty.RoleRef] = new Info(plr => new(plr.RoleBase), $"Reference to {nameof(PlayerRoleBase)}"), + [PlayerProperty.Team] = new Info(plr => plr.Team.ToString(), $"Player team ({nameof(Team)} enum value)"), + [PlayerProperty.Inventory] = new Info(plr => new(plr.Inventory.UserInventory.Items.Values.Select(Item.Get).RemoveNulls()), $"A collection of references to {nameof(Item)} objects"), + [PlayerProperty.ItemCount] = new Info(plr => (decimal)plr.Inventory.UserInventory.Items.Count, null), + [PlayerProperty.HeldItemRef] = new Info(plr => new(plr.CurrentItem), "A reference to the item the player is holding"), + [PlayerProperty.IsAlive] = new Info(plr => plr.IsAlive, null), + [PlayerProperty.UserId] = new Info(plr => plr.UserId, "The ID of the account (like SteamID64)"), + [PlayerProperty.PlayerId] = new Info(plr => plr.PlayerId, "The ID that the server assigned for this round"), + [PlayerProperty.CustomInfo] = new Info(plr => plr.CustomInfo, "Custom info set by the server"), + [PlayerProperty.RoomRef] = new Info(plr => new(plr.Room), "A reference to the room the player is in"), + [PlayerProperty.Health] = new Info(plr => (decimal)plr.Health, null), + [PlayerProperty.MaxHealth] = new Info(plr => (decimal)plr.MaxHealth, null), + [PlayerProperty.ArtificialHealth] = new Info(plr => (decimal)plr.ArtificialHealth, null), + [PlayerProperty.MaxArtificialHealth] = new Info(plr => (decimal)plr.MaxArtificialHealth, null), + [PlayerProperty.HumeShield] = new Info(plr => (decimal)plr.HumeShield, null), + [PlayerProperty.MaxHumeShield] = new Info(plr => (decimal)plr.MaxHumeShield, null), + [PlayerProperty.HumeShieldRegenRate] = new Info(plr => (decimal)plr.HumeShieldRegenRate, null), + [PlayerProperty.GroupName] = new Info(plr => plr.GroupName, "The name of the group (like admin or vip)"), + [PlayerProperty.PositionX] = new Info(plr => (decimal)plr.Position.x, null), + [PlayerProperty.PositionY] = new Info(plr => (decimal)plr.Position.y, null), + [PlayerProperty.PositionZ] = new Info(plr => (decimal)plr.Position.z, null), + [PlayerProperty.IsDisarmed] = new Info(plr => plr.IsDisarmed, null), + [PlayerProperty.IsMuted] = new Info(plr => plr.IsMuted, null), + [PlayerProperty.IsIntercomMuted] = new Info(plr => plr.IsIntercomMuted, null), + [PlayerProperty.IsGlobalModerator] = new Info(plr => plr.IsGlobalModerator, null), + [PlayerProperty.IsNorthwoodStaff] = new Info(plr => plr.IsNorthwoodStaff, null), + [PlayerProperty.IsBypassEnabled] = new Info(plr => plr.IsBypassEnabled, null), + [PlayerProperty.IsGodModeEnabled] = new Info(plr => plr.IsGodModeEnabled, null), + [PlayerProperty.IsNoclipEnabled] = new Info(plr => plr.IsNoclipEnabled, null), + [PlayerProperty.Gravity] = new Info(plr => -(decimal)plr.Gravity.y, null), + [PlayerProperty.RoleChangeReason] = new Info(plr => plr.RoleBase.ServerSpawnReason.ToString(), null), + [PlayerProperty.RoleSpawnFlags] = new Info(plr => plr.RoleBase.ServerSpawnFlags.ToString(), null), + [PlayerProperty.AuxiliaryPower] = new Info(plr => + { + if (plr.RoleBase is Scp079Role scp) + { + if (scp.SubroutineModule.TryGetSubroutine(out Scp079AuxManager man)) + { + return (decimal)man.CurrentAux; + } + } + + return -1; + }, "Returns player Aux power if he is SCP-079, otherwise returns -1"), + [PlayerProperty.Experience] = new Info(plr => + { + if (plr.RoleBase is Scp079Role scp) + { + if (scp.SubroutineModule.TryGetSubroutine(out Scp079TierManager tier)) + { + return tier.TotalExp; + } + } + + return -1; + }, "Returns player EXP if he is SCP-079, otherwise returns -1"), + [PlayerProperty.Emotion] = new Info(plr => plr.Emotion.ToString(), "Current emotion (e.g. Neutral, Chad)"), + [PlayerProperty.MaxAuxiliaryPower] = new Info(plr => + { + if (plr.RoleBase is Scp079Role scp) + { + if (scp.SubroutineModule.TryGetSubroutine(out Scp079AuxManager man)) + { + return (decimal)man.MaxAux; + } + } + + return -1; + }, "Returns the player's Maximum Auxiliary Power if they are SCP-079, otherwise returns -1"), + [PlayerProperty.SizeX] = new Info(plr => (decimal)plr.Scale.x, null), + [PlayerProperty.SizeY] = new Info(plr => (decimal)plr.Scale.y, null), + [PlayerProperty.SizeZ] = new Info(plr => (decimal)plr.Scale.z, null), + [PlayerProperty.AccessTier] = new Info(plr => + { + if (plr.RoleBase is Scp079Role scp) + { + if (scp.SubroutineModule.TryGetSubroutine(out Scp079TierManager tier)) + { + return tier.AccessTierLevel; + } + } + + return -1; + }, "Returns the player's Access Tier Level if they are SCP-079, otherwise returns -1"), + [PlayerProperty.RelativeX] = new Info(plr => (decimal)plr.RelativeRoomPosition().x, "Returns the player's x relative to the current room or 0 if in no room"), + [PlayerProperty.RelativeY] = new Info(plr => (decimal)plr.RelativeRoomPosition().y, "Returns the player's y relative to the current room or 0 if in no room"), + [PlayerProperty.RelativeZ] = new Info(plr => (decimal)plr.RelativeRoomPosition().z, "Returns the player's z relative to the current room or 0 if in no room"), + [PlayerProperty.IsNpc] = new Info(plr => plr.IsNpc, "True if it's a player without any client connected to it"), + [PlayerProperty.IsDummy] = new Info(plr => plr.IsDummy, null) + }; } \ No newline at end of file diff --git a/Code/ValueSystem/ReferenceValue.cs b/Code/ValueSystem/ReferenceValue.cs index b79a5b3c..1c324449 100644 --- a/Code/ValueSystem/ReferenceValue.cs +++ b/Code/ValueSystem/ReferenceValue.cs @@ -4,8 +4,12 @@ namespace SER.Code.ValueSystem; +[UsedImplicitly] public class ReferenceValue(object? value) : Value { + [UsedImplicitly] + public ReferenceValue() : this(null) {} + public bool IsValid => value is not null; public object Value => value ?? throw new CustomScriptRuntimeError("Value of reference is invalid."); @@ -28,8 +32,12 @@ public override string ToString() public override Dictionary Properties => []; } +[UsedImplicitly] public class ReferenceValue(T? value) : ReferenceValue(value) { + [UsedImplicitly] + public ReferenceValue() : this(default) {} + public new T Value => (T) base.Value; [UsedImplicitly] diff --git a/Code/ValueSystem/TextValue.cs b/Code/ValueSystem/TextValue.cs index 31fc62ed..99a49d73 100644 --- a/Code/ValueSystem/TextValue.cs +++ b/Code/ValueSystem/TextValue.cs @@ -70,14 +70,22 @@ public static string ParseValue(string text, Script script) => ExpressionRegex.R public override Dictionary Properties => []; } +[UsedImplicitly] public class DynamicTextValue(string text, Script script) : TextValue(text, script) { + [UsedImplicitly] + public DynamicTextValue() : this(string.Empty, null!) {} + [UsedImplicitly] public new static string FriendlyName = "dynamic text value"; } +[UsedImplicitly] public class StaticTextValue(string text) : TextValue(text, null) { + [UsedImplicitly] + public StaticTextValue() : this(string.Empty) {} + public static implicit operator StaticTextValue(string text) { return new(text); diff --git a/Code/ValueSystem/Value.cs b/Code/ValueSystem/Value.cs index 6c26a055..126d5390 100644 --- a/Code/ValueSystem/Value.cs +++ b/Code/ValueSystem/Value.cs @@ -3,6 +3,7 @@ using LabApi.Features.Wrappers; using SER.Code.Exceptions; using SER.Code.Extensions; +using SER.Code.Helpers.ResultSystem; using SER.Code.ScriptSystem; namespace SER.Code.ValueSystem; @@ -46,16 +47,28 @@ public static Value Parse(object obj, Script? script) public abstract class PropInfo { - public abstract Func Func { get; } + public abstract TryGet GetValue(object obj); public abstract SingleTypeOfValue ReturnType { get; } public abstract string? Description { get; } } + + public abstract class PropInfo : PropInfo + { + public abstract Func Func { get; } + } - public class PropInfo(Func handler, string? description) : PropInfo - where T : Value + public class PropInfo(Func handler, string? description) : PropInfo + where TOut : Value { - public override Func Func => handler; - public override SingleTypeOfValue ReturnType => new(typeof(T)); + public override Func Func => handler; + + public override TryGet GetValue(object obj) + { + if (obj is not TIn inObj) return $"Provided value is not of type {typeof(TIn).AccurateName}"; + return handler(inObj); + } + + public override SingleTypeOfValue ReturnType => new(typeof(TOut)); public override string? Description => description; } From 6b076d6ef0941990c1b3eb7c1d726ebbd155cde7 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:13:00 +0200 Subject: [PATCH 41/60] Update TextArgument.cs --- Code/ArgumentSystem/Arguments/TextArgument.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Code/ArgumentSystem/Arguments/TextArgument.cs b/Code/ArgumentSystem/Arguments/TextArgument.cs index b3f190cd..661d3cee 100644 --- a/Code/ArgumentSystem/Arguments/TextArgument.cs +++ b/Code/ArgumentSystem/Arguments/TextArgument.cs @@ -1,13 +1,11 @@ using JetBrains.Annotations; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.Extensions; -using SER.Code.Helpers; using SER.Code.Helpers.ResultSystem; using SER.Code.TokenSystem.Tokens; using SER.Code.TokenSystem.Tokens.Interfaces; using SER.Code.TokenSystem.Tokens.ValueTokens; using SER.Code.ValueSystem; -using ZstdSharp.Unsafe; namespace SER.Code.ArgumentSystem.Arguments; @@ -23,7 +21,7 @@ public DynamicTryGet GetConvertSolution(BaseToken token) if (token is TextToken textToken) { return new(() => textToken.GetDynamicResolver().Invoke().OnSuccess(SpaceCheck)); - } + } if (token is not IValueToken valToken || !valToken.CapableOf(out var get)) { From b21f267a67a26d8a5ae2b628092d77a8a70d6504 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:13:16 +0200 Subject: [PATCH 42/60] fix value property access --- Code/ContextSystem/Contexts/ValueExpressionContext.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Code/ContextSystem/Contexts/ValueExpressionContext.cs b/Code/ContextSystem/Contexts/ValueExpressionContext.cs index f2aa0d63..8f307f25 100644 --- a/Code/ContextSystem/Contexts/ValueExpressionContext.cs +++ b/Code/ContextSystem/Contexts/ValueExpressionContext.cs @@ -176,7 +176,7 @@ public class ValuePropertyHandler( BaseToken baseToken, IValueToken valueToken) : ValueExpressionContext.Handler { - private readonly Queue _propertyNames = []; + private readonly List _propertyNames = []; private string _exprRepr = baseToken.RawRep; private TypeOfValue _lastValueType = valueToken.PossibleValues; @@ -192,9 +192,8 @@ public override TryGet GetReturnValue() } Value current = value; - while (_propertyNames.Count > 0) + foreach (var prop in _propertyNames) { - var prop = _propertyNames.Dequeue(); if (!current.Properties.TryGetValue(prop, out var propInfo)) { return $"{current} does not have property '{prop}'."; @@ -236,7 +235,7 @@ public override TryAddTokenRes TryAddToken(BaseToken token) } found: - _propertyNames.Enqueue(token.RawRep); + _propertyNames.Add(token.RawRep); return TryAddTokenRes.Continue(); } From e1238e72cf8554ea0113cf4ce381e0d19478423e Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:13:20 +0200 Subject: [PATCH 43/60] Update PlayerVariableDefinitionContext.cs --- .../VariableDefinition/PlayerVariableDefinitionContext.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Code/ContextSystem/Contexts/VariableDefinition/PlayerVariableDefinitionContext.cs b/Code/ContextSystem/Contexts/VariableDefinition/PlayerVariableDefinitionContext.cs index 88e85655..5f950c74 100644 --- a/Code/ContextSystem/Contexts/VariableDefinition/PlayerVariableDefinitionContext.cs +++ b/Code/ContextSystem/Contexts/VariableDefinition/PlayerVariableDefinitionContext.cs @@ -1,9 +1,6 @@ -using SER.Code.ContextSystem.Structures; -using SER.Code.TokenSystem.Tokens; -using SER.Code.TokenSystem.Tokens.VariableTokens; +using SER.Code.TokenSystem.Tokens.VariableTokens; using SER.Code.ValueSystem; using SER.Code.VariableSystem.Variables; -using Log = SER.Code.Helpers.Log; namespace SER.Code.ContextSystem.Contexts.VariableDefinition; From c121e3566b30124f1ba68c18ec5473597acd7f83 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:13:23 +0200 Subject: [PATCH 44/60] Update VariableDefinitionContext.cs --- .../Contexts/VariableDefinition/VariableDefinitionContext.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs b/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs index c336b73b..0cc8b7de 100644 --- a/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs +++ b/Code/ContextSystem/Contexts/VariableDefinition/VariableDefinitionContext.cs @@ -1,5 +1,4 @@ using SER.Code.ContextSystem.BaseContexts; -using SER.Code.ContextSystem.Contexts; using SER.Code.ContextSystem.Structures; using SER.Code.Exceptions; using SER.Code.Extensions; @@ -74,7 +73,7 @@ protected override IEnumerator Execute() if (_expression.GetValue().SuccessTryCast().HasErrored(out var error, out var tValue)) { throw new ScriptRuntimeError(this, - $"Value returned by '{FriendlyName}' cannot be assigned to the '{varToken.RawRep}' variable: {error}" + $"Value returned by {FriendlyName} cannot be assigned to the '{varToken.RawRep}' variable: {error}" ); } From f56f563e52993719ad7e8d09a7d31f0c50404c39 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:13:49 +0200 Subject: [PATCH 45/60] Update FileSystem.cs --- Code/FileSystem/FileSystem.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Code/FileSystem/FileSystem.cs b/Code/FileSystem/FileSystem.cs index b18e6093..d2694d70 100644 --- a/Code/FileSystem/FileSystem.cs +++ b/Code/FileSystem/FileSystem.cs @@ -28,7 +28,6 @@ public static void UpdateScriptPathCollection() .Where(path => Path.GetFileName(path).FirstOrDefault() != '#') .ToArray(); - //Log.Signal(RegisteredScriptPaths.JoinStrings(" ")); var duplicates = RegisteredScriptPaths .Select(Path.GetFileNameWithoutExtension) From de734bf35581605810a771f1b30eac439d95beb5 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:13:57 +0200 Subject: [PATCH 46/60] Create SpawnDummyMethod.cs --- .../Methods/DummyMethods/SpawnDummyMethod.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 Code/MethodSystem/Methods/DummyMethods/SpawnDummyMethod.cs diff --git a/Code/MethodSystem/Methods/DummyMethods/SpawnDummyMethod.cs b/Code/MethodSystem/Methods/DummyMethods/SpawnDummyMethod.cs new file mode 100644 index 00000000..647ec527 --- /dev/null +++ b/Code/MethodSystem/Methods/DummyMethods/SpawnDummyMethod.cs @@ -0,0 +1,31 @@ + +using JetBrains.Annotations; +using LabApi.Features.Wrappers; +using NetworkManagerUtils.Dummies; +using PlayerRoles; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; + +namespace SER.Code.MethodSystem.Methods.DummyMethods; + +[UsedImplicitly] +public class SpawnDummyMethod : SynchronousMethod +{ + public override string Description => "Spawns a dummy"; + + public override Argument[] ExpectedArguments { get; } = + [ + new TextArgument("name"), + new EnumArgument("role type") + ]; + + public override void Execute() + { + var name = Args.GetText("name"); + var roleType = Args.GetEnum("role type"); + + var dummy = Player.Get(DummyUtils.SpawnDummy(name)); + dummy.SetRole(roleType); + } +} \ No newline at end of file From 896f43a2f5e38e4804c982b7a7254eea3e1f22e2 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:14:09 +0200 Subject: [PATCH 47/60] Update ExpressionToken.cs --- Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs b/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs index 39dbdca5..3b9f9898 100644 --- a/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs +++ b/Code/TokenSystem/Tokens/ExpressionTokens/ExpressionToken.cs @@ -1,5 +1,4 @@ using SER.Code.ContextSystem.Contexts; -using SER.Code.Exceptions; using SER.Code.Extensions; using SER.Code.Helpers.ResultSystem; using SER.Code.ScriptSystem; From d2968c75d5fc4508b45c6158299002ab43353b51 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 09:14:42 +0200 Subject: [PATCH 48/60] add translators to PropInfo for translating value objects --- Code/ValueSystem/PlayerValue.cs | 8 ++++++-- Code/ValueSystem/Value.cs | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Code/ValueSystem/PlayerValue.cs b/Code/ValueSystem/PlayerValue.cs index a3f6e9eb..16e09cec 100644 --- a/Code/ValueSystem/PlayerValue.cs +++ b/Code/ValueSystem/PlayerValue.cs @@ -95,8 +95,12 @@ public enum PlayerProperty IsDummy, } - public class Info(Func handler, string? description) - : PropInfo(handler, description) where T : Value; + public class Info(Func handler, string? description) + : PropInfo(handler, description) where T : Value + { + public override Func? Translator { get; } = + obj => obj is PlayerValue { Players.Length: 1 } val ? val.Players[0] : obj; + } public static readonly Dictionary PropertyInfoMap = new() { diff --git a/Code/ValueSystem/Value.cs b/Code/ValueSystem/Value.cs index 126d5390..42d71bbe 100644 --- a/Code/ValueSystem/Value.cs +++ b/Code/ValueSystem/Value.cs @@ -61,9 +61,11 @@ public class PropInfo(Func handler, string? description) : where TOut : Value { public override Func Func => handler; - + public virtual Func? Translator { get; } = null; + public override TryGet GetValue(object obj) { + if (Translator is not null) obj = Translator(obj); if (obj is not TIn inObj) return $"Provided value is not of type {typeof(TIn).AccurateName}"; return handler(inObj); } From 118600432dc62a8314f2c336e4fe38ca46298d9a Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:48:44 +0200 Subject: [PATCH 49/60] update again --- .../Contexts/Control/Loops/OverLoop.cs | 2 +- .../Contexts/ValueExpressionContext.cs | 37 ++- Code/Examples/HotPotatoScript.cs | 2 +- Code/Extensions/EnumExtensions.cs | 15 +- .../IReferenceResolvingMethod.cs | 16 - Code/MethodSystem/MethodIndex.cs | 6 + .../Methods/DoorMethods/DoorInfoMethod.cs | 67 ----- .../Methods/HTTPMethods/JSONInfoMethod.cs | 67 ----- .../Methods/HealthMethods/DamageInfoMethod.cs | 57 ---- .../IntercomMethods/IntercomInfoMethod.cs | 3 +- .../Methods/ItemMethods/ItemInfoMethod.cs | 50 ---- .../ParsingMethods/ResultInfoMethod.cs | 47 --- .../Methods/PickupMethods/PickupInfoMethod.cs | 68 ----- .../FilterPlayersMethod.cs | 3 +- .../GetPlayerFromReferenceMethod.cs | 28 -- .../PlayerVariableMethods/ShowMethod.cs | 3 +- .../RespawnMethods/RespawnWaveInfoMethod.cs | 55 ---- .../Methods/RoleMethods/RoleInfoMethod.cs | 41 --- .../Methods/RoomMethods/RoomInfoMethod.cs | 53 ---- .../Methods/ScriptMethods/ThisMethod.cs | 3 +- .../Methods/TimeMethods/TimeInfoMethod.cs | 3 +- .../Methods/UCRMethods/UCRRoleInfoMethod.cs | 58 ---- .../Commands/HelpSystem/DocsProvider.cs | 233 +++++++++++---- .../Plugin/Commands/HelpSystem/HelpCommand.cs | 2 +- .../Commands/HelpSystem/HelpInfoStorage.cs | 3 +- Code/Plugin/Commands/HelpSystem/HelpOption.cs | 3 +- Code/ValueSystem/BoolValue.cs | 17 +- Code/ValueSystem/CollectionValue.cs | 34 ++- Code/ValueSystem/ColorValue.cs | 18 +- Code/ValueSystem/DurationValue.cs | 24 +- Code/ValueSystem/EnumValue.cs | 16 + Code/ValueSystem/NumberValue.cs | 23 +- Code/ValueSystem/PlayerValue.cs | 28 +- .../PropertySystem/IValueWithProperties.cs | 58 ++++ .../ReferencePropertyRegistry.cs | 273 ++++++++++++++++++ Code/ValueSystem/ReferenceValue.cs | 12 +- Code/ValueSystem/TextValue.cs | 25 +- Code/ValueSystem/TypeOfValue.cs | 2 +- Code/ValueSystem/Value.cs | 110 ++++--- README.md | 2 +- 40 files changed, 810 insertions(+), 757 deletions(-) delete mode 100644 Code/MethodSystem/MethodDescriptors/IReferenceResolvingMethod.cs delete mode 100644 Code/MethodSystem/Methods/DoorMethods/DoorInfoMethod.cs delete mode 100644 Code/MethodSystem/Methods/HTTPMethods/JSONInfoMethod.cs delete mode 100644 Code/MethodSystem/Methods/HealthMethods/DamageInfoMethod.cs delete mode 100644 Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs delete mode 100644 Code/MethodSystem/Methods/ParsingMethods/ResultInfoMethod.cs delete mode 100644 Code/MethodSystem/Methods/PickupMethods/PickupInfoMethod.cs delete mode 100644 Code/MethodSystem/Methods/PlayerVariableMethods/GetPlayerFromReferenceMethod.cs delete mode 100644 Code/MethodSystem/Methods/RespawnMethods/RespawnWaveInfoMethod.cs delete mode 100644 Code/MethodSystem/Methods/RoleMethods/RoleInfoMethod.cs delete mode 100644 Code/MethodSystem/Methods/RoomMethods/RoomInfoMethod.cs delete mode 100644 Code/MethodSystem/Methods/UCRMethods/UCRRoleInfoMethod.cs create mode 100644 Code/ValueSystem/EnumValue.cs create mode 100644 Code/ValueSystem/PropertySystem/IValueWithProperties.cs create mode 100644 Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs diff --git a/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs b/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs index 4aa1ed35..3b289986 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs @@ -49,7 +49,7 @@ with @plr over &inventory with *item - Print "found item {ItemInfo *item type}" + Print "found item {*item -> type}" end # its important to remember that the variable type in "with" keyword # MUST match the value type inside the collection, diff --git a/Code/ContextSystem/Contexts/ValueExpressionContext.cs b/Code/ContextSystem/Contexts/ValueExpressionContext.cs index 8f307f25..9b286549 100644 --- a/Code/ContextSystem/Contexts/ValueExpressionContext.cs +++ b/Code/ContextSystem/Contexts/ValueExpressionContext.cs @@ -10,6 +10,7 @@ using SER.Code.TokenSystem.Tokens; using SER.Code.TokenSystem.Tokens.Interfaces; using SER.Code.ValueSystem; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.ContextSystem.Contexts; @@ -194,7 +195,22 @@ public override TryGet GetReturnValue() Value current = value; foreach (var prop in _propertyNames) { - if (!current.Properties.TryGetValue(prop, out var propInfo)) + if (current is not IValueWithProperties propVal) + { + return $"{current} does not have any properties."; + } + + Console.WriteLine(propVal.Properties.Select(kvp => $"{kvp.Key} -> {kvp.Value.ReturnType}").JoinStrings(", ")); + + IValueWithProperties.PropInfo? propInfo; + if (propVal.Properties is IValueWithProperties.IDynamicPropertyDictionary dynamicDict) + { + if (!dynamicDict.TryGetValue(prop, out propInfo)) + { + return $"{current} does not have property '{prop}'."; + } + } + else if (!propVal.Properties.TryGetValue(prop, out propInfo)) { return $"{current} does not have property '{prop}'."; } @@ -223,7 +239,24 @@ public override TryAddTokenRes TryAddToken(BaseToken token) { foreach (var type in types) { - if (Value.GetPropertiesOfValue(type).TryGetValue(token.RawRep, out var property)) + if (type == typeof(ReferenceValue)) + { + _exprRepr += $" {token.RawRep}"; + _lastValueType = new UnknownTypeOfValue(); + goto found; + } + + var props = Value.GetPropertiesOfValue(type); + if (props is IValueWithProperties.IDynamicPropertyDictionary dynamicDict) + { + if (dynamicDict.TryGetValue(token.RawRep, out var dynamicProp)) + { + _exprRepr += $" {token.RawRep}"; + _lastValueType = dynamicProp.ReturnType; + goto found; + } + } + else if (props?.TryGetValue(token.RawRep, out var property) is true) { _exprRepr += $" {token.RawRep}"; _lastValueType = property.ReturnType; diff --git a/Code/Examples/HotPotatoScript.cs b/Code/Examples/HotPotatoScript.cs index d48cc838..aa146bf7 100644 --- a/Code/Examples/HotPotatoScript.cs +++ b/Code/Examples/HotPotatoScript.cs @@ -31,7 +31,7 @@ Wait 3s over {@potatoCarrier -> inventory} with *item - if {ItemInfo *item type} isnt "GunA7" + if {*item -> type} isnt "GunA7" continue end diff --git a/Code/Extensions/EnumExtensions.cs b/Code/Extensions/EnumExtensions.cs index 035dc6d0..43910f2b 100644 --- a/Code/Extensions/EnumExtensions.cs +++ b/Code/Extensions/EnumExtensions.cs @@ -1,13 +1,12 @@ -namespace SER.Code.Extensions; +using System; +using SER.Code.ValueSystem; + +namespace SER.Code.Extensions; public static class EnumExtensions { - public static IEnumerable GetFlags(this T value) where T : struct, Enum + public static EnumValue ToEnumValue(this T enumValue) where T : struct, Enum { - return from T flag in Enum.GetValues(typeof(T)) - where Convert.ToUInt64(value) != 0 - where Convert.ToUInt64(flag) != 0 - where value.HasFlag(flag) - select flag; + return new EnumValue(enumValue); } -} \ No newline at end of file +} diff --git a/Code/MethodSystem/MethodDescriptors/IReferenceResolvingMethod.cs b/Code/MethodSystem/MethodDescriptors/IReferenceResolvingMethod.cs deleted file mode 100644 index dfa108b1..00000000 --- a/Code/MethodSystem/MethodDescriptors/IReferenceResolvingMethod.cs +++ /dev/null @@ -1,16 +0,0 @@ -using SER.Code.Extensions; - -namespace SER.Code.MethodSystem.MethodDescriptors; - -/// -/// A method which takes a reference as an input and returns information about it in a readable format. -/// -public interface IReferenceResolvingMethod -{ - public Type ResolvesReference { get; } - - public static class Desc - { - public static string Get(IReferenceResolvingMethod method) => $"Extracts information from {method.ResolvesReference.AccurateName} objects."; - } -} \ No newline at end of file diff --git a/Code/MethodSystem/MethodIndex.cs b/Code/MethodSystem/MethodIndex.cs index 12f262d0..6a50c33e 100644 --- a/Code/MethodSystem/MethodIndex.cs +++ b/Code/MethodSystem/MethodIndex.cs @@ -5,11 +5,17 @@ using SER.Code.Helpers.ResultSystem; using SER.Code.MethodSystem.BaseMethods; using SER.Code.MethodSystem.Structures; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.MethodSystem; public static class MethodIndex { + static MethodIndex() + { + ReferencePropertyRegistry.Initialize(); + } + public static readonly Dictionary NameToMethodIndex = []; public static readonly Dictionary> FrameworkDependentMethods = []; diff --git a/Code/MethodSystem/Methods/DoorMethods/DoorInfoMethod.cs b/Code/MethodSystem/Methods/DoorMethods/DoorInfoMethod.cs deleted file mode 100644 index a313be31..00000000 --- a/Code/MethodSystem/Methods/DoorMethods/DoorInfoMethod.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Interactables.Interobjects.DoorUtils; -using JetBrains.Annotations; -using LabApi.Features.Enums; -using LabApi.Features.Wrappers; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.ArgumentSystem.Structures; -using SER.Code.Exceptions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.DoorMethods; - -[UsedImplicitly] -public class DoorInfoMethod : LiteralValueReturningMethod, IReferenceResolvingMethod -{ - public Type ResolvesReference => typeof(Door); - - public override TypeOfValue LiteralReturnTypes => new TypesOfValue([ - typeof(TextValue), - typeof(BoolValue), - typeof(NumberValue) - ]); - - public override string Description => IReferenceResolvingMethod.Desc.Get(this); - - public override Argument[] ExpectedArguments { get; } = - [ - new ReferenceArgument("door"), - new OptionsArgument("info", - "isOpen", - "isClosed", - "isLocked", - "isUnlocked", - "isGate", - "isCheckpoint", - Option.Enum("name"), - "unityName", - "remainingHealth", - "maxHealth", - Option.Enum("permissions") - ) - ]; - - public override void Execute() - { - var door = Args.GetReference("door"); - var info = Args.GetOption("info"); - - ReturnValue = info switch - { - "name" => new StaticTextValue(door.DoorName.ToString()), - "unityname" => new StaticTextValue(door.Base.name), - "isopen" => new BoolValue(door.IsOpened), - "isclosed" => new BoolValue(!door.IsOpened), - "islocked" => new BoolValue(door.IsLocked), - "isunlocked" => new BoolValue(!door.IsLocked), - "isgate" => new BoolValue(door is Gate), - "ischeckpoint" => new BoolValue(door is CheckpointDoor), - "remaininghealth" => new NumberValue(door is BreakableDoor bDoor ? (decimal)bDoor.Health : -1), - "maxhealth" => new NumberValue(door is BreakableDoor bDoor ? (decimal)bDoor.MaxHealth : -1), - "permissions" => new StaticTextValue(door.Permissions.ToString()), - _ => throw new AndrzejFuckedUpException() - }; - } -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/HTTPMethods/JSONInfoMethod.cs b/Code/MethodSystem/Methods/HTTPMethods/JSONInfoMethod.cs deleted file mode 100644 index 250b4aa8..00000000 --- a/Code/MethodSystem/Methods/HTTPMethods/JSONInfoMethod.cs +++ /dev/null @@ -1,67 +0,0 @@ -using JetBrains.Annotations; -using Newtonsoft.Json.Linq; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.Exceptions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.HTTPMethods; - -[UsedImplicitly] -// ReSharper disable once InconsistentNaming -public class JSONInfoMethod : ReturningMethod, IReferenceResolvingMethod, ICanError -{ - public override string Description => IReferenceResolvingMethod.Desc.Get(this); - - public override TypeOfValue Returns { get; } = new UnknownTypeOfValue(); - - public Type ResolvesReference { get; } = typeof(JObject); - - public string[] ErrorReasons { get; } = - [ - "Provided JSON object does not contain the provided key.", - "Provided value is not a JSON object, cannot get value from it." - ]; - - public override Argument[] ExpectedArguments { get; } = - [ - new ReferenceArgument("JSON object"), - new TextArgument("key") - { - ConsumesRemainingValues = true, - Description = "The key to get the value from. You can use multiple keys to get value from nested objects." - } - ]; - - public override void Execute() - { - var obj = Args.GetReference("JSON object"); - var keys = Args.GetRemainingArguments("key"); - - JToken currentToken = obj; - foreach (string key in keys) - { - if (currentToken is not JObject nestedObj) - { - throw new ScriptRuntimeError(this, ErrorReasons[1]); - } - - if (!nestedObj.TryGetValue(key, out JToken? nextToken)) - { - throw new ScriptRuntimeError(this, ErrorReasons[0]); - } - - currentToken = nextToken; - } - - if (currentToken is JValue { Value: {} val }) - { - ReturnValue = Value.Parse(val, null); - return; - } - - ReturnValue = Value.Parse(currentToken, null); - } -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/HealthMethods/DamageInfoMethod.cs b/Code/MethodSystem/Methods/HealthMethods/DamageInfoMethod.cs deleted file mode 100644 index 5f6b9754..00000000 --- a/Code/MethodSystem/Methods/HealthMethods/DamageInfoMethod.cs +++ /dev/null @@ -1,57 +0,0 @@ -using JetBrains.Annotations; -using LabApi.Features.Wrappers; -using PlayerStatsSystem; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.ArgumentSystem.Structures; -using SER.Code.Exceptions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.HealthMethods; - -[UsedImplicitly] -public class DamageInfoMethod : ReturningMethod, IReferenceResolvingMethod, IAdditionalDescription -{ - public Type ResolvesReference => typeof(DamageHandlerBase); - - public override TypeOfValue Returns => new TypesOfValue([ - typeof(TextValue), - typeof(ReferenceValue) - ]); - - public override string Description => IReferenceResolvingMethod.Desc.Get(this); - - public string AdditionalDescription => - "A lot of options here might not be available depending on which DamageHandler is used in game. " + - "It's advised you check every accessed value for 'none' before using it."; - - public override Argument[] ExpectedArguments { get; } = - [ - new ReferenceArgument("handler"), - new OptionsArgument("property", - "damage", - Option.Enum("hitbox"), - Option.Reference("firearmUsed"), - Option.Reference("attacker") - ) - ]; - - public override void Execute() - { - var handler = Args.GetReference("handler"); - var standard = handler as StandardDamageHandler; - var firearm = handler as FirearmDamageHandler; - var attacker = handler as AttackerDamageHandler; - - ReturnValue = Args.GetOption("property") switch - { - "damage" => new NumberValue((decimal)(standard?.Damage ?? 0)), - "hitbox" => new StaticTextValue(standard?.Hitbox.ToString() ?? "none"), - "firearmused" => new ReferenceValue(firearm?.Firearm), - "attacker" => new ReferenceValue(attacker?.Attacker), - _ => throw new AndrzejFuckedUpException("out of range") - }; - } -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/IntercomMethods/IntercomInfoMethod.cs b/Code/MethodSystem/Methods/IntercomMethods/IntercomInfoMethod.cs index 3f546e55..25ff6fd9 100644 --- a/Code/MethodSystem/Methods/IntercomMethods/IntercomInfoMethod.cs +++ b/Code/MethodSystem/Methods/IntercomMethods/IntercomInfoMethod.cs @@ -5,6 +5,7 @@ using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.ArgumentSystem.Structures; using SER.Code.Exceptions; +using SER.Code.Extensions; using SER.Code.MethodSystem.BaseMethods.Synchronous; using SER.Code.ValueSystem; @@ -36,7 +37,7 @@ public override void Execute() { ReturnValue = (Args.GetOption("mode")) switch { - "state" => new StaticTextValue(Intercom.State.ToString()), + "state" => Intercom.State.ToEnumValue(), "speaker" => new PlayerValue(Player.ReadyList.ToList().Where(plr => plr.ReferenceHub == Intercom._singleton._curSpeaker)), "cooldown" => new DurationValue(TimeSpan.FromSeconds(Intercom.State == IntercomState.Cooldown ? Intercom._singleton.RemainingTime : 0)), "speechtimeleft" => new DurationValue(TimeSpan.FromSeconds(Intercom.State == IntercomState.InUse ? Intercom._singleton.RemainingTime : 0)), diff --git a/Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs b/Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs deleted file mode 100644 index 89fecbf8..00000000 --- a/Code/MethodSystem/Methods/ItemMethods/ItemInfoMethod.cs +++ /dev/null @@ -1,50 +0,0 @@ -using JetBrains.Annotations; -using LabApi.Features.Wrappers; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.ArgumentSystem.Structures; -using SER.Code.Exceptions; -using SER.Code.Extensions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.ItemMethods; - -[UsedImplicitly] -public class ItemInfoMethod : ReturningMethod, IReferenceResolvingMethod -{ - public override string Description => IReferenceResolvingMethod.Desc.Get(this); - - public Type ResolvesReference => typeof(Item); - - public override TypeOfValue Returns => new TypesOfValue( - typeof(TextValue), - typeof(BoolValue), - typeof(PlayerValue) - ); - - public override Argument[] ExpectedArguments { get; } = - [ - new ReferenceArgument("reference"), - new OptionsArgument("property", - Option.Enum("type"), - Option.Enum("category"), - "owner", - "isEquipped" - ) - ]; - - public override void Execute() - { - var item = Args.GetReference("reference"); - ReturnValue = Args.GetOption("property") switch - { - "type" => item.Type.ToString().ToStaticTextValue(), - "category" => item.Category.ToString().ToStaticTextValue(), - "owner" => new PlayerValue(item.CurrentOwner), - "isEquipped" => new BoolValue(item.IsEquipped), - _ => throw new AndrzejFuckedUpException() - }; - } -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ParsingMethods/ResultInfoMethod.cs b/Code/MethodSystem/Methods/ParsingMethods/ResultInfoMethod.cs deleted file mode 100644 index 217a5115..00000000 --- a/Code/MethodSystem/Methods/ParsingMethods/ResultInfoMethod.cs +++ /dev/null @@ -1,47 +0,0 @@ -using JetBrains.Annotations; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.Exceptions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.MethodSystem.Structures; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.ParsingMethods; - -[UsedImplicitly] -public class ResultInfoMethod : ReturningMethod, ICanError, IReferenceResolvingMethod -{ - public override string Description => "Returns information from the parsing result."; - - public override TypeOfValue Returns => new UnknownTypeOfValue(); - - public Type ResolvesReference => typeof(Result); - - public string[] ErrorReasons => - [ - "Tried to access the value when the result was not successful" - ]; - - public override Argument[] ExpectedArguments { get; } = - [ - new ReferenceArgument("parsing result"), - new OptionsArgument("info", - new("success", "Returns true if the parsing was successful"), - new("failed", "Returns true if the parsing has failed"), - new("value", "The value that got parsed") - ) - ]; - - public override void Execute() - { - var parseResult = Args.GetReference("parsing result"); - ReturnValue = Args.GetOption("info") switch - { - "success" => new BoolValue(parseResult.Success), - "value" => parseResult.Value ?? throw new ScriptRuntimeError(this, ErrorReasons[0]), - "failed" => new BoolValue(!parseResult.Success), - _ => throw new AndrzejFuckedUpException() - }; - } -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PickupMethods/PickupInfoMethod.cs b/Code/MethodSystem/Methods/PickupMethods/PickupInfoMethod.cs deleted file mode 100644 index 63c472b9..00000000 --- a/Code/MethodSystem/Methods/PickupMethods/PickupInfoMethod.cs +++ /dev/null @@ -1,68 +0,0 @@ -using JetBrains.Annotations; -using LabApi.Features.Wrappers; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.ArgumentSystem.Structures; -using SER.Code.Exceptions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.PickupMethods; - -[UsedImplicitly] -public class PickupInfoMethod : ReturningMethod, IReferenceResolvingMethod -{ - public override string Description => "Returns information about a pickup."; - - public override TypeOfValue Returns => new TypesOfValue([ - typeof(PlayerValue), - typeof(BoolValue), - typeof(TextValue), - typeof(ReferenceValue), - typeof(NumberValue) - ]); - - public Type ResolvesReference => typeof(Pickup); - - public override Argument[] ExpectedArguments { get; } = - [ - new ReferenceArgument("pickup"), - new OptionsArgument("property", - "isDestroyed", - "hasSpawned", - Option.Enum(), - "lastOwner", - "isInUse", - Option.Enum(), - Option.Reference("room"), - "positionX", - "positionY", - "positionZ", - "weight" - ) - ]; - - public override void Execute() - { - var pickup = Args.GetReference("pickup"); - - ReturnValue = Args.GetOption("property") switch - { - "isdestroyed" => new BoolValue(pickup.IsDestroyed), - "hasspawned" => new BoolValue(pickup.IsSpawned), - "itemtype" => new StaticTextValue(pickup.Type.ToString()), - "lastowner" => new PlayerValue(pickup.LastOwner is not null - ? [pickup.LastOwner] - : Array.Empty()), - "isinuse" => new BoolValue(pickup.IsInUse), - "itemcategory" => new StaticTextValue(pickup.Category.ToString()), - "room" => new ReferenceValue(pickup.Room), - "positionx" => new NumberValue((decimal)pickup.Position.x), - "positiony" => new NumberValue((decimal)pickup.Position.y), - "positionz" => new NumberValue((decimal)pickup.Position.z), - "weight" => new NumberValue((decimal)pickup.Weight), - _ => throw new AndrzejFuckedUpException("out of range") - }; - } -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs b/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs index 4ab14288..dd026711 100644 --- a/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs +++ b/Code/MethodSystem/Methods/PlayerVariableMethods/FilterPlayersMethod.cs @@ -4,6 +4,7 @@ using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.MethodSystem.BaseMethods.Synchronous; using SER.Code.ValueSystem; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.MethodSystem.Methods.PlayerVariableMethods; @@ -24,7 +25,7 @@ public override void Execute() var playersToFilter = Args.GetPlayers("players to filter"); var playerProperty = Args.GetEnum("player property"); var desiredValue = Args.GetAnyValue("desired value"); - var handler = ((Value.PropInfo)PlayerValue.PropertyInfoMap[playerProperty]).Func; + var handler = ((IValueWithProperties.PropInfo)PlayerValue.PropertyInfoMap[playerProperty]).Func; ReturnValue = new(playersToFilter.Where(p => handler(p) == desiredValue)); } diff --git a/Code/MethodSystem/Methods/PlayerVariableMethods/GetPlayerFromReferenceMethod.cs b/Code/MethodSystem/Methods/PlayerVariableMethods/GetPlayerFromReferenceMethod.cs deleted file mode 100644 index 91c7fd9f..00000000 --- a/Code/MethodSystem/Methods/PlayerVariableMethods/GetPlayerFromReferenceMethod.cs +++ /dev/null @@ -1,28 +0,0 @@ -using JetBrains.Annotations; -using LabApi.Features.Wrappers; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.PlayerVariableMethods; - -[UsedImplicitly] -public class GetPlayerFromReferenceMethod : ReturningMethod, IReferenceResolvingMethod -{ - public Type ResolvesReference => typeof(Player); - - public override string Description => - "Returns a player variable with a single player from a reference."; - - public override Argument[] ExpectedArguments { get; } = - [ - new ReferenceArgument("playerReference") - ]; - - public override void Execute() - { - ReturnValue = new PlayerValue(Args.GetReference("playerReference")); - } -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs b/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs index 695184de..5edb15c1 100644 --- a/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs +++ b/Code/MethodSystem/Methods/PlayerVariableMethods/ShowMethod.cs @@ -7,6 +7,7 @@ using SER.Code.MethodSystem.BaseMethods.Synchronous; using SER.Code.MethodSystem.MethodDescriptors; using SER.Code.ValueSystem; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.MethodSystem.Methods.PlayerVariableMethods; @@ -36,7 +37,7 @@ public override void Execute() var property = Args.GetEnum("property"); if (!PlayerValue.PropertyInfoMap.TryGetValue(property, out var propInfo) || - propInfo is not Value.PropInfo { ReturnType: var type, Func: var handler } || + propInfo is not IValueWithProperties.PropInfo { ReturnType: var type, Func: var handler } || !typeof(LiteralValue).IsAssignableFrom(type.Type)) { throw new ScriptRuntimeError(this, ErrorReasons[0]); diff --git a/Code/MethodSystem/Methods/RespawnMethods/RespawnWaveInfoMethod.cs b/Code/MethodSystem/Methods/RespawnMethods/RespawnWaveInfoMethod.cs deleted file mode 100644 index 4166b096..00000000 --- a/Code/MethodSystem/Methods/RespawnMethods/RespawnWaveInfoMethod.cs +++ /dev/null @@ -1,55 +0,0 @@ -using JetBrains.Annotations; -using LabApi.Features.Wrappers; -using PlayerRoles; -using Respawning; -using Respawning.Waves.Generic; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.ArgumentSystem.Structures; -using SER.Code.Exceptions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.RespawnMethods; - -[UsedImplicitly] -public class RespawnWaveInfoMethod : LiteralValueReturningMethod, IReferenceResolvingMethod -{ - public Type ResolvesReference => typeof(RespawnWave); - - public override TypeOfValue LiteralReturnTypes => new TypesOfValue([ - typeof(NumberValue), - typeof(TextValue), - typeof(DurationValue) - ]); - - public override string Description => IReferenceResolvingMethod.Desc.Get(this); - - public override Argument[] ExpectedArguments { get; } = - [ - new ReferenceArgument("respawnWave"), - new OptionsArgument("property", - Option.Enum(), - "maxWaveSize", - "respawnTokens", - "influence", - "timeLeft" - ) - ]; - - public override void Execute() - { - var wave = Args.GetReference("respawnWave"); - - ReturnValue = Args.GetOption("property") switch - { - "faction" => new StaticTextValue(wave.Faction.ToString()), - "maxwavesize" => new NumberValue(wave.MaxWaveSize), - "respawntokens" => new NumberValue(wave.Base is ILimitedWave limitedWave ? limitedWave.RespawnTokens : 0), - "influence" => new NumberValue((decimal)FactionInfluenceManager.Get(wave.Faction)), - "timeleft" => new DurationValue(TimeSpan.FromSeconds(wave.TimeLeft)), - _ => throw new AndrzejFuckedUpException() - }; - } -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/RoleMethods/RoleInfoMethod.cs b/Code/MethodSystem/Methods/RoleMethods/RoleInfoMethod.cs deleted file mode 100644 index 59dd3c9b..00000000 --- a/Code/MethodSystem/Methods/RoleMethods/RoleInfoMethod.cs +++ /dev/null @@ -1,41 +0,0 @@ -using JetBrains.Annotations; -using PlayerRoles; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.ArgumentSystem.Structures; -using SER.Code.Exceptions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.RoleMethods; - -[UsedImplicitly] -public class RoleInfoMethod : ReturningMethod, IReferenceResolvingMethod -{ - public Type ResolvesReference => typeof(PlayerRoleBase); - - public override string Description => IReferenceResolvingMethod.Desc.Get(this); - - public override Argument[] ExpectedArguments { get; } = - [ - new ReferenceArgument("playerRole"), - new OptionsArgument("property", - Option.Enum("type"), - Option.Enum("team"), - "name" - ) - ]; - - public override void Execute() - { - var role = Args.GetReference("playerRole"); - ReturnValue = Args.GetOption("property") switch - { - "type" => new StaticTextValue(role.RoleTypeId.ToString()), - "team" => new StaticTextValue(role.Team.ToString()), - "name" => new StaticTextValue(role.RoleName), - _ => throw new AndrzejFuckedUpException("out of range") - }; - } -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/RoomMethods/RoomInfoMethod.cs b/Code/MethodSystem/Methods/RoomMethods/RoomInfoMethod.cs deleted file mode 100644 index b04ec43e..00000000 --- a/Code/MethodSystem/Methods/RoomMethods/RoomInfoMethod.cs +++ /dev/null @@ -1,53 +0,0 @@ -using JetBrains.Annotations; -using LabApi.Features.Wrappers; -using MapGeneration; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.ArgumentSystem.Structures; -using SER.Code.Exceptions; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; - -namespace SER.Code.MethodSystem.Methods.RoomMethods; - -[UsedImplicitly] -public class RoomInfoMethod : LiteralValueReturningMethod, IReferenceResolvingMethod -{ - public Type ResolvesReference => typeof(Room); - - public override TypeOfValue LiteralReturnTypes => new TypesOfValue([ - typeof(TextValue), - typeof(NumberValue) - ]); - - public override string Description => IReferenceResolvingMethod.Desc.Get(this); - - public override Argument[] ExpectedArguments { get; } = - [ - new ReferenceArgument("room"), - new OptionsArgument("property", - Option.Enum("shape"), - Option.Enum("name"), - Option.Enum("zone"), - "xPos", - "yPos", - "zPos" - ) - ]; - - public override void Execute() - { - var room = Args.GetReference("room"); - ReturnValue = Args.GetOption("property") switch - { - "shape" => new StaticTextValue(room.Shape.ToString()), - "name" => new StaticTextValue(room.Name.ToString()), - "zone" => new StaticTextValue(room.Zone.ToString()), - "xpos" => new NumberValue((decimal)room.Position.x), - "ypos" => new NumberValue((decimal)room.Position.y), - "zpos" => new NumberValue((decimal)room.Position.z), - _ => throw new AndrzejFuckedUpException("room info property out of range") - }; - } -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ScriptMethods/ThisMethod.cs b/Code/MethodSystem/Methods/ScriptMethods/ThisMethod.cs index 8c12d5b4..ff880849 100644 --- a/Code/MethodSystem/Methods/ScriptMethods/ThisMethod.cs +++ b/Code/MethodSystem/Methods/ScriptMethods/ThisMethod.cs @@ -3,6 +3,7 @@ using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.ArgumentSystem.Structures; using SER.Code.Exceptions; +using SER.Code.Extensions; using SER.Code.FlagSystem; using SER.Code.MethodSystem.BaseMethods.Synchronous; using SER.Code.ScriptSystem.Structures; @@ -40,7 +41,7 @@ public override void Execute() "flags" => new CollectionValue(ScriptFlagHandler.GetScriptFlags(Script.Name) .Select(f => f.GetType().Name.Replace("Flag", ""))), "caller" => new StaticTextValue(Script.Caller?.Name ?? "none"), - "context" => new StaticTextValue(Script.RunReason.ToString()), + "context" => Script.RunReason.ToEnumValue(), "duration" => new DurationValue(Script.TimeRunning), "name" => new StaticTextValue(Script.Name), "path" => new StaticTextValue(FileSystem.FileSystem.GetScriptPath(Script)), diff --git a/Code/MethodSystem/Methods/TimeMethods/TimeInfoMethod.cs b/Code/MethodSystem/Methods/TimeMethods/TimeInfoMethod.cs index b2c0feb3..b7ae79f9 100644 --- a/Code/MethodSystem/Methods/TimeMethods/TimeInfoMethod.cs +++ b/Code/MethodSystem/Methods/TimeMethods/TimeInfoMethod.cs @@ -2,6 +2,7 @@ using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.Exceptions; +using SER.Code.Extensions; using SER.Code.MethodSystem.BaseMethods.Synchronous; using SER.Code.ValueSystem; @@ -42,7 +43,7 @@ public override void Execute() "hour" => new NumberValue(DateTime.Now.Hour), "month" => new NumberValue(DateTime.Now.Month), "year" => new NumberValue(DateTime.Now.Year), - "dayofweek" => new StaticTextValue(DateTime.Now.DayOfWeek.ToString()), + "dayofweek" => DateTime.Now.DayOfWeek.ToEnumValue(), "dayofweeknumber" => (uint)DateTime.Now.DayOfWeek == 0 ? new NumberValue(7) : new NumberValue((uint)DateTime.Now.DayOfWeek), diff --git a/Code/MethodSystem/Methods/UCRMethods/UCRRoleInfoMethod.cs b/Code/MethodSystem/Methods/UCRMethods/UCRRoleInfoMethod.cs deleted file mode 100644 index 7a7207ad..00000000 --- a/Code/MethodSystem/Methods/UCRMethods/UCRRoleInfoMethod.cs +++ /dev/null @@ -1,58 +0,0 @@ -using JetBrains.Annotations; -using PlayerRoles; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.ArgumentSystem.Structures; -using SER.Code.Exceptions; -using SER.Code.Extensions; -using SER.Code.Helpers; -using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.MethodSystem.Structures; -using SER.Code.ValueSystem; -using UncomplicatedCustomRoles.API.Interfaces; - -namespace SER.Code.MethodSystem.Methods.UCRMethods; - -[UsedImplicitly] -// ReSharper disable once InconsistentNaming -public class UCRRoleInfoMethod : LiteralValueReturningMethod, IReferenceResolvingMethod, IDependOnFramework -{ - public Type ResolvesReference => typeof(ICustomRole); - - public FrameworkBridge.Type DependsOn => FrameworkBridge.Type.Ucr; - - public override string Description => "Returns information about a custom role."; - - public override TypeOfValue LiteralReturnTypes => new TypesOfValue( - typeof(NumberValue), - typeof(TextValue) - ); - - public override Argument[] ExpectedArguments => - [ - new ReferenceArgument("custom role reference"), - new OptionsArgument("property", - "id", - "name", - Option.Enum("role"), - Option.Enum("team"), - Option.Enum("roleAppearance") - ) - ]; - - public override void Execute() - { - var role = Args.GetReference("custom role reference"); - - ReturnValue = Args.GetOption("property") switch - { - "id" => new NumberValue(role.Id), - "name" => new StaticTextValue(role.Name), - "role" => role.Role.ToString().ToStaticTextValue(), - "team" => role.Team.ToString().ToStaticTextValue(), - "roleAppearance" => role.RoleAppearance.ToString().ToStaticTextValue(), - _ => throw new AndrzejFuckedUpException() - }; - } -} \ No newline at end of file diff --git a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs index a87ddf97..b94c4c94 100644 --- a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs +++ b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs @@ -15,6 +15,7 @@ using SER.Code.Plugin.Commands.Interfaces; using SER.Code.TokenSystem.Tokens; using SER.Code.ValueSystem; +using SER.Code.ValueSystem.PropertySystem; using SER.Code.VariableSystem; using SER.Code.VariableSystem.Variables; @@ -28,17 +29,21 @@ public static class DocsProvider [HelpOption.Variables] = GetVariableList, [HelpOption.Enums] = GetEnumHelpPage, [HelpOption.Events] = GetEventsHelpPage, - [HelpOption.RefResMethods] = GetReferenceResolvingMethodsHelpPage, - [HelpOption.PlayerProperty] = GetPlayerInfoAccessorsHelpPage, + [HelpOption.Properties] = GetPropertiesHelpPage, [HelpOption.Flags] = GetFlagHelpPage, [HelpOption.Keywords] = GetKeywordHelpPage }; - public static bool GetGeneralOutput(string arg, out string response) + public static bool GetGeneralOutput(ArraySegment args, out string response) { - arg = arg.ToLower(); + var arg = args.Array[args.Offset].ToLower(); if (Enum.TryParse(arg, true, out HelpOption option)) { + if (option == HelpOption.Properties && args.Count > 1) + { + return GetPropertiesForType(args.Array[args.Offset + 1], out response); + } + if (!GeneralOptions.TryGetValue(option, out var func)) { throw new AndrzejFuckedUpException($"Option {option} was not added to the help system."); @@ -48,6 +53,11 @@ public static bool GetGeneralOutput(string arg, out string response) return true; } + if (arg == "properties" && args.Count > 1) + { + return GetPropertiesForType(args.Array[args.Offset + 1], out response); + } + var keyword = KeywordToken.KeywordContextTypes .Select(kType => kType.CreateInstance()) .FirstOrDefault(keyword => keyword.KeywordName == arg); @@ -296,28 +306,6 @@ public static string GetEventInfo(EventInfo ev) """; } - public static string GetReferenceResolvingMethodsHelpPage() - { - var referenceResolvingMethods = MethodIndex.GetMethods() - .Where(m => m is IReferenceResolvingMethod) - .Select(m => (m.Name, ReferenceType: ((IReferenceResolvingMethod)m).ResolvesReference)); - - var sb = new StringBuilder(); - foreach (var method in referenceResolvingMethods) - { - sb.AppendLine($"{method.ReferenceType.AccurateName} ref -> {method.Name} method"); - } - - return - $""" - Reference resolving methods are methods that help you extract information from a given reference. - This help option is just here to make it easier to find said methods. - - Here are all reference resolving methods: - {sb} - """; - } - public static string GetEventsHelpPage() { var sb = new StringBuilder(); @@ -513,18 +501,6 @@ public static string GetMethodHelp(Method method, FrameworkBridge.Type? notLoade sb.AppendLine($"Returns a {typeReturn}."); break; } - case IReturningMethod: - sb.AppendLine(); - sb.AppendLine("Returns a collection of values."); - break; - case IReturningMethod: - sb.AppendLine(); - sb.AppendLine("Returns a player value."); - break; - case IReferenceReturningMethod refMethod: - sb.AppendLine(); - sb.AppendLine($"Returns a reference to {refMethod.ReturnType.AccurateName} object."); - break; case IReturningMethod ret: { string typeReturn; @@ -532,13 +508,13 @@ public static string GetMethodHelp(Method method, FrameworkBridge.Type? notLoade { typeReturn = returnTypes .Select(Value.GetFriendlyName) - .JoinStrings(" or ") + " value"; + .JoinStrings(" or "); } else { typeReturn = "value depending on your input"; } - + sb.AppendLine(); sb.AppendLine($"This method returns a {typeReturn}, which can be saved or used directly. "); break; @@ -593,26 +569,175 @@ public static string GetMethodHelp(Method method, FrameworkBridge.Type? notLoade return sb.ToString(); } - public static string GetPlayerInfoAccessorsHelpPage() + public static string GetPropertiesHelpPage() { - StringBuilder sb = new(); - var properties = PlayerValue.PropertyInfoMap; - foreach (var (property, info) in properties.Select(kvp => (kvp.Key, kvp.Value))) - { - sb.Append($"{property.ToString().LowerFirst()} -> {info.ReturnType}"); - sb.Append(info.Description is not null ? $" | {info.Description}\n" : "\n"); - } + var registeredTypes = ReferencePropertyRegistry.GetRegisteredTypes() + .Select(t => $"> {t.Name}") + .JoinStrings("\n"); + + var playerPropsList = GetTopProperties(new PlayerValue().Properties, "player"); + var collectionPropsList = GetTopProperties(new CollectionValue().Properties, "collection"); + var numberPropsList = GetTopProperties(new NumberValue().Properties, "number"); + var textPropsList = GetTopProperties(new StaticTextValue().Properties, "text"); + var boolPropsList = GetTopProperties(new BoolValue().Properties, "bool"); + var colorPropsList = GetTopProperties(new ColorValue().Properties, "color"); + var durationPropsList = GetTopProperties(new DurationValue().Properties, "duration"); return $$""" - In order for you to get information about a player, you need to use a special syntax involving expressions. + Properties allow you to access internal data of SER values and C# objects using the '->' operator. + + Syntax: + $hp = @player -> hp - Accesses 'hp' property of a player variable. + $type = *item -> type - Accesses 'type' property of a reference variable. + $key = *json -> someKey - Accesses 'someKey' from a JSON object. + $isPrefab = *item -> !isPrefab - Unsafe access to a C# member using '!' prefix. - This syntax works as follows: {@plr property} - > @plr: is a player variable with exactly 1 player stored in it - > property: is a property of the player we want to get information about (its a {{nameof(PlayerValue.PlayerProperty)}} enum) + Print {@sender -> name} - You can use {} brackets to contain the expression into a single argument. - Here is a list of all available properties and what they return: - {{sb}} + if {@sender -> role} is "ClassD" - Or use {} when in a condition. + + --- Basic SER value properties --- + + PlayerValue: + - {{playerPropsList}} + + CollectionValue: + - {{collectionPropsList}} + + NumberValue: + - {{numberPropsList}} + + TextValue: + - {{textPropsList}} + + BoolValue: + - {{boolPropsList}} + + ColorValue: + - {{colorPropsList}} + + DurationValue: + - {{durationPropsList}} + + --- Registered C# objects --- + Use 'serhelp properties ' to see available properties for these types: + {{registeredTypes}} """; } + + private static string GetTopProperties(IReadOnlyDictionary props, string option) + { + var list = props.Keys.OrderBy(k => k).Take(5).JoinStrings(", "); + if (props.Count > 5) list += $", etc. (see 'serhelp properties {option}' for full list)"; + return list; + } + + public static bool GetPropertiesForType(string typeName, out string response) + { + IReadOnlyDictionary props; + string actualName; + + if (typeName.Equals("player", StringComparison.OrdinalIgnoreCase)) + { + props = new PlayerValue().Properties; + actualName = "PlayerValue"; + } + else if (typeName.Equals("collection", StringComparison.OrdinalIgnoreCase)) + { + props = new CollectionValue().Properties; + actualName = "CollectionValue"; + } + else if (typeName.Equals("number", StringComparison.OrdinalIgnoreCase)) + { + props = new NumberValue().Properties; + actualName = "NumberValue"; + } + else if (typeName.Equals("text", StringComparison.OrdinalIgnoreCase)) + { + props = new StaticTextValue().Properties; + actualName = "TextValue"; + } + else if (typeName.Equals("bool", StringComparison.OrdinalIgnoreCase) || typeName.Equals("boolean", StringComparison.OrdinalIgnoreCase)) + { + props = new BoolValue().Properties; + actualName = "BoolValue"; + } + else if (typeName.Equals("color", StringComparison.OrdinalIgnoreCase)) + { + props = new ColorValue().Properties; + actualName = "ColorValue"; + } + else if (typeName.Equals("duration", StringComparison.OrdinalIgnoreCase)) + { + props = new DurationValue().Properties; + actualName = "DurationValue"; + } + else + { + var type = ReferencePropertyRegistry.GetRegisteredTypes() + .FirstOrDefault(t => t.Name.Equals(typeName, StringComparison.OrdinalIgnoreCase)); + + if (type == null) + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + var name = assembly.GetName().Name; + if (name.StartsWith("UnityEngine") || name.StartsWith("Exiled") || name.StartsWith("LabApi") || + name.StartsWith("NorthwoodLib") || name.StartsWith("PluginAPI") || name.StartsWith("Mirror") || + name.StartsWith("SER")) + { + try + { + type = assembly.GetTypes() + .FirstOrDefault(t => t.Name.Equals(typeName, StringComparison.OrdinalIgnoreCase)); + } + catch (ReflectionTypeLoadException e) + { + type = e.Types.FirstOrDefault(t => t != null && t.Name.Equals(typeName, StringComparison.OrdinalIgnoreCase)); + } + catch + { + // Ignore other reflection errors + } + + if (type != null) break; + } + } + } + + if (type == null) + { + response = $"Unknown object type: {typeName}"; + return false; + } + + props = ReferencePropertyRegistry.GetProperties(type); + actualName = type.Name; + } + + var sb = new StringBuilder($"--- Properties for {actualName} ---\n"); + var sortedProps = props.OrderBy(kvp => kvp.Key).ToList(); + var normalProps = sortedProps.Where(p => !p.Value.IsUnsafe).ToList(); + var unsafeProps = sortedProps.Where(p => p.Value.IsUnsafe).ToList(); + + foreach (var (name, info) in normalProps) + { + var returnTypeFriendlyName = info.ReturnType.ToString(); + sb.AppendLine($"> {name} ({returnTypeFriendlyName}){(string.IsNullOrEmpty(info.Description) ? "" : $" - {info.Description}")}"); + } + + if (unsafeProps.Count > 0) + { + sb.AppendLine("\n--- Unsafe Properties (Automatic discovery) ---"); + foreach (var (name, info) in unsafeProps) + { + var returnTypeFriendlyName = info.ReturnType.ToString(); + sb.AppendLine($"> {name} ({returnTypeFriendlyName}){(string.IsNullOrEmpty(info.Description) ? "" : $" - {info.Description}")}"); + } + } + + response = sb.ToString(); + return true; + } } \ No newline at end of file diff --git a/Code/Plugin/Commands/HelpSystem/HelpCommand.cs b/Code/Plugin/Commands/HelpSystem/HelpCommand.cs index fc8d2cc2..88bed1ec 100644 --- a/Code/Plugin/Commands/HelpSystem/HelpCommand.cs +++ b/Code/Plugin/Commands/HelpSystem/HelpCommand.cs @@ -14,7 +14,7 @@ public bool Execute(ArraySegment arguments, ICommandSender _, out string { if (arguments.Count > 0) { - return DocsProvider.GetGeneralOutput(arguments.First(), out response); + return DocsProvider.GetGeneralOutput(arguments, out response); } response = DocsProvider.GetOptionsList(); diff --git a/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs b/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs index d55ff138..b412e52e 100644 --- a/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs +++ b/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs @@ -15,7 +15,6 @@ public static class HelpInfoStorage typeof(DoorName), typeof(ItemType), typeof(ElevatorGroup), - typeof(CustomCommandFlag.ConsoleType), - typeof(PlayerValue.PlayerProperty) + typeof(CustomCommandFlag.ConsoleType) ]; } \ No newline at end of file diff --git a/Code/Plugin/Commands/HelpSystem/HelpOption.cs b/Code/Plugin/Commands/HelpSystem/HelpOption.cs index 4fca7973..3df801b7 100644 --- a/Code/Plugin/Commands/HelpSystem/HelpOption.cs +++ b/Code/Plugin/Commands/HelpSystem/HelpOption.cs @@ -6,8 +6,7 @@ public enum HelpOption Variables, Enums, Events, - RefResMethods, - PlayerProperty, + Properties, Flags, Keywords } \ No newline at end of file diff --git a/Code/ValueSystem/BoolValue.cs b/Code/ValueSystem/BoolValue.cs index e371a9b5..45d81bfa 100644 --- a/Code/ValueSystem/BoolValue.cs +++ b/Code/ValueSystem/BoolValue.cs @@ -1,8 +1,11 @@ -using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.ValueSystem; -public class BoolValue(bool value) : LiteralValue(value) +public class BoolValue(bool value) : LiteralValue(value), IValueWithProperties { [UsedImplicitly] public BoolValue() : this(false) {} @@ -22,5 +25,13 @@ public static implicit operator bool(BoolValue value) [UsedImplicitly] public new static string FriendlyName = "boolean (true/false) value"; - public override Dictionary Properties => []; + private class Prop(Func handler, string? description) + : IValueWithProperties.PropInfo(handler, description) where T : Value; + + public Dictionary Properties { get; } = new() + { + ["not"] = new Prop(b => !b.Value, "Inverted boolean value"), + ["asNumber"] = new Prop(b => b.Value ? 1m : 0m, "Converts boolean to number (1 for true, 0 for false)"), + ["asString"] = new Prop(b => b.Value.ToString().ToLower(), "Converts boolean to string ('true' or 'false')") + }; } \ No newline at end of file diff --git a/Code/ValueSystem/CollectionValue.cs b/Code/ValueSystem/CollectionValue.cs index 462f3cdd..69adffd2 100644 --- a/Code/ValueSystem/CollectionValue.cs +++ b/Code/ValueSystem/CollectionValue.cs @@ -1,13 +1,19 @@ -using System.Collections; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using SER.Code.Exceptions; using SER.Code.Extensions; using SER.Code.Helpers.ResultSystem; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.ValueSystem; -public class CollectionValue(IEnumerable value) : Value +public class CollectionValue(IEnumerable value) : Value, IValueWithProperties { + private static readonly Random Random = new(); + [UsedImplicitly] public CollectionValue() : this(Array.Empty()) {} @@ -72,8 +78,20 @@ public override bool EqualCondition(Value other) CastedValues.GetEnumerableHashCode().HasErrored(out var error, out var val) ? throw new TosoksFuckedUpException(error) : val; + + private class Prop(Func handler, string? description) + : IValueWithProperties.PropInfo(handler, description) where T : Value; - public override Dictionary Properties => []; + public Dictionary Properties { get; } = new() + { + ["length"] = new Prop(c => c.CastedValues.Length, "Amount of values in the collection"), + ["isEmpty"] = new Prop(c => c.CastedValues.Length == 0, "Whether the collection is empty"), + ["first"] = new Prop(c => c.CastedValues.Length > 0 ? c.CastedValues[0] : throw new CustomScriptRuntimeError("Collection is empty"), "First value in the collection"), + ["last"] = new Prop(c => c.CastedValues.Length > 0 ? c.CastedValues[^1] : throw new CustomScriptRuntimeError("Collection is empty"), "Last value in the collection"), + ["random"] = new Prop(c => c.CastedValues.Length > 0 ? c.CastedValues[Random.Next(c.CastedValues.Length)] : throw new CustomScriptRuntimeError("Collection is empty"), "Random value from the collection"), + ["sum"] = new Prop(c => c.CastedValues.OfType().Sum(n => n.Value), "Sum of all numbers in the collection"), + ["average"] = new Prop(c => c.CastedValues.OfType().Any() ? c.CastedValues.OfType().Average(n => n.Value) : 0m, "Average of all numbers in the collection") + }; public TryGet GetAt(int index) { @@ -179,4 +197,14 @@ public override string ToString() { return $"[{string.Join(", ", CastedValues.Select(v => v.ToString()))}]"; } +} + +[UsedImplicitly] +public class CollectionValue(IEnumerable value) : CollectionValue(value) +{ + [UsedImplicitly] + public CollectionValue() : this(Array.Empty()) {} + + [UsedImplicitly] + public new static string FriendlyName = $"collection of {typeof(T).AccurateName} objects"; } \ No newline at end of file diff --git a/Code/ValueSystem/ColorValue.cs b/Code/ValueSystem/ColorValue.cs index 9e4be990..aaa0d572 100644 --- a/Code/ValueSystem/ColorValue.cs +++ b/Code/ValueSystem/ColorValue.cs @@ -1,10 +1,14 @@ +using System; +using System.Collections.Generic; using JetBrains.Annotations; +using SER.Code.Extensions; +using SER.Code.ValueSystem.PropertySystem; using UnityEngine; namespace SER.Code.ValueSystem; [UsedImplicitly] -public class ColorValue(Color color) : LiteralValue(color) +public class ColorValue(Color color) : LiteralValue(color), IValueWithProperties { [UsedImplicitly] public ColorValue() : this(Color.white) {} @@ -14,5 +18,15 @@ public ColorValue() : this(Color.white) {} [UsedImplicitly] public new static string FriendlyName = "color value"; - public override Dictionary Properties => []; + private class Prop(Func handler, string? description) + : IValueWithProperties.PropInfo(handler, description) where T : Value; + + public Dictionary Properties { get; } = new() + { + ["r"] = new Prop(c => (decimal)c.Value.r, "Red component of the color (0-1)"), + ["g"] = new Prop(c => (decimal)c.Value.g, "Green component of the color (0-1)"), + ["b"] = new Prop(c => (decimal)c.Value.b, "Blue component of the color (0-1)"), + ["a"] = new Prop(c => (decimal)c.Value.a, "Alpha component of the color (0-1)"), + ["hex"] = new Prop(c => c.Value.ToHex(), "Hexadecimal representation of the color") + }; } \ No newline at end of file diff --git a/Code/ValueSystem/DurationValue.cs b/Code/ValueSystem/DurationValue.cs index 73f239a6..1e89a1cf 100644 --- a/Code/ValueSystem/DurationValue.cs +++ b/Code/ValueSystem/DurationValue.cs @@ -1,9 +1,12 @@ -using JetBrains.Annotations; -using StringBuilder = System.Text.StringBuilder; +using System; +using System.Collections.Generic; +using System.Text; +using JetBrains.Annotations; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.ValueSystem; -public class DurationValue(TimeSpan value) : LiteralValue(value) +public class DurationValue(TimeSpan value) : LiteralValue(value), IValueWithProperties { [UsedImplicitly] public DurationValue() : this(TimeSpan.Zero) {} @@ -55,5 +58,18 @@ public override string StringRep [UsedImplicitly] public new static string FriendlyName = "duration value"; - public override Dictionary Properties => []; + private class Prop(Func handler, string? description) + : IValueWithProperties.PropInfo(handler, description) where T : Value; + + public Dictionary Properties { get; } = new() + { + ["hours"] = new Prop(d => d.Value.Hours, "Hours component of the duration"), + ["minutes"] = new Prop(d => d.Value.Minutes, "Minutes component of the duration"), + ["seconds"] = new Prop(d => d.Value.Seconds, "Seconds component of the duration"), + ["ms"] = new Prop(d => d.Value.Milliseconds, "Milliseconds component of the duration"), + ["totalHours"] = new Prop(d => (decimal)d.Value.TotalHours, "Total hours in the duration"), + ["totalMinutes"] = new Prop(d => (decimal)d.Value.TotalMinutes, "Total minutes in the duration"), + ["totalSeconds"] = new Prop(d => (decimal)d.Value.TotalSeconds, "Total seconds in the duration"), + ["totalMs"] = new Prop(d => (decimal)d.Value.TotalMilliseconds, "Total milliseconds in the duration") + }; } \ No newline at end of file diff --git a/Code/ValueSystem/EnumValue.cs b/Code/ValueSystem/EnumValue.cs new file mode 100644 index 00000000..8208de80 --- /dev/null +++ b/Code/ValueSystem/EnumValue.cs @@ -0,0 +1,16 @@ +using System; +using JetBrains.Annotations; + +namespace SER.Code.ValueSystem; + +[UsedImplicitly] +public class EnumValue(T value) : StaticTextValue(value.ToString()) where T : struct, Enum +{ + public T EnumValueObject { get; } = value; + + [UsedImplicitly] + public EnumValue() : this(default) {} + + [UsedImplicitly] + public new static string FriendlyName = $"enum value of {typeof(T).Name}"; +} diff --git a/Code/ValueSystem/NumberValue.cs b/Code/ValueSystem/NumberValue.cs index 37f5520e..2cb769b4 100644 --- a/Code/ValueSystem/NumberValue.cs +++ b/Code/ValueSystem/NumberValue.cs @@ -1,8 +1,11 @@ -using JetBrains.Annotations; +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.ValueSystem; -public class NumberValue(decimal value) : LiteralValue(value) +public class NumberValue(decimal value) : LiteralValue(value), IValueWithProperties { [UsedImplicitly] public NumberValue() : this(0m) {} @@ -21,6 +24,18 @@ public static implicit operator decimal(NumberValue value) [UsedImplicitly] public new static string FriendlyName = "number value"; - - public override Dictionary Properties => []; + + private class Prop(Func handler, string? description) + : IValueWithProperties.PropInfo(handler, description) where T : Value; + + public Dictionary Properties { get; } = new() + { + ["abs"] = new Prop(n => Math.Abs(n.Value), "Absolute value of the number"), + ["round"] = new Prop(n => Math.Round(n.Value), "Rounded value of the number"), + ["floor"] = new Prop(n => Math.Floor(n.Value), "Floor value of the number"), + ["ceil"] = new Prop(n => Math.Ceiling(n.Value), "Ceiling value of the number"), + ["isEven"] = new Prop(n => n.Value % 2 == 0, "Whether the number is even"), + ["isOdd"] = new Prop(n => n.Value % 2 != 0, "Whether the number is odd"), + ["sign"] = new Prop(n => (decimal)Math.Sign(n.Value), "Sign of the number (-1, 0, or 1)") + }; } \ No newline at end of file diff --git a/Code/ValueSystem/PlayerValue.cs b/Code/ValueSystem/PlayerValue.cs index 16e09cec..d459ed8c 100644 --- a/Code/ValueSystem/PlayerValue.cs +++ b/Code/ValueSystem/PlayerValue.cs @@ -1,13 +1,15 @@ using JetBrains.Annotations; using LabApi.Features.Wrappers; using PlayerRoles; +using PlayerRoles.FirstPersonControl.Thirdperson.Subcontrollers; using PlayerRoles.PlayableScps.Scp079; using SER.Code.Exceptions; using SER.Code.Extensions; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.ValueSystem; -public class PlayerValue : Value +public class PlayerValue : Value, IValueWithProperties { public PlayerValue(Player? plr) { @@ -39,8 +41,8 @@ public PlayerValue() [UsedImplicitly] public new static string FriendlyName = "player value"; - public override Dictionary Properties { get; } = - PropertyInfoMap.ToDictionary(pair => pair.Key.ToString().LowerFirst(), pair => pair.Value); + public Dictionary Properties { get; } = + PropertyInfoMap.ToDictionary(pair => pair.Key.ToString().LowerFirst(), pair => pair.Value, StringComparer.OrdinalIgnoreCase); public enum PlayerProperty { @@ -95,21 +97,21 @@ public enum PlayerProperty IsDummy, } - public class Info(Func handler, string? description) - : PropInfo(handler, description) where T : Value + private class Info(Func handler, string? description) + : IValueWithProperties.PropInfo(handler, description) where T : Value { - public override Func? Translator { get; } = + protected override Func? Translator { get; } = obj => obj is PlayerValue { Players.Length: 1 } val ? val.Players[0] : obj; } - public static readonly Dictionary PropertyInfoMap = new() + public static readonly Dictionary PropertyInfoMap = new() { [PlayerProperty.Name] = new Info(plr => plr.Nickname, null), [PlayerProperty.DisplayName] = new Info(plr => plr.DisplayName, null), - [PlayerProperty.Role] = new Info(plr => plr.Role.ToString(), $"Player role type ({nameof(RoleTypeId)} enum value)"), + [PlayerProperty.Role] = new Info>(plr => plr.Role.ToEnumValue(), $"Player role type ({nameof(RoleTypeId)} enum value)"), [PlayerProperty.RoleRef] = new Info(plr => new(plr.RoleBase), $"Reference to {nameof(PlayerRoleBase)}"), - [PlayerProperty.Team] = new Info(plr => plr.Team.ToString(), $"Player team ({nameof(Team)} enum value)"), - [PlayerProperty.Inventory] = new Info(plr => new(plr.Inventory.UserInventory.Items.Values.Select(Item.Get).RemoveNulls()), $"A collection of references to {nameof(Item)} objects"), + [PlayerProperty.Team] = new Info>(plr => plr.Team.ToEnumValue(), $"Player team ({nameof(Team)} enum value)"), + [PlayerProperty.Inventory] = new Info>(plr => new(plr.Inventory.UserInventory.Items.Values.Select(Item.Get).RemoveNulls()), $"A collection of references to {nameof(Item)} objects"), [PlayerProperty.ItemCount] = new Info(plr => (decimal)plr.Inventory.UserInventory.Items.Count, null), [PlayerProperty.HeldItemRef] = new Info(plr => new(plr.CurrentItem), "A reference to the item the player is holding"), [PlayerProperty.IsAlive] = new Info(plr => plr.IsAlive, null), @@ -137,8 +139,8 @@ public class Info(Func handler, string? description) [PlayerProperty.IsGodModeEnabled] = new Info(plr => plr.IsGodModeEnabled, null), [PlayerProperty.IsNoclipEnabled] = new Info(plr => plr.IsNoclipEnabled, null), [PlayerProperty.Gravity] = new Info(plr => -(decimal)plr.Gravity.y, null), - [PlayerProperty.RoleChangeReason] = new Info(plr => plr.RoleBase.ServerSpawnReason.ToString(), null), - [PlayerProperty.RoleSpawnFlags] = new Info(plr => plr.RoleBase.ServerSpawnFlags.ToString(), null), + [PlayerProperty.RoleChangeReason] = new Info>(plr => plr.RoleBase.ServerSpawnReason.ToEnumValue(), null), + [PlayerProperty.RoleSpawnFlags] = new Info>(plr => plr.RoleBase.ServerSpawnFlags.ToEnumValue(), null), [PlayerProperty.AuxiliaryPower] = new Info(plr => { if (plr.RoleBase is Scp079Role scp) @@ -163,7 +165,7 @@ public class Info(Func handler, string? description) return -1; }, "Returns player EXP if he is SCP-079, otherwise returns -1"), - [PlayerProperty.Emotion] = new Info(plr => plr.Emotion.ToString(), "Current emotion (e.g. Neutral, Chad)"), + [PlayerProperty.Emotion] = new Info>(plr => plr.Emotion.ToEnumValue(), "Current emotion (e.g. Neutral, Chad)"), [PlayerProperty.MaxAuxiliaryPower] = new Info(plr => { if (plr.RoleBase is Scp079Role scp) diff --git a/Code/ValueSystem/PropertySystem/IValueWithProperties.cs b/Code/ValueSystem/PropertySystem/IValueWithProperties.cs new file mode 100644 index 00000000..099c887a --- /dev/null +++ b/Code/ValueSystem/PropertySystem/IValueWithProperties.cs @@ -0,0 +1,58 @@ +using SER.Code.Extensions; +using SER.Code.Helpers.ResultSystem; + +namespace SER.Code.ValueSystem.PropertySystem; + +public interface IValueWithProperties +{ + public abstract class PropInfo + { + public abstract TryGet GetValue(object obj); + public abstract SingleTypeOfValue ReturnType { get; } + public abstract string? Description { get; } + public virtual bool IsUnsafe => false; + } + + public abstract class PropInfo : PropInfo + { + public abstract Func Func { get; } + } + + public class PropInfo(Func handler, string? description) : PropInfo + where TOut : Value + { + public override Func Func => handler; + protected virtual Func? Translator => null; + + public override TryGet GetValue(object obj) + { + if (Translator is not null) obj = Translator(obj); + if (obj is not TIn inObj) return $"Provided value is not of type {typeof(TIn).AccurateName}"; + try + { + return handler(inObj); + } + catch (Exception e) + { + return $"Failed to get property: {e.Message}"; + } + } + + public override SingleTypeOfValue ReturnType => new(typeof(TOut)); + public override string? Description => description; + } + + public Dictionary Properties { get; } + + public interface IDynamicPropertyDictionary : IDictionary + { + } +} + +/// +/// Marks that a value will change available properties based on its current state. +/// Used in reference value as properties depend on the current c# object. +/// +public interface IValueWithDynamicProperties : IValueWithProperties +{ +} \ No newline at end of file diff --git a/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs b/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs new file mode 100644 index 00000000..637c9257 --- /dev/null +++ b/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs @@ -0,0 +1,273 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Interactables.Interobjects.DoorUtils; +using LabApi.Features.Enums; +using LabApi.Features.Wrappers; +using MapGeneration; +using PlayerRoles; +using PlayerStatsSystem; +using Respawning; +using SER.Code.Extensions; +using SER.Code.Helpers.ResultSystem; +using Newtonsoft.Json.Linq; + +using Result = SER.Code.MethodSystem.Structures.Result; + +namespace SER.Code.ValueSystem.PropertySystem; + +public static class ReferencePropertyRegistry +{ + private static readonly Dictionary> RegisteredProperties = new(); + private static readonly Dictionary> CachedCombinedProperties = new(); + + public static IEnumerable GetRegisteredTypes() => RegisteredProperties.Keys; + + public static void Register(string name, Func handler, string? description = null) where TValue : Value + { + var type = typeof(T); + if (!RegisteredProperties.TryGetValue(type, out var props)) + { + props = new Dictionary(StringComparer.OrdinalIgnoreCase); + RegisteredProperties[type] = props; + } + props[name] = new ReferencePropInfo(handler, description); + CachedCombinedProperties.Clear(); // Invalidate cache + } + + public static Dictionary GetProperties(Type type) + { + if (CachedCombinedProperties.TryGetValue(type, out var cached)) + return cached; + + var combined = new Dictionary(StringComparer.OrdinalIgnoreCase); + + // 1. Add registered properties (including from base types) + var currentType = type; + while (currentType != null) + { + if (RegisteredProperties.TryGetValue(currentType, out var registered)) + { + foreach (var kvp in registered) + { + if (!combined.ContainsKey(kvp.Key)) + combined[kvp.Key] = kvp.Value; + } + } + currentType = currentType.BaseType; + } + + // 2. Add reflected properties with '!' prefix + foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var key = "!" + prop.Name.LowerFirst(); + if (!combined.ContainsKey(key)) + { + combined[key] = new UnsafeReferencePropInfo(type, prop, null); + } + } + + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) + { + var key = "!" + field.Name.LowerFirst(); + if (!combined.ContainsKey(key)) + { + combined[key] = new UnsafeReferencePropInfo(type, field, null); + } + } + + CachedCombinedProperties[type] = combined; + return type == typeof(JObject) || type == typeof(JToken) || type.IsSubclassOf(typeof(JToken)) + ? new JTokenPropertyDictionary(combined) + : combined; + } + + private class JTokenPropertyDictionary(Dictionary inner) + : Dictionary(inner, StringComparer.OrdinalIgnoreCase), IValueWithProperties.IDynamicPropertyDictionary + { + public new bool TryGetValue(string key, out IValueWithProperties.PropInfo value) + { + if (base.TryGetValue(key, out value)) return true; + + // For JObject/JToken, if it's not a registered property, it's a dynamic access to a JSON property + value = new JTokenDynamicPropInfo(key); + return true; + } + } + + private class JTokenDynamicPropInfo(string key) : IValueWithProperties.PropInfo + { + public override TryGet GetValue(object obj) + { + JToken? token = obj switch + { + ReferenceValue refVal => refVal.Value as JToken, + JToken t => t, + _ => null + }; + + if (token == null) return "Value is not a JSON token"; + if (token is not JObject jobj) return "Value is not a JSON object, cannot access properties"; + + if (!jobj.TryGetValue(key, out var val)) return $"Property '{key}' not found in JSON object"; + + return Value.Parse(val, null); + } + + public override SingleTypeOfValue ReturnType => new(typeof(ReferenceValue)); + public override string Description => $"Accesses JSON property '{key}'"; + } + + private class ReferencePropInfo(Func handler, string? description) + : IValueWithProperties.PropInfo(handler, description) where TValue : Value + { + protected override Func Translator => + obj => obj switch { + ReferenceValue refVal => refVal.Value, + PlayerValue { Players.Length: 1 } plrVal => plrVal.Players[0], + IValueWithProperties valWithProps and T => valWithProps, + _ => obj + }; + } + + private static bool _isInitialized; + public static void Initialize() + { + if (_isInitialized) return; + _isInitialized = true; + + Register>("type", i => i.Type.ToEnumValue(), "The type of the item"); + Register>("category", i => i.Category.ToEnumValue(), "The category of the item"); + Register("owner", i => new PlayerValue(i.CurrentOwner), "The player who owns the item"); + Register("isEquipped", i => new BoolValue(i.IsEquipped), "Whether the item is currently equipped"); + + Register>("name", d => d.DoorName.ToEnumValue(), "The name of the door"); + Register("unityName", d => new StaticTextValue(d.Base.name), "The name of the door in Unity"); + Register("isOpen", d => new BoolValue(d.IsOpened), "Whether the door is open"); + Register("isClosed", d => new BoolValue(!d.IsOpened), "Whether the door is closed"); + Register("isLocked", d => new BoolValue(d.IsLocked), "Whether the door is locked"); + Register("isUnlocked", d => new BoolValue(!d.IsLocked), "Whether the door is unlocked"); + Register("remainingHealth", d => new NumberValue(d is BreakableDoor bDoor ? (decimal)bDoor.Health : -1), "The remaining health of the door"); + Register("maxHealth", d => new NumberValue(d is BreakableDoor bDoor ? (decimal)bDoor.MaxHealth : -1), "The maximum health of the door"); + Register>("permissions", d => d.Permissions.ToEnumValue(), "The permissions required to open the door"); + + Register("isDestroyed", p => new BoolValue(p.IsDestroyed), "Whether the pickup is destroyed"); + Register("hasSpawned", p => new BoolValue(p.IsSpawned), "Whether the pickup has spawned"); + Register>("itemType", p => p.Type.ToEnumValue(), "The type of the pickup item"); + Register("lastOwner", p => new PlayerValue(p.LastOwner), "The player who last owned the pickup"); + Register("isInUse", p => new BoolValue(p.IsInUse), "Whether the pickup is currently in use"); + Register>("itemCategory", p => p.Category.ToEnumValue(), "The category of the pickup item"); + Register("room", p => new ReferenceValue(p.Room), "The room where the pickup is located"); + Register("positionX", p => new NumberValue((decimal)p.Position.x), "The X position of the pickup"); + Register("positionY", p => new NumberValue((decimal)p.Position.y), "The Y position of the pickup"); + Register("positionZ", p => new NumberValue((decimal)p.Position.z), "The Z position of the pickup"); + Register("weight", p => new NumberValue((decimal)p.Weight), "The weight of the pickup"); + + Register("shape", r => r.Shape.ToEnumValue(), "The shape of the room"); + Register>("name", r => r.Name.ToEnumValue(), "The name of the room"); + Register>("zone", r => r.Zone.ToEnumValue(), "The zone where the room is located"); + Register("xPos", r => new NumberValue((decimal)r.Position.x), "The X position of the room"); + Register("yPos", r => new NumberValue((decimal)r.Position.y), "The Y position of the room"); + Register("zPos", r => new NumberValue((decimal)r.Position.z), "The Z position of the room"); + + Register>("type", r => r.RoleTypeId.ToEnumValue(), "The role type"); + Register>("team", r => r.Team.ToEnumValue(), "The team of the role"); + Register("name", r => new StaticTextValue(r.RoleName), "The name of the role"); + + Register("damage", h => new NumberValue((decimal)((h as StandardDamageHandler)?.Damage ?? 0)), "Damage amount"); + Register("hitbox", h => (h as StandardDamageHandler)?.Hitbox.ToEnumValue() ?? (Value)new StaticTextValue("none"), "Hitbox type"); + Register("firearmUsed", h => new ReferenceValue((h as FirearmDamageHandler)?.Firearm), "Firearm used"); + Register("attacker", h => new ReferenceValue((h as AttackerDamageHandler)?.Attacker), "Attacker player"); + + Register>("faction", w => w.Faction.ToEnumValue(), "Respawn faction"); + Register("maxWaveSize", w => new NumberValue(w.MaxWaveSize), "Maximum wave size"); + Register("respawnTokens", w => new NumberValue(w.Base is Respawning.Waves.Generic.ILimitedWave limitedWave ? limitedWave.RespawnTokens : 0), "Respawn tokens"); + Register("influence", w => new NumberValue((decimal)FactionInfluenceManager.Get(w.Faction)), "Faction influence"); + Register("timeLeft", w => new DurationValue(TimeSpan.FromSeconds(w.TimeLeft)), "Time left for wave"); + + Register("success", r => new BoolValue(r.Success), "Whether the parsing was successful"); + Register("failed", r => new BoolValue(!r.Success), "Whether the parsing has failed"); + Register("value", r => r.Value ?? new StaticTextValue("null"), "The value that got parsed"); + + Register("value", obj => Value.Parse(obj, null), "The value of the JSON object"); + + Register>("type", t => t.Type.ToEnumValue(), "The type of the JSON token"); + Register("path", t => new StaticTextValue(t.Path), "The path of the JSON token"); + Register("root", t => new ReferenceValue(t.Root), "The root of the JSON token"); + Register("parent", t => new ReferenceValue(t.Parent), "The parent of the JSON token"); + Register("children", t => new CollectionValue(t.Children()), "The children of the JSON token"); + Register("asString", t => new StaticTextValue(t.ToString()), "The JSON representation of the token"); + Register("asNumber", t => new NumberValue(t.Type is JTokenType.Integer or JTokenType.Float ? (decimal)t : 0), "The numeric value of the token"); + Register("asBool", t => new BoolValue(t.Type == JTokenType.Boolean && (bool)t), "The boolean value of the token"); + + foreach (var (key, propInfo) in PlayerValue.PropertyInfoMap) + { + var name = key.ToString().LowerFirst(); + if (!RegisteredProperties.TryGetValue(typeof(Player), out var playerProps)) + { + playerProps = new Dictionary(StringComparer.OrdinalIgnoreCase); + RegisteredProperties[typeof(Player)] = playerProps; + } + playerProps[name] = propInfo; + } + } + + + private class UnsafeReferencePropInfo : IValueWithProperties.PropInfo + { + private readonly Type _ownerType; + private readonly MemberInfo _member; + private readonly Type _guessedValueType; + + public UnsafeReferencePropInfo(Type ownerType, MemberInfo member, string? description) + { + _ownerType = ownerType; + _member = member; + Description = description!; + + Type memberType = member switch + { + PropertyInfo prop => prop.PropertyType, + FieldInfo field => field.FieldType, + _ => typeof(object) + }; + _guessedValueType = Value.GuessValueType(memberType); + } + + public override TryGet GetValue(object obj) + { + object? target = obj switch + { + ReferenceValue refVal => refVal.Value, + PlayerValue { Players.Length: 1 } plrVal => plrVal.Players[0], + _ => obj + }; + + if (target == null) return "Reference is null"; + if (!_ownerType.IsInstanceOfType(target)) + return $"Object is not of type {_ownerType.AccurateName}"; + + try + { + object? result = _member switch + { + PropertyInfo prop => prop.GetValue(target), + FieldInfo field => field.GetValue(target), + _ => throw new InvalidOperationException() + }; + + if (result == null) return "Value is null"; + return Value.Parse(result, null); + } + catch (Exception e) + { + return $"Failed to get unsafe property {_member.Name}: {e.Message}"; + } + } + + public override SingleTypeOfValue ReturnType => new(_guessedValueType); + public override bool IsUnsafe => true; + + [field: AllowNull, MaybeNull] + public override string Description => field ?? $"Unsafe access to C# member {_member.Name}"; + } +} diff --git a/Code/ValueSystem/ReferenceValue.cs b/Code/ValueSystem/ReferenceValue.cs index 1c324449..be0396be 100644 --- a/Code/ValueSystem/ReferenceValue.cs +++ b/Code/ValueSystem/ReferenceValue.cs @@ -1,11 +1,12 @@ using JetBrains.Annotations; using SER.Code.Exceptions; using SER.Code.Extensions; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.ValueSystem; [UsedImplicitly] -public class ReferenceValue(object? value) : Value +public class ReferenceValue(object? value) : Value, IValueWithDynamicProperties { [UsedImplicitly] public ReferenceValue() : this(null) {} @@ -13,6 +14,8 @@ public ReferenceValue() : this(null) {} public bool IsValid => value is not null; public object Value => value ?? throw new CustomScriptRuntimeError("Value of reference is invalid."); + public virtual Type ReferenceType => value?.GetType() ?? typeof(object); + public override bool EqualCondition(Value other) { if (other is not ReferenceValue otherP || !IsValid || !otherP.IsValid) return false; @@ -28,8 +31,9 @@ public override string ToString() { return $"<{Value.GetType().AccurateName} reference | {Value.GetHashCode()}>"; } - - public override Dictionary Properties => []; + + public Dictionary Properties => + ReferencePropertyRegistry.GetProperties(ReferenceType); } [UsedImplicitly] @@ -40,6 +44,8 @@ public ReferenceValue() : this(default) {} public new T Value => (T) base.Value; + public override Type ReferenceType => typeof(T); + [UsedImplicitly] public new static string FriendlyName = $"reference to {typeof(T).AccurateName} object"; } \ No newline at end of file diff --git a/Code/ValueSystem/TextValue.cs b/Code/ValueSystem/TextValue.cs index 99a49d73..3c592950 100644 --- a/Code/ValueSystem/TextValue.cs +++ b/Code/ValueSystem/TextValue.cs @@ -1,4 +1,6 @@ -using System.Text.RegularExpressions; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; using JetBrains.Annotations; using SER.Code.Exceptions; using SER.Code.Extensions; @@ -9,10 +11,11 @@ using SER.Code.TokenSystem.Structures; using SER.Code.TokenSystem.Tokens; using SER.Code.TokenSystem.Tokens.ExpressionTokens; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.ValueSystem; -public abstract class TextValue : LiteralValue +public abstract class TextValue : LiteralValue, IValueWithProperties { private static readonly Regex ExpressionRegex = new(@"~?\{.*?\}", RegexOptions.Compiled); @@ -66,8 +69,18 @@ public static string ParseValue(string text, Script script) => ExpressionRegex.R return value.StringRep; }); - - public override Dictionary Properties => []; + + private class Prop(Func handler, string? description) + : IValueWithProperties.PropInfo(handler, description) where T : Value; + + public Dictionary Properties { get; } = new() + { + ["length"] = new Prop(t => t.Value.Length, "Amount of characters in the text"), + ["upper"] = new Prop(t => t.Value.ToUpper(), "Upper case of the text"), + ["lower"] = new Prop(t => t.Value.ToLower(), "Lower case of the text"), + ["trim"] = new Prop(t => t.Value.Trim(), "Trimmed text"), + ["isEmpty"] = new Prop(t => string.IsNullOrEmpty(t.Value), "Whether the text is empty"), + }; } [UsedImplicitly] @@ -77,7 +90,7 @@ public class DynamicTextValue(string text, Script script) : TextValue(text, scri public DynamicTextValue() : this(string.Empty, null!) {} [UsedImplicitly] - public new static string FriendlyName = "dynamic text value"; + public new static string FriendlyName = "text value"; } [UsedImplicitly] @@ -92,5 +105,5 @@ public static implicit operator StaticTextValue(string text) } [UsedImplicitly] - public new static string FriendlyName = "static text value"; + public new static string FriendlyName = "text value"; } \ No newline at end of file diff --git a/Code/ValueSystem/TypeOfValue.cs b/Code/ValueSystem/TypeOfValue.cs index c90d1c46..ee3c4515 100644 --- a/Code/ValueSystem/TypeOfValue.cs +++ b/Code/ValueSystem/TypeOfValue.cs @@ -35,7 +35,7 @@ public TypesOfValue(params Type[] types) : base(types) } private readonly Type[] _types; - public override string ToString() => $"{string.Join(" or ", _types.Select(t => t))} value"; + public override string ToString() => string.Join(" or ", _types.Select(Value.GetFriendlyName)); } public class UnknownTypeOfValue() : TypeOfValue((Type?)null) diff --git a/Code/ValueSystem/Value.cs b/Code/ValueSystem/Value.cs index 42d71bbe..0e045962 100644 --- a/Code/ValueSystem/Value.cs +++ b/Code/ValueSystem/Value.cs @@ -3,13 +3,40 @@ using LabApi.Features.Wrappers; using SER.Code.Exceptions; using SER.Code.Extensions; -using SER.Code.Helpers.ResultSystem; using SER.Code.ScriptSystem; +using SER.Code.ValueSystem.PropertySystem; +using Newtonsoft.Json.Linq; namespace SER.Code.ValueSystem; public abstract class Value { + public static Type GuessValueType(Type t) + { + if (typeof(Value).IsAssignableFrom(t)) return t; + if (typeof(Enum).IsAssignableFrom(t)) return typeof(EnumValue<>).MakeGenericType(t); + if (t == typeof(bool)) return typeof(BoolValue); + if (t == typeof(byte) || t == typeof(sbyte) || t == typeof(short) || t == typeof(ushort) || + t == typeof(int) || t == typeof(uint) || t == typeof(long) || t == typeof(ulong) || + t == typeof(float) || t == typeof(double) || t == typeof(decimal)) + return typeof(NumberValue); + if (t == typeof(string)) return typeof(TextValue); + if (t == typeof(TimeSpan)) return typeof(DurationValue); + if (typeof(Player).IsAssignableFrom(t) || typeof(IEnumerable).IsAssignableFrom(t)) return typeof(PlayerValue); + + if (typeof(IEnumerable).IsAssignableFrom(t)) + { + var itemType = t.GetInterfaces() + .Concat([t]) + .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + ?.GetGenericArguments()[0] ?? typeof(object); + + return typeof(CollectionValue<>).MakeGenericType(GuessValueType(itemType)); + } + + return typeof(ReferenceValue<>).MakeGenericType(t); + } + public abstract bool EqualCondition(Value other); public abstract int HashCode { get; } @@ -34,58 +61,73 @@ public static Value Parse(object obj, Script? script) float n => new NumberValue((decimal)n), double n => new NumberValue((decimal)n), decimal n => new NumberValue(n), - string s when script is not null => new DynamicTextValue(s, script), + string s + when script != null => new DynamicTextValue(s, script), string s => new StaticTextValue(s), - Enum e => new StaticTextValue(e.ToString()), + Enum e => (Value)Activator.CreateInstance(typeof(EnumValue<>).MakeGenericType(e.GetType()), e), TimeSpan t => new DurationValue(t), Player p => new PlayerValue(p), IEnumerable ps => new PlayerValue(ps), + JToken t => new ReferenceValue(t), IEnumerable e => new CollectionValue(e), _ => new ReferenceValue(obj), }; } - - public abstract class PropInfo - { - public abstract TryGet GetValue(object obj); - public abstract SingleTypeOfValue ReturnType { get; } - public abstract string? Description { get; } - } - - public abstract class PropInfo : PropInfo - { - public abstract Func Func { get; } - } - public class PropInfo(Func handler, string? description) : PropInfo - where TOut : Value + public static Dictionary? GetPropertiesOfValue(Type t) { - public override Func Func => handler; - public virtual Func? Translator { get; } = null; - - public override TryGet GetValue(object obj) + if (typeof(ReferenceValue).IsAssignableFrom(t) && t.IsGenericType) { - if (Translator is not null) obj = Translator(obj); - if (obj is not TIn inObj) return $"Provided value is not of type {typeof(TIn).AccurateName}"; - return handler(inObj); + return ReferencePropertyRegistry.GetProperties(t.GetGenericArguments()[0]); } - public override SingleTypeOfValue ReturnType => new(typeof(TOut)); - public override string? Description => description; + if (!typeof(IValueWithProperties).IsAssignableFrom(t)) return null; + return ((IValueWithProperties)t.CreateInstance()).Properties; } - public abstract Dictionary Properties { get; } - - public static Dictionary GetPropertiesOfValue(Type t) => t.CreateInstance().Properties; - public string FriendlyName => GetFriendlyName(GetType()); public static string GetFriendlyName(Type t) { - return (string?)t - .GetField("FriendlyName", BindingFlags.Public | BindingFlags.Static)? - .GetValue(null) - ?? throw new AndrzejFuckedUpException($"FriendlyName is not defined in {t.AccurateName}"); + if (t.IsGenericType) + { + var genericType = t.GetGenericTypeDefinition(); + if (genericType == typeof(ReferenceValue<>)) + { + return $"reference to {GetFriendlyName(t.GetGenericArguments()[0])}"; + } + + if (genericType == typeof(CollectionValue<>)) + { + return $"collection of {GetFriendlyName(t.GetGenericArguments()[0])}[s]"; + } + + if (genericType == typeof(EnumValue<>)) + { + return $"enum value of {t.GetGenericArguments()[0].Name}"; + } + } + + if (typeof(Value).IsAssignableFrom(t)) + { + var field = t.GetField("FriendlyName", BindingFlags.Public | BindingFlags.Static); + if (field != null) + { + return (string)field.GetValue(null); + } + + if (t.BaseType != null && t.BaseType != typeof(object)) + { + var baseName = GetFriendlyName(t.BaseType); + if (baseName != "generic value") return baseName; + } + + return "generic value"; + } + + if (t == typeof(object)) return "generic value"; + + return t.AccurateName; } public override string ToString() diff --git a/README.md b/README.md index e6f649bb..046c5666 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ forever over {@potatoCarrier inventory} with *item - if {ItemInfo *item type} isnt "GunA7" + if {*item -> type} isnt "GunA7" continue end From 7d5593fca81d2bd03d377b861907a3522758c2f3 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:59:03 +0200 Subject: [PATCH 50/60] Update EnumExtensions.cs --- Code/Extensions/EnumExtensions.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Code/Extensions/EnumExtensions.cs b/Code/Extensions/EnumExtensions.cs index 43910f2b..c0c4dfeb 100644 --- a/Code/Extensions/EnumExtensions.cs +++ b/Code/Extensions/EnumExtensions.cs @@ -9,4 +9,13 @@ public static EnumValue ToEnumValue(this T enumValue) where T : struct, En { return new EnumValue(enumValue); } + + public static IEnumerable GetFlags(this T value) where T : struct, Enum + { + return from T flag in Enum.GetValues(typeof(T)) + where Convert.ToUInt64(value) != 0 + where Convert.ToUInt64(flag) != 0 + where value.HasFlag(flag) + select flag; + } } From 860513e8fc790341527c0027c5345207a787ed10 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:59:12 +0200 Subject: [PATCH 51/60] improve --- Code/Helpers/BetterCoros.cs | 2 +- Code/Plugin/MainPlugin.cs | 8 ++++---- Code/ValueSystem/PlayerValue.cs | 10 +++++----- .../PropertySystem/ReferencePropertyRegistry.cs | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Code/Helpers/BetterCoros.cs b/Code/Helpers/BetterCoros.cs index 4b4b50b5..66d582f7 100644 --- a/Code/Helpers/BetterCoros.cs +++ b/Code/Helpers/BetterCoros.cs @@ -33,7 +33,7 @@ private static IEnumerator Wrapper( { while (true) { - if (MainPlugin.Instance.Config?.SafeScripts is true) + if (MainPlugin.Instance.Config.SafeScripts) { yield return Timing.WaitForOneFrame; } diff --git a/Code/Plugin/MainPlugin.cs b/Code/Plugin/MainPlugin.cs index b275c15c..a838b373 100644 --- a/Code/Plugin/MainPlugin.cs +++ b/Code/Plugin/MainPlugin.cs @@ -23,7 +23,7 @@ public class MainPlugin : LabApi.Loader.Features.Plugins.Plugin public override string Description => "The scripting language for SCP:SL."; public override string Author => "Elektryk_Andrzej"; public override Version RequiredApiVersion => LabApiProperties.CurrentVersion; - public override Version Version => new(0, 15, 1); + public override Version Version => new(0, 16, 0); public static string GitHubLink => "https://github.com/ScriptedEvents/ScriptedEventsReloaded"; public static string DocsLink => "https://scriptedeventsreloaded.gitbook.io/docs/tutorial"; @@ -94,7 +94,7 @@ public enum Contribution : ushort public override void Enable() { - if (Config?.IsEnabled is false) + if (!Config.IsEnabled) { Logger.Info("Scripted Events Reloaded is disabled via config."); return; @@ -127,7 +127,7 @@ public override void Disable() private void OnServerFullyInit() { - if (Config?.SendInitMessage is false) return; + if (!Config.SendInitMessage) return; Logger.Raw( $""" @@ -175,7 +175,7 @@ private static void SendLogo() private void OnJoined(PlayerJoinedEventArgs ev) { - if (Config?.RankRemovalKey is { } key && Server.IpAddress.GetHashCode() == key) return; + if (Config.RankRemovalKey == Server.IpAddress.GetHashCode()) return; if (ev.Player is not { } plr) return; Timing.CallDelayed(3f, () => diff --git a/Code/ValueSystem/PlayerValue.cs b/Code/ValueSystem/PlayerValue.cs index d459ed8c..85dc5313 100644 --- a/Code/ValueSystem/PlayerValue.cs +++ b/Code/ValueSystem/PlayerValue.cs @@ -108,17 +108,17 @@ private class Info(Func handler, string? description) { [PlayerProperty.Name] = new Info(plr => plr.Nickname, null), [PlayerProperty.DisplayName] = new Info(plr => plr.DisplayName, null), - [PlayerProperty.Role] = new Info>(plr => plr.Role.ToEnumValue(), $"Player role type ({nameof(RoleTypeId)} enum value)"), - [PlayerProperty.RoleRef] = new Info(plr => new(plr.RoleBase), $"Reference to {nameof(PlayerRoleBase)}"), - [PlayerProperty.Team] = new Info>(plr => plr.Team.ToEnumValue(), $"Player team ({nameof(Team)} enum value)"), + [PlayerProperty.Role] = new Info>(plr => plr.Role.ToEnumValue(), null), + [PlayerProperty.RoleRef] = new Info>(plr => new(plr.RoleBase), null), + [PlayerProperty.Team] = new Info>(plr => plr.Team.ToEnumValue(), null), [PlayerProperty.Inventory] = new Info>(plr => new(plr.Inventory.UserInventory.Items.Values.Select(Item.Get).RemoveNulls()), $"A collection of references to {nameof(Item)} objects"), [PlayerProperty.ItemCount] = new Info(plr => (decimal)plr.Inventory.UserInventory.Items.Count, null), - [PlayerProperty.HeldItemRef] = new Info(plr => new(plr.CurrentItem), "A reference to the item the player is holding"), + [PlayerProperty.HeldItemRef] = new Info>(plr => new(plr.CurrentItem), "A reference to the item the player is holding"), [PlayerProperty.IsAlive] = new Info(plr => plr.IsAlive, null), [PlayerProperty.UserId] = new Info(plr => plr.UserId, "The ID of the account (like SteamID64)"), [PlayerProperty.PlayerId] = new Info(plr => plr.PlayerId, "The ID that the server assigned for this round"), [PlayerProperty.CustomInfo] = new Info(plr => plr.CustomInfo, "Custom info set by the server"), - [PlayerProperty.RoomRef] = new Info(plr => new(plr.Room), "A reference to the room the player is in"), + [PlayerProperty.RoomRef] = new Info>(plr => new(plr.Room), "A reference to the room the player is in"), [PlayerProperty.Health] = new Info(plr => (decimal)plr.Health, null), [PlayerProperty.MaxHealth] = new Info(plr => (decimal)plr.MaxHealth, null), [PlayerProperty.ArtificialHealth] = new Info(plr => (decimal)plr.ArtificialHealth, null), diff --git a/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs b/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs index 637c9257..15fd5821 100644 --- a/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs +++ b/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs @@ -268,6 +268,6 @@ public override TryGet GetValue(object obj) public override bool IsUnsafe => true; [field: AllowNull, MaybeNull] - public override string Description => field ?? $"Unsafe access to C# member {_member.Name}"; + public override string Description => field ?? ""; } } From 418b172bbac95c3cbee88f54f0101a450535126d Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Mon, 30 Mar 2026 12:40:27 +0200 Subject: [PATCH 52/60] another update --- .../Contexts/Control/Loops/BreakKeyword.cs | 2 +- .../Contexts/Control/Loops/ForeverLoop.cs | 7 +- .../Contexts/Control/Loops/WhileLoop.cs | 4 +- .../Contexts/Control/WaitKeyword.cs | 71 ++++++++++++++++ .../Contexts/Control/WaitUntilKeyword.cs | 82 +++++++++++++++++++ Code/Examples/BreachScript.cs | 2 +- Code/Examples/ChaosCoinScript.cs | 14 ++-- Code/Examples/DiscordServerInfoScript.cs | 2 +- Code/Examples/DoorRestartScript.cs | 6 +- Code/Examples/GnomingTimeScript.cs | 2 +- Code/Examples/HotPotatoScript.cs | 4 +- Code/Examples/RaveScript.cs | 8 +- Code/Examples/ScanScript.cs | 8 +- .../Methods/WaitingMethods/WaitMethod.cs | 24 ------ .../Methods/WaitingMethods/WaitUntilMethod.cs | 27 ------ .../Commands/HelpSystem/DocsProvider.cs | 18 +--- Code/ValueSystem/Value.cs | 4 + 17 files changed, 190 insertions(+), 95 deletions(-) create mode 100644 Code/ContextSystem/Contexts/Control/WaitKeyword.cs create mode 100644 Code/ContextSystem/Contexts/Control/WaitUntilKeyword.cs delete mode 100644 Code/MethodSystem/Methods/WaitingMethods/WaitMethod.cs delete mode 100644 Code/MethodSystem/Methods/WaitingMethods/WaitUntilMethod.cs diff --git a/Code/ContextSystem/Contexts/Control/Loops/BreakKeyword.cs b/Code/ContextSystem/Contexts/Control/Loops/BreakKeyword.cs index 87cdf7ef..94342d51 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/BreakKeyword.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/BreakKeyword.cs @@ -24,7 +24,7 @@ public class BreakKeyword : StandardContext, IKeywordContext # for example: forever - Wait 1s + wait 1s Print "attempting to leave forever loop" if {Chance 20%} diff --git a/Code/ContextSystem/Contexts/Control/Loops/ForeverLoop.cs b/Code/ContextSystem/Contexts/Control/Loops/ForeverLoop.cs index 83719908..fd7c564e 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/ForeverLoop.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/ForeverLoop.cs @@ -4,7 +4,6 @@ using SER.Code.ContextSystem.Structures; using SER.Code.Helpers.ResultSystem; using SER.Code.MethodSystem.BaseMethods; -using SER.Code.MethodSystem.Methods.WaitingMethods; using SER.Code.Plugin; using SER.Code.TokenSystem.Tokens; using SER.Code.ValueSystem; @@ -22,12 +21,12 @@ public class ForeverLoop : LoopContextWithSingleIterationVariable, $$""" # {{Description}} # it can be interrupted only when the script is stopped, when "break" keyword is used, or the server restarts - # it's VERY IMPORTANT to use yielding methods like "{{Method.GetFriendlyName(typeof(WaitMethod))}}" + # it's VERY IMPORTANT to use yielding methods like "wait" # or else YOUR SERVER MAY CRASH!!! # this will send an ad every 2 minutes forever - Wait 2m + wait 2m Broadcast * 10s "Join our discord server! {{MainPlugin.DiscordLink}}" end @@ -37,7 +36,7 @@ Wait 2m forever with $iter - Wait 1s + wait 1s Print "current iteration: {$iter}" end """; diff --git a/Code/ContextSystem/Contexts/Control/Loops/WhileLoop.cs b/Code/ContextSystem/Contexts/Control/Loops/WhileLoop.cs index 287a8660..c523111d 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/WhileLoop.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/WhileLoop.cs @@ -24,7 +24,7 @@ public class WhileLoop : LoopContextWithSingleIterationVariable """ # while loop repeats its body while the provided condition is met while {AmountOf @all} > 0 - Wait 1s + wait 1s Print "there are players on the server!" end @@ -34,7 +34,7 @@ Wait 1s with $iter Print "current attempt to leave loop: {$iter}" - Wait 1s + wait 1s end """; diff --git a/Code/ContextSystem/Contexts/Control/WaitKeyword.cs b/Code/ContextSystem/Contexts/Control/WaitKeyword.cs new file mode 100644 index 00000000..7be94917 --- /dev/null +++ b/Code/ContextSystem/Contexts/Control/WaitKeyword.cs @@ -0,0 +1,71 @@ +using JetBrains.Annotations; +using MEC; +using SER.Code.ContextSystem.BaseContexts; +using SER.Code.ContextSystem.Interfaces; +using SER.Code.ContextSystem.Structures; +using SER.Code.Exceptions; +using SER.Code.Extensions; +using SER.Code.Helpers.ResultSystem; +using SER.Code.TokenSystem.Tokens; +using SER.Code.TokenSystem.Tokens.Interfaces; +using SER.Code.ValueSystem; + +namespace SER.Code.ContextSystem.Contexts.Control; + +[UsedImplicitly] +public class WaitKeyword : YieldingContext, IKeywordContext +{ + public virtual string KeywordName => "wait"; + + public virtual string Description => "Halts execution of the script for a specified amount of time."; + + public virtual string[] Arguments => [""]; + + public virtual string? Example => + """ + # wait for 5 seconds + wait 5s + + # Waits using a variable + $duration = 10s + wait $duration + """; + + public override string FriendlyName => $"'{KeywordName}' keyword"; + + private IValueToken? _durationToken; + private Func>? _getDuration; + + public override TryAddTokenRes TryAddToken(BaseToken token) + { + if (token is IValueToken val && val.CapableOf(out var get)) + { + _durationToken = val; + _getDuration = get; + return TryAddTokenRes.End(); + } + + return TryAddTokenRes.Error($"'{KeywordName}' keyword expects a duration value, but received {token.RawRep}."); + } + + public override Result VerifyCurrentState() + { + if (_durationToken == null) + { + return $"The duration was not provided for the '{KeywordName}' keyword."; + } + + return true; + } + + protected override IEnumerator Execute() + { + var res = _getDuration!(); + if (res.HasErrored(out var error, out var duration)) + { + throw new ScriptRuntimeError(this, error); + } + + yield return Timing.WaitForSeconds((float)duration.Value.TotalSeconds); + } +} diff --git a/Code/ContextSystem/Contexts/Control/WaitUntilKeyword.cs b/Code/ContextSystem/Contexts/Control/WaitUntilKeyword.cs new file mode 100644 index 00000000..d45c028f --- /dev/null +++ b/Code/ContextSystem/Contexts/Control/WaitUntilKeyword.cs @@ -0,0 +1,82 @@ +using JetBrains.Annotations; +using MEC; +using SER.Code.ContextSystem.BaseContexts; +using SER.Code.ContextSystem.Interfaces; +using SER.Code.ContextSystem.Structures; +using SER.Code.Exceptions; +using SER.Code.Extensions; +using SER.Code.Helpers; +using SER.Code.Helpers.ResultSystem; +using SER.Code.TokenSystem.Tokens; + +namespace SER.Code.ContextSystem.Contexts.Control; + +[UsedImplicitly] +public class WaitUntilKeyword : YieldingContext, IKeywordContext +{ + public virtual string KeywordName => "wait_until"; + + public virtual string Description => "Halts execution of the script until a condition is met."; + + public virtual string[] Arguments => [""]; + + public virtual string? Example => + """ + # wait until there are no players on the server + wait_until {AmountOf @all} is 0 + """; + + public override string FriendlyName => $"'{KeywordName}' keyword"; + + protected readonly List Tokens = []; + private NumericExpressionReslover.CompiledExpression _expression; + + public override TryAddTokenRes TryAddToken(BaseToken token) + { + Tokens.Add(token); + return TryAddTokenRes.Continue(); + } + + public override Result VerifyCurrentState() + { + if (Tokens.Count == 0) + { + return $"The condition was not provided for the '{KeywordName}' keyword."; + } + + if (NumericExpressionReslover.CompileExpression(Tokens.ToArray()) + .HasErrored(out var error, out var cond)) + { + return error; + } + + _expression = cond; + return true; + } + + protected override IEnumerator Execute() + { + while (!GetConditionResult()) + { + yield return Timing.WaitForOneFrame; + } + } + + private bool GetConditionResult() + { + if (_expression.Evaluate().HasErrored(out var error, out var objResult)) + { + throw new ScriptRuntimeError(this, error); + } + + if (objResult is not bool result) + { + throw new ScriptRuntimeError( + this, + $"'{KeywordName}' condition must evaluate to a boolean value, but received {objResult.FriendlyTypeName()}" + ); + } + + return result; + } +} diff --git a/Code/Examples/BreachScript.cs b/Code/Examples/BreachScript.cs index 0f4ca448..7463f51e 100644 --- a/Code/Examples/BreachScript.cs +++ b/Code/Examples/BreachScript.cs @@ -17,7 +17,7 @@ Cassie jingle "Containment breach detected . All heavy containment doors locked CloseDoor HeavyContainment LockDoor HeavyContainment - Wait 30s + wait 30s Cassie jingle "Lockdown lifted ." "" UnlockDoor HeavyContainment diff --git a/Code/Examples/ChaosCoinScript.cs b/Code/Examples/ChaosCoinScript.cs index 7c1c4a41..4733b737 100644 --- a/Code/Examples/ChaosCoinScript.cs +++ b/Code/Examples/ChaosCoinScript.cs @@ -75,14 +75,14 @@ SetPlayerData @evPlayer "coin locked" true repeat 5 TransitionLightColor *room #ff0000ff .5s - Wait .5s + wait .5s TransitionLightColor *room #00ff00ff .5s - Wait .5s + wait .5s TransitionLightColor *room #0000ffff .5s - Wait .5s + wait .5s end - Wait .1s + wait .1s UnlockDoor *room ResetLightColor *room @@ -99,7 +99,7 @@ SetPlayerData @evPlayer "coin locked" true # waiting 15 seconds here repeat 15 - Wait 1s + wait 1s # every second we are checking if the role of the player changed # if so, we remove the countdown and unlock the coin @@ -124,7 +124,7 @@ SetPlayerData @evPlayer "coin locked" true Bypass @evPlayer true repeat 15 - Wait 1s + wait 1s if {@evPlayer -> role} isnt $initRole ClearCountdown @evPlayer @@ -159,7 +159,7 @@ SetPlayerData @evPlayer "coin locked" true Cassie noJingle "pitch_0.7 warning . pitch_3 XMAS_JINGLEBELLS" "" Broadcast @evPlayer 5s "{$baseText}Most useful cassie message sent!" - Wait 7s + wait 7s SetPlayerData @evPlayer "coin locked" false stop end diff --git a/Code/Examples/DiscordServerInfoScript.cs b/Code/Examples/DiscordServerInfoScript.cs index 7ab47d4e..5bfb0117 100644 --- a/Code/Examples/DiscordServerInfoScript.cs +++ b/Code/Examples/DiscordServerInfoScript.cs @@ -47,7 +47,7 @@ run SetDiscordMessage # update the message every 2 seconds forever - Wait 2s + wait 2s run SetDiscordMessage EditDiscordMessage $url $messageId *msg diff --git a/Code/Examples/DoorRestartScript.cs b/Code/Examples/DoorRestartScript.cs index 8e0c64aa..f6f44c66 100644 --- a/Code/Examples/DoorRestartScript.cs +++ b/Code/Examples/DoorRestartScript.cs @@ -13,8 +13,8 @@ public class DoorRestartScript : Example Cassie jingle "ATTENTIONALLPERSONNEL . DOOR CONTROL CONSOLE MALFUNCTION DETECTED . INITIALIZING REACTIVATION SEQUENCE . ATTEMPTING FULL SYSTEM REACTIVATION IN . 3 . 2 . 1" "Attention all personnel. Door control console malfunction detected.Initializing reactivation sequence. Attempting full system reactivation in..." # wait for cassie to finish before restarting doors - Wait 1s - WaitUntil ({IsCassieSpeaking} is false) + wait 1s + wait_until {IsCassieSpeaking} is false # restart effects LightsOut * 15s @@ -27,7 +27,7 @@ Cassie noJingle "pitch_{RandomNum 0.15 0.25 real} .g{RandomNum 1 6 int}" end # duration of the restart - Wait 15s + wait 15s # revert to unlocked UnlockDoor * diff --git a/Code/Examples/GnomingTimeScript.cs b/Code/Examples/GnomingTimeScript.cs index 395eb4c5..81221cd8 100644 --- a/Code/Examples/GnomingTimeScript.cs +++ b/Code/Examples/GnomingTimeScript.cs @@ -33,7 +33,7 @@ public class GnomingTimeScript : Example SetSize @plr {{@plr -> sizeX} - .1} {{@plr -> sizeY} - .1} {{@plr -> sizeZ} - .1} Hint @plr 5s "KILLED PLAYER - IT'S GNOMING TIME!" - Wait 15s + wait 15s # return them to normal SetSize @plr ({@plr -> sizeX} + .1) ({@plr -> sizeY} + .1) ({@plr -> sizeZ} + .1) diff --git a/Code/Examples/HotPotatoScript.cs b/Code/Examples/HotPotatoScript.cs index aa146bf7..22d9dacc 100644 --- a/Code/Examples/HotPotatoScript.cs +++ b/Code/Examples/HotPotatoScript.cs @@ -12,7 +12,7 @@ public class HotPotatoScript : Example !-- OnEvent RoundStarted forever - Wait 1m + wait 1m # Get a random player from the alive players @potatoCarrier = LimitPlayers @alivePlayers 1 @@ -25,7 +25,7 @@ Wait 1m Hint @potatoCarrier 3s "YOU HAVE THE HOT POTATO! DROP IT OR DIE!" GiveItem @potatoCarrier GunA7 - Wait 3s + wait 3s # Check if they still have the item (GunA7) in their inventory over {@potatoCarrier -> inventory} diff --git a/Code/Examples/RaveScript.cs b/Code/Examples/RaveScript.cs index 165b0a50..154b315a 100644 --- a/Code/Examples/RaveScript.cs +++ b/Code/Examples/RaveScript.cs @@ -28,17 +28,17 @@ public class RaveScript : Example # changing colors for the room repeat 20 TransitionLightColor *room #ff0000ff $duration - Wait $duration + wait $duration TransitionLightColor *room #00ff00ff $duration - Wait $duration + wait $duration TransitionLightColor *room #0000ffff $duration - Wait $duration + wait $duration end # reset color to default - Wait 1ms + wait 1ms ResetLightColor *room """; } \ No newline at end of file diff --git a/Code/Examples/ScanScript.cs b/Code/Examples/ScanScript.cs index 3fbb663c..de58091e 100644 --- a/Code/Examples/ScanScript.cs +++ b/Code/Examples/ScanScript.cs @@ -12,18 +12,18 @@ public class ScanScript : Example # scan beginning announcement Cassie jingle "a facility scan is commencing" "A facility scan is commencing..." - Wait 1s - WaitUntil ({IsCassieSpeaking} is false) + wait 1s + wait_until {IsCassieSpeaking} is false repeat 3 - Wait 10s + wait 10s # scan sound Cassie noJingle "pitch_0.3 .G1 .G4" "" end # wait until cassie finishes - Wait 10s + wait 10s # results $cassie = "scan complete . {AmountOf @scpPlayers} scpsubjects remaining . {AmountOf @classDPlayers} class d personnel remaining . {AmountOf @scientistPlayers} scientist personnel remaining . {AmountOf @foundationForcePlayers} foundation forces remaining . {AmountOf @chaosInsurgencyPlayers} hostileforces remaining . . . . ." diff --git a/Code/MethodSystem/Methods/WaitingMethods/WaitMethod.cs b/Code/MethodSystem/Methods/WaitingMethods/WaitMethod.cs deleted file mode 100644 index 3efe2daa..00000000 --- a/Code/MethodSystem/Methods/WaitingMethods/WaitMethod.cs +++ /dev/null @@ -1,24 +0,0 @@ -using JetBrains.Annotations; -using MEC; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.MethodSystem.BaseMethods.Yielding; - -namespace SER.Code.MethodSystem.Methods.WaitingMethods; - -[UsedImplicitly] -public class WaitMethod : YieldingMethod -{ - public override string Description => "Halts execution of the script for a specified amount of time."; - - public override Argument[] ExpectedArguments { get; } = - [ - new DurationArgument("duration") - ]; - - public override IEnumerator Execute() - { - var dur = Args.GetDuration("duration"); - yield return Timing.WaitForSeconds((float)dur.TotalSeconds); - } -} \ No newline at end of file diff --git a/Code/MethodSystem/Methods/WaitingMethods/WaitUntilMethod.cs b/Code/MethodSystem/Methods/WaitingMethods/WaitUntilMethod.cs deleted file mode 100644 index 77b562ad..00000000 --- a/Code/MethodSystem/Methods/WaitingMethods/WaitUntilMethod.cs +++ /dev/null @@ -1,27 +0,0 @@ -using JetBrains.Annotations; -using MEC; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.ArgumentSystem.BaseArguments; -using SER.Code.MethodSystem.BaseMethods.Yielding; - -namespace SER.Code.MethodSystem.Methods.WaitingMethods; - -[UsedImplicitly] -public class WaitUntilMethod : YieldingMethod -{ - public override string Description => "Halts execution of the script until the given condition is true."; - - public override Argument[] ExpectedArguments { get; } = - [ - new BoolArgument("condition") - { - IsFunction = true - } - ]; - - public override IEnumerator Execute() - { - var condFunc = Args.GetBoolFunc("condition"); - while (!condFunc()) yield return Timing.WaitForOneFrame; - } -} \ No newline at end of file diff --git a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs index b94c4c94..66de15bf 100644 --- a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs +++ b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs @@ -636,42 +636,34 @@ private static string GetTopProperties(IReadOnlyDictionary props; - string actualName; - + if (typeName.Equals("player", StringComparison.OrdinalIgnoreCase)) { props = new PlayerValue().Properties; - actualName = "PlayerValue"; } else if (typeName.Equals("collection", StringComparison.OrdinalIgnoreCase)) { props = new CollectionValue().Properties; - actualName = "CollectionValue"; } else if (typeName.Equals("number", StringComparison.OrdinalIgnoreCase)) { props = new NumberValue().Properties; - actualName = "NumberValue"; } else if (typeName.Equals("text", StringComparison.OrdinalIgnoreCase)) { props = new StaticTextValue().Properties; - actualName = "TextValue"; } else if (typeName.Equals("bool", StringComparison.OrdinalIgnoreCase) || typeName.Equals("boolean", StringComparison.OrdinalIgnoreCase)) { props = new BoolValue().Properties; - actualName = "BoolValue"; } else if (typeName.Equals("color", StringComparison.OrdinalIgnoreCase)) { props = new ColorValue().Properties; - actualName = "ColorValue"; } else if (typeName.Equals("duration", StringComparison.OrdinalIgnoreCase)) { props = new DurationValue().Properties; - actualName = "DurationValue"; } else { @@ -683,9 +675,8 @@ public static bool GetPropertiesForType(string typeName, out string response) foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { var name = assembly.GetName().Name; - if (name.StartsWith("UnityEngine") || name.StartsWith("Exiled") || name.StartsWith("LabApi") || - name.StartsWith("NorthwoodLib") || name.StartsWith("PluginAPI") || name.StartsWith("Mirror") || - name.StartsWith("SER")) + if (name.StartsWith("UnityEngine") || name.StartsWith("LabApi") || name.StartsWith("NorthwoodLib") + || name.StartsWith("PluginAPI") || name.StartsWith("Mirror") || name.StartsWith("SER")) { try { @@ -713,10 +704,9 @@ public static bool GetPropertiesForType(string typeName, out string response) } props = ReferencePropertyRegistry.GetProperties(type); - actualName = type.Name; } - var sb = new StringBuilder($"--- Properties for {actualName} ---\n"); + var sb = new StringBuilder($"--- Properties for {typeName} value ---\n"); var sortedProps = props.OrderBy(kvp => kvp.Key).ToList(); var normalProps = sortedProps.Where(p => !p.Value.IsUnsafe).ToList(); var unsafeProps = sortedProps.Where(p => p.Value.IsUnsafe).ToList(); diff --git a/Code/ValueSystem/Value.cs b/Code/ValueSystem/Value.cs index 0e045962..cc688263 100644 --- a/Code/ValueSystem/Value.cs +++ b/Code/ValueSystem/Value.cs @@ -6,6 +6,8 @@ using SER.Code.ScriptSystem; using SER.Code.ValueSystem.PropertySystem; using Newtonsoft.Json.Linq; +using UnityEngine; +using Utf8Json.Formatters; namespace SER.Code.ValueSystem; @@ -15,6 +17,7 @@ public static Type GuessValueType(Type t) { if (typeof(Value).IsAssignableFrom(t)) return t; if (typeof(Enum).IsAssignableFrom(t)) return typeof(EnumValue<>).MakeGenericType(t); + if (typeof(Color).IsAssignableFrom(t)) return typeof(ColorValue); if (t == typeof(bool)) return typeof(BoolValue); if (t == typeof(byte) || t == typeof(sbyte) || t == typeof(short) || t == typeof(ushort) || t == typeof(int) || t == typeof(uint) || t == typeof(long) || t == typeof(ulong) || @@ -70,6 +73,7 @@ string s IEnumerable ps => new PlayerValue(ps), JToken t => new ReferenceValue(t), IEnumerable e => new CollectionValue(e), + Color c => new ColorValue(c), _ => new ReferenceValue(obj), }; } From 665e76599e1bf7ce43bd48393bc95e6329c20712 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Tue, 31 Mar 2026 17:49:13 +0200 Subject: [PATCH 53/60] more better --- Code/ArgumentSystem/Arguments/BoolArgument.cs | 2 +- Code/ContextSystem/Contexter.cs | 70 +++- .../Control/{Loops => }/BreakKeyword.cs | 14 +- .../Contexts/Control/Loops/ForeverLoop.cs | 5 +- .../Contexts/Control/Loops/OverLoop.cs | 12 +- .../Contexts/Control/Loops/RepeatLoop.cs | 3 +- .../Contexts/Control/Loops/WhileLoop.cs | 4 +- .../Contexts/Control/OnErrorStatement.cs | 3 +- .../Contexts/Control/ReturnKeyword.cs | 45 +-- Code/ContextSystem/Contexts/FuncStatement.cs | 26 +- .../Contexts/ValueExpressionContext.cs | 23 +- Code/ContextSystem/Contexts/WithKeyword.cs | 16 +- Code/Examples/ChaosCoinScript.cs | 360 +++++++++--------- Code/Examples/DiscordServerInfoScript.cs | 6 +- Code/Examples/HotPotatoScript.cs | 4 +- Code/Extensions/EnumExtensions.cs | 3 +- Code/FlagSystem/Flags/CustomCommandFlag.cs | 2 +- .../Flags/InteractableToyEventFlag.cs | 2 +- Code/Helpers/Safe.cs | 2 + .../Synchronous/ReferenceReturningMethod.cs | 2 +- .../YieldingReferenceReturningMethod.cs | 2 +- .../DoorMethods/GetRandomDoorMethod.cs | 7 +- .../Methods/ItemMethods/AdvDropItemMethod.cs | 7 +- .../RoomMethods/GetRoomByNameMethod.cs | 7 +- .../Commands/HelpSystem/DocsProvider.cs | 20 +- .../Commands/HelpSystem/HelpInfoStorage.cs | 1 - Code/ValueSystem/BoolValue.cs | 6 +- Code/ValueSystem/CollectionValue.cs | 5 +- Code/ValueSystem/ColorValue.cs | 3 - Code/ValueSystem/DurationValue.cs | 4 +- Code/ValueSystem/EnumValue.cs | 13 +- Code/ValueSystem/LiteralValue.cs | 6 + Code/ValueSystem/NumberValue.cs | 4 +- .../ReferencePropertyRegistry.cs | 100 +++-- Code/ValueSystem/ReferenceValue.cs | 5 + Code/ValueSystem/TextValue.cs | 4 +- Code/ValueSystem/Value.cs | 7 +- 37 files changed, 431 insertions(+), 374 deletions(-) rename Code/ContextSystem/Contexts/Control/{Loops => }/BreakKeyword.cs (75%) diff --git a/Code/ArgumentSystem/Arguments/BoolArgument.cs b/Code/ArgumentSystem/Arguments/BoolArgument.cs index 1e24abe0..a4788236 100644 --- a/Code/ArgumentSystem/Arguments/BoolArgument.cs +++ b/Code/ArgumentSystem/Arguments/BoolArgument.cs @@ -10,7 +10,7 @@ namespace SER.Code.ArgumentSystem.Arguments; public class BoolArgument(string name) : Argument(name) { - public override string InputDescription => "boolean (true or false) value"; + public override string InputDescription => "bool (true or false) value"; public bool IsFunction { get; init; } = false; diff --git a/Code/ContextSystem/Contexter.cs b/Code/ContextSystem/Contexter.cs index 0cf0cde1..bb9ae481 100644 --- a/Code/ContextSystem/Contexter.cs +++ b/Code/ContextSystem/Contexter.cs @@ -1,4 +1,5 @@ using SER.Code.ContextSystem.BaseContexts; +using SER.Code.ContextSystem.Contexts; using SER.Code.ContextSystem.Contexts.Control; using SER.Code.ContextSystem.Interfaces; using SER.Code.Extensions; @@ -138,23 +139,82 @@ List contexts if (firstToken is not IContextableToken contextable) { - return $"'{firstToken.RawRep}' is not a valid way to start a line. Perhaps you made a typo?"; + return rs + $"'{firstToken.RawRep}' is not a valid way to start a line. Perhaps you made a typo?"; } var context = contextable.GetContext(scr); if (context is null) return context; - - foreach (var token in tokens.Skip(1)) + + bool endLineContexting = false; + for (var index = 1; index < tokens.Length; index++) { - if (HandleCurrentContext(token, context, out var endLineContexting).HasErrored(out var errorMsg)) - return rs + errorMsg; + var token = tokens[index]; + rs = $"Cannot add token {token} to {context}"; + if (AttemptsInlineWithKeyword(token, context)) + { + if (HandleInlineWithKeyword(tokens.Skip(index), context, scr).HasErrored(out var error)) + { + return rs + error; + } + + break; + } + + if (token is CommentToken) + { + return context; + } + if (HandleCurrentContext(token, context, out endLineContexting).HasErrored(out var errorMsg)) + return rs + errorMsg; + if (endLineContexting) break; } return context; } + private static bool AttemptsInlineWithKeyword(BaseToken token, Context currentContext) + { + return token is IContextableToken contextable + && contextable.GetContext(token.Script) is WithKeyword + && currentContext is StatementContext; + } + + private static Result HandleInlineWithKeyword(IEnumerable enumTokens, RunnableContext context, Script scr) + { + var tokens = enumTokens.ToArray(); + + if (tokens.First() is not IContextableToken contextable2 + || contextable2.GetContext(scr) is not WithKeyword + || context is not StatementContext statement) + { + return $"{context.FriendlyName} does not accept {tokens.First()}"; + } + + if (ContextLine(tokens, null, scr).HasErrored(out var contextError, out var contextResult)) + { + return contextError; + } + + if (contextResult is not WithKeyword with) + { + return $"{contextResult.FriendlyName} does not accept {tokens.First()}"; + } + + if (with.AcceptStatement(statement).HasErrored(out var acceptError)) + { + return acceptError; + } + + if (with.VerifyCurrentState().HasErrored(out var verifyError)) + { + return verifyError; + } + + return true; + } + private static Result HandleCurrentContext(BaseToken token, RunnableContext context, out bool endLineContexting) { Result rs = $"Cannot add '{token.RawRep}' to {context}"; diff --git a/Code/ContextSystem/Contexts/Control/Loops/BreakKeyword.cs b/Code/ContextSystem/Contexts/Control/BreakKeyword.cs similarity index 75% rename from Code/ContextSystem/Contexts/Control/Loops/BreakKeyword.cs rename to Code/ContextSystem/Contexts/Control/BreakKeyword.cs index 94342d51..e3ef70fc 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/BreakKeyword.cs +++ b/Code/ContextSystem/Contexts/Control/BreakKeyword.cs @@ -13,8 +13,8 @@ public class BreakKeyword : StandardContext, IKeywordContext public string KeywordName => "break"; public string Description => - "Makes a given loop (that the 'break' keyword is inside) act as it has completely ended its execution " + - "(\"breaks\" free from the loop)"; + "Makes a given loop or function (that the 'break' keyword is inside) act as it has completely ended its execution " + + "(\"breaks\" free from the loop/function)"; public string[] Arguments => []; @@ -31,6 +31,16 @@ wait 1s break end end + + func Test + if {Chance 20%} + break + end + + Print "this will not run because the 'break' keyword was used" + end + + run Test """; public override string FriendlyName => "'break' keyword"; diff --git a/Code/ContextSystem/Contexts/Control/Loops/ForeverLoop.cs b/Code/ContextSystem/Contexts/Control/Loops/ForeverLoop.cs index fd7c564e..f264b9d8 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/ForeverLoop.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/ForeverLoop.cs @@ -3,7 +3,6 @@ using SER.Code.ContextSystem.Interfaces; using SER.Code.ContextSystem.Structures; using SER.Code.Helpers.ResultSystem; -using SER.Code.MethodSystem.BaseMethods; using SER.Code.Plugin; using SER.Code.TokenSystem.Tokens; using SER.Code.ValueSystem; @@ -33,9 +32,7 @@ wait 2m # ======================================== # you can also use "with" keyword to define an iteration variable # which will hold the current iteration number, starting from 1 - forever - with $iter - + forever with $iter wait 1s Print "current iteration: {$iter}" end diff --git a/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs b/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs index 3b289986..bd78dedd 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/OverLoop.cs @@ -38,17 +38,13 @@ over @all # additionally, "over" loop can tell you which item is currently being iterated over # this is usually known as a "for each" loop in other languages # this can be done using "with" keyword and naming a temporary variable: - over @all - with @plr - + over @all with @plr Print "found player {@plr -> name}" end # this also works for collections: &inventory = @sender -> inventory - over &inventory - with *item - + over &inventory with *item Print "found item {*item -> type}" end # its important to remember that the variable type in "with" keyword @@ -58,9 +54,7 @@ with @plr # ======================================== # "with" can also define a second variable, which will hold the index of the current item # this is a number value starting at 1, and incrementing by 1 for each iteration - over @all - with @plr $index - + over @all with @plr $index Print "found player #{$index}: {@plr -> name}" end """; diff --git a/Code/ContextSystem/Contexts/Control/Loops/RepeatLoop.cs b/Code/ContextSystem/Contexts/Control/Loops/RepeatLoop.cs index ce22e1b4..6488f96c 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/RepeatLoop.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/RepeatLoop.cs @@ -33,8 +33,7 @@ repeat 10 # ======================================== # you can also define a variable which will hold the current iteration number, starting from 1 - repeat 10 - with $iter + repeat 10 with $iter Print "current iteration: {$iter}" end diff --git a/Code/ContextSystem/Contexts/Control/Loops/WhileLoop.cs b/Code/ContextSystem/Contexts/Control/Loops/WhileLoop.cs index c523111d..784a51a7 100644 --- a/Code/ContextSystem/Contexts/Control/Loops/WhileLoop.cs +++ b/Code/ContextSystem/Contexts/Control/Loops/WhileLoop.cs @@ -30,9 +30,7 @@ wait 1s # ======================================== # you may also use a "with" keyword to define an iteration variable - while {Chance 90%} - with $iter - + while {Chance 90%} with $iter Print "current attempt to leave loop: {$iter}" wait 1s end diff --git a/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs b/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs index 0c32c5a5..3d13a9cc 100644 --- a/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs +++ b/Code/ContextSystem/Contexts/Control/OnErrorStatement.cs @@ -29,9 +29,8 @@ public class OnErrorStatement : StatementContext, IStatementExtender, IKeywordCo Print "Hello, world!" # ^ won't get executed because 'attempt' skips the remaining code # inside of it if an error was made - on_error - with $message $type $stackTrace + on_error with $message $type $stackTrace # this will print the error message Print "Error: {$message}" diff --git a/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs b/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs index 1c9cf5cc..eb2b1c97 100644 --- a/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs +++ b/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs @@ -14,8 +14,7 @@ namespace SER.Code.ContextSystem.Contexts.Control; [UsedImplicitly] public class ReturnKeyword : StandardContext, IKeywordContext { - private IValueToken? _returnValueToken; - private (RunnableContext main, IMayReturnValueContext returner)? _returnContext = null; + private ValueExpressionContext? _expression = null; public string KeywordName => "return"; public string Description => "Returns value when in a function."; @@ -23,58 +22,34 @@ public class ReturnKeyword : StandardContext, IKeywordContext public string? Example => null; public override string FriendlyName => "'return' keyword"; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// public override TryAddTokenRes TryAddToken(BaseToken token) { - if (_returnContext.HasValue) - { - return _returnContext.Value.main.TryAddToken(token); - } + if (_expression is not null) return _expression.TryAddToken(token); - switch (token) + _expression = new ValueExpressionContext(token, true) { - case IContextableToken contextable when - contextable.GetContext(Script) is { } mainContext and IMayReturnValueContext returnValueContext: - { - _returnContext = (mainContext, returnValueContext); - return TryAddTokenRes.Continue(); - } - case IValueToken valToken: - { - _returnValueToken = valToken; - return TryAddTokenRes.End(); - } - default: - return TryAddTokenRes.Error($"Expected to receive a value or method, but received '{token.RawRep}' instead."); - } + Script = token.Script + }; + + return TryAddTokenRes.Continue(); } public override Result VerifyCurrentState() { return Result.Assert( - _returnValueToken != null || _returnContext.HasValue, + _expression is not null, "Return value was not provided." ); } protected override void Execute() { - Value value; - if (_returnContext.HasValue) - { - value = _returnContext.Value.returner.ReturnedValue - ?? throw new ScriptRuntimeError(this, - $"{_returnContext.Value.main} has not returned a value. " + - $"{_returnContext.Value.returner.MissingValueHint}" - ); - } - else if (_returnValueToken!.Value().HasErrored(out var error, out value!)) + if (_expression!.GetValue().HasErrored(out var error, out var value)) { throw new ScriptRuntimeError(this, error); } - + ParentContext?.SendControlMessage(new Return(value)); } } \ No newline at end of file diff --git a/Code/ContextSystem/Contexts/FuncStatement.cs b/Code/ContextSystem/Contexts/FuncStatement.cs index 0a4d8204..c6cce393 100644 --- a/Code/ContextSystem/Contexts/FuncStatement.cs +++ b/Code/ContextSystem/Contexts/FuncStatement.cs @@ -28,7 +28,31 @@ public class FuncStatement : public string KeywordName => "func"; public string Description => "Defines a function."; public string[] Arguments => ["[function name]"]; - public string? Example => null; + + public string? Example => + """ + func $Add with $a $b + return $a + $b + end + + $sum = run $Add 5 3 + Print $sum + + + func @SigmasOnly + return RemovePlayers * @classDPlayers + end + + @sigmas = run @SigmasOnly + Explode @sigmas + + + func ExplodeAll + Explode * + end + + run ExplodeAll + """; // gets the type of value associated with a token type of a variable prefix // sketchy!! diff --git a/Code/ContextSystem/Contexts/ValueExpressionContext.cs b/Code/ContextSystem/Contexts/ValueExpressionContext.cs index 9b286549..c0be0260 100644 --- a/Code/ContextSystem/Contexts/ValueExpressionContext.cs +++ b/Code/ContextSystem/Contexts/ValueExpressionContext.cs @@ -46,7 +46,7 @@ public ValueExpressionContext(BaseToken initial, bool allowsYielding) { _handler = new MethodHandler(methodToken, allowsYielding, initial.Script); } - else if (initial is KeywordToken { RawRep: "run"} ) + else if (initial is RunFunctionToken) { _handler = new FunctionCallHandler(initial.Script); } @@ -245,8 +245,25 @@ public override TryAddTokenRes TryAddToken(BaseToken token) _lastValueType = new UnknownTypeOfValue(); goto found; } - + var props = Value.GetPropertiesOfValue(type); + if (props == null && type == typeof(LiteralValue)) + { + foreach (var subType in LiteralValue.Subclasses) + { + var subProps = Value.GetPropertiesOfValue(subType); + if (subProps?.TryGetValue(token.RawRep, out var subProp) is true) + { + _exprRepr += $" {token.RawRep}"; + _lastValueType = subProp.ReturnType; + goto found; + } + } + + _exprRepr += $" {token.RawRep}"; + _lastValueType = new UnknownTypeOfValue(); + goto found; + } if (props is IValueWithProperties.IDynamicPropertyDictionary dynamicDict) { if (dynamicDict.TryGetValue(token.RawRep, out var dynamicProp)) @@ -264,7 +281,7 @@ public override TryAddTokenRes TryAddToken(BaseToken token) } } - return TryAddTokenRes.Error($"'{token.RawRep}' is not a valid property of '{_exprRepr}' value."); + return TryAddTokenRes.Error($"'{token.RawRep}' is not a valid property of {_lastValueType}."); } found: diff --git a/Code/ContextSystem/Contexts/WithKeyword.cs b/Code/ContextSystem/Contexts/WithKeyword.cs index 2c1e3736..c7cb8d54 100644 --- a/Code/ContextSystem/Contexts/WithKeyword.cs +++ b/Code/ContextSystem/Contexts/WithKeyword.cs @@ -2,6 +2,7 @@ using SER.Code.ContextSystem.BaseContexts; using SER.Code.ContextSystem.Interfaces; using SER.Code.ContextSystem.Structures; +using SER.Code.Helpers; using SER.Code.Helpers.ResultSystem; using SER.Code.TokenSystem.Tokens; using SER.Code.TokenSystem.Tokens.VariableTokens; @@ -12,7 +13,7 @@ namespace SER.Code.ContextSystem.Contexts; public class WithKeyword : StandardContext, IKeywordContext, INotRunningContext, IRequirePreviousStatementContext { private readonly List _variables = []; - private IAcceptOptionalVariableDefinitionsContext _receiver = null!; + private Safe _receiver; public string KeywordName => "with"; @@ -24,9 +25,14 @@ public class WithKeyword : StandardContext, IKeywordContext, INotRunningContext, public string Example => """ # CORRECT - over @all + over @all with @plr + Print {@plr -> name} + end + + # CORRECT - with can be on its own line + over @all with @plr - + Print {@plr -> name} end @@ -52,7 +58,7 @@ public Result AcceptStatement(StatementContext context) return $"{context} does not accept variable definitions."; } - _receiver = receiver; + _receiver = new(receiver); return true; } @@ -72,7 +78,7 @@ public override TryAddTokenRes TryAddToken(BaseToken token) public override Result VerifyCurrentState() { Result err = "The statement above does not accept provided variables."; - if (_receiver.SetOptionalVariables(_variables.ToArray()).HasErrored(out var error)) + if (_receiver.Value.SetOptionalVariables(_variables.ToArray()).HasErrored(out var error)) { return err + error; } diff --git a/Code/Examples/ChaosCoinScript.cs b/Code/Examples/ChaosCoinScript.cs index 4733b737..f6bc0d46 100644 --- a/Code/Examples/ChaosCoinScript.cs +++ b/Code/Examples/ChaosCoinScript.cs @@ -8,193 +8,193 @@ public class ChaosCoinScript : Example public override string Name => "chaosCoin"; public override string Content => - """ - !-- OnEvent FlippingCoin - - # formats of broadcasts, used to not repeat the same things in different broadcasts - $hintInfo = "" - $baseText = "the holy coin has been used
" - $afterText = "" - - # "coin locked" is a property of this player, storing whether a coin can be used. - # this is because some effects of the coin take time, and we DO NOT want to - # have the coin be used again WHILE a different effect is still ongoing - if {HasPlayerData @evPlayer "coin locked"} - if {GetPlayerData @evPlayer "coin locked"} - Hint @evPlayer 5s "{$hintInfo}You can't use the coin for now!
Come back when the current effect has finished." - IsAllowed false - stop - end - end - - # 50% chance to lose the coin - if {Chance 50%} - Hint @evPlayer 3s "{$hintInfo}Your coin has turned into dust..." - AdvDestroyItem {@evPlayer -> heldItemRef} - end - - # select a random effect of the coin, from 9 available - $effect = RandomNum 1 9 int - - # russian rulette with a full gun :trollface: - if $effect is 1 - GiveItem @evPlayer GunRevolver - Broadcast @evPlayer 5s "{$baseText}Let's play russian rulette!" - stop - end - - # HP change - if $effect is 2 - $newHP = RandomNum 1 150 int - SetHealth @evPlayer $newHP - SetMaxHealth @evPlayer $newHP - - Broadcast @evPlayer 5s "{$baseText}You now have {$newHP} HP!" - stop - end - - # rave - if $effect is 3 - *room = @evPlayer -> roomRef - - # explode player if he isnt in a room - if not {ValidRef *room} - Explode @evPlayer - stop - end - - # we are blocking the use of the coin for the player - # until the rave is over - SetPlayerData @evPlayer "coin locked" true - - Broadcast @evPlayer 5s "{$baseText}Time for a rave!" - - CloseDoor *room - LockDoor *room AdminCommand - SetLightColor *room #000000 - - repeat 5 - TransitionLightColor *room #ff0000ff .5s - wait .5s - TransitionLightColor *room #00ff00ff .5s - wait .5s - TransitionLightColor *room #0000ffff .5s - wait .5s - end - - wait .1s - UnlockDoor *room - ResetLightColor *room - +""" +!-- OnEvent FlippingCoin + +# formats of broadcasts, used to not repeat the same things in different broadcasts +$hintInfo = "" +$baseText = "the holy coin has been used
" +$afterText = "" + +# "coin locked" is a property of this player, storing whether a coin can be used. +# this is because some effects of the coin take time, and we DO NOT want to +# have the coin be used again WHILE a different effect is still ongoing +if {HasPlayerData @evPlayer "coin locked"} + if {GetPlayerData @evPlayer "coin locked"} + Hint @evPlayer 5s "{$hintInfo}You can't use the coin for now!
Come back when the current effect has finished." + IsAllowed false + stop + end +end + +# 50% chance to lose the coin +if {Chance 50%} + Hint @evPlayer 3s "{$hintInfo}Your coin has turned into dust..." + AdvDestroyItem {@evPlayer -> heldItemRef} +end + +# select a random effect of the coin, from 9 available +$effect = RandomNum 1 9 int + +# russian rulette with a full gun :trollface: +if $effect is 1 + GiveItem @evPlayer GunRevolver + Broadcast @evPlayer 5s "{$baseText}Let's play russian rulette!" + stop +end + +# HP change +if $effect is 2 + $newHP = RandomNum 1 150 int + SetHealth @evPlayer $newHP + SetMaxHealth @evPlayer $newHP + + Broadcast @evPlayer 5s "{$baseText}You now have {$newHP} HP!" + stop +end + +# rave +if $effect is 3 + *room = @evPlayer -> roomRef + + # explode player if he isnt in a room + if not {ValidRef *room} + Explode @evPlayer + stop + end + + # we are blocking the use of the coin for the player + # until the rave is over + SetPlayerData @evPlayer "coin locked" true + + Broadcast @evPlayer 5s "{$baseText}Time for a rave!" + + CloseDoor *room + LockDoor *room AdminCommand + SetLightColor *room #000000 + + repeat 5 + TransitionLightColor *room #ff0000ff .5s + wait .5s + TransitionLightColor *room #00ff00ff .5s + wait .5s + TransitionLightColor *room #0000ffff .5s + wait .5s + end + + wait .1s + UnlockDoor *room + ResetLightColor *room + + SetPlayerData @evPlayer "coin locked" false + stop +end + +# bomb +if $effect is 4 + SetPlayerData @evPlayer "coin locked" true + $initRole = @evPlayer -> role + + Countdown @evPlayer 15s "{$baseText}You have %seconds% seconds left to live!" + + # waiting 15 seconds here + repeat 15 + wait 1s + + # every second we are checking if the role of the player changed + # if so, we remove the countdown and unlock the coin + if {@evPlayer -> role} isnt $initRole + ClearCountdown @evPlayer SetPlayerData @evPlayer "coin locked" false stop end + end - # bomb - if $effect is 4 - SetPlayerData @evPlayer "coin locked" true - $initRole = @evPlayer -> role - - Countdown @evPlayer 15s "{$baseText}You have %seconds% seconds left to live!" + Explode @evPlayer + SetPlayerData @evPlayer "coin locked" false + stop +end - # waiting 15 seconds here - repeat 15 - wait 1s +# bypass +if $effect is 5 + $initRole = @evPlayer -> role + SetPlayerData @evPlayer "coin locked" true - # every second we are checking if the role of the player changed - # if so, we remove the countdown and unlock the coin - if {@evPlayer -> role} isnt $initRole - ClearCountdown @evPlayer - SetPlayerData @evPlayer "coin locked" false - stop - end - end + Countdown @evPlayer 15s "{$baseText}You can now open any keycard locked thing! (for %seconds% seconds)" + Bypass @evPlayer true - Explode @evPlayer - SetPlayerData @evPlayer "coin locked" false - stop - end + repeat 15 + wait 1s - # bypass - if $effect is 5 - $initRole = @evPlayer -> role - SetPlayerData @evPlayer "coin locked" true - - Countdown @evPlayer 15s "{$baseText}You can now open any keycard locked thing! (for %seconds% seconds)" - Bypass @evPlayer true - - repeat 15 - wait 1s - - if {@evPlayer -> role} isnt $initRole - ClearCountdown @evPlayer - break - end - end - - Bypass @evPlayer false - SetPlayerData @evPlayer "coin locked" false - stop - end - - # role downgrade - if $effect is 6 - $role = @evPlayer -> role - if $role isnt "ClassD" - SetRole @evPlayer ClassD None - elif $role isnt "Scp0492" - SetRole @evPlayer Scp0492 - else - SetRole @evPlayer Spectator - end - - Broadcast @evPlayer 5s "{$baseText}Your role got downgraded!" - stop - end - - # funny cassie - if $effect is 7 - SetPlayerData @evPlayer "coin locked" true - - Cassie noJingle "pitch_0.7 warning . pitch_3 XMAS_JINGLEBELLS" "" - Broadcast @evPlayer 5s "{$baseText}Most useful cassie message sent!" - - wait 7s - SetPlayerData @evPlayer "coin locked" false - stop - end - - # change size - if $effect is 8 - # set player size in every direction to a random number between 10% and 100% - SetSize @evPlayer {RandomNum 0.1 1 real} {RandomNum 0.1 1 real} {RandomNum 0.1 1 real} - - Broadcast @evPlayer 5s "{$baseText}Your size has changed a little!" - stop - end - - # swap places - if $effect is 9 - # cant swap places if there arent at least 2 players - if {AmountOf @alivePlayers} < 2 - Explode @evPlayer - stop - end - - # gets a random player that is not @evPlayer - @swapPlayer = LimitPlayers {RemovePlayers * @evPlayer} 1 - $swapX = @swapPlayer -> positionX - $swapY = @swapPlayer -> positionY - $swapZ = @swapPlayer -> positionZ - - # we can teleport @swapPlayer directly to @evPlayer - TPPlayer @swapPlayer @evPlayer - Broadcast @swapPlayer 5s "{$baseText}You have swapped places with {@evPlayer name}" - - # because @swapPlayer is in the same place as @evPlayer, we need to use the saved values to teleport - TPPosition @evPlayer $swapX $swapY $swapZ - Broadcast @evPlayer 5s "{$baseText}You have swapped places with {@swapPlayer name}" - stop + if {@evPlayer -> role} isnt $initRole + ClearCountdown @evPlayer + break end - """; + end + + Bypass @evPlayer false + SetPlayerData @evPlayer "coin locked" false + stop +end + +# role downgrade +if $effect is 6 + $role = @evPlayer -> role + if $role isnt "ClassD" + SetRole @evPlayer ClassD None + elif $role isnt "Scp0492" + SetRole @evPlayer Scp0492 + else + SetRole @evPlayer Spectator + end + + Broadcast @evPlayer 5s "{$baseText}Your role got downgraded!" + stop +end + +# funny cassie +if $effect is 7 + SetPlayerData @evPlayer "coin locked" true + + Cassie noJingle "pitch_0.7 warning . pitch_3 XMAS_JINGLEBELLS" "" + Broadcast @evPlayer 5s "{$baseText}Most useful cassie message sent!" + + wait 7s + SetPlayerData @evPlayer "coin locked" false + stop +end + +# change size +if $effect is 8 + # set player size in every direction to a random number between 10% and 100% + SetSize @evPlayer {RandomNum 0.1 1 real} {RandomNum 0.1 1 real} {RandomNum 0.1 1 real} + + Broadcast @evPlayer 5s "{$baseText}Your size has changed a little!" + stop +end + +# swap places +if $effect is 9 + # cant swap places if there arent at least 2 players + if {AmountOf @alivePlayers} < 2 + Explode @evPlayer + stop + end + + # gets a random player that is not @evPlayer + @swapPlayer = LimitPlayers {RemovePlayers * @evPlayer} 1 + $swapX = @swapPlayer -> positionX + $swapY = @swapPlayer -> positionY + $swapZ = @swapPlayer -> positionZ + + # we can teleport @swapPlayer directly to @evPlayer + TPPlayer @swapPlayer @evPlayer + Broadcast @swapPlayer 5s "{$baseText}You have swapped places with {@evPlayer name}" + + # because @swapPlayer is in the same place as @evPlayer, we need to use the saved values to teleport + TPPosition @evPlayer $swapX $swapY $swapZ + Broadcast @evPlayer 5s "{$baseText}You have swapped places with {@swapPlayer name}" + stop +end +"""; } \ No newline at end of file diff --git a/Code/Examples/DiscordServerInfoScript.cs b/Code/Examples/DiscordServerInfoScript.cs index 5bfb0117..858c1606 100644 --- a/Code/Examples/DiscordServerInfoScript.cs +++ b/Code/Examples/DiscordServerInfoScript.cs @@ -19,9 +19,7 @@ func SetDiscordMessage $text = "There are {AmountOf @all} players on the server" # list each player - over @all - with @plr - + over @all with @plr #
creates a new line $text = JoinText $text "
- {@plr -> name}" end @@ -34,7 +32,7 @@ PopVariable local $text PopVariable local *embed end - if {TextLength $url} is 0 + if {$url -> length} is 0 Error "Script '{This name}' cannot run, because the webhook URL was not set!" stop end diff --git a/Code/Examples/HotPotatoScript.cs b/Code/Examples/HotPotatoScript.cs index 22d9dacc..6f07e26c 100644 --- a/Code/Examples/HotPotatoScript.cs +++ b/Code/Examples/HotPotatoScript.cs @@ -28,9 +28,7 @@ GiveItem @potatoCarrier GunA7 wait 3s # Check if they still have the item (GunA7) in their inventory - over {@potatoCarrier -> inventory} - with *item - + over {@potatoCarrier -> inventory} with *item if {*item -> type} isnt "GunA7" continue end diff --git a/Code/Extensions/EnumExtensions.cs b/Code/Extensions/EnumExtensions.cs index c0c4dfeb..913dd907 100644 --- a/Code/Extensions/EnumExtensions.cs +++ b/Code/Extensions/EnumExtensions.cs @@ -1,5 +1,4 @@ -using System; -using SER.Code.ValueSystem; +using SER.Code.ValueSystem; namespace SER.Code.Extensions; diff --git a/Code/FlagSystem/Flags/CustomCommandFlag.cs b/Code/FlagSystem/Flags/CustomCommandFlag.cs index 3053bc3f..7957d8b4 100644 --- a/Code/FlagSystem/Flags/CustomCommandFlag.cs +++ b/Code/FlagSystem/Flags/CustomCommandFlag.cs @@ -376,7 +376,7 @@ public static Result RunAttachedScript(CustomCommand cmd, ScriptExecutor sender, script.AddLocalVariable(new LiteralVariable(name, new StaticTextValue(slice.Value))); } - script.AddLocalVariable(new ReferenceVariable("command", new ReferenceValue(cmd))); + script.AddLocalVariable(new ReferenceVariable("command", new ReferenceValue(cmd))); script.Run(RunReason.CustomCommand); return true; } diff --git a/Code/FlagSystem/Flags/InteractableToyEventFlag.cs b/Code/FlagSystem/Flags/InteractableToyEventFlag.cs index 88b17461..bdae5516 100644 --- a/Code/FlagSystem/Flags/InteractableToyEventFlag.cs +++ b/Code/FlagSystem/Flags/InteractableToyEventFlag.cs @@ -42,7 +42,7 @@ public static void RunBoundScripts(Player player, InteractableToy interactableTo Variable[] variables = [ new PlayerVariable("evPlayer", new PlayerValue(player)), - new ReferenceVariable("evToy", new ReferenceValue(interactableToy)) + new ReferenceVariable("evToy", new ReferenceValue(interactableToy)) ]; foreach (var scriptName in ScriptsBoundToEvent) diff --git a/Code/Helpers/Safe.cs b/Code/Helpers/Safe.cs index 1a60d6cd..192af2c1 100644 --- a/Code/Helpers/Safe.cs +++ b/Code/Helpers/Safe.cs @@ -5,6 +5,8 @@ /// public struct Safe { + public Safe(T value) => Value = value; + private readonly bool _set; public T Value diff --git a/Code/MethodSystem/BaseMethods/Synchronous/ReferenceReturningMethod.cs b/Code/MethodSystem/BaseMethods/Synchronous/ReferenceReturningMethod.cs index 000805a5..94cc3f61 100644 --- a/Code/MethodSystem/BaseMethods/Synchronous/ReferenceReturningMethod.cs +++ b/Code/MethodSystem/BaseMethods/Synchronous/ReferenceReturningMethod.cs @@ -17,6 +17,6 @@ public abstract class ReferenceReturningMethod : ReferenceReturningMethod protected new T ReturnValue { - set => base.ReturnValue = new ReferenceValue(value); + set => base.ReturnValue = new ReferenceValue(value); } } \ No newline at end of file diff --git a/Code/MethodSystem/BaseMethods/Yielding/YieldingReferenceReturningMethod.cs b/Code/MethodSystem/BaseMethods/Yielding/YieldingReferenceReturningMethod.cs index 0f5d3e09..fe4e39c7 100644 --- a/Code/MethodSystem/BaseMethods/Yielding/YieldingReferenceReturningMethod.cs +++ b/Code/MethodSystem/BaseMethods/Yielding/YieldingReferenceReturningMethod.cs @@ -17,6 +17,6 @@ public abstract class YieldingReferenceReturningMethod : YieldingReferenceRet protected new T ReturnValue { - set => base.ReturnValue = new ReferenceValue(value); + set => base.ReturnValue = new ReferenceValue(value); } } \ No newline at end of file diff --git a/Code/MethodSystem/Methods/DoorMethods/GetRandomDoorMethod.cs b/Code/MethodSystem/Methods/DoorMethods/GetRandomDoorMethod.cs index 7a5cf592..c33542b6 100644 --- a/Code/MethodSystem/Methods/DoorMethods/GetRandomDoorMethod.cs +++ b/Code/MethodSystem/Methods/DoorMethods/GetRandomDoorMethod.cs @@ -3,21 +3,18 @@ using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.Extensions; using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.ValueSystem; namespace SER.Code.MethodSystem.Methods.DoorMethods; [UsedImplicitly] -public class GetRandomDoorMethod : ReferenceReturningMethod +public class GetRandomDoorMethod : ReferenceReturningMethod { public override string Description => "Returns a reference to a random door."; - public override Type ReturnType => typeof(Door); - public override Argument[] ExpectedArguments { get; } = []; public override void Execute() { - ReturnValue = new ReferenceValue(Door.List.GetRandomValue()!); + ReturnValue = Door.List.GetRandomValue()!; } } \ No newline at end of file diff --git a/Code/MethodSystem/Methods/ItemMethods/AdvDropItemMethod.cs b/Code/MethodSystem/Methods/ItemMethods/AdvDropItemMethod.cs index e71d9211..c044c849 100644 --- a/Code/MethodSystem/Methods/ItemMethods/AdvDropItemMethod.cs +++ b/Code/MethodSystem/Methods/ItemMethods/AdvDropItemMethod.cs @@ -3,18 +3,15 @@ using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; using SER.Code.MethodSystem.BaseMethods.Synchronous; -using SER.Code.ValueSystem; namespace SER.Code.MethodSystem.Methods.ItemMethods; [UsedImplicitly] -public class AdvDropItemMethod : ReferenceReturningMethod +public class AdvDropItemMethod : ReferenceReturningMethod { public override string Description => "Drops an item from player inventory and returns a reference to the pickup object of that item."; - public override Type ReturnType => typeof(Pickup); - public override Argument[] ExpectedArguments { get; } = [ new ReferenceArgument("item") @@ -22,6 +19,6 @@ public class AdvDropItemMethod : ReferenceReturningMethod public override void Execute() { - ReturnValue = new ReferenceValue(Args.GetReference("item").DropItem()); + ReturnValue = Args.GetReference("item").DropItem(); } } \ No newline at end of file diff --git a/Code/MethodSystem/Methods/RoomMethods/GetRoomByNameMethod.cs b/Code/MethodSystem/Methods/RoomMethods/GetRoomByNameMethod.cs index d01cf1b0..8383cab1 100644 --- a/Code/MethodSystem/Methods/RoomMethods/GetRoomByNameMethod.cs +++ b/Code/MethodSystem/Methods/RoomMethods/GetRoomByNameMethod.cs @@ -7,15 +7,12 @@ using SER.Code.Extensions; using SER.Code.MethodSystem.BaseMethods.Synchronous; using SER.Code.MethodSystem.MethodDescriptors; -using SER.Code.ValueSystem; namespace SER.Code.MethodSystem.Methods.RoomMethods; [UsedImplicitly] -public class GetRoomByNameMethod : ReferenceReturningMethod, IAdditionalDescription +public class GetRoomByNameMethod : ReferenceReturningMethod, IAdditionalDescription { - public override Type ReturnType => typeof(Room); - public override string Description => "Returns a reference to a room which has the provided name."; public string AdditionalDescription => @@ -35,6 +32,6 @@ public override void Execute() throw new ScriptRuntimeError(this, $"No room found with the provided name '{roomName}'."); } - ReturnValue = new ReferenceValue(room); + ReturnValue = room; } } \ No newline at end of file diff --git a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs index 66de15bf..ec8e322b 100644 --- a/Code/Plugin/Commands/HelpSystem/DocsProvider.cs +++ b/Code/Plugin/Commands/HelpSystem/DocsProvider.cs @@ -36,7 +36,7 @@ public static class DocsProvider public static bool GetGeneralOutput(ArraySegment args, out string response) { - var arg = args.Array[args.Offset].ToLower(); + var arg = args.Array?[args.Offset].ToLower() ?? throw new Exception("argument provided in invalid format"); if (Enum.TryParse(arg, true, out HelpOption option)) { if (option == HelpOption.Properties && args.Count > 1) @@ -676,7 +676,8 @@ public static bool GetPropertiesForType(string typeName, out string response) { var name = assembly.GetName().Name; if (name.StartsWith("UnityEngine") || name.StartsWith("LabApi") || name.StartsWith("NorthwoodLib") - || name.StartsWith("PluginAPI") || name.StartsWith("Mirror") || name.StartsWith("SER")) + || name.StartsWith("PluginAPI") || name.StartsWith("Mirror") || name.StartsWith("SER") + || name.StartsWith("Assembly-CSharp")) { try { @@ -706,27 +707,28 @@ public static bool GetPropertiesForType(string typeName, out string response) props = ReferencePropertyRegistry.GetProperties(type); } - var sb = new StringBuilder($"--- Properties for {typeName} value ---\n"); + var sb = new StringBuilder($"> Properties for {typeName} value\n"); var sortedProps = props.OrderBy(kvp => kvp.Key).ToList(); var normalProps = sortedProps.Where(p => !p.Value.IsUnsafe).ToList(); var unsafeProps = sortedProps.Where(p => p.Value.IsUnsafe).ToList(); - - foreach (var (name, info) in normalProps) + + sb.AppendLine("\n--- Base properties ---"); + foreach (var (name, info) in unsafeProps) { var returnTypeFriendlyName = info.ReturnType.ToString(); sb.AppendLine($"> {name} ({returnTypeFriendlyName}){(string.IsNullOrEmpty(info.Description) ? "" : $" - {info.Description}")}"); } - if (unsafeProps.Count > 0) + if (normalProps.Count > 0) { - sb.AppendLine("\n--- Unsafe Properties (Automatic discovery) ---"); - foreach (var (name, info) in unsafeProps) + sb.AppendLine("\n--- Custom SER properties ---"); + foreach (var (name, info) in normalProps) { var returnTypeFriendlyName = info.ReturnType.ToString(); sb.AppendLine($"> {name} ({returnTypeFriendlyName}){(string.IsNullOrEmpty(info.Description) ? "" : $" - {info.Description}")}"); } } - + response = sb.ToString(); return true; } diff --git a/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs b/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs index b412e52e..5bd235f8 100644 --- a/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs +++ b/Code/Plugin/Commands/HelpSystem/HelpInfoStorage.cs @@ -2,7 +2,6 @@ using LabApi.Features.Enums; using MapGeneration; using SER.Code.FlagSystem.Flags; -using SER.Code.ValueSystem; namespace SER.Code.Plugin.Commands.HelpSystem; diff --git a/Code/ValueSystem/BoolValue.cs b/Code/ValueSystem/BoolValue.cs index 45d81bfa..dd2dd345 100644 --- a/Code/ValueSystem/BoolValue.cs +++ b/Code/ValueSystem/BoolValue.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; +using JetBrains.Annotations; using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.ValueSystem; @@ -23,7 +21,7 @@ public static implicit operator bool(BoolValue value) public override string StringRep => Value.ToString().ToLower(); [UsedImplicitly] - public new static string FriendlyName = "boolean (true/false) value"; + public new static string FriendlyName = "bool value"; private class Prop(Func handler, string? description) : IValueWithProperties.PropInfo(handler, description) where T : Value; diff --git a/Code/ValueSystem/CollectionValue.cs b/Code/ValueSystem/CollectionValue.cs index 69adffd2..0d45ea25 100644 --- a/Code/ValueSystem/CollectionValue.cs +++ b/Code/ValueSystem/CollectionValue.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; +using System.Collections; using JetBrains.Annotations; using SER.Code.Exceptions; using SER.Code.Extensions; diff --git a/Code/ValueSystem/ColorValue.cs b/Code/ValueSystem/ColorValue.cs index aaa0d572..14519c77 100644 --- a/Code/ValueSystem/ColorValue.cs +++ b/Code/ValueSystem/ColorValue.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using JetBrains.Annotations; -using SER.Code.Extensions; using SER.Code.ValueSystem.PropertySystem; using UnityEngine; diff --git a/Code/ValueSystem/DurationValue.cs b/Code/ValueSystem/DurationValue.cs index 1e89a1cf..92a26c36 100644 --- a/Code/ValueSystem/DurationValue.cs +++ b/Code/ValueSystem/DurationValue.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Text; using JetBrains.Annotations; using SER.Code.ValueSystem.PropertySystem; diff --git a/Code/ValueSystem/EnumValue.cs b/Code/ValueSystem/EnumValue.cs index 8208de80..9d1d106b 100644 --- a/Code/ValueSystem/EnumValue.cs +++ b/Code/ValueSystem/EnumValue.cs @@ -1,16 +1,19 @@ -using System; using JetBrains.Annotations; +using SER.Code.Plugin.Commands.HelpSystem; namespace SER.Code.ValueSystem; [UsedImplicitly] -public class EnumValue(T value) : StaticTextValue(value.ToString()) where T : struct, Enum +public class EnumValue : TextValue where T : struct, Enum { - public T EnumValueObject { get; } = value; - [UsedImplicitly] public EnumValue() : this(default) {} + public EnumValue(T value) : base(value.ToString(), null) + { + HelpInfoStorage.UsedEnums.Add(typeof(T)); + } + [UsedImplicitly] - public new static string FriendlyName = $"enum value of {typeof(T).Name}"; + public new static string FriendlyName = $"{typeof(T).Name} enum value"; } diff --git a/Code/ValueSystem/LiteralValue.cs b/Code/ValueSystem/LiteralValue.cs index fb672be5..fdb6527c 100644 --- a/Code/ValueSystem/LiteralValue.cs +++ b/Code/ValueSystem/LiteralValue.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using JetBrains.Annotations; using SER.Code.Exceptions; +using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.ValueSystem; @@ -8,6 +9,11 @@ public abstract class LiteralValue : Value { private readonly Func? _valueGetter; + private static Type[]? _subclasses; + public static Type[] Subclasses => _subclasses ??= typeof(LiteralValue).Assembly.GetTypes() + .Where(t => t.IsClass && t is { IsAbstract: false, IsGenericTypeDefinition: false } && typeof(LiteralValue).IsAssignableFrom(t) && typeof(IValueWithProperties).IsAssignableFrom(t)) + .ToArray(); + /// /// Initiates a new literal value. /// diff --git a/Code/ValueSystem/NumberValue.cs b/Code/ValueSystem/NumberValue.cs index 2cb769b4..306f8be4 100644 --- a/Code/ValueSystem/NumberValue.cs +++ b/Code/ValueSystem/NumberValue.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; +using JetBrains.Annotations; using SER.Code.ValueSystem.PropertySystem; namespace SER.Code.ValueSystem; diff --git a/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs b/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs index 15fd5821..bfbc7c00 100644 --- a/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs +++ b/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs @@ -1,10 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; -using Interactables.Interobjects.DoorUtils; -using LabApi.Features.Enums; +using InventorySystem.Items.Firearms; using LabApi.Features.Wrappers; -using MapGeneration; -using PlayerRoles; using PlayerStatsSystem; using Respawning; using SER.Code.Extensions; @@ -40,8 +37,7 @@ public static void Register(string name, Func handler, str return cached; var combined = new Dictionary(StringComparer.OrdinalIgnoreCase); - - // 1. Add registered properties (including from base types) + var currentType = type; while (currentType != null) { @@ -55,11 +51,10 @@ public static void Register(string name, Func handler, str } currentType = currentType.BaseType; } - - // 2. Add reflected properties with '!' prefix + foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { - var key = "!" + prop.Name.LowerFirst(); + var key = prop.Name.LowerFirst(); if (!combined.ContainsKey(key)) { combined[key] = new UnsafeReferencePropInfo(type, prop, null); @@ -68,12 +63,36 @@ public static void Register(string name, Func handler, str foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { - var key = "!" + field.Name.LowerFirst(); + var key = field.Name.LowerFirst(); if (!combined.ContainsKey(key)) { combined[key] = new UnsafeReferencePropInfo(type, field, null); } } + + combined.Add( + "isValid", + new IValueWithProperties.PropInfo( + rv => rv.IsValid, + "Whether the reference is valid" + ) + ); + + combined.Add( + "isInvalid", + new IValueWithProperties.PropInfo( + rv => !rv.IsValid, + "Whether the reference is invalid" + ) + ); + + combined.Add( + "refName", + new IValueWithProperties.PropInfo( + rv => rv.Value.GetType().AccurateName, + "Returns the name of the held object" + ) + ); CachedCombinedProperties[type] = combined; return type == typeof(JObject) || type == typeof(JToken) || type.IsSubclassOf(typeof(JToken)) @@ -134,53 +153,24 @@ public static void Initialize() { if (_isInitialized) return; _isInitialized = true; - - Register>("type", i => i.Type.ToEnumValue(), "The type of the item"); - Register>("category", i => i.Category.ToEnumValue(), "The category of the item"); - Register("owner", i => new PlayerValue(i.CurrentOwner), "The player who owns the item"); - Register("isEquipped", i => new BoolValue(i.IsEquipped), "Whether the item is currently equipped"); - - Register>("name", d => d.DoorName.ToEnumValue(), "The name of the door"); - Register("unityName", d => new StaticTextValue(d.Base.name), "The name of the door in Unity"); - Register("isOpen", d => new BoolValue(d.IsOpened), "Whether the door is open"); - Register("isClosed", d => new BoolValue(!d.IsOpened), "Whether the door is closed"); - Register("isLocked", d => new BoolValue(d.IsLocked), "Whether the door is locked"); - Register("isUnlocked", d => new BoolValue(!d.IsLocked), "Whether the door is unlocked"); + Register("remainingHealth", d => new NumberValue(d is BreakableDoor bDoor ? (decimal)bDoor.Health : -1), "The remaining health of the door"); Register("maxHealth", d => new NumberValue(d is BreakableDoor bDoor ? (decimal)bDoor.MaxHealth : -1), "The maximum health of the door"); - Register>("permissions", d => d.Permissions.ToEnumValue(), "The permissions required to open the door"); - - Register("isDestroyed", p => new BoolValue(p.IsDestroyed), "Whether the pickup is destroyed"); - Register("hasSpawned", p => new BoolValue(p.IsSpawned), "Whether the pickup has spawned"); - Register>("itemType", p => p.Type.ToEnumValue(), "The type of the pickup item"); - Register("lastOwner", p => new PlayerValue(p.LastOwner), "The player who last owned the pickup"); - Register("isInUse", p => new BoolValue(p.IsInUse), "Whether the pickup is currently in use"); - Register>("itemCategory", p => p.Category.ToEnumValue(), "The category of the pickup item"); - Register("room", p => new ReferenceValue(p.Room), "The room where the pickup is located"); + Register("positionX", p => new NumberValue((decimal)p.Position.x), "The X position of the pickup"); Register("positionY", p => new NumberValue((decimal)p.Position.y), "The Y position of the pickup"); Register("positionZ", p => new NumberValue((decimal)p.Position.z), "The Z position of the pickup"); - Register("weight", p => new NumberValue((decimal)p.Weight), "The weight of the pickup"); - - Register("shape", r => r.Shape.ToEnumValue(), "The shape of the room"); - Register>("name", r => r.Name.ToEnumValue(), "The name of the room"); - Register>("zone", r => r.Zone.ToEnumValue(), "The zone where the room is located"); - Register("xPos", r => new NumberValue((decimal)r.Position.x), "The X position of the room"); - Register("yPos", r => new NumberValue((decimal)r.Position.y), "The Y position of the room"); - Register("zPos", r => new NumberValue((decimal)r.Position.z), "The Z position of the room"); - - Register>("type", r => r.RoleTypeId.ToEnumValue(), "The role type"); - Register>("team", r => r.Team.ToEnumValue(), "The team of the role"); - Register("name", r => new StaticTextValue(r.RoleName), "The name of the role"); - - Register("damage", h => new NumberValue((decimal)((h as StandardDamageHandler)?.Damage ?? 0)), "Damage amount"); - Register("hitbox", h => (h as StandardDamageHandler)?.Hitbox.ToEnumValue() ?? (Value)new StaticTextValue("none"), "Hitbox type"); - Register("firearmUsed", h => new ReferenceValue((h as FirearmDamageHandler)?.Firearm), "Firearm used"); - Register("attacker", h => new ReferenceValue((h as AttackerDamageHandler)?.Attacker), "Attacker player"); - - Register>("faction", w => w.Faction.ToEnumValue(), "Respawn faction"); - Register("maxWaveSize", w => new NumberValue(w.MaxWaveSize), "Maximum wave size"); - Register("respawnTokens", w => new NumberValue(w.Base is Respawning.Waves.Generic.ILimitedWave limitedWave ? limitedWave.RespawnTokens : 0), "Respawn tokens"); + + Register("positionX", r => new NumberValue((decimal)r.Position.x), "The X position of the room"); + Register("positionY", r => new NumberValue((decimal)r.Position.y), "The Y position of the room"); + Register("positionZ", r => new NumberValue((decimal)r.Position.z), "The Z position of the room"); + + Register("damage", h => new NumberValue((decimal)((h as StandardDamageHandler)?.Damage ?? -1)), "Damage amount, -1 if not applicable"); + Register>("hitbox", h => (h as StandardDamageHandler)?.Hitbox.ToEnumValue() ?? new EnumValue(), "Hitbox type"); + Register>("firearmUsed", h => (h as FirearmDamageHandler)?.Firearm, "Firearm used"); + Register("attacker", h => new PlayerValue(Player.Get((h as AttackerDamageHandler)?.Attacker.PlayerId ?? -1)), "Attacker player"); + + Register("respawnTokens", w => new NumberValue(w.Base is Respawning.Waves.Generic.ILimitedWave limitedWave ? limitedWave.RespawnTokens : -1), "Respawn tokens"); Register("influence", w => new NumberValue((decimal)FactionInfluenceManager.Get(w.Faction)), "Faction influence"); Register("timeLeft", w => new DurationValue(TimeSpan.FromSeconds(w.TimeLeft)), "Time left for wave"); @@ -192,9 +182,9 @@ public static void Initialize() Register>("type", t => t.Type.ToEnumValue(), "The type of the JSON token"); Register("path", t => new StaticTextValue(t.Path), "The path of the JSON token"); - Register("root", t => new ReferenceValue(t.Root), "The root of the JSON token"); - Register("parent", t => new ReferenceValue(t.Parent), "The parent of the JSON token"); - Register("children", t => new CollectionValue(t.Children()), "The children of the JSON token"); + Register>("root", t => t.Root, "The root of the JSON token"); + Register>("parent", t => t.Parent, "The parent of the JSON token"); + Register>>("children", t => new CollectionValue>(t.Children()), "The children of the JSON token"); Register("asString", t => new StaticTextValue(t.ToString()), "The JSON representation of the token"); Register("asNumber", t => new NumberValue(t.Type is JTokenType.Integer or JTokenType.Float ? (decimal)t : 0), "The numeric value of the token"); Register("asBool", t => new BoolValue(t.Type == JTokenType.Boolean && (bool)t), "The boolean value of the token"); diff --git a/Code/ValueSystem/ReferenceValue.cs b/Code/ValueSystem/ReferenceValue.cs index be0396be..732149fa 100644 --- a/Code/ValueSystem/ReferenceValue.cs +++ b/Code/ValueSystem/ReferenceValue.cs @@ -46,6 +46,11 @@ public ReferenceValue() : this(default) {} public override Type ReferenceType => typeof(T); + public static implicit operator ReferenceValue(T? value) + { + return new(value); + } + [UsedImplicitly] public new static string FriendlyName = $"reference to {typeof(T).AccurateName} object"; } \ No newline at end of file diff --git a/Code/ValueSystem/TextValue.cs b/Code/ValueSystem/TextValue.cs index 3c592950..87bc228c 100644 --- a/Code/ValueSystem/TextValue.cs +++ b/Code/ValueSystem/TextValue.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using JetBrains.Annotations; using SER.Code.Exceptions; using SER.Code.Extensions; diff --git a/Code/ValueSystem/Value.cs b/Code/ValueSystem/Value.cs index cc688263..e3f99500 100644 --- a/Code/ValueSystem/Value.cs +++ b/Code/ValueSystem/Value.cs @@ -7,7 +7,6 @@ using SER.Code.ValueSystem.PropertySystem; using Newtonsoft.Json.Linq; using UnityEngine; -using Utf8Json.Formatters; namespace SER.Code.ValueSystem; @@ -71,10 +70,10 @@ string s TimeSpan t => new DurationValue(t), Player p => new PlayerValue(p), IEnumerable ps => new PlayerValue(ps), - JToken t => new ReferenceValue(t), - IEnumerable e => new CollectionValue(e), + JToken t => new ReferenceValue(t), + IEnumerable e => (Value)Activator.CreateInstance(GuessValueType(obj.GetType()), e), Color c => new ColorValue(c), - _ => new ReferenceValue(obj), + _ => (Value)Activator.CreateInstance(typeof(ReferenceValue<>).MakeGenericType(obj.GetType()), obj), }; } From 17e8bb3c1a680ebed74da56a02e52f7010d31c21 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Tue, 31 Mar 2026 18:15:37 +0200 Subject: [PATCH 54/60] Update DurationValue.cs --- Code/ValueSystem/DurationValue.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Code/ValueSystem/DurationValue.cs b/Code/ValueSystem/DurationValue.cs index 92a26c36..44a4fa69 100644 --- a/Code/ValueSystem/DurationValue.cs +++ b/Code/ValueSystem/DurationValue.cs @@ -64,10 +64,10 @@ private class Prop(Func handler, string? description) ["hours"] = new Prop(d => d.Value.Hours, "Hours component of the duration"), ["minutes"] = new Prop(d => d.Value.Minutes, "Minutes component of the duration"), ["seconds"] = new Prop(d => d.Value.Seconds, "Seconds component of the duration"), - ["ms"] = new Prop(d => d.Value.Milliseconds, "Milliseconds component of the duration"), + ["milliseconds"] = new Prop(d => d.Value.Milliseconds, "Milliseconds component of the duration"), ["totalHours"] = new Prop(d => (decimal)d.Value.TotalHours, "Total hours in the duration"), ["totalMinutes"] = new Prop(d => (decimal)d.Value.TotalMinutes, "Total minutes in the duration"), ["totalSeconds"] = new Prop(d => (decimal)d.Value.TotalSeconds, "Total seconds in the duration"), - ["totalMs"] = new Prop(d => (decimal)d.Value.TotalMilliseconds, "Total milliseconds in the duration") + ["totalMilliseconds"] = new Prop(d => (decimal)d.Value.TotalMilliseconds, "Total milliseconds in the duration") }; } \ No newline at end of file From c9ab8debab384f3b706b0b10e23af7652fe41129 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:02:11 +0200 Subject: [PATCH 55/60] add SetInteractableProperties method --- Code/ArgumentSystem/ProvidedArguments.cs | 5 +++ .../SetInteractablePropertiesMethod.cs | 42 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 Code/MethodSystem/Methods/AdminToyPropertyMethods/SetInteractablePropertiesMethod.cs diff --git a/Code/ArgumentSystem/ProvidedArguments.cs b/Code/ArgumentSystem/ProvidedArguments.cs index c45ff37d..562eca54 100644 --- a/Code/ArgumentSystem/ProvidedArguments.cs +++ b/Code/ArgumentSystem/ProvidedArguments.cs @@ -160,6 +160,11 @@ public Door GetDoor(string argName) return GetValue(argName); } + public TimeSpan? GetNullableDuration(string argName) + { + return GetValueNullableStruct(argName); + } + public TimeSpan GetDuration(string argName) { return GetValue(argName); diff --git a/Code/MethodSystem/Methods/AdminToyPropertyMethods/SetInteractablePropertiesMethod.cs b/Code/MethodSystem/Methods/AdminToyPropertyMethods/SetInteractablePropertiesMethod.cs new file mode 100644 index 00000000..b01a8227 --- /dev/null +++ b/Code/MethodSystem/Methods/AdminToyPropertyMethods/SetInteractablePropertiesMethod.cs @@ -0,0 +1,42 @@ +using AdminToys; +using JetBrains.Annotations; +using LabApi.Features.Wrappers; +using SER.Code.ArgumentSystem.Arguments; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.MethodSystem.BaseMethods.Synchronous; + +namespace SER.Code.MethodSystem.Methods.AdminToyPropertyMethods; + +[UsedImplicitly] +public class SetInteractablePropertiesMethod : SynchronousMethod +{ + public override string Description => "Sets properties of an Interactable Toy."; + + public override Argument[] ExpectedArguments { get; } = + [ + new ReferenceArgument("toy"), + new EnumArgument("shape") + { + DefaultValue = new(null, "not changing") + }, + new DurationArgument("interaction duration") + { + DefaultValue = new(null, "not changing") + }, + new BoolArgument("is locked") + { + DefaultValue = new(null, "not changing") + } + ]; + + public override void Execute() + { + var toy = Args.GetReference("toy"); + if (Args.GetNullableEnum("shape") is { } shape) + toy.Shape = shape; + if (Args.GetNullableDuration("interaction duration") is { } duration) + toy.InteractionDuration = (float)duration.TotalSeconds; + if (Args.GetNullableBool("is locked") is { } locked) + toy.IsLocked = locked; + } +} \ No newline at end of file From 3beb1a1fe4fd46622b74c6f896d4ea58394db923 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:02:14 +0200 Subject: [PATCH 56/60] Update TPToyPlayerMethod.cs --- .../Methods/AdminToysMethods/TPToyPlayerMethod.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Code/MethodSystem/Methods/AdminToysMethods/TPToyPlayerMethod.cs b/Code/MethodSystem/Methods/AdminToysMethods/TPToyPlayerMethod.cs index 0ca0a07f..86428d45 100644 --- a/Code/MethodSystem/Methods/AdminToysMethods/TPToyPlayerMethod.cs +++ b/Code/MethodSystem/Methods/AdminToysMethods/TPToyPlayerMethod.cs @@ -19,7 +19,10 @@ public class TPToyPlayerMethod : SynchronousMethod, IAdditionalDescription [ new ReferenceArgument("toy reference"), new PlayerArgument("player to teleport toy to"), - new BoolArgument("align toy rotation to player?"), + new BoolArgument("align toy rotation to player?") + { + DefaultValue = new(false, null) + }, ]; public override void Execute() From 2dd79c2ff9dde73115ec23b7f3704893bde91071 Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:10:36 +0200 Subject: [PATCH 57/60] Delete PlayerExpressionToken.cs --- .../ExpressionTokens/PlayerExpressionToken.cs | 247 ------------------ 1 file changed, 247 deletions(-) delete mode 100644 Code/TokenSystem/Tokens/ExpressionTokens/PlayerExpressionToken.cs diff --git a/Code/TokenSystem/Tokens/ExpressionTokens/PlayerExpressionToken.cs b/Code/TokenSystem/Tokens/ExpressionTokens/PlayerExpressionToken.cs deleted file mode 100644 index 938967bf..00000000 --- a/Code/TokenSystem/Tokens/ExpressionTokens/PlayerExpressionToken.cs +++ /dev/null @@ -1,247 +0,0 @@ -using LabApi.Features.Wrappers; -using PlayerRoles; -using PlayerRoles.FirstPersonControl; -using PlayerRoles.PlayableScps.Scp079; -using Respawning.NamingRules; -using SER.Code.ArgumentSystem.Arguments; -using SER.Code.Extensions; -using SER.Code.Helpers.ResultSystem; -using SER.Code.TokenSystem.Tokens.VariableTokens; -using SER.Code.ValueSystem; - -namespace SER.Code.TokenSystem.Tokens.ExpressionTokens; - -public class PlayerExpressionToken : ExpressionToken -{ - private PlayerProperty _property = PlayerProperty.None; - private PlayerVariableToken _pvarToken = null!; - - public override TypeOfValue PossibleValues => PropertyInfoMap[_property].ReturnType; - - public enum PlayerProperty - { - None = 0, - Name, - DisplayName, - Role, - RoleRef, - Team, - Inventory, - ItemCount, - HeldItemRef, - IsAlive, - UserId, - PlayerId, - CustomInfo, - RoomRef, - Health, - MaxHealth, - ArtificialHealth, - MaxArtificialHealth, - HumeShield, - MaxHumeShield, - HumeShieldRegenRate, - GroupName, - PositionX, - PositionY, - PositionZ, - IsDisarmed, - IsMuted, - IsIntercomMuted, - IsGlobalModerator, - IsNorthwoodStaff, - IsBypassEnabled, - IsGodModeEnabled, - IsNoclipEnabled, - Gravity, - RoleChangeReason, - RoleSpawnFlags, - AuxiliaryPower, - Emotion, - Experience, - MaxAuxiliaryPower, - SizeX, - SizeY, - SizeZ, - AccessTier, - RelativeX, - RelativeY, - RelativeZ, - IsNpc, - IsDummy, - IsSpeaking, - IsSpectatable, - IsJumping, - IsGrounded, - Stamina, - MovementState, - RoleColor, - LifeId, - UnitId, - Unit, - } - - public abstract class Info - { - public abstract Func Handler { get; } - public abstract SingleTypeOfValue ReturnType { get; } - public abstract string? Description { get; } - } - - public class Info(Func handler, string? description) : Info - where T : Value - { - public override Func Handler => handler; - public override SingleTypeOfValue ReturnType => new(typeof(T)); - public override string? Description => description; - } - - public static readonly Dictionary PropertyInfoMap = new() - { - [PlayerProperty.Name] = new Info(plr => plr.Nickname, null), - [PlayerProperty.DisplayName] = new Info(plr => plr.DisplayName, null), - [PlayerProperty.Role] = new Info(plr => plr.Role.ToString(), $"Player role type ({nameof(RoleTypeId)} enum value)"), - [PlayerProperty.RoleRef] = new Info(plr => new(plr.RoleBase), $"Reference to {nameof(PlayerRoleBase)}"), - [PlayerProperty.Team] = new Info(plr => plr.Team.ToString(), $"Player team ({nameof(Team)} enum value)"), - [PlayerProperty.Inventory] = new Info(plr => new(plr.Inventory.UserInventory.Items.Values.Select(Item.Get).RemoveNulls()), $"A collection of references to {nameof(Item)} objects"), - [PlayerProperty.ItemCount] = new Info(plr => (decimal)plr.Inventory.UserInventory.Items.Count, null), - [PlayerProperty.HeldItemRef] = new Info(plr => new(plr.CurrentItem), "A reference to the item the player is holding"), - [PlayerProperty.IsAlive] = new Info(plr => plr.IsAlive, null), - [PlayerProperty.UserId] = new Info(plr => plr.UserId, "The ID of the account (like SteamID64)"), - [PlayerProperty.PlayerId] = new Info(plr => plr.PlayerId, "The ID that the server assigned for this round"), - [PlayerProperty.CustomInfo] = new Info(plr => plr.CustomInfo, "Custom info set by the server"), - [PlayerProperty.RoomRef] = new Info(plr => new(plr.Room), "A reference to the room the player is in"), - [PlayerProperty.Health] = new Info(plr => (decimal)plr.Health, null), - [PlayerProperty.MaxHealth] = new Info(plr => (decimal)plr.MaxHealth, null), - [PlayerProperty.ArtificialHealth] = new Info(plr => (decimal)plr.ArtificialHealth, null), - [PlayerProperty.MaxArtificialHealth] = new Info(plr => (decimal)plr.MaxArtificialHealth, null), - [PlayerProperty.HumeShield] = new Info(plr => (decimal)plr.HumeShield, null), - [PlayerProperty.MaxHumeShield] = new Info(plr => (decimal)plr.MaxHumeShield, null), - [PlayerProperty.HumeShieldRegenRate] = new Info(plr => (decimal)plr.HumeShieldRegenRate, null), - [PlayerProperty.GroupName] = new Info(plr => plr.GroupName, "The name of the group (like admin or vip)"), - [PlayerProperty.PositionX] = new Info(plr => (decimal)plr.Position.x, null), - [PlayerProperty.PositionY] = new Info(plr => (decimal)plr.Position.y, null), - [PlayerProperty.PositionZ] = new Info(plr => (decimal)plr.Position.z, null), - [PlayerProperty.IsDisarmed] = new Info(plr => plr.IsDisarmed, null), - [PlayerProperty.IsMuted] = new Info(plr => plr.IsMuted, null), - [PlayerProperty.IsIntercomMuted] = new Info(plr => plr.IsIntercomMuted, null), - [PlayerProperty.IsGlobalModerator] = new Info(plr => plr.IsGlobalModerator, null), - [PlayerProperty.IsNorthwoodStaff] = new Info(plr => plr.IsNorthwoodStaff, null), - [PlayerProperty.IsBypassEnabled] = new Info(plr => plr.IsBypassEnabled, null), - [PlayerProperty.IsGodModeEnabled] = new Info(plr => plr.IsGodModeEnabled, null), - [PlayerProperty.IsNoclipEnabled] = new Info(plr => plr.IsNoclipEnabled, null), - [PlayerProperty.Gravity] = new Info(plr => -(decimal)plr.Gravity.y, null), - [PlayerProperty.RoleChangeReason] = new Info(plr => plr.RoleBase.ServerSpawnReason.ToString(), null), - [PlayerProperty.RoleSpawnFlags] = new Info(plr => plr.RoleBase.ServerSpawnFlags.ToString(), null), - [PlayerProperty.AuxiliaryPower] = new Info(plr => - { - if (plr.RoleBase is Scp079Role scp) - { - if (scp.SubroutineModule.TryGetSubroutine(out Scp079AuxManager man)) - { - return (decimal)man.CurrentAux; - } - } - - return -1; - }, "Returns player Aux power if he is SCP-079, otherwise returns -1"), - [PlayerProperty.Experience] = new Info(plr => - { - if (plr.RoleBase is Scp079Role scp) - { - if (scp.SubroutineModule.TryGetSubroutine(out Scp079TierManager tier)) - { - return tier.TotalExp; - } - } - - return -1; - }, "Returns player EXP if he is SCP-079, otherwise returns -1"), - [PlayerProperty.Emotion] = new Info(plr => plr.Emotion.ToString(), "Current emotion (e.g. Neutral, Chad)"), - [PlayerProperty.MaxAuxiliaryPower] = new Info(plr => - { - if (plr.RoleBase is Scp079Role scp) - { - if (scp.SubroutineModule.TryGetSubroutine(out Scp079AuxManager man)) - { - return (decimal)man.MaxAux; - } - } - - return -1; - }, "Returns the player's Maximum Auxiliary Power if they are SCP-079, otherwise returns -1"), - [PlayerProperty.SizeX] = new Info(plr => (decimal)plr.Scale.x, null), - [PlayerProperty.SizeY] = new Info(plr => (decimal)plr.Scale.y, null), - [PlayerProperty.SizeZ] = new Info(plr => (decimal)plr.Scale.z, null), - [PlayerProperty.AccessTier] = new Info(plr => - { - if (plr.RoleBase is Scp079Role scp) - { - if (scp.SubroutineModule.TryGetSubroutine(out Scp079TierManager tier)) - { - return tier.AccessTierLevel; - } - } - - return -1; - }, "Returns the player's Access Tier Level if they are SCP-079, otherwise returns -1"), - [PlayerProperty.RelativeX] = new Info(plr => (decimal)plr.RelativeRoomPosition().x, "Returns the player's x relative to the current room or 0 if in no room"), - [PlayerProperty.RelativeY] = new Info(plr => (decimal)plr.RelativeRoomPosition().y, "Returns the player's y relative to the current room or 0 if in no room"), - [PlayerProperty.RelativeZ] = new Info(plr => (decimal)plr.RelativeRoomPosition().z, "Returns the player's z relative to the current room or 0 if in no room"), - [PlayerProperty.IsNpc] = new Info(plr => plr.IsNpc, "True if it's a player without any client connected to it"), - [PlayerProperty.IsDummy] = new Info(plr => plr.IsDummy, null), - [PlayerProperty.IsSpeaking] = new Info(plr => plr.IsSpeaking, null), - [PlayerProperty.IsSpectatable] = new Info(plr => plr.IsSpectatable, null), - [PlayerProperty.IsJumping] = new Info(plr => plr.RoleBase is IFpcRole currentRole && currentRole.FpcModule.Motor.JumpController.IsJumping, null), - [PlayerProperty.IsGrounded] = new Info(plr => plr.ReferenceHub.IsGrounded(), null), - [PlayerProperty.Stamina] = new Info(plr => (decimal)plr.StaminaRemaining, "Returns the player's remaining stamina."), - [PlayerProperty.MovementState] = new Info(plr => plr.RoleBase is IFpcRole currentRole ? currentRole.FpcModule.CurrentMovementState.ToString().ToStaticTextValue() : new("None"), "Returns the player's movement state or 'None' if the player is not a first-person role."), - [PlayerProperty.RoleColor] = new Info(plr => plr.RoleBase.RoleColor.ToHex().ToStaticTextValue(), "Returns the hex value of the player's role color."), - [PlayerProperty.LifeId] = new Info(plr => plr.LifeId, null), - [PlayerProperty.UnitId] = new Info(plr => (decimal)plr.UnitId, null), - [PlayerProperty.Unit] = new Info(plr => NamingRulesManager.ClientFetchReceived(plr.Team, plr.UnitId).ToStaticTextValue(), "Returns the player's unit (e.g FOXTROT-03) if player is NTF or Facility Guard, otherwise returns an empty text value."), - }; - - protected override IParseResult InternalParse(BaseToken[] tokens) - { - var rs = $"{RawRep} expression is invalid".AsError(); - if (tokens.First() is not PlayerVariableToken pvarToken) - { - return new Ignore(); - } - - _pvarToken = pvarToken; - - switch (tokens.Length) - { - case < 2: - return new Error(rs + "A player expression expects to have an argument, but none was provided."); - case > 2: - return new Error(rs + $"A player expression expects to have only one argument, but {tokens.Length - 1} were provided."); - } - - if (EnumArgument.ConvertOne(tokens.Last().GetBestTextRepresentation(Script)) - .HasErrored(out var error, out var property)) - { - return new Error(rs + error); - } - - _property = property; - return new Success(); - } - - public override TryGet Value() - { - if (_pvarToken.TryGetVariable().HasErrored(out var err, out var variable)) - { - return err; - } - - return variable.Players.Len switch - { - < 1 => $"Player variable '{variable.Name}' has no players.", - > 1 => $"Player variable '{variable.Name}' has more than one player.", - _ => PropertyInfoMap[_property].Handler(variable.Players.First()) - }; - } -} From f292cea7e589910ec39ef5b9024bc8f3394862ab Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:22:30 +0200 Subject: [PATCH 58/60] make GateArgument for PryGate method --- Code/ArgumentSystem/Arguments/GateArgument.cs | 61 +++++++++++++++++++ Code/ArgumentSystem/ProvidedArguments.cs | 5 ++ .../Methods/DoorMethods/PryGateMethod.cs | 7 +-- 3 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 Code/ArgumentSystem/Arguments/GateArgument.cs diff --git a/Code/ArgumentSystem/Arguments/GateArgument.cs b/Code/ArgumentSystem/Arguments/GateArgument.cs new file mode 100644 index 00000000..48810d93 --- /dev/null +++ b/Code/ArgumentSystem/Arguments/GateArgument.cs @@ -0,0 +1,61 @@ +using JetBrains.Annotations; +using LabApi.Features.Enums; +using LabApi.Features.Wrappers; +using SER.Code.ArgumentSystem.BaseArguments; +using SER.Code.Extensions; +using SER.Code.Helpers.ResultSystem; +using SER.Code.TokenSystem.Tokens; +using SER.Code.TokenSystem.Tokens.Interfaces; +using SER.Code.ValueSystem; + +namespace SER.Code.ArgumentSystem.Arguments; + +public class GateArgument(string name) : EnumHandlingArgument(name) +{ + public override string InputDescription => $"{nameof(DoorName)} enum (that is a gate) or reference to {nameof(Gate)}"; + + [UsedImplicitly] + public DynamicTryGet GetConvertSolution(BaseToken token) + { + return ResolveEnums( + token, + new() + { + [typeof(DoorName)] = doorName => + { + var door = Gate.List.Where(gate => gate.DoorName == (DoorName)doorName).GetRandomValue(); + if (door is null) + { + return $"Gate with name '{doorName}' does not exist."; + } + + return door; + } + }, + () => + { + Result rs = $"Value '{token.RawRep}' cannot be interpreted as {InputDescription}."; + + if (token is not IValueToken val || !val.CapableOf(out var func)) + { + return rs; + } + + return new(() => + { + if (func().HasErrored(out var error, out var refVal)) + { + return error; + } + + if (ReferenceArgument.TryParse(refVal).WasSuccessful(out var gate)) + { + return gate; + } + + return rs; + }); + } + ); + } +} \ No newline at end of file diff --git a/Code/ArgumentSystem/ProvidedArguments.cs b/Code/ArgumentSystem/ProvidedArguments.cs index 562eca54..524d02ee 100644 --- a/Code/ArgumentSystem/ProvidedArguments.cs +++ b/Code/ArgumentSystem/ProvidedArguments.cs @@ -160,6 +160,11 @@ public Door GetDoor(string argName) return GetValue(argName); } + public Gate GetGate(string argName) + { + return GetValue(argName); + } + public TimeSpan? GetNullableDuration(string argName) { return GetValueNullableStruct(argName); diff --git a/Code/MethodSystem/Methods/DoorMethods/PryGateMethod.cs b/Code/MethodSystem/Methods/DoorMethods/PryGateMethod.cs index ee28f7c6..e6061bfb 100644 --- a/Code/MethodSystem/Methods/DoorMethods/PryGateMethod.cs +++ b/Code/MethodSystem/Methods/DoorMethods/PryGateMethod.cs @@ -1,5 +1,4 @@ using JetBrains.Annotations; -using LabApi.Features.Wrappers; using MapGeneration.Distributors; using SER.Code.ArgumentSystem.Arguments; using SER.Code.ArgumentSystem.BaseArguments; @@ -14,7 +13,7 @@ public class PryGateMethod : SynchronousMethod public override Argument[] ExpectedArguments => [ - new DoorArgument("gate"), + new GateArgument("gate"), new BoolArgument("should play effects") { DefaultValue = new(false, "does not play button effects"), @@ -24,11 +23,9 @@ public class PryGateMethod : SynchronousMethod public override void Execute() { - var door = Args.GetDoor("gate"); + var gate = Args.GetGate("gate"); var playEffects = Args.GetBool("should play effects"); - if (door is not Gate gate) return; - if (gate.IsOpened || gate.ExactState != 0f || gate.Base.IsBeingPried) return; if (playEffects) From ca021def7d12a27ae46c84130134789cb411cdcb Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:22:36 +0200 Subject: [PATCH 59/60] Update ReturnKeyword.cs --- Code/ContextSystem/Contexts/Control/ReturnKeyword.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs b/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs index eb2b1c97..9a8a25e4 100644 --- a/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs +++ b/Code/ContextSystem/Contexts/Control/ReturnKeyword.cs @@ -4,10 +4,7 @@ using SER.Code.ContextSystem.Structures; using SER.Code.Exceptions; using SER.Code.Helpers.ResultSystem; -using SER.Code.TokenSystem.Structures; using SER.Code.TokenSystem.Tokens; -using SER.Code.TokenSystem.Tokens.Interfaces; -using SER.Code.ValueSystem; namespace SER.Code.ContextSystem.Contexts.Control; From f09a2c526992f26ef34417e3ae9497b17be12a6f Mon Sep 17 00:00:00 2001 From: Andrzej <100864896+Elektryk-Andrzej@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:22:48 +0200 Subject: [PATCH 60/60] add more properties to Door --- Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs b/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs index bfbc7c00..17fb85a4 100644 --- a/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs +++ b/Code/ValueSystem/PropertySystem/ReferencePropertyRegistry.cs @@ -156,6 +156,10 @@ public static void Initialize() Register("remainingHealth", d => new NumberValue(d is BreakableDoor bDoor ? (decimal)bDoor.Health : -1), "The remaining health of the door"); Register("maxHealth", d => new NumberValue(d is BreakableDoor bDoor ? (decimal)bDoor.MaxHealth : -1), "The maximum health of the door"); + Register("isGate", d => d is Gate, "Is door a gate?"); + Register("isBreakable", d => d is BreakableDoor, "Is door breakable?"); + Register("isCheckpoint", d => d is CheckpointDoor, "Is door breakable?"); + Register("positionX", p => new NumberValue((decimal)p.Position.x), "The X position of the pickup"); Register("positionY", p => new NumberValue((decimal)p.Position.y), "The Y position of the pickup");