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

add rascript-cli command line interpreter #434

Merged
merged 2 commits into from
Feb 27, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
.vs
lib
/Tests/Regressions
**/launchSettings.json
14 changes: 14 additions & 0 deletions RATools.sln
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RATools.Parser", "Source\Pa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RATools.Parser.Tests", "Tests\Parser\RATools.Parser.Tests.csproj", "{38F7E1F1-D759-4E0A-81B7-BF1EC773D9D2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "rascript-cli", "Source\rascript-cli\rascript-cli.csproj", "{6679BB28-DE37-4586-A4D6-0DA196C28380}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -153,6 +155,18 @@ Global
{38F7E1F1-D759-4E0A-81B7-BF1EC773D9D2}.Release|x64.Build.0 = Release|Any CPU
{38F7E1F1-D759-4E0A-81B7-BF1EC773D9D2}.Release|x86.ActiveCfg = Release|Any CPU
{38F7E1F1-D759-4E0A-81B7-BF1EC773D9D2}.Release|x86.Build.0 = Release|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Debug|x64.ActiveCfg = Debug|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Debug|x64.Build.0 = Debug|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Debug|x86.ActiveCfg = Debug|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Debug|x86.Build.0 = Debug|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Release|Any CPU.Build.0 = Release|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Release|x64.ActiveCfg = Release|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Release|x64.Build.0 = Release|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Release|x86.ActiveCfg = Release|Any CPU
{6679BB28-DE37-4586-A4D6-0DA196C28380}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
40 changes: 39 additions & 1 deletion Source/Data/Achievement.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace RATools.Data
{
Expand Down Expand Up @@ -85,6 +87,42 @@ public IEnumerable<IEnumerable<Requirement>> AlternateRequirements
yield return alt.Requirements;
}
}

public static Achievement FindMergeAchievement(IEnumerable<Achievement> achievements, Achievement achievement)
{
Achievement match;

// first pass - look for ID match
if (achievement.Id != 0)
{
match = achievements.FirstOrDefault(a => a.Id == achievement.Id);
if (match != null) // exact ID match, don't check anything else
return match;
}

// ignore achievements with non-local IDs. they're only eligible for matching by ID.
var localAchievements = achievements.Where(a => a.Id == 0 || a.Id >= FirstLocalId);

// second pass - look for title match
if (!String.IsNullOrEmpty(achievement.Title))
{
match = localAchievements.FirstOrDefault(a => String.Compare(a.Title, achievement.Title, StringComparison.InvariantCultureIgnoreCase) == 0);
if (match != null)
return match;
}

// third pass - look for description match
if (!String.IsNullOrEmpty(achievement.Description))
{
match = localAchievements.FirstOrDefault(a => String.Compare(a.Description, achievement.Description, StringComparison.InvariantCultureIgnoreCase) == 0);
if (match != null)
return match;
}

// TODO: attempt to match requirements

return null;
}
}

public enum AchievementType
Expand Down
5 changes: 5 additions & 0 deletions Source/Data/AssetBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,10 @@ public virtual bool IsUnofficial
{
get { return false; }
}

/// <summary>
/// Gets the first unique identifier reserved for locally generated assets.
/// </summary>
public static readonly int FirstLocalId = 111000001;
}
}
42 changes: 41 additions & 1 deletion Source/Data/Leaderboard.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
namespace RATools.Data
using System;
using System.Collections.Generic;
using System.Linq;

namespace RATools.Data
{
/// <summary>
/// Defines a leaderboard.
Expand Down Expand Up @@ -146,6 +150,42 @@ public static string GetFormatString(ValueFormat format)
return "UNKNOWN";
}
}

public static Leaderboard FindMergeLeaderboard(IEnumerable<Leaderboard> leaderboards, Leaderboard leaderboard)
{
Leaderboard match;

// first pass - look for ID match
if (leaderboard.Id != 0)
{
match = leaderboards.FirstOrDefault(l => l.Id == leaderboard.Id);
if (match != null) // exact ID match, don't check anything else
return match;
}

// ignore leaderboards with non-local IDs. they're only eligible for matching by ID.
var localLeaderboards = leaderboards.Where(a => a.Id == 0 || a.Id >= FirstLocalId);

// second pass - look for title match
if (!String.IsNullOrEmpty(leaderboard.Title))
{
match = localLeaderboards.FirstOrDefault(l => String.Compare(l.Title, leaderboard.Title, StringComparison.InvariantCultureIgnoreCase) == 0);
if (match != null)
return match;
}

// third pass - look for description match
if (!String.IsNullOrEmpty(leaderboard.Description))
{
match = localLeaderboards.FirstOrDefault(l => String.Compare(l.Description, leaderboard.Description, StringComparison.InvariantCultureIgnoreCase) == 0);
if (match != null)
return match;
}

// TODO: attempt to match requirements

return null;
}
}

