From 85ebb24dc236b9e9f3bc7000d52646aef1b57d00 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Sun, 19 Apr 2026 12:04:26 -0600 Subject: [PATCH 1/4] move AchievementList dialog into Achievement folder viewer --- Source/Data/AchievementSet.cs | 5 + Source/Parser/PublishedAssets.cs | 13 ++ Source/ViewModels/AchievementViewModel.cs | 8 + .../ViewModels/AchievementsListViewModel.cs | 169 ++++++++++++++++++ Source/ViewModels/GameViewModel.cs | 70 ++++++-- Source/ViewModels/MainWindowViewModel.cs | 26 +-- ...AchievementSetFolderNavigationViewModel.cs | 11 ++ .../AchievementsFolderNavigationViewModel.cs | 27 +++ .../EditorNavigationViewModelBase.cs | 25 ++- .../Navigation/NavigationListViewModel.cs | 15 +- .../Navigation/NavigationViewModelBase.cs | 39 ++++ .../ViewModels/ViewAchievementsViewModel.cs | 60 ------- Source/Views/AchievementsListViewer.xaml | 112 ++++++++++++ Source/Views/GameViewer.xaml | 2 + Source/Views/MainWindow.xaml | 2 - Source/Views/MainWindow.xaml.cs | 1 - Source/Views/ViewAchievementsDialog.xaml | 99 ---------- Source/Views/ViewAchievementsDialog.xaml.cs | 15 -- 18 files changed, 479 insertions(+), 220 deletions(-) create mode 100644 Source/ViewModels/AchievementsListViewModel.cs create mode 100644 Source/ViewModels/Navigation/AchievementsFolderNavigationViewModel.cs delete mode 100644 Source/ViewModels/ViewAchievementsViewModel.cs create mode 100644 Source/Views/AchievementsListViewer.xaml delete mode 100644 Source/Views/ViewAchievementsDialog.xaml delete mode 100644 Source/Views/ViewAchievementsDialog.xaml.cs diff --git a/Source/Data/AchievementSet.cs b/Source/Data/AchievementSet.cs index 721ce46a..4c699d48 100644 --- a/Source/Data/AchievementSet.cs +++ b/Source/Data/AchievementSet.cs @@ -9,5 +9,10 @@ public class AchievementSet : AssetBase /// Gets or sets the type classification of the achievement set. /// public AchievementSetType Type { get; set; } + + /// + /// Gets the name of the badge for the achievement set. + /// + public string BadgeName { get; set; } } } diff --git a/Source/Parser/PublishedAssets.cs b/Source/Parser/PublishedAssets.cs index b97d5d6a..3229f363 100644 --- a/Source/Parser/PublishedAssets.cs +++ b/Source/Parser/PublishedAssets.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; namespace RATools.Parser @@ -129,6 +130,7 @@ private void Read() OwnerGameId = GameId, Title = Title, Type = AchievementSetType.Core, + BadgeName = ExtractBadgeName(publishedData.GetField("ImageIconUrl").StringValue), }); var publishedAchievements = publishedData.GetField("Achievements"); @@ -168,6 +170,16 @@ private static AchievementSetType ConvertType(string type) } } + private static string ExtractBadgeName(string url) + { + if (url == null) + return null; + + var index = url.LastIndexOf('/'); + var filename = url.Substring(index + 1); + return Path.GetFileNameWithoutExtension(filename); + } + private void ReadSets(JsonField sets) { foreach (var set in sets.ObjectArrayValue) @@ -182,6 +194,7 @@ private void ReadSets(JsonField sets) OwnerGameId = gameId, Title = set.GetField("Title").StringValue ?? Title, Type = ConvertType(set.GetField("Type").StringValue), + BadgeName = ExtractBadgeName(set.GetField("ImageIconUrl").StringValue), }); var publishedAchievements = set.GetField("Achievements"); diff --git a/Source/ViewModels/AchievementViewModel.cs b/Source/ViewModels/AchievementViewModel.cs index 27aa4b1b..950056c1 100644 --- a/Source/ViewModels/AchievementViewModel.cs +++ b/Source/ViewModels/AchievementViewModel.cs @@ -184,5 +184,13 @@ private static string GetMeasuredTarget(IEnumerable requirements) return null; } + + public bool BelongsToSet(AchievementSet achievementSet) + { + if (OwnerSetId == 0) + return (achievementSet == null || achievementSet.Type == AchievementSetType.Core); + + return (achievementSet == null || achievementSet.Id == OwnerSetId); + } } } diff --git a/Source/ViewModels/AchievementsListViewModel.cs b/Source/ViewModels/AchievementsListViewModel.cs new file mode 100644 index 00000000..4a182e18 --- /dev/null +++ b/Source/ViewModels/AchievementsListViewModel.cs @@ -0,0 +1,169 @@ +using Jamiras.Commands; +using Jamiras.DataModels; +using Jamiras.ViewModels; +using RATools.Data; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace RATools.ViewModels +{ + public class AchievementsListViewModel : ViewerViewModelBase + { + public AchievementsListViewModel(GameViewModel owner, AchievementSet achievementSet) + : base(owner) + { + var achievements = owner.Editors.OfType() + .Where(a => a.BelongsToSet(achievementSet)).ToArray(); + Achievements = achievements; + + Title = achievementSet.Title; + CountAchievements(achievements); + _badgeName = achievementSet?.BadgeName ?? owner.BadgeName; + + Id = (achievementSet != null) ? achievementSet.Id : 0; + + ExportCommand = new DelegateCommand(Export); + } + + private void CountAchievements(AchievementViewModel[] achievements) + { + var description = new StringBuilder(); + description.AppendFormat("{0} achievements", achievements.Length); + var initialDescriptionLength = description.Length; + description.Append(" ("); + + var points = new StringBuilder(); + points.AppendFormat("{0} points", achievements.Sum(a => a.Points)); + var initialPointsLength = points.Length; + points.Append(" ("); + + var published = achievements.Where(a => !a.Published?.Asset?.IsUnofficial ?? false); + if (published.Any()) + { + description.AppendFormat("{0} published, ", published.Count()); + points.AppendFormat("{0} published, ", published.Sum(a => a.Points)); + } + + var unpublished = achievements.Where(a => a.Published?.Asset?.IsUnofficial ?? false); + if (unpublished.Any()) + { + description.AppendFormat("{0} unpublished, ", unpublished.Count()); + points.AppendFormat("{0} unpublished, ", unpublished.Sum(a => a.Points)); + } + + var local = achievements.Where(a => a.Published?.Asset == null); + if (local.Any()) + { + description.AppendFormat("{0} local, ", local.Count()); + points.AppendFormat("{0} local, ", local.Sum(a => a.Points)); + } + + description.Length -= 2; + description.Append(')'); + + points.Length -= 2; + points.Append(')'); + + bool hasComma = false; + for (int i = initialDescriptionLength; i < description.Length; i++) + { + if (description[i] == ',') + { + hasComma = true; + break; + } + } + if (!hasComma) + { + description.Length = initialDescriptionLength; + points.Length = initialPointsLength; + } + + Description = description.ToString(); + PointsSummary = points.ToString(); + } + + private readonly string _badgeName; + + public override string ViewerType => "AchievementList"; + + public int Id { get; private set; } + + public static readonly ModelProperty BadgeProperty = ModelProperty.RegisterDependant(typeof(AchievementsListViewModel), "Badge", typeof(ImageSource), new ModelProperty[0], GetBadge); + public ImageSource Badge + { + get { return (ImageSource)GetValue(BadgeProperty); } + } + + private static ImageSource GetBadge(ModelBase model) + { + var vm = (AchievementsListViewModel)model; + if (!String.IsNullOrEmpty(vm._badgeName) && vm._badgeName != "0") + { + if (String.IsNullOrEmpty(vm._owner.RACacheDirectory)) + return null; + + var name = "i" + vm._badgeName; + if (Path.GetExtension(name) == "") + name += ".png"; + var path = Path.Combine(Path.Combine(vm._owner.RACacheDirectory, "..\\Badge"), name); + if (File.Exists(path)) + { + var image = new BitmapImage(new Uri(path)); + image.Freeze(); + return image; + } + } + + return null; + } + + public static readonly ModelProperty PointsSummaryProperty = ModelProperty.Register(typeof(AchievementsListViewModel), "PointsSummary", typeof(string), String.Empty); + public string PointsSummary + { + get { return (string)GetValue(PointsSummaryProperty); } + protected set { SetValue(PointsSummaryProperty, value); } + } + + /// + /// Gets the list of achievements. + /// + public IEnumerable Achievements { get; private set; } + + public int Points { get { return Achievements.Sum(a => a.Points); } } + + public CommandBase ExportCommand { get; private set; } + + private void Export() + { + var filename = Path.GetFileNameWithoutExtension(_owner.Script.Filename) ?? "achievements"; + + var vm = new FileDialogViewModel(); + vm.DialogTitle = "Export achievement information"; + vm.Filters["CSV file"] = "*.csv"; + vm.FileNames = new[] { filename + ".csv" }; + vm.OverwritePrompt = true; + + if (vm.ShowSaveFileDialog() == DialogResult.Ok) + { + using (var file = File.CreateText(vm.FileNames[0])) + { + file.WriteLine("Id,Title,Description,Points"); + + foreach (var achievement in Achievements) + { + file.Write("{0},\"{1}\",", achievement.Id, achievement.Title.Replace("\"", "\\\"")); + file.Write("\"{0}\",", achievement.Description.Replace("\"", "\\\"")); + file.Write("{0}", achievement.Points); + file.WriteLine(); + } + } + } + } + } +} diff --git a/Source/ViewModels/GameViewModel.cs b/Source/ViewModels/GameViewModel.cs index 86eb3518..d9cc320d 100644 --- a/Source/ViewModels/GameViewModel.cs +++ b/Source/ViewModels/GameViewModel.cs @@ -227,9 +227,48 @@ internal void PopulateEditorList(AchievementScriptInterpreter interpreter) var navigation = new NavigationListViewModel(this, _publishedAssets, _localAssets, _editors); NavigationNodes = navigation.Merge(interpreter); - SelectedNavigationNode = FindEditorNavigationNode(NavigationNodes, SelectedEditor); + UpdateSelectedNavigationNode(SelectedNavigationNode); } + internal void UpdateSelectedNavigationNode(NavigationViewModelBase searchNode) + { + var newNode = searchNode != null + ? FindNavigationNode(NavigationNodes, searchNode) + : FindEditorNavigationNode(NavigationNodes, Script); + if (!ReferenceEquals(searchNode, newNode)) + { + SelectedNavigationNode = newNode; + + if (newNode != null) + newNode.IsSelected = true; + + if (searchNode != null) + searchNode.IsSelected = false; + } + } + + internal static NavigationViewModelBase FindNavigationNode(IEnumerable nodes, NavigationViewModelBase searchNode) + { + foreach (var node in nodes) + { + if (node == searchNode) + return node; + } + + foreach (var node in nodes) + { + if (node.Children != null && node.Children.Count > 0) + { + var child = FindNavigationNode(node.Children, searchNode); + if (child != null) + return child; + } + } + + return null; + } + + public int CompileProgress { get; internal set; } public int CompileProgressLine { get; internal set; } @@ -264,7 +303,7 @@ public NavigationViewModelBase SelectedNavigationNode private static void OnSelectedNavigationNodeChanged(object sender, ModelPropertyChangedEventArgs e) { - var editorNavigationViewModel = e.NewValue as EditorNavigationViewModelBase; + var editorNavigationViewModel = e.NewValue as IEditorNavigationViewModel; if (editorNavigationViewModel != null) ((GameViewModel)sender).SelectedEditor = editorNavigationViewModel.Editor; } @@ -307,10 +346,10 @@ private static void OnSelectedEditorChanged(object sender, ModelPropertyChangedE private static NavigationViewModelBase FindEditorNavigationNode(IEnumerable nodes, ViewerViewModelBase editor) { - foreach (var node in nodes.OfType()) + foreach (var node in nodes.OfType()) { if (ReferenceEquals(node.Editor, editor)) - return node; + return (NavigationViewModelBase)node; } foreach (var node in nodes) @@ -522,13 +561,20 @@ private void HandleLocalAssetChange(AssetBase asset, LocalAssets.LocalAssetChang } } - public static readonly ModelProperty TitleProperty = ModelProperty.Register(typeof(MainWindowViewModel), "Title", typeof(string), String.Empty); + public static readonly ModelProperty TitleProperty = ModelProperty.Register(typeof(GameViewModel), "Title", typeof(string), String.Empty); public string Title { get { return (string)GetValue(TitleProperty); } private set { SetValue(TitleProperty, value); } } + public static readonly ModelProperty BadgeNameProperty = ModelProperty.Register(typeof(GameViewModel), "BadgeName", typeof(string), String.Empty); + public string BadgeName + { + get { return (string)GetValue(BadgeNameProperty); } + set { SetValue(BadgeNameProperty, value); } + } + public IEnumerable PublishedSets { get @@ -537,49 +583,49 @@ public IEnumerable PublishedSets } } - public static readonly ModelProperty GeneratedAchievementCountProperty = ModelProperty.Register(typeof(MainWindowViewModel), "GeneratedAchievementCount", typeof(int), 0); + public static readonly ModelProperty GeneratedAchievementCountProperty = ModelProperty.Register(typeof(GameViewModel), "GeneratedAchievementCount", typeof(int), 0); public int GeneratedAchievementCount { get { return (int)GetValue(GeneratedAchievementCountProperty); } private set { SetValue(GeneratedAchievementCountProperty, value); } } - public static readonly ModelProperty CoreAchievementCountProperty = ModelProperty.Register(typeof(MainWindowViewModel), "CoreAchievementCount", typeof(int), 0); + public static readonly ModelProperty CoreAchievementCountProperty = ModelProperty.Register(typeof(GameViewModel), "CoreAchievementCount", typeof(int), 0); public int CoreAchievementCount { get { return (int)GetValue(CoreAchievementCountProperty); } private set { SetValue(CoreAchievementCountProperty, value); } } - public static readonly ModelProperty CoreAchievementPointsProperty = ModelProperty.Register(typeof(MainWindowViewModel), "CoreAchievementPoints", typeof(int), 0); + public static readonly ModelProperty CoreAchievementPointsProperty = ModelProperty.Register(typeof(GameViewModel), "CoreAchievementPoints", typeof(int), 0); public int CoreAchievementPoints { get { return (int)GetValue(CoreAchievementPointsProperty); } private set { SetValue(CoreAchievementPointsProperty, value); } } - public static readonly ModelProperty UnofficialAchievementCountProperty = ModelProperty.Register(typeof(MainWindowViewModel), "UnofficialAchievementCount", typeof(int), 0); + public static readonly ModelProperty UnofficialAchievementCountProperty = ModelProperty.Register(typeof(GameViewModel), "UnofficialAchievementCount", typeof(int), 0); public int UnofficialAchievementCount { get { return (int)GetValue(UnofficialAchievementCountProperty); } private set { SetValue(UnofficialAchievementCountProperty, value); } } - public static readonly ModelProperty UnofficialAchievementPointsProperty = ModelProperty.Register(typeof(MainWindowViewModel), "UnofficialAchievementPoints", typeof(int), 0); + public static readonly ModelProperty UnofficialAchievementPointsProperty = ModelProperty.Register(typeof(GameViewModel), "UnofficialAchievementPoints", typeof(int), 0); public int UnofficialAchievementPoints { get { return (int)GetValue(UnofficialAchievementPointsProperty); } private set { SetValue(UnofficialAchievementPointsProperty, value); } } - public static readonly ModelProperty LocalAchievementCountProperty = ModelProperty.Register(typeof(MainWindowViewModel), "LocalAchievementCount", typeof(int), 0); + public static readonly ModelProperty LocalAchievementCountProperty = ModelProperty.Register(typeof(GameViewModel), "LocalAchievementCount", typeof(int), 0); public int LocalAchievementCount { get { return (int)GetValue(LocalAchievementCountProperty); } private set { SetValue(LocalAchievementCountProperty, value); } } - public static readonly ModelProperty LocalAchievementPointsProperty = ModelProperty.Register(typeof(MainWindowViewModel), "LocalAchievementPoints", typeof(int), 0); + public static readonly ModelProperty LocalAchievementPointsProperty = ModelProperty.Register(typeof(GameViewModel), "LocalAchievementPoints", typeof(int), 0); public int LocalAchievementPoints { get { return (int)GetValue(LocalAchievementPointsProperty); } diff --git a/Source/ViewModels/MainWindowViewModel.cs b/Source/ViewModels/MainWindowViewModel.cs index deb00b03..7ff3143b 100644 --- a/Source/ViewModels/MainWindowViewModel.cs +++ b/Source/ViewModels/MainWindowViewModel.cs @@ -4,12 +4,14 @@ using Jamiras.Services; using Jamiras.ViewModels; using RATools.Services; +using RATools.ViewModels.Navigation; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; +using System.Xml.Linq; namespace RATools.ViewModels { @@ -28,7 +30,6 @@ public MainWindowViewModel() DragDropScriptCommand = new DelegateCommand(DragDropFile, CanDragDropFile); UpdateLocalCommand = DisabledCommand.Instance; - ViewAchievementsCommand = DisabledCommand.Instance; GameBadgesCommand = new DelegateCommand(GameBadges); GameStatsCommand = new DelegateCommand(GameStats); @@ -154,12 +155,10 @@ private static void OnGameChanged(object sender, ModelPropertyChangedEventArgs e vm.SaveScriptAsCommand = new DelegateCommand(() => vm.SaveScriptAs()); vm.RefreshScriptCommand = new DelegateCommand(vm.RefreshScript); vm.UpdateLocalCommand = new DelegateCommand(vm.UpdateLocal); - vm.ViewAchievementsCommand = new DelegateCommand(vm.ViewAchievements); vm.OnPropertyChanged(() => vm.SaveScriptCommand); vm.OnPropertyChanged(() => vm.SaveScriptAsCommand); vm.OnPropertyChanged(() => vm.RefreshScriptCommand); vm.OnPropertyChanged(() => vm.UpdateLocalCommand); - vm.OnPropertyChanged(() => vm.ViewAchievementsCommand); } } @@ -214,7 +213,7 @@ private void OpenFile(string filename) int line = 1; int column = 1; - string selectedEditor = null; + NavigationViewModelBase selectedNavigationNode = null; if (Game != null && Game.Script.Filename == filename) { if (Game.Script.CompareState == GeneratedCompareState.LocalDiffers) @@ -226,8 +225,7 @@ private void OpenFile(string filename) // capture current location so we can restore it after refreshing line = Game.Script.Editor.CursorLine; column = Game.Script.Editor.CursorColumn; - if (Game.SelectedEditor != null) - selectedEditor = Game.SelectedEditor.Title; + selectedNavigationNode = Game.SelectedNavigationNode; } else if (!CloseEditor()) { @@ -359,7 +357,7 @@ private void OpenFile(string filename) existingViewModel.Script.SetContent(content); viewModel = existingViewModel; - existingViewModel.SelectedEditor = existingViewModel.Editors.FirstOrDefault(e => e.Title == selectedEditor); + existingViewModel.UpdateSelectedNavigationNode(selectedNavigationNode); existingViewModel.Script.Editor.MoveCursorTo(line, column, Jamiras.ViewModels.CodeEditor.CodeEditorViewModel.MoveCursorFlags.None); } else @@ -483,20 +481,6 @@ private void UpdateLocal() dialog.ShowDialog(); } - public CommandBase ViewAchievementsCommand { get; private set; } - private void ViewAchievements() - { - var game = Game; - if (game == null) - { - TaskDialogViewModel.ShowErrorMessage("No game loaded", "Cannot view achievements if game not loaded."); - return; - } - - var dialog = new ViewAchievementsViewModel(game); - dialog.ShowDialog(); - } - public static readonly ModelProperty ShowHexValuesProperty = ModelProperty.Register(typeof(MainWindowViewModel), "ShowHexValues", typeof(bool), false, OnShowHexValuesChanged); public bool ShowHexValues { diff --git a/Source/ViewModels/Navigation/AchievementSetFolderNavigationViewModel.cs b/Source/ViewModels/Navigation/AchievementSetFolderNavigationViewModel.cs index 7d0144a1..7ce16830 100644 --- a/Source/ViewModels/Navigation/AchievementSetFolderNavigationViewModel.cs +++ b/Source/ViewModels/Navigation/AchievementSetFolderNavigationViewModel.cs @@ -11,5 +11,16 @@ public AchievementSetFolderNavigationViewModel(AchievementSet achievementSet) } public AchievementSet AchievementSet { get; private set; } + + public override bool Equals(object obj) + { + var that = obj as AchievementSetFolderNavigationViewModel; + return (that != null && this.AchievementSet.Id == that.AchievementSet.Id); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } } } diff --git a/Source/ViewModels/Navigation/AchievementsFolderNavigationViewModel.cs b/Source/ViewModels/Navigation/AchievementsFolderNavigationViewModel.cs new file mode 100644 index 00000000..52d438e6 --- /dev/null +++ b/Source/ViewModels/Navigation/AchievementsFolderNavigationViewModel.cs @@ -0,0 +1,27 @@ +using RATools.Data; + +namespace RATools.ViewModels.Navigation +{ + internal class AchievementsFolderNavigationViewModel : AssetFolderNavigationViewModel, IEditorNavigationViewModel + { + public AchievementsFolderNavigationViewModel(GameViewModel game, AchievementSet achievementSet) + : base("Achievements") + { + _game = game; + _achievementSet = achievementSet; + } + + private readonly GameViewModel _game; + private readonly AchievementSet _achievementSet; + private AchievementsListViewModel _editor; + + public ViewerViewModelBase Editor + { + get + { + _editor ??= new AchievementsListViewModel(_game, _achievementSet); + return _editor; + } + } + } +} diff --git a/Source/ViewModels/Navigation/EditorNavigationViewModelBase.cs b/Source/ViewModels/Navigation/EditorNavigationViewModelBase.cs index 6310df6f..1faf10f1 100644 --- a/Source/ViewModels/Navigation/EditorNavigationViewModelBase.cs +++ b/Source/ViewModels/Navigation/EditorNavigationViewModelBase.cs @@ -7,7 +7,7 @@ namespace RATools.ViewModels.Navigation { - public abstract class EditorNavigationViewModelBase : NavigationViewModelBase + public abstract class EditorNavigationViewModelBase : NavigationViewModelBase, IEditorNavigationViewModel { public EditorNavigationViewModelBase() { @@ -125,5 +125,28 @@ public bool IsNodeFor(AssetViewModelBase editor) return false; } + public override bool Equals(object obj) + { + var that = obj as EditorNavigationViewModelBase; + if (that == null || this.Editor?.ViewerType != that.Editor?.ViewerType) + return false; + + var assetEditorViewModel = this.Editor as AssetViewModelBase; + if (assetEditorViewModel != null) + return assetEditorViewModel.Id == ((AssetViewModelBase)that.Editor).Id; + + return base.Equals(obj); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } + + public interface IEditorNavigationViewModel + { + public ViewerViewModelBase Editor { get; } } + } diff --git a/Source/ViewModels/Navigation/NavigationListViewModel.cs b/Source/ViewModels/Navigation/NavigationListViewModel.cs index 014f2b19..66dcc513 100644 --- a/Source/ViewModels/Navigation/NavigationListViewModel.cs +++ b/Source/ViewModels/Navigation/NavigationListViewModel.cs @@ -363,7 +363,7 @@ private void UpdateNavigationNodes(IEnumerable navigati bool hasSubsets = false; foreach (var achievementSetNode in navigationNodes.OfType()) { - var achievementsFolder = achievementSetNode.Children.OfType().First(n => n.Label == "Achievements"); + var achievementsFolder = achievementSetNode.Children.OfType().First(); var leaderboardsFolder = achievementSetNode.Children.OfType().First(n => n.Label == "Leaderboards"); UpdateAchievementSetNodes(achievementsFolder, leaderboardsFolder, achievementSetNode.AchievementSet); hasSubsets = true; @@ -371,7 +371,7 @@ private void UpdateNavigationNodes(IEnumerable navigati if (!hasSubsets) { - var achievementsFolder = navigationNodes.OfType().First(n => n.Label == "Achievements"); + var achievementsFolder = navigationNodes.OfType().First(); var leaderboardsFolder = navigationNodes.OfType().First(n => n.Label == "Leaderboards"); UpdateAchievementSetNodes(achievementsFolder, leaderboardsFolder, null); } @@ -382,11 +382,8 @@ private void UpdateAchievementSetNodes(AssetFolderNavigationViewModel achievemen var achievementNodes = achievementsFolder.Children.OfType().ToList(); foreach (var achievement in _editors.OfType()) { - if (achievementSet != null && achievement.OwnerSetId != achievementSet.Id) - { - if (achievement.OwnerSetId != 0 || achievementSet.Type != AchievementSetType.Core) - continue; - } + if (!achievement.BelongsToSet(achievementSet)) + continue; if (achievement.Generated.Asset == null && achievement.Local.Asset == null && achievement.Published.Asset == null) { @@ -513,7 +510,7 @@ public IEnumerable Merge(AchievementScriptInterpreter i if (sets.Count < 2) { - newNavigationNodes.Add(new AssetFolderNavigationViewModel("Achievements")); + newNavigationNodes.Add(new AchievementsFolderNavigationViewModel(_gameViewModel, sets[0])); newNavigationNodes.Add(new AssetFolderNavigationViewModel("Leaderboards")); } else @@ -521,7 +518,7 @@ public IEnumerable Merge(AchievementScriptInterpreter i foreach (var achievementSet in sets) { var setFolderNode = new AchievementSetFolderNavigationViewModel(achievementSet); - setFolderNode.AddChild(new AssetFolderNavigationViewModel("Achievements")); + setFolderNode.AddChild(new AchievementsFolderNavigationViewModel(_gameViewModel, achievementSet)); setFolderNode.AddChild(new AssetFolderNavigationViewModel("Leaderboards")); newNavigationNodes.Add(setFolderNode); } diff --git a/Source/ViewModels/Navigation/NavigationViewModelBase.cs b/Source/ViewModels/Navigation/NavigationViewModelBase.cs index 9ca2d11b..e332ba64 100644 --- a/Source/ViewModels/Navigation/NavigationViewModelBase.cs +++ b/Source/ViewModels/Navigation/NavigationViewModelBase.cs @@ -96,6 +96,13 @@ internal void UpdateCompareState() CompareState = compareState; } + public static readonly ModelProperty IsSelectedProperty = ModelProperty.Register(typeof(NavigationViewModelBase), "IsSelected", typeof(bool), false); + public bool IsSelected + { + get { return (bool)GetValue(IsSelectedProperty); } + set { SetValue(IsSelectedProperty, value); } + } + public static readonly ModelProperty IsExpandedProperty = ModelProperty.Register(typeof(NavigationViewModelBase), "IsExpanded", typeof(bool), true); public bool IsExpanded { @@ -122,5 +129,37 @@ public void AddChild(NavigationViewModelBase child) } public IEnumerable ContextMenu { get; protected set; } + + public static bool operator ==(NavigationViewModelBase left, NavigationViewModelBase right) + { + if (ReferenceEquals(left, right)) + return true; + if (left is null || right is null) + return false; + + return left.Equals(right); + } + + public static bool operator !=(NavigationViewModelBase left, NavigationViewModelBase right) + { + if (ReferenceEquals(left, right)) + return false; + if (left is null || right is null) + return true; + + return !left.Equals(right); + } + + public override bool Equals(object obj) + { + var that = obj as NavigationViewModelBase; + + return that != null && this.ImageName == that.ImageName && this.Label == that.Label; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } } } diff --git a/Source/ViewModels/ViewAchievementsViewModel.cs b/Source/ViewModels/ViewAchievementsViewModel.cs deleted file mode 100644 index 52fb2eea..00000000 --- a/Source/ViewModels/ViewAchievementsViewModel.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Jamiras.Commands; -using Jamiras.ViewModels; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace RATools.ViewModels -{ - public class ViewAchievementsViewModel : DialogViewModelBase - { - public ViewAchievementsViewModel(GameViewModel game) - { - _game = game; - - DialogTitle = "Achievements - " + game.Title; - CanClose = true; - CancelButtonText = null; - ExtraButtonCommand = new DelegateCommand(Export); - - Achievements = game.Editors.OfType().ToArray(); - } - - private readonly GameViewModel _game; - - /// - /// Gets the list of achievements. - /// - public IEnumerable Achievements { get; private set; } - - public string ExtraButtonText { get { return "Export"; } } - public CommandBase ExtraButtonCommand { get; private set; } - - private void Export() - { - var filename = Path.GetFileNameWithoutExtension(_game.Script.Filename) ?? "achievements"; - - var vm = new FileDialogViewModel(); - vm.DialogTitle = "Export achievement information"; - vm.Filters["CSV file"] = "*.csv"; - vm.FileNames = new[] { filename + ".csv" }; - vm.OverwritePrompt = true; - - if (vm.ShowSaveFileDialog() == DialogResult.Ok) - { - using (var file = File.CreateText(vm.FileNames[0])) - { - file.WriteLine("Id,Title,Description,Points"); - - foreach (var achievement in Achievements) - { - file.Write("{0},\"{1}\",", achievement.Id, achievement.Title.Replace("\"", "\\\"")); - file.Write("\"{0}\",", achievement.Description.Replace("\"", "\\\"")); - file.Write("{0}", achievement.Points); - file.WriteLine(); - } - } - } - } - } -} diff --git a/Source/Views/AchievementsListViewer.xaml b/Source/Views/AchievementsListViewer.xaml new file mode 100644 index 00000000..94199289 --- /dev/null +++ b/Source/Views/AchievementsListViewer.xaml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Views/GameViewer.xaml b/Source/Views/GameViewer.xaml index f3394b9b..35b79cd9 100644 --- a/Source/Views/GameViewer.xaml +++ b/Source/Views/GameViewer.xaml @@ -5,6 +5,7 @@ + @@ -156,6 +157,7 @@ + diff --git a/Source/Views/MainWindow.xaml b/Source/Views/MainWindow.xaml index 059034fc..5a353afd 100644 --- a/Source/Views/MainWindow.xaml +++ b/Source/Views/MainWindow.xaml @@ -54,8 +54,6 @@ - - diff --git a/Source/Views/MainWindow.xaml.cs b/Source/Views/MainWindow.xaml.cs index 508ca812..0b9d1de9 100644 --- a/Source/Views/MainWindow.xaml.cs +++ b/Source/Views/MainWindow.xaml.cs @@ -58,7 +58,6 @@ protected override void OnInitialized(EventArgs e) dialogService.RegisterDialogHandler(typeof(NewScriptDialogViewModel), vm => new NewScriptDialog()); dialogService.RegisterDialogHandler(typeof(OptionsDialogViewModel), vm => new OkCancelView(new OptionsDialog())); dialogService.RegisterDialogHandler(typeof(UpdateLocalViewModel), vm => new OkCancelView(new UpdateLocalDialog())); - dialogService.RegisterDialogHandler(typeof(ViewAchievementsViewModel), vm => new OkCancelView(new ViewAchievementsDialog())); dialogService.RegisterDialogHandler(typeof(GameBadgesViewModel), vm => new GameBadgesDialog()); dialogService.RegisterDialogHandler(typeof(GameStatsViewModel), vm => new GameStatsDialog()); dialogService.RegisterDialogHandler(typeof(GameStatsViewModel.UserHistoryViewModel), vm => new UserHistoryDialog()); diff --git a/Source/Views/ViewAchievementsDialog.xaml b/Source/Views/ViewAchievementsDialog.xaml deleted file mode 100644 index cc18a140..00000000 --- a/Source/Views/ViewAchievementsDialog.xaml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Source/Views/ViewAchievementsDialog.xaml.cs b/Source/Views/ViewAchievementsDialog.xaml.cs deleted file mode 100644 index c6644f8f..00000000 --- a/Source/Views/ViewAchievementsDialog.xaml.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Windows.Controls; - -namespace RATools.Views -{ - /// - /// Interaction logic for ViewAchievementsDialog.xaml - /// - public partial class ViewAchievementsDialog : UserControl - { - public ViewAchievementsDialog() - { - InitializeComponent(); - } - } -} From 65b9efca99ff4d297a3ecd60fbb9af8229d19dc7 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Tue, 21 Apr 2026 17:53:08 -0600 Subject: [PATCH 2/4] fix navigation to/from achievement list node --- .../ViewModels/AchievementsListViewModel.cs | 24 ++++++++--- Source/ViewModels/AssetViewModelBase.cs | 2 + Source/ViewModels/GameViewModel.cs | 42 +++++++++++-------- .../AchievementsFolderNavigationViewModel.cs | 3 +- Source/ViewModels/ViewerViewModelBase.cs | 2 + 5 files changed, 49 insertions(+), 24 deletions(-) diff --git a/Source/ViewModels/AchievementsListViewModel.cs b/Source/ViewModels/AchievementsListViewModel.cs index 4a182e18..6a30675a 100644 --- a/Source/ViewModels/AchievementsListViewModel.cs +++ b/Source/ViewModels/AchievementsListViewModel.cs @@ -2,6 +2,7 @@ using Jamiras.DataModels; using Jamiras.ViewModels; using RATools.Data; +using RATools.ViewModels.Navigation; using System; using System.Collections.Generic; using System.IO; @@ -14,22 +15,35 @@ namespace RATools.ViewModels { public class AchievementsListViewModel : ViewerViewModelBase { - public AchievementsListViewModel(GameViewModel owner, AchievementSet achievementSet) + public AchievementsListViewModel(GameViewModel owner, AchievementSet achievementSet, IEnumerable navigationNodes) : base(owner) { - var achievements = owner.Editors.OfType() - .Where(a => a.BelongsToSet(achievementSet)).ToArray(); + AchievementViewModel[] achievements; + + if (navigationNodes != null) + { + achievements = navigationNodes.OfType() + .Select(vm => vm.Editor as AchievementViewModel).ToArray(); + } + else + { + achievements = owner.Editors.OfType() + .Where(a => a.BelongsToSet(achievementSet)).ToArray(); + } + Achievements = achievements; Title = achievementSet.Title; CountAchievements(achievements); _badgeName = achievementSet?.BadgeName ?? owner.BadgeName; - Id = (achievementSet != null) ? achievementSet.Id : 0; + _id = (achievementSet != null) ? achievementSet.Id : 0; ExportCommand = new DelegateCommand(Export); } + private readonly int _id; + private void CountAchievements(AchievementViewModel[] achievements) { var description = new StringBuilder(); @@ -92,7 +106,7 @@ private void CountAchievements(AchievementViewModel[] achievements) public override string ViewerType => "AchievementList"; - public int Id { get; private set; } + public override int ViewerId { get { return _id; } } public static readonly ModelProperty BadgeProperty = ModelProperty.RegisterDependant(typeof(AchievementsListViewModel), "Badge", typeof(ImageSource), new ModelProperty[0], GetBadge); public ImageSource Badge diff --git a/Source/ViewModels/AssetViewModelBase.cs b/Source/ViewModels/AssetViewModelBase.cs index 03401d77..115eb65d 100644 --- a/Source/ViewModels/AssetViewModelBase.cs +++ b/Source/ViewModels/AssetViewModelBase.cs @@ -36,6 +36,8 @@ public AssetViewModelBase(GameViewModel owner) } } + public override int ViewerId { get { return this.Id; } } + /// /// The asset generated by the script. /// diff --git a/Source/ViewModels/GameViewModel.cs b/Source/ViewModels/GameViewModel.cs index d9cc320d..d8dba841 100644 --- a/Source/ViewModels/GameViewModel.cs +++ b/Source/ViewModels/GameViewModel.cs @@ -134,20 +134,7 @@ public void NavigateBack() private void NavigateTo(NavigationItem item) { - ViewerViewModelBase editor = null; - foreach (var scan in Editors) - { - if (scan.ViewerType != item.EditorType) - continue; - - var assetEditor = scan as AssetViewModelBase; - if (assetEditor != null && assetEditor.Id != item.EditorId) - continue; - - editor = scan; - break; - } - + ViewerViewModelBase editor = FindEditor(NavigationNodes, item); if (editor == null) return; @@ -199,10 +186,7 @@ public void CaptureNavigationLocation() private static NavigationItem CaptureNavigationItem(ViewerViewModelBase editor) { - var item = new NavigationItem { EditorType = editor.ViewerType }; - var assetEditor = editor as AssetViewModelBase; - if (assetEditor != null) - item.EditorId = assetEditor.Id; + var item = new NavigationItem { EditorType = editor.ViewerType, EditorId = editor.ViewerId }; var scriptEditor = editor as ScriptViewModel; if (scriptEditor != null) @@ -211,6 +195,28 @@ private static NavigationItem CaptureNavigationItem(ViewerViewModelBase editor) return item; } + private static ViewerViewModelBase FindEditor(IEnumerable nodes, NavigationItem item) + { + foreach (var node in nodes.OfType()) + { + var editor = node.Editor; + if (editor.ViewerType == item.EditorType && editor.ViewerId == item.EditorId) + return editor; + } + + foreach (var node in nodes) + { + if (node.Children != null && node.Children.Count > 0) + { + var child = FindEditor(node.Children, item); + if (child != null) + return child; + } + } + + return null; + } + internal void PopulateEditorList(AchievementScriptInterpreter interpreter) { if (interpreter != null) diff --git a/Source/ViewModels/Navigation/AchievementsFolderNavigationViewModel.cs b/Source/ViewModels/Navigation/AchievementsFolderNavigationViewModel.cs index 52d438e6..763b2a50 100644 --- a/Source/ViewModels/Navigation/AchievementsFolderNavigationViewModel.cs +++ b/Source/ViewModels/Navigation/AchievementsFolderNavigationViewModel.cs @@ -1,4 +1,5 @@ using RATools.Data; +using System.Linq; namespace RATools.ViewModels.Navigation { @@ -19,7 +20,7 @@ public ViewerViewModelBase Editor { get { - _editor ??= new AchievementsListViewModel(_game, _achievementSet); + _editor ??= new AchievementsListViewModel(_game, _achievementSet, Children.OfType()); return _editor; } } diff --git a/Source/ViewModels/ViewerViewModelBase.cs b/Source/ViewModels/ViewerViewModelBase.cs index 727b5f09..a868855b 100644 --- a/Source/ViewModels/ViewerViewModelBase.cs +++ b/Source/ViewModels/ViewerViewModelBase.cs @@ -23,6 +23,8 @@ public virtual string ViewerImage get { return String.Format("/RATools;component/Resources/{0}.png", ViewerType.ToLower()); } } + public virtual int ViewerId { get { return 0; } } + public CommandBase UpdateLocalCommand { get; protected set; } internal int SortOrder { get; set; } From a76763b563ee804e50b06f0e4d1832fe4638830b Mon Sep 17 00:00:00 2001 From: Jamiras Date: Mon, 20 Apr 2026 16:28:13 -0600 Subject: [PATCH 3/4] support for changing badge from achievement viewer --- Source/Data/Achievement.cs | 12 +++ .../Parser/Functions/AchievementFunction.cs | 2 +- Source/ViewModels/AchievementViewModel.cs | 77 ++++++++++++++++++- Source/ViewModels/AssetViewModelBase.cs | 26 ++----- .../Navigation/NavigationListViewModel.cs | 23 +++++- Source/Views/AchievementViewer.xaml | 9 ++- Source/Views/AchievementsListViewer.xaml | 34 ++++---- 7 files changed, 144 insertions(+), 39 deletions(-) diff --git a/Source/Data/Achievement.cs b/Source/Data/Achievement.cs index 6623ae36..205390f1 100644 --- a/Source/Data/Achievement.cs +++ b/Source/Data/Achievement.cs @@ -31,6 +31,18 @@ public Achievement() /// public string BadgeName { get; set; } + public static bool IsValidBadgeName(string badgeName) + { + if (String.IsNullOrEmpty(badgeName)) + return false; + if (badgeName == "0") + return false; + if (badgeName == "00000") + return false; + + return true; + } + /// /// Gets the trigger for the achievement. /// diff --git a/Source/Parser/Functions/AchievementFunction.cs b/Source/Parser/Functions/AchievementFunction.cs index b9dff5d6..deb74bdc 100644 --- a/Source/Parser/Functions/AchievementFunction.cs +++ b/Source/Parser/Functions/AchievementFunction.cs @@ -25,7 +25,7 @@ public AchievementFunction() Parameters.Add(new VariableDefinitionExpression("modified")); DefaultParameters["modified"] = new StringConstantExpression(""); Parameters.Add(new VariableDefinitionExpression("badge")); - DefaultParameters["badge"] = new StringConstantExpression("0"); + DefaultParameters["badge"] = new StringConstantExpression("00000"); Parameters.Add(new VariableDefinitionExpression("type")); DefaultParameters["type"] = new StringConstantExpression(""); diff --git a/Source/ViewModels/AchievementViewModel.cs b/Source/ViewModels/AchievementViewModel.cs index 950056c1..611634d6 100644 --- a/Source/ViewModels/AchievementViewModel.cs +++ b/Source/ViewModels/AchievementViewModel.cs @@ -2,10 +2,12 @@ using Jamiras.Components; using Jamiras.DataModels; using Jamiras.Services; +using Jamiras.ViewModels; using RATools.Data; using RATools.Services; using System; using System.Collections.Generic; +using System.IO; using System.Text; namespace RATools.ViewModels @@ -16,6 +18,7 @@ public AchievementViewModel(GameViewModel owner) : base(owner) { CopyDefinitionToClipboardCommand = new DelegateCommand(CopyDefinitionToClipboard); + ChangeImageCommand = new DelegateCommand(ChangeImage); } public override string ViewerType @@ -37,7 +40,13 @@ public override void Refresh() else if (localAsset != null) AchievementType = localAsset.Type; - if (String.IsNullOrEmpty(BadgeName)) + if (generatedAsset != null && Achievement.IsValidBadgeName(generatedAsset.BadgeName)) + BadgeName = generatedAsset.BadgeName; + else if (localAsset != null && Achievement.IsValidBadgeName(localAsset.BadgeName)) + BadgeName = localAsset.BadgeName; + else if (coreAsset != null && Achievement.IsValidBadgeName(coreAsset.BadgeName)) + BadgeName = coreAsset.BadgeName; + else BadgeName = "00000"; } @@ -79,6 +88,7 @@ public string TypeImage protected override bool AreAssetSpecificPropertiesModified(AssetSourceViewModel left, AssetSourceViewModel right) { IsPointsModified = (left.Points.Value != right.Points.Value); + bool badgeModified = left.BadgeName != right.BadgeName; var leftAchievement = left.Asset as Achievement; var rightAchievement = right.Asset as Achievement; @@ -86,7 +96,7 @@ protected override bool AreAssetSpecificPropertiesModified(AssetSourceViewModel var rightAchievementType = rightAchievement?.Type ?? AchievementType.Standard; IsAchievementTypeModified = leftAchievementType != rightAchievementType; - return IsPointsModified || IsAchievementTypeModified; + return IsPointsModified || IsAchievementTypeModified || badgeModified; } internal override IEnumerable BuildTriggerList(AssetSourceViewModel assetViewModel) @@ -114,7 +124,7 @@ protected override void UpdateLocal(AssetBase asset, AssetBase localAsset, Strin if (achievement != null) { - if (String.IsNullOrEmpty(achievement.BadgeName) || achievement.BadgeName == "0") + if (!Achievement.IsValidBadgeName(achievement.BadgeName)) achievement.BadgeName = BadgeName; } @@ -136,6 +146,67 @@ private void CopyDefinitionToClipboard() } } + public DelegateCommand ChangeImageCommand { get; private set; } + + private void ChangeImage() + { + var vm = new FileDialogViewModel(); + vm.DialogTitle = "Select badge image"; + vm.Filters["Image file"] = "*.png;*.gif;*.jpg;*.jpeg"; + vm.CheckFileExists = true; + + if (vm.ShowOpenFileDialog() != DialogResult.Ok) + return; + + var bValid = false; + var bytes = File.ReadAllBytes(vm.FileNames[0]); + var sExtension = Path.GetExtension(vm.FileNames[0]).Substring(1).ToLower(); + switch (sExtension) + { + case "png": + bValid = bytes[1] == 'P' && bytes[2] == 'N' && bytes[3] == 'G'; + break; + + case "gif": + bValid = bytes[0] == 'G' && bytes[1] == 'I' && bytes[2] == 'F' && bytes[3] == '8'; + break; + + case "jpg": + case "jpeg": + bValid = bytes[6] == 'J' && bytes[7] == 'F' && bytes[8] == 'I' && bytes[9] == 'F'; + break; + } + + if (!bValid) + { + MessageBoxViewModel.ShowMessage("File does not appear to be a valid " + sExtension + " image."); + return; + } + + var achievement = Generated.Asset as Achievement; + if (achievement == null) + achievement = Published.Asset as Achievement; + + if (achievement != null) + { + var md5 = Convert.ToHexString(System.Security.Cryptography.MD5.HashData(bytes)).ToLower(); + var localFolder = Path.Combine(_owner.RACacheDirectory, "..", "Badge", "local"); + if (!Directory.Exists(localFolder)) + Directory.CreateDirectory(localFolder); + var localFilename = Path.Combine(localFolder, String.Format("{0}-{1}.{2}", _owner.GameId, md5, sExtension)); + if (!File.Exists(localFilename)) + File.Copy(vm.FileNames[0], localFilename); + + achievement.BadgeName = localFilename.Substring(localFilename.LastIndexOf("local")); + if (ReferenceEquals(Generated.Asset, achievement)) + Generated.BadgeName = achievement.BadgeName; + else + Published.BadgeName = achievement.BadgeName; + + Refresh(); + } + } + public string MeasuredTarget { get diff --git a/Source/ViewModels/AssetViewModelBase.cs b/Source/ViewModels/AssetViewModelBase.cs index 115eb65d..f8182077 100644 --- a/Source/ViewModels/AssetViewModelBase.cs +++ b/Source/ViewModels/AssetViewModelBase.cs @@ -425,14 +425,14 @@ internal string BadgeName private static ImageSource GetBadge(ModelBase model) { var vm = (AssetViewModelBase)model; - if (IsValidBadgeName(vm.Generated.BadgeName)) + if (Achievement.IsValidBadgeName(vm.Generated.BadgeName)) return vm.Generated.Badge; - if (IsValidBadgeName(vm.Local.BadgeName)) + if (Achievement.IsValidBadgeName(vm.Local.BadgeName)) return vm.Local.Badge; - if (IsValidBadgeName(vm.Published.BadgeName)) + if (Achievement.IsValidBadgeName(vm.Published.BadgeName)) return vm.Published.Badge; - if (IsValidBadgeName(vm.BadgeName)) + if (Achievement.IsValidBadgeName(vm.BadgeName)) { vm.Local.BadgeName = vm.BadgeName; return vm.Local.Badge; @@ -441,18 +441,6 @@ private static ImageSource GetBadge(ModelBase model) return null; } - private static bool IsValidBadgeName(string badgeName) - { - if (String.IsNullOrEmpty(badgeName)) - return false; - if (badgeName == "0") - return false; - if (badgeName == "00000") - return false; - - return true; - } - public override void Refresh() { var generatedAsset = Generated.Asset; @@ -484,11 +472,11 @@ public override void Refresh() else Id = Published.Id; - if (IsValidBadgeName(Generated.BadgeName)) + if (Achievement.IsValidBadgeName(Generated.BadgeName)) BadgeName = Generated.BadgeName; - else if (IsValidBadgeName(Local.BadgeName)) + else if (Achievement.IsValidBadgeName(Local.BadgeName)) BadgeName = Local.BadgeName; - else if (IsValidBadgeName(Published.BadgeName)) + else if (Achievement.IsValidBadgeName(Published.BadgeName)) BadgeName = Published.BadgeName; else BadgeName = null; diff --git a/Source/ViewModels/Navigation/NavigationListViewModel.cs b/Source/ViewModels/Navigation/NavigationListViewModel.cs index 66dcc513..2405ebee 100644 --- a/Source/ViewModels/Navigation/NavigationListViewModel.cs +++ b/Source/ViewModels/Navigation/NavigationListViewModel.cs @@ -72,9 +72,28 @@ private void MergeGenerated(AchievementScriptInterpreter interpreter) MergeAndPruneAssets(interpreter.Achievements, 10000, (vm, a) => { + var generatedAchievement = a as Achievement; + if (generatedAchievement != null) + { + vm.SourceLine = interpreter.GetSourceLine(generatedAchievement); + + if (!Achievement.IsValidBadgeName(generatedAchievement.BadgeName)) + { + var localAchievement = vm.Local.Asset as Achievement; + if (localAchievement != null && Achievement.IsValidBadgeName(localAchievement.BadgeName)) + { + generatedAchievement.BadgeName = localAchievement.BadgeName; + } + else + { + var publishedAchievement = vm.Published.Asset as Achievement; + if (publishedAchievement != null && Achievement.IsValidBadgeName(publishedAchievement.BadgeName)) + generatedAchievement.BadgeName = publishedAchievement.BadgeName; + } + } + } + vm.Generated.Asset = a; - if (a != null) - vm.SourceLine = interpreter.GetSourceLine((Achievement)a); }, (vm) => vm.Generated.Asset); MergeAndPruneAssets(interpreter.Leaderboards, 10000, (vm, a) => diff --git a/Source/Views/AchievementViewer.xaml b/Source/Views/AchievementViewer.xaml index 074764b1..c1de9641 100644 --- a/Source/Views/AchievementViewer.xaml +++ b/Source/Views/AchievementViewer.xaml @@ -40,7 +40,14 @@ - + + + + + + + + diff --git a/Source/Views/AchievementsListViewer.xaml b/Source/Views/AchievementsListViewer.xaml index 94199289..e5874050 100644 --- a/Source/Views/AchievementsListViewer.xaml +++ b/Source/Views/AchievementsListViewer.xaml @@ -48,19 +48,27 @@ - - - - - - + + + + + + + + + + + + + + From da01feac796362091c3d6c1141fbca27af02a18b Mon Sep 17 00:00:00 2001 From: Jamiras Date: Thu, 23 Apr 2026 13:38:29 -0600 Subject: [PATCH 4/4] update tests --- Tests/Parser/AchievementScriptInterpreterTests.cs | 2 +- Tests/Parser/Functions/AchievementFunctionTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Parser/AchievementScriptInterpreterTests.cs b/Tests/Parser/AchievementScriptInterpreterTests.cs index 7b0b94fa..d3261e4d 100644 --- a/Tests/Parser/AchievementScriptInterpreterTests.cs +++ b/Tests/Parser/AchievementScriptInterpreterTests.cs @@ -1103,7 +1103,7 @@ public void TestDefaultParameterPassthrough() // calling achievement() directly without a badge or id var achievement = parser.Achievements.ElementAt(0); Assert.That(achievement.Id, Is.EqualTo(0)); - Assert.That(achievement.BadgeName, Is.EqualTo("0")); + Assert.That(achievement.BadgeName, Is.EqualTo("00000")); // calling achievement() indirectly without a badge or id achievement = parser.Achievements.ElementAt(1); diff --git a/Tests/Parser/Functions/AchievementFunctionTests.cs b/Tests/Parser/Functions/AchievementFunctionTests.cs index a5beae26..6f50e056 100644 --- a/Tests/Parser/Functions/AchievementFunctionTests.cs +++ b/Tests/Parser/Functions/AchievementFunctionTests.cs @@ -36,7 +36,7 @@ public void TestDefinition() Assert.That(def.DefaultParameters["modified"], Is.InstanceOf()); Assert.That(((StringConstantExpression)def.DefaultParameters["modified"]).Value, Is.EqualTo("")); Assert.That(def.DefaultParameters["badge"], Is.InstanceOf()); - Assert.That(((StringConstantExpression)def.DefaultParameters["badge"]).Value, Is.EqualTo("0")); + Assert.That(((StringConstantExpression)def.DefaultParameters["badge"]).Value, Is.EqualTo("00000")); Assert.That(def.DefaultParameters["type"], Is.InstanceOf()); Assert.That(((StringConstantExpression)def.DefaultParameters["type"]).Value, Is.EqualTo("")); Assert.That(def.DefaultParameters["set"], Is.InstanceOf());