Skip to content

Commit

Permalink
Start work on set src and atom-as-a-first-arg in verbs (#1668)
Browse files Browse the repository at this point in the history
  • Loading branch information
wixoaGit authored Feb 12, 2024
1 parent ac544a1 commit 6e250f8
Show file tree
Hide file tree
Showing 28 changed files with 475 additions and 169 deletions.
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,8 +14,9 @@ public sealed class ProcDefinitionJson {
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; }
}
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

0 comments on commit 6e250f8

Please sign in to comment.