/// <summary>
Expand Down
3 changes: 1 addition & 2 deletions Source/Data/RATools.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<Platforms>AnyCPU</Platforms>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable>
<UseWPF>true</UseWPF>
<BaseOutputPath>..\..\bin\</BaseOutputPath>
</PropertyGroup>

Expand Down
42 changes: 26 additions & 16 deletions Source/Parser/AchievementScriptInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,27 +210,13 @@ public string ErrorMessage
internal ErrorExpression Error { get; private set; }

/// <summary>
/// Processes the provided script.
/// Converts the provided script to a collection of expressions.
/// </summary>
/// <returns>
/// <c>true</c> if the script was successfully processed,
/// <c>false</c> if not - in which case <see cref="ErrorMessage"/> will indicate why.
/// </returns>
public bool Run(Tokenizer input)
public ExpressionGroupCollection Parse(Tokenizer input)
{
var expressionGroups = new AssetExpressionGroupCollection();
expressionGroups.Parse(input);

if (Error == null)
{
foreach (var group in expressionGroups.Groups)
{
Error = group.ParseErrors.FirstOrDefault();
if (Error != null)
return false;
}
}

GameTitle = null;
foreach (var comment in expressionGroups.Groups.First().Expressions.OfType<CommentExpression>())
{
Expand All @@ -245,6 +231,30 @@ public bool Run(Tokenizer input)
}
}

return expressionGroups;
}

/// <summary>
/// Processes the provided script.
/// </summary>
/// <returns>
/// <c>true</c> if the script was successfully processed,
/// <c>false</c> if not - in which case <see cref="ErrorMessage"/> will indicate why.
/// </returns>
public bool Run(Tokenizer input)
{
var expressionGroups = Parse(input);

if (Error == null)
{
foreach (var group in expressionGroups.Groups)
{
Error = group.ParseErrors.FirstOrDefault();
if (Error != null)
return false;
}
}

return Run(expressionGroups, null);
}

Expand Down
3 changes: 1 addition & 2 deletions Source/Parser/RATools.Parser.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<Platforms>AnyCPU</Platforms>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<Nullable>disable</Nullable>
<UseWPF>true</UseWPF>
<BaseOutputPath>..\..\bin\</BaseOutputPath>
</PropertyGroup>

Expand Down
4 changes: 4 additions & 0 deletions Source/RATools.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@
<ItemGroup>
<Compile Remove="Data\**" />
<Compile Remove="Parser\**" />
<Compile Remove="rascript-cli\**" />
<EmbeddedResource Remove="Data\**" />
<EmbeddedResource Remove="Parser\**" />
<EmbeddedResource Remove="rascript-cli\**" />
<None Remove="Data\**" />
<None Remove="Parser\**" />
<None Remove="rascript-cli\**" />
<Page Remove="Data\**" />
<Page Remove="Parser\**" />
<Page Remove="rascript-cli\**" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Source/ViewModels/AssetViewModelBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ public override void Refresh()

