Skip to content

Commit

Permalink
Added SpriteSequence support for other file types
Browse files Browse the repository at this point in the history
This adds Hover/GoTo/Completion support for SpriteSequenceImage and SpriteSequence references (using `SequenceReferenceAttribute`) in Rules and Weapons files.
  • Loading branch information
penev92 committed Oct 21, 2022
1 parent b6ade2b commit 8055c2d
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using OpenRA.MiniYamlParser;
using Oraide.Core;
using Oraide.Core.Entities;
using Oraide.Core.Entities.Csharp;
using Oraide.Core.Entities.MiniYaml;
using Oraide.LanguageServer.Caching;

Expand Down Expand Up @@ -270,6 +271,72 @@ protected bool TryMergeYamlFiles(IEnumerable<string> filePaths, out List<MiniYam
}
}

protected string ResolveSpriteSequenceImageNameForRules(CursorTarget cursorTarget, ClassFieldInfo fieldInfo, MapManifest? mapManifest)
{
var files = new List<string>(symbolCache[cursorTarget.ModId].ModManifest.RulesFiles);
if (mapManifest?.RulesFiles != null)
files.AddRange(mapManifest?.RulesFiles);

return ResolveSpriteSequenceImageName(cursorTarget, fieldInfo, files);
}

protected string ResolveSpriteSequenceImageNameForWeapons(CursorTarget cursorTarget, ClassFieldInfo fieldInfo, MapManifest? mapManifest)
{
var files = new List<string>(symbolCache[cursorTarget.ModId].ModManifest.WeaponsFiles);
if (mapManifest?.WeaponsFiles != null)
files.AddRange(mapManifest?.WeaponsFiles);

return ResolveSpriteSequenceImageName(cursorTarget, fieldInfo, files);
}

private string ResolveSpriteSequenceImageName(CursorTarget cursorTarget, ClassFieldInfo fieldInfo, IEnumerable<string> files)
{
// Initializing the target image name as the obscure default OpenRA uses - the actor name.
var imageName = cursorTarget.TargetNode.ParentNode.ParentNode.Key;

// Resolve the actual name that we need to use.
var sequenceAttribute = fieldInfo.OtherAttributes.FirstOrDefault(x => x.Name == "SequenceReference");
var imageFieldName = sequenceAttribute.Value != null && sequenceAttribute.Value.Contains(',')
? sequenceAttribute.Value.Substring(0, sequenceAttribute.Value.IndexOf(','))
: sequenceAttribute.Value;

var modData = symbolCache[cursorTarget.ModId];
var resolvedFileList = files.Select(x => OpenRaFolderUtils.ResolveFilePath(x, (modData.ModId, modData.ModFolder)));
if (TryMergeYamlFiles(resolvedFileList, out var nodes))
{
// Check for overriding image names on the trait in question.
var actorNode = nodes.First(x => x.Key == cursorTarget.TargetNode.ParentNode.ParentNode.Key);
var traitNode = actorNode.Value.Nodes.FirstOrDefault(x => x.Key == cursorTarget.TargetNode.ParentNode.Key);
var imageNode = traitNode?.Value.Nodes.FirstOrDefault(x => x.Key == imageFieldName);
if (imageNode?.Value.Value != null)
imageName = imageNode.Value.Value;
}

return imageName;
}

protected IEnumerable<SpriteSequenceDefinition> GetSpriteSequencesForImage(string modId, string imageName, MapManifest? mapManifest)
{
var files = new List<string>(symbolCache[modId].ModManifest.SpriteSequences);
if (mapManifest?.WeaponsFiles != null)
files.AddRange(mapManifest?.SpriteSequenceFiles);

var resolvedFileList = files.Select(x => OpenRaFolderUtils.ResolveFilePath(x, (modId, symbolCache[modId].ModFolder)));

if (TryMergeYamlFiles(resolvedFileList, out var imageNodes))
{
var sequenceNodes = imageNodes.FirstOrDefault(x => x.Key == imageName)?.Value.Nodes;
var sequences = sequenceNodes?
.Where(x => x.Key != "Defaults")
.Select(x => new SpriteSequenceDefinition(x.Key, x.ParentNode?.Key, x.Value.Value,
new MemberLocation("", 0, 0)));

return sequences ?? Enumerable.Empty<SpriteSequenceDefinition>();
}

return Enumerable.Empty<SpriteSequenceDefinition>();
}

