private void UpdateLocal() { var game = Game; if (game == null) { MessageBoxViewModel.ShowMessage("No game loaded"); return; } if (game.Script.Editor.ErrorsToolWindow.References.Count > 0) { MessageBoxViewModel.ShowMessage("Cannot update while errors exist."); game.Script.Editor.ErrorsToolWindow.IsVisible = true; return; } if (String.IsNullOrEmpty(game.RACacheDirectory)) { MessageBoxViewModel.ShowMessage("Could not identify local directory."); return; } var dialog = new UpdateLocalViewModel(game); dialog.ShowDialog(); }
/// <summary> /// Replaces all matching items with the new text. /// </summary> public void ReplaceAll() { int replaceCount = MatchCount; if (replaceCount > 0) { int matchCount = replaceCount; while (matchCount > 1) { Replace(); matchCount--; } var line = Owner.CursorLine; var column = Owner.CursorColumn; Replace(); if (MatchCount == 0) { Owner.MoveCursorTo(line, column + SearchText.Text.Length, CodeEditorViewModel.MoveCursorFlags.None); } else { replaceCount -= MatchCount; } } MessageBoxViewModel.ShowMessage(string.Format("Replaced {0} occurrances", replaceCount)); Owner.IsFocusRequested = true; }
/// <summary> /// Attempts to open an Access database. /// </summary> /// <param name="fileName">Path to the Access database.</param> public bool Connect(string fileName) { _logger.Write("Opening database: {0}", fileName); // try newer driver first string connectionString = "Driver={Microsoft Access Driver (*.mdb, *.accdb)}; DBQ=" + fileName; var connection = new OdbcConnection(connectionString); try { connection.Open(); } catch (OdbcException ex) { _logger.Write("Failed to open database: " + ex.Message); if (ex.Message.Contains("[IM002]")) { if (IntPtr.Size != 4 && File.Exists(fileName)) { MessageBoxViewModel.ShowMessage("Access driver not found - assuming 64-bit access driver not installed"); } // https://knowledge.autodesk.com/support/autocad/learn-explore/caas/sfdcarticles/sfdcarticles/How-to-install-64-bit-Microsoft-Database-Drivers-alongside-32-bit-Microsoft-Office.html // * download AccessDatabaseEngine_X64.exe from https://www.microsoft.com/en-us/download/details.aspx?displaylang=en&id=13255 // * run it with the /quiet option: > AccessDatabaseEngine_X64.exe /quiet // * delete or rename the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\14.0\Common\FilesPaths\mso.dll registry key } return(false); } catch (InvalidOperationException ex) { _logger.Write("Failed to open database: " + ex.Message); // then try older driver connectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" + fileName; connection = new OdbcConnection(connectionString); connection.Open(); } while (connection.State == System.Data.ConnectionState.Connecting) { System.Threading.Thread.Sleep(100); } if (connection.State == System.Data.ConnectionState.Open) { _logger.Write("Database opened"); _connection = connection; } else { _logger.Write("Failed to open database: " + connection.State); } return(connection.State == System.Data.ConnectionState.Open); }
private void RefreshFromServer(int gameId, IFileSystemService fileSystemService, IHttpRequestService httpRequestService) { var file = Path.Combine(_settings.DumpDirectory, String.Format("{0}.json", gameId)); if (fileSystemService.FileExists(file)) { bool fileValid = (DateTime.Now - fileSystemService.GetFileLastModified(file)) < TimeSpan.FromHours(16); if (fileValid) { return; } } Debug.WriteLine(String.Format("{0} fetching patch data {1}", DateTime.Now, gameId)); var url = String.Format("http://retroachievements.org/dorequest.php?u={0}&t={1}&g={2}&h=1&r=patch", _settings.UserName, _settings.DoRequestToken, gameId); var request = new HttpRequest(url); var response = httpRequestService.Request(request); if (response.Status == System.Net.HttpStatusCode.OK) { using (var outputStream = fileSystemService.CreateFile(file)) { byte[] buffer = new byte[4096]; using (var stream = response.GetResponseStream()) { int bytesRead; while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0) { if (bytesRead < 100) { var str = Encoding.UTF8.GetString(buffer, 0, bytesRead); if (str.Contains("Error")) { if (_progress != null) { _progress.IsEnabled = false; _progress.Label = String.Empty; } _backgroundWorkerService.InvokeOnUiThread(() => MessageBoxViewModel.ShowMessage(str)); return; } } outputStream.Write(buffer, 0, bytesRead); } } } string contents; using (var stream = new StreamReader(fileSystemService.OpenFile(file, OpenFileMode.Read))) { contents = stream.ReadToEnd(); } } }
/// <summary> /// Commits the achivement list back to the 'XXX-User.txt' file. /// </summary> public void Commit(string author) { var warning = new StringBuilder(); using (var writer = new StreamWriter(_fileSystemService.CreateFile(_filename))) { writer.WriteLine(_version); writer.WriteLine(Title); foreach (var achievement in _achievements) { writer.Write(achievement.Id); writer.Write(":\""); var requirements = AchievementBuilder.SerializeRequirements(achievement); if (requirements.Length > AchievementMaxLength) { warning.AppendFormat("Achievement \"{0}\" exceeds serialized limit ({1}/{2})", achievement.Title, requirements.Length, AchievementMaxLength); warning.AppendLine(); } writer.Write(requirements); writer.Write("\":\""); WriteEscaped(writer, achievement.Title); writer.Write("\":\""); WriteEscaped(writer, achievement.Description); writer.Write("\":"); writer.Write(" : : :"); // discontinued features writer.Write(author); // author writer.Write(':'); writer.Write(achievement.Points); writer.Write(':'); writer.Write("0:0:0:0:"); // created, modified, upvotes, downvotes writer.Write(achievement.BadgeName); writer.WriteLine(); } } if (warning.Length > 0) { MessageBoxViewModel.ShowMessage(warning.ToString()); } }
private void OpenTickets() { var settings = ServiceRepository.Instance.FindService <ISettings>(); if (String.IsNullOrEmpty(settings.Cookie)) { MessageBoxViewModel.ShowMessage("This feature requires a Cookie value to be set in the ini file."); return; } if (String.IsNullOrEmpty(settings.UserName)) { MessageBoxViewModel.ShowMessage("This feature requires a User value to be set in the ini file."); return; } var vm = new OpenTicketsViewModel(); vm.ShowDialog(); }
private void Search() { int gameId = GameId.Value.GetValueOrDefault(); foreach (var directory in ServiceRepository.Instance.FindService <ISettings>().DataDirectories) { var notesFile = Path.Combine(directory, gameId + "-Notes.json"); if (!File.Exists(notesFile)) { notesFile = Path.Combine(directory, gameId + "-Notes2.txt"); } if (File.Exists(notesFile)) { LoadGame(gameId, directory); return; } } MessageBoxViewModel.ShowMessage("Could not locate notes file for game " + gameId); return; }
private void DoPlayback() { var backgroundWorkerService = ServiceRepository.Instance.FindService <IBackgroundWorkerService>(); string line; { var builder = new StringBuilder(); while ((line = _reader.ReadLine()) != null) { if (line == "==-==-==-==-==-==") { break; } builder.AppendLine(line); } backgroundWorkerService.InvokeOnUiThread(() => { SetContent(builder.ToString()); }); } int totalTime = 0; var stopwatch = Stopwatch.StartNew(); while ((line = _reader.ReadLine()) != null) { var parts = line.Split(' '); var delay = Int32.Parse(parts[0]); totalTime += delay; if (delay > 1000) { delay = 1000; } if (delay > 5) // adjust the sleep just a little bit to accomodate for thread marshalling { delay -= 5; } System.Threading.Thread.Sleep(delay); if (parts.Length == 2) { if (parts[1].Length == 1) { TypeCharacter(parts[1][0]); } else { var modifiers = ModifierKeys.None; foreach (var modifier in parts[1].Split('+')) { if (modifier == "Ctrl") { modifiers |= ModifierKeys.Control; } else if (modifier == "Shift") { modifiers |= ModifierKeys.Shift; } else if (modifier == "Alt") { modifiers |= ModifierKeys.Alt; } else { Key key; if (Enum.TryParse(modifier, out key)) { if (key == Key.S && modifiers == ModifierKeys.Control) { _owner.Script.ResetModified(); } else { var e = new KeyPressedEventArgs(key, modifiers); backgroundWorkerService.InvokeOnUiThread(() => { OnKeyPressed(e); }); } } } } } } else { var flags = MoveCursorFlags.None; if (parts[1] == "Shift+Click") { flags = MoveCursorFlags.Highlighting; } var location = parts[2].Split(','); var clickLine = Int32.Parse(location[0]); var clickColumn = Int32.Parse(location[1]); backgroundWorkerService.InvokeOnUiThread(() => { MoveCursorTo(clickLine, clickColumn, flags); }); } } _reader.Close(); stopwatch.Stop(); var totalElapsed = TimeSpan.FromMilliseconds(totalTime); var playbackElapsed = stopwatch.Elapsed; backgroundWorkerService.InvokeOnUiThread(() => { MessageBoxViewModel.ShowMessage(String.Format("Playback complete.\n\n" + "Original time: {0}\nPlayback time: {1}", totalElapsed, playbackElapsed)); }); }
public RichPresenceViewModel(GameViewModel owner, string richPresence) { Title = "Rich Presence"; _richPresence = richPresence ?? string.Empty; var genLines = _richPresence.Trim().Length > 0 ? _richPresence.Replace("\r\n", "\n").Split('\n') : new string[0]; string[] localLines = new string[0]; if (String.IsNullOrEmpty(owner.RACacheDirectory)) { UpdateLocalCommand = DisabledCommand.Instance; } else { UpdateLocalCommand = new DelegateCommand(UpdateLocal); _richFile = Path.Combine(owner.RACacheDirectory, owner.GameId + "-Rich.txt"); if (File.Exists(_richFile)) { var coreRichPresence = File.ReadAllText(_richFile); RichPresenceLength = coreRichPresence.Length; if (RichPresenceLength > 0) { localLines = coreRichPresence.Replace("\r\n", "\n").Split('\n'); } } } var lines = new List <RichPresenceLine>(); int genIndex = 0, localIndex = 0; bool isModified = false; var genTags = new TinyDictionary <string, int>(); for (int i = 0; i < genLines.Length; i++) { var line = genLines[i]; if (line.StartsWith("Format:") || line.StartsWith("Lookup:") || line.StartsWith("Display:")) { genTags[line] = i; } } var localTags = new TinyDictionary <string, int>(); for (int i = 0; i < localLines.Length; i++) { var line = localLines[i]; if (line.StartsWith("Format:") || line.StartsWith("Lookup:") || line.StartsWith("Display:")) { localTags[line] = i; } } _hasGenerated = genLines.Length > 0; _hasLocal = localLines.Length > 0; while (genIndex < genLines.Length && localIndex < localLines.Length) { if (genLines[genIndex] == localLines[localIndex]) { // matching lines, advance both lines.Add(new RichPresenceLine(localLines[localIndex++], genLines[genIndex++])); continue; } isModified = true; if (genLines[genIndex].Length == 0) { // blank generated line, advance core lines.Add(new RichPresenceLine(localLines[localIndex++], genLines[genIndex])); continue; } if (localLines[localIndex].Length == 0) { // blank core line, advance generated lines.Add(new RichPresenceLine(localLines[localIndex], genLines[genIndex++])); continue; } // if we're starting a lookup or value, try to line them up int genTagLine, localTagLine; if (!genTags.TryGetValue(localLines[localIndex], out genTagLine)) { genTagLine = -1; } if (!localTags.TryGetValue(genLines[genIndex], out localTagLine)) { localTagLine = -1; } if (genTagLine != -1 && localTagLine != -1) { if (genTagLine > localTagLine) { genTagLine = -1; } else { localTagLine = -1; } } if (genTagLine != -1) { do { lines.Add(new RichPresenceLine("", genLines[genIndex++])); } while (genIndex < genLines.Length && genLines[genIndex].Length > 0); if (genIndex < genLines.Length) { lines.Add(new RichPresenceLine("", genLines[genIndex++])); } continue; } if (localTagLine != -1) { do { lines.Add(new RichPresenceLine(localLines[localIndex++], "")); } while (localIndex < localLines.Length && localLines[localIndex].Length > 0); if (localIndex < localLines.Length) { lines.Add(new RichPresenceLine(localLines[localIndex++], "")); } continue; } // non-matching lines, scan ahead to find a match bool found = false; for (int temp = genIndex + 1; temp < genLines.Length; temp++) { if (genLines[temp].Length == 0) { break; } if (genLines[temp] == localLines[localIndex]) { while (genIndex < temp) { lines.Add(new RichPresenceLine("", genLines[genIndex++])); } found = true; break; } } if (!found) { for (int temp = localIndex + 1; temp < localLines.Length; temp++) { if (localLines[temp].Length == 0) { break; } if (localLines[temp] == genLines[genIndex]) { while (localIndex < temp) { lines.Add(new RichPresenceLine(localLines[localIndex++], "")); } found = true; break; } } } // if a match was found, the next iteration will match them. if one wasn't found, advance both. if (!found) { lines.Add(new RichPresenceLine(localLines[localIndex++], genLines[genIndex++])); } } if (!_hasGenerated) { foreach (var line in localLines) { lines.Add(new RichPresenceLine(line)); } GeneratedSource = "Local (Not Generated)"; CompareSource = String.Empty; ModificationMessage = null; CompareState = GeneratedCompareState.None; CanUpdate = false; } else if (isModified || genIndex != genLines.Length || localIndex != localLines.Length) { while (genIndex < genLines.Length) { lines.Add(new RichPresenceLine("", genLines[genIndex++])); } while (localIndex < localLines.Length) { lines.Add(new RichPresenceLine(localLines[localIndex++], "")); } if (!_hasLocal) { GeneratedSource = "Generated (Not in Local)"; CompareSource = String.Empty; } RichPresenceLength = _richPresence.Length; ModificationMessage = _hasLocal ? "Local value differs from generated value" : "Local value does not exist"; CompareState = GeneratedCompareState.LocalDiffers; CanUpdate = true; } else { GeneratedSource = "Generated (Same as Local)"; CompareSource = String.Empty; ModificationMessage = null; CanUpdate = false; lines.Clear(); foreach (var line in genLines) { lines.Add(new RichPresenceLine(line)); } } Lines = lines; if (_hasGenerated) { CopyToClipboardCommand = new DelegateCommand(() => { ServiceRepository.Instance.FindService <IClipboardService>().SetData(_richPresence); if (_richPresence.Length > RichPresenceMaxLength) { MessageBoxViewModel.ShowMessage("Rich Presence exceeds maximum length of " + RichPresenceMaxLength + " characters (" + _richPresence.Length + ")"); } }); } }
private void LoadTickets() { var games = new Dictionary <int, GameTickets>(); var tickets = new Dictionary <int, AchievementTickets>(); int totalTickets = 0; Progress.Reset(25); Progress.Label = "Fetching Open Tickets"; int pageTickets; int page = 0; do { var ticketsPage = RAWebCache.Instance.GetOpenTicketsPage(page); if (ticketsPage == null) { return; } if (page == 0 && !ticketsPage.Contains("<title>Ticket")) { _backgroundWorkerService.InvokeOnUiThread(() => { MessageBoxViewModel.ShowMessage("Could not retrieve open tickets. Please make sure the Cookie value is up to date in your ini file."); Progress.Label = String.Empty; }); var filename = Path.Combine(Path.GetTempPath(), String.Format("raTickets{0}.html", page)); File.Delete(filename); return; } pageTickets = GetPageTickets(games, tickets, ticketsPage); ++page; totalTickets += pageTickets; Progress.Current++; } while (pageTickets == 100); Progress.Label = "Sorting data"; var ticketList = new List <AchievementTickets>(tickets.Count); foreach (var kvp in tickets) { ticketList.Add(kvp.Value); } ticketList.Sort((l, r) => { var diff = r.OpenTickets.Count - l.OpenTickets.Count; if (diff == 0) { diff = String.Compare(l.AchievementName, r.AchievementName, StringComparison.OrdinalIgnoreCase); } return(diff); }); TopAchievements = ticketList; var gameList = new List <GameTickets>(games.Count); foreach (var kvp in games) { gameList.Add(kvp.Value); } gameList.Sort((l, r) => { var diff = r.OpenTickets - l.OpenTickets; if (diff == 0) { diff = String.Compare(l.GameName, r.GameName, StringComparison.OrdinalIgnoreCase); } return(diff); }); TopGames = gameList; OpenTickets = totalTickets; DialogTitle = "Open Tickets: " + totalTickets; Progress.Label = String.Empty; }
private void OpenFile(string filename) { if (!File.Exists(filename)) { MessageBoxViewModel.ShowMessage("Could not open " + filename); return; } int line = 1; int column = 1; string selectedEditor = null; if (Game != null && Game.Script.Filename == filename) { if (Game.Script.CompareState == GeneratedCompareState.LocalDiffers) { var vm = new MessageBoxViewModel("Revert to the last saved state? Your changes will be lost."); vm.DialogTitle = "Revert Script"; if (vm.ShowOkCancelDialog() == DialogResult.Cancel) { return; } } // capture current location so we can restore it after refreshing line = Game.Script.Editor.CursorLine; column = Game.Script.Editor.CursorColumn; selectedEditor = Game.SelectedEditor.Title; } else if (!CloseEditor()) { return; } var backupFilename = ScriptViewModel.GetBackupFilename(filename); bool usingBackup = false; if (File.Exists(backupFilename)) { var vm2 = new MessageBoxViewModel("Found an autosave file from " + File.GetLastWriteTime(backupFilename) + ".\nDo you want to open it instead?"); vm2.DialogTitle = Path.GetFileName(filename); switch (vm2.ShowYesNoCancelDialog()) { case DialogResult.Cancel: return; case DialogResult.Yes: filename = backupFilename; usingBackup = true; break; default: break; } } var logger = ServiceRepository.Instance.FindService <ILogService>().GetLogger("RATools"); logger.WriteVerbose("Opening " + filename); string content; try { content = File.ReadAllText(filename); } catch (IOException ex) { MessageBoxViewModel.ShowMessage(ex.Message); return; } var tokenizer = Tokenizer.CreateTokenizer(content); var expressionGroup = new AchievementScriptParser().Parse(tokenizer); int gameId = 0; var idComment = expressionGroup.Comments.FirstOrDefault(c => c.Value.Contains("#ID")); if (idComment != null) { var tokens = idComment.Value.Split('='); if (tokens.Length > 1) { Int32.TryParse(tokens[1].ToString(), out gameId); } } if (gameId == 0) { logger.WriteVerbose("Could not find game ID"); MessageBoxViewModel.ShowMessage("Could not find game id"); return; } if (!usingBackup) { AddRecentFile(filename); } logger.WriteVerbose("Game ID: " + gameId); var gameTitle = expressionGroup.Comments[0].Value.Substring(2).Trim(); GameViewModel viewModel = null; foreach (var directory in ServiceRepository.Instance.FindService <ISettings>().DataDirectories) { var notesFile = Path.Combine(directory, gameId + "-Notes2.txt"); if (File.Exists(notesFile)) { logger.WriteVerbose("Found code notes in " + directory); viewModel = new GameViewModel(gameId, gameTitle, directory.ToString()); } else { notesFile = Path.Combine(directory, gameId + "-Notes.json"); if (File.Exists(notesFile)) { logger.WriteVerbose("Found code notes in " + directory); viewModel = new GameViewModel(gameId, gameTitle, directory.ToString()); } } } if (viewModel == null) { logger.WriteVerbose("Could not find code notes"); MessageBoxViewModel.ShowMessage("Could not locate notes file for game " + gameId); viewModel = new GameViewModel(gameId, gameTitle); } var existingViewModel = Game as GameViewModel; // if we're just refreshing the current game script, only update the script content, // which will be reprocessed and update the editor list. If it's not the same script, // or notes have changed, use the new view model. if (existingViewModel != null && existingViewModel.GameId == viewModel.GameId && existingViewModel.Script.Filename == filename && existingViewModel.Notes.Count == viewModel.Notes.Count) { existingViewModel.Script.SetContent(content); viewModel = existingViewModel; existingViewModel.SelectedEditor = Game.Editors.FirstOrDefault(e => e.Title == selectedEditor); existingViewModel.Script.Editor.MoveCursorTo(line, column, Jamiras.ViewModels.CodeEditor.CodeEditorViewModel.MoveCursorFlags.None); } else { if (usingBackup) { viewModel.Script.Filename = Path.GetFileName(filename); var title = viewModel.Title + " (from backup)"; viewModel.SetValue(GameViewModel.TitleProperty, title); } else { viewModel.Script.Filename = filename; } viewModel.Script.SetContent(content); Game = viewModel; } if (viewModel.Script.Editor.ErrorsToolWindow.References.Count > 0) { viewModel.Script.Editor.ErrorsToolWindow.IsVisible = true; } }
internal void LoadGameFromServer(string gamePage, List <AchievementStats> achievementStats, List <UserStats> userStats) { NumberOfPlayers = 0; // reset so it gets updated from server data AchievementStats mostWon, leastWon; TotalPoints = LoadAchievementStatsFromServer(gamePage, achievementStats, out mostWon, out leastWon); var masteryPoints = (TotalPoints * 2).ToString(); var masters = GetMastersFromServer(gamePage, masteryPoints); Progress.Label = "Fetching user stats"; Progress.Reset(achievementStats.Count); achievementStats.Sort((l, r) => { var diff = r.EarnedHardcoreBy - l.EarnedHardcoreBy; if (diff == 0) { diff = String.Compare(l.Title, r.Title, StringComparison.OrdinalIgnoreCase); } return(diff); }); var possibleMasters = new List <string>(); var nonHardcoreUsers = new List <string>(); foreach (var achievement in achievementStats) { var achievementPage = RAWebCache.Instance.GetAchievementPage(achievement.Id); if (achievementPage != null) { var tokenizer = Tokenizer.CreateTokenizer(achievementPage); tokenizer.ReadTo("<h3>Winners</h3>"); // NOTE: this only lists the ~50 most recent unlocks! For games with more than 50 users who have mastered it, the oldest may be missed! do { var front = tokenizer.ReadTo("<a href='/user/"); if (tokenizer.NextChar == '\0') { break; } if (front.Contains("<div id=\"rightcontainer\">")) { break; } tokenizer.ReadTo("'>"); tokenizer.Advance(2); // skip user image, we'll get the name from the text var user = tokenizer.ReadTo("</a>"); if (user.StartsWith("<img")) { continue; } var mid = tokenizer.ReadTo("<small>"); if (mid.Contains("Hardcore!")) { tokenizer.Advance(7); var when = tokenizer.ReadTo("</small>"); var date = DateTime.Parse(when.ToString()); date = DateTime.SpecifyKind(date, DateTimeKind.Utc); var stats = new UserStats { User = user.ToString() }; var index = userStats.BinarySearch(stats, stats); if (index < 0) { userStats.Insert(~index, stats); } else { stats = userStats[index]; } stats.Achievements[achievement.Id] = date; if (ReferenceEquals(achievement, leastWon)) { if (!masters.Contains(stats.User)) { possibleMasters.Add(stats.User); } } } else { if (!nonHardcoreUsers.Contains(user.ToString())) { nonHardcoreUsers.Add(user.ToString()); } } } while (true); } Progress.Current++; } // if more than 50 people have earned achievements, people who mastered the game early may no longer display // in the individual pages. fetch mastery data by user if (mostWon == null || mostWon.EarnedBy <= 50) { HardcoreMasteredUserCountEstimated = false; } else { HardcoreMasteredUserCountEstimated = (leastWon.EarnedBy > 50); bool incompleteData = false; possibleMasters.AddRange(masters); Progress.Reset(possibleMasters.Count); foreach (var user in possibleMasters) { Progress.Current++; var stats = new UserStats { User = user }; var index = userStats.BinarySearch(stats, stats); if (index < 0) { userStats.Insert(~index, stats); } else { stats = userStats[index]; if (stats.Achievements.Count >= achievementStats.Count) { continue; } } if (!incompleteData && !MergeUserGameMastery(stats)) { incompleteData = true; } stats.Incomplete = incompleteData; } if (incompleteData) { _backgroundWorkerService.InvokeOnUiThread(() => { var settings = ServiceRepository.Instance.FindService <ISettings>(); if (String.IsNullOrEmpty(settings.ApiKey)) { MessageBoxViewModel.ShowMessage("Data is limited without an ApiKey in the ini file."); } else { MessageBoxViewModel.ShowMessage("Failed to fetch mastery information. Please make sure the ApiKey value is up to date in your ini file."); } }); } } NonHardcoreUserCount = nonHardcoreUsers.Count; WriteGameStats(_gameName, achievementStats, userStats); }