if (Generated.Id != 0)
Id = Generated.Id;
else if (Local.Id > 111000000 && Published.Id != 0)
else if (Local.Id >= AssetBase.FirstLocalId && Published.Id != 0)
Id = Published.Id;
else if (Local.Id != 0)
Id = Local.Id;
Expand Down
101 changes: 60 additions & 41 deletions Source/ViewModels/GameViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ internal void PopulateEditorList(AchievementScriptInterpreter interpreter)
private void UpdateTemporaryIds(List<ViewerViewModelBase> editors)
{
// find the maximum temporary id already assigned
int nextLocalId = 111000001;
int nextLocalId = AssetBase.FirstLocalId;
foreach (var assetViewModel in editors.OfType<AssetViewModelBase>())
{
var id = assetViewModel.Local.Id;
Expand Down Expand Up @@ -746,62 +746,81 @@ private void MergeAchievements(List<ViewerViewModelBase> editors, IEnumerable<As
else
assetEditors.AddRange(editors.OfType<LeaderboardViewModel>());

// first pass - look for ID matches
for (int i = assetEditors.Count - 1; i >= 0; i--)
var achievements = new List<Achievement>();
var leaderboards = new List<Leaderboard>();
foreach (var editor in assetEditors)
{
AssetBase mergeAsset = null;
var assetEditor = assetEditors[i];

if (assetEditor.Generated.Id > 0)
mergeAsset = mergeAssets.FirstOrDefault(a => a.Id == assetEditor.Generated.Id);
if (mergeAsset == null && assetEditor.Published.Id > 0)
mergeAsset = mergeAssets.FirstOrDefault(a => a.Id == assetEditor.Published.Id);

if (mergeAsset != null)
if (editor is AchievementViewModel)
{
assign(assetEditor, mergeAsset);

mergeAssets.Remove(mergeAsset);
assetEditors.RemoveAt(i);
var achievement = editor.Published.Asset as Achievement;
if (achievement != null)
achievements.Add(achievement);
achievement = editor.Generated.Asset as Achievement;
if (achievement != null)
achievements.Add(achievement);
}
else if (editor is LeaderboardViewModel)
{
var leaderboard = editor.Published.Asset as Leaderboard;
if (leaderboard != null)
leaderboards.Add(leaderboard);
leaderboard = editor.Generated.Asset as Leaderboard;
if (leaderboard != null)
leaderboards.Add(leaderboard);
}
}

// second pass - look for title matches
for (int i = mergeAssets.Count - 1; i >= 0; i--)
int j = 0;
while (j < mergeAssets.Count && assetEditors.Count > 0)
{
var mergeAsset = mergeAssets[i];
var assetEditor = assetEditors.FirstOrDefault(a =>
(String.Compare(a.Generated.Title.Text, mergeAsset.Title, StringComparison.InvariantCultureIgnoreCase) == 0 ||
String.Compare(a.Published.Title.Text, mergeAsset.Title, StringComparison.InvariantCultureIgnoreCase) == 0)
);
var mergeAsset = mergeAssets[j];

if (assetEditor != null)
{
assign(assetEditor, mergeAsset);
AssetBase match = null;
var achievement = mergeAsset as Achievement;
if (achievement != null)
match = Achievement.FindMergeAchievement(achievements, achievement);

mergeAssets.RemoveAt(i);
assetEditors.Remove(assetEditor);
}
}
var leaderboard = mergeAsset as Leaderboard;
if (leaderboard != null)
match = Leaderboard.FindMergeLeaderboard(leaderboards, leaderboard);

// third pass - look for description matches
for (int i = mergeAssets.Count - 1; i >= 0; i--)
{
var mergeAsset = mergeAssets[i];
var assetEditor = assetEditors.FirstOrDefault(a =>
String.Compare(a.Generated.Description.Text, mergeAsset.Description, StringComparison.InvariantCultureIgnoreCase) == 0);
if (match == null)
{
j++;
continue;
}

if (assetEditor != null)
for (int i = assetEditors.Count - 1; i >= 0; i--)
{
var assetEditor = assetEditors[i];
if (ReferenceEquals(assetEditor.Published.Asset, match))
{
achievement = assetEditor.Published.Asset as Achievement;
leaderboard = assetEditor.Published.Asset as Leaderboard;
}
else if (ReferenceEquals(assetEditor.Generated.Asset, match))
{
achievement = assetEditor.Generated.Asset as Achievement;
leaderboard = assetEditor.Generated.Asset as Leaderboard;
}
else
{
continue;
}

assign(assetEditor, mergeAsset);

mergeAssets.RemoveAt(i);
assetEditors.Remove(assetEditor);
if (achievement != null)
achievements.Remove(achievement);
else if (leaderboard != null)
leaderboards.Remove(leaderboard);

mergeAssets.Remove(mergeAsset);
assetEditors.RemoveAt(i);
break;
}
}

// TODO: attempt to match requirements

// create new entries for each remaining unmerged achievement
foreach (var mergeAsset in mergeAssets)
{
Expand Down
Loading
Loading