protected string NormalizeFilePath(string filePath)
{
// Because VSCode sends us weird partially-url-encoded file paths.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ protected override void Initialize(CursorTarget cursorTarget)
{
modId = cursorTarget.ModId;

// TODO: Don't map everything to CompletionItems here! Defer that until we know what we need, then only map that (like in DefinitionHandler).
// Using .First() is not great but we have no way to differentiate between traits of the same name
// until the server learns the concept of a mod and loaded assemblies.
traitNames = symbolCache[modId].CodeSymbols.TraitInfos.Where(x => !x.First().IsAbstract).Select(x => x.First().ToCompletionItem());
Expand Down Expand Up @@ -263,17 +264,21 @@ protected override IEnumerable<CompletionItem> HandleRulesValue(CursorTarget cur
var tempCursorNames = cursorNames;
var tempPaletteNames = paletteNames;

MapManifest mapManifest = default;
if (cursorTarget.FileType == FileType.MapRules)
{
var mapReference = symbolCache[cursorTarget.ModId].Maps
mapManifest = symbolCache[cursorTarget.ModId].Maps
.FirstOrDefault(x => x.RulesFiles.Contains(cursorTarget.FileReference));

if (mapReference.MapReference != null && symbolCache.Maps.TryGetValue(mapReference.MapReference, out var mapSymbols))
if (mapManifest.MapReference != null && symbolCache.Maps.TryGetValue(mapManifest.MapReference, out var mapSymbols))
{
// TODO: Don't map to everything CompletionItems here! Defer that until we know what we need, then only map that (like in DefinitionHandler).
tempActorNames = tempActorNames.Union(mapSymbols.ActorDefinitions.Select(x => x.First().ToCompletionItem()));
tempWeaponNames = tempWeaponNames.Union(mapSymbols.WeaponDefinitions.Select(x => x.First().ToCompletionItem()));
tempConditionNames = tempConditionNames.Union(mapSymbols.ConditionDefinitions.Select(x => x.First().ToCompletionItem()));
tempPaletteNames = tempPaletteNames.Union(mapSymbols.PaletteDefinitions.Select(x => x.First().ToCompletionItem()));
spriteSequenceImageNames = spriteSequenceImageNames.Union(
mapSymbols.SpriteSequenceImageDefinitions.Select(x => x.First().ToCompletionItem()));
}
}

Expand All @@ -295,6 +300,23 @@ protected override IEnumerable<CompletionItem> HandleRulesValue(CursorTarget cur
if (fieldInfo.OtherAttributes.Any(x => x.Name == "PaletteReference"))
return tempPaletteNames.Where(x => !string.IsNullOrEmpty(x.Label));

// Pretend there is such a thing as a "SequenceImageReferenceAttribute" until we add it in OpenRA one day.
// NOTE: This will improve if/when we add the attribute.
if (traitInfo.TraitPropertyInfos.Any(x => x.OtherAttributes.Any(y => y.Name == "SequenceReference"
&& y.Value != null
&& y.Value.Contains(',') ? y.Value.Substring(0, y.Value.IndexOf(',')) == fieldInfo.Name : y.Value == fieldInfo.Name)))
{
return spriteSequenceImageNames;
}

if (fieldInfo.OtherAttributes.Any(x => x.Name == "SequenceReference"))
{
// Resolve sequence image inheritance so we can show all inherited sequences.
var imageName = ResolveSpriteSequenceImageNameForRules(cursorTarget, fieldInfo, mapManifest);
var sequences = GetSpriteSequencesForImage(cursorTarget.ModId, imageName, mapManifest);
return sequences.Select(x => x.ToCompletionItem());
}

return Enumerable.Empty<CompletionItem>();
}

Expand Down Expand Up @@ -378,7 +400,60 @@ protected override IEnumerable<CompletionItem> HandleWeaponValue(CursorTarget cu
}

case 2:
{
ClassFieldInfo fieldInfo = default;
var fieldInfos = Array.Empty<ClassFieldInfo>();
var parentNode = cursorTarget.TargetNode.ParentNode;
if (parentNode.Key == "Projectile")
{
var projectileInfo = symbolCache[modId].CodeSymbols.WeaponInfo.ProjectileInfos.FirstOrDefault(x => x.Name == cursorTarget.TargetNode.ParentNode.Value);
if (projectileInfo.Name != null)
{
fieldInfo = projectileInfo.PropertyInfos.FirstOrDefault(x => x.Name == cursorTarget.TargetNode.Key);
fieldInfos = projectileInfo.PropertyInfos;
}
}
else if (parentNode.Key == "Warhead" || parentNode.Key.StartsWith("Warhead@"))
{
var warheadInfo = symbolCache[modId].CodeSymbols.WeaponInfo.WarheadInfos.FirstOrDefault(x => x.Name == cursorTarget.TargetNode.ParentNode.Value);
if (warheadInfo.Name != null)
{
fieldInfo = warheadInfo.PropertyInfos.FirstOrDefault(x => x.Name == cursorTarget.TargetNode.Key);
fieldInfos = warheadInfo.PropertyInfos;
}
}

MapManifest mapManifest = default;
if (cursorTarget.FileType == FileType.MapRules)
{
mapManifest = symbolCache[cursorTarget.ModId].Maps
.FirstOrDefault(x => x.RulesFiles.Contains(cursorTarget.FileReference));

if (mapManifest.MapReference != null && symbolCache.Maps.TryGetValue(mapManifest.MapReference, out var mapSymbols))
{
spriteSequenceImageNames = spriteSequenceImageNames.Union(
mapSymbols.SpriteSequenceImageDefinitions.Select(x => x.First().ToCompletionItem()));
}
}

// Pretend there is such a thing as a "SequenceImageReferenceAttribute" until we add it in OpenRA one day.
// NOTE: This will improve if/when we add the attribute.
if (fieldInfos.Any(x => x.OtherAttributes.Any(y => y.Name == "SequenceReference"
&& y.Value.Contains(',') ? y.Value.Substring(0, y.Value.IndexOf(',')) == fieldInfo.Name : y.Value == fieldInfo.Name)))
{
return spriteSequenceImageNames;
}

if (fieldInfo.OtherAttributes.Any(x => x.Name == "SequenceReference"))
{
// Resolve sequence image inheritance so we can show all inherited sequences.
var imageName = ResolveSpriteSequenceImageNameForWeapons(cursorTarget, fieldInfo, mapManifest);
var sequences = GetSpriteSequencesForImage(cursorTarget.ModId, imageName, mapManifest);
return sequences.Select(x => x.ToCompletionItem());
}

return Enumerable.Empty<CompletionItem>();
}

default:
return Enumerable.Empty<CompletionItem>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,24 @@ protected override IEnumerable<Location> HandleRulesValue(CursorTarget cursorTar
var conditionDefinitions = modSymbols.ConditionDefinitions[cursorTarget.TargetString];
var cursorDefinitions = modSymbols.CursorDefinitions[cursorTarget.TargetString];
var paletteDefinitions = modSymbols.PaletteDefinitions[cursorTarget.TargetString];
var spriteSequenceImageDefinitions = modSymbols.SpriteSequenceImageDefinitions;

MapManifest mapManifest = default;
if (cursorTarget.FileType == FileType.MapRules)
{
var mapReference = symbolCache[cursorTarget.ModId].Maps
mapManifest = symbolCache[cursorTarget.ModId].Maps
.FirstOrDefault(x => x.RulesFiles.Contains(cursorTarget.FileReference));

if (mapReference.MapReference != null && symbolCache.Maps.TryGetValue(mapReference.MapReference, out var mapSymbols))
if (mapManifest.MapReference != null && symbolCache.Maps.TryGetValue(mapManifest.MapReference, out var mapSymbols))
{
actorDefinitions = actorDefinitions.Union(mapSymbols.ActorDefinitions[cursorTarget.TargetString]);
weaponDefinitions = weaponDefinitions.Union(mapSymbols.WeaponDefinitions[cursorTarget.TargetString]);
conditionDefinitions = conditionDefinitions.Union(mapSymbols.ConditionDefinitions[cursorTarget.TargetString]);

spriteSequenceImageDefinitions = spriteSequenceImageDefinitions
.SelectMany(x => x)
.Union(mapSymbols.SpriteSequenceImageDefinitions.SelectMany(x => x))
.ToLookup(x => x.Name, y => y);
}
}

Expand All @@ -175,6 +182,22 @@ protected override IEnumerable<Location> HandleRulesValue(CursorTarget cursorTar

if (fieldInfo.OtherAttributes.Any(x => x.Name == "PaletteReference"))
return paletteDefinitions.Select(x => x.Location.ToLspLocation(x.Type.Length));

// Pretend there is such a thing as a "SequenceImageReferenceAttribute" until we add it in OpenRA one day.
// NOTE: This will improve if/when we add the attribute.
if (traitInfo.TraitPropertyInfos.Any(x => x.OtherAttributes.Any(y => y.Name == "SequenceReference"
&& y.Value.Contains(',') ? y.Value.Substring(0, y.Value.IndexOf(',')) == fieldInfo.Name : y.Value == fieldInfo.Name)))
{
return spriteSequenceImageDefinitions[cursorTarget.TargetString].Select(x => x.Location.ToLspLocation(x.Name.Length));
}

if (fieldInfo.OtherAttributes.Any(x => x.Name == "SequenceReference"))
{
var imageName = ResolveSpriteSequenceImageNameForRules(cursorTarget, fieldInfo, mapManifest);
return spriteSequenceImageDefinitions[imageName].SelectMany(x => x.Sequences)
.Where(x => x.Name == cursorTarget.TargetString)
.Select(x => x.Location.ToLspLocation(x.Name.Length));
}
}
}

Expand Down Expand Up @@ -287,7 +310,65 @@ protected override IEnumerable<Location> HandleWeaponValue(CursorTarget cursorTa
}

case 2:
{
ClassFieldInfo fieldInfo = default;
var fieldInfos = Array.Empty<ClassFieldInfo>();
var parentNode = cursorTarget.TargetNode.ParentNode;
if (parentNode.Key == "Projectile")
{
var projectileInfo = codeSymbols.WeaponInfo.ProjectileInfos.FirstOrDefault(x => x.Name == cursorTarget.TargetNode.ParentNode.Value);
if (projectileInfo.Name != null)
{
fieldInfo = projectileInfo.PropertyInfos.FirstOrDefault(x => x.Name == cursorTarget.TargetNode.Key);
fieldInfos = projectileInfo.PropertyInfos;
}
}
else if (parentNode.Key == "Warhead" || parentNode.Key.StartsWith("Warhead@"))
{
var warheadInfo = codeSymbols.WeaponInfo.WarheadInfos.FirstOrDefault(x => x.Name == cursorTarget.TargetNode.ParentNode.Value);
if (warheadInfo.Name != null)
{
fieldInfo = warheadInfo.PropertyInfos.FirstOrDefault(x => x.Name == cursorTarget.TargetNode.Key);
fieldInfos = warheadInfo.PropertyInfos;
}
}

var spriteSequenceImageDefinitions = symbolCache[cursorTarget.ModId].ModSymbols.SpriteSequenceImageDefinitions;

MapManifest mapManifest = default;
if (cursorTarget.FileType == FileType.MapRules)
{
mapManifest = symbolCache[cursorTarget.ModId].Maps
.FirstOrDefault(x => x.RulesFiles.Contains(cursorTarget.FileReference));

if (mapManifest.MapReference != null && symbolCache.Maps.TryGetValue(mapManifest.MapReference, out var mapSymbols))
{
// Merge mod symbols with map symbols.
spriteSequenceImageDefinitions = spriteSequenceImageDefinitions
.SelectMany(x => x)
.Union(mapSymbols.SpriteSequenceImageDefinitions.SelectMany(x => x))
.ToLookup(x => x.Name, y => y);
}
}

// Pretend there is such a thing as a "SequenceImageReferenceAttribute" until we add it in OpenRA one day.
// NOTE: This will improve if/when we add the attribute.
if (fieldInfos.Any(x => x.OtherAttributes.Any(y => y.Name == "SequenceReference"
&& y.Value.Contains(',') ? y.Value.Substring(0, y.Value.IndexOf(',')) == fieldInfo.Name : y.Value == fieldInfo.Name)))
{
return spriteSequenceImageDefinitions[cursorTarget.TargetString].Select(x => x.Location.ToLspLocation(x.Name.Length));
}

if (fieldInfo.OtherAttributes.Any(x => x.Name == "SequenceReference"))
{
var imageName = ResolveSpriteSequenceImageNameForWeapons(cursorTarget, fieldInfo, mapManifest);
return spriteSequenceImageDefinitions[imageName].SelectMany(x => x.Sequences)
.Where(x => x.Name == cursorTarget.TargetString)
.Select(x => x.Location.ToLspLocation(x.Name.Length));
}

return Enumerable.Empty<Location>();
}

default:
return Enumerable.Empty<Location>();
Expand Down
Loading

0 comments on commit 8055c2d

Please sign in to comment.