Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start work on set src and atom-as-a-first-arg in verbs #1668

Merged
merged 6 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DMCompiler/Compiler/DM/DMAST.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1769,10 +1769,10 @@ public override void Visit(DMASTVisitor visitor) {
}

public sealed class DMASTNewPath : DMASTExpression {
public readonly DMASTPath Path;
public readonly DMASTConstantPath Path;
public readonly DMASTCallParameter[] Parameters;

public DMASTNewPath(Location location, DMASTPath path, DMASTCallParameter[] parameters) : base(location) {
public DMASTNewPath(Location location, DMASTConstantPath path, DMASTCallParameter[] parameters) : base(location) {
Path = path;
Parameters = parameters;
}
Expand Down
2 changes: 1 addition & 1 deletion DMCompiler/Compiler/DM/DMParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2082,7 +2082,7 @@ public void ExpressionTo(out DMASTExpression? endRange, out DMASTExpression? ste
DMASTCallParameter[]? parameters = ProcCall();

DMASTExpression? newExpression = type switch {
DMASTConstantPath path => new DMASTNewPath(loc, path.Value, parameters),
DMASTConstantPath path => new DMASTNewPath(loc, path, parameters),
DMASTExpression expr => new DMASTNewExpr(loc, expr, parameters),
null => new DMASTNewInferred(loc, parameters),
};
Expand Down
2 changes: 2 additions & 0 deletions DMCompiler/DM/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public DMProcScope(DMProcScope? parentScope) {
public int Id;
public Dictionary<string, int> GlobalVariables = new();

public VerbSrc? VerbSrc;
public string? VerbName;
public string? VerbCategory = string.Empty;
public string? VerbDesc;
Expand Down Expand Up @@ -140,6 +141,7 @@ public ProcDefinitionJson GetJsonRepresentation() {
procDefinition.Attributes = Attributes;
}

procDefinition.VerbSrc = VerbSrc;
procDefinition.VerbName = VerbName;
// Normally VerbCategory is "" by default and null to hide it, but we invert those during (de)serialization to reduce JSON size
VerbCategory = VerbCategory switch {
Expand Down
34 changes: 19 additions & 15 deletions DMCompiler/DM/Expressions/Builtins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,29 +63,33 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) {
}

// new /x/y/z (...)
sealed class NewPath : DMExpression {
private readonly DreamPath _targetPath;
private readonly ArgumentList _arguments;

public NewPath(Location location, DreamPath targetPath, ArgumentList arguments) : base(location) {
_targetPath = targetPath;
_arguments = arguments;
}
internal sealed class NewPath(Location location, Path targetPath, ArgumentList arguments) : DMExpression(location) {
public override DreamPath? Path => targetPath.Value;

public override void EmitPushValue(DMObject dmObject, DMProc proc) {
if (!DMObjectTree.TryGetTypeId(_targetPath, out var typeId)) {
DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {_targetPath} does not exist");

if (!targetPath.TryResolvePath(out var pathInfo)) {
proc.PushNull();
return;
}

var argumentInfo = _arguments.EmitArguments(dmObject, proc);
var argumentInfo = arguments.EmitArguments(dmObject, proc);

switch (pathInfo.Value.Type) {
case Expressions.Path.PathType.TypeReference:
proc.PushType(pathInfo.Value.Id);
break;
case Expressions.Path.PathType.ProcReference: // "new /proc/new_verb(Destination)" is a thing
proc.PushProc(pathInfo.Value.Id);
break;
case Expressions.Path.PathType.ProcStub:
case Expressions.Path.PathType.VerbStub:
DMCompiler.Emit(WarningCode.BadExpression, Location, "Cannot use \"new\" with a proc stub");
proc.PushNull();
return;
}

proc.PushType(typeId);
proc.CreateObject(argumentInfo.Type, argumentInfo.StackSize);
}

public override DreamPath? Path => _targetPath;
}

// locate()
Expand Down
15 changes: 5 additions & 10 deletions DMCompiler/DM/Expressions/Constant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -477,26 +477,21 @@ public override bool TryAsJsonRepresentation(out object? json) {
}

// /a/b/c
sealed class Path : Constant {
public DreamPath Value { get; }
internal sealed class Path(Location location, DMObject dmObject, DreamPath value) : Constant(location) {
public DreamPath Value { get; } = value;

/// <summary>
/// The DMObject this expression resides in. Used for path searches.
/// </summary>
private readonly DMObject _dmObject;
private readonly DMObject _dmObject = dmObject;

private enum PathType {
public enum PathType {
TypeReference,
ProcReference,
ProcStub,
VerbStub
}

public Path(Location location, DMObject dmObject, DreamPath value) : base(location) {
Value = value;
_dmObject = dmObject;
}

public override void EmitPushValue(DMObject dmObject, DMProc proc) {
if (!TryResolvePath(out var pathInfo)) {
proc.PushNull();
Expand Down Expand Up @@ -554,7 +549,7 @@ public override bool TryAsJsonRepresentation(out object? json) {
return true;
}

private bool TryResolvePath([NotNullWhen(true)] out (PathType Type, int Id)? pathInfo) {
public bool TryResolvePath([NotNullWhen(true)] out (PathType Type, int Id)? pathInfo) {
DreamPath path = Value;

// An upward search with no left-hand side
Expand Down
21 changes: 21 additions & 0 deletions DMCompiler/DM/VerbSrc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace DMCompiler.DM;

/// <summary>
/// The value of "set src = ..." in a verb
/// </summary>
public enum VerbSrc {
View,
InView,
OView,
InOView,
Range,
InRange,
ORange,
InORange,
World,
InWorld,
Usr,
InUsr,
UsrLoc,
UsrGroup
}
18 changes: 12 additions & 6 deletions DMCompiler/DM/Visitors/DMExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,18 +193,24 @@ public static DMExpression BuildExpression(DMASTExpression expression, DMObject
BuildExpression(ternary.B, dmObject, proc, inferredPath),
BuildExpression(ternary.C ?? new DMASTConstantNull(ternary.Location), dmObject, proc, inferredPath));
case DMASTNewPath newPath:
return new NewPath(newPath.Location,
newPath.Path.Path,
if (BuildExpression(newPath.Path, dmObject, proc, inferredPath) is not Path path) {
DMCompiler.Emit(WarningCode.BadExpression, newPath.Path.Location, "Expected a path expression");
return new Null(newPath.Location);
}

return new NewPath(newPath.Location, path,
new ArgumentList(newPath.Location, dmObject, proc, newPath.Parameters, inferredPath));
case DMASTNewExpr newExpr:
return new New(newExpr.Location,
BuildExpression(newExpr.Expression, dmObject, proc, inferredPath),
new ArgumentList(newExpr.Location, dmObject, proc, newExpr.Parameters, inferredPath));
case DMASTNewInferred newInferred:
if (inferredPath is null)
throw new CompileErrorException(newInferred.Location, "An inferred new requires a type!");
return new NewPath(newInferred.Location,
inferredPath.Value,
if (inferredPath is null) {
DMCompiler.Emit(WarningCode.BadExpression, newInferred.Location, "Could not infer a type");
return new Null(newInferred.Location);
}

return new NewPath(newInferred.Location, new Path(newInferred.Location, dmObject, inferredPath.Value),
new ArgumentList(newInferred.Location, dmObject, proc, newInferred.Parameters, inferredPath));
case DMASTPreIncrement preIncrement:
return new PreIncrement(preIncrement.Location, BuildExpression(preIncrement.Expression, dmObject, proc, inferredPath));
Expand Down
68 changes: 66 additions & 2 deletions DMCompiler/DM/Visitors/DMProcBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,73 @@ public void ProcessStatementBreak(DMASTProcStatementBreak statementBreak) {
public void ProcessStatementSet(DMASTProcStatementSet statementSet) {
var attribute = statementSet.Attribute.ToLower();

// TODO deal with "src"
if(attribute == "src") {
DMCompiler.UnimplementedWarning(statementSet.Location, "'set src' is unimplemented");
// TODO: Would be much better if the parser was just more strict with the expression
switch (statementSet.Value) {
case DMASTIdentifier {Identifier: "usr"}:
_proc.VerbSrc = statementSet.WasInKeyword ? VerbSrc.InUsr : VerbSrc.Usr;
if (statementSet.WasInKeyword)
DMCompiler.UnimplementedWarning(statementSet.Location,
"'set src = usr.contents' is unimplemented");
break;
case DMASTDereference {Expression: DMASTIdentifier{Identifier: "usr"}, Operations: var operations}:
if (operations is not [DMASTDereference.FieldOperation {Identifier: var deref}])
goto default;

if (deref == "contents") {
_proc.VerbSrc = VerbSrc.InUsr;
DMCompiler.UnimplementedWarning(statementSet.Location,
"'set src = usr.contents' is unimplemented");
} else if (deref == "loc") {
_proc.VerbSrc = VerbSrc.UsrLoc;
DMCompiler.UnimplementedWarning(statementSet.Location,
"'set src = usr.loc' is unimplemented");
} else if (deref == "group") {
_proc.VerbSrc = VerbSrc.UsrGroup;
DMCompiler.UnimplementedWarning(statementSet.Location,
"'set src = usr.group' is unimplemented");
} else {
goto default;
}

break;
case DMASTIdentifier {Identifier: "world"}:
_proc.VerbSrc = statementSet.WasInKeyword ? VerbSrc.InWorld : VerbSrc.World;
if (statementSet.WasInKeyword)
DMCompiler.UnimplementedWarning(statementSet.Location,
"'set src = world.contents' is unimplemented");
else
DMCompiler.UnimplementedWarning(statementSet.Location,
"'set src = world' is unimplemented");
break;
case DMASTDereference {Expression: DMASTIdentifier{Identifier: "world"}, Operations: var operations}:
if (operations is not [DMASTDereference.FieldOperation {Identifier: "contents"}])
goto default;

_proc.VerbSrc = VerbSrc.InWorld;
DMCompiler.UnimplementedWarning(statementSet.Location,
"'set src = world.contents' is unimplemented");
break;
case DMASTProcCall {Callable: DMASTCallableProcIdentifier {Identifier: { } viewType and ("view" or "oview")}}:
// TODO: Ranges
if (statementSet.WasInKeyword)
_proc.VerbSrc = viewType == "view" ? VerbSrc.InView : VerbSrc.InOView;
else
_proc.VerbSrc = viewType == "view" ? VerbSrc.View : VerbSrc.OView;
break;
// range() and orange() are undocumented, but they work
case DMASTProcCall {Callable: DMASTCallableProcIdentifier {Identifier: { } viewType and ("range" or "orange")}}:
// TODO: Ranges
if (statementSet.WasInKeyword)
_proc.VerbSrc = viewType == "range" ? VerbSrc.InRange : VerbSrc.InORange;
else
_proc.VerbSrc = viewType == "range" ? VerbSrc.Range : VerbSrc.ORange;
break;
default:
DMCompiler.Emit(WarningCode.BadExpression, statementSet.Value.Location, "Invalid verb src");
break;
}

return;
}

Expand Down
3 changes: 2 additions & 1 deletion DMCompiler/Json/DreamProcJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,22 @@
public List<SourceInfoJson> SourceInfo { get; set; }
public byte[]? Bytecode { get; set; }

public VerbSrc? VerbSrc { get; set; }
public string? VerbName { get; set; }
public string? VerbCategory { get; set; } = null;
public string? VerbCategory { get; set; }
public string? VerbDesc { get; set; }
public sbyte Invisibility { get; set; }
}

public sealed class ProcArgumentJson {
public string Name { get; set; }

Check warning on line 25 in DMCompiler/Json/DreamProcJson.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public DMValueType Type { get; set; }
}

public sealed class LocalVariableJson {
public int Offset { get; set; }
public int? Remove { get; set; }
public string Add { get; set; }

Check warning on line 32 in DMCompiler/Json/DreamProcJson.cs

View workflow job for this annotation

GitHub Actions / build (windows-latest)

Non-nullable property 'Add' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}

public sealed class SourceInfoJson {
Expand Down
1 change: 1 addition & 0 deletions OpenDream.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=sendmaps/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=splicetext/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=splittext/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=src_0027s/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=statpanel/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=timeofday/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=typesof/@EntryIndexedValue">True</s:Boolean>
Expand Down
85 changes: 70 additions & 15 deletions OpenDreamClient/ClientVerbSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using OpenDreamClient.Rendering;
using OpenDreamShared.Dream;
using OpenDreamShared.Rendering;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Asynchronous;
using Robust.Shared.Timing;
Expand All @@ -15,6 +16,7 @@ public sealed class ClientVerbSystem : VerbSystem {
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;

private EntityQuery<DMISpriteComponent> _spriteQuery;
private EntityQuery<DreamMobSightComponent> _sightQuery;
Expand Down Expand Up @@ -68,12 +70,12 @@ public IEnumerable<VerbInfo> GetAllVerbs() {
/// Find all the verbs the client is currently capable of executing
/// </summary>
/// <param name="ignoreHiddenAttr">Whether to ignore "set hidden = TRUE"</param>
/// <returns>The ID, target, and information of every executable verb</returns>
/// <returns>The ID, src, and information of every executable verb</returns>
public IEnumerable<(int Id, ClientObjectReference Src, VerbInfo VerbInfo)> GetExecutableVerbs(bool ignoreHiddenAttr = false) {
DMISpriteComponent? playerSprite = null;
ClientObjectReference? ourMob = null;
sbyte? seeInvisibility = null;
if (_playerManager.LocalEntity != null) {
playerSprite = _spriteQuery.GetComponent(_playerManager.LocalEntity.Value);
ourMob = new ClientObjectReference(_entityManager.GetNetEntity(_playerManager.LocalEntity.Value));
seeInvisibility = _sightQuery.GetComponent(_playerManager.LocalEntity.Value).SeeInvisibility;
}

Expand All @@ -89,25 +91,78 @@ public IEnumerable<VerbInfo> GetAllVerbs() {
}
}

// Then, the verbs attached to our mob
if (playerSprite?.Icon.Appearance is { } playerAppearance) {
var playerNetEntity = _entityManager.GetNetEntity(_playerManager.LocalEntity);
// Then, the verbs on objects around us
var viewOverlay = _overlayManager.GetOverlay<DreamViewOverlay>();
foreach (var entity in viewOverlay.EntitiesInView) {
if (!_spriteQuery.TryGetComponent(entity, out var sprite))
continue;
if (sprite.Icon.Appearance is not { } appearance)
continue;

if (playerNetEntity != null) {
foreach (var verbId in playerAppearance.Verbs) {
if (!_verbs.TryGetValue(verbId, out var verb))
continue;
if (verb.IsHidden(ignoreHiddenAttr, seeInvisibility!.Value))
continue;
foreach (var verbId in appearance.Verbs) {
if (!_verbs.TryGetValue(verbId, out var verb))
continue;
if (verb.IsHidden(ignoreHiddenAttr, seeInvisibility!.Value))
continue;

var src = new ClientObjectReference(_entityManager.GetNetEntity(entity));

yield return (verbId, new(playerNetEntity.Value), verb);
// Check the verb's "set src" allows us to execute this
switch (verb.Accessibility) {
case VerbAccessibility.Usr:
if (!src.Equals(ourMob))
continue;

break;
default:
// TODO: All the other kinds
break;
}

yield return (verbId, src, verb);
}
}

// TODO: Turfs, Areas
}

public bool TryGetVerbInfo(int verbId, out VerbInfo verbInfo) {
return _verbs.TryGetValue(verbId, out verbInfo);
/// <summary>
/// Find all the verbs the client is currently capable of executing on the given target
/// </summary>
/// <param name="target">The target of the verb</param>
/// <returns>The ID, src, and information of every executable verb</returns>
public IEnumerable<(int Id, ClientObjectReference Src, VerbInfo VerbInfo)> GetExecutableVerbs(ClientObjectReference target) {
foreach (var verb in GetExecutableVerbs()) {
DreamValueType? targetType = verb.VerbInfo.GetTargetType();
if (targetType == null) {
// Verbs without a target but an "in view()/range()" accessibility will still show
if (verb.Src.Equals(target) &&
verb.VerbInfo.Accessibility
is VerbAccessibility.InRange or VerbAccessibility.InORange
or VerbAccessibility.InView or VerbAccessibility.InOView)
yield return verb;

continue;
}

switch (target.Type) {
case ClientObjectReference.RefType.Entity:
var entity = _entityManager.GetEntity(target.Entity);
var isMob = _entityManager.HasComponent<DreamMobSightComponent>(entity);

if ((targetType & DreamValueType.Mob) != 0x0 && isMob)
yield return verb;
if ((targetType & DreamValueType.Obj) != 0x0 && !isMob)
yield return verb;

break;
case ClientObjectReference.RefType.Turf:
if ((targetType & DreamValueType.Turf) != 0x0)
yield return verb;

break;
}
}
}

/// <summary>
Expand Down
Loading
Loading