diff --git a/Content.Server/Administration/Commands/BQL/BqlParser.cs b/Content.Server/Administration/Commands/BQL/BqlParser.cs deleted file mode 100644 index a04c91f01a..0000000000 --- a/Content.Server/Administration/Commands/BQL/BqlParser.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using Content.Shared.Tag; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; - -// this is all really shit but it works and only runs once a command. -namespace Content.Server.Administration.Commands.BQL -{ - public static class BqlParser - { - private enum TokenKind - { - With, - Named, - ParentedTo, - Prototyped, - Tagged, - Select, - Do, - String, - } - - private readonly struct Token - { - public readonly TokenKind Kind; - public readonly string Text; - - private Token(TokenKind kind, string text) - { - Kind = kind; - Text = text; - } - - //I didn't want to write a proper parser. --moony - public static Tuple ExtractOneToken(string inp) - { - inp = inp.TrimStart(); - return inp switch - { - _ when inp.StartsWith("with ") => new Tuple(inp[4..], new Token(TokenKind.With, "with")), - _ when inp.StartsWith("named ") => new Tuple(inp[5..], new Token(TokenKind.Named, "named")), - _ when inp.StartsWith("parented_to ") => new Tuple(inp[11..], new Token(TokenKind.ParentedTo, "parented_to")), - _ when inp.StartsWith("prototyped ") => new Tuple(inp[10..], new Token(TokenKind.Prototyped, "prototyped")), - _ when inp.StartsWith("tagged ") => new Tuple(inp[6..], new Token(TokenKind.Tagged, "tagged")), - _ when inp.StartsWith("select ") => new Tuple(inp[6..], new Token(TokenKind.Select, "select")), - _ when inp.StartsWith("do ") => new Tuple(inp[2..], new Token(TokenKind.Do, "do")), - _ => ExtractStringToken(inp) - }; - } - - private static Tuple ExtractStringToken(string inp) - { - inp = inp.TrimStart(); - if (inp.StartsWith("\"")) - { - var acc = ""; - var skipNext = false; - foreach (var rune in inp[1..]) - { - if (skipNext) - { - acc += rune; - skipNext = false; - continue; - } - - switch (rune) - { - case '\\': - skipNext = true; - continue; - case '"': - return new Tuple(inp[(acc.Length+2)..], new Token(TokenKind.String, acc)); - default: - acc += rune; - continue; - } - } - - throw new Exception("Missing a \" somewhere."); - } - - if (inp.Contains(" ") == false) - { - return new Tuple("", new Token(TokenKind.String, inp)); - } - var word = inp[..inp.IndexOf(" ", StringComparison.Ordinal)]; - var rem = inp[inp.IndexOf(" ", StringComparison.Ordinal)..]; - return new Tuple(rem, new Token(TokenKind.String, word)); - } - } - - // Extracts and evaluates a query, then returns the rest. - public static Tuple> DoEntityQuery(string query, IEntityManager entityManager) - { - var remainingQuery = query; - var componentFactory = IoCManager.Resolve(); - var entities = entityManager.GetEntities(); - - while (true) - { - Token t; - (remainingQuery, t) = Token.ExtractOneToken(remainingQuery); - - switch (t.Kind) - { - case TokenKind.With: - { - Token nt; - (remainingQuery, nt) = Token.ExtractOneToken(remainingQuery); - var comp = componentFactory.GetRegistration(nt.Text).Type; - entities = entities.Where(e => e.HasComponent(comp)); - break; - } - case TokenKind.Named: - { - Token nt; - (remainingQuery, nt) = Token.ExtractOneToken(remainingQuery); - var r = new Regex("^" + nt.Text + "$"); - entities = entities.Where(e => r.IsMatch(e.Name)); - break; - } - case TokenKind.Tagged: - { - Token nt; - (remainingQuery, nt) = Token.ExtractOneToken(remainingQuery); - var text = nt.Text; - entities = entities.Where(e => - { - if (e.TryGetComponent(out var tagComponent)) - { - return tagComponent.Tags.Contains(text); - } - - return false; - }); - break; - } - case TokenKind.ParentedTo: - { - Token nt; - (remainingQuery, nt) = Token.ExtractOneToken(remainingQuery); - var uid = EntityUid.Parse(nt.Text); - entities = entities.Where(e => e.Transform.Parent?.Owner.Uid == uid); - break; - } - case TokenKind.Prototyped: - { - Token nt; - (remainingQuery, nt) = Token.ExtractOneToken(remainingQuery); - entities = entities.Where(e => e.Prototype?.ID == nt.Text); - break; - } - case TokenKind.Select: - { - Token nt; - (remainingQuery, nt) = Token.ExtractOneToken(remainingQuery); - entities = entities.OrderBy(a => Guid.NewGuid()); //Cheeky way of randomizing. - if (int.TryParse(nt.Text, out var x)) - { - entities = entities.Take(x); - } - else if (nt.Text.Last() == '%' && int.TryParse(nt.Text[..^1], out x)) - { - var enumerable = entities.ToArray(); - var amount = (int)Math.Floor(enumerable.Length * (x * 0.01)); - entities = enumerable.Take(amount); - } - else - { - throw new Exception("The value " + nt.Text + " is not a valid number nor a valid percentage."); - } - break; - } - case TokenKind.Do: - return new Tuple>(remainingQuery, entities); - default: - throw new Exception("Unknown token called " + t.Text + ", which was parsed as a "+ t.Kind.ToString()); - } - - if (remainingQuery.TrimStart() == "") - return new Tuple>(remainingQuery, entities); - } - } - } -} diff --git a/Content.Server/Administration/Commands/BQL/ForAllCommand.cs b/Content.Server/Administration/Commands/BQL/ForAllCommand.cs deleted file mode 100644 index db22e738c3..0000000000 --- a/Content.Server/Administration/Commands/BQL/ForAllCommand.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Linq; -using Content.Server.Commands; -using Content.Shared.Administration; -using Robust.Shared.Console; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; - -namespace Content.Server.Administration.Commands.BQL -{ - [AdminCommand(AdminFlags.Admin)] - public class ForAllCommand : IConsoleCommand - { - public string Command => "forall"; - public string Description => "Runs a command over all entities with a given component"; - public string Help => "Usage: forall "; - public void Execute(IConsoleShell shell, string argStr, string[] args) - { - if (args.Length < 2) - { - shell.WriteLine(Help); - return; - } - - var entityManager = IoCManager.Resolve(); - var (command, entities) = BqlParser.DoEntityQuery(argStr[6..], entityManager); - - foreach (var ent in entities.ToList()) - { - var cmds = CommandUtils.SubstituteEntityDetails(shell, ent, command).Split(";"); - foreach (var cmd in cmds) - { - shell.ExecuteCommand(cmd); - } - } - } - } -} diff --git a/Content.Server/Bql/QuerySelectors.cs b/Content.Server/Bql/QuerySelectors.cs new file mode 100644 index 0000000000..c173fb9b1f --- /dev/null +++ b/Content.Server/Bql/QuerySelectors.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Content.Server.Mind.Components; +using Content.Server.Power.Components; +using Content.Server.Chemistry.Components.SolutionManager; +using Content.Shared.Tag; +using Robust.Server.Bql; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; + +namespace Content.Server.Bql +{ + public class QuerySelectors + { + [RegisterBqlQuerySelector] + public class MindfulQuerySelector : BqlQuerySelector + { + public override string Token => "mindful"; + + public override QuerySelectorArgument[] Arguments => Array.Empty(); + + public override IEnumerable DoSelection(IEnumerable input, + IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) + { + return input.Where(e => + { + if (entityManager.TryGetComponent(e, out var mind)) + return (mind.Mind?.VisitingEntity?.Uid == e) ^ isInverted; + + return isInverted; + }); + } + + public override IEnumerable DoInitialSelection(IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) + { + + return DoSelection( + entityManager.EntityQuery().Select(x => x.Owner.Uid), + arguments, isInverted, entityManager); + } + } + + [RegisterBqlQuerySelector] + public class TaggedQuerySelector : BqlQuerySelector + { + public override string Token => "tagged"; + + public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.String }; + + public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) + { + return input.Where(e => + (entityManager.TryGetComponent(e, out var tag) && + tag.Tags.Contains((string) arguments[0])) ^ isInverted); + } + + public override IEnumerable DoInitialSelection(IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) + { + return DoSelection(entityManager.EntityQuery().Select(x => x.Owner.Uid), arguments, + isInverted, entityManager); + + } + } + + [RegisterBqlQuerySelector] + public class AliveQuerySelector : BqlQuerySelector + { + public override string Token => "alive"; + + public override QuerySelectorArgument[] Arguments => Array.Empty(); + + public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) + { + return input.Where(e => + (entityManager.TryGetComponent(e, out var mind) && + !(mind.Mind?.CharacterDeadPhysically ?? false)) ^ isInverted); + } + + public override IEnumerable DoInitialSelection(IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) + { + return DoSelection(entityManager.EntityQuery().Select(x => x.Owner.Uid), arguments, + isInverted, entityManager); + } + } + + [RegisterBqlQuerySelector] + public class HasReagentQuerySelector : BqlQuerySelector + { + public override string Token => "hasreagent"; + + public override QuerySelectorArgument[] Arguments => new [] { QuerySelectorArgument.String }; + + public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) + { + var reagent = (string) arguments[0]; + return input.Where(e => + { + if (entityManager.TryGetComponent(e, out var solutionContainerManagerComponent)) + { + return solutionContainerManagerComponent.Solutions + .Any(solution => solution.Value.ContainsReagent(reagent)) ^ isInverted; + } + + return isInverted; + }); + } + + public override IEnumerable DoInitialSelection(IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) + { + return DoSelection(entityManager.EntityQuery().Select(x => x.Owner.Uid), arguments, + isInverted, entityManager); + } + } + + [RegisterBqlQuerySelector] + public class ApcPoweredQuerySelector : BqlQuerySelector + { + public override string Token => "apcpowered"; + + public override QuerySelectorArgument[] Arguments => Array.Empty(); + + public override IEnumerable DoSelection(IEnumerable input, IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) + { + return input.Where(e => + entityManager.TryGetComponent(e, out var apcPowerReceiver) + ? apcPowerReceiver.Powered ^ isInverted + : isInverted); + } + + public override IEnumerable DoInitialSelection(IReadOnlyList arguments, bool isInverted, IEntityManager entityManager) + { + return DoSelection(entityManager.EntityQuery().Select(x => x.Owner.Uid), arguments, + isInverted, entityManager); + } + } + } +} diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index 766a3b8f7f..116a55018a 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -18,6 +18,7 @@ using Content.Server.Voting.Managers; using Content.Shared.Actions; using Content.Shared.Alert; using Content.Shared.Kitchen; +using Robust.Server.Bql; using Robust.Server.Player; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; @@ -91,6 +92,7 @@ namespace Content.Server.Entry _euiManager.Initialize(); IoCManager.Resolve().GetEntitySystem().PostInitialize(); + IoCManager.Resolve().DoAutoRegistrations(); } public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs) diff --git a/Resources/Textures/Tiles/tile.png b/Resources/Textures/Tiles/tile.png new file mode 100644 index 0000000000..fed721a4a9 Binary files /dev/null and b/Resources/Textures/Tiles/tile.png differ diff --git a/Resources/engineCommandPerms.yml b/Resources/engineCommandPerms.yml index 231352cd1e..d3cee48deb 100644 --- a/Resources/engineCommandPerms.yml +++ b/Resources/engineCommandPerms.yml @@ -49,6 +49,7 @@ - tp - tpto - respawn + - forall - Flags: SERVER Commands: