oh god it's BQL time (#5050)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
This commit is contained in:
@@ -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<string, Token> ExtractOneToken(string inp)
|
|
||||||
{
|
|
||||||
inp = inp.TrimStart();
|
|
||||||
return inp switch
|
|
||||||
{
|
|
||||||
_ when inp.StartsWith("with ") => new Tuple<string, Token>(inp[4..], new Token(TokenKind.With, "with")),
|
|
||||||
_ when inp.StartsWith("named ") => new Tuple<string, Token>(inp[5..], new Token(TokenKind.Named, "named")),
|
|
||||||
_ when inp.StartsWith("parented_to ") => new Tuple<string, Token>(inp[11..], new Token(TokenKind.ParentedTo, "parented_to")),
|
|
||||||
_ when inp.StartsWith("prototyped ") => new Tuple<string, Token>(inp[10..], new Token(TokenKind.Prototyped, "prototyped")),
|
|
||||||
_ when inp.StartsWith("tagged ") => new Tuple<string, Token>(inp[6..], new Token(TokenKind.Tagged, "tagged")),
|
|
||||||
_ when inp.StartsWith("select ") => new Tuple<string, Token>(inp[6..], new Token(TokenKind.Select, "select")),
|
|
||||||
_ when inp.StartsWith("do ") => new Tuple<string, Token>(inp[2..], new Token(TokenKind.Do, "do")),
|
|
||||||
_ => ExtractStringToken(inp)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Tuple<string, Token> 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<string, Token>(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<string, Token>("", new Token(TokenKind.String, inp));
|
|
||||||
}
|
|
||||||
var word = inp[..inp.IndexOf(" ", StringComparison.Ordinal)];
|
|
||||||
var rem = inp[inp.IndexOf(" ", StringComparison.Ordinal)..];
|
|
||||||
return new Tuple<string, Token>(rem, new Token(TokenKind.String, word));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extracts and evaluates a query, then returns the rest.
|
|
||||||
public static Tuple<string, IEnumerable<IEntity>> DoEntityQuery(string query, IEntityManager entityManager)
|
|
||||||
{
|
|
||||||
var remainingQuery = query;
|
|
||||||
var componentFactory = IoCManager.Resolve<IComponentFactory>();
|
|
||||||
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<TagComponent>(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<string, IEnumerable<IEntity>>(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<string, IEnumerable<IEntity>>(remainingQuery, entities);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 <comp> <command...>";
|
|
||||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
||||||
{
|
|
||||||
if (args.Length < 2)
|
|
||||||
{
|
|
||||||
shell.WriteLine(Help);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
138
Content.Server/Bql/QuerySelectors.cs
Normal file
138
Content.Server/Bql/QuerySelectors.cs
Normal file
@@ -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<QuerySelectorArgument>();
|
||||||
|
|
||||||
|
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input,
|
||||||
|
IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
return input.Where(e =>
|
||||||
|
{
|
||||||
|
if (entityManager.TryGetComponent<MindComponent>(e, out var mind))
|
||||||
|
return (mind.Mind?.VisitingEntity?.Uid == e) ^ isInverted;
|
||||||
|
|
||||||
|
return isInverted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
|
||||||
|
return DoSelection(
|
||||||
|
entityManager.EntityQuery<MindComponent>().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<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
return input.Where(e =>
|
||||||
|
(entityManager.TryGetComponent<TagComponent>(e, out var tag) &&
|
||||||
|
tag.Tags.Contains((string) arguments[0])) ^ isInverted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
return DoSelection(entityManager.EntityQuery<TagComponent>().Select(x => x.Owner.Uid), arguments,
|
||||||
|
isInverted, entityManager);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RegisterBqlQuerySelector]
|
||||||
|
public class AliveQuerySelector : BqlQuerySelector
|
||||||
|
{
|
||||||
|
public override string Token => "alive";
|
||||||
|
|
||||||
|
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||||
|
|
||||||
|
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
return input.Where(e =>
|
||||||
|
(entityManager.TryGetComponent<MindComponent>(e, out var mind) &&
|
||||||
|
!(mind.Mind?.CharacterDeadPhysically ?? false)) ^ isInverted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
return DoSelection(entityManager.EntityQuery<MindComponent>().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<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
var reagent = (string) arguments[0];
|
||||||
|
return input.Where(e =>
|
||||||
|
{
|
||||||
|
if (entityManager.TryGetComponent<SolutionContainerManagerComponent>(e, out var solutionContainerManagerComponent))
|
||||||
|
{
|
||||||
|
return solutionContainerManagerComponent.Solutions
|
||||||
|
.Any(solution => solution.Value.ContainsReagent(reagent)) ^ isInverted;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isInverted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
return DoSelection(entityManager.EntityQuery<SolutionContainerManagerComponent>().Select(x => x.Owner.Uid), arguments,
|
||||||
|
isInverted, entityManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RegisterBqlQuerySelector]
|
||||||
|
public class ApcPoweredQuerySelector : BqlQuerySelector
|
||||||
|
{
|
||||||
|
public override string Token => "apcpowered";
|
||||||
|
|
||||||
|
public override QuerySelectorArgument[] Arguments => Array.Empty<QuerySelectorArgument>();
|
||||||
|
|
||||||
|
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
return input.Where(e =>
|
||||||
|
entityManager.TryGetComponent<ApcPowerReceiverComponent>(e, out var apcPowerReceiver)
|
||||||
|
? apcPowerReceiver.Powered ^ isInverted
|
||||||
|
: isInverted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<EntityUid> DoInitialSelection(IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
return DoSelection(entityManager.EntityQuery<ApcPowerReceiverComponent>().Select(x => x.Owner.Uid), arguments,
|
||||||
|
isInverted, entityManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ using Content.Server.Voting.Managers;
|
|||||||
using Content.Shared.Actions;
|
using Content.Shared.Actions;
|
||||||
using Content.Shared.Alert;
|
using Content.Shared.Alert;
|
||||||
using Content.Shared.Kitchen;
|
using Content.Shared.Kitchen;
|
||||||
|
using Robust.Server.Bql;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.ContentPack;
|
using Robust.Shared.ContentPack;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
@@ -91,6 +92,7 @@ namespace Content.Server.Entry
|
|||||||
_euiManager.Initialize();
|
_euiManager.Initialize();
|
||||||
|
|
||||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
|
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
|
||||||
|
IoCManager.Resolve<IBqlQueryManager>().DoAutoRegistrations();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs)
|
public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs)
|
||||||
|
|||||||
BIN
Resources/Textures/Tiles/tile.png
Normal file
BIN
Resources/Textures/Tiles/tile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 605 B |
@@ -49,6 +49,7 @@
|
|||||||
- tp
|
- tp
|
||||||
- tpto
|
- tpto
|
||||||
- respawn
|
- respawn
|
||||||
|
- forall
|
||||||
|
|
||||||
- Flags: SERVER
|
- Flags: SERVER
|
||||||
Commands:
|
Commands:
|
||||||
|
|||||||
Reference in New Issue
Block a user